diff --git a/.github/Dockerfile b/.github/Dockerfile index 3ba5466c30b8..e9b449421858 100644 --- a/.github/Dockerfile +++ b/.github/Dockerfile @@ -22,5 +22,5 @@ RUN apt-get update && \ # Install sbt ENV SBT_HOME /usr/local/sbt ENV PATH ${SBT_HOME}/bin:${PATH} -ENV SBT_VERSION 1.10.5 +ENV SBT_VERSION 1.10.7 RUN curl -sL "https://github.com/sbt/sbt/releases/download/v$SBT_VERSION/sbt-$SBT_VERSION.tgz" | gunzip | tar -x -C /usr/local \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bc5648aa7626..0bb2943f581f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -80,7 +80,7 @@ jobs: ## Workaround for https://github.com/actions/runner/issues/2033 (See https://github.com/scala/scala3/pull/19720) - name: Reset existing repo run: | - git config --global --add safe.directory /__w/scala3/scala3 + git config --global --add safe.directory $GITHUB_WORKSPACE git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/scala/scala3" && git reset --hard FETCH_HEAD || true - name: Checkout cleanup script @@ -133,7 +133,7 @@ jobs: - name: Reset existing repo run: | - git config --global --add safe.directory /__w/scala3/scala3 + git config --global --add safe.directory $GITHUB_WORKSPACE git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/scala/scala3" && git reset --hard FETCH_HEAD || true - name: Checkout cleanup script @@ -190,7 +190,7 @@ jobs: - name: Reset existing repo run: | - git config --global --add safe.directory /__w/scala3/scala3 + git config --global --add safe.directory $GITHUB_WORKSPACE git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/scala/scala3" && git reset --hard FETCH_HEAD || true - name: Checkout cleanup script @@ -229,7 +229,7 @@ jobs: - name: Reset existing repo shell: cmd run: | - git config --global --add safe.directory /__w/scala3/scala3 + git config --global --add safe.directory $GITHUB_WORKSPACE git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/scala/scala3" && git reset --hard FETCH_HEAD || true - name: Git Checkout @@ -273,7 +273,7 @@ jobs: - name: Reset existing repo shell: cmd run: | - git config --global --add safe.directory /__w/scala3/scala3 + git config --global --add safe.directory $GITHUB_WORKSPACE git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/scala/scala3" && git reset --hard FETCH_HEAD || true - name: Git Checkout @@ -319,7 +319,7 @@ jobs: - name: Reset existing repo run: | - git config --global --add safe.directory /__w/scala3/scala3 + git config --global --add safe.directory $GITHUB_WORKSPACE git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/scala/scala3" && git reset --hard FETCH_HEAD || true - name: Checkout cleanup script @@ -374,7 +374,7 @@ jobs: 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 + git config --global --add safe.directory $GITHUB_WORKSPACE git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/scala/scala3" && git reset --hard FETCH_HEAD || true - name: Checkout cleanup script @@ -391,7 +391,7 @@ jobs: - name: Test run: | - git config --global --add safe.directory /__w/scala3/scala3 + git config --global --add safe.directory $GITHUB_WORKSPACE git submodule sync git submodule update --init --recursive --jobs 7 ./project/scripts/sbt "community-build/testOnly dotty.communitybuild.CommunityBuildTestA" @@ -431,7 +431,7 @@ jobs: 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 + git config --global --add safe.directory $GITHUB_WORKSPACE git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/scala/scala3" && git reset --hard FETCH_HEAD || true - name: Checkout cleanup script @@ -448,7 +448,7 @@ jobs: - name: Test run: | - git config --global --add safe.directory /__w/scala3/scala3 + git config --global --add safe.directory $GITHUB_WORKSPACE git submodule sync git submodule update --init --recursive --jobs 7 ./project/scripts/sbt "community-build/testOnly dotty.communitybuild.CommunityBuildTestB" @@ -488,7 +488,7 @@ jobs: 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 + git config --global --add safe.directory $GITHUB_WORKSPACE git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/scala/scala3" && git reset --hard FETCH_HEAD || true - name: Checkout cleanup script @@ -505,7 +505,7 @@ jobs: - name: Test run: | - git config --global --add safe.directory /__w/scala3/scala3 + git config --global --add safe.directory $GITHUB_WORKSPACE git submodule sync git submodule update --init --recursive --jobs 7 ./project/scripts/sbt "community-build/testOnly dotty.communitybuild.CommunityBuildTestC" @@ -541,7 +541,7 @@ jobs: - name: Reset existing repo run: | - git config --global --add safe.directory /__w/scala3/scala3 + git config --global --add safe.directory $GITHUB_WORKSPACE git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/scala/scala3" && git reset --hard FETCH_HEAD || true - name: Checkout cleanup script @@ -593,7 +593,7 @@ jobs: - name: Reset existing repo run: | - git config --global --add safe.directory /__w/scala3/scala3 + git config --global --add safe.directory $GITHUB_WORKSPACE git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/scala/scala3" && git reset --hard FETCH_HEAD || true - name: Checkout cleanup script @@ -630,7 +630,10 @@ jobs: - ${{ github.workspace }}/../../cache/general:/root/.cache strategy: matrix: - branch: [main, lts-3.3] + series: [ + {repository: scala/scala3, branch: main}, # Scala Next nightly + {repository: scala/scala3-lts, branch: lts-3.3} # Scala LTS nightly + ] needs: [test_non_bootstrapped, test, mima, community_build_a, community_build_b, community_build_c, test_sbt, test_java8] if: "(github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.repository == 'scala/scala3'" env: @@ -648,7 +651,7 @@ jobs: 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 + git config --global --add safe.directory $GITHUB_WORKSPACE git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/scala/scala3" && git reset --hard FETCH_HEAD || true - name: Checkout cleanup script @@ -660,7 +663,8 @@ jobs: - name: Git Checkout uses: actions/checkout@v4 with: - ref: ${{ matrix.branch }} + repository: ${{ matrix.series.repository }} + ref: ${{ matrix.series.branch }} - name: Add SBT proxy repositories run: cp -vf .github/workflows/repositories /root/.sbt/ ; true @@ -706,7 +710,7 @@ jobs: steps: - name: Reset existing repo run: | - git config --global --add safe.directory /__w/scala3/scala3 + git config --global --add safe.directory $GITHUB_WORKSPACE git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/scala/scala3" && git reset --hard FETCH_HEAD || true - name: Checkout cleanup script @@ -723,7 +727,7 @@ jobs: - name: Generate Website run: | - git config --global --add safe.directory /__w/scala3/scala3 + git config --global --add safe.directory $GITHUB_WORKSPACE ./project/scripts/genDocs -doc-snapshot - name: Deploy Website to https://dotty.epfl.ch @@ -764,7 +768,7 @@ jobs: 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 + git config --global --add safe.directory $GITHUB_WORKSPACE git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/scala/scala3" && git reset --hard FETCH_HEAD || true - name: Checkout cleanup script @@ -832,9 +836,10 @@ jobs: sha256sum "${msiInstaller}" > "${msiInstaller}.sha256" - name: Install GH CLI - uses: dev-hanz-ops/install-gh-cli-action@v0.2.0 + uses: dev-hanz-ops/install-gh-cli-action@v0.2.1 with: gh-cli-version: 2.59.0 + # Create the GitHub release - name: Create GitHub Release env: @@ -843,7 +848,7 @@ jobs: run: | # We need to config safe.directory in every step that might reference git # It is not persisted between steps - git config --global --add safe.directory /__w/scala3/scala3 + git config --global --add safe.directory $GITHUB_WORKSPACE gh release create \ --draft \ --title "${{ env.RELEASE_TAG }}" \ diff --git a/.github/workflows/lts-backport.yaml b/.github/workflows/lts-backport.yaml index b26075c180e9..f8b930707c69 100644 --- a/.github/workflows/lts-backport.yaml +++ b/.github/workflows/lts-backport.yaml @@ -15,7 +15,7 @@ jobs: with: fetch-depth: 0 - uses: coursier/cache-action@v6 - - uses: VirtusLab/scala-cli-setup@v1.5.4 + - uses: VirtusLab/scala-cli-setup@v1.7.1 - run: scala-cli ./project/scripts/addToBackportingProject.scala -- ${{ github.sha }} env: GRAPHQL_API_TOKEN: ${{ secrets.GRAPHQL_API_TOKEN }} diff --git a/.github/workflows/publish-sdkman.yml b/.github/workflows/publish-sdkman.yml index e47c95d01f19..c0f29db7f0b0 100644 --- a/.github/workflows/publish-sdkman.yml +++ b/.github/workflows/publish-sdkman.yml @@ -46,7 +46,7 @@ jobs: - platform: WINDOWS_64 archive : 'scala3-${{ inputs.version }}-x86_64-pc-win32.zip' steps: - - uses: sdkman/sdkman-release-action@1f2d4209b4f5a38721d4ae20014ea8e1689d869e + - uses: sdkman/sdkman-release-action@a60691d56279724b4c9ff0399c0ae21d641ab75e with: CONSUMER-KEY : ${{ secrets.CONSUMER-KEY }} CONSUMER-TOKEN : ${{ secrets.CONSUMER-TOKEN }} diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index ab5f2b3d2fe1..cf13174e1f8e 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -49,7 +49,7 @@ jobs: env: USER_FOR_TEST: ${{ secrets.SPEC_DEPLOY_USER }} if: ${{ env.USER_FOR_TEST != '' }} - uses: burnett01/rsync-deployments@7.0.1 + uses: burnett01/rsync-deployments@7.0.2 with: switches: -rzv path: docs/_spec/_site/ diff --git a/.github/workflows/launchers.yml b/.github/workflows/test-launchers.yml similarity index 98% rename from .github/workflows/launchers.yml rename to .github/workflows/test-launchers.yml index 4ee07e4bfcc9..25bd5a4bf42f 100644 --- a/.github/workflows/launchers.yml +++ b/.github/workflows/test-launchers.yml @@ -28,8 +28,7 @@ jobs: linux-aarch64: name: Deploy and Test on Linux ARM64 architecture - runs-on: macos-latest - if: ${{ false }} + runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@v4 - name: Set up JDK 17 diff --git a/.gitignore b/.gitignore index 7ee4342439be..aa5ce1d5baa0 100644 --- a/.gitignore +++ b/.gitignore @@ -62,7 +62,6 @@ testlogs/ # Put local stuff here local/ -compiler/test/debug/Gen.jar /bin/.cp diff --git a/.gitmodules b/.gitmodules index 8f87e992013a..6c3e826c4fa5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,9 +19,6 @@ [submodule "community-build/community-projects/fastparse"] path = community-build/community-projects/fastparse url = https://github.com/dotty-staging/fastparse -[submodule "community-build/community-projects/stdLib213"] - path = community-build/community-projects/stdLib213 - url = https://github.com/dotty-staging/scala213 [submodule "community-build/community-projects/sourcecode"] path = community-build/community-projects/sourcecode url = https://github.com/dotty-staging/sourcecode diff --git a/bin/replQ b/bin/replQ new file mode 100755 index 000000000000..5d0b84c4a229 --- /dev/null +++ b/bin/replQ @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" >& /dev/null && pwd)/.." +. $ROOT/bin/commonQ + +java -Dscala.usejavacp=true -cp $cp dotty.tools.repl.Main -usejavacp "$@" diff --git a/changelogs/3.7.0-RC1.md b/changelogs/3.7.0-RC1.md new file mode 100644 index 000000000000..e895cfc324e0 --- /dev/null +++ b/changelogs/3.7.0-RC1.md @@ -0,0 +1,266 @@ +# Highlights of the release + +- Add `@scala.annotation.internal.preview` annotation and `-preview` flag. [#22317](https://github.com/scala/scala3/pull/22317) +- Make SIP-52 `@publicInBinary` a stable feature in 3.7 [#22591](https://github.com/scala/scala3/pull/22591) +- Make SIP-58 - `Named Tuples` a stable feature in 3.7 [#22753](https://github.com/scala/scala3/pull/22753) +- Make SIP-62 - `Better Fors` a preview feature in 3.7 [#22776](https://github.com/scala/scala3/pull/22776) +- Implement SIP-61 `@unroll` annotation as experimental [#21693](https://github.com/scala/scala3/pull/21693) +- Upgrade Scala 2 Standard Library to 2.13.16 [#22386](https://github.com/scala/scala3/pull/22386) +- Upgrade Scala.js to 1.18.1. [#22397](https://github.com/scala/scala3/pull/22397) +- Upgrade Scala CLI to 1.7.0 [#22735](https://github.com/scala/scala3/pull/22735) +- Expression Compiler is now part of Scala 3 compiler [#22597](https://github.com/scala/scala3/pull/22597) +- Quotes API: Added `apply` methods to import selectors [#22457](https://github.com/scala/scala3/pull/22457) +- Quotes API: Implement experimental `summonIgnoring` method [#22417](https://github.com/scala/scala3/pull/22417) +- Quotes API: Add class parameters, flags, and privateWithin and annotations to experimental `newClass` methods [#21880](https://github.com/scala/scala3/pull/21880) +- Experimental: Capture Calculus - Inferring `tracked` modifier [#21628](https://github.com/scala/scala3/pull/21628) +- Presentation Compiler: Show inferred type on holes in hover [#21423](https://github.com/scala/scala3/pull/21423) +- JVM Backend: Fix #15736 blocking Scala 3 on Android [#22632](https://github.com/scala/scala3/pull/22632) +- REPL: add flag to quit after evaluating init script [#22636](https://github.com/scala/scala3/pull/22636) +- REPL: implement :jar (deprecate :require) [#22343](https://github.com/scala/scala3/pull/22343) +- Linting: Improvements to -Wunused and -Wconf [#20894](https://github.com/scala/scala3/pull/20894) +- Implicit parameters warn at call site without `using` keyword [#22441](https://github.com/scala/scala3/pull/22441) +- Minimal support for dependent case classes [#21698](https://github.com/scala/scala3/pull/21698) + +# Other changes and fixes + +## Annotations + +- Lift arguments of explicitly constructed annotations [#22553](https://github.com/scala/scala3/pull/22553) +- Fix copy of annotation on `@main` methods [#22582](https://github.com/scala/scala3/pull/22582) +- `@publicInBinary` has now effect on secondary constructors [#22630](https://github.com/scala/scala3/pull/22630) +- Fix mapping of annotations [#22407](https://github.com/scala/scala3/pull/22407) + +## Backend: Scala.js + +- Emit `js.NewArray` IR nodes when possible. [#22446](https://github.com/scala/scala3/pull/22446) + +## Classpath + +- Fix empty ClassPath attribute in one or more classpath jars causes crash [#22462](https://github.com/scala/scala3/pull/22462) + +## Documentation + +- Improve the usage of inclusive language [#22360](https://github.com/scala/scala3/pull/22360) + +## Erasure + +- Handle type aliases in contextFunctionResultTypeAfter [#21517](https://github.com/scala/scala3/pull/21517) +- Align erasure of `Array[Nothing]` and `Array[Null]` with Scala 2 [#22517](https://github.com/scala/scala3/pull/22517) + +## Desugering + +- Under `betterFors` don't drop the trailing `map` if it would result in a different type (also drop `_ => ()`) [#22619](https://github.com/scala/scala3/pull/22619) + +## Experimental: Capture Checking + +- Canonicalize capture variable subtype comparisons [#22299](https://github.com/scala/scala3/pull/22299) +- Permit Capture Refs for Uninitialized Type and Term Params in BiTypeMap [#22451](https://github.com/scala/scala3/pull/22451) +- Fix maximal capability handling and expand aliases [#22341](https://github.com/scala/scala3/pull/22341) + +## Experimental: Modularity + +- Widen skolem types when adding parent refinements [#22488](https://github.com/scala/scala3/pull/22488) + +## Experimental: Global Initialization Checker + +- Refactor the abstract domain of global init checker to compile http4s [#22179](https://github.com/scala/scala3/pull/22179) +- Fix global init checking crash when using a value defined in by-name closure [#22625](https://github.com/scala/scala3/pull/22625) + +## Experimentals + +- Expand value references to packages to their underlying package objects [#22011](https://github.com/scala/scala3/pull/22011) + +## Implicits + +- Restrict implicit args to using [#22458](https://github.com/scala/scala3/pull/22458) + +## Linting + +- Suppress spurious Suppression [#22383](https://github.com/scala/scala3/pull/22383) +- CheckUnused checks span.exists before testing its parts [#22504](https://github.com/scala/scala3/pull/22504) +- Don't warn retainedBody [#22510](https://github.com/scala/scala3/pull/22510) +- Handle Typeable [#22663](https://github.com/scala/scala3/pull/22663) +- Nowarn public implicit val class params [#22664](https://github.com/scala/scala3/pull/22664) +- Exclude synthetic this.m, Any.m from import lookup [#22695](https://github.com/scala/scala3/pull/22695) +- Warn unused member of anonymous class [#22729](https://github.com/scala/scala3/pull/22729) +- Ignore params to default arg getters [#22749](https://github.com/scala/scala3/pull/22749) +- Lazy val def member is pattern var [#22750](https://github.com/scala/scala3/pull/22750) +- Restore resolving prefixes of implicit Ident [#22751](https://github.com/scala/scala3/pull/22751) +- No warning for parameter of overriding method [#22757](https://github.com/scala/scala3/pull/22757) +- Dealias before checking for member in lint [#22708](https://github.com/scala/scala3/pull/22708) +- Warn on bad extensions of aliases [#22362](https://github.com/scala/scala3/pull/22362) +- Warn universal extensions on opaque types [#22502](https://github.com/scala/scala3/pull/22502) +- Discourage default arg for extension receiver [#22492](https://github.com/scala/scala3/pull/22492) +- Rename on import is never wildcard [#22712](https://github.com/scala/scala3/pull/22712) +- Collect nowarn symbols instead of skipping them [#22766](https://github.com/scala/scala3/pull/22766) + +## Match Types + +- Handle NoType in TypeComparer.disjointnessBoundary [#21520](https://github.com/scala/scala3/pull/21520) + +## Named Tuples + +- Special case NamedTuple.From for arguments derived from Tuple [#22449](https://github.com/scala/scala3/pull/22449) +- Generate mirrors for named tuples [#22469](https://github.com/scala/scala3/pull/22469) + +## Opaque Types + +- Fix stack overflow errors when generating opaque type proxies [#22479](https://github.com/scala/scala3/pull/22479) +- Fix inline proxy generation for opaque types referencing other opaque types [#22381](https://github.com/scala/scala3/pull/22381) +- Fix opaque types leaking rhs when inlined and found in type params (and a related stale symbol issue) [#22655](https://github.com/scala/scala3/pull/22655) + +## Overloading + +- Make overload pruning based on result types less aggressive [#21744](https://github.com/scala/scala3/pull/21744) + +## Parser + +- Fix annotations being not expected in the middle of an array type by java parser [#22391](https://github.com/scala/scala3/pull/22391) +- No outdent at eof [#22435](https://github.com/scala/scala3/pull/22435) +- Allow observing an indent after conditional [#22611](https://github.com/scala/scala3/pull/22611) +- Correctly detect colon lambda eol indent for optional brace of argument [#22477](https://github.com/scala/scala3/pull/22477) + +## Pattern Matching + +- Avoid crash in uninhab check in Space [#22601](https://github.com/scala/scala3/pull/22601) +- Account for named tuples in space subtraction [#22658](https://github.com/scala/scala3/pull/22658) +- Check exhaustivity of any case class [#22604](https://github.com/scala/scala3/pull/22604) + +## Presentation Compiler + +- Add enum type param support in sourceSymbol [#18603](https://github.com/scala/scala3/pull/18603) +- Map name position to desugared version of named context bound [#22374](https://github.com/scala/scala3/pull/22374) +- Hover and go to definition for named tuples [#22202](https://github.com/scala/scala3/pull/22202) +- Completions: do not complete package [#20532](https://github.com/scala/scala3/pull/20532) +- Print parens for single method argument only if a direct tuple type [#21510](https://github.com/scala/scala3/pull/21510) +- Improvement: use heuristic to figure out `nameSpan` if `pointDelta` too big [#22484](https://github.com/scala/scala3/pull/22484) +- Fix inferredTypeEdits for symbols [#22485](https://github.com/scala/scala3/pull/22485) +- Fix: Only fallback to the definition of a synthetic valdef if it is zero extent [#22551](https://github.com/scala/scala3/pull/22551) +- Better LSP completions inside of backticks [#22555](https://github.com/scala/scala3/pull/22555) +- Don't search for members in pc info when irrelevant [#22674](https://github.com/scala/scala3/pull/22674) +- Backport from Metals [#22426](https://github.com/scala/scala3/pull/22426) +- Backport from Metals [#22491](https://github.com/scala/scala3/pull/22491) +- Backport from Metals [#22665](https://github.com/scala/scala3/pull/22665) + +## Runner + +- Upgrade Scala CLI to [1.7.0 highlights](https://github.com/VirtusLab/scala-cli/releases/tag/v1.7.0) + - Switch to scalameta/scalafmt images of scalafmt 3.9.1+ [#3502](https://github.com/VirtusLab/scala-cli/pull/3502) + - Support the `--test` command line option for `run` subcommand [#3519](https://github.com/VirtusLab/scala-cli/pull/3519) + - Support the `--test` command line option for `package` subcommand [#3519](https://github.com/VirtusLab/scala-cli/pull/3519) + - Detect objects with main class in scripts [#3479](https://github.com/VirtusLab/scala-cli/pull/3479) + - Support for Scala.js 1.18.2 [#3454](https://github.com/VirtusLab/scala-cli/pull/3454) + - Support for Scala Native 0.5.7 [#3527](https://github.com/VirtusLab/scala-cli/pull/3527) + - Add support for running a main method from the test scope [#3502](https://github.com/VirtusLab/scala-cli/pull/3502) + +## Quotes + +- Add a check for correct Array shape in quotes.reflect.ClassOfConstant [#22033](https://github.com/scala/scala3/pull/22033) +- Fix issue with static `this` references erroring in quoted code [#22618](https://github.com/scala/scala3/pull/22618) +- Fix #21721: make case TypeBlock(_,_) not match non-type Block [#21722](https://github.com/scala/scala3/pull/21722) +- Make Ref.apply() return trees usable in the largest scope possible [#22240](https://github.com/scala/scala3/pull/22240) +- Make sure Block does not incorrectly match a TypeBlock [#22716](https://github.com/scala/scala3/pull/22716) +- Do not approximate prefixes when using memberType in reflect API [#22448](https://github.com/scala/scala3/pull/22448) +- Bring back pattern match exhaustivity checking for macros [#22622](https://github.com/scala/scala3/pull/22622) + +## REPL + +- REPL: JLine 3.29.0 (was 3.27.1) [#22679](https://github.com/scala/scala3/pull/22679) +- Repl: emit warning for the `:sh` command [#22694](https://github.com/scala/scala3/pull/22694) +- Add warning for :kind command [#22572](https://github.com/scala/scala3/pull/22572) + +## Reporting + +- Filter help renders box border [#22434](https://github.com/scala/scala3/pull/22434) +- Register nowarn when inlining [#22682](https://github.com/scala/scala3/pull/22682) +- Rule out exports of member of the current class [#22545](https://github.com/scala/scala3/pull/22545) + +## Scaladoc + +- Render `@deprecated` correctly even when named arguments weren't used [#21925](https://github.com/scala/scala3/pull/21925) +- Remove DRI from Scaladoc warnings [#22330](https://github.com/scala/scala3/pull/22330) + +## SemanticDB + +- Don't add `()` to semanticdb symbol for java variables [#22573](https://github.com/scala/scala3/pull/22573) +- Fix compiler crash when using betasty with missing java classfiles [#22599](https://github.com/scala/scala3/pull/22599) + +## Transform + +- Check only stable qual for import prefix [#22633](https://github.com/scala/scala3/pull/22633) +- Treat static vals as enclosures in lambdalift [#22452](https://github.com/scala/scala3/pull/22452) +- Record calls to constructors in lambdaLift [#22487](https://github.com/scala/scala3/pull/22487) +- Only check logicalOwners for methods, and not for classes, when looking for proxies [#22356](https://github.com/scala/scala3/pull/22356) +- Add error-checking when fetching rhs of trees from TASTy [#22565](https://github.com/scala/scala3/pull/22565) + +## Typer + +- Root of Java select must be class or rooted package [#21800](https://github.com/scala/scala3/pull/21800) +- Check if a prefix is valid before selecting from a type [#22368](https://github.com/scala/scala3/pull/22368) +- Preserve hard unions in widenSingletons [#22369](https://github.com/scala/scala3/pull/22369) +- Constructor proxy is restricted if class is protected [#22563](https://github.com/scala/scala3/pull/22563) +- Constructor companion gets privateWithin [#22627](https://github.com/scala/scala3/pull/22627) +- Revert lambda cleanup [#22697](https://github.com/scala/scala3/pull/22697) +- Avoid infinite recursion when looking for suggestions [#22361](https://github.com/scala/scala3/pull/22361) +- Fix cyclic check, regardless of definition order [#22342](https://github.com/scala/scala3/pull/22342) +- Avoid inf recursion in provablyDisjointClasses [#22489](https://github.com/scala/scala3/pull/22489) + +## Value Classes + +- Allow private members when computing the denotation of a NamedType [#22549](https://github.com/scala/scala3/pull/22549) + +## Other changes + +- Remove locale dependent FileSystemException check [#21633](https://github.com/scala/scala3/pull/21633) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.6.4..3.7.0-RC1` these are: + +``` + 59 Martin Odersky + 43 Som Snytt + 33 Adrien Piquerez + 32 Hamza Remmal + 21 Wojciech Mazur + 19 aherlihy + 19 kasiaMarek + 15 Jan Chyb + 13 Dale Wijnand + 11 Kacper Korban + 10 EnzeXing + 7 Guillaume Martres + 7 Matt Bovel + 7 Oliver Bračevac + 7 noti0na1 + 6 Sébastien Doeraene + 5 HarrisL2 + 5 Jamie Thompson + 5 dependabot[bot] + 4 Joel Wilsson + 4 Seth Tisue + 3 Piotr Chabelski + 3 Roman Janusz + 3 anna herlihy + 2 David Hua + 1 Alec Theriault + 1 Daisy Li + 1 Daniel Thoma + 1 Dmitrii Naumenko + 1 Felix Herrmann + 1 He-Pin(kerr) + 1 João Ferreira + 1 Jędrzej Rochala + 1 Katarzyna Marek + 1 Kenji Yoshida + 1 Niklas Fiekas + 1 Rocco Mathijn Andela + 1 Vadim Chelyshov + 1 Yichen Xu + 1 adpi2 + 1 fan-tom + 1 philwalk + 1 rochala +``` diff --git a/changelogs/3.7.0-RC2.md b/changelogs/3.7.0-RC2.md new file mode 100644 index 000000000000..d97aaf5c9812 --- /dev/null +++ b/changelogs/3.7.0-RC2.md @@ -0,0 +1,27 @@ +# Backported changes + +- Backport "Check trailing blank line at EOF for OUTDENT" to 3.7.0 [#22942](https://github.com/scala/scala3/pull/22942) +- Backport "Fail compilation if multiple conflicting top-level private defs/vals are in the same package" to 3.7 [#22932](https://github.com/scala/scala3/pull/22932) +- Backport "Deprecate `Yno-kind-polymorphism`" to 3.7 [#22931](https://github.com/scala/scala3/pull/22931) +- Backport "Revert unconditional lint of Inlined expansion" to 3.7 [#22930](https://github.com/scala/scala3/pull/22930) +- Backport "Bump Scala CLI to v1.7.1 (was v1.7.0)" to 3.7 [#22929](https://github.com/scala/scala3/pull/22929) +- Backport "Fix #22794: Emit the definition of Arrays.newArray even though it's a primitive." to 3.7.0 [#22801](https://github.com/scala/scala3/pull/22801) + +# Reverted changes + +- Revert "Make overload pruning based on result types less aggressive" in 3.7.0 [#22940](https://github.com/scala/scala3/pull/22940) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.0-RC1..3.7.0-RC2` these are: + +``` + 4 Som Snytt + 4 Wojciech Mazur + 1 Jan Chyb + 1 Piotr Chabelski + 1 Sébastien Doeraene + 1 Yichen Xu +``` diff --git a/changelogs/3.7.0-RC3.md b/changelogs/3.7.0-RC3.md new file mode 100644 index 000000000000..3408fbf56d59 --- /dev/null +++ b/changelogs/3.7.0-RC3.md @@ -0,0 +1,16 @@ +# Backported changes + +- Backport "Two fixes to NamedTuple pattern matching" to 3.7.0 [#22995](https://github.com/scala/scala3/pull/22995) +- Backport "changes to scala.caps in preparation to make Capability stable" to 3.7.0 [#22967](https://github.com/scala/scala3/pull/22967) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.0-RC2..3.7.0-RC3` these are: + +``` + 4 Martin Odersky + 2 Wojciech Mazur + 1 Natsu Kagami +``` diff --git a/changelogs/3.7.0-RC4.md b/changelogs/3.7.0-RC4.md new file mode 100644 index 000000000000..2b39d025541f --- /dev/null +++ b/changelogs/3.7.0-RC4.md @@ -0,0 +1,15 @@ +# Backported changes + +- Backport "Upgrade to Scala.js 1.19.0." to 3.7 [#23035](https://github.com/scala/scala3/pull/23035) + + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.0-RC3..3.7.0-RC4` these are: + +``` + 2 Sébastien Doeraene + 1 Tomasz Godzik +``` diff --git a/changelogs/3.7.0.md b/changelogs/3.7.0.md new file mode 100644 index 000000000000..8cfe9c75116f --- /dev/null +++ b/changelogs/3.7.0.md @@ -0,0 +1,279 @@ +# Highlights of the release + +- Add `@scala.annotation.internal.preview` annotation and `-preview` flag. [#22317](https://github.com/scala/scala3/pull/22317) +- Make SIP-52 `@publicInBinary` a stable feature in 3.7 [#22591](https://github.com/scala/scala3/pull/22591) +- Make SIP-58 - `Named Tuples` a stable feature in 3.7 [#22753](https://github.com/scala/scala3/pull/22753) +- Make SIP-62 - `Better Fors` a preview feature in 3.7 [#22776](https://github.com/scala/scala3/pull/22776) +- Implement SIP-61 `@unroll` annotation as experimental [#21693](https://github.com/scala/scala3/pull/21693) +- Upgrade Scala 2 Standard Library to 2.13.16 [#22386](https://github.com/scala/scala3/pull/22386) +- Upgrade Scala.js to 1.19.0 [#23026](https://github.com/scala/scala3/pull/23026) +- Upgrade Scala CLI to 1.7.1 [#22843](https://github.com/scala/scala3/pull/22843) +- Expression Compiler is now part of Scala 3 compiler [#22597](https://github.com/scala/scala3/pull/22597) +- Quotes API: Added `apply` methods to import selectors [#22457](https://github.com/scala/scala3/pull/22457) +- Quotes API: Implement experimental `summonIgnoring` method [#22417](https://github.com/scala/scala3/pull/22417) +- Quotes API: Add class parameters, flags, and privateWithin and annotations to experimental `newClass` methods [#21880](https://github.com/scala/scala3/pull/21880) +- Experimental: Capture Calculus - Inferring `tracked` modifier [#21628](https://github.com/scala/scala3/pull/21628) +- Presentation Compiler: Show inferred type on holes in hover [#21423](https://github.com/scala/scala3/pull/21423) +- JVM Backend: Fix #15736 blocking Scala 3 on Android [#22632](https://github.com/scala/scala3/pull/22632) +- REPL: add flag to quit after evaluating init script [#22636](https://github.com/scala/scala3/pull/22636) +- REPL: implement :jar (deprecate :require) [#22343](https://github.com/scala/scala3/pull/22343) +- Linting: Improvements to -Wunused and -Wconf [#20894](https://github.com/scala/scala3/pull/20894) +- Implicit parameters warn at call site without `using` keyword [#22441](https://github.com/scala/scala3/pull/22441) +- Support for dependent case classes [#21698](https://github.com/scala/scala3/pull/21698) +- Deprecate `Yno-kind-polymorphism` [#22814](https://github.com/scala/scala3/pull/22814) + +# Other changes and fixes + +## Annotations + +- Lift arguments of explicitly constructed annotations [#22553](https://github.com/scala/scala3/pull/22553) +- Fix copy of annotation on `@main` methods [#22582](https://github.com/scala/scala3/pull/22582) +- `@publicInBinary` has now effect on secondary constructors [#22630](https://github.com/scala/scala3/pull/22630) +- Fix mapping of annotations [#22407](https://github.com/scala/scala3/pull/22407) + +## Backend: Scala.js + +- Emit `js.NewArray` IR nodes when possible. [#22446](https://github.com/scala/scala3/pull/22446) +- Fix #22794: Emit the definition of Arrays.newArray even though it's a primitive [#22797](https://github.com/scala/scala3/pull/22797) + +## Classpath + +- Fix empty ClassPath attribute in one or more classpath jars causes crash [#22462](https://github.com/scala/scala3/pull/22462) + +## Documentation + +- Improve the usage of inclusive language [#22360](https://github.com/scala/scala3/pull/22360) + +## Erasure + +- Handle type aliases in contextFunctionResultTypeAfter [#21517](https://github.com/scala/scala3/pull/21517) +- Align erasure of `Array[Nothing]` and `Array[Null]` with Scala 2 [#22517](https://github.com/scala/scala3/pull/22517) + +## Desugering + +- Under `betterFors` don't drop the trailing `map` if it would result in a different type (also drop `_ => ()`) [#22619](https://github.com/scala/scala3/pull/22619) + +## Experimental: Capture Checking + +- Canonicalize capture variable subtype comparisons [#22299](https://github.com/scala/scala3/pull/22299) +- Permit Capture Refs for Uninitialized Type and Term Params in BiTypeMap [#22451](https://github.com/scala/scala3/pull/22451) +- Fix maximal capability handling and expand aliases [#22341](https://github.com/scala/scala3/pull/22341) + +## Experimental: Modularity + +- Widen skolem types when adding parent refinements [#22488](https://github.com/scala/scala3/pull/22488) + +## Experimental: Global Initialization Checker + +- Refactor the abstract domain of global init checker to compile http4s [#22179](https://github.com/scala/scala3/pull/22179) +- Fix global init checking crash when using a value defined in by-name closure [#22625](https://github.com/scala/scala3/pull/22625) + +## Experimentals + +- Expand value references to packages to their underlying package objects [#22011](https://github.com/scala/scala3/pull/22011) + +## Implicits + +- Restrict implicit args to using [#22458](https://github.com/scala/scala3/pull/22458) + +## Linting + +- Suppress spurious Suppression [#22383](https://github.com/scala/scala3/pull/22383) +- CheckUnused checks span.exists before testing its parts [#22504](https://github.com/scala/scala3/pull/22504) +- Don't warn retainedBody [#22510](https://github.com/scala/scala3/pull/22510) +- Handle Typeable [#22663](https://github.com/scala/scala3/pull/22663) +- Nowarn public implicit val class params [#22664](https://github.com/scala/scala3/pull/22664) +- Exclude synthetic this.m, Any.m from import lookup [#22695](https://github.com/scala/scala3/pull/22695) +- Warn unused member of anonymous class [#22729](https://github.com/scala/scala3/pull/22729) +- Ignore params to default arg getters [#22749](https://github.com/scala/scala3/pull/22749) +- Lazy val def member is pattern var [#22750](https://github.com/scala/scala3/pull/22750) +- Restore resolving prefixes of implicit Ident [#22751](https://github.com/scala/scala3/pull/22751) +- No warning for parameter of overriding method [#22757](https://github.com/scala/scala3/pull/22757) +- Dealias before checking for member in lint [#22708](https://github.com/scala/scala3/pull/22708) +- Warn on bad extensions of aliases [#22362](https://github.com/scala/scala3/pull/22362) +- Warn universal extensions on opaque types [#22502](https://github.com/scala/scala3/pull/22502) +- Discourage default arg for extension receiver [#22492](https://github.com/scala/scala3/pull/22492) +- Rename on import is never wildcard [#22712](https://github.com/scala/scala3/pull/22712) +- Collect nowarn symbols instead of skipping them [#22766](https://github.com/scala/scala3/pull/22766) +- Revert unconditional lint of Inlined expansion [#22815](https://github.com/scala/scala3/pull/22815) + +## Match Types + +- Handle NoType in TypeComparer.disjointnessBoundary [#21520](https://github.com/scala/scala3/pull/21520) + +## Named Tuples + +- Special case NamedTuple.From for arguments derived from Tuple [#22449](https://github.com/scala/scala3/pull/22449) +- Generate mirrors for named tuples [#22469](https://github.com/scala/scala3/pull/22469) +- Two fixes to NamedTuple pattern matching [#22953](https://github.com/scala/scala3/pull/22953) + +## Opaque Types + +- Fix stack overflow errors when generating opaque type proxies [#22479](https://github.com/scala/scala3/pull/22479) +- Fix inline proxy generation for opaque types referencing other opaque types [#22381](https://github.com/scala/scala3/pull/22381) +- Fix opaque types leaking rhs when inlined and found in type params (and a related stale symbol issue) [#22655](https://github.com/scala/scala3/pull/22655) + +## Overloading + +- Make overload pruning based on result types less aggressive [#21744](https://github.com/scala/scala3/pull/21744) +- Fail compilation if multiple conflicting top-level private defs/vals are in the same package [#22759](https://github.com/scala/scala3/pull/22759) + +## Parser + +- Fix annotations being not expected in the middle of an array type by java parser [#22391](https://github.com/scala/scala3/pull/22391) +- No outdent at eof [#22435](https://github.com/scala/scala3/pull/22435) +- Allow observing an indent after conditional [#22611](https://github.com/scala/scala3/pull/22611) +- Correctly detect colon lambda eol indent for optional brace of argument [#22477](https://github.com/scala/scala3/pull/22477) + +## Pattern Matching + +- Avoid crash in uninhab check in Space [#22601](https://github.com/scala/scala3/pull/22601) +- Account for named tuples in space subtraction [#22658](https://github.com/scala/scala3/pull/22658) +- Check exhaustivity of any case class [#22604](https://github.com/scala/scala3/pull/22604) + +## Presentation Compiler + +- Add enum type param support in sourceSymbol [#18603](https://github.com/scala/scala3/pull/18603) +- Map name position to desugared version of named context bound [#22374](https://github.com/scala/scala3/pull/22374) +- Hover and go to definition for named tuples [#22202](https://github.com/scala/scala3/pull/22202) +- Completions: do not complete package [#20532](https://github.com/scala/scala3/pull/20532) +- Print parens for single method argument only if a direct tuple type [#21510](https://github.com/scala/scala3/pull/21510) +- Improvement: use heuristic to figure out `nameSpan` if `pointDelta` too big [#22484](https://github.com/scala/scala3/pull/22484) +- Fix inferredTypeEdits for symbols [#22485](https://github.com/scala/scala3/pull/22485) +- Fix: Only fallback to the definition of a synthetic valdef if it is zero extent [#22551](https://github.com/scala/scala3/pull/22551) +- Better LSP completions inside of backticks [#22555](https://github.com/scala/scala3/pull/22555) +- Don't search for members in pc info when irrelevant [#22674](https://github.com/scala/scala3/pull/22674) +- Backport from Metals [#22426](https://github.com/scala/scala3/pull/22426) +- Backport from Metals [#22491](https://github.com/scala/scala3/pull/22491) +- Backport from Metals [#22665](https://github.com/scala/scala3/pull/22665) + +## Runner + +- Upgrade Scala CLI to [1.7.1 highlights](https://github.com/VirtusLab/scala-cli/releases/tag/v1.7.1) + - Switch to scalameta/scalafmt images of scalafmt 3.9.1+ [#3502](https://github.com/VirtusLab/scala-cli/pull/3502) + - Support the `--test` command line option for `run` subcommand [#3519](https://github.com/VirtusLab/scala-cli/pull/3519) + - Support the `--test` command line option for `package` subcommand [#3519](https://github.com/VirtusLab/scala-cli/pull/3519) + - Detect objects with main class in scripts [#3479](https://github.com/VirtusLab/scala-cli/pull/3479) + - Support for Scala.js 1.18.2 [#3454](https://github.com/VirtusLab/scala-cli/pull/3454) + - Support for Scala Native 0.5.7 [#3527](https://github.com/VirtusLab/scala-cli/pull/3527) + - Add support for running a main method from the test scope [#3502](https://github.com/VirtusLab/scala-cli/pull/3502) + +## Quotes + +- Add a check for correct Array shape in quotes.reflect.ClassOfConstant [#22033](https://github.com/scala/scala3/pull/22033) +- Fix issue with static `this` references erroring in quoted code [#22618](https://github.com/scala/scala3/pull/22618) +- Fix #21721: make case TypeBlock(_,_) not match non-type Block [#21722](https://github.com/scala/scala3/pull/21722) +- Make Ref.apply() return trees usable in the largest scope possible [#22240](https://github.com/scala/scala3/pull/22240) +- Make sure Block does not incorrectly match a TypeBlock [#22716](https://github.com/scala/scala3/pull/22716) +- Do not approximate prefixes when using memberType in reflect API [#22448](https://github.com/scala/scala3/pull/22448) +- Bring back pattern match exhaustivity checking for macros [#22622](https://github.com/scala/scala3/pull/22622) + +## REPL + +- REPL: JLine 3.29.0 (was 3.27.1) [#22679](https://github.com/scala/scala3/pull/22679) +- Repl: emit warning for the `:sh` command [#22694](https://github.com/scala/scala3/pull/22694) +- Add warning for :kind command [#22572](https://github.com/scala/scala3/pull/22572) +- Check trailing blank line at EOF for OUTDENT [#22855](https://github.com/scala/scala3/pull/22855) + +## Reporting + +- Filter help renders box border [#22434](https://github.com/scala/scala3/pull/22434) +- Register nowarn when inlining [#22682](https://github.com/scala/scala3/pull/22682) +- Rule out exports of member of the current class [#22545](https://github.com/scala/scala3/pull/22545) + +## Standard Library + +- Changes in preparation to make `caps.Capability` stable [#22849](https://github.com/scala/scala3/pull/22849) +- Mitigate change in status of scala.caps [#22956](https://github.com/scala/scala3/pull/22956) + +## Scaladoc + +- Render `@deprecated` correctly even when named arguments weren't used [#21925](https://github.com/scala/scala3/pull/21925) +- Remove DRI from Scaladoc warnings [#22330](https://github.com/scala/scala3/pull/22330) + +## SemanticDB + +- Don't add `()` to semanticdb symbol for java variables [#22573](https://github.com/scala/scala3/pull/22573) +- Fix compiler crash when using betasty with missing java classfiles [#22599](https://github.com/scala/scala3/pull/22599) + +## Transform + +- Check only stable qual for import prefix [#22633](https://github.com/scala/scala3/pull/22633) +- Treat static vals as enclosures in lambdalift [#22452](https://github.com/scala/scala3/pull/22452) +- Record calls to constructors in lambdaLift [#22487](https://github.com/scala/scala3/pull/22487) +- Only check logicalOwners for methods, and not for classes, when looking for proxies [#22356](https://github.com/scala/scala3/pull/22356) +- Add error-checking when fetching rhs of trees from TASTy [#22565](https://github.com/scala/scala3/pull/22565) + +## Typer + +- Root of Java select must be class or rooted package [#21800](https://github.com/scala/scala3/pull/21800) +- Check if a prefix is valid before selecting from a type [#22368](https://github.com/scala/scala3/pull/22368) +- Preserve hard unions in widenSingletons [#22369](https://github.com/scala/scala3/pull/22369) +- Constructor proxy is restricted if class is protected [#22563](https://github.com/scala/scala3/pull/22563) +- Constructor companion gets privateWithin [#22627](https://github.com/scala/scala3/pull/22627) +- Revert lambda cleanup [#22697](https://github.com/scala/scala3/pull/22697) +- Avoid infinite recursion when looking for suggestions [#22361](https://github.com/scala/scala3/pull/22361) +- Fix cyclic check, regardless of definition order [#22342](https://github.com/scala/scala3/pull/22342) +- Avoid inf recursion in provablyDisjointClasses [#22489](https://github.com/scala/scala3/pull/22489) + +## Value Classes + +- Allow private members when computing the denotation of a NamedType [#22549](https://github.com/scala/scala3/pull/22549) + +## Other changes + +- Remove locale dependent FileSystemException check [#21633](https://github.com/scala/scala3/pull/21633) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.6.4..3.7.0` these are: + +``` + 63 Martin Odersky + 47 Som Snytt + 33 Adrien Piquerez + 32 Hamza Remmal + 29 Wojciech Mazur + 19 aherlihy + 19 kasiaMarek + 16 Jan Chyb + 13 Dale Wijnand + 11 Kacper Korban + 10 EnzeXing + 9 Sébastien Doeraene + 7 Guillaume Martres + 7 Matt Bovel + 7 Oliver Bračevac + 7 noti0na1 + 5 HarrisL2 + 5 Jamie Thompson + 5 dependabot[bot] + 4 Joel Wilsson + 4 Piotr Chabelski + 4 Seth Tisue + 3 Roman Janusz + 3 anna herlihy + 2 David Hua + 2 Tomasz Godzik + 2 Yichen Xu + 1 Alec Theriault + 1 Daisy Li + 1 Daniel Thoma + 1 Dmitrii Naumenko + 1 Felix Herrmann + 1 He-Pin(kerr) + 1 João Ferreira + 1 Jędrzej Rochala + 1 Katarzyna Marek + 1 Kenji Yoshida + 1 Natsu Kagami + 1 Niklas Fiekas + 1 Rocco Mathijn Andela + 1 Vadim Chelyshov + 1 adpi2 + 1 fan-tom + 1 philwalk + 1 rochala +``` diff --git a/community-build/community-projects/intent b/community-build/community-projects/intent index 466662fb36ed..c0c4a1939b04 160000 --- a/community-build/community-projects/intent +++ b/community-build/community-projects/intent @@ -1 +1 @@ -Subproject commit 466662fb36ed38d1f045449682bdc109496c6b2d +Subproject commit c0c4a1939b04a6ce4ae5de3aa8949f04674af1f7 diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index d6eeedbfc1e0..ab674686d089 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit d6eeedbfc1e04f2eff55506f07f93f448cc21407 +Subproject commit ab674686d089f13da2e29c3b78fe6c3ab0211189 diff --git a/community-build/community-projects/stdLib213 b/community-build/community-projects/stdLib213 deleted file mode 160000 index fcc67cd56c67..000000000000 --- a/community-build/community-projects/stdLib213 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fcc67cd56c67851bf31019ec25ccb09d08b9561b diff --git a/community-build/src/scala/dotty/communitybuild/projects.scala b/community-build/src/scala/dotty/communitybuild/projects.scala index 2575bb2037c0..b575bd2eadf8 100644 --- a/community-build/src/scala/dotty/communitybuild/projects.scala +++ b/community-build/src/scala/dotty/communitybuild/projects.scala @@ -128,7 +128,7 @@ final case class SbtCommunityProject( case Some(ivyHome) => List(s"-Dsbt.ivy.home=$ivyHome") case _ => Nil extraSbtArgs ++ sbtProps ++ List( - "-sbt-version", "1.10.5", + "-sbt-version", "1.10.7", "-Dsbt.supershell=false", s"-Ddotty.communitybuild.dir=$communitybuildDir", s"--addPluginSbtFile=$sbtPluginFilePath" diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index 35b24ab57b00..1eba6c0b1bf8 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -1773,8 +1773,6 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { val returnUnit = lambdaTarget.info.resultType.typeSymbol == defn.UnitClass val functionalInterfaceDesc: String = generatedType.descriptor val desc = capturedParamsTypes.map(tpe => toTypeKind(tpe)).mkString(("("), "", ")") + functionalInterfaceDesc - // TODO specialization - val instantiatedMethodType = new MethodBType(lambdaParamTypes.map(p => toTypeKind(p)), toTypeKind(lambdaTarget.info.resultType)).toASMType val samMethod = atPhase(erasurePhase) { val samMethods = toDenot(functionalInterface).info.possibleSamMethods.toList @@ -1787,7 +1785,21 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { } val methodName = samMethod.javaSimpleName - val samMethodType = asmMethodType(samMethod).toASMType + val samMethodBType = asmMethodType(samMethod) + val samMethodType = samMethodBType.toASMType + + def boxInstantiated(instantiatedType: BType, samType: BType): BType = + if(!samType.isPrimitive && instantiatedType.isPrimitive) + boxedClassOfPrimitive(instantiatedType.asPrimitiveBType) + else instantiatedType + // TODO specialization + val instantiatedMethodBType = new MethodBType( + lambdaParamTypes.map(p => toTypeKind(p)), + boxInstantiated(toTypeKind(lambdaTarget.info.resultType), samMethodBType.returnType) + ) + + val instantiatedMethodType = instantiatedMethodBType.toASMType + // scala/bug#10334: make sure that a lambda object for `T => U` has a method `apply(T)U`, not only the `(Object)Object` // version. Using the lambda a structural type `{def apply(t: T): U}` causes a reflective lookup for this method. val needsGenericBridge = samMethodType != instantiatedMethodType diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index e632def24700..5390626eb2cc 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -169,7 +169,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { /* ---------------- helper utils for generating classes and fields ---------------- */ - def genPlainClass(cd0: TypeDef) = cd0 match { + def genPlainClass(cd0: TypeDef) = (cd0: @unchecked) match { case TypeDef(_, impl: Template) => assert(cnode == null, "GenBCode detected nested methods.") @@ -818,7 +818,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { methSymbol = dd.symbol jMethodName = methSymbol.javaSimpleName - returnType = asmMethodType(dd.symbol).returnType + returnType = asmMethodType(methSymbol).returnType isMethSymStaticCtor = methSymbol.isStaticConstructor resetMethodBookkeeping(dd) @@ -915,7 +915,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { for (p <- params) { emitLocalVarScope(p.symbol, veryFirstProgramPoint, onePastLastProgramPoint, force = true) } } - if (isMethSymStaticCtor) { appendToStaticCtor(dd) } + if (isMethSymStaticCtor) { appendToStaticCtor() } } // end of emitNormalMethodBody() lineNumber(rhs) @@ -936,7 +936,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { * * TODO document, explain interplay with `fabricateStaticInitAndroid()` */ - private def appendToStaticCtor(dd: DefDef): Unit = { + private def appendToStaticCtor(): Unit = { def insertBefore( location: asm.tree.AbstractInsnNode, diff --git a/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala b/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala index e2730c1e84ab..81929c11fdcf 100644 --- a/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala +++ b/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala @@ -12,10 +12,10 @@ import java.util.zip.{CRC32, Deflater, ZipEntry, ZipOutputStream} import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.Decorators.em +import dotty.tools.dotc.util.chaining.* import dotty.tools.io.{AbstractFile, PlainFile, VirtualFile} import dotty.tools.io.PlainFile.toPlainFile import BTypes.InternalName -import scala.util.chaining.* import dotty.tools.io.JarArchive import scala.language.unsafeNulls diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index 7ba39768871b..8ffc9637a001 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -26,8 +26,8 @@ import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.report import dotty.tools.sjs.ir -import dotty.tools.sjs.ir.{ClassKind, Position, Names => jsNames, Trees => js, Types => jstpe} -import dotty.tools.sjs.ir.Names.{ClassName, MethodName, SimpleMethodName} +import dotty.tools.sjs.ir.{ClassKind, Position, Trees => js, Types => jstpe, WellKnownNames => jswkn} +import dotty.tools.sjs.ir.Names.{ClassName, LocalName, MethodName, SimpleMethodName} import dotty.tools.sjs.ir.OriginalName import dotty.tools.sjs.ir.OriginalName.NoOriginalName import dotty.tools.sjs.ir.Trees.OptimizerHints @@ -77,7 +77,7 @@ class JSCodeGen()(using genCtx: Context) { val currentClassSym = new ScopedVar[Symbol] private val currentMethodSym = new ScopedVar[Symbol] private val localNames = new ScopedVar[LocalNameGenerator] - private val thisLocalVarIdent = new ScopedVar[Option[js.LocalIdent]] + private val thisLocalVarName = new ScopedVar[Option[LocalName]] private val isModuleInitialized = new ScopedVar[ScopedVar.VarBox[Boolean]] private val undefinedDefaultParams = new ScopedVar[mutable.Set[Symbol]] @@ -90,7 +90,7 @@ class JSCodeGen()(using genCtx: Context) { currentClassSym := null, currentMethodSym := null, localNames := null, - thisLocalVarIdent := null, + thisLocalVarName := null, isModuleInitialized := null, undefinedDefaultParams := null ) { @@ -101,7 +101,7 @@ class JSCodeGen()(using genCtx: Context) { private def withPerMethodBodyState[A](methodSym: Symbol)(body: => A): A = { withScopedVars( currentMethodSym := methodSym, - thisLocalVarIdent := None, + thisLocalVarName := None, isModuleInitialized := new ScopedVar.VarBox(false), undefinedDefaultParams := mutable.Set.empty, ) { @@ -127,12 +127,19 @@ class JSCodeGen()(using genCtx: Context) { /** Implicitly materializes the current local name generator. */ implicit def implicitLocalNames: LocalNameGenerator = localNames.get + def currentThisTypeNullable: jstpe.Type = + encodeClassType(currentClassSym) + def currentThisType: jstpe.Type = { - encodeClassType(currentClassSym) match { - case tpe @ jstpe.ClassType(cls) => - jstpe.BoxedClassToPrimType.getOrElse(cls, tpe) - case tpe => + currentThisTypeNullable match { + case tpe @ jstpe.ClassType(cls, _) => + jswkn.BoxedClassToPrimType.getOrElse(cls, tpe.toNonNullable) + case tpe @ jstpe.AnyType => + // We are in a JS class, in which even `this` is nullable tpe + case tpe => + throw new AssertionError( + s"Unexpected IR this type $tpe for class ${currentClassSym.get}") } } @@ -417,7 +424,7 @@ class JSCodeGen()(using genCtx: Context) { val staticInitializerStats = reflectInit ::: staticModuleInit if (staticInitializerStats.nonEmpty) - List(genStaticConstructorWithStats(ir.Names.StaticInitializerName, js.Block(staticInitializerStats))) + List(genStaticConstructorWithStats(jswkn.StaticInitializerName, js.Block(staticInitializerStats))) else Nil } @@ -446,7 +453,7 @@ class JSCodeGen()(using genCtx: Context) { originalName, ClassKind.Class, None, - Some(js.ClassIdent(ir.Names.ObjectClass)), + Some(js.ClassIdent(jswkn.ObjectClass)), Nil, None, None, @@ -567,7 +574,7 @@ class JSCodeGen()(using genCtx: Context) { if (staticFields.nonEmpty) { generatedMethods += - genStaticConstructorWithStats(ir.Names.ClassInitializerName, genLoadModule(companionModuleClass)) + genStaticConstructorWithStats(jswkn.ClassInitializerName, genLoadModule(companionModuleClass)) } (staticFields, staticExports) @@ -954,7 +961,7 @@ class JSCodeGen()(using genCtx: Context) { * In dotc this is usually not an issue, because it unboxes `null` to * the zero of the underlying type, unlike scalac which throws an NPE. */ - jstpe.ClassType(encodeClassName(tpe.tycon.typeSymbol)) + jstpe.ClassType(encodeClassName(tpe.tycon.typeSymbol), nullable = true) case _ => // Other types are not boxed, so we can initialized them to their true zero. @@ -971,7 +978,7 @@ class JSCodeGen()(using genCtx: Context) { js.MethodIdent(name), NoOriginalName, Nil, - jstpe.NoType, + jstpe.VoidType, Some(stats))( OptimizerHints.empty, Unversioned) } @@ -993,7 +1000,7 @@ class JSCodeGen()(using genCtx: Context) { val fqcnArg = js.StringLiteral(sym.fullName.toString) val runtimeClassArg = js.ClassOf(toTypeRef(sym.info)) val loadModuleFunArg = - js.Closure(arrow = true, Nil, Nil, None, genLoadModule(sym), Nil) + js.Closure(js.ClosureFlags.arrow, Nil, Nil, None, jstpe.AnyType, genLoadModule(sym), Nil) val stat = genApplyMethod( genLoadModule(jsdefn.ReflectModule), @@ -1028,7 +1035,7 @@ class JSCodeGen()(using genCtx: Context) { val paramTypesArray = js.JSArrayConstr(parameterTypes) - val newInstanceFun = js.Closure(arrow = true, Nil, formalParams, None, { + val newInstanceFun = js.Closure(js.ClosureFlags.arrow, Nil, formalParams, None, jstpe.AnyType, { js.New(encodeClassName(sym), encodeMethodSym(ctor), actualParams) }, Nil) @@ -1150,7 +1157,7 @@ class JSCodeGen()(using genCtx: Context) { var preSuperStats = List.newBuilder[js.Tree] var jsSuperCall: Option[js.JSSuperConstructorCall] = None - val postSuperStats = List.newBuilder[js.Tree] + val postSuperStats = mutable.ListBuffer.empty[js.Tree] /* Move param accessor initializers after the super constructor call since * JS cannot access `this` before the super constructor call. @@ -1210,6 +1217,13 @@ class JSCodeGen()(using genCtx: Context) { assert(jsSuperCall.isDefined, s"Did not find Super call in primary JS construtor at ${dd.sourcePos}") + /* Insert a StoreModule if required. + * Do this now so we have the pos of the super ctor call. + * +=: prepends to the ListBuffer in O(1) -- yes, it's a cryptic name. + */ + if (isStaticModule(currentClassSym)) + js.StoreModule()(jsSuperCall.get.pos) +=: postSuperStats + new PrimaryJSCtor(sym, genParamsAndInfo(sym, dd.paramss), js.JSConstructorBody(preSuperStats.result(), jsSuperCall.get, postSuperStats.result())(dd.span)) } @@ -1339,7 +1353,7 @@ class JSCodeGen()(using genCtx: Context) { } } - js.If(cond, body, js.Skip())(jstpe.NoType) + js.If(cond, body, js.Skip())(jstpe.VoidType) } /* preStats / postStats use pre/post order traversal respectively to @@ -1543,7 +1557,7 @@ class JSCodeGen()(using genCtx: Context) { def jsParams = params.map(genParamDef(_)) - if (primitives.isPrimitive(sym)) { + if (primitives.isPrimitive(sym) && sym != defn.newArrayMethod) { None } else if (sym.is(Deferred) && currentClassSym.isNonNativeJSClass) { // scala-js/#4409: Do not emit abstract methods in non-native JS classes @@ -1591,7 +1605,7 @@ class JSCodeGen()(using genCtx: Context) { if (sym.isClassConstructor) { val namespace = js.MemberNamespace.Constructor js.MethodDef(js.MemberFlags.empty.withNamespace(namespace), - methodName, originalName, jsParams, jstpe.NoType, Some(genStat(rhs)))( + methodName, originalName, jsParams, jstpe.VoidType, Some(genStat(rhs)))( optimizerHints, Unversioned) } else { val namespace = if (isMethodStaticInIR(sym)) { @@ -1630,7 +1644,7 @@ class JSCodeGen()(using genCtx: Context) { val jsParams = paramsSyms.map(genParamDef(_)) def genBody() = localNames.makeLabeledIfRequiresEnclosingReturn(resultIRType) { - if (resultIRType == jstpe.NoType) genStat(tree) + if (resultIRType == jstpe.VoidType) genStat(tree) else genExpr(tree) } @@ -1641,7 +1655,7 @@ class JSCodeGen()(using genCtx: Context) { } else { val thisLocalIdent = freshLocalIdent("this") withScopedVars( - thisLocalVarIdent := Some(thisLocalIdent) + thisLocalVarName := Some(thisLocalIdent.name) ) { val staticNamespace = if (namespace.isPrivate) js.MemberNamespace.PrivateStatic @@ -1660,18 +1674,9 @@ class JSCodeGen()(using genCtx: Context) { // ParamDefs --------------------------------------------------------------- - def genParamDef(sym: Symbol): js.ParamDef = - genParamDef(sym, toIRType(sym.info)) - - private def genParamDef(sym: Symbol, ptpe: jstpe.Type): js.ParamDef = - genParamDef(sym, ptpe, sym.span) - - private def genParamDef(sym: Symbol, pos: Position): js.ParamDef = - genParamDef(sym, toIRType(sym.info), pos) - - private def genParamDef(sym: Symbol, ptpe: jstpe.Type, pos: Position): js.ParamDef = { - js.ParamDef(encodeLocalSym(sym)(implicitly, pos, implicitly), - originalNameOfLocal(sym), ptpe, mutable = false)(pos) + private def genParamDef(sym: Symbol): js.ParamDef = { + implicit val pos = sym.span + js.ParamDef(encodeLocalSym(sym), originalNameOfLocal(sym), toIRType(sym.info), mutable = false) } // Generate statements and expressions ------------------------------------- @@ -1691,7 +1696,7 @@ class JSCodeGen()(using genCtx: Context) { tree match { case js.Block(stats :+ expr) => js.Block(stats :+ exprToStat(expr)) - case _:js.Literal | _:js.This | _:js.VarRef => + case _:js.Literal | _:js.VarRef => js.Skip() case _ => tree @@ -1702,8 +1707,8 @@ class JSCodeGen()(using genCtx: Context) { */ private def genExpr(tree: Tree): js.Tree = { val result = genStatOrExpr(tree, isStat = false) - assert(result.tpe != jstpe.NoType, - s"genExpr($tree) returned a tree with type NoType at pos ${tree.span}") + assert(result.tpe != jstpe.VoidType, + s"genExpr($tree) returned a tree with type VoidType at pos ${tree.span}") result } @@ -1798,7 +1803,7 @@ class JSCodeGen()(using genCtx: Context) { case If(cond, thenp, elsep) => val tpe = - if (isStat) jstpe.NoType + if (isStat) jstpe.VoidType else toIRType(tree.tpe) js.If(genExpr(cond), genStatOrExpr(thenp, isStat), @@ -1813,8 +1818,8 @@ class JSCodeGen()(using genCtx: Context) { if (fromSym.is(Label)) encodeLabelSym(fromSym) else localNames.get.getEnclosingReturnLabel() js.Return(toIRType(expr.tpe) match { - case jstpe.NoType => js.Block(genStat(expr), js.Undefined()) - case _ => genExpr(expr) + case jstpe.VoidType => genStat(expr) + case _ => genExpr(expr) }, label) case WhileDo(cond, body) => @@ -2002,7 +2007,7 @@ class JSCodeGen()(using genCtx: Context) { /** Gen JS this of the current class. * Normally encoded straightforwardly as a JS this. - * But must be replaced by the `thisLocalVarIdent` local variable if there + * But must be replaced by the `thisLocalVarName` local variable if there * is one. */ private def genThis()(implicit pos: Position): js.Tree = { @@ -2011,10 +2016,10 @@ class JSCodeGen()(using genCtx: Context) { "Trying to generate `this` inside the body") }*/ - thisLocalVarIdent.fold[js.Tree] { + thisLocalVarName.fold[js.Tree] { js.This()(currentThisType) - } { thisLocalIdent => - js.VarRef(thisLocalIdent)(currentThisType) + } { thisLocalName => + js.VarRef(thisLocalName)(currentThisTypeNullable) } } @@ -2049,7 +2054,7 @@ class JSCodeGen()(using genCtx: Context) { val blockAST = genStatOrExpr(block, isStat) val resultType = - if (isStat) jstpe.NoType + if (isStat) jstpe.VoidType else toIRType(tree.tpe) val handled = @@ -2066,7 +2071,7 @@ class JSCodeGen()(using genCtx: Context) { resultType: jstpe.Type, isStat: Boolean)(implicit pos: SourcePosition): js.Tree = { val exceptIdent = freshLocalIdent("e") - val origExceptVar = js.VarRef(exceptIdent)(jstpe.AnyType) + val origExceptVar = js.VarRef(exceptIdent.name)(jstpe.AnyType) val mightCatchJavaScriptException = catches.exists { caseDef => caseDef.pat match { @@ -2081,13 +2086,14 @@ class JSCodeGen()(using genCtx: Context) { val (exceptValDef, exceptVar) = if (mightCatchJavaScriptException) { val valDef = js.VarDef(freshLocalIdent("e"), NoOriginalName, - encodeClassType(defn.ThrowableClass), mutable = false, js.WrapAsThrowable(origExceptVar)) + encodeClassType(defn.ThrowableClass), mutable = false, + js.UnaryOp(js.UnaryOp.WrapAsThrowable, origExceptVar)) (valDef, valDef.ref) } else { (js.Skip(), origExceptVar) } - val elseHandler: js.Tree = js.Throw(origExceptVar) + val elseHandler: js.Tree = js.UnaryOp(js.UnaryOp.Throw, origExceptVar) val handler = catches.foldRight(elseHandler) { (caseDef, elsep) => implicit val pos: SourcePosition = caseDef.sourcePos @@ -2227,10 +2233,7 @@ class JSCodeGen()(using genCtx: Context) { val Apply(fun @ Select(sup @ Super(qual, _), _), args) = tree: @unchecked val sym = fun.symbol - if (sym == defn.Any_getClass) { - // The only primitive that is also callable as super call - js.GetClass(genThis()) - } else if (currentClassSym.isNonNativeJSClass) { + if (currentClassSym.isNonNativeJSClass) { genJSSuperCall(tree, isStat) } else { /* #3013 `qual` can be `this.$outer()` in some cases since Scala 2.12, @@ -2303,7 +2306,7 @@ class JSCodeGen()(using genCtx: Context) { val newMethodIdent = js.MethodIdent(newName) js.ApplyStatic(js.ApplyFlags.empty, className, newMethodIdent, args)( - jstpe.ClassType(className)) + jstpe.ClassType(className, nullable = true)) } /** Gen JS code for a new of a JS class (subclass of `js.Any`). */ @@ -2386,7 +2389,7 @@ class JSCodeGen()(using genCtx: Context) { // Make new class def with static members val newClassDef = { implicit val pos = originalClassDef.pos - val parent = js.ClassIdent(jsNames.ObjectClass) + val parent = js.ClassIdent(jswkn.ObjectClass) js.ClassDef(originalClassDef.name, originalClassDef.originalName, ClassKind.AbstractJSType, None, Some(parent), interfaces = Nil, jsSuperClass = None, jsNativeLoadSpec = None, fields = Nil, @@ -2419,12 +2422,12 @@ class JSCodeGen()(using genCtx: Context) { * class capture. It seems Scala 2 has the same vulnerability. How do we * avoid this? */ - val selfName = freshLocalIdent("this")(pos) + val selfIdent = freshLocalIdent("this")(pos) def selfRef(implicit pos: ir.Position) = - js.VarRef(selfName)(jstpe.AnyType) + js.VarRef(selfIdent.name)(jstpe.AnyType) def memberLambda(params: List[js.ParamDef], restParam: Option[js.ParamDef], body: js.Tree)(implicit pos: ir.Position): js.Closure = - js.Closure(arrow = false, captureParams = Nil, params, restParam, body, captureValues = Nil) + js.Closure(js.ClosureFlags.function, captureParams = Nil, params, restParam, jstpe.AnyType, body, captureValues = Nil) val fieldDefinitions = jsFieldDefs.toList.map { fdef => implicit val pos = fdef.pos @@ -2518,30 +2521,26 @@ class JSCodeGen()(using genCtx: Context) { js.JSNew(jsSuperClassRef, args) } - val selfVarDef = js.VarDef(selfName, thisOriginalName, jstpe.AnyType, mutable = false, newTree) + val selfVarDef = js.VarDef(selfIdent.copy(), // copy for the correct `pos` + thisOriginalName, jstpe.AnyType, mutable = false, newTree) selfVarDef :: memberDefinitions } // After the super call, substitute `selfRef` for `This()` - val afterSuper = new ir.Transformers.Transformer { - override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match { + val afterSuper = new ir.Transformers.LocalScopeTransformer { + override def transform(tree: js.Tree): js.Tree = tree match { case js.This() => selfRef(tree.pos) - - // Don't traverse closure boundaries - case closure: js.Closure => - val newCaptureValues = closure.captureValues.map(transformExpr) - closure.copy(captureValues = newCaptureValues)(closure.pos) - case tree => - super.transform(tree, isStat) + super.transform(tree) } - }.transformStats(ctorBody.afterSuper) + }.transformTrees(ctorBody.afterSuper) beforeSuper ::: superCall ::: afterSuper } - val closure = js.Closure(arrow = true, jsClassCaptures, Nil, None, + // Wrap everything in a lambda, for namespacing + val closure = js.Closure(js.ClosureFlags.arrow, jsClassCaptures, Nil, None, jstpe.AnyType, js.Block(inlinedCtorStats, selfRef), jsSuperClassValue :: args) js.JSFunctionApply(closure, Nil) } @@ -2571,6 +2570,8 @@ class JSCodeGen()(using genCtx: Context) { genCoercion(tree, receiver, code) else if (code == JSPrimitives.THROW) genThrow(tree, args) + else if (code == JSPrimitives.NEW_ARRAY) + genNewArray(tree, args) else if (JSPrimitives.isJSPrimitive(code)) genJSPrimitive(tree, args, code, isStat) else @@ -2988,7 +2989,8 @@ class JSCodeGen()(using genCtx: Context) { js.Assign(genSelect(), genArgs(1)) } else { // length of the array - js.ArrayLength(genArray) + js.UnaryOp(js.UnaryOp.Array_length, + js.UnaryOp(js.UnaryOp.CheckNotNull, genArray)) } } @@ -3004,15 +3006,13 @@ class JSCodeGen()(using genCtx: Context) { val genArg = genStatOrExpr(arg, isStat) genReceiver match { - case js.This() => - // common case for which there is no side-effect nor NPE + case genReceiver: js.VarRef if !genReceiver.tpe.isNullable => + // common case (notably for `this`) for which there is no side-effect nor NPE genArg case _ => implicit val pos = tree.span js.Block( - js.If(js.BinaryOp(js.BinaryOp.===, genReceiver, js.Null()), - js.Throw(js.New(NullPointerExceptionClass, js.MethodIdent(jsNames.NoArgConstructorName), Nil)), - js.Skip())(jstpe.NoType), + js.UnaryOp(js.UnaryOp.CheckNotNull, genReceiver), genArg) } } @@ -3034,9 +3034,28 @@ class JSCodeGen()(using genCtx: Context) { genException match { case js.New(cls, _, _) if cls != JavaScriptExceptionClassName => // Common case where ex is neither null nor a js.JavaScriptException - js.Throw(genException) + js.UnaryOp(js.UnaryOp.Throw, genException) + case _ => + js.UnaryOp(js.UnaryOp.Throw, js.UnaryOp(js.UnaryOp.UnwrapFromThrowable, + js.UnaryOp(js.UnaryOp.CheckNotNull, genException))) + } + } + + /** Gen a call to the special `newArray` method. */ + private def genNewArray(tree: Apply, args: List[Tree]): js.Tree = { + implicit val pos: SourcePosition = tree.sourcePos + + val List(elemClazz, Literal(arrayClassConstant), dimsArray: JavaSeqLiteral) = args: @unchecked + + dimsArray.elems match { + case singleDim :: Nil => + // Use a js.NewArray + val arrayTypeRef = toTypeRef(arrayClassConstant.typeValue).asInstanceOf[jstpe.ArrayTypeRef] + js.NewArray(arrayTypeRef, genExpr(singleDim)) case _ => - js.Throw(js.UnwrapFromThrowable(genException)) + // Delegate to jlr.Array.newInstance + js.ApplyStatic(js.ApplyFlags.empty, JLRArrayClassName, js.MethodIdent(JLRArrayNewInstanceMethodName), + List(genExpr(elemClazz), genJavaSeqLiteral(dimsArray)))(jstpe.AnyType) } } @@ -3332,15 +3351,15 @@ class JSCodeGen()(using genCtx: Context) { // Sanity check: we can handle Ints and Strings (including `null`s), but nothing else genSelector.tpe match { - case jstpe.IntType | jstpe.ClassType(jsNames.BoxedStringClass) | jstpe.NullType | jstpe.NothingType => + case jstpe.IntType | jstpe.ClassType(jswkn.BoxedStringClass, _) | jstpe.NullType | jstpe.NothingType => // ok case _ => abortMatch(s"Invalid selector type ${genSelector.tpe}") } val resultType = toIRType(tree.tpe) match { - case jstpe.NothingType => jstpe.NothingType // must take priority over NoType below - case _ if isStat => jstpe.NoType + case jstpe.NothingType => jstpe.NothingType // must take priority over VoidType below + case _ if isStat => jstpe.VoidType case resType => resType } @@ -3496,6 +3515,8 @@ class JSCodeGen()(using genCtx: Context) { atPhase(elimRepeatedPhase)(samMethod.info.paramInfoss.flatten.exists(_.isRepeatedParam)) } } + val isFunctionXXL = + funInterfaceSym.name == tpnme.FunctionXXL && funInterfaceSym.owner == defn.ScalaRuntimePackageClass val formalParamNames = sym.info.paramNamess.flatten.drop(envSize) val formalParamTypes = sym.info.paramInfoss.flatten.drop(envSize) @@ -3505,8 +3526,11 @@ class JSCodeGen()(using genCtx: Context) { val formalAndActualParams = formalParamNames.lazyZip(formalParamTypes).lazyZip(formalParamRepeateds).map { (name, tpe, repeated) => + val formalTpe = + if (isFunctionXXL) jstpe.ArrayType(ObjectArrayTypeRef, nullable = true) + else jstpe.AnyType val formalParam = js.ParamDef(freshLocalIdent(name), - OriginalName(name.toString), jstpe.AnyType, mutable = false) + OriginalName(name.toString), formalTpe, mutable = false) val actualParam = if (repeated) genJSArrayToVarArgs(formalParam.ref)(tree.sourcePos) else unbox(formalParam.ref, tpe) @@ -3541,10 +3565,11 @@ class JSCodeGen()(using genCtx: Context) { if (isThisFunction) { val thisParam :: otherParams = formalParams: @unchecked js.Closure( - arrow = false, + js.ClosureFlags.function, formalCaptures, otherParams, restParam, + jstpe.AnyType, js.Block( js.VarDef(thisParam.name, thisParam.originalName, thisParam.ptpe, mutable = false, @@ -3552,23 +3577,32 @@ class JSCodeGen()(using genCtx: Context) { genBody), actualCaptures) } else { - val closure = js.Closure(arrow = true, formalCaptures, formalParams, restParam, genBody, actualCaptures) + val closure = js.Closure(js.ClosureFlags.typed, formalCaptures, + formalParams, restParam, jstpe.AnyType, genBody, actualCaptures) if (!funInterfaceSym.exists || defn.isFunctionClass(funInterfaceSym)) { val formalCount = formalParams.size - val cls = ClassName("scala.scalajs.runtime.AnonFunction" + formalCount) - val ctorName = MethodName.constructor( - jstpe.ClassRef(ClassName("scala.scalajs.js.Function" + formalCount)) :: Nil) - js.New(cls, js.MethodIdent(ctorName), List(closure)) - } else if (funInterfaceSym.name == tpnme.FunctionXXL && funInterfaceSym.owner == defn.ScalaRuntimePackageClass) { - val cls = ClassName("scala.scalajs.runtime.AnonFunctionXXL") - val ctorName = MethodName.constructor( - jstpe.ClassRef(ClassName("scala.scalajs.js.Function1")) :: Nil) - js.New(cls, js.MethodIdent(ctorName), List(closure)) + val descriptor = js.NewLambda.Descriptor( + superClass = encodeClassName(defn.AbstractFunctionClass(formalCount)), + interfaces = Nil, + methodName = MethodName(applySimpleMethodName, List.fill(formalCount)(jswkn.ObjectRef), jswkn.ObjectRef), + paramTypes = List.fill(formalCount)(jstpe.AnyType), + resultType = jstpe.AnyType + ) + js.NewLambda(descriptor, closure)(encodeClassType(defn.FunctionSymbol(formalCount)).toNonNullable) + } else if (isFunctionXXL) { + val descriptor = js.NewLambda.Descriptor( + superClass = jswkn.ObjectClass, + interfaces = List(encodeClassName(defn.FunctionXXLClass)), + methodName = MethodName(applySimpleMethodName, List(ObjectArrayTypeRef), jswkn.ObjectRef), + paramTypes = List(jstpe.ArrayType(ObjectArrayTypeRef, nullable = true)), + resultType = jstpe.AnyType + ) + js.NewLambda(descriptor, closure)(encodeClassType(funInterfaceSym).toNonNullable) } else { assert(funInterfaceSym.isJSType, s"Invalid functional interface $funInterfaceSym reached the back-end") - closure + closure.copy(flags = js.ClosureFlags.arrow) } } } @@ -3681,8 +3715,8 @@ class JSCodeGen()(using genCtx: Context) { } private def genThrowClassCastException()(implicit pos: Position): js.Tree = { - js.Throw(js.New(jsNames.ClassCastExceptionClass, - js.MethodIdent(jsNames.NoArgConstructorName), Nil)) + js.UnaryOp(js.UnaryOp.Throw, js.New(jswkn.ClassCastExceptionClass, + js.MethodIdent(jswkn.NoArgConstructorName), Nil)) } /** Gen JS code for an isInstanceOf test (for reference types only) */ @@ -3707,7 +3741,7 @@ class JSCodeGen()(using genCtx: Context) { // The Scala type system prevents x.isInstanceOf[Null] and ...[Nothing] assert(sym != defn.NullClass && sym != defn.NothingClass, s"Found a .isInstanceOf[$sym] at $pos") - js.IsInstanceOf(value, toIRType(to)) + js.IsInstanceOf(value, toIRType(to).toNonNullable) } } @@ -3764,7 +3798,7 @@ class JSCodeGen()(using genCtx: Context) { private def makePrimitiveBox(expr: js.Tree, tpe: Type)( implicit pos: Position): js.Tree = { toIRType(tpe) match { - case jstpe.NoType => // for JS interop cases + case jstpe.VoidType => // for JS interop cases js.Block(expr, js.Undefined()) case jstpe.BooleanType | jstpe.CharType | jstpe.ByteType | jstpe.ShortType | jstpe.IntType | jstpe.LongType | jstpe.FloatType | @@ -3780,8 +3814,8 @@ class JSCodeGen()(using genCtx: Context) { private def makePrimitiveUnbox(expr: js.Tree, tpe: Type)( implicit pos: Position): js.Tree = { toIRType(tpe) match { - case jstpe.NoType => expr // for JS interop cases - case irTpe => js.AsInstanceOf(expr, irTpe) + case jstpe.VoidType => expr // for JS interop cases + case irTpe => js.AsInstanceOf(expr, irTpe) } } @@ -3885,10 +3919,6 @@ class JSCodeGen()(using genCtx: Context) { genStatOrExpr(args(1), isStat) } - case LINKING_INFO => - // runtime.linkingInfo - js.JSLinkingInfo() - case DEBUGGER => // js.special.debugger() js.Debugger() @@ -3973,7 +4003,7 @@ class JSCodeGen()(using genCtx: Context) { case arg: js.JSGlobalRef => js.JSTypeOfGlobalRef(arg) case _ => js.JSUnaryOp(js.JSUnaryOp.typeof, arg) } - js.AsInstanceOf(typeofExpr, jstpe.ClassType(jsNames.BoxedStringClass)) + js.AsInstanceOf(typeofExpr, jstpe.ClassType(jswkn.BoxedStringClass, nullable = true)) case STRICT_EQ => // js.special.strictEquals(arg1, arg2) @@ -4017,7 +4047,7 @@ class JSCodeGen()(using genCtx: Context) { val fVarDef = js.VarDef(freshLocalIdent("f"), NoOriginalName, jstpe.AnyType, mutable = false, arg2) val keyVarIdent = freshLocalIdent("key") - val keyVarRef = js.VarRef(keyVarIdent)(jstpe.AnyType) + val keyVarRef = js.VarRef(keyVarIdent.name)(jstpe.AnyType) js.Block( objVarDef, fVarDef, @@ -4027,7 +4057,7 @@ class JSCodeGen()(using genCtx: Context) { case JS_THROW => // js.special.throw(arg) - js.Throw(genArgs1) + js.UnaryOp(js.UnaryOp.Throw, genArgs1) case JS_TRY_CATCH => /* js.special.tryCatch(arg1, arg2) @@ -4052,7 +4082,7 @@ class JSCodeGen()(using genCtx: Context) { val handlerVarDef = js.VarDef(freshLocalIdent("handler"), NoOriginalName, jstpe.AnyType, mutable = false, arg2) val exceptionVarIdent = freshLocalIdent("e") - val exceptionVarRef = js.VarRef(exceptionVarIdent)(jstpe.AnyType) + val exceptionVarRef = js.VarRef(exceptionVarIdent.name)(jstpe.AnyType) js.Block( bodyVarDef, handlerVarDef, @@ -4066,11 +4096,12 @@ class JSCodeGen()(using genCtx: Context) { case WRAP_AS_THROWABLE => // js.special.wrapAsThrowable(arg) - js.WrapAsThrowable(genArgs1) + js.UnaryOp(js.UnaryOp.WrapAsThrowable, genArgs1) case UNWRAP_FROM_THROWABLE => // js.special.unwrapFromThrowable(arg) - js.UnwrapFromThrowable(genArgs1) + js.UnaryOp(js.UnaryOp.UnwrapFromThrowable, + js.UnaryOp(js.UnaryOp.CheckNotNull, genArgs1)) case UNION_FROM | UNION_FROM_TYPE_CONSTRUCTOR => /* js.|.from and js.|.fromTypeConstructor @@ -4220,7 +4251,7 @@ class JSCodeGen()(using genCtx: Context) { "literal classOf[T] expressions (typically compiler-generated). " + "Other uses are not supported in Scala.js.", otherTree.sourcePos) - (jstpe.AnyType, jstpe.ClassRef(jsNames.ObjectClass)) + (jstpe.AnyType, jstpe.ClassRef(jswkn.ObjectClass)) } // Gen the actual args, downcasting them to the formal param types @@ -4447,7 +4478,7 @@ class JSCodeGen()(using genCtx: Context) { } private def genVarRef(sym: Symbol)(implicit pos: Position): js.VarRef = - js.VarRef(encodeLocalSym(sym))(toIRType(sym.info)) + js.VarRef(encodeLocalSymName(sym))(toIRType(sym.info)) private def genAssignableField(sym: Symbol, qualifier: Tree)(implicit pos: SourcePosition): (js.AssignLhs, Boolean) = { def qual = genExpr(qualifier) @@ -4851,17 +4882,21 @@ class JSCodeGen()(using genCtx: Context) { object JSCodeGen { - private val NullPointerExceptionClass = ClassName("java.lang.NullPointerException") + private val JLRArrayClassName = ClassName("java.lang.reflect.Array") private val JSObjectClassName = ClassName("scala.scalajs.js.Object") private val JavaScriptExceptionClassName = ClassName("scala.scalajs.js.JavaScriptException") - private val ObjectClassRef = jstpe.ClassRef(ir.Names.ObjectClass) + private val ObjectArrayTypeRef = jstpe.ArrayTypeRef(jswkn.ObjectRef, 1) + private val applySimpleMethodName = SimpleMethodName("apply") private val newSimpleMethodName = SimpleMethodName("new") - private val selectedValueMethodName = MethodName("selectedValue", Nil, ObjectClassRef) + private val selectedValueMethodName = MethodName("selectedValue", Nil, jswkn.ObjectRef) + + private val JLRArrayNewInstanceMethodName = + MethodName("newInstance", List(jstpe.ClassRef(jswkn.ClassClass), jstpe.ArrayTypeRef(jstpe.IntRef, 1)), jswkn.ObjectRef) - private val ObjectArgConstructorName = MethodName.constructor(List(ObjectClassRef)) + private val ObjectArgConstructorName = MethodName.constructor(List(jswkn.ObjectRef)) private val thisOriginalName = OriginalName("this") @@ -4882,10 +4917,8 @@ object JSCodeGen { def traverse(traverser: ir.Traversers.Traverser): Unit = () - def transform(transformer: ir.Transformers.Transformer, isStat: Boolean)( - implicit pos: ir.Position): js.Tree = { + def transform(transformer: ir.Transformers.Transformer)(implicit pos: ir.Position): js.Tree = js.Transient(this) - } def printIR(out: ir.Printers.IRTreePrinter): Unit = out.print("") diff --git a/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala b/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala index ab7f9a89f9c5..2f6591763fdd 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala @@ -179,8 +179,6 @@ final class JSDefinitions()(using Context) { def Runtime_createLocalJSClass(using Context) = Runtime_createLocalJSClassR.symbol @threadUnsafe lazy val Runtime_withContextualJSClassValueR = RuntimePackageClass.requiredMethodRef("withContextualJSClassValue") def Runtime_withContextualJSClassValue(using Context) = Runtime_withContextualJSClassValueR.symbol - @threadUnsafe lazy val Runtime_linkingInfoR = RuntimePackageClass.requiredMethodRef("linkingInfo") - def Runtime_linkingInfo(using Context) = Runtime_linkingInfoR.symbol @threadUnsafe lazy val Runtime_dynamicImportR = RuntimePackageClass.requiredMethodRef("dynamicImport") def Runtime_dynamicImport(using Context) = Runtime_dynamicImportR.symbol diff --git a/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala b/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala index 9a7753680bc3..959a05fd6c43 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala @@ -16,7 +16,7 @@ import StdNames.* import dotty.tools.dotc.transform.sjs.JSSymUtils.* import dotty.tools.sjs.ir -import dotty.tools.sjs.ir.{Trees => js, Types => jstpe} +import dotty.tools.sjs.ir.{Trees => js, Types => jstpe, WellKnownNames => jswkn} import dotty.tools.sjs.ir.Names.{LocalName, LabelName, SimpleFieldName, FieldName, SimpleMethodName, MethodName, ClassName} import dotty.tools.sjs.ir.OriginalName import dotty.tools.sjs.ir.OriginalName.NoOriginalName @@ -135,16 +135,13 @@ object JSEncoding { def freshLabelName(base: String): LabelName = freshLabelName(LabelName(base)) - def freshLabelIdent(base: String)(implicit pos: ir.Position): js.LabelIdent = - js.LabelIdent(freshLabelName(base)) - def labelSymbolName(sym: Symbol)(using Context): LabelName = labelSymbolNames.getOrElseUpdate(sym, freshLabelName(sym.javaSimpleName)) - def getEnclosingReturnLabel()(implicit pos: ir.Position): js.LabelIdent = { + def getEnclosingReturnLabel(): LabelName = { if (returnLabelName.isEmpty) returnLabelName = Some(freshLabelName("_return")) - js.LabelIdent(returnLabelName.get) + returnLabelName.get } /* If this `LocalNameGenerator` has a `returnLabelName` (often added in the @@ -155,7 +152,7 @@ object JSEncoding { case None => body case Some(labelName) => - js.Labeled(js.LabelIdent(labelName), tpe, body) + js.Labeled(labelName, tpe, body) } } } @@ -166,10 +163,9 @@ object JSEncoding { // Encoding methods ---------------------------------------------------------- - def encodeLabelSym(sym: Symbol)( - implicit ctx: Context, pos: ir.Position, localNames: LocalNameGenerator): js.LabelIdent = { + def encodeLabelSym(sym: Symbol)(implicit ctx: Context, localNames: LocalNameGenerator): LabelName = { require(sym.is(Flags.Label), "encodeLabelSym called with non-label symbol: " + sym) - js.LabelIdent(localNames.labelSymbolName(sym)) + localNames.labelSymbolName(sym) } def encodeFieldSym(sym: Symbol)(implicit ctx: Context, pos: ir.Position): js.FieldIdent = @@ -239,7 +235,7 @@ object JSEncoding { def encodeDynamicImportForwarderIdent(params: List[Symbol])(using Context, ir.Position): js.MethodIdent = { val paramTypeRefs = params.map(sym => paramOrResultTypeRef(sym.info)) - val resultTypeRef = jstpe.ClassRef(ir.Names.ObjectClass) + val resultTypeRef = jstpe.ClassRef(jswkn.ObjectClass) val methodName = MethodName(dynamicImportForwarderSimpleName, paramTypeRefs, resultTypeRef) js.MethodIdent(methodName) } @@ -248,11 +244,13 @@ object JSEncoding { private def paramOrResultTypeRef(tpe: Type)(using Context): jstpe.TypeRef = toParamOrResultTypeRef(toTypeRef(tpe)) - def encodeLocalSym(sym: Symbol)( - implicit ctx: Context, pos: ir.Position, localNames: LocalNameGenerator): js.LocalIdent = { + def encodeLocalSym(sym: Symbol)(using Context, ir.Position, LocalNameGenerator): js.LocalIdent = + js.LocalIdent(encodeLocalSymName(sym)) + + def encodeLocalSymName(sym: Symbol)(using ctx: Context, localNames: LocalNameGenerator): LocalName = { require(!sym.owner.isClass && sym.isTerm && !sym.is(Flags.Method) && !sym.is(Flags.Module), "encodeLocalSym called with non-local symbol: " + sym) - js.LocalIdent(localNames.localSymbolName(sym)) + localNames.localSymbolName(sym) } def encodeClassType(sym: Symbol)(using Context): jstpe.Type = { @@ -261,7 +259,7 @@ object JSEncoding { else { assert(sym != defn.ArrayClass, "encodeClassType() cannot be called with ArrayClass") - jstpe.ClassType(encodeClassName(sym)) + jstpe.ClassType(encodeClassName(sym), nullable = true) } } @@ -284,7 +282,7 @@ object JSEncoding { * - scala.Null to scala.runtime.Null$. */ if (sym1 == defn.BoxedUnitClass) - ir.Names.BoxedUnitClass + jswkn.BoxedUnitClass else if (sym1 == defn.NothingClass) ScalaRuntimeNothingClassName else if (sym1 == defn.NullClass) @@ -324,10 +322,13 @@ object JSEncoding { else if (sym == defn.NullClass) jstpe.NullType else - jstpe.ClassType(typeRef.className) + jstpe.ClassType(typeRef.className, nullable = true) case typeRef: jstpe.ArrayTypeRef => - jstpe.ArrayType(typeRef) + jstpe.ArrayType(typeRef, nullable = true) + + case typeRef: jstpe.TransientTypeRef => + throw AssertionError(s"Unexpected transient type ref $typeRef for ${typeRefInternal._2}") } } @@ -361,7 +362,7 @@ object JSEncoding { */ def nonClassTypeRefToTypeRef(sym: Symbol): (jstpe.TypeRef, Symbol) = { //assert(sym.isType && isCompilingArray, sym) - (jstpe.ClassRef(ir.Names.ObjectClass), defn.ObjectClass) + (jstpe.ClassRef(jswkn.ObjectClass), defn.ObjectClass) } tp.widenDealias match { diff --git a/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala b/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala index e6c73357aa4c..425710c6be9a 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala @@ -22,8 +22,7 @@ import TypeErasure.ErasedValueType import dotty.tools.dotc.util.{SourcePosition, SrcPos} import dotty.tools.dotc.report -import dotty.tools.sjs.ir.{Position, Names => jsNames, Trees => js, Types => jstpe} -import dotty.tools.sjs.ir.Names.DefaultModuleID +import dotty.tools.sjs.ir.{Position, Names => jsNames, Trees => js, Types => jstpe, WellKnownNames => jswkn} import dotty.tools.sjs.ir.OriginalName.NoOriginalName import dotty.tools.sjs.ir.Position.NoPosition import dotty.tools.sjs.ir.Trees.OptimizerHints @@ -87,7 +86,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { symForAnnot.annotations.collect { case annot if annot.symbol == jsdefn.JSExportTopLevelAnnot => val jsName = annot.argumentConstantString(0).get - val moduleID = annot.argumentConstantString(1).getOrElse(DefaultModuleID) + val moduleID = annot.argumentConstantString(1).getOrElse(jswkn.DefaultModuleID) TopLevelExportInfo(moduleID, jsName)(annot.tree.sourcePos) } } @@ -652,7 +651,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { val superClass = { val superClassSym = currentClassSym.asClass.superClass if (superClassSym.isNestedJSClass) - js.VarRef(js.LocalIdent(JSSuperClassParamName))(jstpe.AnyType) + js.VarRef(JSSuperClassParamName)(jstpe.AnyType) else js.LoadJSConstructor(encodeClassName(superClassSym)) } @@ -793,7 +792,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { } // #15419 If the getter returns void, we must "box" it by returning undefined - if (callGetter.tpe == jstpe.NoType) + if (callGetter.tpe == jstpe.VoidType) js.Block(callGetter, js.Undefined()) else callGetter @@ -837,7 +836,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { } private def genThrowTypeError(msg: String = "No matching overload")(implicit pos: Position): js.Tree = - js.Throw(js.JSNew(js.JSGlobalRef("TypeError"), js.StringLiteral(msg) :: Nil)) + js.UnaryOp(js.UnaryOp.Throw, js.JSNew(js.JSGlobalRef("TypeError"), js.StringLiteral(msg) :: Nil)) abstract class Exported( val sym: Symbol, @@ -935,9 +934,9 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { import dotty.tools.sjs.ir.Names (toIRType(tpe): @unchecked) match { - case jstpe.AnyType => NoTypeTest + case jstpe.AnyType | jstpe.AnyNotNullType => NoTypeTest - case jstpe.NoType => PrimitiveTypeTest(jstpe.UndefType, 0) + case jstpe.VoidType => PrimitiveTypeTest(jstpe.UndefType, 0) case jstpe.BooleanType => PrimitiveTypeTest(jstpe.BooleanType, 1) case jstpe.CharType => PrimitiveTypeTest(jstpe.CharType, 2) case jstpe.ByteType => PrimitiveTypeTest(jstpe.ByteType, 3) @@ -947,11 +946,11 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { case jstpe.FloatType => PrimitiveTypeTest(jstpe.FloatType, 7) case jstpe.DoubleType => PrimitiveTypeTest(jstpe.DoubleType, 8) - case jstpe.ClassType(Names.BoxedUnitClass) => PrimitiveTypeTest(jstpe.UndefType, 0) - case jstpe.ClassType(Names.BoxedStringClass) => PrimitiveTypeTest(jstpe.StringType, 9) - case jstpe.ClassType(_) => InstanceOfTypeTest(tpe) + case jstpe.ClassType(jswkn.BoxedUnitClass, _) => PrimitiveTypeTest(jstpe.UndefType, 0) + case jstpe.ClassType(jswkn.BoxedStringClass, _) => PrimitiveTypeTest(jstpe.StringType, 9) + case jstpe.ClassType(_, _) => InstanceOfTypeTest(tpe) - case jstpe.ArrayType(_) => InstanceOfTypeTest(tpe) + case jstpe.ArrayType(_, _) => InstanceOfTypeTest(tpe) } } } @@ -998,7 +997,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { def genArgRef(index: Int)(implicit pos: Position): js.Tree = { if (index < minArgc) - js.VarRef(js.LocalIdent(fixedParamNames(index)))(jstpe.AnyType) + js.VarRef(fixedParamNames(index))(jstpe.AnyType) else js.JSSelect(genRestArgRef(), js.IntLiteral(index - minArgc)) } @@ -1014,16 +1013,16 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { def genRestArgRef()(implicit pos: Position): js.Tree = { assert(needsRestParam, s"trying to generate a reference to non-existent rest param at $pos") - js.VarRef(js.LocalIdent(restParamName))(jstpe.AnyType) + js.VarRef(restParamName)(jstpe.AnyType) } def genAllArgsRefsForForwarder()(implicit pos: Position): List[js.TreeOrJSSpread] = { val fixedArgRefs = fixedParamNames.toList.map { paramName => - js.VarRef(js.LocalIdent(paramName))(jstpe.AnyType) + js.VarRef(paramName)(jstpe.AnyType) } if (needsRestParam) { - val restArgRef = js.VarRef(js.LocalIdent(restParamName))(jstpe.AnyType) + val restArgRef = js.VarRef(restParamName)(jstpe.AnyType) fixedArgRefs :+ js.JSSpread(restArgRef) } else { fixedArgRefs diff --git a/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala b/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala index a3a37795826a..41e62094b04f 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala @@ -34,8 +34,7 @@ object JSPrimitives { inline val CREATE_INNER_JS_CLASS = CONSTRUCTOROF + 1 // runtime.createInnerJSClass inline val CREATE_LOCAL_JS_CLASS = CREATE_INNER_JS_CLASS + 1 // runtime.createLocalJSClass inline val WITH_CONTEXTUAL_JS_CLASS_VALUE = CREATE_LOCAL_JS_CLASS + 1 // runtime.withContextualJSClassValue - inline val LINKING_INFO = WITH_CONTEXTUAL_JS_CLASS_VALUE + 1 // runtime.linkingInfo - inline val DYNAMIC_IMPORT = LINKING_INFO + 1 // runtime.dynamicImport + inline val DYNAMIC_IMPORT = WITH_CONTEXTUAL_JS_CLASS_VALUE + 1 // runtime.dynamicImport inline val STRICT_EQ = DYNAMIC_IMPORT + 1 // js.special.strictEquals inline val IN = STRICT_EQ + 1 // js.special.in @@ -48,9 +47,10 @@ object JSPrimitives { inline val UNWRAP_FROM_THROWABLE = WRAP_AS_THROWABLE + 1 // js.special.unwrapFromThrowable inline val DEBUGGER = UNWRAP_FROM_THROWABLE + 1 // js.special.debugger - inline val THROW = DEBUGGER + 1 + inline val THROW = DEBUGGER + 1 // .throw + inline val NEW_ARRAY = THROW + 1 // scala.runtime.Arrays.newArray - inline val UNION_FROM = THROW + 1 // js.|.from + inline val UNION_FROM = NEW_ARRAY + 1 // js.|.from inline val UNION_FROM_TYPE_CONSTRUCTOR = UNION_FROM + 1 // js.|.fromTypeConstructor inline val REFLECT_SELECTABLE_SELECTDYN = UNION_FROM_TYPE_CONSTRUCTOR + 1 // scala.reflect.Selectable.selectDynamic @@ -122,7 +122,6 @@ class JSPrimitives(ictx: Context) extends DottyPrimitives(ictx) { addPrimitive(jsdefn.Runtime_createInnerJSClass, CREATE_INNER_JS_CLASS) addPrimitive(jsdefn.Runtime_createLocalJSClass, CREATE_LOCAL_JS_CLASS) addPrimitive(jsdefn.Runtime_withContextualJSClassValue, WITH_CONTEXTUAL_JS_CLASS_VALUE) - addPrimitive(jsdefn.Runtime_linkingInfo, LINKING_INFO) addPrimitive(jsdefn.Runtime_dynamicImport, DYNAMIC_IMPORT) addPrimitive(jsdefn.Special_strictEquals, STRICT_EQ) @@ -137,6 +136,7 @@ class JSPrimitives(ictx: Context) extends DottyPrimitives(ictx) { addPrimitive(jsdefn.Special_debugger, DEBUGGER) addPrimitive(defn.throwMethod, THROW) + addPrimitive(defn.newArrayMethod, NEW_ARRAY) addPrimitive(jsdefn.PseudoUnion_from, UNION_FROM) addPrimitive(jsdefn.PseudoUnion_fromTypeConstructor, UNION_FROM_TYPE_CONSTRUCTOR) diff --git a/compiler/src/dotty/tools/debug/ExpressionCompiler.scala b/compiler/src/dotty/tools/debug/ExpressionCompiler.scala new file mode 100644 index 000000000000..83c20b0f54a7 --- /dev/null +++ b/compiler/src/dotty/tools/debug/ExpressionCompiler.scala @@ -0,0 +1,31 @@ +package dotty.tools.debug + +import dotty.tools.dotc.Compiler +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.transform.ElimByName + +/** + * The expression compiler powers the debug console in Metals and the IJ Scala plugin, + * enabling evaluation of arbitrary Scala expressions at runtime (even macros). + * It produces class files that can be loaded by the running Scala program, + * to compute the evaluation output. + * + * To do so, it extends the Compiler with 3 phases: + * - InsertExpression: parses and inserts the expression in the original source tree + * - ExtractExpression: extract the typed expression and places it in the new expression class + * - ResolveReflectEval: resolves local variables or inacessible members using reflection calls + */ +class ExpressionCompiler(config: ExpressionCompilerConfig) extends Compiler: + + override protected def frontendPhases: List[List[Phase]] = + val parser :: others = super.frontendPhases: @unchecked + parser :: List(InsertExpression(config)) :: others + + override protected def transformPhases: List[List[Phase]] = + val store = ExpressionStore() + // the ExtractExpression phase should be after ElimByName and ExtensionMethods, and before LambdaLift + val transformPhases = super.transformPhases + val index = transformPhases.indexWhere(_.exists(_.phaseName == ElimByName.name)) + val (before, after) = transformPhases.splitAt(index + 1) + (before :+ List(ExtractExpression(config, store))) ++ (after :+ List(ResolveReflectEval(config, store))) diff --git a/compiler/src/dotty/tools/debug/ExpressionCompilerBridge.scala b/compiler/src/dotty/tools/debug/ExpressionCompilerBridge.scala new file mode 100644 index 000000000000..3ffbdc860abe --- /dev/null +++ b/compiler/src/dotty/tools/debug/ExpressionCompilerBridge.scala @@ -0,0 +1,38 @@ +package dotty.tools.debug + +import java.nio.file.Path +import java.util.function.Consumer +import java.{util => ju} +import scala.jdk.CollectionConverters.* +import scala.util.control.NonFatal +import dotty.tools.dotc.reporting.StoreReporter +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.Driver + +class ExpressionCompilerBridge: + def run( + outputDir: Path, + classPath: String, + options: Array[String], + sourceFile: Path, + config: ExpressionCompilerConfig + ): Boolean = + val args = Array( + "-d", + outputDir.toString, + "-classpath", + classPath, + "-Yskip:pureStats" + // Debugging: Print the tree after phases of the debugger + // "-Vprint:insert-expression,resolve-reflect-eval", + ) ++ options :+ sourceFile.toString + val driver = new Driver: + protected override def newCompiler(using Context): ExpressionCompiler = ExpressionCompiler(config) + val reporter = ExpressionReporter(error => config.errorReporter.accept(error)) + try + driver.process(args, reporter) + !reporter.hasErrors + catch + case NonFatal(cause) => + cause.printStackTrace() + throw cause diff --git a/compiler/src/dotty/tools/debug/ExpressionCompilerConfig.scala b/compiler/src/dotty/tools/debug/ExpressionCompilerConfig.scala new file mode 100644 index 000000000000..895489143f9e --- /dev/null +++ b/compiler/src/dotty/tools/debug/ExpressionCompilerConfig.scala @@ -0,0 +1,65 @@ +package dotty.tools.debug + +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.core.Names.* +import dotty.tools.dotc.core.Flags.* +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.SymUtils + +import java.{util => ju} +import ju.function.Consumer + +class ExpressionCompilerConfig private[debug] ( + packageName: String, + outputClassName: String, + private[debug] val breakpointLine: Int, + private[debug] val expression: String, + private[debug] val localVariables: ju.Set[String], + private[debug] val errorReporter: Consumer[String], + private[debug] val testMode: Boolean +): + def this() = this( + packageName = "", + outputClassName = "", + breakpointLine = -1, + expression = "", + localVariables = ju.Collections.emptySet, + errorReporter = _ => (), + testMode = false, + ) + + def withPackageName(packageName: String): ExpressionCompilerConfig = copy(packageName = packageName) + def withOutputClassName(outputClassName: String): ExpressionCompilerConfig = copy(outputClassName = outputClassName) + def withBreakpointLine(breakpointLine: Int): ExpressionCompilerConfig = copy(breakpointLine = breakpointLine) + def withExpression(expression: String): ExpressionCompilerConfig = copy(expression = expression) + def withLocalVariables(localVariables: ju.Set[String]): ExpressionCompilerConfig = copy(localVariables = localVariables) + def withErrorReporter(errorReporter: Consumer[String]): ExpressionCompilerConfig = copy(errorReporter = errorReporter) + + private[debug] val expressionTermName: TermName = termName(outputClassName.toLowerCase.toString) + private[debug] val expressionClassName: TypeName = typeName(outputClassName) + + private[debug] def expressionClass(using Context): ClassSymbol = + if packageName.isEmpty then requiredClass(outputClassName) + else requiredClass(s"$packageName.$outputClassName") + + private[debug] def evaluateMethod(using Context): Symbol = + expressionClass.info.decl(termName("evaluate")).symbol + + private def copy( + packageName: String = packageName, + outputClassName: String = outputClassName, + breakpointLine: Int = breakpointLine, + expression: String = expression, + localVariables: ju.Set[String] = localVariables, + errorReporter: Consumer[String] = errorReporter, + ) = new ExpressionCompilerConfig( + packageName, + outputClassName, + breakpointLine, + expression, + localVariables, + errorReporter, + testMode + ) diff --git a/compiler/src/dotty/tools/debug/ExpressionReporter.scala b/compiler/src/dotty/tools/debug/ExpressionReporter.scala new file mode 100644 index 000000000000..1de1c37bae15 --- /dev/null +++ b/compiler/src/dotty/tools/debug/ExpressionReporter.scala @@ -0,0 +1,17 @@ +package dotty.tools.debug + +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.reporting.AbstractReporter +import dotty.tools.dotc.reporting.Diagnostic + +private class ExpressionReporter(reportError: String => Unit) extends AbstractReporter: + override def doReport(dia: Diagnostic)(using Context): Unit = + // Debugging: println(messageAndPos(dia)) + dia match + case error: Diagnostic.Error => + val newPos = error.pos.source.positionInUltimateSource(error.pos) + val errorWithNewPos = new Diagnostic.Error(error.msg, newPos) + reportError(stripColor(messageAndPos(errorWithNewPos))) + case _ => + // TODO report the warnings in the expression + () diff --git a/compiler/src/dotty/tools/debug/ExpressionStore.scala b/compiler/src/dotty/tools/debug/ExpressionStore.scala new file mode 100644 index 000000000000..3151c43b9a7a --- /dev/null +++ b/compiler/src/dotty/tools/debug/ExpressionStore.scala @@ -0,0 +1,24 @@ +package dotty.tools.debug + +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.core.Names.* +import dotty.tools.dotc.core.Flags.* +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.SymUtils + +private class ExpressionStore: + var symbol: TermSymbol | Null = null + // To resolve captured variables, we store: + // - All classes in the chain of owners of the expression + // - The first local method enclosing the expression + var classOwners: Seq[ClassSymbol] = Seq.empty + var capturingMethod: Option[TermSymbol] = None + + def store(exprSym: Symbol)(using Context): Unit = + symbol = exprSym.asTerm + classOwners = exprSym.ownersIterator.collect { case cls: ClassSymbol => cls }.toSeq + capturingMethod = exprSym.ownersIterator + .find(sym => (sym.isClass || sym.is(Method)) && sym.enclosure.is(Method)) // the first local class or method + .collect { case sym if sym.is(Method) => sym.asTerm } // if it is a method diff --git a/compiler/src/dotty/tools/debug/ExtractExpression.scala b/compiler/src/dotty/tools/debug/ExtractExpression.scala new file mode 100644 index 000000000000..151d75270c6e --- /dev/null +++ b/compiler/src/dotty/tools/debug/ExtractExpression.scala @@ -0,0 +1,368 @@ +package dotty.tools.debug + +import dotty.tools.dotc.core.SymUtils +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Flags.* +import dotty.tools.dotc.core.Names.* +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.core.DenotTransformers.DenotTransformer +import dotty.tools.dotc.core.Denotations.SingleDenotation +import dotty.tools.dotc.core.SymDenotations.SymDenotation +import dotty.tools.dotc.transform.MacroTransform +import dotty.tools.dotc.core.Phases.* +import dotty.tools.dotc.report +import dotty.tools.dotc.util.SrcPos +import scala.annotation.nowarn + +/** + * This phase extracts the typed expression from the source tree, transfoms it and places it + * in the evaluate method of the Expression class. + * + * Before: + * package example: + * class A: + * def m: T = + * val expression = + * println("") + * typed_expr + * body + * + * class Expression(thisObject: Any, names: Array[String], values: Array[Any]): + * def evaluate(): Any = () + * + * After: + * package example: + * class A: + * def m: T = body + * + * class Expression(thisObject: Any, names: Array[String], values: Array[Any]): + * def evaluate(): Any = + { + * transformed_expr + * } + * + * Every access to a local variable, or an inaccessible member is transformed into a temporary reflectEval call. + * A ReflectEvalStrategy is attached to each reflectEval call to describe what should be evaluated and how. + * When printing trees for debugging, the ReflectEvalStrategy appears as a String literal argument. + * + * Examples: + * + * 1. Get local variable `a`: + * reflectEval(null, "ReflectEvalStrategy.LocalValue(a)", []) + * + * 2. Call private method `a.m(x1, x2)`: + * reflectEval(a, "ReflectEvalStrategy.MethodCall(m)", [x1, x2]) + * + * 3. Set private field `a.b = c`: + * reflectEval(a, "ReflectEvalStrategy.FieldAssign(b)", [c]) + * + * etc + * + */ +private class ExtractExpression( + config: ExpressionCompilerConfig, + expressionStore: ExpressionStore +) extends MacroTransform with DenotTransformer: + override def phaseName: String = ExtractExpression.name + + /** Update the owner of the symbols inserted into `evaluate`. */ + override def transform(ref: SingleDenotation)(using Context): SingleDenotation = + ref match + case ref: SymDenotation if isExpressionVal(ref.symbol.maybeOwner) => + // update owner of the symDenotation, e.g. local vals + // that are extracted out of the expression val to the evaluate method + ref.copySymDenotation(owner = config.evaluateMethod) + case _ => + ref + + override def transformPhase(using Context): Phase = this.next + + override protected def newTransformer(using Context): Transformer = new ExtractExpressionTransformer + + private class ExtractExpressionTransformer extends Transformer: + private var expressionTree: Tree | Null = null + override def transform(tree: Tree)(using Context): Tree = + tree match + case PackageDef(pid, stats) => + val (evaluationClassDef, others) = stats.partition(_.symbol == config.expressionClass) + val transformedStats = (others ++ evaluationClassDef).map(transform) + cpy.PackageDef(tree)(pid, transformedStats) + case tree: ValDef if isExpressionVal(tree.symbol) => + expressionTree = tree.rhs + expressionStore.store(tree.symbol) + unitLiteral + case tree: DefDef if tree.symbol == config.evaluateMethod => + val transformedExpr = ExpressionTransformer.transform(expressionTree.nn) + cpy.DefDef(tree)(rhs = transformedExpr) + case tree => + super.transform(tree) + + private object ExpressionTransformer extends TreeMap: + override def transform(tree: Tree)(using Context): Tree = + tree match + case tree: ImportOrExport => tree + case tree if isLocalToExpression(tree.symbol) => super.transform(tree) + + // static object + case tree: (Ident | Select | This) if isStaticObject(tree.symbol) => + getStaticObject(tree)(tree.symbol.moduleClass) + + // non static this or outer this + case tree: This if !tree.symbol.is(Package) => + thisOrOuterValue(tree)(tree.symbol.enclosingClass.asClass) + + // non-static object + case tree: (Ident | Select) if isNonStaticObject(tree.symbol) => + callMethod(tree)(getTransformedQualifier(tree), tree.symbol.asTerm, Nil) + + // local variable + case tree: Ident if isLocalVariable(tree.symbol) => + if tree.symbol.is(Lazy) then + report.error(s"Evaluation of local lazy val not supported", tree.srcPos) + tree + else + getCapturer(tree.symbol.asTerm) match + case Some(capturer) => + if capturer.isClass then getClassCapture(tree)(tree.symbol, capturer.asClass) + else getMethodCapture(tree)(tree.symbol, capturer.asTerm) + case None => getLocalValue(tree)(tree.symbol) + + // assignment to local variable + case tree @ Assign(lhs, _) if isLocalVariable(lhs.symbol) => + val variable = lhs.symbol.asTerm + val rhs = transform(tree.rhs) + getCapturer(variable) match + case Some(capturer) => + if capturer.isClass then setClassCapture(tree)(variable, capturer.asClass, rhs) + else setMethodCapture(tree)(variable, capturer.asTerm, rhs) + case None => setLocalValue(tree)(variable, rhs) + + // inaccessible fields + case tree: (Ident | Select) if tree.symbol.isField && !isAccessibleMember(tree) => + if tree.symbol.is(JavaStatic) then getField(tree)(nullLiteral, tree.symbol.asTerm) + else getField(tree)(getTransformedQualifier(tree), tree.symbol.asTerm) + + // assignment to inaccessible fields + case tree @ Assign(lhs, rhs) if lhs.symbol.isField && !isAccessibleMember(lhs) => + if lhs.symbol.is(JavaStatic) then setField(tree)(nullLiteral, lhs.symbol.asTerm, transform(rhs)) + else setField(tree)(getTransformedQualifier(lhs), lhs.symbol.asTerm, transform(rhs)) + + // inaccessible constructors + case tree: (Select | Apply | TypeApply) + if tree.symbol.isConstructor && (!tree.symbol.owner.isStatic || !isAccessibleMember(tree)) => + callConstructor(tree)(getTransformedQualifierOfNew(tree), tree.symbol.asTerm, getTransformedArgs(tree)) + + // inaccessible methods + case tree: (Ident | Select | Apply | TypeApply) if tree.symbol.isRealMethod && !isAccessibleMember(tree) => + val args = getTransformedArgs(tree) + if tree.symbol.is(JavaStatic) then callMethod(tree)(nullLiteral, tree.symbol.asTerm, args) + else callMethod(tree)(getTransformedQualifier(tree), tree.symbol.asTerm, args) + + // accessible members + case tree: (Ident | Select) if !tree.symbol.isStatic => + val qualifier = getTransformedQualifier(tree) + val qualifierType = widenDealiasQualifierType(tree) + val castQualifier = if qualifier.tpe <:< qualifierType then qualifier else + qualifier.select(defn.Any_asInstanceOf).appliedToType(qualifierType) + cpy.Select(tree)(castQualifier, tree.name) + + case Typed(tree, tpt) if tpt.symbol.isType && !isTypeAccessible(tpt.tpe) => transform(tree) + case tree => super.transform(tree) + + private def getCapturer(variable: TermSymbol)(using Context): Option[Symbol] = + // a local variable can be captured by a class or method + val candidates = expressionStore.symbol.nn.ownersIterator + .takeWhile(_ != variable.owner) + .filter(s => s.isClass || s.is(Method)) + .toSeq + candidates + .findLast(_.isClass) + .orElse(candidates.find(_.is(Method))) + + private def getTransformedArgs(tree: Tree)(using Context): List[Tree] = + tree match + case _: (Ident | Select) => Nil + case Apply(fun, args) => getTransformedArgs(fun) ++ args.map(transform) + case TypeApply(fun, _) => getTransformedArgs(fun) + + private def getTransformedQualifier(tree: Tree)(using Context): Tree = + tree match + case Ident(_) => + tree.tpe match + case TermRef(NoPrefix, _) => + // it's a local method, it can capture its outer value + thisOrOuterValue(tree)(tree.symbol.enclosingClass.asClass) + case TermRef(prefix: NamedType, _) => transform(ref(prefix)) + case TermRef(prefix: ThisType, _) => transform(This(prefix.cls)) + case Select(qualifier, _) => transform(qualifier) + case Apply(fun, _) => getTransformedQualifier(fun) + case TypeApply(fun, _) => getTransformedQualifier(fun) + + private def getTransformedQualifierOfNew(tree: Tree)(using Context): Tree = + tree match + case Select(New(tpt), _) => getTransformedPrefix(tpt) + case Apply(fun, _) => getTransformedQualifierOfNew(fun) + case TypeApply(fun, _) => getTransformedQualifierOfNew(fun) + + private def getTransformedPrefix(typeTree: Tree)(using Context): Tree = + def transformPrefix(prefix: Type): Tree = + prefix match + case NoPrefix => + // it's a local class, it can capture its outer value + thisOrOuterValue(typeTree)(typeTree.symbol.owner.enclosingClass.asClass) + case prefix: ThisType => thisOrOuterValue(typeTree)(prefix.cls) + case ref: TermRef => transform(Ident(ref).withSpan(typeTree.span)) + def rec(tpe: Type): Tree = + tpe match + case TypeRef(prefix, _) => transformPrefix(prefix) + case AppliedType(tycon, _) => rec(tycon) + rec(typeTree.tpe) + end ExpressionTransformer + + private def isExpressionVal(sym: Symbol)(using Context): Boolean = + sym.name == config.expressionTermName + + // symbol can be a class or a method + private def thisOrOuterValue(tree: Tree)(cls: ClassSymbol)(using Context): Tree = + val ths = getThis(tree)(expressionStore.classOwners.head) + val target = expressionStore.classOwners.indexOf(cls) + if target >= 0 then + expressionStore.classOwners + .drop(1) + .take(target) + .foldLeft(ths) { (innerObj, outerSym) => + if innerObj == ths && config.localVariables.contains("$outer") then getLocalOuter(tree)(outerSym) + else getOuter(tree)(innerObj, outerSym) + } + else nullLiteral + + private def getThis(tree: Tree)(cls: ClassSymbol)(using Context): Tree = + reflectEval(tree)(nullLiteral, ReflectEvalStrategy.This(cls), Nil) + + private def getLocalOuter(tree: Tree)(outerCls: ClassSymbol)(using Context): Tree = + val strategy = ReflectEvalStrategy.LocalOuter(outerCls) + reflectEval(tree)(nullLiteral, strategy, Nil) + + private def getOuter(tree: Tree)(qualifier: Tree, outerCls: ClassSymbol)(using Context): Tree = + val strategy = ReflectEvalStrategy.Outer(outerCls) + reflectEval(tree)(qualifier, strategy, Nil) + + private def getLocalValue(tree: Tree)(variable: Symbol)(using Context): Tree = + val isByName = isByNameParam(variable.info) + val strategy = ReflectEvalStrategy.LocalValue(variable.asTerm, isByName) + reflectEval(tree)(nullLiteral, strategy, Nil) + + private def isByNameParam(tpe: Type)(using Context): Boolean = + tpe match + case _: ExprType => true + case ref: TermRef => isByNameParam(ref.symbol.info) + case _ => false + + private def setLocalValue(tree: Tree)(variable: Symbol, rhs: Tree)(using Context): Tree = + val strategy = ReflectEvalStrategy.LocalValueAssign(variable.asTerm) + reflectEval(tree)(nullLiteral, strategy, List(rhs)) + + private def getClassCapture(tree: Tree)(variable: Symbol, cls: ClassSymbol)(using Context): Tree = + val byName = isByNameParam(variable.info) + val strategy = ReflectEvalStrategy.ClassCapture(variable.asTerm, cls, byName) + val qualifier = thisOrOuterValue(tree)(cls) + reflectEval(tree)(qualifier, strategy, Nil) + + private def setClassCapture(tree: Tree)(variable: Symbol, cls: ClassSymbol, value: Tree)(using Context) = + val strategy = ReflectEvalStrategy.ClassCaptureAssign(variable.asTerm, cls) + val qualifier = thisOrOuterValue(tree)(cls) + reflectEval(tree)(qualifier, strategy, List(value)) + + private def getMethodCapture(tree: Tree)(variable: Symbol, method: TermSymbol)(using Context): Tree = + val isByName = isByNameParam(variable.info) + val strategy = + ReflectEvalStrategy.MethodCapture(variable.asTerm, method.asTerm, isByName) + reflectEval(tree)(nullLiteral, strategy, Nil) + + private def setMethodCapture(tree: Tree)(variable: Symbol, method: Symbol, value: Tree)(using Context) = + val strategy = + ReflectEvalStrategy.MethodCaptureAssign(variable.asTerm, method.asTerm) + reflectEval(tree)(nullLiteral, strategy, List(value)) + + private def getStaticObject(tree: Tree)(obj: Symbol)(using ctx: Context): Tree = + val strategy = ReflectEvalStrategy.StaticObject(obj.asClass) + reflectEval(tree)(nullLiteral, strategy, Nil) + + private def getField(tree: Tree)(qualifier: Tree, field: TermSymbol)(using Context): Tree = + val byName = isByNameParam(field.info) + val strategy = ReflectEvalStrategy.Field(field, byName) + reflectEval(tree)(qualifier, strategy, Nil) + + private def setField(tree: Tree)(qualifier: Tree, field: TermSymbol, rhs: Tree)(using Context): Tree = + val strategy = ReflectEvalStrategy.FieldAssign(field) + reflectEval(tree)(qualifier, strategy, List(rhs)) + + private def callMethod(tree: Tree)(qualifier: Tree, method: TermSymbol, args: List[Tree])(using Context): Tree = + val strategy = ReflectEvalStrategy.MethodCall(method) + reflectEval(tree)(qualifier, strategy, args) + + private def callConstructor(tree: Tree)(qualifier: Tree, ctr: TermSymbol, args: List[Tree])(using Context): Tree = + val strategy = ReflectEvalStrategy.ConstructorCall(ctr, ctr.owner.asClass) + reflectEval(tree)(qualifier, strategy, args) + + private def reflectEval(tree: Tree)( + qualifier: Tree, + strategy: ReflectEvalStrategy, + args: List[Tree] + )(using Context): Tree = + val evalArgs = List( + qualifier, + Literal(Constant(strategy.toString)), // only useful for debugging, when printing trees + JavaSeqLiteral(args, TypeTree(ctx.definitions.ObjectType)) + ) + cpy + .Apply(tree)(Select(This(config.expressionClass), termName("reflectEval")), evalArgs) + .withAttachment(ReflectEvalStrategy, strategy) + + private def isStaticObject(symbol: Symbol)(using Context): Boolean = + symbol.is(Module) && symbol.isStatic && !symbol.is(JavaDefined) && !symbol.isRoot + + private def isNonStaticObject(symbol: Symbol)(using Context): Boolean = + symbol.is(Module) && !symbol.isStatic && !symbol.isRoot + + private def isLocalVariable(symbol: Symbol)(using Context): Boolean = + !symbol.is(Method) && symbol.isLocalToBlock + + /** Check if a term is accessible from the expression class. + * At this phase, there is no need test privateWithin, as it will no longer be checked. + * This eliminates the need to use reflection to evaluate privateWithin members, + * which would otherwise degrade performances. + */ + private def isAccessibleMember(tree: Tree)(using Context): Boolean = + val symbol = tree.symbol + symbol.owner.isType && + !symbol.isPrivate && + !symbol.is(Protected) && + isTypeAccessible(widenDealiasQualifierType(tree)) + + private def widenDealiasQualifierType(tree: Tree)(using Context): Type = + tree match + case Ident(_) => tree.symbol.enclosingClass.thisType.widenDealias + case Select(qualifier, _) => qualifier.tpe.widenDealias + case Apply(fun, _) => widenDealiasQualifierType(fun) + case TypeApply(fun, _) => widenDealiasQualifierType(fun) + + // Check if a type is accessible from the expression class + private def isTypeAccessible(tpe: Type)(using Context): Boolean = + def isPublic(sym: Symbol): Boolean = + !sym.isLocal && (sym.isPublic || sym.privateWithin.is(PackageClass)) + tpe.forallParts { + case tpe: NamedType if tpe.symbol != NoSymbol => + isLocalToExpression(tpe.symbol) || isPublic(tpe.symbol) + case _ => true + } + + private def isLocalToExpression(symbol: Symbol)(using Context): Boolean = + val evaluateMethod = config.evaluateMethod + symbol.ownersIterator.exists(_ == evaluateMethod) + +private object ExtractExpression: + val name: String = "extractExpression" diff --git a/compiler/src/dotty/tools/debug/InsertExpression.scala b/compiler/src/dotty/tools/debug/InsertExpression.scala new file mode 100644 index 000000000000..17d2d7f6ea92 --- /dev/null +++ b/compiler/src/dotty/tools/debug/InsertExpression.scala @@ -0,0 +1,252 @@ +package dotty.tools.debug + +import dotty.tools.dotc.ast.untpd.* +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Names.* +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.parsing.Parsers +import dotty.tools.dotc.report +import dotty.tools.dotc.transform.MegaPhase.MiniPhase +import dotty.tools.dotc.util.NoSourcePosition +import dotty.tools.dotc.util.SourceFile +import dotty.tools.dotc.util.SourcePosition +import dotty.tools.dotc.util.Spans.Span +import dotty.tools.dotc.util.SrcPos +import dotty.tools.io.VirtualFile + +import java.nio.charset.StandardCharsets + +/** + * This phase inserts the expression being evaluated at the line of the breakpoint + * and inserts the expression class in the same package (so that it can access package private symbols). + * + * Before: + * package example: + * class A: + * def m: T = + * body // breakpoint here + * + * After: + * package example: + * class A: + * def m: T = + * val expression = + * println("") // effect, to prevent constant-folding + * expr // inserted expression + * body // breakpoint here + * + * class Expression(thisObject: Any, names: Array[String], values: Array[Any]): + * def evaluate(): Any = () + * + */ +private class InsertExpression(config: ExpressionCompilerConfig) extends Phase: + private var expressionInserted = false + + override def phaseName: String = InsertExpression.name + override def isCheckable: Boolean = false + + // TODO move reflection methods (callMethod, getField, etc) to scala3-library + // under scala.runtime (or scala.debug?) to avoid recompiling them again and again + private val expressionClassSource = + s"""|class ${config.expressionClassName}(thisObject: Any, names: Array[String], values: Array[Any]) { + | import java.lang.reflect.InvocationTargetException + | val classLoader = getClass.getClassLoader + | + | def evaluate(): Any = + | () + | + | def getThisObject(): Any = thisObject + | + | def getLocalValue(name: String): Any = { + | val idx = names.indexOf(name) + | if idx == -1 then throw new NoSuchElementException(name) + | else values(idx) + | } + | + | def setLocalValue(name: String, value: Any): Any = { + | val idx = names.indexOf(name) + | if idx == -1 then throw new NoSuchElementException(name) + | else values(idx) = value + | } + | + | def callMethod(obj: Any, className: String, methodName: String, paramTypesNames: Array[String], returnTypeName: String, args: Array[Object]): Any = { + | val clazz = classLoader.loadClass(className) + | val method = clazz.getDeclaredMethods + | .find { m => + | m.getName == methodName && + | m.getReturnType.getName == returnTypeName && + | m.getParameterTypes.map(_.getName).toSeq == paramTypesNames.toSeq + | } + | .getOrElse(throw new NoSuchMethodException(methodName)) + | method.setAccessible(true) + | val res = unwrapException(method.invoke(obj, args*)) + | if returnTypeName == "void" then () else res + | } + | + | def callConstructor(className: String, paramTypesNames: Array[String], args: Array[Object]): Any = { + | val clazz = classLoader.loadClass(className) + | val constructor = clazz.getConstructors + | .find { c => c.getParameterTypes.map(_.getName).toSeq == paramTypesNames.toSeq } + | .getOrElse(throw new NoSuchMethodException(s"new $$className")) + | constructor.setAccessible(true) + | unwrapException(constructor.newInstance(args*)) + | } + | + | def getField(obj: Any, className: String, fieldName: String): Any = { + | val clazz = classLoader.loadClass(className) + | val field = clazz.getDeclaredField(fieldName) + | field.setAccessible(true) + | field.get(obj) + | } + | + | def setField(obj: Any, className: String, fieldName: String, value: Any): Any = { + | val clazz = classLoader.loadClass(className) + | val field = clazz.getDeclaredField(fieldName) + | field.setAccessible(true) + | field.set(obj, value) + | } + | + | def getOuter(obj: Any, outerTypeName: String): Any = { + | val clazz = obj.getClass + | val field = getSuperclassIterator(clazz) + | .flatMap(_.getDeclaredFields.toSeq) + | .find { field => field.getName == "$$outer" && field.getType.getName == outerTypeName } + | .getOrElse(throw new NoSuchFieldException("$$outer")) + | field.setAccessible(true) + | field.get(obj) + | } + | + | def getStaticObject(className: String): Any = { + | val clazz = classLoader.loadClass(className) + | val field = clazz.getDeclaredField("MODULE$$") + | field.setAccessible(true) + | field.get(null) + | } + | + | def getSuperclassIterator(clazz: Class[?]): Iterator[Class[?]] = + | Iterator.iterate(clazz: Class[?] | Null)(_.nn.getSuperclass) + | .takeWhile(_ != null) + | .map(_.nn) + | + | // A fake method that is used as a placeholder in the extract-expression phase. + | // The resolve-reflect-eval phase resolves it to a call of one of the other methods in this class. + | def reflectEval(qualifier: Object, term: String, args: Array[Object]): Any = ??? + | + | private def unwrapException(f: => Any): Any = + | try f catch { + | case e: InvocationTargetException => throw e.getCause + | } + |} + |""".stripMargin + + override def run(using Context): Unit = + val inserter = Inserter(parseExpression, parseExpressionClass) + ctx.compilationUnit.untpdTree = inserter.transform(ctx.compilationUnit.untpdTree) + + private class Inserter(expression: Tree, expressionClass: Seq[Tree]) extends UntypedTreeMap: + override def transform(tree: Tree)(using Context): Tree = + tree match + case tree: PackageDef => + val transformed = super.transform(tree).asInstanceOf[PackageDef] + if expressionInserted then + // set to `false` to prevent inserting `Expression` class in other `PackageDef`s + expressionInserted = false + cpy.PackageDef(transformed)( + transformed.pid, + transformed.stats ++ expressionClass.map(_.withSpan(tree.span)) + ) + else transformed + case tree @ DefDef(name, paramss, tpt, rhs) if rhs != EmptyTree && isOnBreakpoint(tree) => + cpy.DefDef(tree)(name, paramss, tpt, mkExprBlock(expression, tree.rhs)) + case tree @ Match(selector, caseDefs) if isOnBreakpoint(tree) || caseDefs.exists(isOnBreakpoint) => + // the expression is on the match or a case of the match + // if it is on the case of the match the program could pause on the pattern, the guard or the body + // we assume it pauses on the pattern because that is the first instruction + // in that case we cannot compile the expression val in the pattern, but we can compile it in the selector + cpy.Match(tree)(mkExprBlock(expression, selector), caseDefs) + case tree @ ValDef(name, tpt, _) if isOnBreakpoint(tree) => + cpy.ValDef(tree)(name, tpt, mkExprBlock(expression, tree.rhs)) + case tree @ PatDef(mods, pat, tpt, rhs) if isOnBreakpoint(tree) => + PatDef(mods, pat, tpt, mkExprBlock(expression, rhs)) + case tree: (Ident | Select | GenericApply | Literal | This | New | InterpolatedString | OpTree | Tuple | + Assign | Block) if isOnBreakpoint(tree) => + mkExprBlock(expression, tree) + + // for loop: we insert the expression on the first enumeration + case tree @ ForYield(enums, rhs) if isOnBreakpoint(tree) => + ForYield(transform(enums.head) :: enums.tail, rhs) + case tree @ ForDo(enums, rhs) if isOnBreakpoint(tree) => + ForDo(transform(enums.head) :: enums.tail, rhs) + + // generator of for loop: we insert the expression on the rhs + case tree @ GenFrom(pat, rhs, checkMode) if isOnBreakpoint(tree) => + GenFrom(pat, mkExprBlock(expression, rhs), checkMode) + case tree @ GenAlias(pat, rhs) if isOnBreakpoint(tree) => + GenAlias(pat, mkExprBlock(expression, rhs)) + + case tree => super.transform(tree) + + private def parseExpression(using Context): Tree = + val prefix = + s"""|object Expression: + | { + | """.stripMargin + // don't use stripMargin on wrappedExpression because expression can contain a line starting with ` |` + val wrappedExpression = prefix + config.expression + "\n }\n" + val expressionFile = SourceFile.virtual("", config.expression) + val contentBytes = wrappedExpression.getBytes(StandardCharsets.UTF_8) + val wrappedExpressionFile = + new VirtualFile("", contentBytes) + val sourceFile = + new SourceFile(wrappedExpressionFile, wrappedExpression.toArray): + override def start: Int = -prefix.size + override def underlying: SourceFile = expressionFile + override def atSpan(span: Span): SourcePosition = + if (span.exists) SourcePosition(this, span) + else NoSourcePosition + + parse(sourceFile) + .asInstanceOf[PackageDef] + .stats + .head + .asInstanceOf[ModuleDef] + .impl + .body + .head + + private def parseExpressionClass(using Context): Seq[Tree] = + val sourceFile = SourceFile.virtual("", expressionClassSource) + parse(sourceFile).asInstanceOf[PackageDef].stats + + private def parse(sourceFile: SourceFile)(using Context): Tree = + val newCtx = ctx.fresh.setSource(sourceFile) + val parser = Parsers.Parser(sourceFile)(using newCtx) + parser.parse() + + private def isOnBreakpoint(tree: Tree)(using Context): Boolean = + val startLine = + if tree.span.exists then tree.sourcePos.startLine + 1 else -1 + startLine == config.breakpointLine + + private def mkExprBlock(expr: Tree, tree: Tree)(using Context): Tree = + if expressionInserted then + warnOrError("expression already inserted", tree.srcPos) + tree + else + expressionInserted = true + val valDef = ValDef(config.expressionTermName, TypeTree(), expr) + // we insert a fake effectful tree to avoid the constant-folding of the block during the firstTransform phase + val effect = Apply( + Select(Select(Ident(termName("scala")), termName("Predef")), termName("print")), + List(Literal(Constant(""))) + ) + Block(List(valDef, effect), tree) + + // only fails in test mode + private def warnOrError(msg: String, srcPos: SrcPos)(using Context): Unit = + if config.testMode then report.error(msg, srcPos) + else report.warning(msg, srcPos) + +private object InsertExpression: + val name: String = "insertExpression" diff --git a/compiler/src/dotty/tools/debug/JavaEncoding.scala b/compiler/src/dotty/tools/debug/JavaEncoding.scala new file mode 100644 index 000000000000..d461c7b0d23c --- /dev/null +++ b/compiler/src/dotty/tools/debug/JavaEncoding.scala @@ -0,0 +1,76 @@ +package dotty.tools.debug + +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.core.Flags.* +import dotty.tools.dotc.core.Names.* +import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions +import dotty.tools.dotc.util.NameTransformer + +// Inspired by https://github.com/lampepfl/dotty/blob/main/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala +private object JavaEncoding: + def encode(tpe: Type)(using Context): String = + tpe.widenDealias match + // Array type such as Array[Int] (kept by erasure) + case JavaArrayType(el) => s"[${binaryName(el)}" + case tpe: TypeRef => encode(tpe.symbol.asType) + case AnnotatedType(t, _) => encode(t) + + def encode(sym: TypeSymbol)(using Context): String = + /* When compiling Array.scala, the type parameter T is not erased and shows up in method + * signatures, e.g. `def apply(i: Int): T`. A TypeRef to T is replaced by ObjectReference. + */ + if !sym.isClass then "java.lang.Object" + else if sym.isPrimitiveValueClass then primitiveName(sym) + else className(sym) + + def encode(name: TermName)(using Context): String = + NameTransformer.encode(name.toSimpleName).toString + + private def binaryName(tpe: Type)(using Context): String = + tpe match + case JavaArrayType(el) => s"[${binaryName(el)}" + case tpe: TypeRef => + if tpe.symbol.isPrimitiveValueClass then primitiveBinaryName(tpe.symbol) + else classBinaryName(tpe.symbol) + case AnnotatedType(t, _) => binaryName(t) + + private def primitiveName(sym: Symbol)(using Context): String = + if sym == defn.UnitClass then "void" + else if sym == defn.BooleanClass then "boolean" + else if sym == defn.CharClass then "char" + else if sym == defn.ByteClass then "byte" + else if sym == defn.ShortClass then "short" + else if sym == defn.IntClass then "int" + else if sym == defn.LongClass then "long" + else if sym == defn.FloatClass then "float" + else if sym == defn.DoubleClass then "double" + else throw new Exception(s"Unknown primitive value class $sym") + + private def primitiveBinaryName(sym: Symbol)(using Context): String = + if sym == defn.BooleanClass then "Z" + else if sym == defn.CharClass then "C" + else if sym == defn.ByteClass then "B" + else if sym == defn.ShortClass then "S" + else if sym == defn.IntClass then "I" + else if sym == defn.LongClass then "J" + else if sym == defn.FloatClass then "F" + else if sym == defn.DoubleClass then "D" + else throw new Exception(s"Unknown primitive value class $sym") + + private def className(sym: Symbol)(using Context): String = + val sym1 = + if (sym.isAllOf(ModuleClass | JavaDefined)) sym.linkedClass + else sym + + /* Some re-wirings: + * - scala.Nothing to scala.runtime.Nothing$. + * - scala.Null to scala.runtime.Null$. + */ + if sym1 == defn.NothingClass then "scala.runtime.Nothing$" + else if sym1 == defn.NullClass then "scala.runtime.Null$" + else sym1.javaClassName + + private def classBinaryName(sym: Symbol)(using Context): String = + s"L${className(sym)};" diff --git a/compiler/src/dotty/tools/debug/ReflectEvalStrategy.scala b/compiler/src/dotty/tools/debug/ReflectEvalStrategy.scala new file mode 100644 index 000000000000..36ef2327965e --- /dev/null +++ b/compiler/src/dotty/tools/debug/ReflectEvalStrategy.scala @@ -0,0 +1,29 @@ +package dotty.tools.debug + +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.util.Property.* + +/** + * The [[ExtractExpression]] phase attaches an [[ReflectEvalStrategy]] to each `reflectEval` node + * capturing information about the term that requires evaluation via reflection (because it is + * inaccessible from the evaluation class). + * Subsequently, the [[ResolveReflectEval]] phase converts each evaluation strategy into a method + * call within the expression class. + */ +private enum ReflectEvalStrategy: + case This(cls: ClassSymbol) + case Outer(outerCls: ClassSymbol) + case LocalOuter(outerCls: ClassSymbol) // the $outer param in a constructor + case LocalValue(variable: TermSymbol, isByName: Boolean) + case LocalValueAssign(variable: TermSymbol) + case MethodCapture(variable: TermSymbol, method: TermSymbol, isByName: Boolean) + case MethodCaptureAssign(variable: TermSymbol, method: TermSymbol) + case ClassCapture(variable: TermSymbol, cls: ClassSymbol, isByName: Boolean) + case ClassCaptureAssign(variable: TermSymbol, cls: ClassSymbol) + case StaticObject(obj: ClassSymbol) + case Field(field: TermSymbol, isByName: Boolean) + case FieldAssign(field: TermSymbol) + case MethodCall(method: TermSymbol) + case ConstructorCall(ctr: TermSymbol, cls: ClassSymbol) + +object ReflectEvalStrategy extends StickyKey[ReflectEvalStrategy] diff --git a/compiler/src/dotty/tools/debug/ResolveReflectEval.scala b/compiler/src/dotty/tools/debug/ResolveReflectEval.scala new file mode 100644 index 000000000000..f79aa462fcb4 --- /dev/null +++ b/compiler/src/dotty/tools/debug/ResolveReflectEval.scala @@ -0,0 +1,374 @@ +package dotty.tools.debug + +import dotty.tools.dotc.core.SymUtils +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Flags.* +import dotty.tools.dotc.core.NameKinds.QualifiedInfo +import dotty.tools.dotc.core.Names.* +import dotty.tools.dotc.core.Phases +import dotty.tools.dotc.core.StdNames.* +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.core.TypeErasure.ErasedValueType +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.report +import dotty.tools.dotc.transform.MegaPhase.MiniPhase +import dotty.tools.dotc.transform.ValueClasses + +/** + * This phase transforms every reflectEval call to an actual method call that performs reflection. + * Specifically it does: + * - encode symbols to Java + * - box and unbox value classes where necessary + * - box and unbox captured variables where necessary + * - evaluate by-name params where necessary + * - resolve captured variables and check they are available (they may not be captured at runtime) + * + * Before: + * this.reflectEval(a, "ReflectEvalStrategy.MethodCall(m)", args) + * + * After: + * this.callMethod(a, "example.A", "m", ["ArgType1", "ArgType2"], "ResType", args) + * + */ +private class ResolveReflectEval(config: ExpressionCompilerConfig, expressionStore: ExpressionStore) extends MiniPhase: + private val reflectEvalName = termName("reflectEval") + private val elemName = termName("elem") + override def phaseName: String = ResolveReflectEval.name + + override def transformTypeDef(tree: TypeDef)(using Context): Tree = + ExpressionTransformer.transform(tree) + + object ExpressionTransformer extends TreeMap: + override def transform(tree: Tree)(using Context): Tree = + tree match + case tree: DefDef if tree.symbol == config.evaluateMethod => + // unbox the result of the `evaluate` method if it is a value class + val gen = new Gen( + Apply( + Select(This(config.expressionClass), reflectEvalName), + List(nullLiteral, nullLiteral, nullLiteral) + ) + ) + val rhs = gen.unboxIfValueClass(expressionStore.symbol.nn, transform(tree.rhs)) + cpy.DefDef(tree)(rhs = rhs) + + case reflectEval: Apply if isReflectEval(reflectEval.fun.symbol) => + val qualifier :: _ :: argsTree :: Nil = reflectEval.args.map(transform): @unchecked + val args = argsTree.asInstanceOf[JavaSeqLiteral].elems + val gen = new Gen(reflectEval) + tree.attachment(ReflectEvalStrategy) match + case ReflectEvalStrategy.This(cls) => gen.getThisObject + case ReflectEvalStrategy.LocalOuter(cls) => gen.getLocalValue("$outer") + case ReflectEvalStrategy.Outer(outerCls) => gen.getOuter(qualifier, outerCls) + + case ReflectEvalStrategy.LocalValue(variable, isByName) => + val variableName = JavaEncoding.encode(variable.name) + val rawLocalValue = gen.getLocalValue(variableName) + val localValue = if isByName then gen.evaluateByName(rawLocalValue) else rawLocalValue + val derefLocalValue = gen.derefCapturedVar(localValue, variable) + gen.boxIfValueClass(variable, derefLocalValue) + + case ReflectEvalStrategy.LocalValueAssign(variable) => + val value = gen.unboxIfValueClass(variable, args.head) + val typeSymbol = variable.info.typeSymbol.asType + val variableName = JavaEncoding.encode(variable.name) + JavaEncoding.encode(typeSymbol) match + case s"scala.runtime.${_}Ref" => + val elemField = typeSymbol.info.decl(elemName).symbol + gen.setField(tree)( + gen.getLocalValue(variableName), + elemField.asTerm, + value + ) + case _ => gen.setLocalValue(variableName, value) + + case ReflectEvalStrategy.ClassCapture(variable, cls, isByName) => + val rawCapture = gen + .getClassCapture(tree)(qualifier, variable.name, cls) + .getOrElse { + report.error(s"No capture found for $variable in $cls", tree.srcPos) + ref(defn.Predef_undefined) + } + val capture = if isByName then gen.evaluateByName(rawCapture) else rawCapture + val capturedValue = gen.derefCapturedVar(capture, variable) + gen.boxIfValueClass(variable, capturedValue) + + case ReflectEvalStrategy.ClassCaptureAssign(variable, cls) => + val capture = gen + .getClassCapture(tree)(qualifier, variable.name, cls) + .getOrElse { + report.error(s"No capture found for $variable in $cls", tree.srcPos) + ref(defn.Predef_undefined) + } + val value = gen.unboxIfValueClass(variable, args.head) + val typeSymbol = variable.info.typeSymbol + val elemField = typeSymbol.info.decl(elemName).symbol + gen.setField(tree)(capture, elemField.asTerm, value) + + case ReflectEvalStrategy.MethodCapture(variable, method, isByName) => + val rawCapture = gen + .getMethodCapture(method, variable.name) + .getOrElse { + report.error(s"No capture found for $variable in $method", tree.srcPos) + ref(defn.Predef_undefined) + } + val capture = if isByName then gen.evaluateByName(rawCapture) else rawCapture + val capturedValue = gen.derefCapturedVar(capture, variable) + gen.boxIfValueClass(variable, capturedValue) + + case ReflectEvalStrategy.MethodCaptureAssign(variable, method) => + val capture = gen + .getMethodCapture(method, variable.name) + .getOrElse { + report.error(s"No capture found for $variable in $method", tree.srcPos) + ref(defn.Predef_undefined) + } + val value = gen.unboxIfValueClass(variable, args.head) + val typeSymbol = variable.info.typeSymbol + val elemField = typeSymbol.info.decl(elemName).symbol + gen.setField(tree)(capture, elemField.asTerm, value) + + case ReflectEvalStrategy.StaticObject(obj) => gen.getStaticObject(obj) + + case ReflectEvalStrategy.Field(field, isByName) => + // if the field is lazy, if it is private in a value class or a trait + // then we must call the getter method + val fieldValue = + if field.is(Lazy) || field.owner.isValueClass || field.owner.is(Trait) + then gen.callMethod(tree)(qualifier, field.getter.asTerm, Nil) + else + val rawValue = gen.getField(tree)(qualifier, field) + if isByName then gen.evaluateByName(rawValue) else rawValue + gen.boxIfValueClass(field, fieldValue) + + case ReflectEvalStrategy.FieldAssign(field) => + val arg = gen.unboxIfValueClass(field, args.head) + if field.owner.is(Trait) then + gen.callMethod(tree)(qualifier, field.setter.asTerm, List(arg)) + else gen.setField(tree)(qualifier, field, arg) + + case ReflectEvalStrategy.MethodCall(method) => gen.callMethod(tree)(qualifier, method, args) + case ReflectEvalStrategy.ConstructorCall(ctor, cls) => gen.callConstructor(tree)(qualifier, ctor, args) + case _ => super.transform(tree) + + private def isReflectEval(symbol: Symbol)(using Context): Boolean = + symbol.name == reflectEvalName && symbol.owner == config.expressionClass + + class Gen(reflectEval: Apply)(using Context): + private val expressionThis = reflectEval.fun.asInstanceOf[Select].qualifier + + def derefCapturedVar(tree: Tree, term: TermSymbol): Tree = + val typeSymbol = term.info.typeSymbol.asType + JavaEncoding.encode(typeSymbol) match + case s"scala.runtime.${_}Ref" => + val elemField = typeSymbol.info.decl(elemName).symbol + getField(tree)(tree, elemField.asTerm) + case _ => tree + + def boxIfValueClass(term: TermSymbol, tree: Tree): Tree = + getErasedValueType(atPhase(Phases.elimErasedValueTypePhase)(term.info)) match + case Some(erasedValueType) => + boxValueClass(erasedValueType.tycon.typeSymbol.asClass, tree) + case None => tree + + def boxValueClass(valueClass: ClassSymbol, tree: Tree): Tree = + // qualifier is null: a value class cannot be nested into a class + val ctor = valueClass.primaryConstructor.asTerm + callConstructor(tree)(nullLiteral, ctor, List(tree)) + + def unboxIfValueClass(term: TermSymbol, tree: Tree): Tree = + getErasedValueType(atPhase(Phases.elimErasedValueTypePhase)(term.info)) match + case Some(erasedValueType) => unboxValueClass(tree, erasedValueType) + case None => tree + + private def getErasedValueType(tpe: Type): Option[ErasedValueType] = tpe match + case tpe: ErasedValueType => Some(tpe) + case tpe: MethodOrPoly => getErasedValueType(tpe.resultType) + case tpe => None + + private def unboxValueClass(tree: Tree, tpe: ErasedValueType): Tree = + val cls = tpe.tycon.typeSymbol.asClass + val unboxMethod = ValueClasses.valueClassUnbox(cls).asTerm + callMethod(tree)(tree, unboxMethod, Nil) + + def getThisObject: Tree = + Apply(Select(expressionThis, termName("getThisObject")), Nil) + + def getLocalValue(name: String): Tree = + Apply( + Select(expressionThis, termName("getLocalValue")), + List(Literal(Constant(name))) + ) + + def setLocalValue(name: String, value: Tree): Tree = + Apply( + Select(expressionThis, termName("setLocalValue")), + List(Literal(Constant(name)), value) + ) + + def getOuter(qualifier: Tree, outerCls: ClassSymbol): Tree = + Apply( + Select(expressionThis, termName("getOuter")), + List(qualifier, Literal(Constant(JavaEncoding.encode(outerCls)))) + ) + + def getClassCapture(tree: Tree)(qualifier: Tree, originalName: Name, cls: ClassSymbol): Option[Tree] = + cls.info.decls.iterator + .filter(term => term.isField) + .find { field => + field.name match + case DerivedName(underlying, _) if field.isPrivate => + underlying == originalName + case DerivedName(DerivedName(_, info: QualifiedInfo), _) => + info.name == originalName + case _ => false + } + .map(field => getField(tree: Tree)(qualifier, field.asTerm)) + + def getMethodCapture(method: TermSymbol, originalName: TermName): Option[Tree] = + val methodType = method.info.asInstanceOf[MethodType] + methodType.paramNames + .collectFirst { case name @ DerivedName(n, _) if n == originalName => name } + .map(param => getLocalValue(JavaEncoding.encode(param))) + + def getStaticObject(obj: ClassSymbol): Tree = + Apply( + Select(expressionThis, termName("getStaticObject")), + List(Literal(Constant(JavaEncoding.encode(obj)))) + ) + + def getField(tree: Tree)(qualifier: Tree, field: TermSymbol): Tree = + if field.owner.isTerm then + report.error(s"Cannot access local val ${field.name} in ${field.owner} as field", tree.srcPos) + ref(defn.Predef_undefined) + else + Apply( + Select(expressionThis, termName("getField")), + List( + qualifier, + Literal(Constant(JavaEncoding.encode(field.owner.asType))), + Literal(Constant(JavaEncoding.encode(field.name))) + ) + ) + + def setField(tree: Tree)(qualifier: Tree, field: TermSymbol, value: Tree): Tree = + if field.owner.isTerm then + report.error(s"Cannot access local var ${field.name} in ${field.owner} as field", tree.srcPos) + ref(defn.Predef_undefined) + else + Apply( + Select(expressionThis, termName("setField")), + List( + qualifier, + Literal(Constant(JavaEncoding.encode(field.owner.asType))), + Literal(Constant(JavaEncoding.encode(field.name))), + value + ) + ) + + def evaluateByName(function: Tree): Tree = + val castFunction = function.cast(defn.Function0.typeRef.appliedTo(defn.AnyType)) + Apply(Select(castFunction, termName("apply")), List()) + + def callMethod(tree: Tree)(qualifier: Tree, method: TermSymbol, args: List[Tree]): Tree = + val methodType = method.info.asInstanceOf[MethodType] + val paramTypesNames = methodType.paramInfos.map(JavaEncoding.encode) + val paramTypesArray = JavaSeqLiteral( + paramTypesNames.map(t => Literal(Constant(t))), + TypeTree(ctx.definitions.StringType) + ) + + def unknownCapture(name: Name): Tree = + report.error(s"Unknown captured variable $name in $method", reflectEval.srcPos) + ref(defn.Predef_undefined) + val capturedArgs = methodType.paramNames.dropRight(args.size).map { + case name @ DerivedName(underlying, _) => capturedValue(tree)(method, underlying).getOrElse(unknownCapture(name)) + case name => unknownCapture(name) + } + + val erasedMethodInfo = atPhase(Phases.elimErasedValueTypePhase)(method.info).asInstanceOf[MethodType] + val unboxedArgs = erasedMethodInfo.paramInfos.takeRight(args.size).zip(args).map { + case (tpe: ErasedValueType, arg) => unboxValueClass(arg, tpe) + case (_, arg) => arg + } + + val returnTypeName = JavaEncoding.encode(methodType.resType) + val methodName = JavaEncoding.encode(method.name) + val result = Apply( + Select(expressionThis, termName("callMethod")), + List( + qualifier, + Literal(Constant(JavaEncoding.encode(method.owner.asType))), + Literal(Constant(methodName)), + paramTypesArray, + Literal(Constant(returnTypeName)), + JavaSeqLiteral(capturedArgs ++ unboxedArgs, TypeTree(ctx.definitions.ObjectType)) + ) + ) + erasedMethodInfo.resType match + case tpe: ErasedValueType => boxValueClass(tpe.tycon.typeSymbol.asClass, result) + case _ => result + end callMethod + + def callConstructor(tree: Tree)(qualifier: Tree, ctor: TermSymbol, args: List[Tree]): Tree = + val methodType = ctor.info.asInstanceOf[MethodType] + val paramTypesNames = methodType.paramInfos.map(JavaEncoding.encode) + val clsName = JavaEncoding.encode(methodType.resType) + + val capturedArgs = + methodType.paramNames.dropRight(args.size).map { + case outer if outer == nme.OUTER => qualifier + case name @ DerivedName(underlying, _) => + // if derived then probably a capture + capturedValue(tree: Tree)(ctor.owner, underlying) + .getOrElse { + report.error(s"Unknown captured variable $name in $ctor of ${ctor.owner}", reflectEval.srcPos) + ref(defn.Predef_undefined) + } + case name => + val paramName = JavaEncoding.encode(name) + getLocalValue(paramName) + } + + val erasedCtrInfo = atPhase(Phases.elimErasedValueTypePhase)(ctor.info) + .asInstanceOf[MethodType] + val unboxedArgs = erasedCtrInfo.paramInfos.takeRight(args.size).zip(args).map { + case (tpe: ErasedValueType, arg) => unboxValueClass(arg, tpe) + case (_, arg) => arg + } + + val paramTypesArray = JavaSeqLiteral( + paramTypesNames.map(t => Literal(Constant(t))), + TypeTree(ctx.definitions.StringType) + ) + Apply( + Select(expressionThis, termName("callConstructor")), + List( + Literal(Constant(clsName)), + paramTypesArray, + JavaSeqLiteral(capturedArgs ++ unboxedArgs, TypeTree(ctx.definitions.ObjectType)) + ) + ) + end callConstructor + + private def capturedValue(tree: Tree)(sym: Symbol, originalName: TermName): Option[Tree] = + val encodedName = JavaEncoding.encode(originalName) + if expressionStore.classOwners.contains(sym) then capturedByClass(tree: Tree)(sym.asClass, originalName) + else if config.localVariables.contains(encodedName) then Some(getLocalValue(encodedName)) + else + // if the captured value is not a local variables + // then it must have been captured by the outer method + expressionStore.capturingMethod.flatMap(getMethodCapture(_, originalName)) + + private def capturedByClass(tree: Tree)(cls: ClassSymbol, originalName: TermName): Option[Tree] = + val target = expressionStore.classOwners.indexOf(cls) + val qualifier = expressionStore.classOwners + .drop(1) + .take(target) + .foldLeft(getThisObject)((q, cls) => getOuter(q, cls)) + getClassCapture(tree: Tree)(qualifier, originalName, cls) + +private object ResolveReflectEval: + val name = "resolvReflectEval" diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index 0d755797d026..9c985ecd84b3 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -59,6 +59,9 @@ class CompilationUnit protected (val source: SourceFile, val info: CompilationUn var hasMacroAnnotations: Boolean = false + def hasUnrollDefs: Boolean = unrolledClasses.nonEmpty + var unrolledClasses: Set[Symbol] = Set.empty + /** Set to `true` if inliner added anonymous mirrors that need to be completed */ var needsMirrorSupport: Boolean = false diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index d8ba1ab5dc2e..6aab7d54d59e 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -7,9 +7,8 @@ import typer.{TyperPhase, RefChecks} import parsing.Parser import Phases.Phase import transform.* -import dotty.tools.backend import backend.jvm.{CollectSuperCalls, GenBCode} -import localopt.StringInterpolatorOpt +import localopt.{StringInterpolatorOpt, DropForMap} /** The central class of the dotc compiler. The job of a compiler is to create * runs, which process given `phases` in a given `rootContext`. @@ -34,12 +33,12 @@ class Compiler { protected def frontendPhases: List[List[Phase]] = List(new Parser) :: // Compiler frontend: scanner, parser List(new TyperPhase) :: // Compiler frontend: namer, typer - List(new CheckUnused.PostTyper) :: // Check for unused elements - List(new CheckShadowing) :: // Check shadowing elements + List(CheckUnused.PostTyper(), CheckShadowing()) :: // Check for unused, shadowed elements List(new YCheckPositions) :: // YCheck positions List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks List(new semanticdb.ExtractSemanticDB.ExtractSemanticInfo) :: // Extract info into .semanticdb files List(new PostTyper) :: // Additional checks and cleanups after type checking + List(new UnrollDefinitions) :: // Unroll annotated methods if detected in PostTyper List(new sjs.PrepJSInterop) :: // Additional checks and transformations for Scala.js (Scala.js only) List(new SetRootTree) :: // Set the `rootTreeOrProvider` on class symbols Nil @@ -50,10 +49,10 @@ class Compiler { List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks List(new Inlining) :: // Inline and execute macros List(new PostInlining) :: // Add mirror support for inlined code - List(new CheckUnused.PostInlining) :: // Check for unused elements List(new Staging) :: // Check staging levels and heal staged types List(new Splicing) :: // Replace level 1 splices with holes List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures + List(new CheckUnused.PostInlining) :: // Check for unused elements Nil /** Phases dealing with the transformation from pickled trees to backend trees */ @@ -69,7 +68,8 @@ class Compiler { new InlineVals, // Check right hand-sides of an `inline val`s new ExpandSAMs, // Expand single abstract method closures to anonymous classes new ElimRepeated, // Rewrite vararg parameters and arguments - new RefChecks) :: // Various checks mostly related to abstract members and overriding + new RefChecks, // Various checks mostly related to abstract members and overriding + new DropForMap) :: // Drop unused trailing map calls in for comprehensions List(new semanticdb.ExtractSemanticDB.AppendDiagnostics) :: // Attach warnings to extracted SemanticDB and write to .semanticdb file List(new init.Checker) :: // Check initialization of objects List(new ProtectedAccessors, // Add accessors for protected members diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 50fd668c7696..d0fe07303e41 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -17,20 +17,20 @@ import Phases.{unfusedPhases, Phase} import sbt.interfaces.ProgressCallback import util.* -import reporting.{Suppression, Action, Profile, ActiveProfile, NoProfile} -import reporting.Diagnostic -import reporting.Diagnostic.Warning +import reporting.{Suppression, Action, Profile, ActiveProfile, MessageFilter, NoProfile, WConf} +import reporting.Diagnostic, Diagnostic.Warning import rewrites.Rewrites import profile.Profiler import printing.XprintMode import typer.ImplicitRunInfo import config.Feature import StdNames.nme +import Spans.Span import java.io.{BufferedWriter, OutputStreamWriter} import java.nio.charset.StandardCharsets -import scala.collection.mutable +import scala.collection.mutable, mutable.ListBuffer import scala.util.control.NonFatal import scala.io.Codec @@ -38,6 +38,8 @@ import Run.Progress import scala.compiletime.uninitialized import dotty.tools.dotc.transform.MegaPhase import dotty.tools.dotc.transform.Pickler.AsyncTastyHolder +import dotty.tools.dotc.util.chaining.* +import java.util.{Timer, TimerTask} /** A compiler run. Exports various methods to compile source files */ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with ConstraintRunInfo { @@ -69,7 +71,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint private var myFiles: Set[AbstractFile] = uninitialized // `@nowarn` annotations by source file, populated during typer - private val mySuppressions: mutable.LinkedHashMap[SourceFile, mutable.ListBuffer[Suppression]] = mutable.LinkedHashMap.empty + private val mySuppressions: mutable.LinkedHashMap[SourceFile, ListBuffer[Suppression]] = mutable.LinkedHashMap.empty // source files whose `@nowarn` annotations are processed private val mySuppressionsComplete: mutable.Set[SourceFile] = mutable.Set.empty // warnings issued before a source file's `@nowarn` annotations are processed, suspended so that `@nowarn` can filter them @@ -98,9 +100,30 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint Action.Warning } + def registerNowarn(annotPos: SourcePosition, range: Span)(conf: String, pos: SrcPos)(using Context): Unit = + var verbose = false + val filters = conf match + case "" => + List(MessageFilter.Any) + case "none" => + List(MessageFilter.None) + case "verbose" | "v" => + verbose = true + List(MessageFilter.Any) + case conf => + WConf.parseFilters(conf).left.map: parseErrors => + report.warning(s"Invalid message filter\n${parseErrors.mkString("\n")}", pos) + List(MessageFilter.None) + .merge + addSuppression: + Suppression(annotPos, filters, range.start, range.end, verbose) + .tap: sup => + if filters == List(MessageFilter.None) then sup.markUsed() // invalid suppressions, don't report as unused + def addSuppression(sup: Suppression): Unit = - val source = sup.annotPos.source - mySuppressions.getOrElseUpdate(source, mutable.ListBuffer.empty) += sup + val suppressions = mySuppressions.getOrElseUpdate(sup.annotPos.source, ListBuffer.empty) + if sup.start != sup.end && suppressions.forall(x => x.start != sup.start || x.end != sup.end) then + suppressions += sup def reportSuspendedMessages(source: SourceFile)(using Context): Unit = { // sort suppressions. they are not added in any particular order because of lazy type completion @@ -115,11 +138,12 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint mySuspendedMessages.keysIterator.toList.foreach(reportSuspendedMessages) // report unused nowarns only if all all phases are done if !hasErrors && ctx.settings.WunusedHas.nowarn then - for { + for source <- mySuppressions.keysIterator.toList sups <- mySuppressions.remove(source) sup <- sups.reverse - } if (!sup.used) + if !sup.used + do report.warning("@nowarn annotation does not suppress any warnings", sup.annotPos) /** The compilation units currently being compiled, this may return different @@ -130,7 +154,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint private def units_=(us: List[CompilationUnit]): Unit = myUnits = us - var suspendedUnits: mutable.ListBuffer[CompilationUnit] = mutable.ListBuffer() + var suspendedUnits: ListBuffer[CompilationUnit] = ListBuffer.empty var suspendedHints: mutable.Map[CompilationUnit, (String, Boolean)] = mutable.HashMap() /** Were any units suspended in the typer phase? if so then pipeline tasty can not complete. */ @@ -172,7 +196,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint val staticRefs = util.EqHashMap[Name, Denotation](initialCapacity = 1024) /** Actions that need to be performed at the end of the current compilation run */ - private var finalizeActions = mutable.ListBuffer[() => Unit]() + private var finalizeActions = ListBuffer.empty[() => Unit] private var _progress: Progress | Null = null // Set if progress reporting is enabled @@ -380,7 +404,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint initializeAsyncTasty() else () => {} - runPhases(allPhases = fusedPhases)(using runCtx) + showProgress(runPhases(allPhases = fusedPhases)(using runCtx)) cancelAsyncTasty() ctx.reporter.finalizeReporting() @@ -431,6 +455,26 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint process()(using unitCtx) } + /** If set to true, prints every 10 seconds the files currently being compiled. + * Turn this flag on if you want to find out which test among many takes more time + * to compile than the others or causes an infinite loop in the compiler. + */ + private inline val debugPrintProgress = false + + /** Period between progress reports, in ms */ + private inline val printProgressPeriod = 10000 + + /** Shows progress if debugPrintProgress is true */ + private def showProgress(proc: => Unit)(using Context): Unit = + if !debugPrintProgress then proc + else + val watchdog = new TimerTask: + def run() = println(i"[compiling $units]") + try + new Timer().schedule(watchdog, printProgressPeriod, printProgressPeriod) + proc + finally watchdog.cancel() + private sealed trait PrintedTree private /*final*/ case class SomePrintedTree(phase: String, tree: String) extends PrintedTree private object NoPrintedTree extends PrintedTree diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 4ddd0006dc26..c235143e97f1 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -11,17 +11,15 @@ import NameKinds.{UniqueName, ContextBoundParamName, ContextFunctionParamName, D import typer.{Namer, Checking} import util.{Property, SourceFile, SourcePosition, SrcPos, Chars} import config.{Feature, Config} -import config.Feature.{sourceVersion, migrateTo3, enabled, betterForsEnabled} +import config.Feature.{sourceVersion, migrateTo3, enabled} import config.SourceVersion.* import collection.mutable import reporting.* -import annotation.constructorOnly import printing.Formatting.hl import config.Printers import parsing.Parsers -import scala.annotation.internal.sharable -import scala.annotation.threadUnsafe +import scala.annotation.{unchecked as _, *}, internal.sharable object desugar { import untpd.* @@ -47,6 +45,14 @@ object desugar { */ val UntupledParam: Property.Key[Unit] = Property.StickyKey() + /** An attachment key to indicate that a ValDef originated from a pattern. + */ + val PatternVar: Property.Key[Unit] = Property.StickyKey() + + /** An attachment key for Trees originating in for-comprehension, such as tupling of assignments. + */ + val ForArtifact: Property.Key[Unit] = Property.StickyKey() + /** An attachment key to indicate that a ValDef is an evidence parameter * for a context bound. */ @@ -56,6 +62,11 @@ object desugar { */ val PolyFunctionApply: Property.Key[Unit] = Property.StickyKey() + /** An attachment key to indicate that an Apply is created as a last `map` + * scall in a for-comprehension. + */ + val TrailingForMap: Property.Key[Unit] = Property.StickyKey() + /** What static check should be applied to a Match? */ enum MatchCheck { case None, Exhaustive, IrrefutablePatDef, IrrefutableGenFrom @@ -272,18 +283,20 @@ object desugar { case ContextBounds(tbounds, ctxbounds) => val isMember = evidenceFlags.isAllOf(DeferredGivenFlags) for bound <- ctxbounds do - val evidenceName = bound match + val (evidenceName, spanPoint) = bound match case ContextBoundTypeTree(_, _, ownName) if !ownName.isEmpty => - ownName // if there is an explicitly given name, use it. + val realName = ownName.stripModuleClassSuffix.lastPart + (ownName, bound.span.end - realName.length) // if there is an explicitly given name, use it. case _ => if Config.nameSingleContextBounds && !isMember && ctxbounds.tail.isEmpty && Feature.enabled(Feature.modularity) - then tdef.name.toTermName - else freshName(bound) + then (tdef.name.toTermName, bound.span.point) + else (freshName(bound), bound.span.point) evidenceNames += evidenceName - val evidenceParam = ValDef(evidenceName, bound, EmptyTree).withFlags(evidenceFlags) + val evidenceParam = + ValDef(evidenceName, bound, EmptyTree).withFlags(evidenceFlags).withSpan(bound.span.withPoint(spanPoint)) evidenceParam.pushAttachment(ContextBoundParam, ()) evidenceBuf += evidenceParam tbounds @@ -408,6 +421,7 @@ object desugar { .withMods(Modifiers( meth.mods.flags & (AccessFlags | Synthetic) | (vparam.mods.flags & Inline), meth.mods.privateWithin)) + .withSpan(vparam.rhs.span) val rest = defaultGetters(vparams :: paramss1, n + 1) if vparam.rhs.isEmpty then rest else defaultGetter :: rest case _ :: paramss1 => // skip empty parameter lists and type parameters @@ -857,7 +871,7 @@ object desugar { val nu = vparamss.foldLeft(makeNew(classTypeRef)) { (nu, vparams) => val app = Apply(nu, vparams.map(refOfDef)) vparams match { - case vparam :: _ if vparam.mods.is(Given) || vparam.name.is(ContextBoundParamName) => + case vparam :: _ if vparam.mods.isOneOf(GivenOrImplicit) || vparam.name.is(ContextBoundParamName) => app.setApplyKind(ApplyKind.Using) case _ => app } @@ -1505,7 +1519,7 @@ object desugar { val matchExpr = if (tupleOptimizable) rhs else - val caseDef = CaseDef(pat, EmptyTree, makeTuple(ids)) + val caseDef = CaseDef(pat, EmptyTree, makeTuple(ids).withAttachment(ForArtifact, ())) Match(makeSelector(rhs, MatchCheck.IrrefutablePatDef), caseDef :: Nil) vars match { case Nil if !mods.is(Lazy) => @@ -1530,11 +1544,13 @@ object desugar { DefDef(named.name.asTermName, Nil, tpt, selector(n)) .withMods(mods &~ Lazy) .withSpan(named.span) + .withAttachment(PatternVar, ()) else valDef( ValDef(named.name.asTermName, tpt, selector(n)) .withMods(mods) .withSpan(named.span) + .withAttachment(PatternVar, ()) ) flatTree(firstDef :: restDefs) } @@ -1920,10 +1936,12 @@ object desugar { val vdef = ValDef(named.name.asTermName, tpt, rhs) .withMods(mods) .withSpan(original.span.withPoint(named.span.start)) + .withAttachment(PatternVar, ()) val mayNeedSetter = valDef(vdef) mayNeedSetter } + @unused private def derivedDefDef(original: Tree, named: NameTree, tpt: Tree, rhs: Tree, mods: Modifiers)(implicit src: SourceFile) = DefDef(named.name.asTermName, Nil, tpt, rhs) .withMods(mods) @@ -1954,14 +1972,8 @@ object desugar { * * 3. * - * for (P <- G) yield P ==> G - * - * If betterFors is enabled, P is a variable or a tuple of variables and G is not a withFilter. - * * for (P <- G) yield E ==> G.map (P => E) * - * Otherwise - * * 4. * * for (P_1 <- G_1; P_2 <- G_2; ...) ... @@ -2032,6 +2044,16 @@ object desugar { makeCaseLambda(CaseDef(gen.pat, EmptyTree, body) :: Nil, matchCheckMode) } + def hasGivenBind(pat: Tree): Boolean = pat.existsSubTree { + case pat @ Bind(_, pat1) => pat.mods.is(Given) + case _ => false + } + + /** Does this pattern define any given bindings */ + def isNestedGivenPattern(pat: Tree): Boolean = pat match + case pat @ Bind(_, pat1) => hasGivenBind(pat1) + case _ => hasGivenBind(pat) + /** If `pat` is not an Identifier, a Typed(Ident, _), or a Bind, wrap * it in a Bind with a fresh name. Return the transformed pattern, and the identifier * that refers to the bound variable for the pattern. Wildcard Binds are @@ -2134,25 +2156,34 @@ object desugar { case (Tuple(ts1), Tuple(ts2)) => ts1.corresponds(ts2)(deepEquals) case _ => false + def markTrailingMap(aply: Apply, gen: GenFrom, selectName: TermName): Unit = + if sourceVersion.enablesBetterFors + && selectName == mapName + && gen.checkMode != GenCheckMode.Filtered // results of withFilter have the wrong type + && (deepEquals(gen.pat, body) || deepEquals(body, Tuple(Nil))) + then + aply.putAttachment(TrailingForMap, ()) + enums match { - case Nil if betterForsEnabled => body + case Nil if sourceVersion.enablesBetterFors => body case (gen: GenFrom) :: Nil => - if betterForsEnabled - && gen.checkMode != GenCheckMode.Filtered // results of withFilter have the wrong type - && deepEquals(gen.pat, body) - then gen.expr // avoid a redundant map with identity - else Apply(rhsSelect(gen, mapName), makeLambda(gen, body)) + val aply = Apply(rhsSelect(gen, mapName), makeLambda(gen, body)) + markTrailingMap(aply, gen, mapName) + aply case (gen: GenFrom) :: (rest @ (GenFrom(_, _, _) :: _)) => val cont = makeFor(mapName, flatMapName, rest, body) Apply(rhsSelect(gen, flatMapName), makeLambda(gen, cont)) case (gen: GenFrom) :: rest - if betterForsEnabled - && rest.dropWhile(_.isInstanceOf[GenAlias]).headOption.forall(e => e.isInstanceOf[GenFrom]) => // possible aliases followed by a generator or end of for + if sourceVersion.enablesBetterFors + && rest.dropWhile(_.isInstanceOf[GenAlias]).headOption.forall(e => e.isInstanceOf[GenFrom]) // possible aliases followed by a generator or end of for + && !rest.takeWhile(_.isInstanceOf[GenAlias]).exists(a => isNestedGivenPattern(a.asInstanceOf[GenAlias].pat)) => val cont = makeFor(mapName, flatMapName, rest, body) val selectName = if rest.exists(_.isInstanceOf[GenFrom]) then flatMapName else mapName - Apply(rhsSelect(gen, selectName), makeLambda(gen, cont)) + val aply = Apply(rhsSelect(gen, selectName), makeLambda(gen, cont)) + markTrailingMap(aply, gen, selectName) + aply case (gen: GenFrom) :: (rest @ GenAlias(_, _) :: _) => val (valeqs, rest1) = rest.span(_.isInstanceOf[GenAlias]) val pats = valeqs map { case GenAlias(pat, _) => pat } @@ -2165,15 +2196,15 @@ object desugar { case _ => Modifiers() makePatDef(valeq, mods, defpat, rhs) } - val rhs1 = makeFor(nme.map, nme.flatMap, GenFrom(defpat0, gen.expr, gen.checkMode) :: Nil, Block(pdefs, makeTuple(id0 :: ids))) + val rhs1 = makeFor(nme.map, nme.flatMap, GenFrom(defpat0, gen.expr, gen.checkMode) :: Nil, Block(pdefs, makeTuple(id0 :: ids).withAttachment(ForArtifact, ()))) val allpats = gen.pat :: pats val vfrom1 = GenFrom(makeTuple(allpats), rhs1, GenCheckMode.Ignore) makeFor(mapName, flatMapName, vfrom1 :: rest1, body) case (gen: GenFrom) :: test :: rest => val filtered = Apply(rhsSelect(gen, nme.withFilter), makeLambda(gen, test)) - val genFrom = GenFrom(gen.pat, filtered, if betterForsEnabled then GenCheckMode.Filtered else GenCheckMode.Ignore) + val genFrom = GenFrom(gen.pat, filtered, if sourceVersion.enablesBetterFors then GenCheckMode.Filtered else GenCheckMode.Ignore) makeFor(mapName, flatMapName, genFrom :: rest, body) - case GenAlias(_, _) :: _ if betterForsEnabled => + case GenAlias(_, _) :: _ if sourceVersion.enablesBetterFors => val (valeqs, rest) = enums.span(_.isInstanceOf[GenAlias]) val pats = valeqs.map { case GenAlias(pat, _) => pat } val rhss = valeqs.map { case GenAlias(_, rhs) => rhs } diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index 7bf83d548c97..9ed19c93d1ba 100644 --- a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -98,17 +98,17 @@ object MainProxies { val body = Try(call, handler :: Nil, EmptyTree) val mainArg = ValDef(nme.args, TypeTree(defn.ArrayType.appliedTo(defn.StringType)), EmptyTree) .withFlags(Param) - /** Replace typed `Ident`s that have been typed with a TypeSplice with the reference to the symbol. - * The annotations will be retype-checked in another scope that may not have the same imports. + + /** This context is used to create the `TypeSplices` wrapping annotations + * below. These should have `mainFun` as their owner (and not the + * enclosing package class that we would get otherwise) so that + * subsequent owner changes (for example in `Typer.typedTypedSplice`) are + * correct. See #22364 and associated tests. */ - def insertTypeSplices = new TreeMap { - override def transform(tree: Tree)(using Context): Tree = tree match - case tree: tpd.Ident @unchecked => TypedSplice(tree) - case tree => super.transform(tree) - } + val annotsCtx = ctx.fresh.setOwner(mainFun) val annots = mainFun.annotations .filterNot(_.matches(defn.MainAnnot)) - .map(annot => insertTypeSplices.transform(annot.tree)) + .map(annot => TypedSplice(annot.tree)(using annotsCtx)) val mainMeth = DefDef(nme.main, (mainArg :: Nil) :: Nil, TypeTree(defn.UnitType), body) .withFlags(JavaStatic | Synthetic) .withAnnotations(annots) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index e0fe17755257..32ab8378ae16 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -466,6 +466,8 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] */ private def defKind(tree: Tree)(using Context): FlagSet = unsplice(tree) match { case EmptyTree | _: Import => NoInitsInterface + case tree: TypeDef if ctx.settings.YcompileScala2Library.value => + if (tree.isClassDef) EmptyFlags else NoInitsInterface case tree: TypeDef => if (tree.isClassDef) NoInits else NoInitsInterface case tree: DefDef => if tree.unforcedRhs == EmptyTree @@ -477,6 +479,8 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] NoInitsInterface else if tree.mods.is(Given) && tree.paramss.isEmpty then EmptyFlags // might become a lazy val: TODO: check whether we need to suppress NoInits once we have new lazy val impl + else if ctx.settings.YcompileScala2Library.value then + EmptyFlags else NoInits case tree: ValDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else EmptyFlags diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 4c7ca396117e..fdefc14aadd6 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -461,8 +461,11 @@ object Trees { else if qualifier.span.exists && qualifier.span.start > span.point then // right associative val realName = name.stripModuleClassSuffix.lastPart Span(span.start, span.start + realName.length, point) - else - Span(point, span.end, point) + else if span.pointMayBeIncorrect then + val realName = name.stripModuleClassSuffix.lastPart + val probablyPoint = span.end - realName.length + Span(probablyPoint, span.end, probablyPoint) + else Span(point, span.end, point) else span } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 77e3387c5ce0..ae3ed9fcad3b 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -487,6 +487,21 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def ref(sym: Symbol)(using Context): Tree = ref(NamedType(sym.owner.thisType, sym.name, sym.denot)) + // Like `ref`, but avoids wrapping innermost module class references with This(), + // instead mapping those to objects, so that the resulting trees can be used in + // largest scope possible (method added for macros) + def generalisedRef(sym: Symbol)(using Context): Tree = + // Removes ThisType from inner module classes, replacing those with references to objects + def simplifyThisTypePrefix(tpe: Type)(using Context): Type = + tpe match + case ThisType(tref @ TypeRef(prefix, _)) if tref.symbol.flags.is(Module) => + TermRef(simplifyThisTypePrefix(prefix), tref.symbol.companionModule) + case TypeRef(prefix, designator) => + TypeRef(simplifyThisTypePrefix(prefix), designator) + case _ => + tpe + ref(NamedType(simplifyThisTypePrefix(sym.owner.thisType), sym.name, sym.denot)) + private def followOuterLinks(t: Tree)(using Context) = t match { case t: This if ctx.erasedTypes && !(t.symbol == ctx.owner.enclosingClass || t.symbol.isStaticOwner) => // after erasure outer paths should be respected diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 2acfc4cf86e3..57c74d90b45d 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -518,6 +518,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def scalaUnit(implicit src: SourceFile): Select = scalaDot(tpnme.Unit) def scalaAny(implicit src: SourceFile): Select = scalaDot(tpnme.Any) + def capsInternalDot(name: Name)(using SourceFile): Select = + Select(Select(scalaDot(nme.caps), nme.internal), name) + def captureRoot(using Context): Select = Select(scalaDot(nme.caps), nme.CAPTURE_ROOT) @@ -525,7 +528,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { Annotated(parent, New(scalaAnnotationDot(annotName), List(refs))) def makeCapsOf(tp: RefTree)(using Context): Tree = - TypeApply(Select(scalaDot(nme.caps), nme.capsOf), tp :: Nil) + TypeApply(capsInternalDot(nme.capsOf), tp :: Nil) // Capture set variable `[C^]` becomes: `[C >: CapSet <: CapSet^{cap}]` def makeCapsBound()(using Context): TypeBoundsTree = diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index bc4eb92234eb..92cd40a65d5a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -46,8 +46,8 @@ object ccConfig: */ def useSealed(using Context) = Feature.sourceVersion.stable != SourceVersion.`3.5` -end ccConfig +end ccConfig /** Are we at checkCaptures phase? */ def isCaptureChecking(using Context): Boolean = @@ -629,6 +629,19 @@ class CleanupRetains(using Context) extends TypeMap: RetainingType(tp, Nil, byName = annot.symbol == defn.RetainsByNameAnnot) case _ => mapOver(tp) +/** A typemap that follows aliases and keeps their transformed results if + * there is a change. + */ +trait FollowAliasesMap(using Context) extends TypeMap: + var follow = true // Used for debugging so that we can compare results with and w/o following. + def mapFollowingAliases(t: Type): Type = + val t1 = t.dealiasKeepAnnots + if follow && (t1 ne t) then + val t2 = apply(t1) + if t2 ne t1 then t2 + else t + else mapOver(t) + /** An extractor for `caps.reachCapability(ref)`, which is used to express a reach * capability as a tree in a @retains annotation. */ diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala index 9bda9102cbb8..2caba4cf7d89 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala @@ -83,7 +83,7 @@ trait CaptureRef extends TypeProxy, ValueType: else myCaptureSet = CaptureSet.Pending val computed = CaptureSet.ofInfo(this) - if !isCaptureChecking || underlying.isProvisional then + if !isCaptureChecking || ctx.mode.is(Mode.IgnoreCaptures) || underlying.isProvisional then myCaptureSet = null else myCaptureSet = computed @@ -108,7 +108,6 @@ trait CaptureRef extends TypeProxy, ValueType: * TODO: Document cases with more comments. */ final def subsumes(y: CaptureRef)(using Context): Boolean = - def subsumingRefs(x: Type, y: Type): Boolean = x match case x: CaptureRef => y match case y: CaptureRef => x.subsumes(y) @@ -119,12 +118,21 @@ trait CaptureRef extends TypeProxy, ValueType: case info: SingletonCaptureRef => test(info) case info: AndType => viaInfo(info.tp1)(test) || viaInfo(info.tp2)(test) case info: OrType => viaInfo(info.tp1)(test) && viaInfo(info.tp2)(test) + case info @ CapturingType(_,_) if this.derivesFrom(defn.Caps_CapSet) => + /* + If `this` is a capture set variable `C^`, then it is possible that it can be + reached from term variables in a reachability chain through the context. + For instance, in `def test[C^](src: Foo^{C^}) = { val x: Foo^{src} = src; val y: Foo^{x} = x; y }` + we expect that `C^` subsumes `x` and `y` in the body of the method + (cf. test case cc-poly-varargs.scala for a more involved example). + */ + test(info) case _ => false (this eq y) || this.isRootCapability || y.match - case y: TermRef => + case y: TermRef if !y.isRootCapability => y.prefix.match case ypre: CaptureRef => this.subsumes(ypre) @@ -149,7 +157,11 @@ trait CaptureRef extends TypeProxy, ValueType: y.info match case TypeBounds(_, hi: CaptureRef) => this.subsumes(hi) case _ => y.captureSetOfInfo.elems.forall(this.subsumes) - case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) => + case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) || this.derivesFrom(defn.Caps_CapSet) => + /* The second condition in the guard is for `this` being a `CapSet^{a,b...}` and etablishing a + potential reachability chain through `y`'s capture to a binding with + `this`'s capture set (cf. `CapturingType` case in `def viaInfo` above for more context). + */ refs.elems.forall(this.subsumes) case _ => false || this.match diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 1750e98f708a..39c41c369864 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -508,8 +508,13 @@ object CaptureSet: res.addToTrace(this) private def levelOK(elem: CaptureRef)(using Context): Boolean = - if elem.isRootCapability || Existential.isExistentialVar(elem) then + if elem.isRootCapability then !noUniversal + else if Existential.isExistentialVar(elem) then + !noUniversal + && !TypeComparer.isOpenedExistential(elem) + // Opened existentials on the left cannot be added to nested capture sets on the right + // of a comparison. Test case is open-existential.scala. else elem match case elem: TermRef if level.isDefined => elem.prefix match @@ -1065,13 +1070,12 @@ object CaptureSet: /** The capture set of the type underlying CaptureRef */ def ofInfo(ref: CaptureRef)(using Context): CaptureSet = ref match - case ref: (TermRef | TermParamRef) if ref.isMaxCapability => - if ref.isTrackableRef then ref.singletonCaptureSet - else CaptureSet.universal case ReachCapability(ref1) => ref1.widen.deepCaptureSet(includeTypevars = true) .showing(i"Deep capture set of $ref: ${ref1.widen} = ${result}", capt) - case _ => ofType(ref.underlying, followResult = true) + case _ => + if ref.isMaxCapability then ref.singletonCaptureSet + else ofType(ref.underlying, followResult = true) /** Capture set of a type */ def ofType(tp: Type, followResult: Boolean)(using Context): CaptureSet = @@ -1081,10 +1085,9 @@ object CaptureSet: tp.captureSet case tp: TermParamRef => tp.captureSet - case _: TypeRef => - empty - case _: TypeParamRef => - empty + case tp: (TypeRef | TypeParamRef) => + if tp.derivesFrom(defn.Caps_CapSet) then tp.captureSet + else empty case CapturingType(parent, refs) => recur(parent) ++ refs case tp @ AnnotatedType(parent, ann) if ann.hasSymbol(defn.ReachCapabilityAnnot) => diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index ebe128d7776c..a5e96f1f9ce2 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -13,7 +13,7 @@ import ast.tpd, tpd.* import transform.{PreRecheck, Recheck}, Recheck.* import CaptureSet.{IdentityCaptRefMap, IdempotentCaptRefMap} import Synthetics.isExcluded -import util.{Property, SimpleIdentitySet} +import util.SimpleIdentitySet import reporting.Message import printing.{Printer, Texts}, Texts.{Text, Str} import collection.mutable @@ -40,7 +40,7 @@ trait SetupAPI: object Setup: - val name: String = "ccSetup" + val name: String = "setupCC" val description: String = "prepare compilation unit for capture checking" /** Recognizer for `res $throws exc`, returning `(res, exc)` in case of success */ @@ -192,11 +192,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * 3. Refine other class types C by adding capture set variables to their parameter getters * (see addCaptureRefinements), provided `refine` is true. * 4. Add capture set variables to all types that can be tracked + * 5. Perform normalizeCaptures * * Polytype bounds are only cleaned using step 1, but not otherwise transformed. */ private def transformInferredType(tp: Type)(using Context): Type = - def mapInferred(refine: Boolean): TypeMap = new TypeMap: + def mapInferred(refine: Boolean): TypeMap = new TypeMap with FollowAliasesMap: override def toString = "map inferred" /** Refine a possibly applied class type C where the class has tracked parameters @@ -277,7 +278,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: paramInfos = tp.paramInfos.mapConserve(_.dropAllRetains.bounds), resType = this(tp.resType)) case _ => - mapOver(tp) + mapFollowingAliases(tp) addVar(addCaptureRefinements(normalizeCaptures(tp1)), ctx.owner) end apply end mapInferred @@ -299,9 +300,10 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * 3. Add universal capture sets to types deriving from Capability * 4. Map `cap` in function result types to existentially bound variables. * 5. Schedule deferred well-formed tests for types with retains annotations. + * 6. Perform normalizeCaptures */ private def transformExplicitType(tp: Type, tptToCheck: Tree = EmptyTree)(using Context): Type = - val toCapturing = new DeepTypeMap: + val toCapturing = new DeepTypeMap with FollowAliasesMap: override def toString = "expand aliases" /** Expand $throws aliases. This is hard-coded here since $throws aliases in stdlib @@ -337,7 +339,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tp @ CapturingType(parent, refs) if (refs eq defn.universalCSImpliedByCapability) && !tp.isBoxedCapturing => parent - case tp @ CapturingType(parent, refs) => tp case _ => tp def apply(t: Type) = @@ -363,7 +364,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // Map references to capability classes C to C^ if t.derivesFromCapability && !t.isSingleton && t.typeSymbol != defn.Caps_Exists then CapturingType(t, defn.universalCSImpliedByCapability, boxed = false) - else normalizeCaptures(mapOver(t)) + else normalizeCaptures(mapFollowingAliases(t)) end toCapturing def fail(msg: Message) = @@ -569,7 +570,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // For example, `(x: T, y: x.f.type) => Unit`. In this case, when we // substitute `x.f.type`, `x` becomes a `TermParamRef`. But the new method // type is still under initialization and `paramInfos` is still `null`, - // so the new `NamedType` will not have a denoation. + // so the new `NamedType` will not have a denotation. def adaptedInfo(psym: Symbol, info: mt.PInfo): mt.PInfo = mt.companion match case mtc: MethodTypeCompanion => mtc.adaptParamInfo(psym, info).asInstanceOf[mt.PInfo] case _ => info @@ -819,7 +820,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: CapturingType(OrType(parent1, tp2, tp.isSoft), refs1, tp1.isBoxed) case tp @ OrType(tp1, tp2 @ CapturingType(parent2, refs2)) => CapturingType(OrType(tp1, parent2, tp.isSoft), refs2, tp2.isBoxed) - case tp @ AppliedType(tycon, args) if !defn.isFunctionClass(tp.dealias.typeSymbol) => + case tp @ AppliedType(tycon, args) + if !defn.isFunctionClass(tp.dealias.typeSymbol) && (tp.dealias eq tp) => tp.derivedAppliedType(tycon, args.mapConserve(box)) case tp: RealTypeBounds => tp.derivedTypeBounds(tp.lo, box(tp.hi)) diff --git a/compiler/src/dotty/tools/dotc/config/CliCommand.scala b/compiler/src/dotty/tools/dotc/config/CliCommand.scala index b0046ee49cd1..a0edb2b8cded 100644 --- a/compiler/src/dotty/tools/dotc/config/CliCommand.scala +++ b/compiler/src/dotty/tools/dotc/config/CliCommand.scala @@ -7,7 +7,7 @@ import Settings.* import core.Contexts.* import printing.Highlighting -import scala.util.chaining.given +import dotty.tools.dotc.util.chaining.* import scala.PartialFunction.cond trait CliCommand: diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 8b9a64924ace..6df190f3147e 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -11,6 +11,7 @@ import SourceVersion.* import reporting.Message import NameKinds.QualifiedName import Annotations.ExperimentalAnnotation +import Annotations.PreviewAnnotation import Settings.Setting.ChoiceWithHelp object Feature: @@ -28,17 +29,13 @@ object Feature: val dependent = experimental("dependent") val erasedDefinitions = experimental("erasedDefinitions") val symbolLiterals = deprecated("symbolLiterals") - val fewerBraces = experimental("fewerBraces") val saferExceptions = experimental("saferExceptions") - val clauseInterleaving = experimental("clauseInterleaving") val pureFunctions = experimental("pureFunctions") val captureChecking = experimental("captureChecking") val into = experimental("into") - val namedTuples = experimental("namedTuples") val modularity = experimental("modularity") - val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors") val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions") - val betterFors = experimental("betterFors") + val packageObjectValues = experimental("packageObjectValues") def experimentalAutoEnableFeatures(using Context): List[TermName] = defn.languageExperimentalFeatures @@ -60,16 +57,11 @@ object Feature: (dependent, "Allow dependent method types"), (erasedDefinitions, "Allow erased definitions"), (symbolLiterals, "Allow symbol literals"), - (fewerBraces, "Enable support for using indentation for arguments"), (saferExceptions, "Enable safer exceptions"), - (clauseInterleaving, "Enable clause interleaving"), (pureFunctions, "Enable pure functions for capture checking"), (captureChecking, "Enable experimental capture checking"), (into, "Allow into modifier on parameter types"), - (namedTuples, "Allow named tuples"), - (modularity, "Enable experimental modularity features"), - (betterMatchTypeExtractors, "Enable better match type extractors"), - (betterFors, "Enable improvements in `for` comprehensions") + (modularity, "Enable experimental modularity features") ) // legacy language features from Scala 2 that are no longer supported. @@ -124,11 +116,6 @@ object Feature: def namedTypeArgsEnabled(using Context) = enabled(namedTypeArguments) - def clauseInterleavingEnabled(using Context) = - sourceVersion.isAtLeast(`3.6`) || enabled(clauseInterleaving) - - def betterForsEnabled(using Context) = enabled(betterFors) - def genericNumberLiteralsEnabled(using Context) = enabled(genericNumberLiterals) def scala2ExperimentalMacroEnabled(using Context) = enabled(scala2macros) @@ -169,9 +156,6 @@ object Feature: def migrateTo3(using Context): Boolean = sourceVersion == `3.0-migration` - def fewerBracesEnabled(using Context) = - sourceVersion.isAtLeast(`3.3`) || enabled(fewerBraces) - /** If current source migrates to `version`, issue given warning message * and return `true`, otherwise return `false`. */ @@ -242,4 +226,29 @@ object Feature: true else false + + def isPreviewEnabled(using Context): Boolean = + ctx.settings.preview.value + + def checkPreviewFeature(which: String, srcPos: SrcPos, note: => String = "")(using Context) = + if !isPreviewEnabled then + report.error(previewUseSite(which) + note, srcPos) + + def checkPreviewDef(sym: Symbol, srcPos: SrcPos)(using Context) = if !isPreviewEnabled then + val previewSym = + if sym.hasAnnotation(defn.PreviewAnnot) then sym + else if sym.owner.hasAnnotation(defn.PreviewAnnot) then sym.owner + else NoSymbol + val msg = + previewSym.getAnnotation(defn.PreviewAnnot).collectFirst { + case PreviewAnnotation(msg) if msg.nonEmpty => s": $msg" + }.getOrElse("") + val markedPreview = + if previewSym.exists + then i"$previewSym is marked @preview$msg" + else i"$sym inherits @preview$msg" + report.error(i"${markedPreview}\n\n${previewUseSite("definition")}", srcPos) + + private def previewUseSite(which: String): String = + s"Preview $which may only be used when compiling with the `-preview` compiler flag" end Feature diff --git a/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala b/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala index f3c2f295ce82..a6dbf696a575 100644 --- a/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala +++ b/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala @@ -27,6 +27,13 @@ class JavaPlatform extends Platform { case _ => false }) + def addToClassPath(cPath: ClassPath)(using Context): Unit = classPath match { + case AggregateClassPath(entries) => + currentClassPath = Some(AggregateClassPath(entries :+ cPath)) + case cp: ClassPath => + currentClassPath = Some(AggregateClassPath(cp :: cPath :: Nil)) + } + /** Update classpath with a substituted subentry */ def updateClassPath(subst: Map[ClassPath, ClassPath]): Unit = currentClassPath.get match { case AggregateClassPath(entries) => diff --git a/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala b/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala index 1d99caa789d3..f77aa0b06308 100644 --- a/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala +++ b/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala @@ -32,6 +32,7 @@ enum MigrationVersion(val warnFrom: SourceVersion, val errorFrom: SourceVersion) case ParameterEnclosedByParenthesis extends MigrationVersion(future, future) case XmlLiteral extends MigrationVersion(future, future) case GivenSyntax extends MigrationVersion(future, never) + case ImplicitParamsWithoutUsing extends MigrationVersion(`3.7`, future) require(warnFrom.ordinal <= errorFrom.ordinal) diff --git a/compiler/src/dotty/tools/dotc/config/PathResolver.scala b/compiler/src/dotty/tools/dotc/config/PathResolver.scala index f60727e6bba2..67be0e3587cb 100644 --- a/compiler/src/dotty/tools/dotc/config/PathResolver.scala +++ b/compiler/src/dotty/tools/dotc/config/PathResolver.scala @@ -53,8 +53,7 @@ object PathResolver { def classPathEnv: String = envOrElse("CLASSPATH", "") def sourcePathEnv: String = envOrElse("SOURCEPATH", "") - //using propOrNone/getOrElse instead of propOrElse so that searchForBootClasspath is lazy evaluated - def javaBootClassPath: String = propOrNone("sun.boot.class.path") getOrElse searchForBootClasspath + def javaBootClassPath: String = propOrElse("sun.boot.class.path", searchForBootClasspath) def javaExtDirs: String = propOrEmpty("java.ext.dirs") def scalaHome: String = propOrEmpty("scala.home") diff --git a/compiler/src/dotty/tools/dotc/config/Platform.scala b/compiler/src/dotty/tools/dotc/config/Platform.scala index 2a0b207e68c1..c7d319e736b4 100644 --- a/compiler/src/dotty/tools/dotc/config/Platform.scala +++ b/compiler/src/dotty/tools/dotc/config/Platform.scala @@ -21,6 +21,9 @@ abstract class Platform { /** Update classpath with a substitution that maps entries to entries */ def updateClassPath(subst: Map[ClassPath, ClassPath]): Unit + /** Add new entry to classpath */ + def addToClassPath(cPath: ClassPath)(using Context): Unit + /** Any platform-specific phases. */ //def platformPhases: List[SubComponent] diff --git a/compiler/src/dotty/tools/dotc/config/Properties.scala b/compiler/src/dotty/tools/dotc/config/Properties.scala index 41cd14955759..c2046899aaef 100644 --- a/compiler/src/dotty/tools/dotc/config/Properties.scala +++ b/compiler/src/dotty/tools/dotc/config/Properties.scala @@ -45,7 +45,7 @@ trait PropertiesTrait { def propIsSet(name: String): Boolean = System.getProperty(name) != null def propIsSetTo(name: String, value: String): Boolean = propOrNull(name) == value - def propOrElse(name: String, alt: String): String = System.getProperty(name, alt) + def propOrElse(name: String, alt: => String): String = Option(System.getProperty(name)).getOrElse(alt) def propOrEmpty(name: String): String = propOrElse(name, "") def propOrNull(name: String): String = propOrElse(name, null) def propOrNone(name: String): Option[String] = Option(propOrNull(name)) @@ -53,11 +53,11 @@ trait PropertiesTrait { def setProp(name: String, value: String): String = System.setProperty(name, value) def clearProp(name: String): String = System.clearProperty(name) - def envOrElse(name: String, alt: String): String = Option(System getenv name) getOrElse alt + def envOrElse(name: String, alt: => String): String = Option(System getenv name) getOrElse alt def envOrNone(name: String): Option[String] = Option(System getenv name) // for values based on propFilename - def scalaPropOrElse(name: String, alt: String): String = scalaProps.getProperty(name, alt) + def scalaPropOrElse(name: String, alt: => String): String = scalaProps.getProperty(name, alt) def scalaPropOrEmpty(name: String): String = scalaPropOrElse(name, "") def scalaPropOrNone(name: String): Option[String] = Option(scalaProps.getProperty(name)) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 599812a9a390..4ee4d17cd63e 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -11,7 +11,7 @@ import dotty.tools.io.{AbstractFile, Directory, JDK9Reflectors, PlainDirectory, import Setting.ChoiceWithHelp import ScalaSettingCategories.* -import scala.util.chaining.* +import dotty.tools.dotc.util.chaining.* import java.util.zip.Deflater @@ -116,6 +116,7 @@ trait CommonScalaSettings: val unchecked: Setting[Boolean] = BooleanSetting(RootSetting, "unchecked", "Enable additional warnings where generated code depends on assumptions.", initialValue = true, aliases = List("--unchecked")) val language: Setting[List[ChoiceWithHelp[String]]] = MultiChoiceHelpSetting(RootSetting, "language", "feature", "Enable one or more language features.", choices = ScalaSettingsProperties.supportedLanguageFeatures, legacyChoices = ScalaSettingsProperties.legacyLanguageFeatures, default = Nil, aliases = List("--language")) val experimental: Setting[Boolean] = BooleanSetting(RootSetting, "experimental", "Annotate all top-level definitions with @experimental. This enables the use of experimental features anywhere in the project.") + val preview: Setting[Boolean] = BooleanSetting(RootSetting, "preview", "Enable the use of preview features anywhere in the project.") /* Coverage settings */ val coverageOutputDir = PathSetting(RootSetting, "coverage-out", "Destination for coverage classfiles and instrumentation data.", "", aliases = List("--coverage-out")) @@ -127,6 +128,7 @@ trait CommonScalaSettings: val usejavacp: Setting[Boolean] = BooleanSetting(RootSetting, "usejavacp", "Utilize the java.class.path in classpath resolution.", aliases = List("--use-java-class-path")) val scalajs: Setting[Boolean] = BooleanSetting(RootSetting, "scalajs", "Compile in Scala.js mode (requires scalajs-library.jar on the classpath).", aliases = List("--scalajs")) val replInitScript: Setting[String] = StringSetting(RootSetting, "repl-init-script", "code", "The code will be run on REPL startup.", "", aliases = List("--repl-init-script")) + val replQuitAfterInit: Setting[Boolean] = BooleanSetting(RootSetting, "repl-quit-after-init", "Quit REPL after evaluating the init script.", aliases = List("--repl-quit-after-init")) end CommonScalaSettings /** -P "plugin" settings. Various tools might support plugins. */ @@ -173,28 +175,21 @@ private sealed trait WarningSettings: choices = List( ChoiceWithHelp("nowarn", ""), ChoiceWithHelp("all", ""), - ChoiceWithHelp( - name = "imports", - description = "Warn if an import selector is not referenced.\n" + - "NOTE : overrided by -Wunused:strict-no-implicit-warn"), + ChoiceWithHelp("imports", "Warn if an import selector is not referenced."), ChoiceWithHelp("privates", "Warn if a private member is unused"), ChoiceWithHelp("locals", "Warn if a local definition is unused"), ChoiceWithHelp("explicits", "Warn if an explicit parameter is unused"), ChoiceWithHelp("implicits", "Warn if an implicit parameter is unused"), ChoiceWithHelp("params", "Enable -Wunused:explicits,implicits"), + ChoiceWithHelp("patvars","Warn if a variable bound in a pattern is unused"), + //ChoiceWithHelp("inlined", "Apply -Wunused to inlined expansions"), // TODO ChoiceWithHelp("linted", "Enable -Wunused:imports,privates,locals,implicits"), ChoiceWithHelp( name = "strict-no-implicit-warn", - description = "Same as -Wunused:import, only for imports of explicit named members.\n" + - "NOTE : This overrides -Wunused:imports and NOT set by -Wunused:all" + description = """Same as -Wunused:imports, only for imports of explicit named members. + |NOTE : This overrides -Wunused:imports and NOT set by -Wunused:all""".stripMargin ), - // ChoiceWithHelp("patvars","Warn if a variable bound in a pattern is unused"), - ChoiceWithHelp( - name = "unsafe-warn-patvars", - description = "(UNSAFE) Warn if a variable bound in a pattern is unused.\n" + - "This warning can generate false positive, as warning cannot be\n" + - "suppressed yet." - ) + ChoiceWithHelp("unsafe-warn-patvars", "Deprecated alias for `patvars`"), ), default = Nil ) @@ -206,7 +201,6 @@ private sealed trait WarningSettings: // Is any choice set for -Wunused? def any(using Context): Boolean = Wall.value || Wunused.value.nonEmpty - // overrided by strict-no-implicit-warn def imports(using Context) = (allOr("imports") || allOr("linted")) && !(strictNoImplicitWarn) def locals(using Context) = @@ -220,9 +214,8 @@ private sealed trait WarningSettings: def params(using Context) = allOr("params") def privates(using Context) = allOr("privates") || allOr("linted") - def patvars(using Context) = - isChoiceSet("unsafe-warn-patvars") // not with "all" - // allOr("patvars") // todo : rename once fixed + def patvars(using Context) = allOr("patvars") || isChoiceSet("unsafe-warn-patvars") + def inlined(using Context) = isChoiceSet("inlined") def linted(using Context) = allOr("linted") def strictNoImplicitWarn(using Context) = @@ -450,7 +443,8 @@ private sealed trait YSettings: val YwithBestEffortTasty: Setting[Boolean] = BooleanSetting(ForkSetting, "Ywith-best-effort-tasty", "Allow to compile using best-effort tasty files. If such file is used, the compiler will stop after the pickler phase.") // Experimental language features - val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-kind-polymorphism", "Disable kind polymorphism.") + @deprecated(message = "This flag has no effect and will be removed in a future version.", since = "3.7.0") + val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-kind-polymorphism", "Disable kind polymorphism. (This flag has no effect)", deprecation = Deprecation.removed()) val YexplicitNulls: Setting[Boolean] = BooleanSetting(ForkSetting, "Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.") val YnoFlexibleTypes: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-flexible-types", "Disable turning nullable Java return types and parameter types into flexible types, which behave like abstract types with a nullable lower bound and non-nullable upper bound.") val YcheckInitGlobal: Setting[Boolean] = BooleanSetting(ForkSetting, "Ysafe-init-global", "Check safe initialization of global objects.") diff --git a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala index 3a7285751827..30a88fb79f2a 100644 --- a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala +++ b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala @@ -3,6 +3,8 @@ package dotc package config import core.Decorators.* +import core.Contexts.* +import Feature.isPreviewEnabled import util.Property enum SourceVersion: @@ -13,6 +15,7 @@ enum SourceVersion: case `3.5-migration`, `3.5` case `3.6-migration`, `3.6` case `3.7-migration`, `3.7` + case `3.8-migration`, `3.8` // !!! Keep in sync with scala.runtime.stdlibPatches.language !!! case `future-migration`, `future` @@ -30,8 +33,14 @@ enum SourceVersion: def isAtMost(v: SourceVersion) = stable.ordinal <= v.ordinal + def enablesFewerBraces = isAtLeast(`3.3`) + def enablesClauseInterleaving = isAtLeast(`3.6`) + def enablesNewGivens = isAtLeast(`3.6`) + def enablesNamedTuples = isAtLeast(`3.7`) + def enablesBetterFors(using Context) = isAtLeast(`3.7`) && isPreviewEnabled + object SourceVersion extends Property.Key[SourceVersion]: - def defaultSourceVersion = `3.6` + def defaultSourceVersion = `3.7` /** language versions that may appear in a language import, are deprecated, but not removed from the standard library. */ val illegalSourceVersionNames = List("3.1-migration", "never").map(_.toTermName) diff --git a/compiler/src/dotty/tools/dotc/config/WrappedProperties.scala b/compiler/src/dotty/tools/dotc/config/WrappedProperties.scala index 20304b74c1da..a72830331e9f 100644 --- a/compiler/src/dotty/tools/dotc/config/WrappedProperties.scala +++ b/compiler/src/dotty/tools/dotc/config/WrappedProperties.scala @@ -14,12 +14,12 @@ trait WrappedProperties extends PropertiesTrait { protected def propCategory: String = "wrapped" protected def pickJarBasedOn: Class[?] = this.getClass - override def propIsSet(name: String): Boolean = wrap(super.propIsSet(name)) exists (x => x) - override def propOrElse(name: String, alt: String): String = wrap(super.propOrElse(name, alt)) getOrElse alt - override def setProp(name: String, value: String): String = wrap(super.setProp(name, value)).orNull - override def clearProp(name: String): String = wrap(super.clearProp(name)).orNull - override def envOrElse(name: String, alt: String): String = wrap(super.envOrElse(name, alt)) getOrElse alt - override def envOrNone(name: String): Option[String] = wrap(super.envOrNone(name)).flatten + override def propIsSet(name: String): Boolean = wrap(super.propIsSet(name)) exists (x => x) + override def propOrElse(name: String, alt: => String): String = wrap(super.propOrElse(name, alt)) getOrElse alt + override def setProp(name: String, value: String): String = wrap(super.setProp(name, value)).orNull + override def clearProp(name: String): String = wrap(super.clearProp(name)).orNull + override def envOrElse(name: String, alt: => String): String = wrap(super.envOrElse(name, alt)) getOrElse alt + override def envOrNone(name: String): Option[String] = wrap(super.envOrNone(name)).flatten def systemProperties: Iterator[(String, String)] = { import scala.jdk.CollectionConverters.* diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index d7f50d4638ab..1615679a036e 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -65,7 +65,7 @@ object Annotations { if tm.isRange(x) then x else val tp1 = tm(tree.tpe) - foldOver(if !tp1.exists || (tp1 frozen_=:= tree.tpe) then x else tp1, tree) + foldOver(if !tp1.exists || tp1.eql(tree.tpe) then x else tp1, tree) val diff = findDiff(NoType, args) if tm.isRange(diff) then EmptyAnnotation else if diff.exists then derivedAnnotation(tm.mapOver(tree)) @@ -303,5 +303,16 @@ object Annotations { case annot @ ExperimentalAnnotation(msg) => ExperimentalAnnotation(msg, annot.tree.span) } } - + + object PreviewAnnotation { + /** Matches and extracts the message from an instance of `@preview(msg)` + * Returns `Some("")` for `@preview` with no message. + */ + def unapply(a: Annotation)(using Context): Option[String] = + if a.symbol ne defn.PreviewAnnot then + None + else a.argumentConstant(0) match + case Some(Constant(msg: String)) => Some(msg) + case _ => Some("") + } } diff --git a/compiler/src/dotty/tools/dotc/core/Comments.scala b/compiler/src/dotty/tools/dotc/core/Comments.scala index 92160c97973d..b1d1e387c2cf 100644 --- a/compiler/src/dotty/tools/dotc/core/Comments.scala +++ b/compiler/src/dotty/tools/dotc/core/Comments.scala @@ -405,15 +405,10 @@ object Comments { val Trim = "(?s)^[\\s&&[^\n\r]]*(.*?)\\s*$".r val raw = ctx.docCtx.flatMap(_.docstring(sym).map(_.raw)).getOrElse("") - defs(sym) ++= defines(raw).map { - str => { - val start = skipWhitespace(str, "@define".length) - val (key, value) = str.splitAt(skipVariable(str, start)) - key.drop(start) -> value - } - } map { - case (key, Trim(value)) => - variableName(key) -> value.replaceAll("\\s+\\*+$", "") + defs(sym) ++= defines(raw).map { str => + val start = skipWhitespace(str, "@define".length) + val (key, Trim(value)) = str.splitAt(skipVariable(str, start)): @unchecked + variableName(key.drop(start)) -> value.replaceAll("\\s+\\*+$", "") } } diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 7c54d1392720..e12ab1cc2da2 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -122,7 +122,7 @@ object Contexts { * risk of capturing complete trees. * - To make sure these rules are kept, it would be good to do a sanity * check using bytecode inspection with javap or scalap: Keep track - * of all class fields of type context; allow them only in whitelisted + * of all class fields of type context; allow them only in allowlisted * classes (which should be short-lived). */ abstract class Context(val base: ContextBase) { thiscontext => @@ -786,9 +786,6 @@ object Contexts { def withNotNullInfos(infos: List[NotNullInfo]): Context = if !c.explicitNulls || (c.notNullInfos eq infos) then c else c.fresh.setNotNullInfos(infos) - def relaxedOverrideContext: Context = - c.withModeBits(c.mode &~ Mode.SafeNulls | Mode.RelaxedOverriding) - // TODO: Fix issue when converting ModeChanges and FreshModeChanges to extension givens extension (c: Context) { final def withModeBits(mode: Mode): Context = diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index dd20c2db9192..e44bfcee2cf7 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -449,10 +449,7 @@ class Definitions { @tu lazy val AnyKindClass: ClassSymbol = { val cls = newCompleteClassSymbol(ScalaPackageClass, tpnme.AnyKind, AbstractFinal | Permanent, Nil, newScope(0)) - if (!ctx.settings.YnoKindPolymorphism.value) - // Enable kind-polymorphism by exposing scala.AnyKind - cls.entered - cls + cls.entered } def AnyKindType: TypeRef = AnyKindClass.typeRef @@ -494,6 +491,8 @@ class Definitions { @tu lazy val Predef_undefined: Symbol = ScalaPredefModule.requiredMethod(nme.???) @tu lazy val ScalaPredefModuleClass: ClassSymbol = ScalaPredefModule.moduleClass.asClass + @tu lazy val SameTypeClass: ClassSymbol = requiredClass("scala.=:=") + @tu lazy val SameType_refl: Symbol = SameTypeClass.companionModule.requiredMethod(nme.refl) @tu lazy val SubTypeClass: ClassSymbol = requiredClass("scala.<:<") @tu lazy val SubType_refl: Symbol = SubTypeClass.companionModule.requiredMethod(nme.refl) @@ -616,6 +615,7 @@ class Definitions { @tu lazy val Int_== : Symbol = IntClass.requiredMethod(nme.EQ, List(IntType)) @tu lazy val Int_>= : Symbol = IntClass.requiredMethod(nme.GE, List(IntType)) @tu lazy val Int_<= : Symbol = IntClass.requiredMethod(nme.LE, List(IntType)) + @tu lazy val Int_> : Symbol = IntClass.requiredMethod(nme.GT, List(IntType)) @tu lazy val LongType: TypeRef = valueTypeRef("scala.Long", java.lang.Long.TYPE, LongEnc, nme.specializedTypeNames.Long) def LongClass(using Context): ClassSymbol = LongType.symbol.asClass @tu lazy val Long_+ : Symbol = LongClass.requiredMethod(nme.PLUS, List(LongType)) @@ -827,6 +827,7 @@ class Definitions { @tu lazy val ReflectSelectableTypeRef: TypeRef = requiredClassRef("scala.reflect.Selectable") + @tu lazy val TypeableType: TypeSymbol = requiredPackage("scala.reflect.Typeable$package").moduleClass.requiredType("Typeable") @tu lazy val TypeTestClass: ClassSymbol = requiredClass("scala.reflect.TypeTest") @tu lazy val TypeTest_unapply: Symbol = TypeTestClass.requiredMethod(nme.unapply) @tu lazy val TypeTestModule_identity: Symbol = TypeTestClass.companionModule.requiredMethod(nme.identity) @@ -834,6 +835,7 @@ class Definitions { @tu lazy val QuotedExprClass: ClassSymbol = requiredClass("scala.quoted.Expr") @tu lazy val QuotesClass: ClassSymbol = requiredClass("scala.quoted.Quotes") + @tu lazy val Quotes_reflectModule: Symbol = QuotesClass.requiredClass("reflectModule") @tu lazy val Quotes_reflect: Symbol = QuotesClass.requiredValue("reflect") @tu lazy val Quotes_reflect_asTerm: Symbol = Quotes_reflect.requiredMethod("asTerm") @tu lazy val Quotes_reflect_Apply: Symbol = Quotes_reflect.requiredValue("Apply") @@ -955,6 +957,7 @@ class Definitions { def NonEmptyTupleClass(using Context): ClassSymbol = NonEmptyTupleTypeRef.symbol.asClass lazy val NonEmptyTuple_tail: Symbol = NonEmptyTupleClass.requiredMethod("tail") @tu lazy val PairClass: ClassSymbol = requiredClass("scala.*:") + @tu lazy val PairClass_unapply: Symbol = PairClass.companionModule.requiredMethod("unapply") @tu lazy val TupleXXLClass: ClassSymbol = requiredClass("scala.runtime.TupleXXL") def TupleXXLModule(using Context): Symbol = TupleXXLClass.companionModule @@ -990,17 +993,19 @@ class Definitions { @tu lazy val LabelClass: Symbol = requiredClass("scala.util.boundary.Label") @tu lazy val BreakClass: Symbol = requiredClass("scala.util.boundary.Break") - @tu lazy val CapsModule: Symbol = requiredModule("scala.caps") + @tu lazy val CapsModule: Symbol = requiredPackage("scala.caps") @tu lazy val captureRoot: TermSymbol = CapsModule.requiredValue("cap") @tu lazy val Caps_Capability: TypeSymbol = CapsModule.requiredType("Capability") @tu lazy val Caps_CapSet: ClassSymbol = requiredClass("scala.caps.CapSet") - @tu lazy val Caps_reachCapability: TermSymbol = CapsModule.requiredMethod("reachCapability") - @tu lazy val Caps_capsOf: TermSymbol = CapsModule.requiredMethod("capsOf") + @tu lazy val CapsInternalModule: Symbol = requiredModule("scala.caps.internal") + @tu lazy val Caps_reachCapability: TermSymbol = CapsInternalModule.requiredMethod("reachCapability") + @tu lazy val Caps_capsOf: TermSymbol = CapsInternalModule.requiredMethod("capsOf") @tu lazy val Caps_Exists: ClassSymbol = requiredClass("scala.caps.Exists") @tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe") @tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure") @tu lazy val Caps_ContainsTrait: TypeSymbol = CapsModule.requiredType("Contains") - @tu lazy val Caps_containsImpl: TermSymbol = CapsModule.requiredMethod("containsImpl") + @tu lazy val Caps_ContainsModule: Symbol = requiredModule("scala.caps.Contains") + @tu lazy val Caps_containsImpl: TermSymbol = Caps_ContainsModule.requiredMethod("containsImpl") /** The same as CaptureSet.universal but generated implicitly for references of Capability subtypes */ @tu lazy val universalCSImpliedByCapability = CaptureSet(captureRoot.termRef) @@ -1036,6 +1041,7 @@ class Definitions { @tu lazy val MigrationAnnot: ClassSymbol = requiredClass("scala.annotation.migration") @tu lazy val NowarnAnnot: ClassSymbol = requiredClass("scala.annotation.nowarn") @tu lazy val UnusedAnnot: ClassSymbol = requiredClass("scala.annotation.unused") + @tu lazy val UnrollAnnot: ClassSymbol = requiredClass("scala.annotation.unroll") @tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait") @tu lazy val NativeAnnot: ClassSymbol = requiredClass("scala.native") @tu lazy val RepeatedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Repeated") @@ -1052,15 +1058,17 @@ class Definitions { @tu lazy val CompileTimeOnlyAnnot: ClassSymbol = requiredClass("scala.annotation.compileTimeOnly") @tu lazy val SwitchAnnot: ClassSymbol = requiredClass("scala.annotation.switch") @tu lazy val ExperimentalAnnot: ClassSymbol = requiredClass("scala.annotation.experimental") + @tu lazy val PreviewAnnot: ClassSymbol = requiredClass("scala.annotation.internal.preview") @tu lazy val ThrowsAnnot: ClassSymbol = requiredClass("scala.throws") @tu lazy val TransientAnnot: ClassSymbol = requiredClass("scala.transient") @tu lazy val UncheckedAnnot: ClassSymbol = requiredClass("scala.unchecked") @tu lazy val UncheckedStableAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedStable") @tu lazy val UncheckedVarianceAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedVariance") @tu lazy val UncheckedCapturesAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedCaptures") - @tu lazy val UntrackedCapturesAnnot: ClassSymbol = requiredClass("scala.caps.untrackedCaptures") + @tu lazy val UntrackedCapturesAnnot: ClassSymbol = requiredClass("scala.caps.unsafe.untrackedCaptures") @tu lazy val UseAnnot: ClassSymbol = requiredClass("scala.caps.use") @tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile") + @tu lazy val LanguageFeatureMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.languageFeature") @tu lazy val BeanGetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanGetter") @tu lazy val BeanSetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanSetter") @tu lazy val FieldMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.field") @@ -1334,12 +1342,19 @@ class Definitions { case ByNameFunction(_) => true case _ => false + object NamedTupleDirect: + def unapply(t: Type)(using Context): Option[(Type, Type)] = + t match + case AppliedType(tycon, nmes :: vals :: Nil) if tycon.typeSymbol == NamedTupleTypeRef.symbol => + Some((nmes, vals)) + case _ => None + object NamedTuple: def apply(nmes: Type, vals: Type)(using Context): Type = AppliedType(NamedTupleTypeRef, nmes :: vals :: Nil) def unapply(t: Type)(using Context): Option[(Type, Type)] = t match - case AppliedType(tycon, nmes :: vals :: Nil) if tycon.typeSymbol == NamedTupleTypeRef.symbol => + case NamedTupleDirect(nmes, vals) => Some((nmes, vals)) case tp: TypeProxy => val t = unapply(tp.superType); t @@ -1792,18 +1807,24 @@ class Definitions { || (sym eq Any_typeTest) || (sym eq Any_typeCast) - /** Is this type a `TupleN` type? + /** Is `tp` a `TupleN` type? + * + * @return true if the type of `tp` is `TupleN[T1, T2, ..., Tn]` + */ + def isDirectTupleNType(tp: Type)(using Context): Boolean = + val arity = tp.argInfos.length + arity <= MaxTupleArity && { + val tupletp = TupleType(arity) + tupletp != null && tp.isRef(tupletp.symbol) + } + + /** Is `tp` (an alias of) a `TupleN` type? * * @return true if the dealiased type of `tp` is `TupleN[T1, T2, ..., Tn]` */ - def isTupleNType(tp: Type)(using Context): Boolean = { + def isTupleNType(tp: Type)(using Context): Boolean = val tp1 = tp.dealias - val arity = tp1.argInfos.length - arity <= MaxTupleArity && { - val tupletp = TupleType(arity) - tupletp != null && tp1.isRef(tupletp.symbol) - } - } + isDirectTupleNType(tp1) def tupleType(elems: List[Type]): Type = { val arity = elems.length @@ -1972,7 +1993,7 @@ class Definitions { Some((args.init, args.last)) case _ => None - /** A whitelist of Scala-2 classes that are known to be pure */ + /** A allowlist of Scala-2 classes that are known to be pure */ def isAssuredNoInits(sym: Symbol): Boolean = (sym `eq` SomeClass) || isTupleClass(sym) @@ -2068,7 +2089,12 @@ class Definitions { */ @tu lazy val ccExperimental: Set[Symbol] = Set( CapsModule, CapsModule.moduleClass, PureClass, + Caps_Capability, // TODO: Remove when Capability is stabilized RequiresCapabilityAnnot, + captureRoot, Caps_CapSet, Caps_ContainsTrait, Caps_ContainsModule, Caps_ContainsModule.moduleClass, UseAnnot, + Caps_Exists, + CapsUnsafeModule, CapsUnsafeModule.moduleClass, + CapsInternalModule, CapsInternalModule.moduleClass, RetainsAnnot, RetainsCapAnnot, RetainsByNameAnnot) /** Experimental language features defined in `scala.runtime.stdLibPatches.language.experimental`. diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 85ff51bc19de..614a8b691c92 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -478,12 +478,11 @@ object Denotations { else if sym1.is(Method) && !sym2.is(Method) then 1 else 0 - val relaxedOverriding = ctx.explicitNulls && (sym1.is(JavaDefined) || sym2.is(JavaDefined)) val matchLoosely = sym1.matchNullaryLoosely || sym2.matchNullaryLoosely - if symScore <= 0 && info2.overrides(info1, relaxedOverriding, matchLoosely, checkClassInfo = false) then + if symScore <= 0 && info2.overrides(info1, matchLoosely, checkClassInfo = false) then denot2 - else if symScore >= 0 && info1.overrides(info2, relaxedOverriding, matchLoosely, checkClassInfo = false) then + else if symScore >= 0 && info1.overrides(info2, matchLoosely, checkClassInfo = false) then denot1 else val jointInfo = infoMeet(info1, info2, safeIntersection) @@ -1072,7 +1071,9 @@ object Denotations { def filterDisjoint(denots: PreDenotation)(using Context): SingleDenotation = if (denots.exists && denots.matches(this)) NoDenotation else this def filterWithFlags(required: FlagSet, excluded: FlagSet)(using Context): SingleDenotation = - val realExcluded = if ctx.isAfterTyper then excluded else excluded | Invisible + val realExcluded = + if ctx.isAfterTyper || ctx.mode.is(Mode.ResolveFromTASTy) then excluded + else excluded | Invisible def symd: SymDenotation = this match case symd: SymDenotation => symd case _ => symbol.denot diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 46ce0d2d7852..f3e62bc36e06 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -6,6 +6,8 @@ import Flags.JavaDefined import StdNames.nme import Symbols.* import Types.* +import dotty.tools.dotc.reporting.* +import dotty.tools.dotc.core.Decorators.i /** This module defines methods to interpret types of Java symbols, which are implicitly nullable in Java, * as Scala types, which are explicitly nullable. @@ -51,7 +53,7 @@ object JavaNullInterop { * * But the selection can throw an NPE if the returned value is `null`. */ - def nullifyMember(sym: Symbol, tp: Type, isEnumValueDef: Boolean)(using Context): Type = { + def nullifyMember(sym: Symbol, tp: Type, isEnumValueDef: Boolean)(using Context): Type = trace(i"nullifyMember ${sym}, ${tp}"){ assert(ctx.explicitNulls) assert(sym.is(JavaDefined), "can only nullify java-defined members") diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 14d7827974c0..6fd76e37977d 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -105,7 +105,7 @@ object Mode { /** Use previous Scheme for implicit resolution. Currently significant * in 3.0-migration where we use Scala-2's scheme instead and in 3.5 and 3.6-migration - * where we use the previous scheme up to 3.4 for comparison with the new scheme. + * where we use the previous scheme up to 3.4 for comparison with the new scheme. */ val OldImplicitResolution: Mode = newMode(15, "OldImplicitResolution") @@ -125,6 +125,9 @@ object Mode { /** Read original positions when unpickling from TASTY */ val ReadPositions: Mode = newMode(17, "ReadPositions") + /** We are resolving a SELECT name from TASTy */ + val ResolveFromTASTy: Mode = newMode(18, "ResolveFromTASTy") + /** We are elaborating the fully qualified name of a package clause. * In this case, identifiers should never be imported. */ @@ -163,10 +166,35 @@ object Mode { */ val ForceInline: Mode = newMode(29, "ForceInline") - /** This mode is enabled when we check Java overriding in explicit nulls. - * Type `Null` becomes a subtype of non-primitive value types in TypeComparer. + /** Are we typing the argument of an annotation? + * + * This mode is used through [[Applications.isAnnotConstr]] to avoid lifting + * arguments of annotation constructors. This mode is disabled in nested + * applications (from [[ProtoTypes.typedArg]]) and in "explicit" annotation + * constructors applications (annotation classes constructed with `new`). + * + * In the following example: + * + * ```scala + * @annot(y = new annot(y = Array("World"), x = 1), x = 2) + * ``` + * + * the mode will be set when typing `@annot(...)` but not when typing + * `new annot(...)`, such that the arguments of the former are not lifted but + * the arguments of the later can be: + * + * ```scala + * @annot(x = 2, y = { + * val y$3: Array[String] = + * Array.apply[String](["World" : String]*)( + * scala.reflect.ClassTag.apply[String](classOf[String])) + * new annot(x = 1, y = y$3) + * }) + * ``` + * + * See #22035, #22526, #22553 and `dependent-annot-default-args.scala`. */ - val RelaxedOverriding: Mode = newMode(30, "RelaxedOverriding") + val InAnnotation: Mode = newMode(30, "InAnnotation") /** Skip inlining of methods. */ val NoInline: Mode = newMode(31, "NoInline") diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index 363a01665564..dbdb46aba334 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -39,14 +39,17 @@ object NamerOps: */ extension (tp: Type) def separateRefinements(cls: ClassSymbol, refinements: mutable.LinkedHashMap[Name, Type] | Null)(using Context): Type = + val widenSkolemsMap = new TypeMap: + def apply(tp: Type) = mapOver(tp.widenSkolem) tp match case RefinedType(tp1, rname, rinfo) => try tp1.separateRefinements(cls, refinements) finally if refinements != null then + val rinfo1 = widenSkolemsMap(rinfo) refinements(rname) = refinements.get(rname) match - case Some(tp) => tp & rinfo - case None => rinfo + case Some(tp) => tp & rinfo1 + case None => rinfo1 case tp @ AnnotatedType(tp1, ann) => tp.derivedAnnotatedType(tp1.separateRefinements(cls, refinements), ann) case tp: RecType => @@ -146,9 +149,11 @@ object NamerOps: */ def addConstructorApplies(scope: MutableScope, cls: ClassSymbol, modcls: ClassSymbol)(using Context): scope.type = def proxy(constr: Symbol): Symbol = + var flags = ApplyProxyFlags | (constr.flagsUNSAFE & AccessFlags) + if cls.is(Protected) && !modcls.is(Protected) then flags |= Protected newSymbol( modcls, nme.apply, - ApplyProxyFlags | (constr.flagsUNSAFE & AccessFlags), + flags, ApplyProxyCompleter(constr), cls.privateWithin, constr.coord) @@ -172,12 +177,15 @@ object NamerOps: /** A new symbol that is the constructor companion for class `cls` */ def classConstructorCompanion(cls: ClassSymbol)(using Context): TermSymbol = + var flags = ConstructorCompanionFlags + if cls.is(Protected) then flags |= Protected val companion = newModuleSymbol( cls.owner, cls.name.toTermName, - ConstructorCompanionFlags, ConstructorCompanionFlags, + flags, flags, constructorCompanionCompleter(cls), - coord = cls.coord, - compUnitInfo = cls.compUnitInfo) + cls.privateWithin, + cls.coord, + cls.compUnitInfo) companion.moduleClass.registerCompanion(cls) cls.registerCompanion(companion.moduleClass) companion diff --git a/compiler/src/dotty/tools/dotc/core/Names.scala b/compiler/src/dotty/tools/dotc/core/Names.scala index 3f9667b08067..a31ab0662ee4 100644 --- a/compiler/src/dotty/tools/dotc/core/Names.scala +++ b/compiler/src/dotty/tools/dotc/core/Names.scala @@ -401,7 +401,7 @@ object Names { } /** It's OK to take a toString if the stacktrace does not contain a method - * from GenBCode or it also contains one of the whitelisted methods below. + * from GenBCode or it also contains one of the allowed methods below. */ private def toStringOK = { val trace: Array[StackTraceElement] = Thread.currentThread.nn.getStackTrace.asInstanceOf[Array[StackTraceElement]] diff --git a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala index 8256a3cdbab1..150c39aa8e13 100644 --- a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -265,9 +265,9 @@ class OrderingConstraint(private val boundsMap: ParamBounds, private var coDeps: ReverseDeps = SimpleIdentityMap.empty /** A map that associates type parameters of this constraint with all other type - * parameters that refer to them in their bounds covariantly, such that, if the + * parameters that refer to them in their bounds contravariantly, such that, if the * type parameter is instantiated to a smaller type, the constraint would be narrowed. - * (i.e. solution set changes other than simply being made larger). + * (i.e. solution set changes other than simply being made smaller). */ private var contraDeps: ReverseDeps = SimpleIdentityMap.empty @@ -370,7 +370,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, /** Adjust reverse dependencies of all type parameters referenced by `bound` * @param isLower `bound` is a lower bound - * @param add if true, add referenced variables to dependencoes, otherwise drop them. + * @param add if true, add referenced variables to dependencies, otherwise drop them. */ def adjustReferenced(bound: Type, isLower: Boolean, add: Boolean) = adjuster.variance = if isLower then 1 else -1 @@ -396,8 +396,8 @@ class OrderingConstraint(private val boundsMap: ParamBounds, } case _ => false - /** Add or remove depenencies referenced in `bounds`. - * @param add if true, dependecies are added, otherwise they are removed + /** Add or remove dependencies referenced in `bounds`. + * @param add if true, dependencies are added, otherwise they are removed */ def adjustBounds(bounds: TypeBounds, add: Boolean) = adjustReferenced(bounds.lo, isLower = true, add) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 56d71c7fb57e..90e5544f19af 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -425,6 +425,7 @@ object StdNames { val array_length : N = "array_length" val array_update : N = "array_update" val arraycopy: N = "arraycopy" + val arity: N = "arity" val as: N = "as" val asTerm: N = "asTerm" val asModule: N = "asModule" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index be651842d9b0..54e18bf1ea1b 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -617,7 +617,7 @@ object SymDenotations { case _ => // Otherwise, no completion is necessary, see the preconditions of `markAbsent()`. (myInfo `eq` NoType) - || is(Invisible) && ctx.isTyper + || (is(Invisible) && !ctx.mode.is(Mode.ResolveFromTASTy)) && ctx.isTyper || is(ModuleVal, butNot = Package) && moduleClass.isAbsent(canForce) } @@ -1232,6 +1232,15 @@ object SymDenotations { else if (this.exists) owner.enclosingMethod else NoSymbol + /** The closest enclosing method or static symbol containing this definition. + * A local dummy owner is mapped to the primary constructor of the class. + */ + final def enclosingMethodOrStatic(using Context): Symbol = + if this.is(Method) || this.hasAnnotation(defn.ScalaStaticAnnot) then symbol + else if this.isClass then primaryConstructor + else if this.exists then owner.enclosingMethodOrStatic + else NoSymbol + /** The closest enclosing extension method containing this definition, * including methods outside the current class. */ @@ -1950,7 +1959,6 @@ object SymDenotations { /** The this-type depends on the kind of class: * - for a package class `p`: ThisType(TypeRef(Noprefix, p)) - * - for a module class `m`: A term ref to m's source module. * - for all other classes `c` with owner `o`: ThisType(TypeRef(o.thisType, c)) */ override def thisType(using Context): Type = { @@ -2754,6 +2762,9 @@ object SymDenotations { /** Sets all missing fields of given denotation */ def complete(denot: SymDenotation)(using Context): Unit + /** Is this a completer for an explicit type tree */ + def isExplicit: Boolean = false + def apply(sym: Symbol): LazyType = this def apply(module: TermSymbol, modcls: ClassSymbol): LazyType = this diff --git a/compiler/src/dotty/tools/dotc/core/SymUtils.scala b/compiler/src/dotty/tools/dotc/core/SymUtils.scala index 1a762737d52f..54ba0e3bdd06 100644 --- a/compiler/src/dotty/tools/dotc/core/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/SymUtils.scala @@ -366,23 +366,30 @@ class SymUtils: && self.owner.linkedClass.isDeclaredInfix /** Is symbol declared or inherits @experimental? */ - def isExperimental(using Context): Boolean = - self.hasAnnotation(defn.ExperimentalAnnot) - || (self.maybeOwner.isClass && self.owner.hasAnnotation(defn.ExperimentalAnnot)) + def isExperimental(using Context): Boolean = isFeatureAnnotated(defn.ExperimentalAnnot) + def isInExperimentalScope(using Context): Boolean = isInFeatureScope(defn.ExperimentalAnnot, _.isExperimental, _.isInExperimentalScope) - def isInExperimentalScope(using Context): Boolean = - def isDefaultArgumentOfExperimentalMethod = + /** Is symbol declared or inherits @preview? */ + def isPreview(using Context): Boolean = isFeatureAnnotated(defn.PreviewAnnot) + def isInPreviewScope(using Context): Boolean = isInFeatureScope(defn.PreviewAnnot, _.isPreview, _.isInPreviewScope) + + private inline def isFeatureAnnotated(checkAnnotaton: ClassSymbol)(using Context): Boolean = + self.hasAnnotation(checkAnnotaton) + || (self.maybeOwner.isClass && self.owner.hasAnnotation(checkAnnotaton)) + + private inline def isInFeatureScope(checkAnnotation: ClassSymbol, checkSymbol: Symbol => Boolean, checkOwner: Symbol => Boolean)(using Context): Boolean = + def isDefaultArgumentOfCheckedMethod = self.name.is(DefaultGetterName) && self.owner.isClass && { val overloads = self.owner.asClass.membersNamed(self.name.firstPart) overloads.filterWithFlags(HasDefaultParams, EmptyFlags) match - case denot: SymDenotation => denot.symbol.isExperimental + case denot: SymDenotation => checkSymbol(denot.symbol) case _ => false } - self.hasAnnotation(defn.ExperimentalAnnot) - || isDefaultArgumentOfExperimentalMethod - || (!self.is(Package) && self.owner.isInExperimentalScope) + self.hasAnnotation(checkAnnotation) + || isDefaultArgumentOfCheckedMethod + || (!self.is(Package) && checkOwner(self.owner)) /** The declared self type of this class, as seen from `site`, stripping * all refinements for opaque types. diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index 68f2e350c3e4..6a9d70a19f49 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -7,6 +7,7 @@ import java.nio.channels.ClosedByInterruptException import scala.util.control.NonFatal +import dotty.tools.dotc.classpath.{ ClassPathFactory, PackageNameUtils } import dotty.tools.dotc.classpath.FileUtils.{hasTastyExtension, hasBetastyExtension} import dotty.tools.io.{ ClassPath, ClassRepresentation, AbstractFile, NoAbstractFile } import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions @@ -77,7 +78,7 @@ object SymbolLoaders { * and give them `completer` as type. */ def enterPackage(owner: Symbol, pname: TermName, completer: (TermSymbol, ClassSymbol) => PackageLoader)(using Context): Symbol = { - val preExisting = owner.info.decls lookup pname + val preExisting = owner.info.decls.lookup(pname) if (preExisting != NoSymbol) // Some jars (often, obfuscated ones) include a package and // object with the same name. Rather than render them unusable, @@ -94,6 +95,18 @@ object SymbolLoaders { s"Resolving package/object name conflict in favor of object ${preExisting.fullName}. The package will be inaccessible.") return NoSymbol } + else if pname == nme.caps && owner == defn.ScalaPackageClass then + // `scala.caps`` was an object until 3.6, it is a package from 3.7. Without special handling + // this would cause a TypeError to be thrown below if a build has several versions of the + // Scala standard library on the classpath. This was the case for 29 projects in OpenCB. + // These projects should be updated. But until that's the case we issue a warning instead + // of a hard failure. + report.warning( + em"""$owner contains object and package with same name: $pname. + |This indicates that there are several versions of the Scala standard library on the classpath. + |The build should be reconfigured so that only one version of the standard library is on the classpath.""") + owner.info.decls.openForMutations.unlink(preExisting) + owner.info.decls.openForMutations.unlink(preExisting.moduleClass) else throw TypeError( em"""$owner contains object and package with same name: $pname @@ -272,7 +285,7 @@ object SymbolLoaders { def maybeModuleClass(classRep: ClassRepresentation): Boolean = classRep.name.nonEmpty && classRep.name.last == '$' - private def enterClasses(root: SymDenotation, packageName: String, flat: Boolean)(using Context) = { + def enterClasses(root: SymDenotation, packageName: String, flat: Boolean)(using Context) = { def isAbsent(classRep: ClassRepresentation) = !root.unforcedDecls.lookup(classRep.name.toTypeName).exists @@ -316,6 +329,32 @@ object SymbolLoaders { } } } + + def mergeNewEntries( + packageClass: ClassSymbol, fullPackageName: String, + jarClasspath: ClassPath, fullClasspath: ClassPath, + )(using Context): Unit = + if jarClasspath.classes(fullPackageName).nonEmpty then + // if the package contains classes in jarClasspath, the package is invalidated (or removed if there are no more classes in it) + val packageVal = packageClass.sourceModule.asInstanceOf[TermSymbol] + if packageClass.isRoot then + val loader = new PackageLoader(packageVal, fullClasspath) + loader.enterClasses(defn.EmptyPackageClass, fullPackageName, flat = false) + loader.enterClasses(defn.EmptyPackageClass, fullPackageName, flat = true) + else if packageClass.ownersIterator.contains(defn.ScalaPackageClass) then + () // skip + else if fullClasspath.hasPackage(fullPackageName) then + packageClass.info = new PackageLoader(packageVal, fullClasspath) + else + packageClass.owner.info.decls.openForMutations.unlink(packageVal) + else + for p <- jarClasspath.packages(fullPackageName) do + val subPackageName = PackageNameUtils.separatePkgAndClassNames(p.name)._2.toTermName + val subPackage = packageClass.info.decl(subPackageName).orElse: + // package does not exist in symbol table, create a new symbol + enterPackage(packageClass, subPackageName, (module, modcls) => new PackageLoader(module, fullClasspath)) + mergeNewEntries(subPackage.asSymDenotation.moduleClass.asClass, p.name, jarClasspath, fullClasspath) + end mergeNewEntries } /** A lazy type that completes itself by calling parameter doComplete. diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 7de75e371752..c8ede8bfdec2 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -357,13 +357,25 @@ object Symbols extends SymUtils { targets.match case (tp: NamedType) :: _ => tp.symbol.sourceSymbol case _ => this - else if (denot.is(Synthetic)) { + else if denot.is(Synthetic) then val linked = denot.linkedClass if (linked.exists && !linked.is(Synthetic)) linked else denot.owner.sourceSymbol - } + else if ( + denot.is(TypeParam) && + denot.maybeOwner.maybeOwner.isAllOf(EnumCase) && + denot.maybeOwner.isPrimaryConstructor + ) then + val enclosingEnumCase = denot.maybeOwner.maybeOwner + val caseTypeParam = enclosingEnumCase.typeParams.find(_.name == denot.name) + if caseTypeParam.exists(_.is(Synthetic)) then + val enumClass = enclosingEnumCase.info.firstParent.typeSymbol + val sourceTypeParam = enumClass.typeParams.find(_.name == denot.name) + sourceTypeParam.getOrElse(this) + else + caseTypeParam.getOrElse(this) else if (denot.isPrimaryConstructor) denot.owner.sourceSymbol else this @@ -629,6 +641,32 @@ object Symbols extends SymUtils { newClassSymbol(owner, name, flags, completer, privateWithin, coord, compUnitInfo) } + /** Same as the other `newNormalizedClassSymbol` except that `parents` can be a function returning a list of arbitrary + * types which get normalized into type refs and parameter bindings and annotations can be assigned in the completer. + */ + def newNormalizedClassSymbol( + owner: Symbol, + name: TypeName, + flags: FlagSet, + parentTypes: Symbol => List[Type], + selfInfo: Type, + privateWithin: Symbol, + annotations: List[Tree], + coord: Coord, + compUnitInfo: CompilationUnitInfo | Null)(using Context): ClassSymbol = { + def completer = new LazyType { + def complete(denot: SymDenotation)(using Context): Unit = { + val cls = denot.asClass.classSymbol + val decls = newScope + val parents = parentTypes(cls).map(_.dealias) + assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class") + denot.info = ClassInfo(owner.thisType, cls, parents, decls, selfInfo) + denot.annotations = annotations.map(Annotations.Annotation(_)) + } + } + newClassSymbol(owner, name, flags, completer, privateWithin, coord, compUnitInfo) + } + def newRefinedClassSymbol(coord: Coord = NoCoord)(using Context): ClassSymbol = newCompleteClassSymbol(ctx.owner, tpnme.REFINE_CLASS, NonMember, parents = Nil, newScope, coord = coord) @@ -706,6 +744,34 @@ object Symbols extends SymUtils { privateWithin, coord, compUnitInfo) } + /** Same as `newNormalizedModuleSymbol` except that `parents` can be a function returning a list of arbitrary + * types which get normalized into type refs and parameter bindings. + */ + def newNormalizedModuleSymbol( + owner: Symbol, + name: TermName, + modFlags: FlagSet, + clsFlags: FlagSet, + parentTypes: ClassSymbol => List[Type], + decls: Scope, + privateWithin: Symbol, + coord: Coord, + compUnitInfo: CompilationUnitInfo | Null)(using Context): TermSymbol = { + def completer(module: Symbol) = new LazyType { + def complete(denot: SymDenotation)(using Context): Unit = { + val cls = denot.asClass.classSymbol + val decls = newScope + val parents = parentTypes(cls).map(_.dealias) + assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class") + denot.info = ClassInfo(owner.thisType, cls, parents, decls, TermRef(owner.thisType, module)) + } + } + newModuleSymbol( + owner, name, modFlags, clsFlags, + (module, modcls) => completer(module), + privateWithin, coord, compUnitInfo) + } + /** Create a package symbol with associated package class * from its non-info fields and a lazy type for loading the package's members. */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index ca3df65625a8..0139ff2c865e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -438,7 +438,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if (recur(info1.alias, tp2)) return true if (tp1.prefix.isStable) return tryLiftedToThis1 case _ => - if (tp1 eq NothingType) || isBottom(tp1) then return true + if isCaptureVarComparison then + return subCaptures(tp1.captureSet, tp2.captureSet, frozenConstraint).isOK + if (tp1 eq NothingType) || isBottom(tp1) then + return true } thirdTry case tp1: TypeParamRef => @@ -586,6 +589,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling || narrowGADTBounds(tp2, tp1, approx, isUpper = false)) && (isBottom(tp1) || GADTusage(tp2.symbol)) + if isCaptureVarComparison then + return subCaptures(tp1.captureSet, tp2.captureSet, frozenConstraint).isOK + isSubApproxHi(tp1, info2.lo) && (trustBounds || isSubApproxHi(tp1, info2.hi)) || compareGADT || tryLiftedToThis2 @@ -857,7 +863,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling } compareTypeBounds case CapturingType(parent2, refs2) => - def compareCapturing = + def compareCapturing: Boolean = val refs1 = tp1.captureSet try if refs1.isAlwaysEmpty then recur(tp1, parent2) @@ -966,17 +972,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling || compareGADT || tryLiftedToThis1 case _ => - // `Mode.RelaxedOverriding` is only enabled when checking Java overriding - // in explicit nulls, and `Null` becomes a bottom type, which allows - // `T | Null` being a subtype of `T`. - // A type variable `T` from Java is translated to `T >: Nothing <: Any`. - // However, `null` can always be a value of `T` for Java side. - // So the best solution here is to let `Null` be a subtype of non-primitive - // value types temporarily. def isNullable(tp: Type): Boolean = tp.dealias match case tp: TypeRef => val tpSym = tp.symbol - ctx.mode.is(Mode.RelaxedOverriding) && !tpSym.isPrimitiveValueClass || tpSym.isNullableClass case tp: TermRef => // https://scala-lang.org/files/archive/spec/2.13/03-types.html#singleton-types @@ -1579,6 +1577,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling (tp2a ne tp2) && recur(tp1, tp2a) && { opaquesUsed = true; true } } + def isCaptureVarComparison: Boolean = + isCaptureCheckingOrSetup + && tp1.derivesFrom(defn.Caps_CapSet) + && tp2.derivesFrom(defn.Caps_CapSet) + // begin recur if tp2 eq NoType then false else if tp1 eq tp2 then true @@ -2845,6 +2848,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling false Existential.isExistentialVar(tp1) && canInstantiateWith(assocExistentials) + def isOpenedExistential(ref: CaptureRef)(using Context): Boolean = + openedExistentials.contains(ref) + /** bi-map taking existentials to the left of a comparison to matching * existentials on the right. This is not a bijection. However * we have `forwards(backwards(bv)) == bv` for an existentially bound `bv`. @@ -2976,11 +2982,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling * * 1. Single inheritance of classes * 2. Final classes cannot be extended - * 3. ConstantTypes with distinct values are non intersecting - * 4. TermRefs with distinct values are non intersecting + * 3. ConstantTypes with distinct values are non-intersecting + * 4. TermRefs with distinct values are non-intersecting * 5. There is no value of type Nothing * - * Note on soundness: the correctness of match types relies on on the + * Note on soundness: the correctness of match types relies on the * property that in all possible contexts, the same match type expression * is either stuck or reduces to the same case. * @@ -3063,6 +3069,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling disjointnessBoundary(tp.effectiveBounds.hi) case tp: ErrorType => defn.AnyType + case tp: NoType.type => + defn.AnyType end disjointnessBoundary (disjointnessBoundary(tp1), disjointnessBoundary(tp2)) match @@ -3197,22 +3205,38 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling end match } + /** Are `cls1` and `cls1` provablyDisjoint classes, i.e., is `cls1 ⋔ cls2` true? + * + * Note that "class" where includes traits, module classes, and (in the recursive case) + * enum value term symbols. + */ private def provablyDisjointClasses(cls1: Symbol, cls2: Symbol)(using Context): Boolean = def isDecomposable(cls: Symbol): Boolean = cls.is(Sealed) && !cls.hasAnonymousChild def decompose(cls: Symbol): List[Symbol] = - cls.children.flatMap { child => + cls.children.map: child => if child.isTerm then - child.info.classSymbols // allow enum vals to be decomposed to their enum class (then filtered out) and any mixins - else child :: Nil - }.filter(child => child.exists && child != cls) + // Enum vals with mixins, such as in i21860 or i22266, + // don't have a single class symbol. + // So instead of decomposing to NoSymbol + // (which leads to erroneously considering an enum type + // as disjoint from one of the mixin, eg. i21860.scala), + // or instead of decomposing to all the class symbols of + // the enum value (which leads to other mixins being decomposed, + // and infinite recursion, eg. i22266), + // we decompose to the enum value term symbol, and handle + // that within the rest of provablyDisjointClasses. + child.info.classSymbol.orElse(child) + else child + .filter(child => child.exists && child != cls) def eitherDerivesFromOther(cls1: Symbol, cls2: Symbol): Boolean = cls1.derivesFrom(cls2) || cls2.derivesFrom(cls1) def smallestNonTraitBase(cls: Symbol): Symbol = - cls.asClass.baseClasses.find(!_.is(Trait)).get + val classes = if cls.isClass then cls.asClass.baseClasses else cls.info.classSymbols + classes.find(!_.is(Trait)).get if (eitherDerivesFromOther(cls1, cls2)) false @@ -3476,6 +3500,9 @@ object TypeComparer { def subsumesExistentially(tp1: TermParamRef, tp2: CaptureRef)(using Context) = comparing(_.subsumesExistentially(tp1, tp2)) + + def isOpenedExistential(ref: CaptureRef)(using Context) = + comparing(_.isOpenedExistential(ref)) } object MatchReducer: diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 33a1b6ae789e..4c705c4252c0 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -326,7 +326,7 @@ object TypeErasure { val sym = t.symbol // Only a few classes have both primitives and references as subclasses. if (sym eq defn.AnyClass) || (sym eq defn.AnyValClass) || (sym eq defn.MatchableClass) || (sym eq defn.SingletonClass) - || isScala2 && !(t.derivesFrom(defn.ObjectClass) || t.isNullType) then + || isScala2 && !(t.derivesFrom(defn.ObjectClass) || t.isNullType | t.isNothingType) then NoSymbol // We only need to check for primitives because derived value classes in arrays are always boxed. else if sym.isPrimitiveValueClass then @@ -596,8 +596,8 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst * will be returned. * * In all other situations, |T| will be computed as follow: - * - For a refined type scala.Array+[T]: - * - if T is Nothing or Null, []Object + * - For a refined type scala.Array[T]: + * - {Scala 2} if T is Nothing or Null, []Object * - otherwise, if T <: Object, []|T| * - otherwise, if T is a type parameter coming from Java, []Object * - otherwise, Object @@ -781,12 +781,14 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst private def eraseArray(tp: Type)(using Context) = { val defn.ArrayOf(elemtp) = tp: @unchecked - if (isGenericArrayElement(elemtp, isScala2 = sourceLanguage.isScala2)) defn.ObjectType - else - try - val eElem = erasureFn(sourceLanguage, semiEraseVCs = false, isConstructor, isSymbol, inSigName)(elemtp) - if eElem.isInstanceOf[WildcardType] then WildcardType - else JavaArrayType(eElem) + if isGenericArrayElement(elemtp, isScala2 = sourceLanguage.isScala2) then + defn.ObjectType + else if sourceLanguage.isScala2 && (elemtp.hiBound.isNullType || elemtp.hiBound.isNothingType) then + JavaArrayType(defn.ObjectType) + else + try erasureFn(sourceLanguage, semiEraseVCs = false, isConstructor, isSymbol, inSigName)(elemtp) match + case _: WildcardType => WildcardType + case elem => JavaArrayType(elem) catch case ex: Throwable => handleRecursive("erase array type", tp.show, ex) } diff --git a/compiler/src/dotty/tools/dotc/core/TypeEval.scala b/compiler/src/dotty/tools/dotc/core/TypeEval.scala index 4d5496cff880..03821ad4812a 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeEval.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeEval.scala @@ -111,6 +111,15 @@ object TypeEval: nestedPairs(fieldLabels) :: nestedPairs(fieldTypes) :: Nil else arg.widenDealias match case arg @ defn.NamedTuple(_, _) => Some(arg) + case arg if arg.derivesFrom(defn.TupleClass) => + val fieldTypesOpt = tupleElementTypes(arg) + fieldTypesOpt match + case Some(fieldTypes) => + val fieldLabels = (for i <- 1 to fieldTypes.length yield ConstantType(Constant(s"_$i"))).toList + Some: + defn.NamedTupleTypeRef.appliedTo: + nestedPairs(fieldLabels) :: nestedPairs(fieldTypes) :: Nil + case _ => None case _ => None def constantFold1[T](extractor: Type => Option[T], op: T => Any): Option[Type] = diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index 14ccf32c7787..739cc2b74a16 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -128,22 +128,19 @@ class TypeUtils: case None => throw new AssertionError("not a tuple") def namedTupleElementTypesUpTo(bound: Int, derived: Boolean, normalize: Boolean = true)(using Context): List[(TermName, Type)] = + def extractNamesTypes(nmes: Type, vals: Type): List[(TermName, Type)] = + val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: + case ConstantType(Constant(str: String)) => str.toTermName + case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") + val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) + names.zip(values) + (if normalize then self.normalized else self).dealias match // for desugaring and printer, ignore derived types to avoid infinite recursion in NamedTuple.unapply - case AppliedType(tycon, nmes :: vals :: Nil) if !derived && tycon.typeSymbol == defn.NamedTupleTypeRef.symbol => - val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: - case ConstantType(Constant(str: String)) => str.toTermName - case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") - val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) - names.zip(values) + case defn.NamedTupleDirect(nmes, vals) => extractNamesTypes(nmes, vals) case t if !derived => Nil // default cause, used for post-typing - case defn.NamedTuple(nmes, vals) => - val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: - case ConstantType(Constant(str: String)) => str.toTermName - case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") - val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) - names.zip(values) + case defn.NamedTuple(nmes, vals) => extractNamesTypes(nmes, vals) case t => Nil diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index f77f268d6ee6..15b295af963e 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1164,17 +1164,14 @@ object Types extends TypeUtils { * * @param isSubType a function used for checking subtype relationships. */ - final def overrides(that: Type, relaxedCheck: Boolean, matchLoosely: => Boolean, checkClassInfo: Boolean = true, + final def overrides(that: Type, matchLoosely: => Boolean, checkClassInfo: Boolean = true, isSubType: (Type, Type) => Context ?=> Boolean = (tp1, tp2) => tp1 frozen_<:< tp2)(using Context): Boolean = { - val overrideCtx = if relaxedCheck then ctx.relaxedOverrideContext else ctx - inContext(overrideCtx) { - !checkClassInfo && this.isInstanceOf[ClassInfo] - || isSubType(this.widenExpr, that.widenExpr) - || matchLoosely && { - val this1 = this.widenNullaryMethod - val that1 = that.widenNullaryMethod - ((this1 `ne` this) || (that1 `ne` that)) && this1.overrides(that1, relaxedCheck, false, checkClassInfo) - } + !checkClassInfo && this.isInstanceOf[ClassInfo] + || isSubType(this.widenExpr, that.widenExpr) + || matchLoosely && { + val this1 = this.widenNullaryMethod + val that1 = that.widenNullaryMethod + ((this1 `ne` this) || (that1 `ne` that)) && this1.overrides(that1, false, checkClassInfo) } } @@ -1200,8 +1197,8 @@ object Types extends TypeUtils { */ def matches(that: Type)(using Context): Boolean = { record("matches") - val overrideCtx = if ctx.explicitNulls then ctx.relaxedOverrideContext else ctx - TypeComparer.matchesType(this, that, relaxed = !ctx.phase.erasedTypes)(using overrideCtx) + withoutMode(Mode.SafeNulls)( + TypeComparer.matchesType(this, that, relaxed = !ctx.phase.erasedTypes)) } /** This is the same as `matches` except that it also matches => T with T and @@ -1407,9 +1404,9 @@ object Types extends TypeUtils { case tp => tp - /** Widen all top-level singletons reachable by dealiasing - * and going to the operands of & and |. - * Overridden and cached in OrType. + /** Widen all top-level singletons reachable by dealiasing and going to the + * operands of intersections and soft unions (only when `skipSoftUnions` is + * `false`). Overridden and cached in [[OrType]]. */ def widenSingletons(skipSoftUnions: Boolean = false)(using Context): Type = dealias match { case tp: SingletonType => @@ -2480,7 +2477,7 @@ object Types extends TypeUtils { else lastd match { case lastd: SymDenotation => if stillValid(lastd) && checkedPeriod.code != NowhereCode then finish(lastd.current) - else finish(memberDenot(lastd.initial.name, allowPrivate = false)) + else finish(memberDenot(lastd.initial.name, allowPrivate = lastd.is(Private))) case _ => fromDesignator } @@ -3634,7 +3631,7 @@ object Types extends TypeUtils { myAtoms override def widenSingletons(skipSoftUnions: Boolean)(using Context): Type = - if isSoft && skipSoftUnions then this + if !isSoft || skipSoftUnions then this else if widenedRunId != ctx.runId then myWidened = computeWidenSingletons() @@ -6079,6 +6076,9 @@ object Types extends TypeUtils { def forward(ref: CaptureRef): CaptureRef = val result = this(ref) def ensureTrackable(tp: Type): CaptureRef = tp match + /* Issue #22437: handle case when info is not yet available during postProcess in CC setup */ + case tp: (TypeParamRef | TermRef) if tp.underlying == NoType => + tp case tp: CaptureRef => if tp.isTrackableRef then tp else ensureTrackable(tp.underlying) @@ -6090,6 +6090,9 @@ object Types extends TypeUtils { /** A restriction of the inverse to a function on tracked CaptureRefs */ def backward(ref: CaptureRef): CaptureRef = inverse(ref) match + /* Ensure bijection for issue #22437 fix in method forward above: */ + case result: (TypeParamRef | TermRef) if result.underlying == NoType => + result case result: CaptureRef if result.isTrackableRef => result end BiTypeMap @@ -6325,6 +6328,8 @@ object Types extends TypeUtils { override def stopAt = thisMap.stopAt def apply(tp: Type) = f(thisMap(tp)) } + + override def toString = s"${getClass.getSimpleName}@$hashCode" // otherwise would print as } /** A type map that maps also parents and self type of a ClassInfo */ diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 3af0fc6603d5..cfbdc854a88f 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -1079,7 +1079,7 @@ class ClassfileParser( // Nothing$ and Null$ were incorrectly emitted with a Scala attribute // instead of ScalaSignature before 2.13.0-M2, see https://github.com/scala/scala/pull/5952 - private val scalaUnpickleWhitelist = List(tpnme.nothingClass, tpnme.nullClass) + private val scalaUnpickleAllowlist = List(tpnme.nothingClass, tpnme.nullClass) /** Parse inner classes. Expects `in.bp` to point to the superclass entry. * Restores the old `bp`. @@ -1152,7 +1152,7 @@ class ClassfileParser( return None } - if scan(tpnme.ScalaATTR) && !scalaUnpickleWhitelist.contains(classRoot.name) + if scan(tpnme.ScalaATTR) && !scalaUnpickleAllowlist.contains(classRoot.name) && !(classRoot.name.startsWith("Tuple") && classRoot.name.endsWith("$sp")) && !(classRoot.name.startsWith("Product") && classRoot.name.endsWith("$sp")) then diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 7b80c7c80a21..cf9885d16d1f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -861,7 +861,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { assert(isModifierTag(tag)) writeByte(tag) } - assert(!flags.is(Scala2x)) + if flags.is(Scala2x) then assert(attributes.scala2StandardLibrary) if (flags.is(Private)) writeModTag(PRIVATE) if (flags.is(Protected)) writeModTag(PROTECTED) if (flags.is(Final, butNot = Module)) writeModTag(FINAL) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index c07121a52191..d6f2812dad0d 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1563,7 +1563,7 @@ class TreeUnpickler(reader: TastyReader, * - sbt-test/tasty-compat/remove-override * - sbt-test/tasty-compat/move-method */ - def lookupInSuper = + def lookupInSuper(using Context) = val cls = ownerTpe.classSymbol if cls.exists then cls.asClass.classDenot @@ -1572,7 +1572,8 @@ class TreeUnpickler(reader: TastyReader, else NoDenotation - val denot = + + def searchDenot(using Context): Denotation = if owner.is(JavaAnnotation) && name == nme.CONSTRUCTOR then // #19951 Fix up to read TASTy produced before 3.5.0 -- ignore the signature ownerTpe.nonPrivateDecl(name).asSeenFrom(prefix) @@ -1580,6 +1581,9 @@ class TreeUnpickler(reader: TastyReader, val d = ownerTpe.decl(name).atSignature(sig, target) (if !d.exists then lookupInSuper else d).asSeenFrom(prefix) + val denot = inContext(ctx.addMode(Mode.ResolveFromTASTy)): + searchDenot // able to resolve Invisible members + makeSelect(qual, name, denot) case REPEATED => val elemtpt = readTpt() diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 3b91312740d1..25245f5ca1b6 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -535,7 +535,14 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas true) && // We discard the private val representing a case accessor. We only enter the case accessor def. // We do need to load these symbols to read properly unpickle the annotations on the symbol (see sbt-test/scala2-compat/i19421). - !flags.isAllOf(CaseAccessor | PrivateLocal, butNot = Method) + !flags.isAllOf(CaseAccessor | PrivateLocal, butNot = Method) && + // Skip entering extension methods: they will be recreated by the ExtensionMethods phase. + // Same trick is used by tasty-query (see + //https://github.com/scalacenter/tasty-query/blob/fdefadcabb2f21d5c4b71f728b81c68f6fddcc0f/tasty-query/shared/src/main/scala/tastyquery/reader/pickles/PickleReader.scala#L261-L273 + //) + // This trick is also useful when reading the Scala 2 Standard library from tasty, since + // the extension methods will not be present, and it avoid having to distinguish between Scala2 pickles and Scala2 tasty (stdlib) + !(owner.is(ModuleClass) && sym.name.endsWith("$extension")) if (canEnter) owner.asClass.enter(sym, symScope(owner)) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 2242174f78f2..92cae663352a 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -98,7 +98,8 @@ object Inliner: // InlinerMap is a TreeTypeMap with special treatment for inlined arguments: // They are generally left alone (not mapped further, and if they wrap a type - // the type Inlined wrapper gets dropped + // the type Inlined wrapper gets dropped. + // As a side effect, register @nowarn annotations from annotated expressions. private class InlinerMap( typeMap: Type => Type, treeMap: Tree => Tree, @@ -115,6 +116,23 @@ object Inliner: ConservativeTreeCopier() ): + override def transform(tree: Tree)(using Context): Tree = + tree match + case Typed(expr, tpt) => + def loop(tpe: Type): Unit = + tpe match + case AnnotatedType(parent, annot) => + if annot.hasSymbol(defn.NowarnAnnot) then + val argPos = annot.argument(0).getOrElse(tree).sourcePos + val conf = annot.argumentConstantString(0).getOrElse("") + ctx.run.nn.suppressions.registerNowarn(tree.sourcePos, expr.span)(conf, argPos) + else + loop(parent) + case _ => + loop(tpt.tpe) + case _ => + super.transform(tree) + override def copy( typeMap: Type => Type, treeMap: Tree => Tree, @@ -144,9 +162,28 @@ object Inliner: else Nil case _ => Nil val refinements = openOpaqueAliases(cls.givenSelfType) + + // Map references in the refinements from the proxied termRef + // to the recursive type of the refined type + // e.g.: Obj.type{type A = Obj.B; type B = Int} -> Obj.type{type A = .B; type B = Int} + def mapRecTermRefReferences(recType: RecType, refinedType: Type) = + new TypeMap { + def apply(tp: Type) = tp match + case RefinedType(a: RefinedType, b, info) => RefinedType(apply(a), b, apply(info)) + case RefinedType(a, b, info) => RefinedType(a, b, apply(info)) + case TypeRef(prefix, des) => TypeRef(apply(prefix), des) + case termRef: TermRef if termRef == ref => recType.recThis + case _ => mapOver(tp) + }.apply(refinedType) + val refinedType = refinements.foldLeft(ref: Type): (parent, refinement) => RefinedType(parent, refinement._1, TypeAlias(refinement._2)) - val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, refinedType, span) + + val recType = RecType.closeOver ( recType => + mapRecTermRefReferences(recType, refinedType) + ) + + val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, recType, span) refiningSym.termRef def unapply(refiningRef: TermRef)(using Context): Option[TermRef] = @@ -365,6 +402,9 @@ class Inliner(val call: tpd.Tree)(using Context): */ private val opaqueProxies = new mutable.ListBuffer[(TermRef, TermRef)] + /** TermRefs for which we already started synthesising proxies */ + private val visitedTermRefs = new mutable.HashSet[TermRef] + protected def hasOpaqueProxies = opaqueProxies.nonEmpty /** Map first halves of opaqueProxies pairs to second halves, using =:= as equality */ @@ -377,17 +417,30 @@ class Inliner(val call: tpd.Tree)(using Context): * type aliases, add proxy definitions to `opaqueProxies` that expose these aliases. */ private def addOpaqueProxies(tp: Type, span: Span, forThisProxy: Boolean)(using Context): Unit = - tp.foreachPart { + val foreachTpPart = + (p: Type => Unit) => + if forThisProxy then + // Performs operations on all parts of this type, outside of the applied type arguments + new ForeachAccumulator(p, StopAt.None) { + override def apply(x: Unit, tp: Type) = tp match + case AppliedType(tycon, _) => super.apply(x, tycon) + case other => super.apply(x, other) + }.apply((), tp) + else tp.foreachPart(p) + foreachTpPart { case ref: TermRef => for cls <- ref.widen.baseClasses do if cls.containsOpaques && (forThisProxy || inlinedMethod.isContainedIn(cls)) - && mapRef(ref).isEmpty + && !visitedTermRefs.contains(ref) then + visitedTermRefs += ref val refiningRef = OpaqueProxy(ref, cls, call.span) val refiningSym = refiningRef.symbol.asTerm val refinedType = refiningRef.info - val refiningDef = ValDef(refiningSym, tpd.ref(ref).cast(refinedType), inferred = true).withSpan(span) + val refiningDef = addProxiesForRecurrentOpaques( + ValDef(refiningSym, tpd.ref(ref).cast(refinedType), inferred = true).withSpan(span) + ) inlining.println(i"add opaque alias proxy $refiningDef for $ref in $tp") bindingsBuf += refiningDef opaqueProxies += ((ref, refiningSym.termRef)) @@ -407,6 +460,27 @@ class Inliner(val call: tpd.Tree)(using Context): } ) + /** Transforms proxies that reference other opaque types, like for: + * object Obj1 { opaque type A = Int } + * object Obj2 { opaque type B = A } + * and proxy$1 of type Obj2.type{type B = Obj1.A} + * creates proxy$2 of type Obj1.type{type A = Int} + * and transforms proxy$1 into Obj2.type{type B = proxy$2.A} + */ + private def addProxiesForRecurrentOpaques(binding: ValDef)(using Context): ValDef = + def fixRefinedTypes(ref: Type): Unit = + ref match + case recType: RecType => fixRefinedTypes(recType.underlying) + case RefinedType(parent, name, info) => + addOpaqueProxies(info.widen, binding.span, true) + fixRefinedTypes(parent) + case _ => + fixRefinedTypes(binding.symbol.info) + binding.symbol.info = mapOpaques.typeMap(binding.symbol.info) + mapOpaques.transform(binding).asInstanceOf[ValDef] + .showing(i"transformed this binding exposing opaque aliases: $result", inlining) + end addProxiesForRecurrentOpaques + /** If `binding` contains TermRefs that refer to objects with opaque * type aliases, add proxy definitions that expose these aliases * and substitute such TermRefs with theproxies. Example from pos/opaque-inline1.scala: diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 85b1234461c8..85bea871b955 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -23,6 +23,7 @@ import collection.mutable import reporting.{NotConstant, trace} import util.Spans.Span import dotty.tools.dotc.core.Periods.PhaseId +import dotty.tools.dotc.util.chaining.* /** Support for querying inlineable methods and for inlining calls to such methods */ object Inlines: @@ -44,6 +45,11 @@ object Inlines: def bodyToInline(sym: SymDenotation)(using Context): Tree = if hasBodyToInline(sym) then sym.getAnnotation(defn.BodyAnnot).get.tree + .tap: body => + for annot <- sym.getAnnotation(defn.NowarnAnnot) do + val argPos = annot.argument(0).getOrElse(annot.tree).sourcePos + val conf = annot.argumentConstantString(0).getOrElse("") + ctx.run.nn.suppressions.registerNowarn(annot.tree.sourcePos, body.span)(conf, argPos) else EmptyTree diff --git a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala index bb950fbe43cd..47a47f10f905 100644 --- a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala @@ -105,8 +105,8 @@ object PrepareInlineable { def preTransform(tree: Tree)(using Context): Tree = tree match { case tree: RefTree if needsAccessor(tree.symbol) => if (tree.symbol.isConstructor) { - report.error("Implementation restriction: cannot use private constructors in inline methods", tree.srcPos) - tree // TODO: create a proper accessor for the private constructor + report.error("Private constructors used in inline methods require @publicInBinary", tree.srcPos) + tree } else val accessor = useAccessor(tree) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 333af6a26b3b..e59a8e0b882d 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -90,6 +90,8 @@ object Completion: * Otherwise, provide no completion suggestion. */ def completionMode(path: List[untpd.Tree], pos: SourcePosition): Mode = path match + // Ignore `package foo@@` and `package foo.bar@@` + case ((_: tpd.Select) | (_: tpd.Ident)):: (_ : tpd.PackageDef) :: _ => Mode.None case GenericImportSelector(sel) => if sel.imported.span.contains(pos.span) then Mode.ImportOrExport // import scala.@@ else if sel.isGiven && sel.bound.span.contains(pos.span) then Mode.ImportOrExport @@ -147,11 +149,12 @@ object Completion: checkBacktickPrefix(ident.source.content(), ident.span.start, ident.span.end) case (tree: untpd.RefTree) :: _ if tree.name != nme.ERROR => - tree.name.toString.take(pos.span.point - tree.span.point) - - case _ => naiveCompletionPrefix(pos.source.content().mkString, pos.point) - + val nameStart = tree.span.point + val start = if pos.source.content().lift(nameStart).contains('`') then nameStart + 1 else nameStart + tree.name.toString.take(pos.span.point - start) + case _ => + naiveCompletionPrefix(pos.source.content().mkString, pos.point) end completionPrefix private object GenericImportSelector: @@ -662,7 +665,7 @@ object Completion: */ private def implicitConversionTargets(qual: tpd.Tree)(using Context): Set[SearchSuccess] = { val typer = ctx.typer - val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span).allImplicits + val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span, Set.empty).allImplicits interactiv.println(i"implicit conversion targets considered: ${conversions.toList}%, %") conversions diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index 16079125a434..973fc2f3d79d 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -144,6 +144,7 @@ object Interactive { ( sym == tree.symbol || sym.exists && sym == tree.symbol.sourceSymbol + || sym.exists && sym.sourceSymbol == tree.symbol || !include.isEmpty && sym.name == tree.symbol.name && sym.maybeOwner != tree.symbol.maybeOwner && ( include.isOverridden && overrides(sym, tree.symbol) || include.isOverriding && overrides(tree.symbol, sym) diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index fe797c66d104..d1164d4742af 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -260,7 +260,8 @@ object JavaParsers { t } - def optArrayBrackets(tpt: Tree): Tree = + def optArrayBrackets(tpt: Tree): Tree = { + annotations() if (in.token == LBRACKET) { val tpt1 = atSpan(tpt.span.start, in.offset) { arrayOf(tpt) } in.nextToken() @@ -268,6 +269,7 @@ object JavaParsers { optArrayBrackets(tpt1) } else tpt + } def basicType(): Tree = atSpan(in.offset) { @@ -341,7 +343,7 @@ object JavaParsers { } def annotations(): List[Tree] = { - var annots = new ListBuffer[Tree] + val annots = new ListBuffer[Tree] while (in.token == AT) { in.nextToken() annotation() match { diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 38f554c10b0a..20eb6e9b33fa 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -34,6 +34,7 @@ import config.Feature.{sourceVersion, migrateTo3} import config.SourceVersion.* import config.SourceVersion import dotty.tools.dotc.config.MigrationVersion +import dotty.tools.dotc.util.chaining.* object Parsers { @@ -104,6 +105,9 @@ object Parsers { private val InCase: Region => Region = Scanners.InCase(_) private val InCond: Region => Region = Scanners.InParens(LPAREN, _) private val InFor : Region => Region = Scanners.InBraces(_) + private val InOldCond: Region => Region = // old-style Cond to allow indent when InParens, see #22608 + case p: Scanners.InParens => Scanners.Indented(p.indentWidth, p.prefix, p) + case r => r def unimplementedExpr(using Context): Select = Select(scalaDot(nme.Predef), nme.???) @@ -667,7 +671,7 @@ object Parsers { else leading :: Nil def maybeNamed(op: () => Tree): () => Tree = () => - if isIdent && in.lookahead.token == EQUALS && in.featureEnabled(Feature.namedTuples) then + if isIdent && in.lookahead.token == EQUALS && sourceVersion.enablesNamedTuples then atSpan(in.offset): val name = ident() in.nextToken() @@ -884,7 +888,7 @@ object Parsers { } }) canRewrite &= (in.isAfterLineEnd || statCtdTokens.contains(in.token)) // test (5) - if canRewrite && (!underColonSyntax || Feature.fewerBracesEnabled) then + if canRewrite && (!underColonSyntax || sourceVersion.enablesFewerBraces) then val openingPatchStr = if !colonRequired then "" else if testChar(startOpening - 1, Chars.isOperatorPart(_)) then " :" @@ -1010,7 +1014,7 @@ object Parsers { skipParams() lookahead.isColon && { - !sourceVersion.isAtLeast(`3.6`) + !sourceVersion.enablesNewGivens || { // in the new given syntax, a `:` at EOL after an identifier represents a single identifier given // Example: // given C: @@ -1087,10 +1091,11 @@ object Parsers { */ def followingIsLambdaAfterColon(): Boolean = val lookahead = in.LookaheadScanner(allowIndent = true) + .tap(_.currentRegion.knownWidth = in.currentRegion.indentWidth) def isArrowIndent() = lookahead.isArrow && { - lookahead.nextToken() + lookahead.observeArrowIndented() lookahead.token == INDENT || lookahead.token == EOF } lookahead.nextToken() @@ -1155,6 +1160,13 @@ object Parsers { patch(source, infixOp.span, asApply.show(using ctx.withoutColors)) asApply // allow to use pre-3.6 syntax in migration mode else infixOp + case Parens(assign @ Assign(ident, value)) if !isNamedTupleOperator => + report.errorOrMigrationWarning(DeprecatedInfixNamedArgumentSyntax(), infixOp.right.srcPos, MigrationVersion.AmbiguousNamedTupleSyntax) + if MigrationVersion.AmbiguousNamedTupleSyntax.needsPatch then + val asApply = cpy.Apply(infixOp)(Select(opInfo.operand, opInfo.operator.name), assign :: Nil) + patch(source, infixOp.span, asApply.show(using ctx.withoutColors)) + asApply // allow to use pre-3.6 syntax in migration mode + else infixOp case _ => infixOp } @@ -1163,7 +1175,7 @@ object Parsers { * body */ def isColonLambda = - Feature.fewerBracesEnabled && in.token == COLONfollow && followingIsLambdaAfterColon() + sourceVersion.enablesFewerBraces && in.token == COLONfollow && followingIsLambdaAfterColon() /** operand { infixop operand | MatchClause } [postfixop], * @@ -1868,7 +1880,7 @@ object Parsers { infixOps(t, canStartInfixTypeTokens, operand, Location.ElseWhere, ParseKind.Type, isOperator = !followingIsVararg() && !isPureArrow - && !(isIdent(nme.as) && sourceVersion.isAtLeast(`3.6`) && inContextBound) + && !(isIdent(nme.as) && sourceVersion.enablesNewGivens && inContextBound) && nextCanFollowOperator(canStartInfixTypeTokens)) /** RefinedType ::= WithType {[nl] Refinement} [`^` CaptureSet] @@ -2172,7 +2184,7 @@ object Parsers { if namedOK && isIdent && in.lookahead.token == EQUALS then commaSeparated(() => namedArgType()) - else if tupleOK && isIdent && in.lookahead.isColon && in.featureEnabled(Feature.namedTuples) then + else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.enablesNamedTuples then commaSeparated(() => namedElem()) else commaSeparated(() => argType()) @@ -2261,11 +2273,12 @@ object Parsers { def contextBound(pname: TypeName): Tree = val t = toplevelTyp(inContextBound = true) val ownName = - if isIdent(nme.as) && sourceVersion.isAtLeast(`3.6`) then + if isIdent(nme.as) && sourceVersion.enablesNewGivens then in.nextToken() ident() else EmptyTermName - ContextBoundTypeTree(t, pname, ownName) + val newSpan = t.span.withPoint(t.span.end).withEnd(in.lastOffset) + ContextBoundTypeTree(t, pname, ownName).withSpan(newSpan) /** ContextBounds ::= ContextBound [`:` ContextBounds] * | `{` ContextBound {`,` ContextBound} `}` @@ -2273,7 +2286,7 @@ object Parsers { def contextBounds(pname: TypeName): List[Tree] = if in.isColon then in.nextToken() - if in.token == LBRACE && sourceVersion.isAtLeast(`3.6`) + if in.token == LBRACE && sourceVersion.enablesNewGivens then inBraces(commaSeparated(() => contextBound(pname))) else val bound = contextBound(pname) @@ -2322,25 +2335,25 @@ object Parsers { def condExpr(altToken: Token): Tree = val t: Tree = if in.token == LPAREN then - var t: Tree = atSpan(in.offset): - makeTupleOrParens(inParensWithCommas(commaSeparated(exprInParens))) - if in.token != altToken then - if toBeContinued(altToken) then - t = inSepRegion(InCond) { + inSepRegion(InOldCond): // allow inferred NEWLINE for observeIndented below + atSpan(in.offset): + makeTupleOrParens(inParensWithCommas(commaSeparated(exprInParens))) + .pipe: t => + if in.token == altToken then t + else if toBeContinued(altToken) then + inSepRegion(InCond): expr1Rest( postfixExprRest( simpleExprRest(t, Location.ElseWhere), Location.ElseWhere), Location.ElseWhere) - } else if rewriteToNewSyntax(t.span) then - dropParensOrBraces(t.span.start, s"${tokenString(altToken)}") + dropParensOrBraces(t.span.start, tokenString(altToken)) in.observeIndented() return t - t else if in.isNestedStart then - try expr() finally newLinesOpt() + expr().tap(_ => newLinesOpt()) else inSepRegion(InCond)(expr()) if rewriteToOldSyntax(t.span.startPos) then revertToParens(t) @@ -2943,7 +2956,7 @@ object Parsers { /** Enumerators ::= Generator {semi Enumerator | Guard} */ def enumerators(): List[Tree] = - if in.featureEnabled(Feature.betterFors) then + if sourceVersion.enablesBetterFors then aliasesUntilGenerator() ++ enumeratorsRest() else generator() :: enumeratorsRest() @@ -3497,7 +3510,7 @@ object Parsers { val hkparams = typeParamClauseOpt(ParamOwner.Hk) val bounds = if paramOwner.acceptsCtxBounds then typeAndCtxBounds(name) - else if sourceVersion.isAtLeast(`3.6`) && paramOwner == ParamOwner.Type then typeAndCtxBounds(name) + else if sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then typeAndCtxBounds(name) else typeBounds() TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods) } @@ -3966,7 +3979,7 @@ object Parsers { val ident = termIdent() var name = ident.name.asTermName val paramss = - if Feature.clauseInterleavingEnabled(using in.languageImportContext) then + if sourceVersion.enablesClauseInterleaving then typeOrTermParamClauses(ParamOwner.Def, numLeadParams) else val tparams = typeParamClauseOpt(ParamOwner.Def) @@ -4066,7 +4079,7 @@ object Parsers { case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF => makeTypeDef(typeAndCtxBounds(tname)) case _ if (staged & StageKind.QuotedPattern) != 0 - || sourceVersion.isAtLeast(`3.6`) && in.isColon => + || sourceVersion.enablesNewGivens && in.isColon => makeTypeDef(typeAndCtxBounds(tname)) case _ => syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token)) @@ -4241,7 +4254,7 @@ object Parsers { def givenDef(start: Offset, mods: Modifiers, givenMod: Mod) = atSpan(start, nameStart) { var mods1 = addMod(mods, givenMod) val nameStart = in.offset - var newSyntaxAllowed = sourceVersion.isAtLeast(`3.6`) + var newSyntaxAllowed = sourceVersion.enablesNewGivens val hasEmbeddedColon = !in.isColon && followingIsGivenDefWithColon() val name = if isIdent && hasEmbeddedColon then ident() else EmptyTermName diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 2007b633a7c5..6ae2f7999d33 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -17,7 +17,7 @@ import scala.collection.mutable import scala.collection.immutable.SortedMap import rewrites.Rewrites.patch import config.Feature -import config.Feature.{migrateTo3, fewerBracesEnabled} +import config.Feature.migrateTo3 import config.SourceVersion.{`3.0`, `3.0-migration`} import config.MigrationVersion import reporting.{NoProfile, Profile, Message} @@ -603,6 +603,20 @@ object Scanners { lastWidth = r.knownWidth newlineIsSeparating = r.isInstanceOf[InBraces] + // can emit OUTDENT if line is not non-empty blank line at EOF + inline def isTrailingBlankLine: Boolean = + token == EOF && { + val end = buf.length - 1 // take terminal NL as empty last line + val prev = buf.lastIndexWhere(!isWhitespace(_), end = end) + prev < 0 || end - prev > 0 && isLineBreakChar(buf(prev)) + } + + inline def canDedent: Boolean = + lastToken != INDENT + && !isLeadingInfixOperator(nextWidth) + && !statCtdTokens.contains(lastToken) + && !isTrailingBlankLine + if newlineIsSeparating && canEndStatTokens.contains(lastToken) && canStartStatTokens.contains(token) @@ -615,7 +629,7 @@ object Scanners { || nextWidth == lastWidth && (indentPrefix == MATCH || indentPrefix == CATCH) && token != CASE then if currentRegion.isOutermost then if nextWidth < lastWidth then currentRegion = topLevelRegion(nextWidth) - else if !isLeadingInfixOperator(nextWidth) && !statCtdTokens.contains(lastToken) && lastToken != INDENT then + else if canDedent then currentRegion match case r: Indented => insert(OUTDENT, offset) @@ -637,7 +651,6 @@ object Scanners { insert(OUTDENT, offset) else if r.isInstanceOf[InBraces] && !closingRegionTokens.contains(token) then report.warning("Line is indented too far to the left, or a `}` is missing", sourcePos()) - else if lastWidth < nextWidth || lastWidth == nextWidth && (lastToken == MATCH || lastToken == CATCH) && token == CASE then if canStartIndentTokens.contains(lastToken) then @@ -657,20 +670,37 @@ object Scanners { def spaceTabMismatchMsg(lastWidth: IndentWidth, nextWidth: IndentWidth): Message = em"""Incompatible combinations of tabs and spaces in indentation prefixes. |Previous indent : $lastWidth - |Latest indent : $nextWidth""" + |Latest indent : $nextWidth""" def observeColonEOL(inTemplate: Boolean): Unit = val enabled = if token == COLONop && inTemplate then report.deprecationWarning(em"`:` after symbolic operator is deprecated; use backticks around operator instead", sourcePos(offset)) true - else token == COLONfollow && (inTemplate || fewerBracesEnabled) + else token == COLONfollow && (inTemplate || sourceVersion.enablesFewerBraces) if enabled then peekAhead() val atEOL = isAfterLineEnd || token == EOF reset() if atEOL then token = COLONeol + // consume => and insert if applicable. Used to detect colon arrow: x => + def observeArrowIndented(): Unit = + if isArrow && indentSyntax then + peekAhead() + val atEOL = isAfterLineEnd + val atEOF = token == EOF + reset() + if atEOF then + token = EOF + else if atEOL then + val nextWidth = indentWidth(next.offset) + val lastWidth = currentRegion.indentWidth + if lastWidth < nextWidth then + currentRegion = Indented(nextWidth, COLONeol, currentRegion) + offset = next.offset + token = INDENT + def observeIndented(): Unit = if indentSyntax && isNewLine then val nextWidth = indentWidth(next.offset) @@ -679,7 +709,6 @@ object Scanners { currentRegion = Indented(nextWidth, COLONeol, currentRegion) offset = next.offset token = INDENT - end observeIndented /** Insert an token if next token closes an indentation region. * Exception: continue if indentation region belongs to a `match` and next token is `case`. @@ -1099,7 +1128,7 @@ object Scanners { reset() next - class LookaheadScanner(val allowIndent: Boolean = false) extends Scanner(source, offset, allowIndent = allowIndent) { + class LookaheadScanner(allowIndent: Boolean = false) extends Scanner(source, offset, allowIndent = allowIndent) { override protected def initialCharBufferSize = 8 override def languageImportContext = Scanner.this.languageImportContext } @@ -1651,7 +1680,7 @@ object Scanners { case class InCase(outer: Region) extends Region(OUTDENT) /** A class describing an indentation region. - * @param width The principal indendation width + * @param width The principal indentation width * @param prefix The token before the initial of the region */ case class Indented(width: IndentWidth, prefix: Token, outer: Region | Null) extends Region(OUTDENT): diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index cac82eb0c4bd..53e9a2c2098b 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -15,7 +15,7 @@ import util.SourcePosition import scala.util.control.NonFatal import scala.annotation.switch import config.{Config, Feature} -import cc.{CapturingType, RetainingType, CaptureSet, ReachCapability, MaybeCapability, isBoxed, retainedElems, isRetainsLike} +import cc.* class PlainPrinter(_ctx: Context) extends Printer { @@ -187,10 +187,11 @@ class PlainPrinter(_ctx: Context) extends Printer { homogenize(tp) match { case tp: TypeType => toTextRHS(tp) + case tp: TermRef if tp.isRootCapability => + toTextCaptureRef(tp) case tp: TermRef if !tp.denotationIsCurrent && !homogenizedView // always print underlying when testing picklers - && !tp.isRootCapability || tp.symbol.is(Module) || tp.symbol.name == nme.IMPORT => toTextRef(tp) ~ ".type" @@ -297,7 +298,10 @@ class PlainPrinter(_ctx: Context) extends Printer { else if (annot.symbol == defn.IntoAnnot || annot.symbol == defn.IntoParamAnnot) && !printDebug then atPrec(GlobalPrec)( Str("into ") ~ toText(tpe) ) - else toTextLocal(tpe) ~ " " ~ toText(annot) + else if annot.isInstanceOf[CaptureAnnotation] then + toTextLocal(tpe) ~ "^" ~ toText(annot) + else + toTextLocal(tpe) ~ " " ~ toText(annot) case FlexibleType(_, tpe) => "(" ~ toText(tpe) ~ ")?" case tp: TypeVar => diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 27ab73f0fe4d..32115e6bc087 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -168,7 +168,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { changePrec(GlobalPrec) { val argStr: Text = if args.length == 2 - && !defn.isTupleNType(args.head) + && !defn.isDirectTupleNType(args.head) && !isGiven then atPrec(InfixPrec) { argText(args.head) } diff --git a/compiler/src/dotty/tools/dotc/report.scala b/compiler/src/dotty/tools/dotc/report.scala index 2ccf918e12fa..7f1eeb8e22eb 100644 --- a/compiler/src/dotty/tools/dotc/report.scala +++ b/compiler/src/dotty/tools/dotc/report.scala @@ -1,15 +1,12 @@ package dotty.tools.dotc -import reporting.* -import Diagnostic.* -import util.{SourcePosition, NoSourcePosition, SrcPos} -import core.* -import Contexts.*, Flags.*, Symbols.*, Decorators.* -import config.SourceVersion import ast.* -import config.Feature.sourceVersion +import core.*, Contexts.*, Flags.*, Symbols.*, Decorators.* +import config.Feature.sourceVersion, config.{MigrationVersion, SourceVersion} +import reporting.*, Diagnostic.* +import util.{SourcePosition, NoSourcePosition, SrcPos} + import java.lang.System.currentTimeMillis -import dotty.tools.dotc.config.MigrationVersion object report: @@ -55,6 +52,9 @@ object report: else issueWarning(new FeatureWarning(msg, pos.sourcePos)) end featureWarning + def warning(msg: Message, pos: SrcPos, origin: String)(using Context): Unit = + issueWarning(LintWarning(msg, addInlineds(pos), origin)) + def warning(msg: Message, pos: SrcPos)(using Context): Unit = issueWarning(new Warning(msg, addInlineds(pos))) diff --git a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala index 6a2d88f4e82f..20be33716831 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala @@ -8,9 +8,9 @@ import dotty.tools.dotc.config.Settings.Setting import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.interfaces.Diagnostic.{ERROR, INFO, WARNING} import dotty.tools.dotc.util.SourcePosition +import dotty.tools.dotc.util.chaining.* import java.util.{Collections, Optional, List => JList} -import scala.util.chaining.* import core.Decorators.toMessage object Diagnostic: @@ -36,6 +36,18 @@ object Diagnostic: pos: SourcePosition ) extends Error(msg, pos) + /** A Warning with an origin. The semantics of `origin` depend on the warning. + * For example, an unused import warning has an origin that specifies the unused selector. + * The origin of a deprecation is the deprecated element. + */ + trait OriginWarning(val origin: String): + self: Warning => + + /** Lints are likely to be filtered. Provide extra axes for filtering by `-Wconf`. + */ + class LintWarning(msg: Message, pos: SourcePosition, origin: String) + extends Warning(msg, pos), OriginWarning(origin) + class Warning( msg: Message, pos: SourcePosition @@ -73,13 +85,9 @@ object Diagnostic: def enablingOption(using Context): Setting[Boolean] = ctx.settings.unchecked } - class DeprecationWarning( - msg: Message, - pos: SourcePosition, - val origin: String - ) extends ConditionalWarning(msg, pos) { + class DeprecationWarning(msg: Message, pos: SourcePosition, origin: String) + extends ConditionalWarning(msg, pos), OriginWarning(origin): def enablingOption(using Context): Setting[Boolean] = ctx.settings.deprecation - } class MigrationWarning( msg: Message, @@ -104,5 +112,5 @@ class Diagnostic( override def diagnosticRelatedInformation: JList[interfaces.DiagnosticRelatedInformation] = Collections.emptyList() - override def toString: String = s"$getClass at $pos: $message" + override def toString: String = s"$getClass at $pos L${pos.line+1}: $message" end Diagnostic diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index cc78203e873f..8f8f4676f43b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -132,7 +132,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case MissingCompanionForStaticID // errorNumber: 116 case PolymorphicMethodMissingTypeInParentID // errorNumber: 117 case ParamsNoInlineID // errorNumber: 118 - case JavaSymbolIsNotAValueID // errorNumber: 119 + case SymbolIsNotAValueID // errorNumber: 119 case DoubleDefinitionID // errorNumber: 120 case MatchCaseOnlyNullWarningID // errorNumber: 121 case ImportedTwiceID // errorNumber: 122 @@ -220,6 +220,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case DeprecatedInfixNamedArgumentSyntaxID // errorNumber: 204 case GivenSearchPriorityID // errorNumber: 205 case EnumMayNotBeValueClassesID // errorNumber: 206 + case IllegalUnrollPlacementID // errorNumber: 207 + case ExtensionHasDefaultID // errorNumber: 208 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index 7db5112b6674..7fddfc8d6ed0 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -62,7 +62,7 @@ trait MessageRendering { } val syntax = - if (ctx.settings.color.value != "never") + if (ctx.settings.color.value != "never" && !ctx.isJava) SyntaxHighlighting.highlight(new String(pos.linesSlice)).toCharArray else pos.linesSlice val lines = linesFrom(syntax) @@ -209,20 +209,21 @@ trait MessageRendering { sb.toString } - private def appendFilterHelp(dia: Diagnostic, sb: StringBuilder): Unit = + private def appendFilterHelp(dia: Diagnostic, sb: StringBuilder)(using Context, Level, Offset): Unit = + extension (sb: StringBuilder) def nl: sb.type = sb.append(EOL).append(offsetBox) import dia.msg val hasId = msg.errorId.errorNumber >= 0 val (category, origin) = dia match - case _: UncheckedWarning => ("unchecked", "") + case _: UncheckedWarning => ("unchecked", "") case w: DeprecationWarning => ("deprecation", w.origin) - case _: FeatureWarning => ("feature", "") - case _ => ("", "") + case _: FeatureWarning => ("feature", "") + case _ => ("", "") var entitled = false def addHelp(what: String)(value: String): Unit = if !entitled then - sb.append(EOL).append("Matching filters for @nowarn or -Wconf:") + sb.nl.append("Matching filters for @nowarn or -Wconf:") entitled = true - sb.append(EOL).append(" - ").append(what).append(value) + sb.nl.append(" - ").append(what).append(value) if hasId then addHelp("id=E")(msg.errorId.errorNumber.toString) addHelp("name=")(msg.errorId.productPrefix.stripSuffix("ID")) diff --git a/compiler/src/dotty/tools/dotc/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index 1896e5269d6c..ac25f2f6cd30 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -14,11 +14,13 @@ import scala.annotation.internal.sharable import scala.util.matching.Regex enum MessageFilter: - def matches(message: Diagnostic): Boolean = this match + def matches(message: Diagnostic): Boolean = + import Diagnostic.* + this match case Any => true - case Deprecated => message.isInstanceOf[Diagnostic.DeprecationWarning] - case Feature => message.isInstanceOf[Diagnostic.FeatureWarning] - case Unchecked => message.isInstanceOf[Diagnostic.UncheckedWarning] + case Deprecated => message.isInstanceOf[DeprecationWarning] + case Feature => message.isInstanceOf[FeatureWarning] + case Unchecked => message.isInstanceOf[UncheckedWarning] case MessageID(errorId) => message.msg.errorId == errorId case MessagePattern(pattern) => val noHighlight = message.msg.message.replaceAll("\\e\\[[\\d;]*[^\\d;]","") @@ -31,7 +33,7 @@ enum MessageFilter: pattern.findFirstIn(path).nonEmpty case Origin(pattern) => message match - case message: Diagnostic.DeprecationWarning => pattern.findFirstIn(message.origin).nonEmpty + case message: OriginWarning => pattern.findFirstIn(message.origin).nonEmpty case _ => false case None => false @@ -56,12 +58,12 @@ object WConf: private type Conf = (List[MessageFilter], Action) def parseAction(s: String): Either[List[String], Action] = s match - case "error" | "e" => Right(Error) - case "warning" | "w" => Right(Warning) - case "verbose" | "v" => Right(Verbose) - case "info" | "i" => Right(Info) - case "silent" | "s" => Right(Silent) - case _ => Left(List(s"unknown action: `$s`")) + case "error" | "e" => Right(Error) + case "warning" | "w" => Right(Warning) + case "verbose" | "v" => Right(Verbose) + case "info" | "i" => Right(Info) + case "silent" | "s" => Right(Silent) + case _ => Left(List(s"unknown action: `$s`")) private def regex(s: String) = try Right(s.r) @@ -134,11 +136,13 @@ object WConf: if (parseErrorss.nonEmpty) Left(parseErrorss.flatten) else Right(WConf(configs)) -class Suppression(val annotPos: SourcePosition, filters: List[MessageFilter], val start: Int, end: Int, val verbose: Boolean): +class Suppression(val annotPos: SourcePosition, filters: List[MessageFilter], val start: Int, val end: Int, val verbose: Boolean): private var _used = false def used: Boolean = _used - def markUsed(): Unit = { _used = true } - + def markUsed(): Unit = + _used = true def matches(dia: Diagnostic): Boolean = val pos = dia.pos pos.exists && start <= pos.start && pos.end <= end && filters.forall(_.matches(dia)) + + override def toString = s"Suppress in ${annotPos.source} $start..$end [${filters.mkString(", ")}]" diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 3fe7b51c7aa0..b5d67f0808b2 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2317,7 +2317,7 @@ class ParamsNoInline(owner: Symbol)(using Context) def explain(using Context) = "" } -class JavaSymbolIsNotAValue(symbol: Symbol)(using Context) extends TypeMsg(JavaSymbolIsNotAValueID) { +class SymbolIsNotAValue(symbol: Symbol)(using Context) extends TypeMsg(SymbolIsNotAValueID) { def msg(using Context) = val kind = if symbol is Package then i"$symbol" @@ -2514,6 +2514,17 @@ class ExtensionNullifiedByMember(method: Symbol, target: Symbol)(using Context) | |The extension may be invoked as though selected from an arbitrary type if conversions are in play.""" +class ExtensionHasDefault(method: Symbol)(using Context) + extends Message(ExtensionHasDefaultID): + def kind = MessageKind.PotentialIssue + def msg(using Context) = + i"""Extension method ${hl(method.name.toString)} should not have a default argument for its receiver.""" + def explain(using Context) = + i"""The receiver cannot be omitted when an extension method is invoked as a selection. + |A default argument for that parameter would never be used in that case. + |An extension method can be invoked as a regular method, but if that is the intended usage, + |it should not be defined as an extension.""" + class TraitCompanionWithMutableStatic()(using Context) extends SyntaxMsg(TraitCompanionWithMutableStaticID) { def msg(using Context) = i"Companion of traits cannot define mutable @static fields" @@ -3290,22 +3301,24 @@ extends TypeMsg(ConstructorProxyNotValueID): |companion value with the (term-)name `A`. However, these context bound companions |are not values themselves, they can only be referred to in selections.""" -class UnusedSymbol(errorText: String)(using Context) +class UnusedSymbol(errorText: String, val actions: List[CodeAction] = Nil)(using Context) extends Message(UnusedSymbolID) { def kind = MessageKind.UnusedSymbol override def msg(using Context) = errorText override def explain(using Context) = "" + override def actions(using Context) = this.actions } -object UnusedSymbol { - def imports(using Context): UnusedSymbol = new UnusedSymbol(i"unused import") - def localDefs(using Context): UnusedSymbol = new UnusedSymbol(i"unused local definition") - def explicitParams(using Context): UnusedSymbol = new UnusedSymbol(i"unused explicit parameter") - def implicitParams(using Context): UnusedSymbol = new UnusedSymbol(i"unused implicit parameter") - def privateMembers(using Context): UnusedSymbol = new UnusedSymbol(i"unused private member") - def patVars(using Context): UnusedSymbol = new UnusedSymbol(i"unused pattern variable") -} +object UnusedSymbol: + def imports(actions: List[CodeAction])(using Context): UnusedSymbol = UnusedSymbol(i"unused import", actions) + def localDefs(using Context): UnusedSymbol = UnusedSymbol(i"unused local definition") + def explicitParams(using Context): UnusedSymbol = UnusedSymbol(i"unused explicit parameter") + def implicitParams(using Context): UnusedSymbol = UnusedSymbol(i"unused implicit parameter") + def privateMembers(using Context): UnusedSymbol = UnusedSymbol(i"unused private member") + def patVars(using Context): UnusedSymbol = UnusedSymbol(i"unused pattern variable") + def unsetLocals(using Context): UnusedSymbol = UnusedSymbol(i"unset local variable, consider using an immutable val instead") + def unsetPrivates(using Context): UnusedSymbol = UnusedSymbol(i"unset private variable, consider using an immutable val instead") class NonNamedArgumentInJavaAnnotation(using Context) extends SyntaxMsg(NonNamedArgumentInJavaAnnotationID): @@ -3338,10 +3351,10 @@ final class QuotedTypeMissing(tpe: Type)(using Context) extends StagingMessage(Q i"Reference to $tpe within quotes requires a given ${witness} in scope" override protected def explain(using Context): String = - i"""Referencing `$tpe` inside a quoted expression requires a `${witness}` to be in scope. + i"""Referencing `$tpe` inside a quoted expression requires a `${witness}` to be in scope. |Since Scala is subject to erasure at runtime, the type information will be missing during the execution of the code. - |`${witness}` is therefore needed to carry `$tpe`'s type information into the quoted code. - |Without an implicit `${witness}`, the type `$tpe` cannot be properly referenced within the expression. + |`${witness}` is therefore needed to carry `$tpe`'s type information into the quoted code. + |Without an implicit `${witness}`, the type `$tpe` cannot be properly referenced within the expression. |To resolve this, ensure that a `${witness}` is available, either through a context-bound or explicitly. |""" @@ -3349,7 +3362,7 @@ end QuotedTypeMissing final class DeprecatedAssignmentSyntax(key: Name, value: untpd.Tree)(using Context) extends SyntaxMsg(DeprecatedAssignmentSyntaxID): override protected def msg(using Context): String = - i"""Deprecated syntax: in the future it would be interpreted as a named tuple with one element, + i"""Deprecated syntax: since 3.7 this is interpreted as a named tuple with one element, |not as an assignment. | |To assign a value, use curly braces: `{${key} = ${value}}`.""" @@ -3359,9 +3372,9 @@ final class DeprecatedAssignmentSyntax(key: Name, value: untpd.Tree)(using Conte class DeprecatedInfixNamedArgumentSyntax()(using Context) extends SyntaxMsg(DeprecatedInfixNamedArgumentSyntaxID): def msg(using Context) = - i"""Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument. + i"""Deprecated syntax: infix named arguments lists are deprecated; since 3.7 it is interpreted as a single name tuple argument. |To avoid this warning, either remove the argument names or use dotted selection.""" - + Message.rewriteNotice("This", version = SourceVersion.`3.6-migration`) + + Message.rewriteNotice("This", version = SourceVersion.`3.7-migration`) def explain(using Context) = "" @@ -3408,3 +3421,26 @@ final class EnumMayNotBeValueClasses(sym: Symbol)(using Context) extends SyntaxM def explain(using Context) = "" end EnumMayNotBeValueClasses + +class IllegalUnrollPlacement(origin: Option[Symbol])(using Context) +extends DeclarationMsg(IllegalUnrollPlacementID): + def msg(using Context) = origin match + case None => "@unroll is only allowed on a method parameter" + case Some(method) => + val isCtor = method.isConstructor + def what = if isCtor then i"a ${if method.owner.is(Trait) then "trait" else "class"} constructor" else i"method ${method.name}" + val prefix = s"Cannot unroll parameters of $what" + if method.is(Deferred) then + i"$prefix: it must not be abstract" + else if isCtor && method.owner.is(Trait) then + i"implementation restriction: $prefix" + else if !(isCtor || method.is(Final) || method.owner.is(ModuleClass)) then + i"$prefix: it is not final" + else if method.owner.companionClass.is(CaseClass) then + i"$prefix of a case class companion object: please annotate the class constructor instead" + else + assert(method.owner.is(CaseClass)) + i"$prefix of a case class: please annotate the class constructor instead" + + def explain(using Context) = "" +end IllegalUnrollPlacement diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 75e859111932..c303c40485ce 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -28,7 +28,7 @@ import ExtractAPI.NonLocalClassSymbolsInCurrentUnits import scala.collection.mutable import scala.util.hashing.MurmurHash3 -import scala.util.chaining.* +import dotty.tools.dotc.util.chaining.* /** This phase sends a representation of the API of classes to sbt via callbacks. * diff --git a/compiler/src/dotty/tools/dotc/sbt/package.scala b/compiler/src/dotty/tools/dotc/sbt/package.scala index 1c6b38b07a84..8efa25569325 100644 --- a/compiler/src/dotty/tools/dotc/sbt/package.scala +++ b/compiler/src/dotty/tools/dotc/sbt/package.scala @@ -10,7 +10,6 @@ import interfaces.IncrementalCallback import dotty.tools.io.FileWriters.BufferingReporter import dotty.tools.dotc.core.Decorators.em -import scala.util.chaining.given import scala.util.control.NonFatal inline val TermNameHash = 1987 // 300th prime diff --git a/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala b/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala index 81f5d37f443f..84993b531c69 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala @@ -90,9 +90,15 @@ class SemanticSymbolBuilder: b.append('+').append(idx + 1) case _ => end find - val sig = sym.signature - val targetName = sym.targetName - find(sym => sym.signature == sig && sym.targetName == targetName) + try + val sig = sym.signature + val targetName = sym.targetName + find(sym => sym.signature == sig && sym.targetName == targetName) + catch + // sym.signature might not exist + // this solves tests/best-effort/compiler-semanticdb-crash + case _: MissingType if ctx.usedBestEffortTasty => + def addDescriptor(sym: Symbol): Unit = if sym.is(ModuleClass) then @@ -111,7 +117,7 @@ class SemanticSymbolBuilder: addName(b, sym.name) if sym.is(Package) then b.append('/') else if sym.isType || sym.isAllOf(JavaModule) then b.append('#') - else if sym.isOneOf(Method | Mutable) + else if sym.is(Method) || (sym.is(Mutable) && !sym.is(JavaDefined)) && (!sym.is(StableRealizable) || sym.isConstructor) then b.append('('); addOverloadIdx(sym); b.append(").") else b.append('.') diff --git a/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala b/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala index 4293ecd6ca43..2d98535657a2 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala @@ -9,13 +9,13 @@ import core.Annotations.Annotation import core.Flags import core.Names.Name import core.StdNames.tpnme -import scala.util.chaining.scalaUtilChainingOps import collection.mutable import dotty.tools.dotc.{semanticdb => s} import Scala3.{FakeSymbol, SemanticSymbol, WildcardTypeSymbol, TypeParamRefSymbol, TermParamRefSymbol, RefinementSymbol} import dotty.tools.dotc.core.Names.Designator +import dotty.tools.dotc.util.chaining.* class TypeOps: import SymbolScopeOps.* diff --git a/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala b/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala index 85f2e84429c3..b958b64f7c54 100644 --- a/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala +++ b/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala @@ -203,11 +203,19 @@ class CrossStageSafety extends TreeMapWithStages { /** Check level consistency of terms references */ private def checkLevelConsistency(tree: Ident | This)(using Context): Unit = + def isStatic(pre: Type)(using Context): Boolean = pre match + case pre: NamedType => + val sym = pre.currentSymbol + sym.is(Package) || sym.isStaticOwner && isStatic(pre.prefix) + case pre: ThisType => isStatic(pre.tref) + case _ => true new TypeTraverser { def traverse(tp: Type): Unit = tp match case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level != levelOf(tp.symbol) => levelError(tp.symbol, tp, tree.srcPos) + case tp: ThisType if isStatic(tp) => + // static object (OK) case tp: ThisType if level != -1 && level != levelOf(tp.cls) => levelError(tp.cls, tp, tree.srcPos) case tp: AnnotatedType => @@ -215,7 +223,7 @@ class CrossStageSafety extends TreeMapWithStages { case _ if tp.typeSymbol.is(Package) => // OK case _ => - traverseChildren(tp) + traverseChildren(tp) }.traverse(tree.tpe) private def levelError(sym: Symbol, tp: Type, pos: SrcPos)(using Context): tp.type = { diff --git a/compiler/src/dotty/tools/dotc/transform/CheckShadowing.scala b/compiler/src/dotty/tools/dotc/transform/CheckShadowing.scala index 87d652bd9133..3adb3ab0ce7d 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckShadowing.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckShadowing.scala @@ -49,26 +49,22 @@ class CheckShadowing extends MiniPhase: override def description: String = CheckShadowing.description + override def isEnabled(using Context): Boolean = ctx.settings.Wshadow.value.nonEmpty + override def isRunnable(using Context): Boolean = - super.isRunnable && - ctx.settings.Wshadow.value.nonEmpty && - !ctx.isJava + super.isRunnable && ctx.settings.Wshadow.value.nonEmpty && !ctx.isJava - // Setup before the traversal override def prepareForUnit(tree: tpd.Tree)(using Context): Context = val data = ShadowingData() val fresh = ctx.fresh.setProperty(_key, data) shadowingDataApply(sd => sd.registerRootImports())(using fresh) - // Reporting on traversal's end override def transformUnit(tree: tpd.Tree)(using Context): tpd.Tree = shadowingDataApply(sd => reportShadowing(sd.getShadowingResult) ) tree - // MiniPhase traversal : - override def prepareForPackageDef(tree: tpd.PackageDef)(using Context): Context = shadowingDataApply(sd => sd.inNewScope()) ctx @@ -91,16 +87,16 @@ class CheckShadowing extends MiniPhase: ) override def prepareForTypeDef(tree: tpd.TypeDef)(using Context): Context = - if tree.symbol.isAliasType then // if alias, the parent is the current symbol - nestedTypeTraverser(tree.symbol).traverse(tree.rhs) - if tree.symbol.is(Param) then // if param, the parent is up - val owner = tree.symbol.owner + val sym = tree.symbol + if sym.isAliasType then // if alias, the parent is the current symbol + nestedTypeTraverser(sym).traverse(tree.rhs) + if sym.is(Param) then // if param, the parent is up + val owner = sym.owner val parent = if (owner.isConstructor) then owner.owner else owner nestedTypeTraverser(parent).traverse(tree.rhs)(using ctx.outer) - shadowingDataApply(sd => sd.registerCandidate(parent, tree)) - else - ctx - + if isValidTypeParamOwner(sym.owner) then + shadowingDataApply(sd => sd.registerCandidate(parent, tree)) + ctx override def transformPackageDef(tree: tpd.PackageDef)(using Context): tpd.Tree = shadowingDataApply(sd => sd.outOfScope()) @@ -115,13 +111,14 @@ class CheckShadowing extends MiniPhase: tree override def transformTypeDef(tree: tpd.TypeDef)(using Context): tpd.Tree = - if tree.symbol.is(Param) && isValidTypeParamOwner(tree.symbol.owner) then // Do not register for constructors the work is done for the Class owned equivalent TypeDef + // Do not register for constructors the work is done for the Class owned equivalent TypeDef + if tree.symbol.is(Param) && isValidTypeParamOwner(tree.symbol.owner) then shadowingDataApply(sd => sd.computeTypeParamShadowsFor(tree.symbol.owner)(using ctx.outer)) - if tree.symbol.isAliasType then // No need to start outer here, because the TypeDef reached here it's already the parent + // No need to start outer here, because the TypeDef reached here it's already the parent + if tree.symbol.isAliasType then shadowingDataApply(sd => sd.computeTypeParamShadowsFor(tree.symbol)(using ctx)) tree - // Helpers : private def isValidTypeParamOwner(owner: Symbol)(using Context): Boolean = !owner.isConstructor && !owner.is(Synthetic) && !owner.is(Exported) @@ -141,7 +138,7 @@ class CheckShadowing extends MiniPhase: override def traverse(tree: tpd.Tree)(using Context): Unit = tree match - case t:tpd.TypeDef => + case t: tpd.TypeDef => val newCtx = shadowingDataApply(sd => sd.registerCandidate(parent, t) ) @@ -157,7 +154,7 @@ class CheckShadowing extends MiniPhase: override def traverse(tree: tpd.Tree)(using Context): Unit = tree match - case t:tpd.Import => + case t: tpd.Import => val newCtx = shadowingDataApply(sd => sd.registerImport(t)) traverseChildren(tree)(using newCtx) case _ => @@ -240,7 +237,7 @@ object CheckShadowing: val declarationScope = ctx.effectiveScope val res = declarationScope.lookup(symbol.name) res match - case s: Symbol if s.isType => Some(s) + case s: Symbol if s.isType && s != symbol => Some(s) case _ => lookForUnitShadowedType(symbol)(using ctx.outer) /** Register if the valDef is a private declaration that shadows an inherited field */ @@ -310,4 +307,3 @@ object CheckShadowing: case class ShadowResult(warnings: List[ShadowWarning]) end CheckShadowing - diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 6e626fc5dd9e..2ee3ea825edc 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -1,883 +1,975 @@ package dotty.tools.dotc.transform -import scala.annotation.tailrec -import scala.collection.mutable - -import dotty.tools.uncheckedNN -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Symbols.* -import dotty.tools.dotc.ast.tpd.{Inlined, TreeTraverser} -import dotty.tools.dotc.ast.untpd -import dotty.tools.dotc.ast.untpd.ImportSelector +import dotty.tools.dotc.ast.desugar.{ForArtifact, PatternVar} +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.ast.untpd, untpd.ImportSelector import dotty.tools.dotc.config.ScalaSettings import dotty.tools.dotc.core.Contexts.* -import dotty.tools.dotc.core.Decorators.{em, i} -import dotty.tools.dotc.core.Denotations.SingleDenotation import dotty.tools.dotc.core.Flags.* -import dotty.tools.dotc.core.Phases.Phase -import dotty.tools.dotc.core.StdNames +import dotty.tools.dotc.core.Names.{Name, SimpleName, DerivedName, TermName, termName} +import dotty.tools.dotc.core.NameOps.{isAnonymousFunctionName, isReplWrapperName} +import dotty.tools.dotc.core.NameKinds.{ + BodyRetainerName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName} +import dotty.tools.dotc.core.StdNames.nme +import dotty.tools.dotc.core.Symbols.{ClassSymbol, NoSymbol, Symbol, defn, isDeprecated, requiredClass, requiredModule} +import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.report -import dotty.tools.dotc.reporting.Message -import dotty.tools.dotc.reporting.UnusedSymbol as UnusedSymbolMessage -import dotty.tools.dotc.typer.ImportInfo -import dotty.tools.dotc.util.{Property, SrcPos} -import dotty.tools.dotc.core.Mode -import dotty.tools.dotc.core.Types.{AnnotatedType, ConstantType, NoType, TermRef, Type, TypeTraverser} -import dotty.tools.dotc.core.Flags.flagsString -import dotty.tools.dotc.core.Flags -import dotty.tools.dotc.core.Names.{Name, TermName} -import dotty.tools.dotc.core.NameOps.isReplWrapperName +import dotty.tools.dotc.reporting.{CodeAction, UnusedSymbol} +import dotty.tools.dotc.rewrites.Rewrites import dotty.tools.dotc.transform.MegaPhase.MiniPhase -import dotty.tools.dotc.core.Annotations -import dotty.tools.dotc.core.Definitions -import dotty.tools.dotc.core.NameKinds.WildcardParamName -import dotty.tools.dotc.core.Symbols.Symbol -import dotty.tools.dotc.core.StdNames.nme -import dotty.tools.dotc.util.Spans.Span -import scala.math.Ordering +import dotty.tools.dotc.typer.{ImportInfo, Typer} +import dotty.tools.dotc.typer.Deriving.OriginalTypeClass +import dotty.tools.dotc.util.{Property, Spans, SrcPos}, Spans.Span +import dotty.tools.dotc.util.Chars.{isLineBreakChar, isWhitespace} +import dotty.tools.dotc.util.chaining.* +import java.util.IdentityHashMap -/** - * A compiler phase that checks for unused imports or definitions - * - * Basically, it gathers definition/imports and their usage. If a - * definition/imports does not have any usage, then it is reported. - */ -class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _key: Property.Key[CheckUnused.UnusedData]) extends MiniPhase: - import CheckUnused.* - import UnusedData.* - - private inline def unusedDataApply[U](inline f: UnusedData => U)(using Context): Context = - ctx.property(_key) match - case Some(ud) => f(ud) - case None => () - ctx +import scala.collection.mutable, mutable.{ArrayBuilder, ListBuffer, Stack} - override def phaseName: String = CheckUnused.phaseNamePrefix + suffix +import CheckUnused.* - override def description: String = CheckUnused.description +/** A compiler phase that checks for unused imports or definitions. + */ +class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPhase: - override def isRunnable(using Context): Boolean = - super.isRunnable && - ctx.settings.WunusedHas.any && - !ctx.isJava + override def phaseName: String = s"checkUnused$suffix" - // ========== SETUP ============ + override def description: String = "check for unused elements" - override def prepareForUnit(tree: tpd.Tree)(using Context): Context = - val data = UnusedData() - tree.getAttachment(_key).foreach(oldData => - data.unusedAggregate = oldData.unusedAggregate - ) - val fresh = ctx.fresh.setProperty(_key, data) - tree.putAttachment(_key, data) - fresh + override def isEnabled(using Context): Boolean = ctx.settings.WunusedHas.any - // ========== END + REPORTING ========== + override def isRunnable(using Context): Boolean = super.isRunnable && ctx.settings.WunusedHas.any && !ctx.isJava - override def transformUnit(tree: tpd.Tree)(using Context): tpd.Tree = - unusedDataApply { ud => - ud.finishAggregation() - if(phaseMode == PhaseMode.Report) then - ud.unusedAggregate.foreach(reportUnused) - } + override def prepareForUnit(tree: Tree)(using Context): Context = + val infos = tree.getAttachment(refInfosKey).getOrElse: + RefInfos().tap(tree.withAttachment(refInfosKey, _)) + ctx.fresh.setProperty(refInfosKey, infos) + override def transformUnit(tree: Tree)(using Context): tree.type = + if phaseMode == PhaseMode.Report then + reportUnused() + tree.removeAttachment(refInfosKey) tree - // ========== MiniPhase Prepare ========== - override def prepareForOther(tree: tpd.Tree)(using Context): Context = - // A standard tree traverser covers cases not handled by the Mega/MiniPhase - traverser.traverse(tree) - ctx - - override def prepareForInlined(tree: tpd.Inlined)(using Context): Context = - traverser.traverse(tree.call) - ctx - - override def prepareForIdent(tree: tpd.Ident)(using Context): Context = + override def transformIdent(tree: Ident)(using Context): tree.type = if tree.symbol.exists then - unusedDataApply { ud => - @tailrec - def loopOnNormalizedPrefixes(prefix: Type, depth: Int): Unit = - // limit to 10 as failsafe for the odd case where there is an infinite cycle - if depth < 10 && prefix.exists then - ud.registerUsed(prefix.classSymbol, None) - loopOnNormalizedPrefixes(prefix.normalizedPrefix, depth + 1) - - loopOnNormalizedPrefixes(tree.typeOpt.normalizedPrefix, depth = 0) - ud.registerUsed(tree.symbol, Some(tree.name)) - } + // if in an inline expansion, resolve at summonInline (synthetic pos) or in an enclosing call site + val resolving = + refInfos.inlined.isEmpty + || tree.srcPos.isZeroExtentSynthetic + || refInfos.inlined.exists(_.sourcePos.contains(tree.srcPos.sourcePos)) + if resolving && !ignoreTree(tree) then + def loopOverPrefixes(prefix: Type, depth: Int): Unit = + if depth < 10 && prefix.exists && !prefix.classSymbol.isEffectiveRoot then + resolveUsage(prefix.classSymbol, nme.NO_NAME, NoPrefix) + loopOverPrefixes(prefix.normalizedPrefix, depth + 1) + if tree.srcPos.isZeroExtentSynthetic then + loopOverPrefixes(tree.typeOpt.normalizedPrefix, depth = 0) + resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject) else if tree.hasType then - unusedDataApply(_.registerUsed(tree.tpe.classSymbol, Some(tree.name))) - else - ctx - - override def prepareForSelect(tree: tpd.Select)(using Context): Context = - val name = tree.removeAttachment(OriginalName) - unusedDataApply(_.registerUsed(tree.symbol, name, includeForImport = tree.qualifier.span.isSynthetic)) - - override def prepareForBlock(tree: tpd.Block)(using Context): Context = - pushInBlockTemplatePackageDef(tree) - - override def prepareForTemplate(tree: tpd.Template)(using Context): Context = - pushInBlockTemplatePackageDef(tree) - - override def prepareForPackageDef(tree: tpd.PackageDef)(using Context): Context = - pushInBlockTemplatePackageDef(tree) - - override def prepareForValDef(tree: tpd.ValDef)(using Context): Context = - unusedDataApply{ud => - // do not register the ValDef generated for `object` - traverseAnnotations(tree.symbol) - if !tree.symbol.is(Module) then - ud.registerDef(tree) - if tree.name.startsWith("derived$") && tree.typeOpt != NoType then - ud.registerUsed(tree.typeOpt.typeSymbol, None, isDerived = true) - ud.addIgnoredUsage(tree.symbol) - } - - override def prepareForDefDef(tree: tpd.DefDef)(using Context): Context = - unusedDataApply: ud => - if !tree.symbol.is(Private) then - tree.termParamss.flatten.foreach { p => - ud.addIgnoredParam(p.symbol) - } - ud.registerTrivial(tree) - traverseAnnotations(tree.symbol) - ud.registerDef(tree) - ud.addIgnoredUsage(tree.symbol) - - override def prepareForTypeDef(tree: tpd.TypeDef)(using Context): Context = - unusedDataApply: ud => - traverseAnnotations(tree.symbol) - if !tree.symbol.is(Param) then // Ignore type parameter (as Scala 2) - ud.registerDef(tree) - ud.addIgnoredUsage(tree.symbol) - - override def prepareForBind(tree: tpd.Bind)(using Context): Context = - traverseAnnotations(tree.symbol) - unusedDataApply(_.registerPatVar(tree)) - - override def prepareForTypeTree(tree: tpd.TypeTree)(using Context): Context = - if !tree.isInstanceOf[tpd.InferredTypeTree] then typeTraverser(unusedDataApply).traverse(tree.tpe) - ctx - - override def prepareForAssign(tree: tpd.Assign)(using Context): Context = - unusedDataApply{ ud => - val sym = tree.lhs.symbol - if sym.exists then - ud.registerSetVar(sym) - } - - // ========== MiniPhase Transform ========== - - override def transformBlock(tree: tpd.Block)(using Context): tpd.Tree = - popOutBlockTemplatePackageDef() + resolveUsage(tree.tpe.classSymbol, tree.name, tree.tpe.importPrefix.skipPackageObject) tree - override def transformTemplate(tree: tpd.Template)(using Context): tpd.Tree = - popOutBlockTemplatePackageDef() + // import x.y; y may be rewritten x.y, also import x.z as y + override def transformSelect(tree: Select)(using Context): tree.type = + val name = tree.removeAttachment(OriginalName).getOrElse(nme.NO_NAME) + inline def isImportable = tree.qualifier.srcPos.isSynthetic + && tree.qualifier.tpe.match + case ThisType(_) | SuperType(_, _) => false + case qualtpe => qualtpe.isStable + if tree.srcPos.isSynthetic && tree.symbol == defn.TypeTest_unapply then + tree.qualifier.tpe.underlying.finalResultType match + case AppliedType(tycon, args) => + val res = + if tycon.typeSymbol == defn.TypeTestClass then args(1) // T in TypeTest[-S, T] + else if tycon.typeSymbol == defn.TypeableType then args(0) // T in Typeable[T] + else return tree + val target = res.dealias.typeSymbol + resolveUsage(target, target.name, res.importPrefix.skipPackageObject) // case _: T => + case _ => + else if isImportable || name.exists(_ != tree.symbol.name) then + if !ignoreTree(tree) then + resolveUsage(tree.symbol, name, tree.qualifier.tpe) + else if !ignoreTree(tree) then + refUsage(tree.symbol) tree - override def transformPackageDef(tree: tpd.PackageDef)(using Context): tpd.Tree = - popOutBlockTemplatePackageDef() + override def transformLiteral(tree: Literal)(using Context): tree.type = + tree.getAttachment(Typer.AdaptedTree).foreach(transformAllDeep) tree - override def transformValDef(tree: tpd.ValDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) + override def prepareForCaseDef(tree: CaseDef)(using Context): Context = + nowarner.traverse(tree.pat) + ctx + + override def prepareForApply(tree: Apply)(using Context): Context = + // ignore tupling of for assignments, as they are not usages of vars + if tree.hasAttachment(ForArtifact) then + tree match + case Apply(TypeApply(Select(fun, nme.apply), _), args) => + if fun.symbol.is(Module) && defn.isTupleClass(fun.symbol.companionClass) then + args.foreach(_.withAttachment(ForArtifact, ())) + case _ => + ctx + + override def prepareForAssign(tree: Assign)(using Context): Context = + tree.lhs.putAttachment(Ignore, ()) // don't take LHS reference as a read + ctx + override def transformAssign(tree: Assign)(using Context): tree.type = + tree.lhs.removeAttachment(Ignore) + val sym = tree.lhs.symbol + if sym.exists then + refInfos.asss.addOne(sym) tree - override def transformDefDef(tree: tpd.DefDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) + override def prepareForMatch(tree: Match)(using Context): Context = + // allow case.pat against tree.selector (simple var pat only for now) + tree.selector match + case Ident(nm) => tree.cases.foreach(k => allowVariableBindings(List(nm), List(k.pat))) + case _ => + ctx + override def transformMatch(tree: Match)(using Context): tree.type = + if tree.isInstanceOf[InlineMatch] && tree.selector.isEmpty then + val sf = defn.Compiletime_summonFrom + resolveUsage(sf, sf.name, NoPrefix) tree - override def transformTypeDef(tree: tpd.TypeDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) + override def transformTypeTree(tree: TypeTree)(using Context): tree.type = + tree.tpe match + case AnnotatedType(_, annot) => transformAllDeep(annot.tree) + case tpt if !tree.isInferred && tpt.typeSymbol.exists => resolveUsage(tpt.typeSymbol, tpt.typeSymbol.name, NoPrefix) + case _ => tree + override def prepareForInlined(tree: Inlined)(using Context): Context = + refInfos.inlined.push(tree.call.srcPos) + ctx + override def transformInlined(tree: Inlined)(using Context): tree.type = + //transformAllDeep(tree.expansion) // traverse expansion with nonempty inlined stack to avoid registering defs + val _ = refInfos.inlined.pop() + if !tree.call.isEmpty && phaseMode.eq(PhaseMode.Aggregate) then + transformAllDeep(tree.call) + tree - // ---------- MiniPhase HELPERS ----------- + override def prepareForBind(tree: Bind)(using Context): Context = + refInfos.register(tree) + ctx - private def pushInBlockTemplatePackageDef(tree: tpd.Block | tpd.Template | tpd.PackageDef)(using Context): Context = - unusedDataApply { ud => - ud.pushScope(UnusedData.ScopeType.fromTree(tree)) - } + override def prepareForValDef(tree: ValDef)(using Context): Context = + if !tree.symbol.is(Deferred) && tree.rhs.symbol != defn.Predef_undefined then + refInfos.register(tree) + tree.tpt match + case RefinedTypeTree(_, refinements) => relax(tree.rhs, refinements) + case _ => ctx + override def transformValDef(tree: ValDef)(using Context): tree.type = + traverseAnnotations(tree.symbol) + if tree.name.startsWith("derived$") && tree.hasType then + def loop(t: Tree): Unit = t match + case Ident(name) => resolveUsage(t.tpe.typeSymbol, name, t.tpe.underlyingPrefix.skipPackageObject) + case Select(t, _) => loop(t) + case _ => + tree.getAttachment(OriginalTypeClass).foreach(loop) + if tree.symbol.isAllOf(DeferredGivenFlags) then + resolveUsage(defn.Compiletime_deferred, nme.NO_NAME, NoPrefix) + tree - private def popOutBlockTemplatePackageDef()(using Context): Context = - unusedDataApply { ud => - ud.popScope() - } + override def prepareForDefDef(tree: DefDef)(using Context): Context = + def trivial = tree.symbol.is(Deferred) || isUnconsuming(tree.rhs) + def nontrivial = tree.symbol.isConstructor || tree.symbol.isAnonymousFunction + def isDefault = tree.symbol.name.is(DefaultGetterName) + if !nontrivial && trivial || isDefault then + refInfos.skip.addOne(tree.symbol) + if tree.symbol.is(Inline) then + refInfos.inliners += 1 + else if !tree.symbol.is(Deferred) && tree.rhs.symbol != defn.Predef_undefined then + refInfos.register(tree) + tree.tpt match + case RefinedTypeTree(_, refinements) => relax(tree.rhs, refinements) + case _ => ctx + override def transformDefDef(tree: DefDef)(using Context): tree.type = + traverseAnnotations(tree.symbol) + if tree.symbol.is(Inline) then + refInfos.inliners -= 1 + if tree.symbol.isAllOf(DeferredGivenFlags) then + resolveUsage(defn.Compiletime_deferred, nme.NO_NAME, NoPrefix) + tree - /** - * This traverse is the **main** component of this phase - * - * It traverses the tree and gathers the data in the - * corresponding context property - */ - private def traverser = new TreeTraverser: - import tpd.* - import UnusedData.ScopeType + override def transformTypeDef(tree: TypeDef)(using Context): tree.type = + traverseAnnotations(tree.symbol) + if !tree.symbol.is(Param) then // type parameter to do? + refInfos.register(tree) + tree - /* Register every imports, definition and usage */ - override def traverse(tree: tpd.Tree)(using Context): Unit = - val newCtx = if tree.symbol.exists then ctx.withOwner(tree.symbol) else ctx - tree match - case imp: tpd.Import => - unusedDataApply(_.registerImport(imp)) - imp.selectors.filter(_.isGiven).map(_.bound).collect { - case untpd.TypedSplice(tree1) => tree1 - }.foreach(traverse(_)(using newCtx)) - traverseChildren(tree)(using newCtx) - case ident: Ident => - prepareForIdent(ident) - traverseChildren(tree)(using newCtx) - case sel: Select => - prepareForSelect(sel) - traverseChildren(tree)(using newCtx) - case tree: (tpd.Block | tpd.Template | tpd.PackageDef) => - //! DIFFERS FROM MINIPHASE - pushInBlockTemplatePackageDef(tree) - traverseChildren(tree)(using newCtx) - popOutBlockTemplatePackageDef() - case t: tpd.ValDef => - prepareForValDef(t) - traverseChildren(tree)(using newCtx) - transformValDef(t) - case t: tpd.DefDef => - prepareForDefDef(t) - traverseChildren(tree)(using newCtx) - transformDefDef(t) - case t: tpd.TypeDef => - prepareForTypeDef(t) - traverseChildren(tree)(using newCtx) - transformTypeDef(t) - case t: tpd.Bind => - prepareForBind(t) - traverseChildren(tree)(using newCtx) - case t:tpd.Assign => - prepareForAssign(t) - traverseChildren(tree) - case _: tpd.InferredTypeTree => - case t@tpd.RefinedTypeTree(tpt, refinements) => - //! DIFFERS FROM MINIPHASE - typeTraverser(unusedDataApply).traverse(t.tpe) - traverse(tpt)(using newCtx) - case t@tpd.TypeTree() => - //! DIFFERS FROM MINIPHASE - typeTraverser(unusedDataApply).traverse(t.tpe) - traverseChildren(tree)(using newCtx) - case _ => - //! DIFFERS FROM MINIPHASE - traverseChildren(tree)(using newCtx) - end traverse - end traverser - - /** This is a type traverser which catch some special Types not traversed by the term traverser above */ - private def typeTraverser(dt: (UnusedData => Any) => Unit)(using Context) = new TypeTraverser: - override def traverse(tp: Type): Unit = - if tp.typeSymbol.exists then dt(_.registerUsed(tp.typeSymbol, Some(tp.typeSymbol.name))) - tp match - case AnnotatedType(_, annot) => - dt(_.registerUsed(annot.symbol, None)) - traverseChildren(tp) - case _ => - traverseChildren(tp) + override def prepareForTemplate(tree: Template)(using Context): Context = + ctx.fresh.setProperty(resolvedKey, Resolved()) + + override def prepareForPackageDef(tree: PackageDef)(using Context): Context = + ctx.fresh.setProperty(resolvedKey, Resolved()) + + override def prepareForStats(trees: List[Tree])(using Context): Context = + ctx.fresh.setProperty(resolvedKey, Resolved()) + + override def transformOther(tree: Tree)(using Context): tree.type = + tree match + case imp: Import => + if phaseMode eq PhaseMode.Aggregate then + refInfos.register(imp) + transformAllDeep(imp.expr) + for selector <- imp.selectors do + if selector.isGiven then + selector.bound match + case untpd.TypedSplice(bound) => transformAllDeep(bound) + case _ => + case AppliedTypeTree(tpt, args) => + transformAllDeep(tpt) + args.foreach(transformAllDeep) + case RefinedTypeTree(tpt, refinements) => + transformAllDeep(tpt) + refinements.foreach(transformAllDeep) + case LambdaTypeTree(tparams, body) => + tparams.foreach(transformAllDeep) + transformAllDeep(body) + case SingletonTypeTree(ref) => + // selftype of object is not a usage + val moduleSelfRef = ctx.owner.is(Module) && ctx.owner == tree.symbol.companionModule.moduleClass + if !moduleSelfRef then + transformAllDeep(ref) + case TypeBoundsTree(lo, hi, alias) => + transformAllDeep(lo) + transformAllDeep(hi) + transformAllDeep(alias) + case tree: NamedArg => transformAllDeep(tree.arg) + case Annotated(arg, annot) => + transformAllDeep(arg) + transformAllDeep(annot) + case Quote(body, tags) => + transformAllDeep(body) + tags.foreach(transformAllDeep) + case Splice(expr) => + transformAllDeep(expr) + case QuotePattern(bindings, body, quotes) => + bindings.foreach(transformAllDeep) + transformAllDeep(body) + transformAllDeep(quotes) + case SplicePattern(body, typeargs, args) => + transformAllDeep(body) + typeargs.foreach(transformAllDeep) + args.foreach(transformAllDeep) + case MatchTypeTree(bound, selector, cases) => + transformAllDeep(bound) + transformAllDeep(selector) + cases.foreach(transformAllDeep) + case ByNameTypeTree(result) => + transformAllDeep(result) + //case _: InferredTypeTree => // do nothing + //case _: Export => // nothing to do + //case _ if tree.isType => + case _ => + tree - /** This traverse the annotations of the symbol */ private def traverseAnnotations(sym: Symbol)(using Context): Unit = - sym.denot.annotations.foreach(annot => traverser.traverse(annot.tree)) - - - /** Do the actual reporting given the result of the anaylsis */ - private def reportUnused(res: UnusedData.UnusedResult)(using Context): Unit = - res.warnings.toList.sortBy(_.pos.span.point)(using Ordering[Int]).foreach { s => - s match - case UnusedSymbol(t, _, WarnTypes.Imports) => - report.warning(UnusedSymbolMessage.imports, t) - case UnusedSymbol(t, _, WarnTypes.LocalDefs) => - report.warning(UnusedSymbolMessage.localDefs, t) - case UnusedSymbol(t, _, WarnTypes.ExplicitParams) => - report.warning(UnusedSymbolMessage.explicitParams, t) - case UnusedSymbol(t, _, WarnTypes.ImplicitParams) => - report.warning(UnusedSymbolMessage.implicitParams, t) - case UnusedSymbol(t, _, WarnTypes.PrivateMembers) => - report.warning(UnusedSymbolMessage.privateMembers, t) - case UnusedSymbol(t, _, WarnTypes.PatVars) => - report.warning(UnusedSymbolMessage.patVars, t) - case UnusedSymbol(t, _, WarnTypes.UnsetLocals) => - report.warning("unset local variable, consider using an immutable val instead", t) - case UnusedSymbol(t, _, WarnTypes.UnsetPrivates) => - report.warning("unset private variable, consider using an immutable val instead", t) - } + for annot <- sym.denot.annotations do + transformAllDeep(annot.tree) + + // if sym is not an enclosing element, record the reference + def refUsage(sym: Symbol)(using Context): Unit = + if !ctx.outersIterator.exists(cur => cur.owner eq sym) then + refInfos.refs.addOne(sym) + /** Look up a reference in enclosing contexts to determine whether it was introduced by a definition or import. + * The binding of highest precedence must then be correct. + * + * Unqualified locals and fully qualified globals are neither imported nor in scope; + * e.g., in `scala.Int`, `scala` is in scope for typer, but here we reverse-engineer the attribution. + * For Select, lint does not look up `.scala` (so top-level syms look like magic) but records `scala.Int`. + * For Ident, look-up finds the root import as usual. A competing import is OK because higher precedence. + */ + def resolveUsage(sym0: Symbol, name: Name, prefix: Type)(using Context): Unit = + import PrecedenceLevels.* + val sym = sym0.userSymbol + + def matchingSelector(info: ImportInfo): ImportSelector | Null = + val qtpe = info.site + def hasAltMember(nm: Name) = qtpe.member(nm).hasAltWith(_.symbol == sym) + def loop(sels: List[ImportSelector]): ImportSelector | Null = sels match + case sel :: sels => + val matches = + if sel.isWildcard then + // if name is different from sym.name, it must be a rename on import, not a wildcard selector + !name.exists(_.toTermName != sym.name.toTermName) + // the qualifier must have the target symbol as a member + && hasAltMember(sym.name) + && { + if sel.isGiven then // Further check that the symbol is a given or implicit and conforms to the bound + sym.isOneOf(GivenOrImplicit) + && (sel.bound.isEmpty || sym.info.finalResultType <:< sel.boundTpe) + && (prefix.eq(NoPrefix) || qtpe =:= prefix) + else + !sym.is(Given) // Normal wildcard, check that the symbol is not a given (but can be implicit) + } + else + // if there is an explicit name, it must match + !name.exists(_.toTermName != sel.rename) + && (prefix.eq(NoPrefix) || qtpe =:= prefix) + && (hasAltMember(sel.name) || hasAltMember(sel.name.toTypeName)) + if matches then sel else loop(sels) + case nil => null + loop(info.selectors) + + def checkMember(ctxsym: Symbol): Boolean = + ctxsym.isClass && sym.owner.isClass + && ctxsym.thisType.baseClasses.contains(sym.owner) + && ctxsym.thisType.member(sym.name).hasAltWith(d => d.containsSym(sym) && !name.exists(_ != d.name)) + + // Attempt to cache a result at the given context. Not all contexts bear a cache, including NoContext. + // If there is already any result for the name and prefix, do nothing. + def addCached(where: Context, result: Precedence): Unit = + if where.moreProperties ne null then + where.property(resolvedKey) match + case Some(resolved) => + resolved.record(sym, name, prefix, result) + case none => + + // Avoid spurious NoSymbol and also primary ctors which are never warned about. + // Selections C.this.toString should be already excluded, but backtopped here for eq, etc. + if !sym.exists || sym.isPrimaryConstructor || sym.isEffectiveRoot || defn.topClasses(sym.owner) then return + + // Find the innermost, highest precedence. Contexts have no nesting levels but assume correctness. + // If the sym is an enclosing definition (the owner of a context), it does not count toward usages. + val isLocal = sym.isLocalToBlock + var candidate: Context = NoContext + var cachePoint: Context = NoContext // last context with Resolved cache + var importer: ImportSelector | Null = null // non-null for import context + var precedence = NoPrecedence // of current resolution + var done = false + var cached = false + val ctxs = ctx.outersIterator + while !done && ctxs.hasNext do + val cur = ctxs.next() + if cur.owner eq sym then + addCached(cachePoint, Definition) + return // found enclosing definition + else if isLocal then + if cur.owner eq sym.owner then + done = true // for local def, just checking that it is not enclosing + else + val cachedPrecedence = + cur.property(resolvedKey) match + case Some(resolved) => + // conservative, cache must be nested below the result context + if precedence.isNone then + cachePoint = cur // no result yet, and future result could be cached here + resolved.hasRecord(sym, name, prefix) + case none => NoPrecedence + cached = !cachedPrecedence.isNone + if cached then + // if prefer cached precedence, then discard previous result + if precedence.weakerThan(cachedPrecedence) then + candidate = NoContext + importer = null + cachePoint = cur // actual cache context + precedence = cachedPrecedence // actual cached precedence + done = true + else if cur.isImportContext then + val sel = matchingSelector(cur.importInfo.nn) + if sel != null then + if cur.importInfo.nn.isRootImport then + if precedence.weakerThan(OtherUnit) then + precedence = OtherUnit + candidate = cur + importer = sel + done = true + else if sel.isWildcard then + if precedence.weakerThan(Wildcard) then + precedence = Wildcard + candidate = cur + importer = sel + else + if precedence.weakerThan(NamedImport) then + precedence = NamedImport + candidate = cur + importer = sel + else if checkMember(cur.owner) then + if sym.srcPos.sourcePos.source == ctx.source then + precedence = Definition + candidate = cur + importer = null // ignore import in same scope; we can't check nesting level + done = true + else if precedence.weakerThan(OtherUnit) then + precedence = OtherUnit + candidate = cur + end while + // record usage and possibly an import + refInfos.refs.addOne(sym) + if candidate != NoContext && candidate.isImportContext && importer != null then + refInfos.sels.put(importer, ()) + // possibly record that we have performed this look-up + // if no result was found, take it as Definition (local or rooted head of fully qualified path) + val adjusted = if precedence.isNone then Definition else precedence + if !cached && (cachePoint ne NoContext) then + addCached(cachePoint, adjusted) + if cachePoint ne ctx then + addCached(ctx, adjusted) // at this ctx, since cachePoint may be far up the outer chain + end resolveUsage end CheckUnused object CheckUnused: - val phaseNamePrefix: String = "checkUnused" - val description: String = "check for unused elements" enum PhaseMode: case Aggregate case Report - private enum WarnTypes: - case Imports - case LocalDefs - case ExplicitParams - case ImplicitParams - case PrivateMembers - case PatVars - case UnsetLocals - case UnsetPrivates - - /** - * The key used to retrieve the "unused entity" analysis metadata, - * from the compilation `Context` - */ - private val _key = Property.StickyKey[UnusedData] + val refInfosKey = Property.StickyKey[RefInfos] - val OriginalName = Property.StickyKey[Name] + val resolvedKey = Property.Key[Resolved] - class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper", _key) + inline def refInfos(using Context): RefInfos = ctx.property(refInfosKey).get - class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining", _key) + inline def resolved(using Context): Resolved = + ctx.property(resolvedKey) match + case Some(res) => res + case _ => throw new MatchError("no Resolved for context") - /** - * A stateful class gathering the infos on : - * - imports - * - definitions - * - usage - */ - private class UnusedData: - import collection.mutable.{Set => MutSet, Map => MutMap, Stack => MutStack, ListBuffer => MutList} - import UnusedData.* - - /** The current scope during the tree traversal */ - val currScopeType: MutStack[ScopeType] = MutStack(ScopeType.Other) - - var unusedAggregate: Option[UnusedResult] = None - - /* IMPORTS */ - private val impInScope = MutStack(MutList[ImportSelectorData]()) - /** - * We store the symbol along with their accessibility without import. - * Accessibility to their definition in outer context/scope - * - * See the `isAccessibleAsIdent` extension method below in the file - */ - private val usedInScope = MutStack(MutSet[(Symbol, Option[Name], Boolean)]()) - private val usedInPosition = MutMap.empty[Name, MutSet[Symbol]] - /* unused import collected during traversal */ - private val unusedImport = MutList.empty[ImportSelectorData] - - /* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */ - private val localDefInScope = MutList.empty[tpd.MemberDef] - private val privateDefInScope = MutList.empty[tpd.MemberDef] - private val explicitParamInScope = MutList.empty[tpd.MemberDef] - private val implicitParamInScope = MutList.empty[tpd.MemberDef] - private val patVarsInScope = MutList.empty[tpd.Bind] - - /** All variables sets*/ - private val setVars = MutSet[Symbol]() - - /** All used symbols */ - private val usedDef = MutSet[Symbol]() - /** Do not register as used */ - private val doNotRegister = MutSet[Symbol]() - - /** Trivial definitions, avoid registering params */ - private val trivialDefs = MutSet[Symbol]() - - private val paramsToSkip = MutSet[Symbol]() - - - def finishAggregation(using Context)(): Unit = - val unusedInThisStage = this.getUnused - this.unusedAggregate match { - case None => - this.unusedAggregate = Some(unusedInThisStage) - case Some(prevUnused) => - val intersection = unusedInThisStage.warnings.intersect(prevUnused.warnings) - this.unusedAggregate = Some(UnusedResult(intersection)) - } - - - /** - * Register a found (used) symbol along with its name - * - * The optional name will be used to target the right import - * as the same element can be imported with different renaming - */ - def registerUsed(sym: Symbol, name: Option[Name], includeForImport: Boolean = true, isDerived: Boolean = false)(using Context): Unit = - if sym.exists && !isConstructorOfSynth(sym) && !doNotRegister(sym) then - if sym.isConstructor then - registerUsed(sym.owner, None, includeForImport) // constructor are "implicitly" imported with the class - else - // If the symbol is accessible in this scope without an import, do not register it for unused import analysis - val includeForImport1 = - includeForImport - && (name.exists(_.toTermName != sym.name.toTermName) || !sym.isAccessibleAsIdent) - - def addIfExists(sym: Symbol): Unit = - if sym.exists then - usedDef += sym - if includeForImport1 then - usedInScope.top += ((sym, name, isDerived)) - addIfExists(sym) - addIfExists(sym.companionModule) - addIfExists(sym.companionClass) - if sym.sourcePos.exists then - for n <- name do - usedInPosition.getOrElseUpdate(n, MutSet.empty) += sym - - /** Register a symbol that should be ignored */ - def addIgnoredUsage(sym: Symbol)(using Context): Unit = - doNotRegister ++= sym.everySymbol - - /** Remove a symbol that shouldn't be ignored anymore */ - def removeIgnoredUsage(sym: Symbol)(using Context): Unit = - doNotRegister --= sym.everySymbol - - def addIgnoredParam(sym: Symbol)(using Context): Unit = - paramsToSkip += sym - - /** Register an import */ - def registerImport(imp: tpd.Import)(using Context): Unit = - if - !tpd.languageImport(imp.expr).nonEmpty - && !imp.isGeneratedByEnum - && !isTransparentAndInline(imp) - && currScopeType.top != ScopeType.ReplWrapper // #18383 Do not report top-level import's in the repl as unused - then - val qualTpe = imp.expr.tpe - - // Put wildcard imports at the end, because they have lower priority within one Import - val reorderdSelectors = - val (wildcardSels, nonWildcardSels) = imp.selectors.partition(_.isWildcard) - nonWildcardSels ::: wildcardSels - - val excludedMembers: mutable.Set[TermName] = mutable.Set.empty - - val newDataInScope = - for sel <- reorderdSelectors yield - val data = new ImportSelectorData(qualTpe, sel) - if shouldSelectorBeReported(imp, sel) || isImportExclusion(sel) || isImportIgnored(imp, sel) then - // Immediately mark the selector as used - data.markUsed() - if isImportExclusion(sel) then - excludedMembers += sel.name - if sel.isWildcard && excludedMembers.nonEmpty then - // mark excluded members for the wildcard import - data.markExcluded(excludedMembers.toSet) - data - impInScope.top.appendAll(newDataInScope) - end registerImport - - /** Register (or not) some `val` or `def` according to the context, scope and flags */ - def registerDef(memDef: tpd.MemberDef)(using Context): Unit = - if memDef.isValidMemberDef && !isDefIgnored(memDef) then - if memDef.isValidParam then - if memDef.symbol.isOneOf(GivenOrImplicit) then - if !paramsToSkip.contains(memDef.symbol) then - implicitParamInScope += memDef - else if !paramsToSkip.contains(memDef.symbol) then - explicitParamInScope += memDef - else if currScopeType.top == ScopeType.Local then - localDefInScope += memDef - else if memDef.shouldReportPrivateDef then - privateDefInScope += memDef - - /** Register pattern variable */ - def registerPatVar(patvar: tpd.Bind)(using Context): Unit = - if !patvar.symbol.isUnusedAnnot then - patVarsInScope += patvar - - /** enter a new scope */ - def pushScope(newScopeType: ScopeType): Unit = - // unused imports : - currScopeType.push(newScopeType) - impInScope.push(MutList()) - usedInScope.push(MutSet()) - - def registerSetVar(sym: Symbol): Unit = - setVars += sym - - /** - * leave the current scope and do : - * - * - If there are imports in this scope check for unused ones - */ - def popScope()(using Context): Unit = - currScopeType.pop() - val usedInfos = usedInScope.pop() - val selDatas = impInScope.pop() - - for usedInfo <- usedInfos do - val (sym, optName, isDerived) = usedInfo - val usedData = selDatas.find { selData => - sym.isInImport(selData, optName, isDerived) - } - usedData match - case Some(data) => - data.markUsed() - case None => - // Propagate the symbol one level up - if usedInScope.nonEmpty then - usedInScope.top += usedInfo - end for // each in `used` - - for selData <- selDatas do - if !selData.isUsed then - unusedImport += selData - end popScope - - /** - * Leave the scope and return a `List` of unused `ImportSelector`s - * - * The given `List` is sorted by line and then column of the position - */ + /** Attachment holding the name of an Ident as written by the user. */ + val OriginalName = Property.StickyKey[Name] - def getUnused(using Context): UnusedResult = - popScope() + /** Suppress warning in a tree, such as a patvar name allowed by special convention. */ + val NoWarn = Property.StickyKey[Unit] - def isUsedInPosition(name: Name, span: Span): Boolean = - usedInPosition.get(name) match - case Some(syms) => syms.exists(sym => span.contains(sym.span)) - case None => false + /** Ignore reference. */ + val Ignore = Property.StickyKey[Unit] - val sortedImp = - if ctx.settings.WunusedHas.imports || ctx.settings.WunusedHas.strictNoImplicitWarn then - unusedImport.toList - .map(d => UnusedSymbol(d.selector.srcPos, d.selector.name, WarnTypes.Imports)) - else - Nil - // Partition to extract unset local variables from usedLocalDefs - val (usedLocalDefs, unusedLocalDefs) = - if ctx.settings.WunusedHas.locals then - localDefInScope.toList.partition(d => d.symbol.usedDefContains) - else - (Nil, Nil) - val sortedLocalDefs = - unusedLocalDefs - .filterNot(d => isUsedInPosition(d.symbol.name, d.span)) - .filterNot(d => containsSyntheticSuffix(d.symbol)) - .map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.LocalDefs)) - val unsetLocalDefs = usedLocalDefs.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetLocals)).toList - - val sortedExplicitParams = - if ctx.settings.WunusedHas.explicits then - explicitParamInScope.toList - .filterNot(d => d.symbol.usedDefContains) - .filterNot(d => isUsedInPosition(d.symbol.name, d.span)) - .filterNot(d => containsSyntheticSuffix(d.symbol)) - .map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ExplicitParams)) - else - Nil - val sortedImplicitParams = - if ctx.settings.WunusedHas.implicits then - implicitParamInScope.toList - .filterNot(d => d.symbol.usedDefContains) - .filterNot(d => containsSyntheticSuffix(d.symbol)) - .map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ImplicitParams)) - else - Nil - // Partition to extract unset private variables from usedPrivates - val (usedPrivates, unusedPrivates) = - if ctx.settings.WunusedHas.privates then - privateDefInScope.toList.partition(d => d.symbol.usedDefContains) - else - (Nil, Nil) - val sortedPrivateDefs = unusedPrivates.filterNot(d => containsSyntheticSuffix(d.symbol)).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PrivateMembers)) - val unsetPrivateDefs = usedPrivates.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetPrivates)) - val sortedPatVars = - if ctx.settings.WunusedHas.patvars then - patVarsInScope.toList - .filterNot(d => d.symbol.usedDefContains) - .filterNot(d => containsSyntheticSuffix(d.symbol)) - .filterNot(d => isUsedInPosition(d.symbol.name, d.span)) - .map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PatVars)) - else - Nil - val warnings = - sortedImp ::: - sortedLocalDefs ::: - sortedExplicitParams ::: - sortedImplicitParams ::: - sortedPrivateDefs ::: - sortedPatVars ::: - unsetLocalDefs ::: - unsetPrivateDefs - UnusedResult(warnings.toSet) - end getUnused - //============================ HELPERS ==================================== - - - /** - * Checks if import selects a def that is transparent and inline - */ - private def isTransparentAndInline(imp: tpd.Import)(using Context): Boolean = - imp.selectors.exists { sel => - val qual = imp.expr - val importedMembers = qual.tpe.member(sel.name).alternatives.map(_.symbol) - importedMembers.exists(s => s.is(Transparent) && s.is(Inline)) - } - - /** - * Heuristic to detect synthetic suffixes in names of symbols - */ - private def containsSyntheticSuffix(symbol: Symbol)(using Context): Boolean = - symbol.name.mangledString.contains("$") - - /** - * Is the constructor of synthetic package object - * Should be ignored as it is always imported/used in package - * Trigger false negative on used import - * - * Without this check example: - * - * --- WITH PACKAGE : WRONG --- - * {{{ - * package a: - * val x: Int = 0 - * package b: - * import a.* // no warning - * }}} - * --- WITH OBJECT : OK --- - * {{{ - * object a: - * val x: Int = 0 - * object b: - * import a.* // unused warning - * }}} - */ - private def isConstructorOfSynth(sym: Symbol)(using Context): Boolean = - sym.exists && sym.isConstructor && sym.owner.isPackageObject && sym.owner.is(Synthetic) + class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper") - /** - * This is used to avoid reporting the parameters of the synthetic main method - * generated by `@main` - */ - private def isSyntheticMainParam(sym: Symbol)(using Context): Boolean = - sym.exists && ctx.platform.isMainMethod(sym.owner) && sym.owner.is(Synthetic) + class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining") + + class RefInfos: + val defs = mutable.Set.empty[(Symbol, SrcPos)] // definitions + val pats = mutable.Set.empty[(Symbol, SrcPos)] // pattern variables + val refs = mutable.Set.empty[Symbol] // references + val asss = mutable.Set.empty[Symbol] // targets of assignment + val skip = mutable.Set.empty[Symbol] // methods to skip (don't warn about their params) + val nowarn = mutable.Set.empty[Symbol] // marked @nowarn + val imps = new IdentityHashMap[Import, Unit] // imports + val sels = new IdentityHashMap[ImportSelector, Unit] // matched selectors + def register(tree: Tree)(using Context): Unit = if inlined.isEmpty then + tree match + case imp: Import => + if inliners == 0 + && languageImport(imp.expr).isEmpty + && !imp.isGeneratedByEnum + && !ctx.outer.owner.name.isReplWrapperName + then + imps.put(imp, ()) + case tree: Bind => + if !tree.name.isInstanceOf[DerivedName] && !tree.name.is(WildcardParamName) then + if tree.hasAttachment(NoWarn) then + nowarn.addOne(tree.symbol) + pats.addOne((tree.symbol, tree.namePos)) + case tree: NamedDefTree => + if tree.hasAttachment(PatternVar) then + if !tree.name.isInstanceOf[DerivedName] then + pats.addOne((tree.symbol, tree.namePos)) + else if (tree.symbol ne NoSymbol) + && !tree.name.isWildcard + && !tree.symbol.is(ModuleVal) // track only the ModuleClass using the object symbol, with correct namePos + then + if tree.hasAttachment(NoWarn) then + nowarn.addOne(tree.symbol) + defs.addOne((tree.symbol.userSymbol, tree.namePos)) + case _ => + if tree.symbol ne NoSymbol then + defs.addOne((tree.symbol, tree.srcPos)) // TODO is this a code path + + val inlined = Stack.empty[SrcPos] // enclosing call.srcPos of inlined code (expansions) + var inliners = 0 // depth of inline def (not inlined yet) + end RefInfos + + // Symbols already resolved in the given Context (with name and prefix of lookup). + class Resolved: + import PrecedenceLevels.* + private val seen = mutable.Map.empty[Symbol, List[(Name, Type, Precedence)]].withDefaultValue(Nil) + // if a result has been recorded, return it; otherwise, NoPrecedence. + def hasRecord(symbol: Symbol, name: Name, prefix: Type)(using Context): Precedence = + seen(symbol).find((n, p, _) => n == name && p =:= prefix) match + case Some((_, _, r)) => r + case none => NoPrecedence + // "record" the look-up result, if there is not already a result for the name and prefix. + def record(symbol: Symbol, name: Name, prefix: Type, result: Precedence)(using Context): Unit = + require(NoPrecedence.weakerThan(result)) + seen.updateWith(symbol): + case svs @ Some(vs) => + if vs.exists((n, p, _) => n == name && p =:= prefix) then svs + else Some((name, prefix, result) :: vs) + case none => Some((name, prefix, result) :: Nil) + + // Names are resolved by definitions and imports, which have four precedence levels: + object PrecedenceLevels: + opaque type Precedence = Int + inline def NoPrecedence: Precedence = 5 + inline def OtherUnit: Precedence = 4 // root import or def from another compilation unit via enclosing package + inline def Wildcard: Precedence = 3 // wildcard import + inline def NamedImport: Precedence = 2 // specific import + inline def Definition: Precedence = 1 // def from this compilation unit + extension (p: Precedence) + inline def weakerThan(q: Precedence): Boolean = p > q + inline def isNone: Boolean = p == NoPrecedence + + def reportUnused()(using Context): Unit = + for (msg, pos, origin) <- warnings do + if origin.isEmpty then report.warning(msg, pos) + else report.warning(msg, pos, origin) + msg.actions.headOption.foreach(Rewrites.applyAction) + + type MessageInfo = (UnusedSymbol, SrcPos, String) // string is origin or empty + + def warnings(using Context): Array[MessageInfo] = + val actionable = ctx.settings.rewrite.value.nonEmpty + val warnings = ArrayBuilder.make[MessageInfo] + def warnAt(pos: SrcPos)(msg: UnusedSymbol, origin: String = ""): Unit = warnings.addOne((msg, pos, origin)) + val infos = refInfos + + def checkUnassigned(sym: Symbol, pos: SrcPos) = + if sym.isLocalToBlock then + if ctx.settings.WunusedHas.locals && sym.is(Mutable) && !infos.asss(sym) then + warnAt(pos)(UnusedSymbol.unsetLocals) + else if ctx.settings.WunusedHas.privates && sym.isAllOf(Private | Mutable) && !infos.asss(sym) then + warnAt(pos)(UnusedSymbol.unsetPrivates) + + def checkPrivate(sym: Symbol, pos: SrcPos) = + if ctx.settings.WunusedHas.privates + && !sym.isPrimaryConstructor + && !sym.isOneOf(SelfName | Synthetic | CaseAccessor) + && !sym.name.is(BodyRetainerName) + && !sym.isSerializationSupport + && !(sym.is(Mutable) && sym.isSetter && sym.owner.is(Trait)) // tracks sym.underlyingSymbol sibling getter + && !infos.nowarn(sym) + then + warnAt(pos)(UnusedSymbol.privateMembers) + + def checkParam(sym: Symbol, pos: SrcPos) = + val m = sym.owner + def allowed = + val dd = defn + m.isDeprecated + || m.is(Synthetic) && !m.isAnonymousFunction + || m.hasAnnotation(defn.UnusedAnnot) // param of unused method + || sym.info.isSingleton + || m.isConstructor && m.owner.thisType.baseClasses.contains(defn.AnnotationClass) + def checkExplicit(): Unit = + // A class param is unused if its param accessor is unused. + // (The class param is not assigned to a field until constructors.) + // A local param accessor warns as a param; a private accessor as a private member. + // Avoid warning for case class elements because they are aliased via unapply. + if m.isPrimaryConstructor then + val alias = m.owner.info.member(sym.name) + if alias.exists then + val aliasSym = alias.symbol + if aliasSym.isAllOf(PrivateParamAccessor, butNot = CaseAccessor) && !infos.refs(alias.symbol) then + if aliasSym.is(Local) then + if ctx.settings.WunusedHas.explicits then + warnAt(pos)(UnusedSymbol.explicitParams) + else + if ctx.settings.WunusedHas.privates then + warnAt(pos)(UnusedSymbol.privateMembers) + else if ctx.settings.WunusedHas.explicits + && !sym.is(Synthetic) // param to setter is unused bc there is no field yet + && !(sym.owner.is(ExtensionMethod) && { + m.paramSymss.dropWhile(_.exists(_.isTypeParam)) match + case (h :: Nil) :: Nil => h == sym // param is the extended receiver + case _ => false + }) + && !sym.name.isInstanceOf[DerivedName] + && !ctx.platform.isMainMethod(m) + then + warnAt(pos)(UnusedSymbol.explicitParams) + end checkExplicit + // begin + if !infos.skip(m) + && !m.nextOverriddenSymbol.exists + && !allowed + then + checkExplicit() + end checkParam + + def checkImplicit(sym: Symbol, pos: SrcPos) = + val m = sym.owner + def allowed = + val dd = defn + m.isDeprecated + || m.is(Synthetic) + || sym.name.is(ContextFunctionParamName) // a ubiquitous parameter + || sym.name.is(ContextBoundParamName) && sym.info.typeSymbol.isMarkerTrait // a ubiquitous parameter + || m.hasAnnotation(dd.UnusedAnnot) // param of unused method + || sym.info.typeSymbol.match // more ubiquity + case dd.DummyImplicitClass | dd.SubTypeClass | dd.SameTypeClass => true + case _ => false + || sym.info.isSingleton // DSL friendly + || sym.isCanEqual + || sym.info.typeSymbol.hasAnnotation(dd.LanguageFeatureMetaAnnot) + || sym.info.isInstanceOf[RefinedType] // can't be expressed as a context bound + if ctx.settings.WunusedHas.implicits + && !infos.skip(m) + && !allowed + then + if m.isPrimaryConstructor then + val alias = m.owner.info.member(sym.name) + if alias.exists then + val aliasSym = alias.symbol + val checking = + aliasSym.isAllOf(PrivateParamAccessor, butNot = CaseAccessor) + || aliasSym.isAllOf(Protected | ParamAccessor, butNot = CaseAccessor) && m.owner.is(Given) + if checking && !infos.refs(alias.symbol) then + warnAt(pos)(UnusedSymbol.implicitParams) + else + warnAt(pos)(UnusedSymbol.implicitParams) - /** - * This is used to ignore exclusion imports (i.e. import `qual`.{`member` => _}) + def checkLocal(sym: Symbol, pos: SrcPos) = + if ctx.settings.WunusedHas.locals + && !sym.is(InlineProxy) + && !sym.isCanEqual + then + warnAt(pos)(UnusedSymbol.localDefs) + + def checkPatvars() = + // convert the one non-synthetic span so all are comparable; filter NoSpan below + def uniformPos(sym: Symbol, pos: SrcPos): SrcPos = + if pos.span.isSynthetic then pos else pos.sourcePos.withSpan(pos.span.toSynthetic) + // patvars in for comprehensions share the pos of where the name was introduced + val byPos = infos.pats.groupMap(uniformPos(_, _))((sym, pos) => sym) + for (pos, syms) <- byPos if pos.span.exists && !syms.exists(_.hasAnnotation(defn.UnusedAnnot)) do + if !syms.exists(infos.refs(_)) then + if !syms.exists(v => !v.isLocal && !v.is(Private) || infos.nowarn(v)) then + warnAt(pos)(UnusedSymbol.patVars) + else if syms.exists(_.is(Mutable)) then // check unassigned var + val sym = // recover the original + if syms.size == 1 then syms.head + else infos.pats.find((s, p) => syms.contains(s) && !p.span.isSynthetic).map(_._1).getOrElse(syms.head) + if sym.is(Mutable) && !infos.asss(sym) then + if sym.isLocalToBlock then + warnAt(pos)(UnusedSymbol.unsetLocals) + else if sym.is(Private) then + warnAt(pos)(UnusedSymbol.unsetPrivates) + + def checkImports() = + // TODO check for unused masking import + import scala.jdk.CollectionConverters.given + import Rewrites.ActionPatch + type ImpSel = (Import, ImportSelector) + def isUsable(imp: Import, sel: ImportSelector): Boolean = + sel.isImportExclusion || infos.sels.containsKey(sel) || imp.isLoose(sel) + def warnImport(warnable: ImpSel, actions: List[CodeAction] = Nil): Unit = + val (imp, sel) = warnable + val msg = UnusedSymbol.imports(actions) + // example collection.mutable.{Map as MutMap} + val origin = cpy.Import(imp)(imp.expr, List(sel)).show(using ctx.withoutColors).stripPrefix("import ") + warnAt(sel.srcPos)(msg, origin) + + if !actionable then + for imp <- infos.imps.keySet.nn.asScala; sel <- imp.selectors if !isUsable(imp, sel) do + warnImport(imp -> sel) + else + // If the rest of the line is blank, include it in the final edit position. (Delete trailing whitespace.) + // If for deletion, and the prefix of the line is also blank, then include that, too. (Del blank line.) + def editPosAt(srcPos: SrcPos, forDeletion: Boolean): SrcPos = + val start = srcPos.span.start + val end = srcPos.span.end + val content = srcPos.sourcePos.source.content() + val prev = content.lastIndexWhere(c => !isWhitespace(c), end = start - 1) + val emptyLeft = prev < 0 || isLineBreakChar(content(prev)) + val next = content.indexWhere(c => !isWhitespace(c), from = end) + val emptyRight = next < 0 || isLineBreakChar(content(next)) + val deleteLine = emptyLeft && emptyRight && forDeletion + val bump = if (deleteLine) 1 else 0 // todo improve to include offset of next line, endline + 1 + val p0 = srcPos.span + val p1 = if (next >= 0 && emptyRight) p0.withEnd(next + bump) else p0 + val p2 = if (deleteLine) p1.withStart(prev + 1) else p1 + srcPos.sourcePos.withSpan(p2) + def actionsOf(actions: (SrcPos, String)*): List[CodeAction] = + val patches = actions.map((srcPos, replacement) => ActionPatch(srcPos.sourcePos, replacement)).toList + List(CodeAction(title = "unused import", description = Some("remove import"), patches)) + def replace(editPos: SrcPos)(replacement: String): List[CodeAction] = actionsOf(editPos -> replacement) + def deletion(editPos: SrcPos): List[CodeAction] = actionsOf(editPos -> "") + def textFor(impsel: ImpSel): String = + val (imp, sel) = impsel + val content = imp.srcPos.sourcePos.source.content() + def textAt(pos: SrcPos) = String(content.slice(pos.span.start, pos.span.end)) + val qual = textAt(imp.expr.srcPos) // keep original + val selector = textAt(sel.srcPos) // keep original + s"$qual.$selector" // don't succumb to vagaries of show + // begin actionable + val sortedImps = infos.imps.keySet.nn.asScala + .filter(_.srcPos.span.exists) // extra caution + .toArray + .sortBy(_.srcPos.span.point) // sorted by pos, not sort in place + var index = 0 + while index < sortedImps.length do + val nextImport = sortedImps.indexSatisfying(from = index + 1)(_.isPrimaryClause) // next import statement + if sortedImps.indexSatisfying(from = index, until = nextImport): imp => + imp.selectors.exists(!isUsable(imp, _)) // check if any selector in statement was unused + < nextImport then + // if no usable selectors in the import statement, delete it entirely. + // if there is exactly one usable selector, then replace with just that selector (i.e., format it). + // else for each clause, delete it or format one selector or delete unused selectors. + // To delete a comma separated item, delete start-to-start, but for last item delete a preceding comma. + // Reminder that first clause span includes the keyword, so delete point-to-start instead. + val existing = sortedImps.slice(index, nextImport) + val (keeping, deleting) = existing.iterator.flatMap(imp => imp.selectors.map(imp -> _)).toList + .partition(isUsable(_, _)) + if keeping.isEmpty then + val editPos = existing.head.srcPos.sourcePos.withSpan: + Span(start = existing.head.srcPos.span.start, end = existing.last.srcPos.span.end) + deleting.init.foreach(warnImport(_)) + warnImport(deleting.last, deletion(editPosAt(editPos, forDeletion = true))) + else if keeping.lengthIs == 1 then + val editPos = existing.head.srcPos.sourcePos.withSpan: + Span(start = existing.head.srcPos.span.start, end = existing.last.srcPos.span.end) + deleting.init.foreach(warnImport(_)) + val text = s"import ${textFor(keeping.head)}" + warnImport(deleting.last, replace(editPosAt(editPos, forDeletion = false))(text)) + else + val lostClauses = existing.iterator.filter(imp => !keeping.exists((i, _) => imp eq i)).toList + for imp <- lostClauses do + val actions = + if imp == existing.last then + val content = imp.srcPos.sourcePos.source.content() + val prev = existing.lastIndexWhere(i0 => keeping.exists((i, _) => i == i0)) + val comma = content.indexOf(',', from = existing(prev).srcPos.span.end) + val commaPos = imp.srcPos.sourcePos.withSpan: + Span(start = comma, end = existing(prev + 1).srcPos.span.start) + val srcPos = imp.srcPos + val editPos = srcPos.sourcePos.withSpan: // exclude keyword + srcPos.span.withStart(srcPos.span.point) + actionsOf(commaPos -> "", editPos -> "") + else + val impIndex = existing.indexOf(imp) + val editPos = imp.srcPos.sourcePos.withSpan: // exclude keyword + Span(start = imp.srcPos.span.point, end = existing(impIndex + 1).srcPos.span.start) + deletion(editPos) + imp.selectors.init.foreach(sel => warnImport(imp -> sel)) + warnImport(imp -> imp.selectors.last, actions) + val singletons = existing.iterator.filter(imp => keeping.count((i, _) => imp eq i) == 1).toList + var seen = List.empty[Import] + for impsel <- deleting do + val (imp, sel) = impsel + if singletons.contains(imp) then + if seen.contains(imp) then + warnImport(impsel) + else + seen ::= imp + val editPos = imp.srcPos.sourcePos.withSpan: + Span(start = imp.srcPos.span.point, end = imp.srcPos.span.end) // exclude keyword + val text = textFor(keeping.find((i, _) => imp eq i).get) + warnImport(impsel, replace(editPosAt(editPos, forDeletion = false))(text)) + else if !lostClauses.contains(imp) then + val actions = + if sel == imp.selectors.last then + val content = sel.srcPos.sourcePos.source.content() + val prev = imp.selectors.lastIndexWhere(s0 => keeping.exists((_, s) => s == s0)) + val comma = content.indexOf(',', from = imp.selectors(prev).srcPos.span.end) + val commaPos = sel.srcPos.sourcePos.withSpan: + Span(start = comma, end = imp.selectors(prev + 1).srcPos.span.start) + val editPos = sel.srcPos + actionsOf(commaPos -> "", editPos -> "") + else + val selIndex = imp.selectors.indexOf(sel) + val editPos = sel.srcPos.sourcePos.withSpan: + sel.srcPos.span.withEnd(imp.selectors(selIndex + 1).srcPos.span.start) + deletion(editPos) + warnImport(impsel, actions) + end if + index = nextImport + end while + + // begin + for (sym, pos) <- infos.defs.iterator if !sym.hasAnnotation(defn.UnusedAnnot) do + if infos.refs(sym) then + checkUnassigned(sym, pos) + else if sym.isEffectivelyPrivate then + checkPrivate(sym, pos) + else if sym.is(Param, butNot = Given | Implicit) then + checkParam(sym, pos) + else if sym.is(Param) then // Given | Implicit + checkImplicit(sym, pos) + else if sym.isLocalToBlock then + checkLocal(sym, pos) + + if ctx.settings.WunusedHas.patvars then + checkPatvars() + + if ctx.settings.WunusedHas.imports || ctx.settings.WunusedHas.strictNoImplicitWarn then + checkImports() + + def sortOrder(msgInfo: MessageInfo): Int = + val srcPos = msgInfo._2 + if srcPos.span.exists then srcPos.span.point else 0 + + warnings.result().sortBy(sortOrder) + end warnings + + // Specific exclusions + def ignoreTree(tree: Tree): Boolean = + tree.hasAttachment(ForArtifact) || tree.hasAttachment(Ignore) + + // The RHS of a def is too trivial to warn about unused params, e.g. def f(x: Int) = ??? + def isUnconsuming(rhs: Tree)(using Context): Boolean = + rhs.symbol == defn.Predef_undefined + || rhs.tpe =:= defn.NothingType // compiletime.error + || rhs.isInstanceOf[Literal] // 42 + || rhs.tpe.match + case ConstantType(_) => true + case tp: TermRef => tp.underlying.classSymbol.is(Module) // Scala 2 SingleType + case _ => false + //|| isPurePath(rhs) // a bit strong + || rhs.match + case Block((dd @ DefDef(anonfun, paramss, _, _)) :: Nil, Closure(Nil, Ident(nm), _)) => + anonfun == nm // isAnonymousFunctionName(anonfun) + && paramss.match + case (ValDef(contextual, _, _) :: Nil) :: Nil => + contextual.is(ContextFunctionParamName) + && isUnconsuming(dd.rhs) // rhs was wrapped in a context function + case _ => false + case Block(Nil, Literal(u)) => u.tpe =:= defn.UnitType // def f(x: X) = {} + case This(_) => true + case Ident(_) => rhs.symbol.is(ParamAccessor) + case Typed(rhs, _) => isUnconsuming(rhs) + case _ => false + + def allowVariableBindings(ok: List[Name], args: List[Tree]): Unit = + ok.zip(args).foreach: + case (param, arg @ Bind(p, _)) if param == p => arg.withAttachment(NoWarn, ()) + case _ => + + // NoWarn Binds if the name matches a "canonical" name, e.g. case element name + val nowarner = new TreeTraverser: + def traverse(tree: Tree)(using Context) = tree match + case UnApply(fun, _, args) => + val unapplied = tree.tpe.finalResultType.dealias.typeSymbol + if unapplied.is(CaseClass) then + allowVariableBindings(unapplied.primaryConstructor.info.firstParamNames, args) + else if fun.symbol == defn.PairClass_unapply then + val ok = fun.symbol.info match + case PolyType(tycon, MethodTpe(_, _, AppliedType(_, tprefs))) => + tprefs.collect: + case ref: TypeParamRef => termName(ref.binder.paramNames(ref.paramNum).toString.toLowerCase.nn) + case _ => Nil + allowVariableBindings(ok, args) + else if fun.symbol == defn.TypeTest_unapply then + () // just recurse into args + else + if unapplied.exists && unapplied.owner == defn.Quotes_reflectModule then + // cheapy search for parameter names via java reflection of Trees + // in lieu of drilling into requiredClass("scala.quoted.runtime.impl.QuotesImpl") + // ...member("reflect")...member(unapplied.name.toTypeName) + // with aliases into requiredModule("dotty.tools.dotc.ast.tpd") + val implName = s"dotty.tools.dotc.ast.Trees$$${unapplied.name}" + try + import scala.language.unsafeNulls + val clz = Class.forName(implName) // TODO improve to use class path or reflect + val ok = clz.getConstructors.head.getParameters.map(p => termName(p.getName)).toList.init + allowVariableBindings(ok, args) + catch case _: ClassNotFoundException => () + args.foreach(traverse) + case tree => traverseChildren(tree) + + // NoWarn members in tree that correspond to refinements; currently uses only names. + def relax(tree: Tree, refinements: List[Tree])(using Context): Unit = + val names = refinements.collect { case named: NamedDefTree => named.name }.toSet + val relaxer = new TreeTraverser: + def traverse(tree: Tree)(using Context) = + tree match + case tree: NamedDefTree if names(tree.name) => tree.withAttachment(NoWarn, ()) + case _ => + traverseChildren(tree) + relaxer.traverse(tree) + + extension (nm: Name) + inline def exists(p: Name => Boolean): Boolean = nm.ne(nme.NO_NAME) && p(nm) + inline def isWildcard: Boolean = nm == nme.WILDCARD || nm.is(WildcardParamName) + + extension (tp: Type) + def importPrefix(using Context): Type = tp match + case tp: NamedType => tp.prefix + case tp: ClassInfo => tp.prefix + case tp: TypeProxy => tp.superType.normalizedPrefix + case _ => NoType + def underlyingPrefix(using Context): Type = tp match + case tp: NamedType => tp.prefix + case tp: ClassInfo => tp.prefix + case tp: TypeProxy => tp.underlying.underlyingPrefix + case _ => NoType + def skipPackageObject(using Context): Type = + if tp.typeSymbol.isPackageObject then tp.underlyingPrefix else tp + def underlying(using Context): Type = tp match + case tp: TypeProxy => tp.underlying + case _ => tp + + private val serializationNames: Set[TermName] = + Set("readResolve", "readObject", "readObjectNoData", "writeObject", "writeReplace").map(termName(_)) + + extension (sym: Symbol) + def isSerializationSupport(using Context): Boolean = + sym.is(Method) && serializationNames(sym.name.toTermName) && sym.owner.isClass + && sym.owner.derivesFrom(defn.JavaSerializableClass) + def isCanEqual(using Context): Boolean = + sym.isOneOf(GivenOrImplicit) && sym.info.finalResultType.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) + def isMarkerTrait(using Context): Boolean = + sym.isClass && sym.info.allMembers.forall: d => + val m = d.symbol + !m.isTerm || m.isSelfSym || m.is(Method) && (m.owner == defn.AnyClass || m.owner == defn.ObjectClass) + def isEffectivelyPrivate(using Context): Boolean = + sym.is(Private, butNot = ParamAccessor) + || sym.owner.isAnonymousClass && !sym.nextOverriddenSymbol.exists + // pick the symbol the user wrote for purposes of tracking + inline def userSymbol(using Context): Symbol= + if sym.denot.is(ModuleClass) then sym.denot.companionModule else sym + + extension (sel: ImportSelector) + def boundTpe: Type = sel.bound match + case untpd.TypedSplice(tree) => tree.tpe + case _ => NoType + /** This is used to ignore exclusion imports of the form import `qual.member as _` + * because `sel.isUnimport` is too broad for old style `import concurrent._`. */ - private def isImportExclusion(sel: ImportSelector): Boolean = sel.renamed match - case untpd.Ident(name) => name == StdNames.nme.WILDCARD + def isImportExclusion: Boolean = sel.renamed match + case untpd.Ident(nme.WILDCARD) => true case _ => false - /** - * If -Wunused:strict-no-implicit-warn import and this import selector could potentially import implicit. - * return true - */ - private def shouldSelectorBeReported(imp: tpd.Import, sel: ImportSelector)(using Context): Boolean = - ctx.settings.WunusedHas.strictNoImplicitWarn && ( - sel.isWildcard || - imp.expr.tpe.member(sel.name.toTermName).alternatives.exists(_.symbol.isOneOf(GivenOrImplicit)) || - imp.expr.tpe.member(sel.name.toTypeName).alternatives.exists(_.symbol.isOneOf(GivenOrImplicit)) - ) - - /** - * Ignore CanEqual imports - */ - private def isImportIgnored(imp: tpd.Import, sel: ImportSelector)(using Context): Boolean = - (sel.isWildcard && sel.isGiven && imp.expr.tpe.allMembers.exists(p => p.symbol.typeRef.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) && p.symbol.isOneOf(GivenOrImplicit))) || - (imp.expr.tpe.member(sel.name.toTermName).alternatives - .exists(p => p.symbol.isOneOf(GivenOrImplicit) && p.symbol.typeRef.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)))) + extension (imp: Import) + /** Is it the first import clause in a statement? `a.x` in `import a.x, b.{y, z}` */ + def isPrimaryClause(using Context): Boolean = + imp.srcPos.span.pointDelta > 0 // primary clause starts at `import` keyword with point at clause proper - /** - * Ignore definitions of CanEqual given - */ - private def isDefIgnored(memDef: tpd.MemberDef)(using Context): Boolean = - memDef.symbol.isOneOf(GivenOrImplicit) && memDef.symbol.typeRef.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) - - extension (tree: ImportSelector) - def boundTpe: Type = tree.bound match { - case untpd.TypedSplice(tree1) => tree1.tpe - case _ => NoType - } - - extension (sym: Symbol) - /** is accessible without import in current context */ - private def isAccessibleAsIdent(using Context): Boolean = - ctx.outersIterator.exists{ c => - c.owner == sym.owner - || sym.owner.isClass && c.owner.isClass - && c.owner.thisType.baseClasses.contains(sym.owner) - && c.owner.thisType.member(sym.name).alternatives.contains(sym) - } - - /** Given an import and accessibility, return selector that matches import<->symbol */ - private def isInImport(selData: ImportSelectorData, altName: Option[Name], isDerived: Boolean)(using Context): Boolean = - assert(sym.exists, s"Symbol $sym does not exist") - - val selector = selData.selector - - if !selector.isWildcard then - if altName.exists(explicitName => selector.rename != explicitName.toTermName) then - // if there is an explicit name, it must match - false - else - if isDerived then - // See i15503i.scala, grep for "package foo.test.i17156" - selData.allSymbolsDealiasedForNamed.contains(dealias(sym)) - else - selData.allSymbolsForNamed.contains(sym) - else - // Wildcard - if selData.excludedMembers.contains(altName.getOrElse(sym.name).toTermName) then - // Wildcard with exclusions that match the symbol - false - else if !selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) then - // The qualifier does not have the target symbol as a member - false - else - if selector.isGiven then - // Further check that the symbol is a given or implicit and conforms to the bound - sym.isOneOf(Given | Implicit) - && (selector.bound.isEmpty || sym.info.finalResultType <:< selector.boundTpe) - else - // Normal wildcard, check that the symbol is not a given (but can be implicit) - !sym.is(Given) - end if - end isInImport - - /** Annotated with @unused */ - private def isUnusedAnnot(using Context): Boolean = - sym.annotations.exists(a => a.symbol == ctx.definitions.UnusedAnnot) - - private def shouldNotReportParamOwner(using Context): Boolean = - if sym.exists then - val owner = sym.owner - trivialDefs(owner) || // is a trivial def - owner.isPrimaryConstructor || - owner.annotations.exists ( // @depreacated - _.symbol == ctx.definitions.DeprecatedAnnot - ) || - owner.isAllOf(Synthetic | PrivateLocal) || - owner.is(Accessor) || - owner.isOverriden - else - false - - private def usedDefContains(using Context): Boolean = - sym.everySymbol.exists(usedDef.apply) - - private def everySymbol(using Context): List[Symbol] = - List(sym, sym.companionClass, sym.companionModule, sym.moduleClass).filter(_.exists) - - /** A function is overriden. Either has `override flags` or parent has a matching member (type and name) */ - private def isOverriden(using Context): Boolean = - sym.is(Flags.Override) || (sym.exists && sym.owner.thisType.parents.exists(p => sym.matchingMember(p).exists)) - - end extension - - extension (defdef: tpd.DefDef) - // so trivial that it never consumes params - private def isTrivial(using Context): Boolean = - val rhs = defdef.rhs - rhs.symbol == ctx.definitions.Predef_undefined || - rhs.tpe =:= ctx.definitions.NothingType || - defdef.symbol.is(Deferred) || - (rhs match { - case _: tpd.Literal => true - case _ => rhs.tpe match - case ConstantType(_) => true - case tp: TermRef => - // Detect Scala 2 SingleType - tp.underlying.classSymbol.is(Flags.Module) - case _ => - false - }) - def registerTrivial(using Context): Unit = - if defdef.isTrivial then - trivialDefs += defdef.symbol - - extension (memDef: tpd.MemberDef) - private def isValidMemberDef(using Context): Boolean = - memDef.symbol.exists - && !memDef.symbol.isUnusedAnnot - && !memDef.symbol.isAllOf(Flags.AccessorCreationFlags) - && !memDef.name.isWildcard - && !memDef.symbol.owner.is(ExtensionMethod) - - private def isValidParam(using Context): Boolean = - val sym = memDef.symbol - (sym.is(Param) || sym.isAllOf(PrivateParamAccessor | Local, butNot = CaseAccessor)) && - !isSyntheticMainParam(sym) && - !sym.shouldNotReportParamOwner - - private def shouldReportPrivateDef(using Context): Boolean = - currScopeType.top == ScopeType.Template && !memDef.symbol.isConstructor && memDef.symbol.is(Private, butNot = SelfName | Synthetic | CaseAccessor) - - private def isUnsetVarDef(using Context): Boolean = - val sym = memDef.symbol - sym.is(Mutable) && !setVars(sym) - - extension (imp: tpd.Import) - /** Enum generate an import for its cases (but outside them), which should be ignored */ - def isGeneratedByEnum(using Context): Boolean = - imp.symbol.exists && imp.symbol.owner.is(Flags.Enum, butNot = Flags.Case) - - extension (thisName: Name) - private def isWildcard: Boolean = - thisName == StdNames.nme.WILDCARD || thisName.is(WildcardParamName) - - end UnusedData - - private object UnusedData: - enum ScopeType: - case Local - case Template - case ReplWrapper - case Other - - object ScopeType: - /** return the scope corresponding to the enclosing scope of the given tree */ - def fromTree(tree: tpd.Tree)(using Context): ScopeType = tree match - case tree: tpd.Template => if tree.symbol.name.isReplWrapperName then ReplWrapper else Template - case _:tpd.Block => Local - case _ => Other - - final class ImportSelectorData(val qualTpe: Type, val selector: ImportSelector): - private var myUsed: Boolean = false - var excludedMembers: Set[TermName] = Set.empty - - def markUsed(): Unit = myUsed = true - - def isUsed: Boolean = myUsed - - def markExcluded(excluded: Set[TermName]): Unit = excludedMembers ++= excluded - - private var myAllSymbols: Set[Symbol] | Null = null - - def allSymbolsForNamed(using Context): Set[Symbol] = - if myAllSymbols == null then - val allDenots = qualTpe.member(selector.name).alternatives ::: qualTpe.member(selector.name.toTypeName).alternatives - myAllSymbols = allDenots.map(_.symbol).toSet - myAllSymbols.uncheckedNN - - private var myAllSymbolsDealiased: Set[Symbol] | Null = null - - def allSymbolsDealiasedForNamed(using Context): Set[Symbol] = - if myAllSymbolsDealiased == null then - myAllSymbolsDealiased = allSymbolsForNamed.map(sym => dealias(sym)) - myAllSymbolsDealiased.uncheckedNN - end ImportSelectorData - - case class UnusedSymbol(pos: SrcPos, name: Name, warnType: WarnTypes) - /** A container for the results of the used elements analysis */ - case class UnusedResult(warnings: Set[UnusedSymbol]) - object UnusedResult: - val Empty = UnusedResult(Set.empty) - end UnusedData - - private def dealias(symbol: Symbol)(using Context): Symbol = - if symbol.isType && symbol.asType.denot.isAliasType then - symbol.asType.typeRef.dealias.typeSymbol - else - symbol + /** Generated import of cases from enum companion. */ + def isGeneratedByEnum(using Context): Boolean = + imp.symbol.exists && imp.symbol.owner.is(Enum, butNot = Case) + /** Under -Wunused:strict-no-implicit-warn, avoid false positives + * if this selector is a wildcard that might import implicits or + * specifically does import an implicit. + * Similarly, import of CanEqual must not warn, as it is always witness. + */ + def isLoose(sel: ImportSelector)(using Context): Boolean = + if ctx.settings.WunusedHas.strictNoImplicitWarn then + if sel.isWildcard + || imp.expr.tpe.member(sel.name.toTermName).hasAltWith(_.symbol.isOneOf(GivenOrImplicit)) + || imp.expr.tpe.member(sel.name.toTypeName).hasAltWith(_.symbol.isOneOf(GivenOrImplicit)) + then return true + if sel.isWildcard && sel.isGiven + then imp.expr.tpe.allMembers.exists(_.symbol.isCanEqual) + else imp.expr.tpe.member(sel.name.toTermName).hasAltWith(_.symbol.isCanEqual) + + extension (pos: SrcPos) + def isZeroExtentSynthetic: Boolean = pos.span.isSynthetic && pos.span.isZeroExtent + def isSynthetic: Boolean = pos.span.isSynthetic && pos.span.exists + + extension [A <: AnyRef](arr: Array[A]) + // returns `until` if not satisfied + def indexSatisfying(from: Int, until: Int = arr.length)(p: A => Boolean): Int = + var i = from + while i < until && !p(arr(i)) do + i += 1 + i end CheckUnused diff --git a/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala b/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala index c31b2673e04a..1c045288c94a 100644 --- a/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala +++ b/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala @@ -101,7 +101,7 @@ object ContextFunctionResults: def contextFunctionResultTypeAfter(meth: Symbol, depth: Int)(using Context) = def recur(tp: Type, n: Int): Type = if n == 0 then tp - else tp match + else tp.dealias match case defn.FunctionTypeOfMethod(mt) => recur(mt.resType, n - 1) recur(meth.info.finalResultType, depth) diff --git a/compiler/src/dotty/tools/dotc/transform/Dependencies.scala b/compiler/src/dotty/tools/dotc/transform/Dependencies.scala index 523ea75be912..9084930b6815 100644 --- a/compiler/src/dotty/tools/dotc/transform/Dependencies.scala +++ b/compiler/src/dotty/tools/dotc/transform/Dependencies.scala @@ -30,7 +30,7 @@ abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Co def tracked: Iterable[Symbol] = free.keys /** The outermost class that captures all free variables of a function - * that are captured by enclosinh classes (this means that the function could + * that are captured by enclosing classes (this means that the function could * be placed in that class without having to add more environment parameters) */ def logicalOwner: collection.Map[Symbol, Symbol] = logicOwner @@ -137,6 +137,7 @@ abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Co if !enclosure.exists then throw NoPath() if enclosure == sym.enclosure then NoSymbol else + /** is sym a constructor or a term that is nested in a constructor? */ def nestedInConstructor(sym: Symbol): Boolean = sym.isConstructor || sym.isTerm && nestedInConstructor(sym.enclosure) @@ -237,6 +238,10 @@ abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Co captureImplicitThis(tree.tpe) case tree: Select => if isExpr(sym) && isLocal(sym) then markCalled(sym, enclosure) + case tree: New => + val constr = tree.tpe.typeSymbol.primaryConstructor + if constr.exists then + symSet(called, enclosure) += constr case tree: This => narrowTo(tree.symbol.asClass) case tree: MemberDef if isExpr(sym) && sym.owner.isTerm => @@ -291,7 +296,6 @@ abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Co val calleeOwner = normalizedCallee.owner if calleeOwner.isTerm then narrowLogicOwner(caller, logicOwner(normalizedCallee)) else - assert(calleeOwner.is(Trait)) // methods nested inside local trait methods cannot be lifted out // beyond the trait. Note that we can also call a trait method through // a qualifier; in that case no restriction to lifted owner arises. diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 7414ca7e69c6..25239aee59cf 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -24,7 +24,6 @@ import typer.NoChecking import inlines.Inlines import typer.ProtoTypes.* import typer.ErrorReporting.errorTree -import typer.Checking.checkValue import core.TypeErasure.* import core.Decorators.* import dotty.tools.dotc.ast.{tpd, untpd} @@ -676,7 +675,7 @@ object Erasure { if tree.name == nme.apply && integrateSelect(tree) then return typed(tree.qualifier, pt) - val qual1 = typed(tree.qualifier, AnySelectionProto) + var qual1 = typed(tree.qualifier, AnySelectionProto) def mapOwner(sym: Symbol): Symbol = if !sym.exists && tree.name == nme.apply then @@ -725,7 +724,8 @@ object Erasure { assert(sym.exists, i"no owner from $owner/${origSym.showLocated} in $tree") - if owner == defn.ObjectClass then checkValue(qual1) + if owner == defn.ObjectClass then + qual1 = checkValue(qual1) def select(qual: Tree, sym: Symbol): Tree = untpd.cpy.Select(tree)(qual, sym.name).withType(NamedType(qual.tpe, sym)) diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala index 9a6a04621074..0f7dde993b17 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala @@ -20,11 +20,14 @@ import ValueClasses.* * Make private accessor in value class not-private. This is necessary to unbox * the value class when accessing it from separate compilation units * - * Also, make non-private any private parameter forwarders that forward to an inherited + * Make non-private any private parameter forwarders that forward to an inherited * public or protected parameter accessor with the same name as the forwarder. * This is necessary since private methods are not allowed to have the same name * as inherited public ones. * + * Also, make non-private any private constructor that is annotated with `@publicInBinary`. + * (See SIP-52) + * * See discussion in https://github.com/scala/scala3/pull/784 * and https://github.com/scala/scala3/issues/783 */ @@ -102,6 +105,8 @@ class ExpandPrivate extends MiniPhase with IdentityDenotTransformer { thisPhase override def transformDefDef(tree: DefDef)(using Context): DefDef = { val sym = tree.symbol tree.rhs match { + case _ if sym.isConstructor && sym.hasPublicInBinary => + sym.ensureNotPrivate.installAfter(thisPhase) case Apply(sel @ Select(_: Super, _), _) if sym.isAllOf(PrivateParamAccessor) && sel.symbol.is(ParamAccessor) && sym.name == sel.symbol.name => sym.ensureNotPrivate.installAfter(thisPhase) diff --git a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala index f0d1c687df8e..cd78e6da36d7 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -74,12 +74,10 @@ class ExtensionMethods extends MiniPhase with DenotTransformer with FullParamete sym.validFor = thisPhase.validFor } - // Create extension methods, except if the class comes from Scala 2 - // because it adds extension methods before pickling. - if !valueClass.is(Scala2x, butNot = Scala2Tasty) then - for (decl <- valueClass.classInfo.decls) - if isMethodWithExtension(decl) then - enterInModuleClass(createExtensionMethod(decl, moduleClassSym.symbol)) + // Create extension methods + for (decl <- valueClass.classInfo.decls) + if isMethodWithExtension(decl) then + enterInModuleClass(createExtensionMethod(decl, moduleClassSym.symbol)) // Create synthetic methods to cast values between the underlying type // and the ErasedValueType. These methods are removed in ElimErasedValueType. diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index c66e6b9471cb..8fc9f02c1e38 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -19,10 +19,14 @@ import NameKinds.OuterSelectName import StdNames.* import config.Feature import inlines.Inlines.inInlineMethod +import util.Property object FirstTransform { val name: String = "firstTransform" val description: String = "some transformations to put trees into a canonical form" + + /** Attachment key for named argument patterns */ + val WasNamedArg: Property.StickyKey[Unit] = Property.StickyKey() } /** The first tree transform @@ -38,6 +42,7 @@ object FirstTransform { */ class FirstTransform extends MiniPhase with SymTransformer { thisPhase => import ast.tpd.* + import FirstTransform.* override def phaseName: String = FirstTransform.name @@ -62,7 +67,7 @@ class FirstTransform extends MiniPhase with SymTransformer { thisPhase => case Select(qual, name) if !name.is(OuterSelectName) && tree.symbol.exists => val qualTpe = qual.tpe assert( - qualTpe.isErasedValueType || qualTpe.derivesFrom(tree.symbol.owner) || + qualTpe.widenDealias.isErasedValueType || qualTpe.derivesFrom(tree.symbol.owner) || tree.symbol.is(JavaStatic) && qualTpe.derivesFrom(tree.symbol.enclosingClass), i"non member selection of ${tree.symbol.showLocated} from ${qualTpe} in $tree") case _: TypeTree => @@ -156,7 +161,13 @@ class FirstTransform extends MiniPhase with SymTransformer { thisPhase => override def transformOther(tree: Tree)(using Context): Tree = tree match { case tree: Export => EmptyTree - case tree: NamedArg => transformAllDeep(tree.arg) + case tree: NamedArg => + val res = transformAllDeep(tree.arg) + if ctx.mode.is(Mode.Pattern) then + // Need to keep NamedArg status for pattern matcher to work correctly when faced + // with single-element named tuples. + res.pushAttachment(WasNamedArg, ()) + res case tree => if (tree.isType) toTypeTree(tree) else tree } diff --git a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala index 47a280af6abc..af168b563048 100644 --- a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -36,8 +36,8 @@ object LambdaLift: val liftedDefs: HashMap[Symbol, ListBuffer[Tree]] = new HashMap val deps = new Dependencies(ctx.compilationUnit.tpdTree, ctx.withPhase(thisPhase)): - def isExpr(sym: Symbol)(using Context): Boolean = sym.is(Method) - def enclosure(using Context) = ctx.owner.enclosingMethod + def isExpr(sym: Symbol)(using Context): Boolean = sym.is(Method) || sym.hasAnnotation(defn.ScalaStaticAnnot) + def enclosure(using Context) = ctx.owner.enclosingMethodOrStatic override def process(tree: Tree)(using Context): Unit = super.process(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala index 6529eed77fa0..a9a17f6db464 100644 --- a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala +++ b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala @@ -225,11 +225,7 @@ object OverridingPairs: } ) else - // releaxed override check for explicit nulls if one of the symbols is Java defined, - // force `Null` to be a subtype of non-primitive value types during override checking. - val relaxedOverriding = ctx.explicitNulls && (member.is(JavaDefined) || other.is(JavaDefined)) member.name.is(DefaultGetterName) // default getters are not checked for compatibility - || memberTp.overrides(otherTp, relaxedOverriding, - member.matchNullaryLoosely || other.matchNullaryLoosely || fallBack, isSubType = isSubType) + || memberTp.overrides(otherTp, member.matchNullaryLoosely || other.matchNullaryLoosely || fallBack, isSubType = isSubType) end OverridingPairs diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index ee608a4297bf..e2505144abda 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -46,10 +46,8 @@ class PatternMatcher extends MiniPhase { case rt => tree.tpe val translated = new Translator(matchType, this).translateMatch(tree) - // Skip analysis on inlined code (eg pos/i19157) - if !tpd.enclosingInlineds.nonEmpty then - // check exhaustivity and unreachability - SpaceEngine.checkMatch(tree) + // check exhaustivity and unreachability + SpaceEngine.checkMatch(tree) translated.ensureConforms(matchType) } @@ -388,9 +386,20 @@ object PatternMatcher { } else letAbstract(get) { getResult => - val selectors = - if (args.tail.isEmpty) ref(getResult) :: Nil - else productSelectors(getResult.info).map(ref(getResult).select(_)) + def isUnaryNamedTupleSelectArg(arg: Tree) = + get.tpe.widenDealias.isNamedTupleType + && arg.removeAttachment(FirstTransform.WasNamedArg).isDefined + // Special case: Normally, we pull out the argument wholesale if + // there is only one. But if the argument is a named argument for + // a single-element named tuple, we have to select the field instead. + // NamedArg trees are eliminated in FirstTransform but for named arguments + // of patterns we add a WasNamedArg attachment, which is used to guide the + // logic here. See i22900.scala for test cases. + val selectors = args match + case arg :: Nil if !isUnaryNamedTupleSelectArg(arg) => + ref(getResult) :: Nil + case _ => + productSelectors(getResult.info).map(ref(getResult).select(_)) matchArgsPlan(selectors, args, onSuccess) } } diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index c8c071064ab8..fcf1b384fda1 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -25,10 +25,10 @@ import scala.annotation.constructorOnly import scala.concurrent.Promise import dotty.tools.dotc.transform.Pickler.writeSigFilesAsync -import scala.util.chaining.given import dotty.tools.io.FileWriters.{EagerReporter, BufferingReporter} import dotty.tools.dotc.sbt.interfaces.IncrementalCallback import dotty.tools.dotc.sbt.asyncZincPhasesCompleted +import dotty.tools.dotc.util.chaining.* import scala.concurrent.ExecutionContext import scala.util.control.NonFatal import java.util.concurrent.atomic.AtomicBoolean diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 898517806e50..df74e102f693 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -21,6 +21,7 @@ import reporting.* import NameKinds.WildcardParamName import cc.* import dotty.tools.dotc.transform.MacroAnnotations.hasMacroAnnotation +import dotty.tools.dotc.core.NameKinds.DefaultGetterName object PostTyper { val name: String = "posttyper" @@ -119,8 +120,31 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => private var inJavaAnnot: Boolean = false + private val seenUnrolledMethods: util.EqHashMap[Symbol, Boolean] = new util.EqHashMap[Symbol, Boolean] + private var noCheckNews: Set[New] = Set() + def isValidUnrolledMethod(method: Symbol, origin: SrcPos)(using Context): Boolean = + seenUnrolledMethods.getOrElseUpdate(method, { + val isCtor = method.isConstructor + if + method.name.is(DefaultGetterName) + then + false // not an error, but not an expandable unrolled method + else if + method.is(Deferred) + || isCtor && method.owner.is(Trait) + || !(isCtor || method.is(Final) || method.owner.is(ModuleClass)) + || method.owner.companionClass.is(CaseClass) + && (method.name == nme.apply || method.name == nme.fromProduct) + || method.owner.is(CaseClass) && method.name == nme.copy + then + report.error(IllegalUnrollPlacement(Some(method)), origin) + false + else + true + }) + def withNoCheckNews[T](ts: List[New])(op: => T): T = { val saved = noCheckNews noCheckNews ++= ts @@ -199,6 +223,12 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => tree } + private def registerIfUnrolledParam(sym: Symbol)(using Context): Unit = + if sym.hasAnnotation(defn.UnrollAnnot) && isValidUnrolledMethod(sym.owner, sym.sourcePos) then + val cls = sym.enclosingClass + val additions = Array(cls, cls.linkedClass).filter(_ != NoSymbol) + ctx.compilationUnit.unrolledClasses ++= additions + private def processValOrDefDef(tree: Tree)(using Context): tree.type = val sym = tree.symbol tree match @@ -215,7 +245,10 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => ++ sym.annotations) else if sym.is(Param) then - sym.keepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) + registerIfUnrolledParam(sym) + // @unused is getter/setter but we want it on ordinary method params + if !sym.owner.is(Method) || sym.owner.isConstructor then + sym.keepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) else if sym.is(ParamAccessor) then // @publicInBinary is not a meta-annotation and therefore not kept by `keepAnnotationsCarrying` val publicInBinaryAnnotOpt = sym.getAnnotation(defn.PublicInBinaryAnnot) @@ -223,7 +256,10 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => for publicInBinaryAnnot <- publicInBinaryAnnotOpt do sym.addAnnotation(publicInBinaryAnnot) else sym.keepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) - if sym.isScala2Macro && !ctx.settings.XignoreScala2Macros.value then + if sym.isScala2Macro && !ctx.settings.XignoreScala2Macros.value && + sym != defn.StringContext_raw && + sym != defn.StringContext_f && + sym != defn.StringContext_s then if !sym.owner.unforcedDecls.exists(p => !p.isScala2Macro && p.name == sym.name && p.signature == sym.signature) // Allow scala.reflect.materializeClassTag to be able to compile scala/reflect/package.scala // This should be removed on Scala 3.x diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 9631136a1c4e..bd87ebd8abe1 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -255,7 +255,10 @@ abstract class Recheck extends Phase, SymTransformer: def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Type = val resType = recheck(tree.tpt) - if tree.rhs.isEmpty then resType + def isUninitWildcard = tree.rhs match + case Ident(nme.WILDCARD) => tree.symbol.is(Mutable) + case _ => false + if tree.rhs.isEmpty || isUninitWildcard then resType else recheck(tree.rhs, resType) def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Type = diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index 6bc5db79fee5..67fd233e117a 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -111,11 +111,8 @@ object ResolveSuper { // of the superaccessor's type, see i5433.scala for an example where this matters val otherTp = other.asSeenFrom(base.thisType).info val accTp = acc.asSeenFrom(base.thisType).info - // Since the super class can be Java defined, - // we use relaxed overriding check for explicit nulls if one of the symbols is Java defined. - // This forces `Null` to be a subtype of non-primitive value types during override checking. - val relaxedOverriding = ctx.explicitNulls && (sym.is(JavaDefined) || acc.is(JavaDefined)) - if !otherTp.overrides(accTp, relaxedOverriding, matchLoosely = true) then + + if !otherTp.overrides(accTp, matchLoosely = true) then report.error(IllegalSuperAccessor(base, memberName, targetName, acc, accTp, other.symbol, otherTp), base.srcPos) bcs = bcs.tail } diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 45606b0dbef5..926a19224e79 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -78,7 +78,13 @@ class SyntheticMembers(thisPhase: DenotTransformer) { private def existingDef(sym: Symbol, clazz: ClassSymbol)(using Context): Symbol = val existing = sym.matchingMember(clazz.thisType) - if existing != sym && !existing.is(Deferred) then existing else NoSymbol + if ctx.settings.YcompileScala2Library.value && clazz.isValueClass && (sym == defn.Any_equals || sym == defn.Any_hashCode) then + NoSymbol + else if existing != sym && !existing.is(Deferred) then + existing + else + NoSymbol + end existingDef private def synthesizeDef(sym: TermSymbol, rhsFn: List[List[Tree]] => Context ?=> Tree)(using Context): Tree = DefDef(sym, rhsFn(_)(using ctx.withOwner(sym))).withSpan(ctx.owner.span.focus) @@ -498,53 +504,103 @@ class SyntheticMembers(thisPhase: DenotTransformer) { /** The class * * ``` - * case class C[T <: U](x: T, y: String*) + * trait U: + * type Elem + * + * case class C[T <: U](a: T, b: a.Elem, c: String*) * ``` * * gets the `fromProduct` method: * * ``` * def fromProduct(x$0: Product): MirroredMonoType = - * new C[U]( - * x$0.productElement(0).asInstanceOf[U], - * x$0.productElement(1).asInstanceOf[Seq[String]]: _*) + * val a$1 = x$0.productElement(0).asInstanceOf[U] + * val b$1 = x$0.productElement(1).asInstanceOf[a$1.Elem] + * val c$1 = x$0.productElement(2).asInstanceOf[Seq[String]] + * new C[U](a$1, b$1, c$1*) * ``` * where * ``` * type MirroredMonoType = C[?] * ``` + * + * However, if the last parameter is annotated `@unroll` then we generate: + * + * def fromProduct(x$0: Product): MirroredMonoType = + * val arity = x$0.productArity + * val a$1 = x$0.productElement(0).asInstanceOf[U] + * val b$1 = x$0.productElement(1).asInstanceOf[a$1.Elem] + * val c$1 = ( + * if arity > 2 then + * x$0.productElement(2) + * else + * + * ).asInstanceOf[Seq[String]] + * new C[U](a$1, b$1, c$1*) */ - def fromProductBody(caseClass: Symbol, param: Tree, optInfo: Option[MirrorImpl.OfProduct])(using Context): Tree = - def extractParams(tpe: Type): List[Type] = - tpe.asInstanceOf[MethodType].paramInfos - - def computeFromCaseClass: (Type, List[Type]) = - val (baseRef, baseInfo) = - val rawRef = caseClass.typeRef - val rawInfo = caseClass.primaryConstructor.info - optInfo match - case Some(info) => - (rawRef.asSeenFrom(info.pre, caseClass.owner), rawInfo.asSeenFrom(info.pre, caseClass.owner)) - case _ => - (rawRef, rawInfo) - baseInfo match + def fromProductBody(caseClass: Symbol, productParam: Tree, optInfo: Option[MirrorImpl.OfProduct])(using Context): Tree = + val classRef = optInfo match + case Some(info) => TypeRef(info.pre, caseClass) + case _ => caseClass.typeRef + val (newPrefix, constrMeth, constrSyms) = + val constr = TermRef(classRef, caseClass.primaryConstructor) + val symss = caseClass.primaryConstructor.paramSymss + (constr.info: @unchecked) match case tl: PolyType => val tvars = constrained(tl) val targs = for tvar <- tvars yield tvar.instantiate(fromBelow = false) - (baseRef.appliedTo(targs), extractParams(tl.instantiate(targs))) - case methTpe => - (baseRef, extractParams(methTpe)) - end computeFromCaseClass - - val (classRefApplied, paramInfos) = computeFromCaseClass - val elems = - for ((formal, idx) <- paramInfos.zipWithIndex) yield - val elem = - param.select(defn.Product_productElement).appliedTo(Literal(Constant(idx))) - .ensureConforms(formal.translateFromRepeated(toArray = false)) - if (formal.isRepeatedParam) ctx.typer.seqToRepeated(elem) else elem - New(classRefApplied, elems) + (AppliedType(classRef, targs), tl.instantiate(targs).asInstanceOf[MethodType], symss(1)) + case mt: MethodType => + (classRef, mt, symss.head) + + // Index of the first parameter marked `@unroll` or -1 + val unrolledFrom = + constrSyms.indexWhere(_.hasAnnotation(defn.UnrollAnnot)) + + // `val arity = x$0.productArity` + val arityDef: Option[ValDef] = + if unrolledFrom != -1 then + Some(SyntheticValDef(nme.arity, productParam.select(defn.Product_productArity).withSpan(ctx.owner.span.focus))) + else None + val arityRefTree = arityDef.map(vd => ref(vd.symbol)) + + // Create symbols for the vals corresponding to each parameter + // If there are dependent parameters, the infos won't be correct yet. + val bindingSyms = constrMeth.paramRefs.map: pref => + newSymbol(ctx.owner, pref.paramName.freshened, Synthetic, + pref.underlying.translateFromRepeated(toArray = false), coord = ctx.owner.span.focus) + val bindingRefs = bindingSyms.map(TermRef(NoPrefix, _)) + // Fix the infos for dependent parameters + if constrMeth.isParamDependent then + bindingSyms.foreach: bindingSym => + bindingSym.info = bindingSym.info.substParams(constrMeth, bindingRefs) + + def defaultGetterAtIndex(idx: Int): Tree = + val defaultGetterPrefix = caseClass.primaryConstructor.name.toTermName + ref(caseClass.companionModule).select(NameKinds.DefaultGetterName(defaultGetterPrefix, idx)) + + val bindingDefs = bindingSyms.zipWithIndex.map: (bindingSym, idx) => + val selection = productParam.select(defn.Product_productElement).appliedTo(Literal(Constant(idx))) + val rhs = ( + if unrolledFrom != -1 && idx >= unrolledFrom then + If(arityRefTree.get.select(defn.Int_>).appliedTo(Literal(Constant(idx))), + thenp = + selection, + elsep = + defaultGetterAtIndex(idx)) + else + selection + ).ensureConforms(bindingSym.info) + ValDef(bindingSym, rhs) + + val newArgs = bindingRefs.lazyZip(constrMeth.paramInfos).map: (bindingRef, paramInfo) => + val refTree = ref(bindingRef) + if paramInfo.isRepeatedParam then ctx.typer.seqToRepeated(refTree) else refTree + Block( + arityDef.toList ::: bindingDefs, + New(newPrefix, newArgs) + ) end fromProductBody /** For an enum T: diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index c35dc80c04a5..90262bc5da85 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -854,30 +854,34 @@ object TreeChecker { val phases = ctx.base.allPhases.toList val treeChecker = new LocalChecker(previousPhases(phases)) + def reportMalformedMacroTree(msg: String | Null, err: Throwable) = + val stack = + if !ctx.settings.Ydebug.value then "\nstacktrace available when compiling with `-Ydebug`" + else if err.getStackTrace == null then " no stacktrace" + else err.getStackTrace.nn.mkString(" ", " \n", "") + report.error( + em"""Malformed tree was found while expanding macro with -Xcheck-macros. + |The tree does not conform to the compiler's tree invariants. + | + |Macro was: + |${scala.quoted.runtime.impl.QuotesImpl.showDecompiledTree(original)} + | + |The macro returned: + |${scala.quoted.runtime.impl.QuotesImpl.showDecompiledTree(expansion)} + | + |Error: + |$msg + |$stack + |""", + original + ) + try treeChecker.typed(expansion)(using checkingCtx) catch case err: java.lang.AssertionError => - val stack = - if !ctx.settings.Ydebug.value then "\nstacktrace available when compiling with `-Ydebug`" - else if err.getStackTrace == null then " no stacktrace" - else err.getStackTrace.nn.mkString(" ", " \n", "") - - report.error( - em"""Malformed tree was found while expanding macro with -Xcheck-macros. - |The tree does not conform to the compiler's tree invariants. - | - |Macro was: - |${scala.quoted.runtime.impl.QuotesImpl.showDecompiledTree(original)} - | - |The macro returned: - |${scala.quoted.runtime.impl.QuotesImpl.showDecompiledTree(expansion)} - | - |Error: - |${err.getMessage} - |$stack - |""", - original - ) + reportMalformedMacroTree(err.getMessage(), err) + case err: UnhandledError => + reportMalformedMacroTree(err.diagnostic.message, err) private[TreeChecker] def previousPhases(phases: List[Phase])(using Context): List[Phase] = phases match { case (phase: MegaPhase) :: phases1 => diff --git a/compiler/src/dotty/tools/dotc/transform/UnrollDefinitions.scala b/compiler/src/dotty/tools/dotc/transform/UnrollDefinitions.scala new file mode 100644 index 000000000000..44379b88bf16 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/UnrollDefinitions.scala @@ -0,0 +1,311 @@ +package dotty.tools.dotc +package transform + +import ast.tpd +import ast.Trees.* +import core.* +import Flags.* +import Decorators.* +import Contexts.* +import Symbols.* +import Constants.Constant +import Decorators.* +import DenotTransformers.IdentityDenotTransformer +import Names.* +import dotty.tools.dotc.core.NameKinds.DefaultGetterName + +import dotty.tools.dotc.core.Types.{MethodType, NamedType, PolyType, Type, NoPrefix, NoType} + +import dotty.tools.dotc.printing.Formatting.hl + +import scala.collection.mutable +import scala.util.boundary, boundary.break +import dotty.tools.dotc.core.StdNames.nme +import dotty.tools.unreachable +import dotty.tools.dotc.util.Spans.Span + +/**Implementation of SIP-61. + * Runs when `@unroll` annotations are found in a compilation unit, installing new definitions + * + * Note that it only generates `Invisible` methods, so no interactions with Zinc/SemanticDB + */ +class UnrollDefinitions extends MacroTransform, IdentityDenotTransformer { + self => + + import tpd.* + + private val _unrolledDefs: util.EqHashMap[Symbol, ComputedIndices] = new util.EqHashMap[Symbol, ComputedIndices] + private def initializeUnrolledDefs(): util.EqHashMap[Symbol, ComputedIndices] = + _unrolledDefs.clear() + _unrolledDefs + + override def phaseName: String = UnrollDefinitions.name + + override def description: String = UnrollDefinitions.description + + override def changesMembers: Boolean = true + + override def run(using Context): Unit = + if ctx.compilationUnit.hasUnrollDefs then + super.run // create and run the transformer on the current compilation unit + + def newTransformer(using Context): Transformer = + UnrollingTransformer(ctx.compilationUnit.unrolledClasses) + + type ComputedIndices = List[(Int, List[Int])] + type ComputeIndices = Context ?=> Symbol => ComputedIndices + + private class UnrollingTransformer(unrolledClasses: Set[Symbol]) extends Transformer { + private val unrolledDefs = initializeUnrolledDefs() + + def computeIndices(annotated: Symbol)(using Context): ComputedIndices = + unrolledDefs.getOrElseUpdate(annotated, { + if annotated.name.is(DefaultGetterName) then + // happens in curried methods, where default argument occurs in parameter list + // after the unrolled parameter list. + // example: + // `final def foo(@unroll y: String = "")(x: Int = 23) = x` + // yields: + // `def foo$default$2(@unroll y: String): Int @uncheckedVariance = 23` + // Perhaps annotations should be preprocessed before they are copied? + Nil + else + val indices = annotated + .paramSymss + .zipWithIndex + .flatMap: (paramClause, paramClauseIndex) => + val annotationIndices = findUnrollAnnotations(paramClause) + if (annotationIndices.isEmpty) None + else Some((paramClauseIndex, annotationIndices)) + if indices.nonEmpty then + // pre-validation should have occurred in posttyper + assert(annotated.is(Final, butNot = Deferred) || annotated.isConstructor || annotated.owner.is(ModuleClass) || annotated.name.is(DefaultGetterName), + i"$annotated is not final&concrete, or a constructor") + indices + }) + end computeIndices + + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match + case tree @ TypeDef(_, impl: Template) if unrolledClasses(tree.symbol) => + super.transform(cpy.TypeDef(tree)(rhs = unrollTemplate(impl, computeIndices))) + case tree => + super.transform(tree) + } + + private def copyParamSym(sym: Symbol, parent: Symbol)(using Context): (Symbol, Symbol) = + val copied = sym.copy(owner = parent, flags = (sym.flags &~ HasDefault), coord = sym.coord) + sym -> copied + + private def symLocation(sym: Symbol)(using Context) = { + val lineDesc = + if (sym.span.exists && sym.span != sym.owner.span) + s" at line ${sym.srcPos.line + 1}" + else "" + i"in ${sym.owner}${lineDesc}" + } + + private def findUnrollAnnotations(params: List[Symbol])(using Context): List[Int] = { + params + .zipWithIndex + .collect { + case (v, i) if v.hasAnnotation(defn.UnrollAnnot) => + i + } + } + + private def isTypeClause(p: ParamClause) = p.headOption.exists(_.isInstanceOf[TypeDef]) + + /** Generate a forwarder that calls the next one in a "chain" of forwarders + * + * @param defdef the original unrolled def that the forwarder is derived from + * @param paramIndex index of the unrolled parameter (in the parameter list) that we stop at + * @param paramCount number of parameters in the annotated parameter list + * @param nextParamIndex index of next unrolled parameter - to fetch default argument + * @param annotatedParamListIndex index of the parameter list that contains unrolled parameters + * @param isCaseApply if `defdef` is a case class apply/constructor - used for selection of default arguments + */ + private def generateSingleForwarder(defdef: DefDef, + paramIndex: Int, + paramCount: Int, + nextParamIndex: Int, + annotatedParamListIndex: Int, + isCaseApply: Boolean)(using Context): DefDef = { + + def initNewForwarder()(using Context): (TermSymbol, List[List[Symbol]]) = { + val forwarderDefSymbol0 = Symbols.newSymbol( + defdef.symbol.owner, + defdef.name, + defdef.symbol.flags &~ HasDefaultParams | + Invisible | Synthetic, + NoType, // fill in later + coord = defdef.span + ).entered + + val newParamSymMappings = extractParamSymss(copyParamSym(_, forwarderDefSymbol0)) + val (oldParams, newParams) = newParamSymMappings.flatten.unzip + + val newParamSymLists0 = + newParamSymMappings.map: pairs => + pairs.map: (oldSym, newSym) => + newSym.info = oldSym.info.substSym(oldParams, newParams) + newSym + + val newResType = defdef.tpt.tpe.substSym(oldParams, newParams) + forwarderDefSymbol0.info = NamerOps.methodType(newParamSymLists0, newResType) + forwarderDefSymbol0.setParamss(newParamSymLists0) + forwarderDefSymbol0 -> newParamSymLists0 + } + + def extractParamSymss[T](onSymbol: Symbol => T): List[List[T]] = + defdef.paramss.zipWithIndex.map{ case (ps, i) => + if (i == annotatedParamListIndex) ps.take(paramIndex).map(p => onSymbol(p.symbol)) + else ps.map(p => onSymbol(p.symbol)) + } + + val (forwarderDefSymbol, newParamSymLists) = initNewForwarder() + + def forwarderRhs(): tpd.Tree = { + val defaultOffset = defdef.paramss + .iterator + .take(annotatedParamListIndex) + .filter(!isTypeClause(_)) + .map(_.size) + .sum + + val defaultCalls = Range(paramIndex, paramCount).map(n => + + def makeSelect(refTree: Tree, name: TermName): Tree = + val sym = refTree.symbol + if !sym.findMember(name, NoPrefix, EmptyFlags, EmptyFlags).exists then + val param = defdef.paramss(annotatedParamListIndex)(n) + val methodStr = s"method ${defdef.name} ${symLocation(defdef.symbol)}" + val paramStr = s"parameter ${param.name}" + val errorMessage = + i"Cannot unroll $methodStr because $paramStr needs a default value" + report.error(errorMessage, param.srcPos) + ref(newErrorSymbol(sym, nme.ERROR, errorMessage.toMessage)) + else + refTree.select(name) + + val inner = if (defdef.symbol.isConstructor) { + makeSelect(ref(defdef.symbol.owner.companionModule), + DefaultGetterName(defdef.name, n + defaultOffset)) + } else if (isCaseApply) { + makeSelect(ref(defdef.symbol.owner.companionModule), + DefaultGetterName(termName(""), n + defaultOffset)) + } else { + makeSelect(This(defdef.symbol.owner.asClass), + DefaultGetterName(defdef.name, n + defaultOffset)) + } + + newParamSymLists + .take(annotatedParamListIndex) + .map(_.map(ref)) + .foldLeft(inner)(_.appliedToArgs(_)) + ) + + val forwarderInner: Tree = + This(defdef.symbol.owner.asClass).select(defdef.symbol) + + val forwarderCallArgs = + newParamSymLists.zipWithIndex.map{case (ps, i) => + if (i == annotatedParamListIndex) ps.map(ref).take(nextParamIndex) ++ defaultCalls + else ps.map(ref) + } + + val forwarderCall0 = forwarderCallArgs.foldLeft(forwarderInner)(_.appliedToArgs(_)) + + val forwarderCall = + if (!defdef.symbol.isConstructor) forwarderCall0 + else Block(List(forwarderCall0), Literal(Constant(()))) + + forwarderCall + } + + val forwarderDef = + tpd.DefDef(forwarderDefSymbol, rhs = forwarderRhs()).withSpan(defdef.span) + + forwarderDef + } + + case class Forwarders(origin: Symbol, forwarders: List[DefDef]) + + private def generateSyntheticDefs(tree: Tree, compute: ComputeIndices)(using Context): Option[Forwarders] = tree match { + case defdef: DefDef if defdef.paramss.nonEmpty => + import dotty.tools.dotc.core.NameOps.isConstructorName + + val isCaseCopy = + defdef.name == nme.copy && defdef.symbol.owner.is(CaseClass) + + val isCaseApply = + defdef.name == nme.apply && defdef.symbol.owner.companionClass.is(CaseClass) + + val annotated = + if (isCaseCopy) defdef.symbol.owner.primaryConstructor + else if (isCaseApply) defdef.symbol.owner.companionClass.primaryConstructor + else defdef.symbol + + compute(annotated) match { + case Nil => None + case (paramClauseIndex, annotationIndices) :: Nil => + val paramCount = annotated.paramSymss(paramClauseIndex).size + val generatedDefs = + val indices = (annotationIndices :+ paramCount).sliding(2).toList.reverse + indices.foldLeft(List.empty[DefDef]): + case (defdefs, paramIndex :: nextParamIndex :: Nil) => + generateSingleForwarder( + defdef, + paramIndex, + paramCount, + nextParamIndex, + paramClauseIndex, + isCaseApply + ) :: defdefs + case _ => unreachable("sliding with at least 2 elements") + Some(Forwarders(origin = defdef.symbol, forwarders = generatedDefs)) + + case multiple => + report.error("Cannot have multiple parameter lists containing `@unroll` annotation", defdef.srcPos) + None + } + + case _ => None + } + + private def unrollTemplate(tmpl: tpd.Template, compute: ComputeIndices)(using Context): tpd.Tree = { + + val generatedBody = tmpl.body.flatMap(generateSyntheticDefs(_, compute)) + val generatedConstr0 = generateSyntheticDefs(tmpl.constr, compute) + val allGenerated = generatedBody ++ generatedConstr0 + + if allGenerated.nonEmpty then + val byName = (tmpl.constr :: tmpl.body).groupMap(_.symbol.name.toString)(_.symbol) + for + syntheticDefs <- allGenerated + dcl <- syntheticDefs.forwarders + do + val replaced = dcl.symbol + byName.get(dcl.name.toString).foreach { syms => + val clashes = syms.filter(ctx.typer.matchesSameStatic(replaced, _)) + for existing <- clashes do + val src = syntheticDefs.origin + report.error(i"""Unrolled $replaced clashes with existing declaration. + |Please remove the clashing definition, or the @unroll annotation. + |Unrolled from ${hl(src.showDcl)} ${symLocation(src)}""".stripMargin, existing.srcPos) + } + end if + + cpy.Template(tmpl)( + tmpl.constr, + tmpl.parents, + tmpl.derived, + tmpl.self, + tmpl.body ++ allGenerated.flatMap(_.forwarders) + ) + } + +} + +object UnrollDefinitions: + val name: String = "unrollDefs" + val description: String = "generates forwarders for methods annotated with @unroll" diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 316213a94f8d..328446a02e23 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -29,6 +29,7 @@ import scala.collection.mutable import scala.annotation.tailrec import scala.annotation.constructorOnly import dotty.tools.dotc.core.Flags.AbstractOrTrait +import dotty.tools.dotc.util.SrcPos /** Check initialization safety of static objects * @@ -54,10 +55,10 @@ import dotty.tools.dotc.core.Flags.AbstractOrTrait * This principle not only put initialization of static objects on a solid foundation, but also * avoids whole-program analysis. * - * 2. The design is based on the concept of "cold aliasing" --- a cold alias may not be actively - * used during initialization, i.e., it's forbidden to call methods or access fields of a cold - * alias. Method arguments are cold aliases by default unless specified to be sensitive. Method - * parameters captured in lambdas or inner classes are always cold aliases. + * 2. The design is based on the concept of "Top" --- a Top value may not be actively + * used during initialization, i.e., it's forbidden to call methods or access fields of a Top. + * Method arguments are widened to Top by default unless specified to be sensitive. + * Method parameters captured in lambdas or inner classes are always widened to Top. * * 3. It is inter-procedural and flow-sensitive. * @@ -82,7 +83,7 @@ class Objects(using Context @constructorOnly): val immutableLazyList: Symbol = requiredModule("scala.collection.immutable.LazyList") val LazyList_empty: Symbol = immutableLazyList.requiredValue("_empty") - val whiteList: Set[Symbol] = Set(SetNode_EmptySetNode, HashSet_EmptySet, Vector_EmptyIterator, MapNode_EmptyMapNode, HashMap_EmptyMap, LazyList_empty) + val allowList: Set[Symbol] = Set(SetNode_EmptySetNode, HashSet_EmptySet, Vector_EmptyIterator, MapNode_EmptyMapNode, HashMap_EmptyMap, LazyList_empty) // ----------------------------- abstract domain ----------------------------- @@ -92,11 +93,12 @@ class Objects(using Context @constructorOnly): * | OfClass(class, vs[outer], ctor, args, env) // instance of a class * | OfArray(object[owner], regions) * | Fun(..., env) // value elements that can be contained in ValueSet + * | SafeValue // values on which method calls and field accesses won't cause warnings. Int, String, etc. * vs ::= ValueSet(ve) // set of abstract values * Bottom ::= ValueSet(Empty) - * val ::= ve | Cold | vs // all possible abstract values in domain + * val ::= ve | Top | UnknownValue | vs | Package // all possible abstract values in domain * Ref ::= ObjectRef | OfClass // values that represent a reference to some (global or instance) object - * ThisValue ::= Ref | Cold // possible values for 'this' + * ThisValue ::= Ref | Top // possible values for 'this' * * refMap = Ref -> ( valsMap, varsMap, outersMap ) // refMap stores field informations of an object or instance * valsMap = valsym -> val // maps immutable fields to their values @@ -188,7 +190,7 @@ class Objects(using Context @constructorOnly): def show(using Context) = val valFields = vals.map(_.show + " -> " + _.show) - "OfClass(" + klass.show + ", outer = " + outer + ", args = " + args.map(_.show) + ", vals = " + valFields + ")" + "OfClass(" + klass.show + ", outer = " + outer + ", args = " + args.map(_.show) + " env = " + env.show + ", vals = " + valFields + ")" object OfClass: def apply( @@ -225,25 +227,82 @@ class Objects(using Context @constructorOnly): case class Fun(code: Tree, thisV: ThisValue, klass: ClassSymbol, env: Env.Data) extends ValueElement: def show(using Context) = "Fun(" + code.show + ", " + thisV.show + ", " + klass.show + ")" + /** + * Represents common base values like Int, String, etc. + * Assumption: all methods calls on such values should not trigger initialization of global objects + * or read/write mutable fields + */ + case class SafeValue(typeSymbol: Symbol) extends ValueElement: + assert(SafeValue.safeTypeSymbols.contains(typeSymbol), "Invalid creation of SafeValue! Type = " + typeSymbol) + def show(using Context): String = "SafeValue of " + typeSymbol.show + + object SafeValue: + val safeTypeSymbols = + defn.StringClass :: + (defn.ScalaNumericValueTypeList ++ + List(defn.UnitType, defn.BooleanType, defn.NullType, defn.ClassClass.typeRef)) + .map(_.symbol) + + def getSafeTypeSymbol(tpe: Type): Option[Symbol] = + val baseType = if tpe.isInstanceOf[AppliedType] then tpe.asInstanceOf[AppliedType].underlying else tpe + if baseType.isInstanceOf[TypeRef] then + val typeRef = baseType.asInstanceOf[TypeRef] + val typeSymbol = typeRef.symbol + val typeAlias = typeRef.translucentSuperType + if safeTypeSymbols.contains(typeSymbol) then + Some(typeSymbol) + else if typeAlias.isInstanceOf[TypeRef] && typeAlias.asInstanceOf[TypeRef].symbol == defn.StringClass then + // Special case, type scala.Predef.String = java.lang.String + Some(defn.StringClass) + else None + else + None + + def apply(tpe: Type): SafeValue = + // tpe could be a AppliedType(java.lang.Class, T) + val typeSymbol = getSafeTypeSymbol(tpe) + assert(typeSymbol.isDefined, "Invalid creation of SafeValue with type " + tpe) + new SafeValue(typeSymbol.get) + /** * Represents a set of values * * It comes from `if` expressions. */ - case class ValueSet(values: ListSet[ValueElement]) extends Value: + case class ValueSet(values: Set[ValueElement]) extends Value: def show(using Context) = values.map(_.show).mkString("[", ",", "]") - /** A cold alias which should not be used during initialization. - * - * Cold is not ValueElement since RefSet containing Cold is equivalent to Cold + case class Package(packageModuleClass: ClassSymbol) extends Value: + def show(using Context): String = "Package(" + packageModuleClass.show + ")" + + object Package: + def apply(packageSym: Symbol): Package = + assert(packageSym.is(Flags.Package), "Invalid symbol to create Package!") + Package(packageSym.moduleClass.asClass) + + /** Represents values unknown to the checker, such as values loaded without source + * UnknownValue is not ValueElement since RefSet containing UnknownValue + * is equivalent to UnknownValue */ - case object Cold extends Value: - def show(using Context) = "Cold" + case object UnknownValue extends Value: + def show(using Context): String = "UnknownValue" + + /** Represents values lost due to widening + * + * This is the top of the abstract domain lattice, which should not + * be used during initialization. + * + * Top is not ValueElement since RefSet containing Top + * is equivalent to Top + */ + + case object Top extends Value: + def show(using Context): String = "Top" val Bottom = ValueSet(ListSet.empty) /** Possible types for 'this' */ - type ThisValue = Ref | Cold.type + type ThisValue = Ref | Top.type /** Checking state */ object State: @@ -437,7 +496,40 @@ class Objects(using Context @constructorOnly): throw new RuntimeException("Incorrect local environment for initializing " + x.show) /** - * Resolve the environment owned by the given method. + * Resolve the environment by searching for a given symbol. + * + * Searches for the environment that owns `target`, starting from `env` as the innermost. + * + * Due to widening, the corresponding environment might not exist. As a result reading the local + * variable will return `Cold` and it's forbidden to write to the local variable. + * + * @param target The symbol to search for. + * @param thisV The value for `this` of the enclosing class where the local variable is referenced. + * @param env The local environment where the local variable is referenced. + * + * @return the environment that owns the `target` and value for `this` owned by the given method. + */ + def resolveEnvByValue(target: Symbol, thisV: ThisValue, env: Data)(using Context): Option[(ThisValue, Data)] = log("Resolving env by value for " + target.show + ", this = " + thisV.show + ", env = " + env.show, printer) { + env match + case localEnv: LocalEnv => + if localEnv.getVal(target).isDefined then Some(thisV -> localEnv) + else if localEnv.getVar(target).isDefined then Some(thisV -> localEnv) + else resolveEnvByValue(target, thisV, localEnv.outer) + case NoEnv => + thisV match + case ref: OfClass => + ref.outer match + case outer : ThisValue => + resolveEnvByValue(target, outer, ref.env) + case _ => + // TODO: properly handle the case where ref.outer is ValueSet + None + case _ => + None + } + + /** + * Resolve the environment owned by the given method `enclosing`. * * The method could be located in outer scope with intermixed classes between its definition * site and usage site. @@ -445,23 +537,25 @@ class Objects(using Context @constructorOnly): * Due to widening, the corresponding environment might not exist. As a result reading the local * variable will return `Cold` and it's forbidden to write to the local variable. * - * @param meth The method which owns the environment - * @param thisV The value for `this` of the enclosing class where the local variable is referenced. - * @param env The local environment where the local variable is referenced. + * @param enclosing The method which owns the environment. This method is called to look up the environment + * owned by the enclosing method of some symbol. + * @param thisV The value for `this` of the enclosing class where the local variable is referenced. + * @param env The local environment where the local variable is referenced. * * @return the environment and value for `this` owned by the given method. */ - def resolveEnv(meth: Symbol, thisV: ThisValue, env: Data)(using Context): Option[(ThisValue, Data)] = log("Resolving env for " + meth.show + ", this = " + thisV.show + ", env = " + env.show, printer) { + def resolveEnvByOwner(enclosing: Symbol, thisV: ThisValue, env: Data)(using Context): Option[(ThisValue, Data)] = log("Resolving env by owner for " + enclosing.show + ", this = " + thisV.show + ", env = " + env.show, printer) { + assert(enclosing.is(Flags.Method), "Only method symbols allows, got " + enclosing.show) env match case localEnv: LocalEnv => - if localEnv.meth == meth then Some(thisV -> env) - else resolveEnv(meth, thisV, localEnv.outer) + if localEnv.meth == enclosing then Some(thisV -> env) + else resolveEnvByOwner(enclosing, thisV, localEnv.outer) case NoEnv => thisV match case ref: OfClass => ref.outer match case outer : ThisValue => - resolveEnv(meth, outer, ref.env) + resolveEnvByOwner(enclosing, outer, ref.env) case _ => // TODO: properly handle the case where ref.outer is ValueSet None @@ -599,18 +693,30 @@ class Objects(using Context @constructorOnly): extension (a: Value) def join(b: Value): Value = + assert(!a.isInstanceOf[Package] && !b.isInstanceOf[Package], "Unexpected join between " + a + " and " + b) (a, b) match - case (Cold, _) => Cold - case (_, Cold) => Cold + case (Top, _) => Top + case (_, Top) => Top + case (UnknownValue, _) => UnknownValue + case (_, UnknownValue) => UnknownValue case (Bottom, b) => b case (a, Bottom) => a case (ValueSet(values1), ValueSet(values2)) => ValueSet(values1 ++ values2) case (a : ValueElement, ValueSet(values)) => ValueSet(values + a) case (ValueSet(values), b : ValueElement) => ValueSet(values + b) - case (a : ValueElement, b : ValueElement) => ValueSet(ListSet(a, b)) - - def widen(height: Int)(using Context): Value = - if height == 0 then Cold + case (a : ValueElement, b : ValueElement) => ValueSet(Set(a, b)) + case _ => Bottom + + def remove(b: Value): Value = (a, b) match + case (ValueSet(values1), b: ValueElement) => ValueSet(values1 - b) + case (ValueSet(values1), ValueSet(values2)) => ValueSet(values1.removedAll(values2)) + case (a: Ref, b: Ref) if a.equals(b) => Bottom + case (a: SafeValue, b: SafeValue) if a == b => Bottom + case (a: Package, b: Package) if a == b => Bottom + case _ => a + + def widen(height: Int)(using Context): Value = log("widening value " + a.show + " down to height " + height, printer, (_: Value).show) { + if height == 0 then Top else a match case Bottom => Bottom @@ -619,7 +725,7 @@ class Objects(using Context @constructorOnly): values.map(ref => ref.widen(height)).join case Fun(code, thisV, klass, env) => - Fun(code, thisV.widenRefOrCold(height), klass, env.widen(height - 1)) + Fun(code, thisV.widenThisValue(height), klass, env.widen(height - 1)) case ref @ OfClass(klass, outer, _, args, env) => val outer2 = outer.widen(height - 1) @@ -628,6 +734,7 @@ class Objects(using Context @constructorOnly): ref.widenedCopy(outer2, args2, env2) case _ => a + } def filterType(tpe: Type)(using Context): Value = tpe match @@ -637,26 +744,46 @@ class Objects(using Context @constructorOnly): if baseClasses.isEmpty then a else filterClass(baseClasses.head) // could have called ClassSymbol, but it does not handle OrType and AndType + // Filter the value according to a class symbol, and only leaves the sub-values + // which could represent an object of the given class def filterClass(sym: Symbol)(using Context): Value = - if !sym.isClass then a - else - val klass = sym.asClass - a match - case Cold => Cold - case ref: Ref => if ref.klass.isSubClass(klass) then ref else Bottom - case ValueSet(values) => values.map(v => v.filterClass(klass)).join - case arr: OfArray => if defn.ArrayClass.isSubClass(klass) then arr else Bottom - case fun: Fun => - if klass.isOneOf(AbstractOrTrait) && klass.baseClasses.exists(defn.isFunctionClass) then fun else Bottom - - extension (value: Ref | Cold.type) - def widenRefOrCold(height : Int)(using Context) : Ref | Cold.type = value.widen(height).asInstanceOf[ThisValue] + if !sym.isClass then a + else + val klass = sym.asClass + a match + case UnknownValue | Top => a + case Package(packageModuleClass) => + // the typer might mistakenly set the receiver to be a package instead of package object. + // See pos/packageObjectStringInterpolator.scala + if packageModuleClass == klass || (klass.denot.isPackageObject && klass.owner == packageModuleClass) then a else Bottom + case v: SafeValue => if v.typeSymbol.asClass.isSubClass(klass) then a else Bottom + case ref: Ref => if ref.klass.isSubClass(klass) then ref else Bottom + case ValueSet(values) => values.map(v => v.filterClass(klass)).join + case arr: OfArray => if defn.ArrayClass.isSubClass(klass) then arr else Bottom + case fun: Fun => + if klass.isOneOf(AbstractOrTrait) && klass.baseClasses.exists(defn.isFunctionClass) then fun else Bottom + + extension (value: ThisValue) + def widenThisValue(height : Int)(using Context) : ThisValue = + assert(height > 0, "Cannot call widenThisValue with height 0!") + value.widen(height).asInstanceOf[ThisValue] extension (values: Iterable[Value]) def join: Value = if values.isEmpty then Bottom else values.reduce { (v1, v2) => v1.join(v2) } def widen(height: Int): Contextual[List[Value]] = values.map(_.widen(height)).toList + /** Check if the checker option reports warnings about unknown code + */ + val reportUnknown: Boolean = false + + def reportWarningForUnknownValue(msg: => String, pos: SrcPos)(using Context): Value = + if reportUnknown then + report.warning(msg, pos) + Bottom + else + UnknownValue + /** Handle method calls `e.m(args)`. * * @param value The value for the receiver. @@ -668,13 +795,63 @@ class Objects(using Context @constructorOnly): */ def call(value: Value, meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, needResolve: Boolean = true): Contextual[Value] = log("call " + meth.show + ", this = " + value.show + ", args = " + args.map(_.value.show), printer, (_: Value).show) { value.filterClass(meth.owner) match - case Cold => - report.warning("Using cold alias. " + Trace.show, Trace.position) + case Top => + report.warning("Value is unknown to the checker due to widening. " + Trace.show, Trace.position) Bottom + case UnknownValue => + reportWarningForUnknownValue("Using unknown value. " + Trace.show, Trace.position) + + case Package(packageModuleClass) => + if meth.equals(defn.throwMethod) then + Bottom + else if meth.owner.denot.isPackageObject then + // calls on packages are unexpected. However the typer might mistakenly + // set the receiver to be a package instead of package object. + // See packageObjectStringInterpolator.scala + // Method call on package object instead + val packageObj = accessObject(meth.owner.moduleClass.asClass) + call(packageObj, meth, args, receiver, superType, needResolve) + else + report.warning("[Internal error] Unexpected call on package = " + value.show + ", meth = " + meth.show + Trace.show, Trace.position) + Bottom + + case v @ SafeValue(_) => + if v.typeSymbol == defn.NullClass then + // call on Null is sensible on AST level but not in practice + Bottom + else + // Assume such method is pure. Check return type, only try to analyze body if return type is not safe + val target = resolve(v.typeSymbol.asClass, meth) + val targetType = target.denot.info + assert(targetType.isInstanceOf[ExprType] || targetType.isInstanceOf[MethodType], + "Unexpected type! Receiver = " + v.show + ", meth = " + target + ", type = " + targetType) + val returnType = + if targetType.isInstanceOf[ExprType] then + // corresponds to parameterless method like `def meth: ExprType[T]` + // See pos/toDouble.scala + targetType.asInstanceOf[ExprType].resType + else + targetType.asInstanceOf[MethodType].resType + val typeSymbol = SafeValue.getSafeTypeSymbol(returnType) + if typeSymbol.isDefined then + // since method is pure and return type is safe, no need to analyze method body + SafeValue(typeSymbol.get) + else if !target.hasSource then + UnknownValue + else + val ddef = target.defTree.asInstanceOf[DefDef] + val cls = target.owner.enclosingClass.asClass + // convert SafeType to an OfClass before analyzing method body + val ref = OfClass(cls, Bottom, NoSymbol, Nil, Env.NoEnv) + call(ref, meth, args, receiver, superType, needResolve) case Bottom => Bottom + // Bottom arguments mean unreachable call + case _ if args.map(_.value).contains(Bottom) => + Bottom + case arr: OfArray => val target = resolve(defn.ArrayClass, meth) @@ -693,7 +870,7 @@ class Objects(using Context @constructorOnly): Bottom else // Array.length is OK - Bottom + SafeValue(defn.IntType) case ref: Ref => val isLocal = !meth.owner.isClass @@ -714,7 +891,7 @@ class Objects(using Context @constructorOnly): arr else if target.equals(defn.Predef_classOf) then // Predef.classOf is a stub method in tasty and is replaced in backend - Bottom + UnknownValue else if target.hasSource then val cls = target.owner.enclosingClass.asClass val ddef = target.defTree.asInstanceOf[DefDef] @@ -724,7 +901,7 @@ class Objects(using Context @constructorOnly): if meth.owner.isClass then (ref, Env.NoEnv) else - Env.resolveEnv(meth.owner.enclosingMethod, ref, summon[Env.Data]).getOrElse(Cold -> Env.NoEnv) + Env.resolveEnvByOwner(meth.owner.enclosingMethod, ref, summon[Env.Data]).getOrElse(Top -> Env.NoEnv) val env2 = Env.ofDefDef(ddef, args.map(_.value), outerEnv) extendTrace(ddef) { @@ -737,7 +914,7 @@ class Objects(using Context @constructorOnly): } } else - Bottom + UnknownValue else if target.exists then select(ref, target, receiver, needResolve = false) else @@ -765,15 +942,15 @@ class Objects(using Context @constructorOnly): value else // In future, we will have Tasty for stdlib classes and can abstractly interpret that Tasty. - // For now, return `Cold` to ensure soundness and trigger a warning. - Cold + // For now, return `UnknownValue` to ensure soundness and trigger a warning when reportUnknown = true. + UnknownValue end if end if case _ => - // by-name closure - given Env.Data = env - extendTrace(code) { eval(code, thisV, klass, cacheResult = true) } + // Should be unreachable, by-name closures are handled by readLocal + report.warning("[Internal error] Only DefDef should be possible here, but found " + code.show + ". " + Trace.show, Trace.position) + Bottom case ValueSet(vs) => vs.map(v => call(v, meth, args, receiver, superType)).join @@ -802,10 +979,11 @@ class Objects(using Context @constructorOnly): Returns.installHandler(ctor) eval(ddef.rhs, ref, cls, cacheResult = true) Returns.popHandler(ctor) + value } else // no source code available - Bottom + UnknownValue case _ => report.warning("[Internal error] unexpected constructor call, meth = " + ctor + ", this = " + value + Trace.show, Trace.position) @@ -821,9 +999,27 @@ class Objects(using Context @constructorOnly): */ def select(value: Value, field: Symbol, receiver: Type, needResolve: Boolean = true): Contextual[Value] = log("select " + field.show + ", this = " + value.show, printer, (_: Value).show) { value.filterClass(field.owner) match - case Cold => - report.warning("Using cold alias", Trace.position) + case Top => + report.warning("Value is unknown to the checker due to widening. " + Trace.show, Trace.position) Bottom + case UnknownValue => + reportWarningForUnknownValue("Using unknown value. " + Trace.show, Trace.position) + + case v @ SafeValue(_) => + if v.typeSymbol != defn.NullClass then + // selection on Null is sensible on AST level; no warning for it + report.warning("[Internal error] Unexpected selection on safe value " + v.show + ", field = " + field.show + ". " + Trace.show, Trace.position) + end if + Bottom + + case Package(packageModuleClass) => + if field.isStaticObject then + accessObject(field.moduleClass.asClass) + else if field.is(Flags.Package) then + Package(field) + else + report.warning("[Internal error] Unexpected selection on package " + packageModuleClass.show + ", field = " + field.show + Trace.show, Trace.position) + Bottom case ref: Ref => val target = if needResolve then resolve(ref.klass, field) else field @@ -833,7 +1029,7 @@ class Objects(using Context @constructorOnly): val rhs = target.defTree.asInstanceOf[ValDef].rhs eval(rhs, ref, target.owner.asClass, cacheResult = true) else - Bottom + UnknownValue else if target.exists then def isNextFieldOfColonColon: Boolean = ref.klass == defn.ConsClass && target.name.toString == "next" if target.isOneOf(Flags.Mutable) && !isNextFieldOfColonColon then @@ -864,9 +1060,9 @@ class Objects(using Context @constructorOnly): report.warning("[Internal error] Unexpected resolution failure: ref.klass = " + ref.klass.show + ", field = " + field.show + Trace.show, Trace.position) Bottom else - // This is possible due to incorrect type cast. - // See tests/init/pos/Type.scala - Bottom + // This is possible due to incorrect type cast or accessing standard library objects + // See tests/init/pos/Type.scala / tests/init/warn/unapplySeq-implicit-arg2.scala + UnknownValue case fun: Fun => report.warning("[Internal error] unexpected tree in selecting a function, fun = " + fun.code.show + Trace.show, fun.code) @@ -876,9 +1072,7 @@ class Objects(using Context @constructorOnly): report.warning("[Internal error] unexpected tree in selecting an array, array = " + arr.show + Trace.show, Trace.position) Bottom - case Bottom => - if field.isStaticObject then accessObject(field.moduleClass.asClass) - else Bottom + case Bottom => Bottom case ValueSet(values) => values.map(ref => select(ref, field, receiver)).join @@ -893,16 +1087,19 @@ class Objects(using Context @constructorOnly): */ def assign(lhs: Value, field: Symbol, rhs: Value, rhsTyp: Type): Contextual[Value] = log("Assign" + field.show + " of " + lhs.show + ", rhs = " + rhs.show, printer, (_: Value).show) { lhs.filterClass(field.owner) match + case Top => + report.warning("Value is unknown to the checker due to widening. " + Trace.show, Trace.position) + case UnknownValue => + val _ = reportWarningForUnknownValue("Assigning to unknown value. " + Trace.show, Trace.position) + case p: Package => + report.warning("[Internal error] unexpected tree in assignment, package = " + p.show + Trace.show, Trace.position) case fun: Fun => report.warning("[Internal error] unexpected tree in assignment, fun = " + fun.code.show + Trace.show, Trace.position) - case arr: OfArray => report.warning("[Internal error] unexpected tree in assignment, array = " + arr.show + " field = " + field + Trace.show, Trace.position) - case Cold => - report.warning("Assigning to cold aliases is forbidden. " + Trace.show, Trace.position) - - case Bottom => + case SafeValue(_) => + report.warning("Assigning to base value is forbidden. " + Trace.show, Trace.position) case ValueSet(values) => values.foreach(ref => assign(ref, field, rhs, rhsTyp)) @@ -933,11 +1130,14 @@ class Objects(using Context @constructorOnly): */ def instantiate(outer: Value, klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo]): Contextual[Value] = log("instantiating " + klass.show + ", outer = " + outer + ", args = " + args.map(_.value.show), printer, (_: Value).show) { outer.filterClass(klass.owner) match - case _ : Fun | _: OfArray => + case _ : Fun | _: OfArray | SafeValue(_) => report.warning("[Internal error] unexpected outer in instantiating a class, outer = " + outer.show + ", class = " + klass.show + ", " + Trace.show, Trace.position) Bottom - case outer: (Ref | Cold.type | Bottom.type) => + case UnknownValue => + reportWarningForUnknownValue("Instantiating when outer is unknown. " + Trace.show, Trace.position) + + case outer: (Ref | Top.type | Package) => if klass == defn.ArrayClass then args.head.tree.tpe match case ConstantType(Constants.Constant(0)) => @@ -951,18 +1151,18 @@ class Objects(using Context @constructorOnly): // Widen the outer to finitize the domain. Arguments already widened in `evalArgs`. val (outerWidened, envWidened) = outer match - case _ : Bottom.type => // For top-level classes - (Bottom, Env.NoEnv) - case thisV : (Ref | Cold.type) => + case Package(_) => // For top-level classes + (outer, Env.NoEnv) + case thisV : ThisValue => if klass.owner.isClass then if klass.owner.is(Flags.Package) then - report.warning("[Internal error] top-level class should have `Bottom` as outer, class = " + klass.show + ", outer = " + outer.show + ", " + Trace.show, Trace.position) + report.warning("[Internal error] top-level class should have `Package` as outer, class = " + klass.show + ", outer = " + outer.show + ", " + Trace.show, Trace.position) (Bottom, Env.NoEnv) else - (thisV.widenRefOrCold(1), Env.NoEnv) + (thisV.widenThisValue(1), Env.NoEnv) else // klass.enclosingMethod returns its primary constructor - Env.resolveEnv(klass.owner.enclosingMethod, thisV, summon[Env.Data]).getOrElse(Cold -> Env.NoEnv) + Env.resolveEnvByOwner(klass.owner.enclosingMethod, thisV, summon[Env.Data]).getOrElse(UnknownValue -> Env.NoEnv) val instance = OfClass(klass, outerWidened, ctor, args.map(_.value), envWidened) callConstructor(instance, ctor, args) @@ -992,7 +1192,9 @@ class Objects(using Context @constructorOnly): */ def readLocal(thisV: ThisValue, sym: Symbol): Contextual[Value] = log("reading local " + sym.show, printer, (_: Value).show) { def isByNameParam(sym: Symbol) = sym.is(Flags.Param) && sym.info.isInstanceOf[ExprType] - Env.resolveEnv(sym.enclosingMethod, thisV, summon[Env.Data]) match + // Can't use enclosingMethod here because values defined in a by-name closure will have the wrong enclosingMethod, + // since our phase is before elimByName. + Env.resolveEnvByValue(sym, thisV, summon[Env.Data]) match case Some(thisV -> env) => if sym.is(Flags.Mutable) then // Assume forward reference check is doing a good job @@ -1022,10 +1224,12 @@ class Objects(using Context @constructorOnly): case fun: Fun => given Env.Data = Env.ofByName(sym, fun.env) eval(fun.code, fun.thisV, fun.klass) - case Cold => - report.warning("Calling cold by-name alias. " + Trace.show, Trace.position) + case UnknownValue => + reportWarningForUnknownValue("Calling on unknown value. " + Trace.show, Trace.position) + case Top => + report.warning("Calling on value lost due to widening. " + Trace.show, Trace.position) Bottom - case _: ValueSet | _: Ref | _: OfArray => + case _: ValueSet | _: Ref | _: OfArray | _: Package | SafeValue(_) => report.warning("[Internal error] Unexpected by-name value " + value.show + ". " + Trace.show, Trace.position) Bottom else @@ -1036,7 +1240,7 @@ class Objects(using Context @constructorOnly): report.warning("Calling cold by-name alias. " + Trace.show, Trace.position) Bottom else - Cold + UnknownValue } /** Handle local variable assignmenbt, `x = e`. @@ -1047,8 +1251,9 @@ class Objects(using Context @constructorOnly): */ def writeLocal(thisV: ThisValue, sym: Symbol, value: Value): Contextual[Value] = log("write local " + sym.show + " with " + value.show, printer, (_: Value).show) { assert(sym.is(Flags.Mutable), "Writing to immutable variable " + sym.show) - - Env.resolveEnv(sym.enclosingMethod, thisV, summon[Env.Data]) match + // Can't use enclosingMethod here because values defined in a by-name closure will have the wrong enclosingMethod, + // since our phase is before elimByName. + Env.resolveEnvByValue(sym, thisV, summon[Env.Data]) match case Some(thisV -> env) => given Env.Data = env Env.getVar(sym) match @@ -1212,8 +1417,8 @@ class Objects(using Context @constructorOnly): case _: This => evalType(expr.tpe, thisV, klass) - case Literal(_) => - Bottom + case Literal(const) => + SafeValue(const.tpe) case Typed(expr, tpt) => if tpt.tpe.hasAnnotation(defn.UncheckedAnnot) then @@ -1288,7 +1493,14 @@ class Objects(using Context @constructorOnly): res case SeqLiteral(elems, elemtpt) => - evalExprs(elems, thisV, klass).join + // Obtain the output Seq from SeqLiteral tree by calling respective wrapArrayMethod + val wrapArrayMethodName = ast.tpd.wrapArrayMethodName(elemtpt.tpe) + val meth = defn.getWrapVarargsArrayModule.requiredMethod(wrapArrayMethodName) + val module = defn.getWrapVarargsArrayModule.moduleClass.asClass + val args = evalArgs(elems.map(Arg.apply), thisV, klass) + val arr = OfArray(State.currentObject, summon[Regions.Data]) + Heap.writeJoin(arr.addr, args.map(_.value).join) + call(ObjectRef(module), meth, List(ArgInfo(arr, summon[Trace], EmptyTree)), module.typeRef, NoType) case Inlined(call, bindings, expansion) => evalExprs(bindings, thisV, klass) @@ -1348,11 +1560,6 @@ class Objects(using Context @constructorOnly): def getMemberMethod(receiver: Type, name: TermName, tp: Type): Denotation = receiver.member(name).suchThat(receiver.memberInfo(_) <:< tp) - def evalCase(caseDef: CaseDef): Value = - evalPattern(scrutinee, caseDef.pat) - eval(caseDef.guard, thisV, klass) - eval(caseDef.body, thisV, klass) - /** Abstract evaluation of patterns. * * It augments the local environment for bound pattern variables. As symbols are globally @@ -1360,17 +1567,18 @@ class Objects(using Context @constructorOnly): * * Currently, we assume all cases are reachable, thus all patterns are assumed to match. */ - def evalPattern(scrutinee: Value, pat: Tree): Value = log("match " + scrutinee.show + " against " + pat.show, printer, (_: Value).show): + def evalPattern(scrutinee: Value, pat: Tree): (Type, Value) = log("match " + scrutinee.show + " against " + pat.show, printer, (_: (Type, Value))._2.show): val trace2 = Trace.trace.add(pat) pat match case Alternative(pats) => - for pat <- pats do evalPattern(scrutinee, pat) - scrutinee + val (types, values) = pats.map(evalPattern(scrutinee, _)).unzip + val orType = types.fold(defn.NothingType)(OrType(_, _, false)) + (orType, values.join) case bind @ Bind(_, pat) => - val value = evalPattern(scrutinee, pat) + val (tpe, value) = evalPattern(scrutinee, pat) initLocal(bind.symbol, value) - scrutinee + (tpe, value) case UnApply(fun, implicits, pats) => given Trace = trace2 @@ -1379,6 +1587,10 @@ class Objects(using Context @constructorOnly): val funRef = fun1.tpe.asInstanceOf[TermRef] val unapplyResTp = funRef.widen.finalResultType + val receiverType = fun1 match + case ident: Ident => funRef.prefix + case select: Select => select.qualifier.tpe + val receiver = fun1 match case ident: Ident => evalType(funRef.prefix, thisV, klass) @@ -1467,17 +1679,20 @@ class Objects(using Context @constructorOnly): end if end if end if - scrutinee + // TODO: receiverType is the companion object type, not the class itself; + // cannot filter scrutinee by this type + (receiverType, scrutinee) case Ident(nme.WILDCARD) | Ident(nme.WILDCARD_STAR) => - scrutinee + (defn.ThrowableType, scrutinee) - case Typed(pat, _) => - evalPattern(scrutinee, pat) + case Typed(pat, typeTree) => + val (_, value) = evalPattern(scrutinee.filterType(typeTree.tpe), pat) + (typeTree.tpe, value) case tree => // For all other trees, the semantics is normal. - eval(tree, thisV, klass) + (defn.ThrowableType, eval(tree, thisV, klass)) end evalPattern @@ -1488,7 +1703,7 @@ class Objects(using Context @constructorOnly): // call .lengthCompare or .length val lengthCompareDenot = getMemberMethod(scrutineeType, nme.lengthCompare, lengthCompareType) if lengthCompareDenot.exists then - call(scrutinee, lengthCompareDenot.symbol, ArgInfo(Bottom, summon[Trace], EmptyTree) :: Nil, scrutineeType, superType = NoType, needResolve = true) + call(scrutinee, lengthCompareDenot.symbol, ArgInfo(UnknownValue, summon[Trace], EmptyTree) :: Nil, scrutineeType, superType = NoType, needResolve = true) else val lengthDenot = getMemberMethod(scrutineeType, nme.length, lengthType) call(scrutinee, lengthDenot.symbol, Nil, scrutineeType, superType = NoType, needResolve = true) @@ -1496,7 +1711,7 @@ class Objects(using Context @constructorOnly): // call .apply val applyDenot = getMemberMethod(scrutineeType, nme.apply, applyType(elemType)) - val applyRes = call(scrutinee, applyDenot.symbol, ArgInfo(Bottom, summon[Trace], EmptyTree) :: Nil, scrutineeType, superType = NoType, needResolve = true) + val applyRes = call(scrutinee, applyDenot.symbol, ArgInfo(SafeValue(defn.IntType), summon[Trace], EmptyTree) :: Nil, scrutineeType, superType = NoType, needResolve = true) if isWildcardStarArgList(pats) then if pats.size == 1 then @@ -1507,7 +1722,7 @@ class Objects(using Context @constructorOnly): else // call .drop val dropDenot = getMemberMethod(scrutineeType, nme.drop, dropType(elemType)) - val dropRes = call(scrutinee, dropDenot.symbol, ArgInfo(Bottom, summon[Trace], EmptyTree) :: Nil, scrutineeType, superType = NoType, needResolve = true) + val dropRes = call(scrutinee, dropDenot.symbol, ArgInfo(SafeValue(defn.IntType), summon[Trace], EmptyTree) :: Nil, scrutineeType, superType = NoType, needResolve = true) for pat <- pats.init do evalPattern(applyRes, pat) evalPattern(dropRes, pats.last) end if @@ -1517,8 +1732,20 @@ class Objects(using Context @constructorOnly): end if end evalSeqPatterns + def canSkipCase(remainingScrutinee: Value, catchValue: Value) = + remainingScrutinee == Bottom || catchValue == Bottom - cases.map(evalCase).join + var remainingScrutinee = scrutinee + val caseResults: mutable.ArrayBuffer[Value] = mutable.ArrayBuffer() + for caseDef <- cases do + val (tpe, value) = evalPattern(remainingScrutinee, caseDef.pat) + eval(caseDef.guard, thisV, klass) + if !canSkipCase(remainingScrutinee, value) then + caseResults.addOne(eval(caseDef.body, thisV, klass)) + if catchesAllOf(caseDef, tpe) then + remainingScrutinee = remainingScrutinee.remove(value) + + caseResults.join end patternMatch /** Handle semantics of leaf nodes @@ -1535,13 +1762,13 @@ class Objects(using Context @constructorOnly): */ def evalType(tp: Type, thisV: ThisValue, klass: ClassSymbol, elideObjectAccess: Boolean = false): Contextual[Value] = log("evaluating " + tp.show, printer, (_: Value).show) { tp match - case _: ConstantType => - Bottom + case consttpe: ConstantType => + SafeValue(consttpe.underlying) case tmref: TermRef if tmref.prefix == NoPrefix => val sym = tmref.symbol if sym.is(Flags.Package) then - Bottom + Package(sym) else if sym.owner.isClass then // The typer incorrectly assigns a TermRef with NoPrefix for `config`, // while the actual denotation points to the symbol of the class member @@ -1567,7 +1794,7 @@ class Objects(using Context @constructorOnly): case tp @ ThisType(tref) => val sym = tref.symbol if sym.is(Flags.Package) then - Bottom + Package(sym) else if sym.isStaticObject && sym != klass then // The typer may use ThisType to refer to an object outside its definition. if elideObjectAccess then @@ -1735,7 +1962,7 @@ class Objects(using Context @constructorOnly): tpl.body.foreach { case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) && !vdef.rhs.isEmpty => val sym = vdef.symbol - val res = if (whiteList.contains(sym)) Bottom else eval(vdef.rhs, thisV, klass) + val res = if (allowList.contains(sym)) Bottom else eval(vdef.rhs, thisV, klass) if sym.is(Flags.Mutable) then val addr = Heap.fieldVarAddr(summon[Regions.Data], sym, State.currentObject) thisV.initVar(sym, addr) @@ -1767,7 +1994,7 @@ class Objects(using Context @constructorOnly): if target == klass then thisV else if target.is(Flags.Package) then - Bottom + Package(target) // TODO: What is the semantics for package.this? else if target.isStaticObject then val res = ObjectRef(target.moduleClass.asClass) if elideObjectAccess then res @@ -1775,7 +2002,8 @@ class Objects(using Context @constructorOnly): else thisV match case Bottom => Bottom - case Cold => Cold + case UnknownValue => UnknownValue + case Top => Top case ref: Ref => val outerCls = klass.owner.lexicallyEnclosingClass.asClass if !ref.hasOuter(klass) then @@ -1786,7 +2014,7 @@ class Objects(using Context @constructorOnly): resolveThis(target, ref.outerValue(klass), outerCls) case ValueSet(values) => values.map(ref => resolveThis(target, ref, klass)).join - case _: Fun | _ : OfArray => + case _: Fun | _ : OfArray | _: Package | SafeValue(_) => report.warning("[Internal error] unexpected thisV = " + thisV + ", target = " + target.show + ", klass = " + klass.show + Trace.show, Trace.position) Bottom } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 85b2764ff0f3..adb2370bb1e0 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -378,7 +378,7 @@ object Semantic: // ----- Checker State ----------------------------------- /** The state that threads through the interpreter */ - type Contextual[T] = (Context, Trace, Promoted, Cache.Data, Reporter) ?=> T + type Contextual[T] = (Context, Trace, Promoted, Cache.Data, Reporter, TreeCache.CacheData) ?=> T // ----- Error Handling ----------------------------------- @@ -443,6 +443,29 @@ object Semantic: inline def reporter(using r: Reporter): Reporter = r +// ----- Cache for Trees ----------------------------- + + object TreeCache: + class CacheData: + private val emptyTrees = mutable.Set[ValOrDefDef]() + + extension (tree: ValOrDefDef) + def getRhs(using Context): Tree = + def getTree: Tree = + val errorCount = ctx.reporter.errorCount + val rhs = tree.rhs + + if (ctx.reporter.errorCount > errorCount) + emptyTrees.add(tree) + report.warning("Ignoring analyses of " + tree.name + " due to error in reading TASTy.") + EmptyTree + else + rhs + + if (emptyTrees.contains(tree)) EmptyTree + else getTree + end TreeCache + // ----- Operations on domains ----------------------------- extension (a: Value) def join(b: Value): Value = @@ -576,7 +599,7 @@ object Semantic: case ref: Ref => val target = if needResolve then resolve(ref.klass, field) else field if target.is(Flags.Lazy) then - val rhs = target.defTree.asInstanceOf[ValDef].rhs + val rhs = target.defTree.asInstanceOf[ValDef].getRhs eval(rhs, ref, target.owner.asClass, cacheResult = true) else if target.exists then val obj = ref.objekt @@ -591,7 +614,7 @@ object Semantic: // return `Hot` here, errors are reported in checking `ThisRef` Hot else if target.hasSource then - val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs + val rhs = target.defTree.asInstanceOf[ValOrDefDef].getRhs eval(rhs, ref, target.owner.asClass, cacheResult = true) else val error = CallUnknown(field)(trace) @@ -715,7 +738,7 @@ object Semantic: else reporter.reportAll(tryReporter.errors) extendTrace(ddef) { - eval(ddef.rhs, ref, cls, cacheResult = true) + eval(ddef.getRhs, ref, cls, cacheResult = true) } else if ref.canIgnoreMethodCall(target) then Hot @@ -777,7 +800,7 @@ object Semantic: val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] extendTrace(cls.defTree) { init(tpl, ref, cls) } else - val initCall = ddef.rhs match + val initCall = ddef.getRhs match case Block(call :: _, _) => call case call => call extendTrace(ddef) { eval(initCall, ref, cls) } @@ -796,7 +819,7 @@ object Semantic: extendTrace(cls.defTree) { eval(tpl, ref, cls, cacheResult = true) } ref else - extendTrace(ddef) { eval(ddef.rhs, ref, cls, cacheResult = true) } + extendTrace(ddef) { eval(ddef.getRhs, ref, cls, cacheResult = true) } else if ref.canIgnoreMethodCall(ctor) then Hot else @@ -906,8 +929,7 @@ object Semantic: case Cold => Cold - case ref: Ref => eval(vdef.rhs, ref, enclosingClass, cacheResult = sym.is(Flags.Lazy)) - + case ref: Ref => eval(vdef.getRhs, ref, enclosingClass, cacheResult = sym.is(Flags.Lazy)) case _ => report.warning("[Internal error] unexpected this value when accessing local variable, sym = " + sym.show + ", thisValue = " + thisValue2.show + Trace.show, Trace.position) Hot @@ -1114,7 +1136,7 @@ object Semantic: * * The class to be checked must be an instantiable concrete class. */ - private def checkClass(classSym: ClassSymbol)(using Cache.Data, Context): Unit = + private def checkClass(classSym: ClassSymbol)(using Cache.Data, Context, TreeCache.CacheData): Unit = val thisRef = ThisRef(classSym) val tpl = classSym.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] @@ -1149,6 +1171,7 @@ object Semantic: */ def checkClasses(classes: List[ClassSymbol])(using Context): Unit = given Cache.Data() + given TreeCache.CacheData() for classSym <- classes if isConcreteClass(classSym) && !classSym.isStaticObject do checkClass(classSym) @@ -1320,10 +1343,10 @@ object Semantic: } case closureDef(ddef) => - Fun(ddef.rhs, thisV, klass) + Fun(ddef.getRhs, thisV, klass) case PolyFun(ddef) => - Fun(ddef.rhs, thisV, klass) + Fun(ddef.getRhs, thisV, klass) case Block(stats, expr) => eval(stats, thisV, klass) @@ -1375,7 +1398,7 @@ object Semantic: case vdef : ValDef => // local val definition - eval(vdef.rhs, thisV, klass) + eval(vdef.getRhs, thisV, klass) case ddef : DefDef => // local method @@ -1593,8 +1616,8 @@ object Semantic: // class body if thisV.isThisRef || !thisV.asInstanceOf[Warm].isPopulatingParams then tpl.body.foreach { - case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) && !vdef.rhs.isEmpty => - val res = eval(vdef.rhs, thisV, klass) + case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) && !vdef.getRhs.isEmpty => + val res = eval(vdef.getRhs, thisV, klass) // TODO: Improve promotion to avoid handling enum initialization specially // // The failing case is tests/init/pos/i12544.scala due to promotion failure. diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/DropForMap.scala b/compiler/src/dotty/tools/dotc/transform/localopt/DropForMap.scala new file mode 100644 index 000000000000..f7594f041204 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/DropForMap.scala @@ -0,0 +1,54 @@ +package dotty.tools.dotc +package transform.localopt + +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.core.Decorators.* +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.StdNames.* +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.transform.MegaPhase.MiniPhase +import dotty.tools.dotc.ast.desugar + +/** Drop unused trailing map calls in for comprehensions. + * We can drop the map call if: + * - it won't change the type of the expression, and + * - the function is an identity function or a const function to unit. + * + * The latter condition is checked in [[Desugar.scala#makeFor]] + */ +class DropForMap extends MiniPhase: + import DropForMap.* + + override def phaseName: String = DropForMap.name + + override def description: String = DropForMap.description + + override def transformApply(tree: Apply)(using Context): Tree = + if !tree.hasAttachment(desugar.TrailingForMap) then tree + else tree match + case aply @ Apply(MapCall(f), List(Lambda(List(param), body))) + if f.tpe =:= aply.tpe => // make sure that the type of the expression won't change + f // drop the map call + case _ => + tree.removeAttachment(desugar.TrailingForMap) + tree + + private object Lambda: + def unapply(tree: Tree)(using Context): Option[(List[ValDef], Tree)] = + tree match + case Block(List(defdef: DefDef), Closure(Nil, ref, _)) + if ref.symbol == defdef.symbol && !defdef.paramss.exists(_.forall(_.isType)) => + Some((defdef.termParamss.flatten, defdef.rhs)) + case _ => None + + private object MapCall: + def unapply(tree: Tree)(using Context): Option[Tree] = tree match + case Select(f, nme.map) => Some(f) + case Apply(fn, _) => unapply(fn) + case TypeApply(fn, _) => unapply(fn) + case _ => None + +object DropForMap: + val name: String = "dropForMap" + val description: String = "Drop unused trailing map calls in for comprehensions" diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala b/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala index 9e40792895c0..4922024b6c35 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala @@ -3,10 +3,8 @@ package transform.localopt import scala.annotation.tailrec import scala.collection.mutable.ListBuffer -import scala.util.chaining.* import scala.util.matching.Regex.Match - import PartialFunction.cond import dotty.tools.dotc.ast.tpd.{Match => _, *} @@ -15,6 +13,7 @@ import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.core.Phases.typerPhase import dotty.tools.dotc.util.Spans.Span +import dotty.tools.dotc.util.chaining.* /** Formatter string checker. */ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List[Tree])(using Context): diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 1ee402deded0..ab5885e6278c 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -237,7 +237,7 @@ object SpaceEngine { else a case (a @ Typ(tp1, _), Prod(tp2, fun, ss)) => // rationale: every instance of `tp1` is covered by `tp2(_)` - if isSubType(tp1, tp2) && covers(fun, tp1, ss.length) then + if isSubType(tp1.stripNamedTuple, tp2) && covers(fun, tp1, ss.length) then minus(Prod(tp1, fun, signature(fun, tp1, ss.length).map(Typ(_, false))), b) else if canDecompose(a) then minus(Or(decompose(a)), b) else a @@ -279,7 +279,7 @@ object SpaceEngine { || unappResult <:< ConstantType(Constant(true)) // only for unapply || (unapp.symbol.is(Synthetic) && unapp.symbol.owner.linkedClass.is(Case)) // scala2 compatibility || unapplySeqTypeElemTp(unappResult).exists // only for unapplySeq - || isProductMatch(unappResult, argLen) + || isProductMatch(unappResult.stripNamedTuple, argLen) || extractorMemberType(unappResult, nme.isEmpty, NoSourcePosition) <:< ConstantType(Constant(false)) || unappResult.derivesFrom(defn.NonEmptyTupleClass) || unapp.symbol == defn.TupleXXL_unapplySeq // Fixes TupleXXL.unapplySeq which returns Some but declares Option @@ -674,7 +674,7 @@ object SpaceEngine { val superType = child.typeRef.superType if typeArgs.exists(_.isBottomType) && superType.isInstanceOf[ClassInfo] then val parentClass = superType.asInstanceOf[ClassInfo].declaredParents.find(_.classSymbol == parent).get - val paramTypeMap = Map.from(parentClass.argTypes.map(_.typeSymbol).zip(typeArgs)) + val paramTypeMap = Map.from(parentClass.argInfos.map(_.typeSymbol).zip(typeArgs)) val substArgs = child.typeRef.typeParamSymbols.map(param => paramTypeMap.getOrElse(param, WildcardType)) substArgs else Nil @@ -804,8 +804,8 @@ object SpaceEngine { else tp.symbol.showName case Typ(tp, decomposed) => val cls = tp.classSymbol - if ctx.definitions.isTupleNType(tp) then - params(tp).map(_ => "_").mkString("(", ", ", ")") + if ctx.definitions.isTupleNType(tp.stripNamedTuple) then + params(tp.stripNamedTuple).map(_ => "_").mkString("(", ", ", ")") else if defn.ListType.isRef(cls) then if flattenList then "_*" else "_: List" else if (defn.ConsType.isRef(cls)) @@ -841,8 +841,6 @@ object SpaceEngine { if Nullables.unsafeNullsEnabled then self.stripNull() else self private def exhaustivityCheckable(sel: Tree)(using Context): Boolean = trace(i"exhaustivityCheckable($sel ${sel.className})") { - val seen = collection.mutable.Set.empty[Symbol] - // Possible to check everything, but be compatible with scalac by default def isCheckable(tp: Type): Boolean = trace(i"isCheckable($tp ${tp.className})"): val tpw = tp.widen.dealias.stripUnsafeNulls() @@ -856,10 +854,7 @@ object SpaceEngine { }) || tpw.isRef(defn.BooleanClass) || classSym.isAllOf(JavaEnum) || - classSym.is(Case) && { - if seen.add(classSym) then productSelectorTypes(tpw, sel.srcPos).exists(isCheckable(_)) - else true // recursive case class: return true and other members can still fail the check - } + classSym.is(Case) !sel.tpe.hasAnnotation(defn.UncheckedAnnot) && !sel.tpe.hasAnnotation(defn.RuntimeCheckedAnnot) @@ -922,6 +917,7 @@ object SpaceEngine { !sel.tpe.hasAnnotation(defn.UncheckedAnnot) && !sel.tpe.widen.isRef(defn.QuotedExprClass) && !sel.tpe.widen.isRef(defn.QuotedTypeClass) + && tpd.enclosingInlineds.isEmpty // Skip reachability on inlined code (eg i19157/i22212) def checkReachability(m: Match)(using Context): Unit = trace(i"checkReachability($m)"): val selTyp = toUnderlying(m.selector.tpe).dealias diff --git a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSExports.scala b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSExports.scala index 5aa35a277cb5..43b29a224564 100644 --- a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSExports.scala +++ b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSExports.scala @@ -21,7 +21,7 @@ import dotty.tools.backend.sjs.JSDefinitions.jsdefn import JSExportUtils.* import JSSymUtils.* -import dotty.tools.sjs.ir.Names.DefaultModuleID +import dotty.tools.sjs.ir.WellKnownNames.DefaultModuleID import dotty.tools.sjs.ir.Trees.TopLevelExportDef.isValidTopLevelExportName object PrepJSExports { diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 98bfbe69ff8c..058cd2de332c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -694,9 +694,11 @@ trait Applications extends Compatibility { sym.is(JavaDefined) && sym.isConstructor && sym.owner.is(JavaAnnotation) - /** Is `sym` a constructor of an annotation? */ - def isAnnotConstr(sym: Symbol): Boolean = - sym.isConstructor && sym.owner.isAnnotation + /** Is `sym` a constructor of an annotation class, and are we in an + * annotation? If so, we don't lift arguments. See [[Mode.InAnnotation]]. + */ + protected final def isAnnotConstr(sym: Symbol): Boolean = + ctx.mode.is(Mode.InAnnotation) && sym.isConstructor && sym.owner.isAnnotation /** Match re-ordered arguments against formal parameters * @param n The position of the first parameter in formals in `methType`. @@ -755,13 +757,14 @@ trait Applications extends Compatibility { } else defaultArgument(normalizedFun, n, testOnly) - def implicitArg = implicitArgTree(formal, appPos.span) - if !defaultArg.isEmpty then defaultArg.tpe.widen match case _: MethodOrPoly if testOnly => matchArgs(args1, formals1, n + 1) case _ => matchArgs(args1, addTyped(treeToArg(defaultArg)), n + 1) - else if methodType.isImplicitMethod && ctx.mode.is(Mode.ImplicitsEnabled) then + else if (methodType.isContextualMethod || applyKind == ApplyKind.Using && methodType.isImplicitMethod) + && ctx.mode.is(Mode.ImplicitsEnabled) + then + val implicitArg = implicitArgTree(formal, appPos.span) matchArgs(args1, addTyped(treeToArg(implicitArg)), n + 1) else missingArg(n) @@ -994,9 +997,7 @@ trait Applications extends Compatibility { case (arg: NamedArg, _) => arg case (arg, name) => NamedArg(name, arg) } - else if isAnnotConstr(methRef.symbol) then - typedArgs - else if !sameSeq(args, orderedArgs) && !typedArgs.forall(isSafeArg) then + else if !isAnnotConstr(methRef.symbol) && !sameSeq(args, orderedArgs) && !typedArgs.forall(isSafeArg) then // need to lift arguments to maintain evaluation order in the // presence of argument reorderings. // (never do this for Java annotation constructors, hence the 'else if') @@ -1198,9 +1199,8 @@ trait Applications extends Compatibility { // // summonFrom { // case given A[t] => - // summonFrom + // summonFrom: // case given `t` => ... - // } // } // // the second `summonFrom` should expand only once the first `summonFrom` is diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index e870ffd0fc90..2d6817f74ff7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -287,11 +287,16 @@ object Checking { */ def checkInfo(tp: Type): Type = tp match { case tp @ TypeAlias(alias) => - tp.derivedAlias(checkPart(alias, "alias")) + val lo1 = atVariance(-1)(checkPart(alias, "alias")) + val hi1 = checkUpper(alias, "alias") + if lo1 eq hi1 then + tp.derivedAlias(lo1) + else + tp.derivedTypeBounds(lo1, hi1) case tp @ MatchAlias(alias) => - tp.derivedAlias(checkUpper(alias, "match")) + tp.derivedAlias(atVariance(0)(checkUpper(alias, "match"))) case tp @ TypeBounds(lo, hi) => - tp.derivedTypeBounds(checkPart(lo, "lower bound"), checkUpper(hi, "upper bound")) + tp.derivedTypeBounds(atVariance(-1)(checkPart(lo, "lower bound")), checkUpper(hi, "upper bound")) case _ => tp } @@ -312,12 +317,12 @@ object Checking { case tp: TermRef => this(tp.info) mapOver(tp) - case tp @ AppliedType(tycon, args) => - tp.derivedAppliedType(this(tycon), args.mapConserve(this(_, nestedCycleOK, nestedCycleOK))) case tp @ RefinedType(parent, name, rinfo) => tp.derivedRefinedType(this(parent), name, this(rinfo, nestedCycleOK, nestedCycleOK)) case tp: RecType => tp.rebind(this(tp.parent)) + case tp: LazyRef => + tp case tp @ TypeRef(pre, _) => try { // A prefix is interesting if it might contain (transitively) a reference @@ -350,14 +355,17 @@ object Checking { if isInteresting(pre) then CyclicReference.trace(i"explore ${tp.symbol} for cyclic references"): - val pre1 = this(pre, false, false) + val pre1 = atVariance(variance max 0)(this(pre, false, false)) if locked.contains(tp) || tp.symbol.infoOrCompleter.isInstanceOf[NoCompleter] + && tp.symbol == sym then throw CyclicReference(tp.symbol) locked += tp try - if tp.symbol.isOpaqueAlias then + if tp.symbol.infoOrCompleter.isInstanceOf[NoCompleter] then + ; // skip checking info (and avoid forcing the symbol with .isOpaqueAlias/etc) + else if tp.symbol.isOpaqueAlias then checkInfo(TypeAlias(tp.translucentSuperType)) else if !tp.symbol.isClass then checkInfo(tp.info) @@ -375,6 +383,16 @@ object Checking { } case _ => mapOver(tp) } + + override def mapArg(arg: Type, tparam: ParamInfo): Type = + val varianceDiff = variance != tparam.paramVarianceSign + atVariance(variance * tparam.paramVarianceSign): + // Using tests/pos/i22257.scala as an example, + // if we consider FP's lower-bound of Fixed[Node] + // than `Node` is a type argument in contravariant + // position, while the type parameter is covariant. + val nestedCycleOK1 = nestedCycleOK || variance != 0 && varianceDiff + this(arg, nestedCycleOK, nestedCycleOK1) } /** Under -Yrequire-targetName, if `sym` has an operator name, check that it has a @@ -786,24 +804,6 @@ object Checking { else "Cannot override non-inline parameter with an inline parameter", p1.srcPos) - def checkValue(tree: Tree)(using Context): Unit = - val sym = tree.tpe.termSymbol - if sym.isNoValue && !ctx.isJava then - report.error(JavaSymbolIsNotAValue(sym), tree.srcPos) - - /** Check that `tree` refers to a value, unless `tree` is selected or applied - * (singleton types x.type don't count as selections). - */ - def checkValue(tree: Tree, proto: Type)(using Context): tree.type = - tree match - case tree: RefTree if tree.name.isTermName => - proto match - case _: SelectionProto if proto ne SingletonTypeProto => // no value check - case _: FunOrPolyProto => // no value check - case _ => checkValue(tree) - case _ => - tree - /** Check that experimental language imports in `trees` * are done only in experimental scopes. For top-level * experimental imports, all top-level definitions are transformed @@ -1036,6 +1036,8 @@ trait Checking { pats.forall(recur(_, pt)) case Typed(arg, tpt) => check(pat, pt) && recur(arg, pt) + case NamedArg(name, pat) => + recur(pat, pt) case Ident(nme.WILDCARD) => true case pat: QuotePattern => @@ -1226,6 +1228,10 @@ trait Checking { /** A hook to exclude selected symbols from double declaration check */ def excludeFromDoubleDeclCheck(sym: Symbol)(using Context): Boolean = false + def matchesSameStatic(decl: Symbol, other: Symbol)(using Context): Boolean = + def staticNonStaticPair = decl.isScalaStatic != other.isScalaStatic + decl.matches(other) && !staticNonStaticPair + /** Check that class does not declare same symbol twice */ def checkNoDoubleDeclaration(cls: Symbol)(using Context): Unit = { val seen = new mutable.HashMap[Name, List[Symbol]].withDefaultValue(Nil) @@ -1237,8 +1243,7 @@ trait Checking { def javaFieldMethodPair = decl.is(JavaDefined) && other.is(JavaDefined) && decl.is(Method) != other.is(Method) - def staticNonStaticPair = decl.isScalaStatic != other.isScalaStatic - if (decl.matches(other) && !javaFieldMethodPair && !staticNonStaticPair) { + if (matchesSameStatic(decl, other) && !javaFieldMethodPair) { def doubleDefError(decl: Symbol, other: Symbol): Unit = if (!decl.info.isErroneous && !other.info.isErroneous) report.error(DoubleDefinition(decl, other, cls), decl.srcPos) @@ -1420,6 +1425,10 @@ trait Checking { case Literal(_) => // ok case _ => report.error(em"@${cls.name} needs a string literal as argument", arg.srcPos) + case Apply(tycon, arg :: Nil) if cls == defn.ImplicitNotFoundAnnot || cls == defn.ImplicitAmbiguousAnnot => + arg.tpe.widenTermRefExpr.normalized match + case _: ConstantType => () + case _ => report.error(em"@${cls.name} requires constant expressions as a parameter", arg.srcPos) case _ => tree diff --git a/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala b/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala index 6020431672b9..f0d4d617bb74 100644 --- a/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala @@ -81,18 +81,37 @@ class CrossVersionChecks extends MiniPhase: report.deprecationWarning(em"inheritance from $psym is deprecated$since$msg", parent.srcPos, origin=psym.showFullName) } + private def unrollError(pos: SrcPos)(using Context): Unit = + report.error(IllegalUnrollPlacement(None), pos) + + private def checkUnrollAnnot(annotSym: Symbol, pos: SrcPos)(using Context): Unit = + if annotSym == defn.UnrollAnnot then + unrollError(pos) + + private def checkUnrollMemberDef(memberDef: MemberDef)(using Context): Unit = + val sym = memberDef.symbol + if + sym.hasAnnotation(defn.UnrollAnnot) + && !(sym.isTerm && sym.is(Param)) + then + val normSym = if sym.is(ModuleVal) then sym.moduleClass else sym + unrollError(normSym.srcPos) + override def transformValDef(tree: ValDef)(using Context): ValDef = + checkUnrollMemberDef(tree) checkDeprecatedOvers(tree) checkExperimentalAnnots(tree.symbol) tree override def transformDefDef(tree: DefDef)(using Context): DefDef = + checkUnrollMemberDef(tree) checkDeprecatedOvers(tree) checkExperimentalAnnots(tree.symbol) tree override def transformTypeDef(tree: TypeDef)(using Context): TypeDef = // TODO do we need to check checkDeprecatedOvers(tree)? + checkUnrollMemberDef(tree) checkExperimentalAnnots(tree.symbol) tree @@ -122,10 +141,14 @@ class CrossVersionChecks extends MiniPhase: if tree.span.isSourceDerived then checkDeprecatedRef(sym, tree.srcPos) checkExperimentalRef(sym, tree.srcPos) + checkPreviewFeatureRef(sym, tree.srcPos) case TermRef(_, sym: Symbol) => if tree.span.isSourceDerived then checkDeprecatedRef(sym, tree.srcPos) checkExperimentalRef(sym, tree.srcPos) + checkPreviewFeatureRef(sym, tree.srcPos) + case AnnotatedType(_, annot) => + checkUnrollAnnot(annot.symbol, tree.srcPos) case _ => } tree @@ -140,7 +163,11 @@ class CrossVersionChecks extends MiniPhase: case tree: TypeTree => transformTypeTree(tree) case _ => } - tree + tree match + case Annotated(_, annot) => + checkUnrollAnnot(annot.tpe.typeSymbol, tree.srcPos) + tree + case tree => tree end CrossVersionChecks @@ -149,11 +176,12 @@ object CrossVersionChecks: val description: String = "check issues related to deprecated and experimental" /** Check that a reference to an experimental definition with symbol `sym` meets cross-version constraints - * for `@deprecated` and `@experimental`. + * for `@deprecated`, `@experimental` and `@preview`. */ def checkRef(sym: Symbol, pos: SrcPos)(using Context): Unit = checkDeprecatedRef(sym, pos) checkExperimentalRef(sym, pos) + checkPreviewFeatureRef(sym, pos) /** Check that a reference to an experimental definition with symbol `sym` is only * used in an experimental scope @@ -162,6 +190,13 @@ object CrossVersionChecks: if sym.isExperimental && !ctx.owner.isInExperimentalScope then Feature.checkExperimentalDef(sym, pos) + /** Check that a reference to a preview definition with symbol `sym` is only + * used in a preview mode. + */ + private[CrossVersionChecks] def checkPreviewFeatureRef(sym: Symbol, pos: SrcPos)(using Context): Unit = + if sym.isPreview && !ctx.owner.isInPreviewScope then + Feature.checkPreviewDef(sym, pos) + /** If @deprecated is present, and the point of reference is not enclosed * in either a deprecated member or a scala bridge method, issue a warning. * diff --git a/compiler/src/dotty/tools/dotc/typer/Deriving.scala b/compiler/src/dotty/tools/dotc/typer/Deriving.scala index 60148319a61c..ef77adf18626 100644 --- a/compiler/src/dotty/tools/dotc/typer/Deriving.scala +++ b/compiler/src/dotty/tools/dotc/typer/Deriving.scala @@ -10,7 +10,7 @@ import Contexts.*, Symbols.*, Types.*, SymDenotations.*, Names.*, NameOps.*, Fla import ProtoTypes.*, ContextOps.* import util.Spans.* import util.SrcPos -import collection.mutable +import collection.mutable.ListBuffer import ErrorReporting.errorTree /** A typer mixin that implements type class derivation functionality */ @@ -25,8 +25,8 @@ trait Deriving { */ class Deriver(cls: ClassSymbol, codePos: SrcPos)(using Context) { - /** A buffer for synthesized symbols for type class instances */ - private var synthetics = new mutable.ListBuffer[Symbol] + /** A buffer for synthesized symbols for type class instances, with what user asked to synthesize. */ + private val synthetics = ListBuffer.empty[(tpd.Tree, Symbol)] /** A version of Type#underlyingClassRef that works also for higher-kinded types */ private def underlyingClassRef(tp: Type): Type = tp match { @@ -41,7 +41,7 @@ trait Deriving { * an instance with the same name does not exist already. * @param reportErrors Report an error if an instance with the same name exists already */ - private def addDerivedInstance(clsName: Name, info: Type, pos: SrcPos): Unit = { + private def addDerivedInstance(derived: tpd.Tree, clsName: Name, info: Type, pos: SrcPos): Unit = { val instanceName = "derived$".concat(clsName) if (ctx.denotNamed(instanceName).exists) report.error(em"duplicate type class derivation for $clsName", pos) @@ -50,9 +50,8 @@ trait Deriving { // derived instance will have too low a priority to be selected over a freshly // derived instance at the summoning site. val flags = if info.isInstanceOf[MethodOrPoly] then GivenMethod else Given | Lazy - synthetics += - newSymbol(ctx.owner, instanceName, flags, info, coord = pos.span) - .entered + val sym = newSymbol(ctx.owner, instanceName, flags, info, coord = pos.span).entered + synthetics += derived -> sym } /** Check derived type tree `derived` for the following well-formedness conditions: @@ -77,7 +76,8 @@ trait Deriving { * that have the same name but different prefixes through selective aliasing. */ private def processDerivedInstance(derived: untpd.Tree): Unit = { - val originalTypeClassType = typedAheadType(derived, AnyTypeConstructorProto).tpe + val originalTypeClassTree = typedAheadType(derived, AnyTypeConstructorProto) + val originalTypeClassType = originalTypeClassTree.tpe val underlyingClassType = underlyingClassRef(originalTypeClassType) val typeClassType = checkClassType( underlyingClassType.orElse(originalTypeClassType), @@ -100,7 +100,7 @@ trait Deriving { val derivedInfo = if derivedParams.isEmpty then monoInfo else PolyType.fromParams(derivedParams, monoInfo) - addDerivedInstance(originalTypeClassType.typeSymbol.name, derivedInfo, derived.srcPos) + addDerivedInstance(originalTypeClassTree, originalTypeClassType.typeSymbol.name, derivedInfo, derived.srcPos) } def deriveSingleParameter: Unit = { @@ -312,7 +312,7 @@ trait Deriving { else tpd.ValDef(sym.asTerm, typeclassInstance(sym)(Nil)) } - synthetics.map(syntheticDef).toList + synthetics.map((t, s) => syntheticDef(s).withAttachment(Deriving.OriginalTypeClass, t)).toList } def finalize(stat: tpd.TypeDef): tpd.Tree = { @@ -321,3 +321,8 @@ trait Deriving { } } } +object Deriving: + import dotty.tools.dotc.util.Property + + /** Attachment holding the name of a type class as written by the user. */ + val OriginalTypeClass = Property.StickyKey[tpd.Tree] diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 9d273ebca866..25fed4e62de9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -570,6 +570,8 @@ object Implicits: i""" |Note that implicit $what cannot be applied because they are ambiguous; |$explanation""" :: Nil + + def asNested = if nested then this else AmbiguousImplicits(alt1, alt2, expectedType, argument, nested = true) end AmbiguousImplicits class MismatchedImplicit(ref: TermRef, @@ -928,8 +930,8 @@ trait Implicits: /** Find an implicit argument for parameter `formal`. * Return a failure as a SearchFailureType in the type of the returned tree. */ - def inferImplicitArg(formal: Type, span: Span)(using Context): Tree = - inferImplicit(formal, EmptyTree, span) match + def inferImplicitArg(formal: Type, span: Span, ignored: Set[Symbol] = Set.empty)(using Context): Tree = + inferImplicit(formal, EmptyTree, span, ignored) match case SearchSuccess(arg, _, _, _) => arg case fail @ SearchFailure(failed) => if fail.isAmbiguous then failed @@ -1082,7 +1084,7 @@ trait Implicits: * it should be applied, EmptyTree otherwise. * @param span The position where errors should be reported. */ - def inferImplicit(pt: Type, argument: Tree, span: Span)(using Context): SearchResult = ctx.profiler.onImplicitSearch(pt): + def inferImplicit(pt: Type, argument: Tree, span: Span, ignored: Set[Symbol] = Set.empty)(using Context): SearchResult = ctx.profiler.onImplicitSearch(pt): trace(s"search implicit ${pt.show}, arg = ${argument.show}: ${argument.tpe.show}", implicits, show = true) { record("inferImplicit") assert(ctx.phase.allowsImplicitSearch, @@ -1110,7 +1112,7 @@ trait Implicits: else i"conversion from ${argument.tpe} to $pt" CyclicReference.trace(i"searching for an implicit $searchStr"): - try ImplicitSearch(pt, argument, span)(using searchCtx).bestImplicit + try ImplicitSearch(pt, argument, span, ignored)(using searchCtx).bestImplicit catch case ce: CyclicReference => ce.inImplicitSearch = true throw ce @@ -1130,9 +1132,9 @@ trait Implicits: result case result: SearchFailure if result.isAmbiguous => val deepPt = pt.deepenProto - if (deepPt ne pt) inferImplicit(deepPt, argument, span) + if (deepPt ne pt) inferImplicit(deepPt, argument, span, ignored) else if (migrateTo3 && !ctx.mode.is(Mode.OldImplicitResolution)) - withMode(Mode.OldImplicitResolution)(inferImplicit(pt, argument, span)) match { + withMode(Mode.OldImplicitResolution)(inferImplicit(pt, argument, span, ignored)) match { case altResult: SearchSuccess => report.migrationWarning( result.reason.msg @@ -1243,7 +1245,7 @@ trait Implicits: } /** An implicit search; parameters as in `inferImplicit` */ - class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span)(using Context): + class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span, ignored: Set[Symbol])(using Context): assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType], em"found: $argument: ${argument.tpe}, expected: $pt") @@ -1383,30 +1385,31 @@ trait Implicits: 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(searchContext().withTyperState(comparisonState)): - compare(ref1, ref2, preferGeneral = true) + def stripExtension(alt: SearchSuccess) = + methPart(stripApply(alt.tree)).tpe: @unchecked match { case ref: TermRef => ref } + val ref1 = stripExtension(alt1) + val ref2 = stripExtension(alt2) + // 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(searchContext().withTyperState(comparisonState)): + compare(ref1, ref2, preferGeneral = true) else // alt1 is a conversion, prefer extension alt2 over it diff = -1 if diff < 0 then alt2 @@ -1670,7 +1673,7 @@ trait Implicits: SearchFailure(TooUnspecific(pt), span) else val contextual = ctxImplicits != null - val preEligible = // the eligible candidates, ignoring positions + var preEligible = // the eligible candidates, ignoring positions if ctxImplicits != null then if ctx.gadt.isNarrowing then withoutMode(Mode.ImplicitsEnabled) { @@ -1678,6 +1681,9 @@ trait Implicits: } else ctxImplicits.eligible(wildProto) else implicitScope(wildProto).eligible + if !ignored.isEmpty then + preEligible = + preEligible.filter(candidate => !ignored.contains(candidate.implicitRef.underlyingRef.symbol)) /** Does candidate `cand` come too late for it to be considered as an * eligible candidate? This is the case if `cand` appears in the same diff --git a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala index 3ae533d58b2e..98fbede5f5ba 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -69,7 +69,7 @@ trait ImportSuggestions: && !(root.name == nme.raw.BAR && ctx.settings.scalajs.value && root == JSDefinitions.jsdefn.PseudoUnionModule) } - def nestedRoots(site: Type)(using Context): List[Symbol] = + def nestedRoots(site: Type, parentSymbols: Set[Symbol])(using Context): List[Symbol] = val seenNames = mutable.Set[Name]() site.baseClasses.flatMap { bc => bc.info.decls.filter { dcl => @@ -79,34 +79,37 @@ trait ImportSuggestions: } } - def rootsStrictlyIn(ref: Type)(using Context): List[TermRef] = + def rootsStrictlyIn(ref: Type, parentSymbols: Set[Symbol] = Set())(using Context): List[TermRef] = val site = ref.widen val refSym = site.typeSymbol - val nested = - if refSym.is(Package) then - if refSym == defn.EmptyPackageClass // Don't search the empty package - || refSym == defn.JavaPackageClass // As an optimization, don't search java... - || refSym == defn.JavaLangPackageClass // ... or java.lang. - then Nil - else refSym.info.decls.filter(lookInside) - else if refSym.infoOrCompleter.isInstanceOf[StubInfo] then - Nil // Don't chase roots that do not exist - else - if !refSym.is(Touched) then - refSym.ensureCompleted() // JavaDefined is reliably known only after completion - if refSym.is(JavaDefined) then Nil - else nestedRoots(site) - nested - .map(mbr => TermRef(ref, mbr.asTerm)) - .flatMap(rootsIn) - .toList + if parentSymbols.contains(refSym) then Nil + else + val nested = + if refSym.is(Package) then + if refSym == defn.EmptyPackageClass // Don't search the empty package + || refSym == defn.JavaPackageClass // As an optimization, don't search java... + || refSym == defn.JavaLangPackageClass // ... or java.lang. + then Nil + else refSym.info.decls.filter(lookInside) + else if refSym.infoOrCompleter.isInstanceOf[StubInfo] then + Nil // Don't chase roots that do not exist + else + if !refSym.is(Touched) then + refSym.ensureCompleted() // JavaDefined is reliably known only after completion + if refSym.is(JavaDefined) then Nil + else nestedRoots(site, parentSymbols) + val newParentSymbols = parentSymbols + refSym + nested + .map(mbr => TermRef(ref, mbr.asTerm)) + .flatMap(rootsIn(_, newParentSymbols)) + .toList - def rootsIn(ref: TermRef)(using Context): List[TermRef] = + def rootsIn(ref: TermRef, parentSymbols: Set[Symbol] = Set())(using Context): List[TermRef] = if seen.contains(ref) then Nil else implicitsDetailed.println(i"search for suggestions in ${ref.symbol.fullName}") seen += ref - ref :: rootsStrictlyIn(ref) + ref :: rootsStrictlyIn(ref, parentSymbols) def rootsOnPath(tp: Type)(using Context): List[TermRef] = tp match case ref: TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 2ebcd96d5bde..7f040ccd2968 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -671,7 +671,6 @@ trait Inferencing { this: Typer => // This is needed because it could establish singleton type upper bounds. See i2998.scala. val tp = tree.tpe.widen - val vs = variances(tp, pt) // Avoid interpolating variables occurring in tree's type if typerstate has unreported errors. // Reason: The errors might reflect unsatisfiable constraints. In that @@ -695,135 +694,138 @@ trait Inferencing { this: Typer => // val y: List[List[String]] = List(List(1)) if state.reporter.hasUnreportedErrors then return tree - def constraint = state.constraint - - trace(i"interpolateTypeVars($tree: ${tree.tpe}, $pt, $qualifying)", typr, (_: Any) => i"$qualifying\n$constraint\n${ctx.gadt}") { - //println(i"$constraint") - //println(i"${ctx.gadt}") - - /** Values of this type report type variables to instantiate with variance indication: - * +1 variable appears covariantly, can be instantiated from lower bound - * -1 variable appears contravariantly, can be instantiated from upper bound - * 0 variable does not appear at all, can be instantiated from either bound - */ - type ToInstantiate = List[(TypeVar, Int)] - - val toInstantiate: ToInstantiate = - val buf = new mutable.ListBuffer[(TypeVar, Int)] - for tvar <- qualifying do - if !tvar.isInstantiated && constraint.contains(tvar) && tvar.nestingLevel >= ctx.nestingLevel then - constrainIfDependentParamRef(tvar, tree) - if !tvar.isInstantiated then - // isInstantiated needs to be checked again, since previous interpolations could already have - // instantiated `tvar` through unification. - val v = vs.computedVariance(tvar) - if v == null then buf += ((tvar, 0)) - else if v.intValue != 0 then buf += ((tvar, v.intValue)) - else comparing(cmp => - if !cmp.levelOK(tvar.nestingLevel, ctx.nestingLevel) then - // Invariant: The type of a tree whose enclosing scope is level - // N only contains type variables of level <= N. - typr.println(i"instantiate nonvariant $tvar of level ${tvar.nestingLevel} to a type variable of level <= ${ctx.nestingLevel}, $constraint") - cmp.atLevel(ctx.nestingLevel, tvar.origin) - else - typr.println(i"no interpolation for nonvariant $tvar in $state") - ) - // constrainIfDependentParamRef could also have instantiated tvars added to buf before the check - buf.filterNot(_._1.isInstantiated).toList - end toInstantiate - - def typeVarsIn(xs: ToInstantiate): TypeVars = - xs.foldLeft(SimpleIdentitySet.empty: TypeVars)((tvs, tvi) => tvs + tvi._1) - - /** Filter list of proposed instantiations so that they don't constrain further - * the current constraint. - */ - def filterByDeps(tvs0: ToInstantiate): ToInstantiate = - val excluded = // ignore dependencies from other variables that are being instantiated - typeVarsIn(tvs0) - def step(tvs: ToInstantiate): ToInstantiate = tvs match - case tvs @ (hd @ (tvar, v)) :: tvs1 => - def aboveOK = !constraint.dependsOn(tvar, excluded, co = true) - def belowOK = !constraint.dependsOn(tvar, excluded, co = false) - if v == 0 && !aboveOK then - step((tvar, 1) :: tvs1) - else if v == 0 && !belowOK then - step((tvar, -1) :: tvs1) - else if v == -1 && !aboveOK || v == 1 && !belowOK then - typr.println(i"drop $tvar, $v in $tp, $pt, qualifying = ${qualifying.toList}, tvs0 = ${tvs0.toList}%, %, excluded = ${excluded.toList}, $constraint") - step(tvs1) - else // no conflict, keep the instantiation proposal - tvs.derivedCons(hd, step(tvs1)) - case Nil => - Nil - val tvs1 = step(tvs0) - if tvs1 eq tvs0 then tvs1 - else filterByDeps(tvs1) // filter again with smaller excluded set - end filterByDeps - - /** Instantiate all type variables in `tvs` in the indicated directions, - * as described in the doc comment of `ToInstantiate`. - * If a type variable A is instantiated from below, and there is another - * type variable B in `buf` that is known to be smaller than A, wait and - * instantiate all other type variables before trying to instantiate A again. - * Dually, wait instantiating a type variable from above as long as it has - * upper bounds in `buf`. - * - * This is done to avoid loss of precision when forming unions. An example - * is in i7558.scala: - * - * type Tr[+V1, +O1 <: V1] - * extension [V2, O2 <: V2](tr: Tr[V2, O2]) def sl: Tr[V2, O2] = ??? - * def as[V3, O3 <: V3](tr: Tr[V3, O3]) : Tr[V3, O3] = tr.sl - * - * Here we interpolate at some point V2 and O2 given the constraint - * - * V2 >: V3, O2 >: O3, O2 <: V2 - * - * where O3 and V3 are type refs with O3 <: V3. - * If we interpolate V2 first to V3 | O2, the widenUnion algorithm will - * instantiate O2 to V3, leading to the final constraint - * - * V2 := V3, O2 := V3 - * - * But if we instantiate O2 first to O3, and V2 next to V3, we get the - * more flexible instantiation - * - * V2 := V3, O2 := O3 - */ - def doInstantiate(tvs: ToInstantiate): Unit = - - /** Try to instantiate `tvs`, return any suspended type variables */ - def tryInstantiate(tvs: ToInstantiate): ToInstantiate = tvs match - case (hd @ (tvar, v)) :: tvs1 => - val fromBelow = v == 1 || (v == 0 && tvar.hasLowerBound) - typr.println( - i"interpolate${if v == 0 then " non-occurring" else ""} $tvar in $state in $tree: $tp, fromBelow = $fromBelow, $constraint") - if tvar.isInstantiated then - tryInstantiate(tvs1) - else - val suspend = tvs1.exists{ (following, _) => - if fromBelow - then constraint.isLess(following.origin, tvar.origin) - else constraint.isLess(tvar.origin, following.origin) - } - if suspend then - typr.println(i"suspended: $hd") - hd :: tryInstantiate(tvs1) - else - tvar.instantiate(fromBelow) - tryInstantiate(tvs1) - case Nil => Nil - if tvs.nonEmpty then doInstantiate(tryInstantiate(tvs)) - end doInstantiate - - doInstantiate(filterByDeps(toInstantiate)) - } + instantiateTypeVars(tp, pt, qualifying, tree) } end if tree end interpolateTypeVars + def instantiateTypeVars(tp: Type, pt: Type, qualifying: List[TypeVar], tree: Tree = EmptyTree)(using Context): Unit = + trace(i"instantiateTypeVars($tp, $pt, $qualifying, $tree)", typr): + val state = ctx.typerState + def constraint = state.constraint + + val vs = variances(tp, pt) + + /** Values of this type report type variables to instantiate with variance indication: + * +1 variable appears covariantly, can be instantiated from lower bound + * -1 variable appears contravariantly, can be instantiated from upper bound + * 0 variable does not appear at all, can be instantiated from either bound + */ + type ToInstantiate = List[(TypeVar, Int)] + + val toInstantiate: ToInstantiate = + val buf = new mutable.ListBuffer[(TypeVar, Int)] + for tvar <- qualifying do + if !tvar.isInstantiated && constraint.contains(tvar) && tvar.nestingLevel >= ctx.nestingLevel then + constrainIfDependentParamRef(tvar, tree) + if !tvar.isInstantiated then + // isInstantiated needs to be checked again, since previous interpolations could already have + // instantiated `tvar` through unification. + val v = vs.computedVariance(tvar) + if v == null then buf += ((tvar, 0)) + else if v.intValue != 0 then buf += ((tvar, v.intValue)) + else comparing(cmp => + if !cmp.levelOK(tvar.nestingLevel, ctx.nestingLevel) then + // Invariant: The type of a tree whose enclosing scope is level + // N only contains type variables of level <= N. + typr.println(i"instantiate nonvariant $tvar of level ${tvar.nestingLevel} to a type variable of level <= ${ctx.nestingLevel}, $constraint") + cmp.atLevel(ctx.nestingLevel, tvar.origin) + else + typr.println(i"no interpolation for nonvariant $tvar in $state") + ) + // constrainIfDependentParamRef could also have instantiated tvars added to buf before the check + buf.filterNot(_._1.isInstantiated).toList + end toInstantiate + + def typeVarsIn(xs: ToInstantiate): TypeVars = + xs.foldLeft(SimpleIdentitySet.empty: TypeVars)((tvs, tvi) => tvs + tvi._1) + + /** Filter list of proposed instantiations so that they don't constrain further + * the current constraint. + */ + def filterByDeps(tvs0: ToInstantiate): ToInstantiate = + val excluded = // ignore dependencies from other variables that are being instantiated + typeVarsIn(tvs0) + def step(tvs: ToInstantiate): ToInstantiate = tvs match + case tvs @ (hd @ (tvar, v)) :: tvs1 => + def aboveOK = !constraint.dependsOn(tvar, excluded, co = true) + def belowOK = !constraint.dependsOn(tvar, excluded, co = false) + if v == 0 && !aboveOK then + step((tvar, 1) :: tvs1) + else if v == 0 && !belowOK then + step((tvar, -1) :: tvs1) + else if v == -1 && !aboveOK || v == 1 && !belowOK then + typr.println(i"drop $tvar, $v in $tp, $pt, qualifying = ${qualifying.toList}, tvs0 = ${tvs0.toList}%, %, excluded = ${excluded.toList}, $constraint") + step(tvs1) + else // no conflict, keep the instantiation proposal + tvs.derivedCons(hd, step(tvs1)) + case Nil => + Nil + val tvs1 = step(tvs0) + if tvs1 eq tvs0 then tvs1 + else filterByDeps(tvs1) // filter again with smaller excluded set + end filterByDeps + + /** Instantiate all type variables in `tvs` in the indicated directions, + * as described in the doc comment of `ToInstantiate`. + * If a type variable A is instantiated from below, and there is another + * type variable B in `buf` that is known to be smaller than A, wait and + * instantiate all other type variables before trying to instantiate A again. + * Dually, wait instantiating a type variable from above as long as it has + * upper bounds in `buf`. + * + * This is done to avoid loss of precision when forming unions. An example + * is in i7558.scala: + * + * type Tr[+V1, +O1 <: V1] + * extension [V2, O2 <: V2](tr: Tr[V2, O2]) def sl: Tr[V2, O2] = ??? + * def as[V3, O3 <: V3](tr: Tr[V3, O3]) : Tr[V3, O3] = tr.sl + * + * Here we interpolate at some point V2 and O2 given the constraint + * + * V2 >: V3, O2 >: O3, O2 <: V2 + * + * where O3 and V3 are type refs with O3 <: V3. + * If we interpolate V2 first to V3 | O2, the widenUnion algorithm will + * instantiate O2 to V3, leading to the final constraint + * + * V2 := V3, O2 := V3 + * + * But if we instantiate O2 first to O3, and V2 next to V3, we get the + * more flexible instantiation + * + * V2 := V3, O2 := O3 + */ + def doInstantiate(tvs: ToInstantiate): Unit = + + /** Try to instantiate `tvs`, return any suspended type variables */ + def tryInstantiate(tvs: ToInstantiate): ToInstantiate = tvs match + case (hd @ (tvar, v)) :: tvs1 => + val fromBelow = v == 1 || (v == 0 && tvar.hasLowerBound) + typr.println( + i"interpolate${if v == 0 then " non-occurring" else ""} $tvar in $state in $tree: $tp, fromBelow = $fromBelow, $constraint") + if tvar.isInstantiated then + tryInstantiate(tvs1) + else + val suspend = tvs1.exists{ (following, _) => + if fromBelow + then constraint.isLess(following.origin, tvar.origin) + else constraint.isLess(tvar.origin, following.origin) + } + if suspend then + typr.println(i"suspended: $hd") + hd :: tryInstantiate(tvs1) + else + tvar.instantiate(fromBelow) + tryInstantiate(tvs1) + case Nil => Nil + if tvs.nonEmpty then doInstantiate(tryInstantiate(tvs)) + end doInstantiate + + doInstantiate(filterByDeps(toInstantiate)) + end instantiateTypeVars + /** If `tvar` represents a parameter of a dependent method type in the current `call` * approximate it from below with the type of the actual argument. Skolemize that * type if necessary to make it a Singleton. diff --git a/compiler/src/dotty/tools/dotc/typer/Migrations.scala b/compiler/src/dotty/tools/dotc/typer/Migrations.scala index f0d1d235a19c..0e6dc27ecf7f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Migrations.scala +++ b/compiler/src/dotty/tools/dotc/typer/Migrations.scala @@ -126,4 +126,19 @@ trait Migrations: patch(Span(pt.args.head.span.start), "using ") end contextBoundParams + /** Report implicit parameter lists and rewrite implicit parameter list to contextual params */ + def implicitParams(tree: Tree, tp: MethodOrPoly, pt: FunProto)(using Context): Unit = + val mversion = mv.ImplicitParamsWithoutUsing + if tp.companion == ImplicitMethodType && pt.applyKind != ApplyKind.Using && pt.args.nonEmpty then + val rewriteMsg = Message.rewriteNotice("This code", mversion.patchFrom) + report.errorOrMigrationWarning( + em"""Implicit parameters should be provided with a `using` clause.$rewriteMsg + |To disable the warning, please use the following option: + | "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s" + |""", + pt.args.head.srcPos, mversion) + if mversion.needsPatch then + patch(Span(pt.args.head.span.start), "using ") + end implicitParams + end Migrations diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index e8b22325d1e9..89dc4cf53472 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -134,7 +134,7 @@ class Namer { typer: Typer => * The logic here is very subtle and fragile due to the fact that * we are not allowed to force anything. */ - def checkNoConflict(name: Name, isPrivate: Boolean, span: Span)(using Context): Name = + def checkNoConflict(name: Name, span: Span)(using Context): Name = val owner = ctx.owner var conflictsDetected = false @@ -169,7 +169,7 @@ class Namer { typer: Typer => def preExisting = ctx.effectiveScope.lookup(name) if (!owner.isClass || name.isTypeName) && preExisting.exists then conflict(preExisting) - else if owner.isPackageObject && !isPrivate && name != nme.CONSTRUCTOR then + else if owner.isPackageObject && name != nme.CONSTRUCTOR then checkNoConflictIn(owner.owner) for pkgObj <- pkgObjs(owner.owner) if pkgObj != owner do checkNoConflictIn(pkgObj) @@ -246,8 +246,10 @@ class Namer { typer: Typer => tree match { case tree: TypeDef if tree.isClassDef => - val flags = checkFlags(tree.mods.flags) - val name = checkNoConflict(tree.name, flags.is(Private), tree.span).asTypeName + var flags = checkFlags(tree.mods.flags) + if ctx.settings.YcompileScala2Library.value then + flags |= Scala2x + val name = checkNoConflict(tree.name, tree.span).asTypeName val cls = createOrRefine[ClassSymbol](tree, name, flags, ctx.owner, cls => adjustIfModule(new ClassCompleter(cls, tree)(ctx), tree), @@ -256,7 +258,7 @@ class Namer { typer: Typer => cls case tree: MemberDef => var flags = checkFlags(tree.mods.flags) - val name = checkNoConflict(tree.name, flags.is(Private), tree.span) + val name = checkNoConflict(tree.name, tree.span) tree match case tree: ValOrDefDef => if tree.isInstanceOf[ValDef] && !flags.is(Param) && name.endsWith("_=") then @@ -278,6 +280,9 @@ class Namer { typer: Typer => if rhs.isEmpty || flags.is(Opaque) then flags |= Deferred if flags.is(Param) then tree.rhs else analyzeRHS(tree.rhs) + def hasExplicitType(tree: ValOrDefDef): Boolean = + !tree.tpt.isEmpty || tree.mods.isOneOf(TermParamOrAccessor) + // to complete a constructor, move one context further out -- this // is the context enclosing the class. Note that the context in which a // constructor is recorded and the context in which it is completed are @@ -291,6 +296,8 @@ class Namer { typer: Typer => val completer = tree match case tree: TypeDef => TypeDefCompleter(tree)(cctx) + case tree: ValOrDefDef if Feature.enabled(Feature.modularity) && hasExplicitType(tree) => + new Completer(tree, isExplicit = true)(cctx) case _ => Completer(tree)(cctx) val info = adjustIfModule(completer, tree) createOrRefine[Symbol](tree, name, flags, ctx.owner, _ => info, @@ -705,7 +712,13 @@ class Namer { typer: Typer => enterSymbol(classConstructorCompanion(classSym.asClass)) else for moduleSym <- companionVals do - if moduleSym.is(Module) && !moduleSym.isDefinedInCurrentRun then + // by not going through `.lastKnownDenotation` (instead using `.current`), + // we guarantee that the `moduleSym` will be brought forward to the current run, + // rendering `moduleSym.isDefinedInCurrentRun` as always true. + // We want to regenerate the companion instead of bringing it forward, + // as even if we are able to bring forward the object symbol, + // we might not be able to do the same with its stale module class symbol (see `tests/pos/i20449`) + if moduleSym.lastKnownDenotation.is(Module) && !moduleSym.isDefinedInCurrentRun then val companion = if needsConstructorProxies(classSym) then classConstructorCompanion(classSym.asClass) @@ -800,7 +813,7 @@ class Namer { typer: Typer => } /** The completer of a symbol defined by a member def or import (except ClassSymbols) */ - class Completer(val original: Tree)(ictx: Context) extends LazyType with SymbolLoaders.SecondCompleter { + class Completer(val original: Tree, override val isExplicit: Boolean = false)(ictx: Context) extends LazyType with SymbolLoaders.SecondCompleter { protected def localContext(owner: Symbol): FreshContext = ctx.fresh.setOwner(owner).setTree(original) @@ -1170,11 +1183,26 @@ class Namer { typer: Typer => def canForward(mbr: SingleDenotation, alias: TermName): CanForward = { import CanForward.* val sym = mbr.symbol + /** + * The export selects a member of the current class (issue #22147). + * Assumes that cls.classInfo.selfType.derivesFrom(sym.owner) is true. + */ + def isCurrentClassMember: Boolean = expr match + case id: (Ident | This) => // Access through self type or this + /* Given the usage context below, where cls's self type is a subtype of sym.owner, + it suffices to check if symbol is the same class. */ + cls == id.symbol + case _ => false if !sym.isAccessibleFrom(pathType) then No("is not accessible") else if sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(ConstructorProxy) || sym.isAllOf(JavaModule) then Skip - else if cls.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred)) then + // if the cls is a subclass or mixes in the owner of the symbol + // and either + // * the symbols owner is the cls itself + // * the symbol is not a deferred symbol + // * the symbol is a member of the current class (#22147) + else if cls.classInfo.selfType.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred) || isCurrentClassMember) then No(i"is already a member of $cls") else if pathMethod.exists && mbr.isType then No("is a type, so it cannot be exported as extension method") @@ -1236,7 +1264,7 @@ class Namer { typer: Typer => val hasDefaults = sym.hasDefaultParams // compute here to ensure HasDefaultParams and NoDefaultParams flags are set val forwarder = if mbr.isType then - val forwarderName = checkNoConflict(alias.toTypeName, isPrivate = false, span) + val forwarderName = checkNoConflict(alias.toTypeName, span) var target = pathType.select(sym) if target.typeParams.nonEmpty then target = target.etaExpand @@ -1292,7 +1320,7 @@ class Namer { typer: Typer => (EmptyFlags, mbrInfo) var mbrFlags = MandatoryExportTermFlags | maybeStable | (sym.flags & RetainedExportTermFlags) if pathMethod.exists then mbrFlags |= ExtensionMethod - val forwarderName = checkNoConflict(alias, isPrivate = false, span) + val forwarderName = checkNoConflict(alias, span) newSymbol(cls, forwarderName, mbrFlags, mbrInfo, coord = span) forwarder.info = avoidPrivateLeaks(forwarder) @@ -1783,7 +1811,7 @@ class Namer { typer: Typer => sym.owner.typeParams.foreach(_.ensureCompleted()) completeTrailingParamss(constr, sym, indexingCtor = true) if Feature.enabled(modularity) then - constr.termParamss.foreach(_.foreach(setTracked)) + constr.termParamss.foreach(_.foreach(setTrackedConstrParam)) /** The signature of a module valdef. * This will compute the corresponding module class TypeRef immediately @@ -1923,22 +1951,22 @@ class Namer { typer: Typer => def wrapRefinedMethType(restpe: Type): Type = wrapMethType(addParamRefinements(restpe, paramSymss)) + def addTrackedIfNeeded(ddef: DefDef, owningSym: Symbol): Unit = + for params <- ddef.termParamss; param <- params do + val psym = symbolOfTree(param) + if needsTracked(psym, param, owningSym) then + psym.setFlag(Tracked) + setParamTrackedWithAccessors(psym, sym.maybeOwner.infoOrCompleter) + + if Feature.enabled(modularity) then addTrackedIfNeeded(ddef, sym.maybeOwner) + if isConstructor then // set result type tree to unit, but take the current class as result type of the symbol typedAheadType(ddef.tpt, defn.UnitType) - val mt = wrapMethType(effectiveResultType(sym, paramSymss)) - if sym.isPrimaryConstructor then checkCaseClassParamDependencies(mt, sym.owner) - mt - else if sym.isAllOf(Given | Method) && Feature.enabled(modularity) then - // set every context bound evidence parameter of a given companion method - // to be tracked, provided it has a type that has an abstract type member. - // Add refinements for all tracked parameters to the result type. - for params <- ddef.termParamss; param <- params do - val psym = symbolOfTree(param) - if needsTracked(psym, param) then psym.setFlag(Tracked) - valOrDefDefSig(ddef, sym, paramSymss, wrapRefinedMethType) + wrapMethType(effectiveResultType(sym, paramSymss)) else - valOrDefDefSig(ddef, sym, paramSymss, wrapMethType) + val paramFn = if Feature.enabled(Feature.modularity) && sym.isAllOf(Given | Method) then wrapRefinedMethType else wrapMethType + valOrDefDefSig(ddef, sym, paramSymss, paramFn) end defDefSig /** Complete the trailing parameters of a DefDef, @@ -1977,46 +2005,97 @@ class Namer { typer: Typer => ddef.trailingParamss.foreach(completeParams) end completeTrailingParamss - /** Checks an implementation restriction on case classes. */ - def checkCaseClassParamDependencies(mt: Type, cls: Symbol)(using Context): Unit = - mt.stripPoly match - case mt: MethodType if cls.is(Case) && mt.isParamDependent => - // See issue #8073 for background - report.error( - em"""Implementation restriction: case classes cannot have dependencies between parameters""", - cls.srcPos) - case _ => + private def setParamTrackedWithAccessors(psym: Symbol, ownerTpe: Type)(using Context): Unit = + for acc <- ownerTpe.decls.lookupAll(psym.name) if acc.is(ParamAccessor) do + acc.resetFlag(PrivateLocal) + psym.setFlag(Tracked) + acc.setFlag(Tracked) - /** Under x.modularity, we add `tracked` to context bound witnesses - * that have abstract type members + /** `psym` needs tracked if it is referenced in any of the public signatures + * of the defining class or when `psym` is a context bound witness with an + * abstract type member + */ + def needsTracked(psym: Symbol, param: ValDef, owningSym: Symbol)(using Context) = + lazy val abstractContextBound = isContextBoundWitnessWithAbstractMembers(psym, param, owningSym) + lazy val isRefInSignatures = + psym.maybeOwner.isPrimaryConstructor + && isReferencedInPublicSignatures(psym) + !psym.is(Tracked) + && psym.isTerm + && ( + abstractContextBound + || isRefInSignatures + ) + + /** Under x.modularity, we add `tracked` to context bound witnesses and + * explicit evidence parameters that have abstract type members */ - def needsTracked(sym: Symbol, param: ValDef)(using Context) = - !sym.is(Tracked) - && param.hasAttachment(ContextBoundParam) - && sym.info.memberNames(abstractTypeNameFilter).nonEmpty - - /** Under x.modularity, set every context bound evidence parameter of a class to be tracked, - * provided it has a type that has an abstract type member. Reset private and local flags - * so that the parameter becomes a `val`. + private def isContextBoundWitnessWithAbstractMembers(psym: Symbol, param: ValDef, owningSym: Symbol)(using Context): Boolean = + val accessorSyms = maybeParamAccessors(owningSym, psym) + (owningSym.isClass || owningSym.isAllOf(Given | Method)) + && (param.hasAttachment(ContextBoundParam) || (psym.isOneOf(GivenOrImplicit) && !accessorSyms.forall(_.isOneOf(PrivateLocal)))) + && psym.info.memberNames(abstractTypeNameFilter).nonEmpty + + extension (sym: Symbol) + private def infoWithForceNonInferingCompleter(using Context): Type = sym.infoOrCompleter match + case tpe: LazyType if tpe.isExplicit => sym.info + case tpe if sym.isType => sym.info + case info => info + + /** Under x.modularity, we add `tracked` to term parameters whose types are + * referenced in public signatures of the defining class + */ + private def isReferencedInPublicSignatures(sym: Symbol)(using Context): Boolean = + val owner = sym.maybeOwner.maybeOwner + val accessorSyms = maybeParamAccessors(owner, sym) + def checkOwnerMemberSignatures(owner: Symbol): Boolean = + owner.infoOrCompleter match + case info: ClassInfo => + info.decls.filter(_.isPublic) + .filter(_ != sym.maybeOwner) + .exists { decl => + tpeContainsSymbolRef(decl.infoWithForceNonInferingCompleter, accessorSyms) + } + case _ => false + checkOwnerMemberSignatures(owner) + + /** Check if any of syms are referenced in tpe */ + private def tpeContainsSymbolRef(tpe: Type, syms: List[Symbol])(using Context): Boolean = + val acc = new ExistsAccumulator( + { tpe => tpe.termSymbol.exists && syms.contains(tpe.termSymbol) }, + StopAt.Static, + forceLazy = false + ) { + override def apply(acc: Boolean, tpe: Type): Boolean = super.apply(acc, tpe.safeDealias) + } + acc(false, tpe) + + private def maybeParamAccessors(owner: Symbol, sym: Symbol)(using Context): List[Symbol] = owner.infoOrCompleter match + case info: ClassInfo => + info.decls.lookupAll(sym.name).filter(d => d.is(ParamAccessor)).toList + case _ => List(sym) + + /** Under x.modularity, set every context bound evidence parameter or public + * using parameter of a class to be tracked, provided it has a type that has + * an abstract type member. Reset private and local flags so that the + * parameter becomes a `val`. */ - def setTracked(param: ValDef)(using Context): Unit = + def setTrackedConstrParam(param: ValDef)(using Context): Unit = val sym = symbolOfTree(param) sym.maybeOwner.maybeOwner.infoOrCompleter match - case info: ClassInfo if needsTracked(sym, param) => + case info: ClassInfo + if !sym.is(Tracked) && isContextBoundWitnessWithAbstractMembers(sym, param, sym.maybeOwner.maybeOwner) => typr.println(i"set tracked $param, $sym: ${sym.info} containing ${sym.info.memberNames(abstractTypeNameFilter).toList}") - for acc <- info.decls.lookupAll(sym.name) if acc.is(ParamAccessor) do - acc.resetFlag(PrivateLocal) - acc.setFlag(Tracked) - sym.setFlag(Tracked) + setParamTrackedWithAccessors(sym, info) case _ => def inferredResultType( - mdef: ValOrDefDef, - sym: Symbol, - paramss: List[List[Symbol]], - paramFn: Type => Type, - fallbackProto: Type - )(using Context): Type = + mdef: ValOrDefDef, + sym: Symbol, + paramss: List[List[Symbol]], + paramFn: Type => Type, + fallbackProto: Type + )(using Context): Type = /** Is this member tracked? This is true if it is marked as `tracked` or if * it overrides a `tracked` member. To account for the later, `isTracked` * is overriden to `true` as a side-effect of computing `inherited`. diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 85f44ead5f28..0cc4aaabfc93 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -71,13 +71,6 @@ object ProtoTypes { |constraint was: ${ctx.typerState.constraint} |constraint now: ${newctx.typerState.constraint}""") if result && (ctx.typerState.constraint ne newctx.typerState.constraint) then - // Remove all type lambdas and tvars introduced by testCompat - for tvar <- newctx.typerState.ownedVars do - inContext(newctx): - if !tvar.isInstantiated then - tvar.instantiate(fromBelow = false) // any direction - - // commit any remaining changes in typer state newctx.typerState.commit() result case _ => testCompat @@ -536,8 +529,8 @@ object ProtoTypes { def typedArg(arg: untpd.Tree, formal: Type)(using Context): Tree = { val wideFormal = formal.widenExpr val argCtx = - if wideFormal eq formal then ctx - else ctx.withNotNullInfos(ctx.notNullInfos.retractMutables) + if wideFormal eq formal then ctx.retractMode(Mode.InAnnotation) + else ctx.retractMode(Mode.InAnnotation).withNotNullInfos(ctx.notNullInfos.retractMutables) val locked = ctx.typerState.ownedVars val targ = cacheTypedArg(arg, typer.typedUnadapted(_, wideFormal, locked)(using argCtx), diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index dcdabaf3a72d..a015348e90a7 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -306,6 +306,7 @@ object RefChecks { * that passes its value on to O. * 1.13. If O is non-experimental, M must be non-experimental. * 1.14. If O has @publicInBinary, M must have @publicInBinary. + * 1.15. If O is non-preview, M must be non-preview * 2. Check that only abstract classes have deferred members * 3. Check that concrete classes do not have deferred definitions * that are not implemented in a subclass. @@ -645,6 +646,8 @@ object RefChecks { overrideError("may not override non-experimental member") else if !member.hasAnnotation(defn.PublicInBinaryAnnot) && other.hasAnnotation(defn.PublicInBinaryAnnot) then // (1.14) overrideError("also needs to be declared with @publicInBinary") + else if !other.isPreview && member.hasAnnotation(defn.PreviewAnnot) then // (1.15) + overrideError("may not override non-preview member") else if other.hasAnnotation(defn.DeprecatedOverridingAnnot) then overrideDeprecation("", member, other, "removed or renamed") end checkOverride @@ -906,7 +909,7 @@ object RefChecks { for (mbrd <- self.member(name).alternatives) { val mbr = mbrd.symbol val mbrType = mbr.info.asSeenFrom(self, mbr.owner) - if (!mbrType.overrides(mbrd.info, relaxedCheck = false, matchLoosely = true)) + if (!mbrType.overrides(mbrd.info, matchLoosely = true)) report.errorOrMigrationWarning( em"""${mbr.showLocated} is not a legal implementation of `$name` in $clazz | its type $mbrType @@ -1134,14 +1137,24 @@ object RefChecks { end checkUnaryMethods /** Check that an extension method is not hidden, i.e., that it is callable as an extension method. + * + * For example, it is not possible to define a type-safe extension `contains` for `Set`, + * since for any parameter type, the existing `contains` method will compile and would be used. * * An extension method is hidden if it does not offer a parameter that is not subsumed * by the corresponding parameter of the member with the same name (or of all alternatives of an overload). * - * This check is suppressed if this method is an override. + * This check is suppressed if the method is an override. (Because the type of the receiver + * may be narrower in the override.) * - * For example, it is not possible to define a type-safe extension `contains` for `Set`, - * since for any parameter type, the existing `contains` method will compile and would be used. + * If the extension method is nullary, it is always hidden by a member of the same name. + * (Either the member is nullary, or the reference is taken as the eta-expansion of the member.) + * + * This check is in lieu of a more expensive use-site check that an application failed to use an extension. + * That check would account for accessibility and opacity. As a limitation, this check considers + * only public members for which corresponding method parameters are either both opaque types or both not. + * It is intended to warn if the receiver type from a third-party library has been augmented with a member + * that nullifies an existing extension. * * If the member has a leading implicit parameter list, then the extension method must also have * a leading implicit parameter list. The reason is that if the implicit arguments are inferred, @@ -1152,45 +1165,41 @@ object RefChecks { * If the member does not have a leading implicit parameter list, then the argument cannot be explicitly * supplied with `using`, as typechecking would fail. But the extension method may have leading implicit * parameters, which are necessarily supplied implicitly in the application. The first non-implicit - * parameters of the extension method must be distinguishable from the member parameters, as described. - * - * If the extension method is nullary, it is always hidden by a member of the same name. - * (Either the member is nullary, or the reference is taken as the eta-expansion of the member.) - * - * This check is in lieu of a more expensive use-site check that an application failed to use an extension. - * That check would account for accessibility and opacity. As a limitation, this check considers - * only public members, a target receiver that is not an alias, and corresponding method parameters - * that are either both opaque types or both not. + * parameters of the extension method must be distinguishable from the member parameters, as described above. */ def checkExtensionMethods(sym: Symbol)(using Context): Unit = - if sym.is(Extension) && !sym.nextOverriddenSymbol.exists then + if sym.is(Extension) then extension (tp: Type) - def strippedResultType = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true).resultType - def firstExplicitParamTypes = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true).firstParamTypes + def explicit = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true) def hasImplicitParams = tp.stripPoly match { case mt: MethodType => mt.isImplicitMethod case _ => false } - val target = sym.info.firstExplicitParamTypes.head // required for extension method, the putative receiver - val methTp = sym.info.strippedResultType // skip leading implicits and the "receiver" parameter + val explicitInfo = sym.info.explicit // consider explicit value params + val target0 = explicitInfo.firstParamTypes.head // required for extension method, the putative receiver + val target = target0.dealiasKeepOpaques.typeSymbol.info + val methTp = explicitInfo.resultType // skip leading implicits and the "receiver" parameter + def memberMatchesMethod(member: Denotation) = + val memberIsImplicit = member.info.hasImplicitParams + val paramTps = + if memberIsImplicit then methTp.stripPoly.firstParamTypes + else methTp.explicit.firstParamTypes + inline def paramsCorrespond = + val memberParamTps = member.info.stripPoly.firstParamTypes + memberParamTps.corresponds(paramTps): (m, x) => + m.typeSymbol.denot.isOpaqueAlias == x.typeSymbol.denot.isOpaqueAlias && (x frozen_<:< m) + paramTps.isEmpty || memberIsImplicit && !methTp.hasImplicitParams || paramsCorrespond def hidden = target.nonPrivateMember(sym.name) - .filterWithPredicate: - member => - member.symbol.isPublic && { - val memberIsImplicit = member.info.hasImplicitParams - val paramTps = - if memberIsImplicit then methTp.stripPoly.firstParamTypes - else methTp.firstExplicitParamTypes - - paramTps.isEmpty || memberIsImplicit && !methTp.hasImplicitParams || { - val memberParamTps = member.info.stripPoly.firstParamTypes - !memberParamTps.isEmpty - && memberParamTps.lengthCompare(paramTps) == 0 - && memberParamTps.lazyZip(paramTps).forall: (m, x) => - m.typeSymbol.denot.isOpaqueAlias == x.typeSymbol.denot.isOpaqueAlias - && (x frozen_<:< m) - } - } + .filterWithPredicate: member => + member.symbol.isPublic && memberMatchesMethod(member) .exists - if !target.typeSymbol.denot.isAliasType && !target.typeSymbol.denot.isOpaqueAlias && hidden + if sym.is(HasDefaultParams) then + val getterDenot = + val receiverName = explicitInfo.firstParamNames.head + val num = sym.info.paramNamess.flatten.indexWhere(_ == receiverName) + val getterName = DefaultGetterName(sym.name.toTermName, num = num) + sym.owner.info.member(getterName) + if getterDenot.exists + then report.warning(ExtensionHasDefault(sym), getterDenot.symbol.srcPos) + if !sym.nextOverriddenSymbol.exists && hidden then report.warning(ExtensionNullifiedByMember(sym, target.typeSymbol), sym.srcPos) end checkExtensionMethods diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index c935e8d6b3cf..5111a9517fab 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -322,6 +322,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): case ClassSymbol(pre: Type, cls: Symbol) case Singleton(src: Symbol, tref: TermRef) case GenericTuple(tps: List[Type]) + case NamedTuple(nameTypePairs: List[(TermName, Type)]) /** Tests that both sides are tuples of the same arity */ infix def sameTuple(that: MirrorSource)(using Context): Boolean = @@ -351,6 +352,11 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val arity = tps.size if arity <= Definitions.MaxTupleArity then s"class Tuple$arity" else s"trait Tuple { def size: $arity }" + case NamedTuple(nameTypePairs) => + val (names, types) = nameTypePairs.unzip + val namesStr = names.map(_.show).mkString("(\"", "\", \"", "\")") + val typesStr = types.map(_.show).mkString("(", ", ", ")") + s"NamedTuple.NamedTuple[${namesStr}, ${typesStr}]" private[Synthesizer] object MirrorSource: @@ -398,6 +404,8 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): // avoid type aliases for tuples Right(MirrorSource.GenericTuple(types)) case _ => reduce(tp.underlying) + case defn.NamedTupleDirect(_, _) => + Right(MirrorSource.NamedTuple(tp.namedTupleElementTypes(derived = false))) case tp: MatchType => val n = tp.tryNormalize if n.exists then reduce(n) else Left(i"its subpart `$tp` is an unreducible match type.") @@ -428,10 +436,25 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): def newTupleMirror(arity: Int): Tree = New(defn.RuntimeTupleMirrorTypeRef, Literal(Constant(arity)) :: Nil) - def makeProductMirror(pre: Type, cls: Symbol, tps: Option[List[Type]]): TreeWithErrors = + def makeNamedTupleProductMirror(nameTypePairs: List[(TermName, Type)]): TreeWithErrors = + val (labels, typeElems) = nameTypePairs.unzip + val elemLabels = labels.map(label => ConstantType(Constant(label.toString))) + val mirrorRef: Type => Tree = _ => newTupleMirror(typeElems.size) + makeProductMirror(typeElems, elemLabels, tpnme.NamedTuple, mirrorRef) + end makeNamedTupleProductMirror + + def makeClassProductMirror(pre: Type, cls: Symbol, tps: Option[List[Type]]) = val accessors = cls.caseAccessors val elemLabels = accessors.map(acc => ConstantType(Constant(acc.name.toString))) val typeElems = tps.getOrElse(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr)) + val mirrorRef = (monoType: Type) => + if cls.useCompanionAsProductMirror then companionPath(pre, cls, span) + else if defn.isTupleClass(cls) then newTupleMirror(typeElems.size) // TODO: cls == defn.PairClass when > 22 + else anonymousMirror(monoType, MirrorImpl.OfProduct(pre), span) + makeProductMirror(typeElems, elemLabels, cls.name, mirrorRef) + end makeClassProductMirror + + def makeProductMirror(typeElems: List[Type], elemLabels: List[Type], label: Name, mirrorRef: Type => Tree): TreeWithErrors = val nestedPairs = TypeOps.nestedPairs(typeElems) val (monoType, elemsType) = mirroredType match case mirroredType: HKTypeLambda => @@ -442,15 +465,11 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): checkRefinement(formal, tpnme.MirroredElemTypes, elemsType, span) checkRefinement(formal, tpnme.MirroredElemLabels, elemsLabels, span) val mirrorType = formal.constrained_& { - mirrorCore(defn.Mirror_ProductClass, monoType, mirroredType, cls.name) + mirrorCore(defn.Mirror_ProductClass, monoType, mirroredType, label) .refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType)) .refinedWith(tpnme.MirroredElemLabels, TypeAlias(elemsLabels)) } - val mirrorRef = - if cls.useCompanionAsProductMirror then companionPath(pre, cls, span) - else if defn.isTupleClass(cls) then newTupleMirror(typeElems.size) // TODO: cls == defn.PairClass when > 22 - else anonymousMirror(monoType, MirrorImpl.OfProduct(pre), span) - withNoErrors(mirrorRef.cast(mirrorType).withSpan(span)) + withNoErrors(mirrorRef(monoType).cast(mirrorType).withSpan(span)) end makeProductMirror MirrorSource.reduce(mirroredType) match @@ -474,10 +493,12 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val arity = tps.size if tps.size <= maxArity then val tupleCls = defn.TupleType(arity).nn.classSymbol - makeProductMirror(tupleCls.owner.reachableThisType, tupleCls, Some(tps)) + makeClassProductMirror(tupleCls.owner.reachableThisType, tupleCls, Some(tps)) else val reason = s"it reduces to a tuple with arity $arity, expected arity <= $maxArity" withErrors(i"${defn.PairClass} is not a generic product because $reason") + case MirrorSource.NamedTuple(nameTypePairs) => + makeNamedTupleProductMirror(nameTypePairs) case MirrorSource.ClassSymbol(pre, cls) => if cls.isGenericProduct then if ctx.runZincPhases then @@ -486,7 +507,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val rec = ctx.compilationUnit.depRecorder rec.addClassDependency(cls, DependencyByMemberRef) rec.addUsedName(cls.primaryConstructor) - makeProductMirror(pre, cls, None) + makeClassProductMirror(pre, cls, None) else withErrors(i"$cls is not a generic product because ${cls.whyNotGenericProduct}") case Left(msg) => withErrors(i"type `$mirroredType` is not a generic product because $msg") @@ -501,6 +522,8 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val arity = tps.size val cls = if arity <= Definitions.MaxTupleArity then defn.TupleType(arity).nn.classSymbol else defn.PairClass ("", NoType, cls) + case Right(MirrorSource.NamedTuple(_)) => + ("named tuples are not sealed classes", NoType, NoSymbol) case Left(msg) => (msg, NoType, NoSymbol) val clsIsGenericSum = cls.isGenericSum(pre) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 6ce0f5f2517c..4d16a342f484 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -12,6 +12,7 @@ import collection.mutable import reporting.* import Checking.{checkNoPrivateLeaks, checkNoWildcard} import cc.CaptureSet +import transform.Splicer trait TypeAssigner { import tpd.* @@ -125,8 +126,8 @@ trait TypeAssigner { /** The type of the selection `tree`, where `qual1` is the typed qualifier part. */ def selectionType(tree: untpd.RefTree, qual1: Tree)(using Context): Type = - val qualType0 = qual1.tpe.widenIfUnstable val qualType = + val qualType0 = qual1.tpe.widenIfUnstable if !qualType0.hasSimpleKind && tree.name != nme.CONSTRUCTOR then // constructors are selected on type constructor, type arguments are passed afterwards errorType(em"$qualType0 takes type parameters", qual1.srcPos) @@ -163,7 +164,7 @@ trait TypeAssigner { else qualType.findMember(name, pre) - if reallyExists(mbr) then qualType.select(name, mbr) + if reallyExists(mbr) && NamedType.validPrefix(qualType) then qualType.select(name, mbr) else if qualType.isErroneous || name.toTermName == nme.ERROR then UnspecifiedErrorType else NoType end selectionType @@ -199,7 +200,7 @@ trait TypeAssigner { /** Type assignment method. Each method takes as parameters * - an untpd.Tree to which it assigns a type, - * - typed child trees it needs to access to cpmpute that type, + * - typed child trees it needs to access to compute that type, * - any further information it needs to access to compute that type. */ def assignType(tree: untpd.Ident, tp: Type)(using Context): Ident = @@ -301,7 +302,10 @@ trait TypeAssigner { if fntpe.isResultDependent then safeSubstMethodParams(fntpe, args.tpes) else fntpe.resultType // fast path optimization else - errorType(em"wrong number of arguments at ${ctx.phase.prev} for $fntpe: ${fn.tpe}, expected: ${fntpe.paramInfos.length}, found: ${args.length}", tree.srcPos) + val erroringPhase = + if Splicer.inMacroExpansion then i"${ctx.phase} (while expanding macro)" + else ctx.phase.prev.toString + errorType(em"wrong number of arguments at $erroringPhase for $fntpe: ${fn.tpe}, expected: ${fntpe.paramInfos.length}, found: ${args.length}", tree.srcPos) case err: ErrorType => err case t => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9b7e4fe36668..be3186720fa1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -36,7 +36,6 @@ import util.{Property, SimpleIdentityMap, SrcPos} import Applications.{tupleComponentTypes, wrapDefs, defaultArgument} import collection.mutable -import annotation.tailrec import Implicits.* import util.Stats.record import config.Printers.{gadts, typr} @@ -52,7 +51,8 @@ import config.Config import config.MigrationVersion import transform.CheckUnused.OriginalName -import scala.annotation.constructorOnly +import scala.annotation.{unchecked as _, *} +import dotty.tools.dotc.util.chaining.* object Typer { @@ -84,6 +84,9 @@ object Typer { /** Indicates that an expression is explicitly ascribed to [[Unit]] type. */ val AscribedToUnit = new Property.StickyKey[Unit] + /** Tree adaptation lost fidelity; this attachment preserves the original tree. */ + val AdaptedTree = new Property.StickyKey[tpd.Tree] + /** An attachment on a Select node with an `apply` field indicating that the `apply` * was inserted by the Typer. */ @@ -615,10 +618,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Shortcut for the root package, this is not just a performance // optimization, it also avoids forcing imports thus potentially avoiding // cyclic references. - if (name == nme.ROOTPKG) - val tree2 = tree.withType(defn.RootPackage.termRef) - checkLegalValue(tree2, pt) - return tree2 + if name == nme.ROOTPKG then + return checkLegalValue(tree.withType(defn.RootPackage.termRef), pt) val rawType = val saved1 = unimported @@ -678,9 +679,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer cpy.Ident(tree)(tree.name.unmangleClassName).withType(checkedType) else tree.withType(checkedType) - val tree2 = toNotNullTermRef(tree1, pt) - checkLegalValue(tree2, pt) - tree2 + checkLegalValue(toNotNullTermRef(tree1, pt), pt) def isLocalExtensionMethodRef: Boolean = rawType match case rawType: TermRef => @@ -720,21 +719,47 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer errorTree(tree, MissingIdent(tree, kind, name, pt)) end typedIdent + def checkValue(tree: Tree)(using Context): Tree = + val sym = tree.tpe.termSymbol + if sym.isNoValue && !ctx.isJava then + if sym.is(Package) + && Feature.enabled(Feature.packageObjectValues) + && tree.tpe.member(nme.PACKAGE).hasAltWith(_.symbol.isPackageObject) + then + typed(untpd.Select(untpd.TypedSplice(tree), nme.PACKAGE)) + else + report.error(SymbolIsNotAValue(sym), tree.srcPos) + tree + else tree + + /** Check that `tree` refers to a value, unless `tree` is selected or applied + * (singleton types x.type don't count as selections). + */ + def checkValue(tree: Tree, proto: Type)(using Context): Tree = + tree match + case tree: RefTree if tree.name.isTermName => + proto match + case _: SelectionProto if proto ne SingletonTypeProto => tree // no value check + case _: FunOrPolyProto => tree // no value check + case _ => checkValue(tree) + case _ => tree + /** (1) If this reference is neither applied nor selected, check that it does * not refer to a package or Java companion object. * (2) Check that a stable identifier pattern is indeed stable (SLS 8.1.5) */ - private def checkLegalValue(tree: Tree, pt: Type)(using Context): Unit = - checkValue(tree, pt) + private def checkLegalValue(tree: Tree, pt: Type)(using Context): Tree = + val tree1 = checkValue(tree, pt) if ctx.mode.is(Mode.Pattern) - && !tree.isType + && !tree1.isType && !pt.isInstanceOf[ApplyingProto] - && !tree.tpe.match + && !tree1.tpe.match case tp: NamedType => tp.denot.hasAltWith(_.symbol.isStableMember && tp.prefix.isStable || tp.info.isStable) case tp => tp.isStable - && !isWildcardArg(tree) + && !isWildcardArg(tree1) then - report.error(StableIdentPattern(tree, pt), tree.srcPos) + report.error(StableIdentPattern(tree1, pt), tree1.srcPos) + tree1 def typedSelectWithAdapt(tree0: untpd.Select, pt: Type, qual: Tree)(using Context): Tree = val selName = tree0.name @@ -748,8 +773,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if checkedType.exists then val select = toNotNullTermRef(assignType(tree, checkedType), pt) if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix") - checkLegalValue(select, pt) - ConstFold(select) + ConstFold(checkLegalValue(select, pt)) else EmptyTree // Otherwise, simplify `m.apply(...)` to `m(...)` @@ -801,7 +825,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def tryNamedTupleSelection() = val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes(true) val nameIdx = namedTupleElems.indexWhere(_._1 == selName) - if nameIdx >= 0 && Feature.enabled(Feature.namedTuples) then + if nameIdx >= 0 && sourceVersion.enablesNamedTuples then typed( untpd.Apply( untpd.Select(untpd.TypedSplice(qual), nme.apply), @@ -998,20 +1022,44 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer record("typedSelect") def typeSelectOnTerm(using Context): Tree = - val qual = typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan)) if ctx.isJava then - javaSelection(qual) + // permitted selection depends on Java context (type or expression). + // we don't propagate (as a mode) whether a.b.m is a type name; OK since we only see type contexts. + // to allow correct selections, approximate by fallback for x.y: take x as class or (rooted) package. + def tryQualFallback(qual: untpd.Ident, name: Name)(using Context): Tree = + val qualTpe = + findRef(name.toTypeName, WildcardType, EmptyFlags, EmptyFlags, qual.srcPos) match + case tpe: NamedType if tpe.symbol.isClass => tpe + case _ => + val maybePackage = defn.RootPackage.info.member(name) + if maybePackage.exists then maybePackage.info else NoType + if qualTpe.exists then + javaSelection(assignType(cpy.Ident(qual)(name), qualTpe)) + else + errorTree(tree, em"no class or package to resolve `$name`") // just fail fallback + def tryQual(qual: untpd.Tree)(using Context): Tree = + javaSelection(typedExpr(qual, shallowSelectionProto(tree.name, pt, this, tree.nameSpan))) + tree.qualifier match + case qual @ Ident(name) => tryAlternatively(tryQual(qual))(tryQualFallback(qual, name)) + case qual => tryQual(qual) else + val qual = typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan)) typedSelectWithAdapt(tree, pt, qual).withSpan(tree.span).computeNullable() def javaSelection(qual: Tree)(using Context) = - val tree1 = assignType(cpy.Select(tree)(qual, tree.name), qual) - tree1.tpe match - case moduleRef: TypeRef if moduleRef.symbol.is(ModuleClass, butNot = JavaDefined) => - // handle unmangling of module names (Foo$ -> Foo[ModuleClass]) - cpy.Select(tree)(qual, tree.name.unmangleClassName).withType(moduleRef) - case _ => - tree1 + qual match + case id @ Ident(name) if id.symbol.is(Package) && !id.symbol.owner.isRoot => + val rooted = defn.RootPackage.info.member(name) + val qual1 = if rooted.exists then assignType(cpy.Ident(id)(name), rooted.info) else qual + assignType(cpy.Select(tree)(qual1, tree.name), qual1) + case _ => + val tree1 = assignType(cpy.Select(tree)(qual, tree.name), qual) + tree1.tpe match + case moduleRef: TypeRef if moduleRef.symbol.is(ModuleClass, butNot = JavaDefined) => + // handle unmangling of module names (Foo$ -> Foo[ModuleClass]) + cpy.Select(tree)(qual, tree.name.unmangleClassName).withType(moduleRef) + case _ => + tree1 def tryJavaSelectOnType(using Context): Tree = tree.qualifier match { case sel @ Select(qual, name) => @@ -1028,17 +1076,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer errorTree(tree, em"cannot convert to type selection") // will never be printed due to fallback } - def selectWithFallback(fallBack: Context ?=> Tree) = - tryAlternatively(typeSelectOnTerm)(fallBack) - if (tree.qualifier.isType) { val qual1 = typedType(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan)) assignType(cpy.Select(tree)(qual1, tree.name), qual1) } else if (ctx.isJava && tree.name.isTypeName) - // SI-3120 Java uses the same syntax, A.B, to express selection from the - // value A and from the type A. We have to try both. - selectWithFallback(tryJavaSelectOnType) // !!! possibly exponential bcs of qualifier retyping + // scala/bug#3120 Java uses the same syntax, A.B, to express selection from the + // value A and from the type A. We have to try both. (possibly exponential bc of qualifier retyping) + tryAlternatively(typeSelectOnTerm)(tryJavaSelectOnType) else typeSelectOnTerm } @@ -2466,7 +2511,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedContextBoundTypeTree(tree: untpd.ContextBoundTypeTree)(using Context): Tree = val tycon = typedType(tree.tycon) def spliced(tree: Tree) = untpd.TypedSplice(tree) - val tparam = untpd.Ident(tree.paramName).withSpan(tree.span) + val tparam = untpd.Ident(tree.paramName).withSpan(tree.span.withEnd(tree.span.point)) if tycon.tpe.typeParams.nonEmpty then val tycon0 = tycon.withType(tycon.tpe.etaCollapse) typed(untpd.AppliedTypeTree(spliced(tycon0), tparam :: Nil)) @@ -2776,7 +2821,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def isInner(owner: Symbol) = owner == sym || sym.is(Param) && owner == sym.owner val outer = ctx.outersIterator.dropWhile(c => isInner(c.owner)).next() def local: FreshContext = outer.fresh.setOwner(newLocalDummy(sym.owner)) - sym.owner.infoOrCompleter match + val ctx0 = sym.owner.infoOrCompleter match case completer: Namer#Completer if sym.is(Param) && completer.completerTypeParams(sym).nonEmpty => // Create a new local context with a dummy owner and a scope containing the @@ -2785,6 +2830,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer local.setScope(newScopeWith(completer.completerTypeParams(sym)*)) case _ => if outer.owner.isClass then local else outer + ctx0.addMode(Mode.InAnnotation) def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(using Context): Unit = { // necessary to force annotation trees to be computed. @@ -2799,35 +2845,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } def typedAnnotation(annot: untpd.Tree)(using Context): Tree = - checkAnnotClass(checkAnnotArgs(typed(annot))) + val typedAnnot = withMode(Mode.InAnnotation)(typed(annot)) + checkAnnotClass(checkAnnotArgs(typedAnnot)) def registerNowarn(tree: Tree, mdef: untpd.Tree)(using Context): Unit = val annot = Annotations.Annotation(tree) - def argPos = annot.argument(0).getOrElse(tree).sourcePos - var verbose = false - val filters = annot.argumentConstantString(0) match - case None => annot.argument(0) match - case Some(t: Select) if t.name.is(DefaultGetterName) => - // default argument used for `@nowarn` and `@nowarn()` - List(MessageFilter.Any) - case _ => - report.warning(s"filter needs to be a compile-time constant string", argPos) - List(MessageFilter.None) - case Some("") => - List(MessageFilter.Any) - case Some("verbose") | Some("v") => - verbose = true - List(MessageFilter.Any) - case Some(s) => - WConf.parseFilters(s).left.map(parseErrors => - report.warning (s"Invalid message filter\n${parseErrors.mkString ("\n")}", argPos) - List(MessageFilter.None) - ).merge - val range = mdef.sourcePos - val sup = Suppression(tree.sourcePos, filters, range.start, range.end, verbose) - // invalid suppressions, don't report as unused - if filters == List(MessageFilter.None) then sup.markUsed() - ctx.run.nn.suppressions.addSuppression(sup) + val argPos = annot.argument(0).getOrElse(tree).sourcePos + val conf = annot.argumentConstantString(0).getOrElse: + annot.argument(0) match + case Some(t: Select) if t.name.is(DefaultGetterName) => + "" // default argument used for `@nowarn` and `@nowarn()` + case _ => + report.warning(s"filter needs to be a compile-time constant string", argPos) + "none" // not a -Wconf filter, mapped to MessageFilter.None by registerNowarn + ctx.run.nn.suppressions.registerNowarn(tree.sourcePos, mdef.span)(conf, argPos) /** Run `typed` on `rhs` except if `rhs` is the right hand side of a deferred given, * in which case the empty tree is returned. @@ -2950,7 +2981,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def checkThisConstrCall(tree: Tree): Unit = tree match case app: Apply if untpd.isSelfConstrCall(app) => - if (sym.span.exists && app.symbol.span.exists && sym.span.start <= app.symbol.span.start) + if !sym.is(Synthetic) + && sym.span.exists && app.symbol.span.exists && sym.span.start <= app.symbol.span.start + then report.error("secondary constructor must call a preceding constructor", app.srcPos) case Block(call :: _, _) => checkThisConstrCall(call) case _ => @@ -3191,7 +3224,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer .withType(dummy.termRef) if (!cls.isOneOf(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls, cdef.sourcePos.withSpan(cdef.nameSpan)) - if cls.isEnum || firstParentTpe.classSymbol.isEnum then + if cls.isEnum || !cls.isRefinementClass && firstParentTpe.classSymbol.isEnum then checkEnum(cdef, cls, firstParent) val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1), cls) @@ -3330,7 +3363,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer end typedPackageDef def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree = { - val annot1 = checkAnnotClass(typedExpr(tree.annot)) + val annot0 = withMode(Mode.InAnnotation)(typedExpr(tree.annot)) + val annot1 = checkAnnotClass(annot0) val annotCls = Annotations.annotClass(annot1) if annotCls == defn.NowarnAnnot then registerNowarn(annot1, tree) @@ -3420,7 +3454,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer /** Translate tuples of all arities */ def typedTuple(tree: untpd.Tuple, pt: Type)(using Context): Tree = - val tree1 = desugar.tuple(tree, pt) + val tree1 = desugar.tuple(tree, pt).withAttachmentsFrom(tree) checkDeprecatedAssignmentSyntax(tree) if tree1 ne tree then typed(tree1, pt) else @@ -3450,19 +3484,22 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer /** Checks if `tree` is a named tuple with one element that could be * interpreted as an assignment, such as `(x = 1)`. If so, issues a warning. */ - def checkDeprecatedAssignmentSyntax(tree: untpd.Tuple)(using Context): Unit = - tree.trees match - case List(NamedArg(name, value)) => + def checkDeprecatedAssignmentSyntax(tree: untpd.Tuple | untpd.Parens)(using Context): Unit = + val assignmentArgs = tree match { + case untpd.Tuple(List(NamedArg(name, value))) => val tmpCtx = ctx.fresh.setNewTyperState() typedAssign(untpd.Assign(untpd.Ident(name), value), WildcardType)(using tmpCtx) - if !tmpCtx.reporter.hasErrors then - // If there are no errors typing the above, then the named tuple is - // ambiguous and we issue a warning. - report.migrationWarning(DeprecatedAssignmentSyntax(name, value), tree.srcPos) - if MigrationVersion.AmbiguousNamedTupleSyntax.needsPatch then - patch(tree.source, Span(tree.span.start, tree.span.start + 1), "{") - patch(tree.source, Span(tree.span.end - 1, tree.span.end), "}") - case _ => () + Option.unless(tmpCtx.reporter.hasErrors)(name -> value) + case untpd.Parens(Assign(ident: untpd.Ident, value)) => Some(ident.name -> value) + case _ => None + } + assignmentArgs.foreach: (name, value) => + // If there are no errors typing the above, then the named tuple is + // ambiguous and we issue a warning. + report.migrationWarning(DeprecatedAssignmentSyntax(name, value), tree.srcPos) + if MigrationVersion.AmbiguousNamedTupleSyntax.needsPatch then + patch(tree.source, Span(tree.span.start, tree.span.start + 1), "{") + patch(tree.source, Span(tree.span.end - 1, tree.span.end), "}") /** Retrieve symbol attached to given tree */ protected def retrieveSym(tree: untpd.Tree)(using Context): Symbol = tree.removeAttachment(SymOfTree) match { @@ -3571,6 +3608,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case tree: untpd.SplicePattern => typedSplicePattern(tree, pt) case tree: untpd.MacroTree => report.error("Unexpected macro", tree.srcPos); tpd.nullLiteral // ill-formed code may reach here case tree: untpd.Hole => typedHole(tree, pt) + case tree: untpd.Parens => + checkDeprecatedAssignmentSyntax(tree) + typedUnadapted(desugar(tree, pt), pt, locked) case _ => typedUnadapted(desugar(tree, pt), pt, locked) } @@ -4159,6 +4199,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def methodStr = methPart(tree).symbol.showLocated if matchingApply(wtp, pt) then migrate(contextBoundParams(tree, wtp, pt)) + migrate(implicitParams(tree, wtp, pt)) if needsTupledDual(wtp, pt) then adapt(tree, pt.tupledDual, locked) else tree else if wtp.isContextualMethod then @@ -4167,9 +4208,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer readapt(tree.appliedToNone) // insert () to primary constructors else errorTree(tree, em"Missing arguments for $methodStr") - case _ => tryInsertApplyOrImplicit(tree, pt, locked) { - errorTree(tree, MethodDoesNotTakeParameters(tree)) - } + case _ => + tryInsertApplyOrImplicit(tree, pt, locked): + errorTree(tree, MethodDoesNotTakeParameters(tree)) } def adaptNoArgsImplicitMethod(wtp: MethodType): Tree = { @@ -4188,6 +4229,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def addImplicitArgs(using Context) = def hasDefaultParams = methPart(tree).symbol.hasDefaultParams + def findDefaultArgument(argIndex: Int): Tree = + def appPart(t: Tree): Tree = t match + case Block(_, expr) => appPart(expr) + case Inlined(_, _, expr) => appPart(expr) + case t => t + defaultArgument(appPart(tree), n = argIndex, testOnly = false) def implicitArgs(formals: List[Type], argIndex: Int, pt: Type): List[Tree] = formals match case Nil => Nil case formal :: formals1 => @@ -4209,13 +4256,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer then implicitArgs(formals, argIndex, pt1) else arg :: implicitArgs(formals1, argIndex + 1, pt1) case failed: SearchFailureType => - lazy val defaultArg = - def appPart(t: Tree): Tree = t match - case Block(stats, expr) => appPart(expr) - case Inlined(_, _, expr) => appPart(expr) - case _ => t - defaultArgument(appPart(tree), argIndex, testOnly = false) - .showing(i"default argument: for $formal, $tree, $argIndex = $result", typr) + lazy val defaultArg = findDefaultArgument(argIndex) + .showing(i"default argument: for $formal, $tree, $argIndex = $result", typr) if !hasDefaultParams || defaultArg.isEmpty then // no need to search further, the adapt fails in any case // the reason why we continue inferring arguments in case of an AmbiguousImplicits @@ -4237,44 +4279,44 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer arg :: inferArgsAfter(arg) end implicitArgs - /** Reports errors for arguments of `appTree` that have a - * `SearchFailureType`. - */ - def issueErrors(fun: Tree, args: List[Tree]): Tree = - // Prefer other errors over ambiguities. If nested in outer searches a missing - // implicit can be healed by simply dropping this alternative and trying something - // else. But an ambiguity is sticky and propagates outwards. If we have both - // a missing implicit on one argument and an ambiguity on another the whole - // branch should be classified as a missing implicit. - val firstNonAmbiguous = args.tpes.find(tp => tp.isError && !tp.isInstanceOf[AmbiguousImplicits]) - def firstError = args.tpes.find(_.isInstanceOf[SearchFailureType]).getOrElse(NoType) - def firstFailure = firstNonAmbiguous.getOrElse(firstError) - val errorType = - firstFailure match - case tp: AmbiguousImplicits => - AmbiguousImplicits(tp.alt1, tp.alt2, tp.expectedType, tp.argument, nested = true) - case tp => - tp - val res = untpd.Apply(fun, args).withType(errorType) - - wtp.paramNames.lazyZip(wtp.paramInfos).lazyZip(args).foreach { (paramName, formal, arg) => - arg.tpe match - case failure: SearchFailureType => - val methodStr = err.refStr(methPart(fun).tpe) - val paramStr = implicitParamString(paramName, methodStr, fun) - val paramSym = fun.symbol.paramSymss.flatten.find(_.name == paramName) - val paramSymWithMethodCallTree = paramSym.map((_, res)) - report.error( - missingArgMsg(arg, formal, paramStr, paramSymWithMethodCallTree), - tree.srcPos.endPos - ) - case _ => - } - - res + // Pick a failure type to propagate, if any. + // Prefer other errors over ambiguities. If nested in outer searches a missing + // implicit can be healed by simply dropping this alternative and trying something + // else. But an ambiguity is sticky and propagates outwards. If we have both + // a missing implicit on one argument and an ambiguity on another the whole + // branch should be classified as a missing implicit. + def propagatedFailure(args: List[Tree]): Type = args match + case arg :: args => arg.tpe match + case ambi: AmbiguousImplicits => propagatedFailure(args) match + case NoType | (_: AmbiguousImplicits) => ambi + case failed => failed + case failed: SearchFailureType => failed + case _ => propagatedFailure(args) + case Nil => NoType + + /** Reports errors for arguments of `appTree` that have a `SearchFailureType`. + */ + def issueErrors(fun: Tree, args: List[Tree], failureType: Type): Tree = + val errorType = failureType match + case ai: AmbiguousImplicits => ai.asNested + case tp => tp + untpd.Apply(fun, args) + .withType(errorType) + .tap: res => + wtp.paramNames.lazyZip(wtp.paramInfos).lazyZip(args).foreach: (paramName, formal, arg) => + arg.tpe match + case failure: SearchFailureType => + val methodStr = err.refStr(methPart(fun).tpe) + val paramStr = implicitParamString(paramName, methodStr, fun) + val paramSym = fun.symbol.paramSymss.flatten.find(_.name == paramName) + val paramSymWithMethodCallTree = paramSym.map((_, res)) + val msg = missingArgMsg(arg, formal, paramStr, paramSymWithMethodCallTree) + report.error(msg, tree.srcPos.endPos) + case _ => val args = implicitArgs(wtp.paramInfos, 0, pt) - if (args.tpes.exists(_.isInstanceOf[SearchFailureType])) { + val failureType = propagatedFailure(args) + if failureType.exists then // If there are several arguments, some arguments might already // have influenced the context, binding variables, but later ones // might fail. In that case the constraint and instantiated variables @@ -4283,32 +4325,40 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // If method has default params, fall back to regular application // where all inferred implicits are passed as named args. - if hasDefaultParams then + if hasDefaultParams && !failureType.isInstanceOf[AmbiguousImplicits] then // Only keep the arguments that don't have an error type, or that - // have an `AmbiguousImplicits` error type. The later ensures that a + // have an `AmbiguousImplicits` error type. The latter ensures that a // default argument can't override an ambiguous implicit. See tests // `given-ambiguous-default*` and `19414*`. val namedArgs = - wtp.paramNames.lazyZip(args) - .filter((_, arg) => !arg.tpe.isError || arg.tpe.isInstanceOf[AmbiguousImplicits]) - .map((pname, arg) => untpd.NamedArg(pname, untpd.TypedSplice(arg))) - - val app = cpy.Apply(tree)(untpd.TypedSplice(tree), namedArgs) - val needsUsing = wtp.isContextualMethod || wtp.match - case MethodType(ContextBoundParamName(_) :: _) => sourceVersion.isAtLeast(`3.4`) - case _ => false - if needsUsing then app.setApplyKind(ApplyKind.Using) - typr.println(i"try with default implicit args $app") - val retyped = typed(app, pt, locked) - - // If the retyped tree still has an error type and is an `Apply` - // node, we can report the errors for each argument nicely. - // Otherwise, we don't report anything here. - retyped match - case Apply(tree, args) if retyped.tpe.isError => issueErrors(tree, args) - case _ => retyped - else issueErrors(tree, args) - } + wtp.paramNames.lazyZip(args).collect: + case (pname, arg) if !arg.tpe.isError || arg.tpe.isInstanceOf[AmbiguousImplicits] => + untpd.NamedArg(pname, untpd.TypedSplice(arg)) + .toList + val usingDefaultArgs = + wtp.paramNames.zipWithIndex + .exists((n, i) => !namedArgs.exists(_.name == n) && !findDefaultArgument(i).isEmpty) + + if !usingDefaultArgs then + issueErrors(tree, args, failureType) + else + val app = cpy.Apply(tree)(untpd.TypedSplice(tree), namedArgs) + // old-style implicit needs to be marked using so that implicits are searched + val needsUsing = wtp.isImplicitMethod || wtp.match + case MethodType(ContextBoundParamName(_) :: _) => sourceVersion.isAtLeast(`3.4`) + case _ => false + if needsUsing then app.setApplyKind(ApplyKind.Using) + typr.println(i"try with default implicit args $app") + // If the retyped tree still has an error type and is an `Apply` + // node, we can report the errors for each argument nicely. + // Otherwise, we don't report anything here. + typed(app, pt, locked) match + case retyped @ Apply(tree, args) if retyped.tpe.isError => + propagatedFailure(args) match + case sft: SearchFailureType => issueErrors(tree, args, sft) + case _ => issueErrors(tree, args, retyped.tpe) + case retyped => retyped + else issueErrors(tree, args, failureType) else inContext(origCtx): // Reset context in case it was set to a supercall context before. @@ -4566,12 +4616,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer /** Adapt an expression of constant type to a different constant type `tpe`. */ def adaptConstant(tree: Tree, tpe: ConstantType): Tree = { - def lit = Literal(tpe.value).withSpan(tree.span) + def lit = Literal(tpe.value).withSpan(tree.span).withAttachment(AdaptedTree, tree) tree match { case Literal(c) => lit case tree @ Block(stats, expr) => tpd.cpy.Block(tree)(stats, adaptConstant(expr, tpe)) case tree => - if (isIdempotentExpr(tree)) lit // See discussion in phase Literalize why we demand isIdempotentExpr + if isIdempotentExpr(tree) then lit // See discussion in phase FirstTransform why we demand isIdempotentExpr else Block(tree :: Nil, lit) } } diff --git a/compiler/src/dotty/tools/dotc/util/Chars.scala b/compiler/src/dotty/tools/dotc/util/Chars.scala index 916bdfa9dca3..e68c48903a63 100644 --- a/compiler/src/dotty/tools/dotc/util/Chars.scala +++ b/compiler/src/dotty/tools/dotc/util/Chars.scala @@ -50,7 +50,7 @@ object Chars: } /** Is character a whitespace character (but not a new line)? */ - def isWhitespace(c: Char): Boolean = + inline def isWhitespace(c: Char): Boolean = c == ' ' || c == '\t' || c == CR /** Can character form part of a doc comment variable $xxx? */ diff --git a/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala b/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala index d7837d9763fe..d132940dd03c 100644 --- a/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala +++ b/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala @@ -1,7 +1,7 @@ package dotty.tools.dotc.util import scala.collection.mutable.ArrayBuffer -import scala.util.chaining.* +import dotty.tools.dotc.util.chaining.* /** A wrapper for a list of cached instances of a type `T`. * The wrapper is recursion-reentrant: several instances are kept, so diff --git a/compiler/src/dotty/tools/dotc/util/SourceFile.scala b/compiler/src/dotty/tools/dotc/util/SourceFile.scala index 3ea43d16a7c8..1c264b395689 100644 --- a/compiler/src/dotty/tools/dotc/util/SourceFile.scala +++ b/compiler/src/dotty/tools/dotc/util/SourceFile.scala @@ -14,7 +14,7 @@ import scala.annotation.internal.sharable import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.compiletime.uninitialized -import scala.util.chaining.given +import dotty.tools.dotc.util.chaining.* import java.io.File.separator import java.net.URI @@ -276,12 +276,11 @@ object SourceFile { def apply(file: AbstractFile | Null, codec: Codec): SourceFile = // Files.exists is slow on Java 8 (https://rules.sonarsource.com/java/tag/performance/RSPEC-3725), - // so cope with failure; also deal with path prefix "Not a directory". + // so cope with failure. val chars = try new String(file.toByteArray, codec.charSet).toCharArray catch - case _: NoSuchFileException => Array.empty[Char] - case fse: FileSystemException if fse.getMessage.endsWith("Not a directory") => Array.empty[Char] + case _: FileSystemException => Array.empty[Char] if isScript(file, chars) then ScriptSourceFile(file, chars) diff --git a/compiler/src/dotty/tools/dotc/util/Spans.scala b/compiler/src/dotty/tools/dotc/util/Spans.scala index e1487408f36b..7d4bbe0e8180 100644 --- a/compiler/src/dotty/tools/dotc/util/Spans.scala +++ b/compiler/src/dotty/tools/dotc/util/Spans.scala @@ -59,6 +59,9 @@ object Spans { if (poff == SyntheticPointDelta) start else start + poff } + def pointMayBeIncorrect = + pointDelta == 0 && end - start >= SyntheticPointDelta + /** The difference between point and start in this span */ def pointDelta: Int = (coords >>> (StartEndBits * 2)).toInt diff --git a/compiler/src/dotty/tools/dotc/util/StackTraceOps.scala b/compiler/src/dotty/tools/dotc/util/StackTraceOps.scala index f991005f0c43..bd5c031a65e0 100644 --- a/compiler/src/dotty/tools/dotc/util/StackTraceOps.scala +++ b/compiler/src/dotty/tools/dotc/util/StackTraceOps.scala @@ -15,7 +15,7 @@ package dotty.tools.dotc.util import scala.language.unsafeNulls import collection.mutable, mutable.ListBuffer -import scala.util.chaining.given +import dotty.tools.dotc.util.chaining.* import java.lang.System.lineSeparator object StackTraceOps: diff --git a/compiler/src/dotty/tools/dotc/util/chaining.scala b/compiler/src/dotty/tools/dotc/util/chaining.scala new file mode 100644 index 000000000000..0c61ab6e73e9 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/chaining.scala @@ -0,0 +1,8 @@ + +package dotty.tools.dotc.util + +object chaining: + + extension [A](x: A) + inline def tap(inline f: A => Unit): x.type = { f(x): Unit; x } + inline def pipe[B](inline f: A => B): B = f(x) diff --git a/compiler/src/dotty/tools/io/Jar.scala b/compiler/src/dotty/tools/io/Jar.scala index dd33b1229610..eadc38e93007 100644 --- a/compiler/src/dotty/tools/io/Jar.scala +++ b/compiler/src/dotty/tools/io/Jar.scala @@ -50,7 +50,7 @@ class Jar(file: File) { def mainClass: Option[String] = manifest.map(_(Name.MAIN_CLASS)) /** The manifest-defined classpath String if available. */ def classPathString: Option[String] = - for (m <- manifest ; cp <- m.attrs.get(Name.CLASS_PATH)) yield cp + for (m <- manifest ; cp <- m.attrs.get(Name.CLASS_PATH) if !cp.trim().isEmpty()) yield cp def classPathElements: List[String] = classPathString match { case Some(s) => s.split("\\s+").toList case _ => Nil diff --git a/compiler/src/dotty/tools/repl/ParseResult.scala b/compiler/src/dotty/tools/repl/ParseResult.scala index 6c9f95d4dca2..2b7740152fa4 100644 --- a/compiler/src/dotty/tools/repl/ParseResult.scala +++ b/compiler/src/dotty/tools/repl/ParseResult.scala @@ -52,6 +52,27 @@ object Load { val command: String = ":load" } +/** `:require` is a deprecated alias for :jar` + */ +case class Require(path: String) extends Command +object Require { + val command: String = ":require" +} + +/** `:jar ` adds a jar to the classpath + */ +case class JarCmd(path: String) extends Command +object JarCmd { + val command: String = ":jar" +} + +/** `:kind ` display the kind of a type. see also :help kind + */ +case class KindOf(expr: String) extends Command +object KindOf { + val command: String = ":kind" +} + /** To find out the type of an expression you may simply do: * * ``` @@ -93,6 +114,12 @@ object Reset { val command: String = ":reset" } +/** `:sh ` run a shell command (result is implicitly => List[String]) */ +case class Sh(expr: String) extends Command +object Sh { + val command: String = ":sh" +} + /** Toggle automatic printing of results */ case object Silent extends Command: val command: String = ":silent" @@ -138,10 +165,14 @@ object ParseResult { Help.command -> (_ => Help), Reset.command -> (arg => Reset(arg)), Imports.command -> (_ => Imports), + JarCmd.command -> (arg => JarCmd(arg)), + KindOf.command -> (arg => KindOf(arg)), Load.command -> (arg => Load(arg)), + Require.command -> (arg => Require(arg)), TypeOf.command -> (arg => TypeOf(arg)), DocOf.command -> (arg => DocOf(arg)), Settings.command -> (arg => Settings(arg)), + Sh.command -> (arg => Sh(arg)), Silent.command -> (_ => Silent), ) diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index f909abfc129a..087eb836dfcb 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -12,16 +12,16 @@ import dotty.tools.dotc.core.Phases.Phase import dotty.tools.dotc.core.StdNames.* import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.reporting.Diagnostic -import dotty.tools.dotc.transform.PostTyper +import dotty.tools.dotc.transform.{CheckUnused, CheckShadowing, PostTyper} import dotty.tools.dotc.typer.ImportInfo.{withRootImports, RootRef} import dotty.tools.dotc.typer.TyperPhase import dotty.tools.dotc.util.Spans.* import dotty.tools.dotc.util.{ParsedComment, Property, SourceFile} import dotty.tools.dotc.{CompilationUnit, Compiler, Run} import dotty.tools.repl.results.* +import dotty.tools.dotc.util.chaining.* import scala.collection.mutable -import scala.util.chaining.given /** This subclass of `Compiler` is adapted for use in the REPL. * @@ -37,6 +37,7 @@ class ReplCompiler extends Compiler: List(Parser()), List(ReplPhase()), List(TyperPhase(addRootImports = false)), + List(CheckUnused.PostTyper(), CheckShadowing()), List(CollectTopLevelImports()), List(PostTyper()), ) diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 58b40f648627..6f682ac96a1a 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -7,6 +7,7 @@ import java.nio.charset.StandardCharsets import dotty.tools.dotc.ast.Trees.* import dotty.tools.dotc.ast.{tpd, untpd} +import dotty.tools.dotc.classpath.ClassPathFactory import dotty.tools.dotc.config.CommandLineParser.tokenize import dotty.tools.dotc.config.Properties.{javaVersion, javaVmName, simpleVersionString} import dotty.tools.dotc.core.Contexts.* @@ -21,6 +22,7 @@ import dotty.tools.dotc.core.NameOps.* import dotty.tools.dotc.core.Names.Name import dotty.tools.dotc.core.StdNames.* import dotty.tools.dotc.core.Symbols.{Symbol, defn} +import dotty.tools.dotc.core.SymbolLoaders import dotty.tools.dotc.interfaces import dotty.tools.dotc.interactive.Completion import dotty.tools.dotc.printing.SyntaxHighlighting @@ -39,6 +41,7 @@ import scala.annotation.tailrec import scala.collection.mutable import scala.compiletime.uninitialized import scala.jdk.CollectionConverters.* +import scala.tools.asm.ClassReader import scala.util.control.NonFatal import scala.util.Using @@ -153,7 +156,9 @@ class ReplDriver(settings: Array[String], * * Possible reason for unsuccessful run are raised flags in CLI like --help or --version */ - final def tryRunning = if shouldStart then runUntilQuit() + final def tryRunning = if shouldStart then + if rootCtx.settings.replQuitAfterInit.value(using rootCtx) then initialState + else runUntilQuit() /** Run REPL with `state` until `:quit` command found * @@ -524,6 +529,65 @@ class ReplDriver(settings: Array[String], state } + case Require(path) => + out.println(":require is no longer supported, but has been replaced with :jar. Please use :jar") + state + + case JarCmd(path) => + val jarFile = AbstractFile.getDirectory(path) + if (jarFile == null) + out.println(s"""Cannot add "$path" to classpath.""") + state + else + def flatten(f: AbstractFile): Iterator[AbstractFile] = + if (f.isClassContainer) f.iterator.flatMap(flatten) + else Iterator(f) + + def tryClassLoad(classFile: AbstractFile): Option[String] = { + val input = classFile.input + try { + val reader = new ClassReader(input) + val clsName = reader.getClassName.replace('/', '.') + rendering.myClassLoader.loadClass(clsName) + Some(clsName) + } catch + case _: ClassNotFoundException => None + finally { + input.close() + } + } + + try { + val entries = flatten(jarFile) + + val existingClass = entries.filter(_.ext.isClass).find(tryClassLoad(_).isDefined) + if (existingClass.nonEmpty) + out.println(s"The path '$path' cannot be loaded, it contains a classfile that already exists on the classpath: ${existingClass.get}") + else inContext(state.context): + val jarClassPath = ClassPathFactory.newClassPath(jarFile) + val prevOutputDir = ctx.settings.outputDir.value + + // add to compiler class path + ctx.platform.addToClassPath(jarClassPath) + SymbolLoaders.mergeNewEntries(defn.RootClass, ClassPath.RootPackage, jarClassPath, ctx.platform.classPath) + + // new class loader with previous output dir and specified jar + val prevClassLoader = rendering.classLoader() + val jarClassLoader = fromURLsParallelCapable( + jarClassPath.asURLs, prevClassLoader) + rendering.myClassLoader = new AbstractFileClassLoader( + prevOutputDir, jarClassLoader) + + out.println(s"Added '$path' to classpath.") + } catch { + case e: Throwable => + out.println(s"Failed to load '$path' to classpath: ${e.getMessage}") + } + state + + case KindOf(expr) => + out.println(s"""The :kind command is not currently supported.""") + state case TypeOf(expr) => expr match { case "" => out.println(s":type ") @@ -546,6 +610,10 @@ class ReplDriver(settings: Array[String], } state + case Sh(expr) => + out.println(s"""The :sh command is deprecated. Use `import scala.sys.process._` and `"command".!` instead.""") + state + case Settings(arg) => arg match case "" => given ctx: Context = state.context diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index fd5b635e11e2..a93e010ddc34 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -25,6 +25,8 @@ import scala.quoted.runtime.impl.printers.* import scala.reflect.TypeTest import dotty.tools.dotc.core.NameKinds.ExceptionBinderName import dotty.tools.dotc.transform.TreeChecker +import dotty.tools.dotc.core.Names +import dotty.tools.dotc.util.Spans.NoCoord object QuotesImpl { @@ -241,9 +243,35 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object ClassDef extends ClassDefModule: def apply(cls: Symbol, parents: List[Tree], body: List[Statement]): ClassDef = - val untpdCtr = untpd.DefDef(nme.CONSTRUCTOR, Nil, tpd.TypeTree(dotc.core.Symbols.defn.UnitClass.typeRef), tpd.EmptyTree) + val paramsDefs: List[untpd.ParamClause] = + cls.primaryConstructor.paramSymss.map { paramSym => + if paramSym.headOption.map(_.isType).getOrElse(false) then + paramSym.map(sym => TypeDef(sym)) + else + paramSym.map(ValDef(_, None)) + } + def throwError() = + throw new RuntimeException( + "Symbols necessary for creation of the ClassDef tree could not be found." + ) + val paramsAccessDefs: List[untpd.ParamClause] = + cls.primaryConstructor.paramSymss.map { paramSym => + if paramSym.headOption.map(_.isType).getOrElse(false) then + paramSym.map { symm => + def isParamAccessor(memberSym: Symbol) = memberSym.flags.is(Flags.Param) && memberSym.name == symm.name + TypeDef(cls.typeMembers.find(isParamAccessor).getOrElse(throwError())) + } + else + paramSym.map { symm => + def isParam(memberSym: Symbol) = memberSym.flags.is(Flags.ParamAccessor) && memberSym.name == symm.name + ValDef(cls.fieldMembers.find(isParam).getOrElse(throwError()), None) + } + } + + val termSymbol: dotc.core.Symbols.TermSymbol = cls.primaryConstructor.asTerm + val untpdCtr = untpd.DefDef(nme.CONSTRUCTOR, paramsDefs, tpd.TypeTree(dotc.core.Symbols.defn.UnitClass.typeRef), tpd.EmptyTree) val ctr = ctx.typeAssigner.assignType(untpdCtr, cls.primaryConstructor) - tpd.ClassDefWithParents(cls.asClass, ctr, parents, body) + tpd.ClassDefWithParents(cls.asClass, ctr, parents, paramsAccessDefs.flatten ++ body) def copy(original: Tree)(name: String, constr: DefDef, parents: List[Tree], selfOpt: Option[ValDef], body: List[Statement]): ClassDef = { val dotc.ast.Trees.TypeDef(_, originalImpl: tpd.Template) = original: @unchecked @@ -473,7 +501,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler withDefaultPos(tpd.ref(tp).asInstanceOf[tpd.RefTree]) def apply(sym: Symbol): Ref = assert(sym.isTerm, s"expected a term symbol, but received $sym") - val refTree = tpd.ref(sym) match + val refTree = tpd.generalisedRef(sym) match case t @ tpd.This(ident) => // not a RefTree, so we need to work around this - issue #19732 // ident in `This` can be a TypeIdent of sym, so we manually prepare the ref here, // knowing that the owner is actually `This`. @@ -824,7 +852,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object BlockTypeTest extends TypeTest[Tree, Block]: def unapply(x: Tree): Option[Block & x.type] = x match - case x: (tpd.Block & x.type) => Some(x) + case x: (tpd.Block & x.type) if x.isTerm => Some(x) case _ => None end BlockTypeTest @@ -1413,7 +1441,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object TypeBlockTypeTest extends TypeTest[Tree, TypeBlock]: def unapply(x: Tree): Option[TypeBlock & x.type] = x match - case tpt: (tpd.Block & x.type) => Some(tpt) + case tpt: (tpd.Block & x.type) if x.isType => Some(tpt) case _ => None end TypeBlockTypeTest @@ -1694,6 +1722,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end SimpleSelectorTypeTest object SimpleSelector extends SimpleSelectorModule: + def apply(name: String): SimpleSelector = + withDefaultPos(untpd.ImportSelector(untpd.Ident(name.toTermName))) def unapply(x: SimpleSelector): Some[String] = Some(x.name.toString) end SimpleSelector @@ -1713,6 +1743,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end RenameSelectorTypeTest object RenameSelector extends RenameSelectorModule: + def apply(fromName: String, toName: String): RenameSelector = + withDefaultPos(untpd.ImportSelector(untpd.Ident(fromName.toTermName), untpd.Ident(toName.toTermName))) def unapply(x: RenameSelector): (String, String) = (x.fromName, x.toName) end RenameSelector @@ -1738,6 +1770,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end OmitSelectorTypeTest object OmitSelector extends OmitSelectorModule: + def apply(name: String): OmitSelector = + withDefaultPos(untpd.ImportSelector(untpd.Ident(name.toTermName), untpd.Ident(nme.WILDCARD))) def unapply(x: OmitSelector): Some[String] = Some(x.imported.name.toString) end OmitSelector @@ -1758,6 +1792,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end GivenSelectorTypeTest object GivenSelector extends GivenSelectorModule: + def apply(bound: Option[TypeTree]): GivenSelector = + withDefaultPos(untpd.ImportSelector( + untpd.Ident(nme.EMPTY), + bound = bound.map(tpt => untpd.TypedSplice(tpt)).getOrElse(EmptyTree) + )) def unapply(x: GivenSelector): Some[Option[TypeTree]] = Some(GivenSelectorMethods.bound(x)) end GivenSelector @@ -1826,7 +1865,15 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def termSymbol: Symbol = self.termSymbol def isSingleton: Boolean = self.isSingleton def memberType(member: Symbol): TypeRepr = - member.info.asSeenFrom(self, member.owner) + // we replace thisTypes here to avoid resolving otherwise unstable prefixes into Nothing + val memberInfo = + if self.typeSymbol.isClassDef then + member.info.substThis(self.classSymbol.asClass, self) + else + member.info + memberInfo + .asSeenFrom(self, member.owner) + def baseClasses: List[Symbol] = self.baseClasses def baseType(cls: Symbol): TypeRepr = self.baseType(cls) def derivesFrom(cls: Symbol): Boolean = self.derivesFrom(cls) @@ -2519,7 +2566,17 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object ClassOfConstant extends ClassOfConstantModule: def apply(x: TypeRepr): ClassOfConstant = - // TODO check that the type is a valid class when creating this constant or let Ycheck do it? + // We only check if the supplied TypeRepr is valid if it contains an Array, + // as so far only that Array could cause issues + def correctTypeApplicationForArray(typeRepr: TypeRepr): Boolean = + val isArray = typeRepr.typeSymbol != dotc.core.Symbols.defn.ArrayClass + typeRepr match + case AppliedType(_, targs) if !targs.isEmpty => true + case _ => isArray + xCheckMacroAssert( + correctTypeApplicationForArray(x), + "Illegal empty Array type constructor. Please supply a type parameter." + ) dotc.core.Constants.Constant(x) def unapply(constant: ClassOfConstant): Some[TypeRepr] = Some(constant.typeValue) end ClassOfConstant @@ -2533,6 +2590,14 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler // See tests/pos-macros/exprSummonWithTypeVar with -Xcheck-macros. dotc.typer.Inferencing.fullyDefinedType(implicitTree.tpe, "", implicitTree) implicitTree + def searchIgnoring(tpe: TypeRepr)(ignored: Symbol*): ImplicitSearchResult = + import tpd.TreeOps + val implicitTree = ctx.typer.inferImplicitArg(tpe, Position.ofMacroExpansion.span, ignored.toSet) + // Make sure that we do not have any uninstantiated type variables. + // See tests/pos-macros/i16636. + // See tests/pos-macros/exprSummonWithTypeVar with -Xcheck-macros. + dotc.typer.Inferencing.fullyDefinedType(implicitTree.tpe, "", implicitTree) + implicitTree end Implicits type ImplicitSearchResult = Tree @@ -2618,8 +2683,134 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler for sym <- decls(cls) do cls.enter(sym) cls - def newModule(owner: Symbol, name: String, modFlags: Flags, clsFlags: Flags, parents: List[TypeRepr], decls: Symbol => List[Symbol], privateWithin: Symbol): Symbol = - assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class") + def newClass( + owner: Symbol, + name: String, + parents: Symbol => List[TypeRepr], + decls: Symbol => List[Symbol], + selfType: Option[TypeRepr], + clsFlags: Flags, + clsPrivateWithin: Symbol, + conParams: List[(String, TypeRepr)] + ): Symbol = + val (conParamNames, conParamTypes) = conParams.unzip + newClass( + owner, + name, + parents, + decls, + selfType, + clsFlags, + clsPrivateWithin, + Nil, + conMethodType = res => MethodType(conParamNames)(_ => conParamTypes, _ => res), + conFlags = Flags.EmptyFlags, + conPrivateWithin = Symbol.noSymbol, + conParamFlags = List(for i <- conParamNames yield Flags.EmptyFlags), + conParamPrivateWithins = List(for i <- conParamNames yield Symbol.noSymbol) + ) + + def newClass( + owner: Symbol, + name: String, + parents: Symbol => List[TypeRepr], + decls: Symbol => List[Symbol], + selfType: Option[TypeRepr], + clsFlags: Flags, + clsPrivateWithin: Symbol, + clsAnnotations: List[Term], + conMethodType: TypeRepr => MethodOrPoly, + conFlags: Flags, + conPrivateWithin: Symbol, + conParamFlags: List[List[Flags]], + conParamPrivateWithins: List[List[Symbol]] + ) = + assert(!clsPrivateWithin.exists || clsPrivateWithin.isType, "clsPrivateWithin must be a type symbol or `Symbol.noSymbol`") + assert(!conPrivateWithin.exists || conPrivateWithin.isType, "consPrivateWithin must be a type symbol or `Symbol.noSymbol`") + checkValidFlags(clsFlags.toTypeFlags, Flags.validClassFlags) + checkValidFlags(conFlags.toTermFlags, Flags.validClassConstructorFlags) + val cls = dotc.core.Symbols.newNormalizedClassSymbol( + owner, + name.toTypeName, + clsFlags, + parents, + selfType.getOrElse(Types.NoType), + clsPrivateWithin, + clsAnnotations, + NoCoord, + compUnitInfo = null + ) + val methodType: MethodOrPoly = conMethodType(cls.typeRef) + def throwShapeException() = throw new Exception("Shapes of conMethodType and conParamFlags differ.") + def checkMethodOrPolyShape(checkedMethodType: TypeRepr, clauseIdx: Int): Unit = + checkedMethodType match + case PolyType(params, _, res) if clauseIdx == 0 => + if (conParamFlags.length < clauseIdx) throwShapeException() + if (conParamFlags(clauseIdx).length != params.length) throwShapeException() + checkMethodOrPolyShape(res, clauseIdx + 1) + case PolyType(_, _, _) => throw new Exception("Clause interleaving not supported for constructors") + case MethodType(params, _, res) => + if (conParamFlags.length <= clauseIdx) throwShapeException() + if (conParamFlags(clauseIdx).length != params.length) throwShapeException() + checkMethodOrPolyShape(res, clauseIdx + 1) + case other => + xCheckMacroAssert( + other.typeSymbol == cls, + "Incorrect type returned from the innermost PolyOrMethod." + ) + (other, methodType) match + case (AppliedType(tycon, args), pt: PolyType) => + xCheckMacroAssert( + args.length == pt.typeParams.length && + args.zip(pt.typeParams).forall { + case (arg, param) => arg == param.paramRef + }, + "Constructor result type does not correspond to the declared type parameters" + ) + case _ => + xCheckMacroAssert( + !(other.isInstanceOf[AppliedType] || methodType.isInstanceOf[PolyType]), + "AppliedType has to be the innermost resultTypeExp result if and only if conMethodType returns a PolyType" + ) + checkMethodOrPolyShape(methodType, clauseIdx = 0) + + cls.enter(dotc.core.Symbols.newSymbol(cls, nme.CONSTRUCTOR, Flags.Synthetic | Flags.Method | conFlags, methodType, conPrivateWithin, dotty.tools.dotc.util.Spans.NoCoord)) + + case class ParamSymbolData(name: String, tpe: TypeRepr, isTypeParam: Boolean, clauseIdx: Int, elementIdx: Int) + def getParamSymbolsData(methodType: TypeRepr, clauseIdx: Int): List[ParamSymbolData] = + methodType match + case MethodType(paramInfosExp, resultTypeExp, res) => + paramInfosExp.zip(resultTypeExp).zipWithIndex.map { case ((name, tpe), elementIdx) => + ParamSymbolData(name, tpe, isTypeParam = false, clauseIdx, elementIdx) + } ++ getParamSymbolsData(res, clauseIdx + 1) + case pt @ PolyType(paramNames, paramBounds, res) => + paramNames.zip(paramBounds).zipWithIndex.map {case ((name, tpe), elementIdx) => + ParamSymbolData(name, tpe, isTypeParam = true, clauseIdx, elementIdx) + } ++ getParamSymbolsData(res, clauseIdx + 1) + case result => + List() + // Maps PolyType indexes to type parameter symbol typerefs + val paramRefMap = collection.mutable.HashMap[Int, Symbol]() + val paramRefRemapper = new Types.TypeMap { + def apply(tp: Types.Type) = tp match { + case pRef: ParamRef if pRef.binder == methodType => paramRefMap(pRef.paramNum).typeRef + case _ => mapOver(tp) + } + } + for case ParamSymbolData(name, tpe, isTypeParam, clauseIdx, elementIdx) <- getParamSymbolsData(methodType, 0) do + if isTypeParam then + checkValidFlags(conParamFlags(clauseIdx)(elementIdx).toTypeFlags, Flags.validClassTypeParamFlags) + val symbol = dotc.core.Symbols.newSymbol(cls, name.toTypeName, Flags.Param | Flags.Deferred | Flags.Private | Flags.PrivateLocal | Flags.Local | conParamFlags(clauseIdx)(elementIdx), tpe, conParamPrivateWithins(clauseIdx)(elementIdx)) + paramRefMap.addOne(elementIdx, symbol) + cls.enter(symbol) + else + checkValidFlags(conParamFlags(clauseIdx)(elementIdx).toTermFlags, Flags.validClassTermParamFlags) + val fixedType = paramRefRemapper(tpe) + cls.enter(dotc.core.Symbols.newSymbol(cls, name.toTermName, Flags.ParamAccessor | conParamFlags(clauseIdx)(elementIdx), fixedType, conParamPrivateWithins(clauseIdx)(elementIdx))) + for sym <- decls(cls) do cls.enter(sym) + cls + + def newModule(owner: Symbol, name: String, modFlags: Flags, clsFlags: Flags, parents: Symbol => List[TypeRepr], decls: Symbol => List[Symbol], privateWithin: Symbol): Symbol = assert(!privateWithin.exists || privateWithin.isType, "privateWithin must be a type symbol or `Symbol.noSymbol`") val mod = dotc.core.Symbols.newNormalizedModuleSymbol( owner, @@ -2628,7 +2819,10 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler clsFlags | dotc.core.Flags.ModuleClassCreationFlags, parents, dotc.core.Scopes.newScope, - privateWithin) + privateWithin, + NoCoord, + compUnitInfo = null + ) val cls = mod.moduleClass.asClass cls.enter(dotc.core.Symbols.newConstructor(cls, dotc.core.Flags.Synthetic, Nil, Nil)) for sym <- decls(cls) do cls.enter(sym) @@ -3026,6 +3220,18 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler // Keep: aligned with Quotes's `newTypeAlias` doc private[QuotesImpl] def validTypeAliasFlags: Flags = Private | Protected | Override | Final | Infix | Local + // Keep: aligned with Quotes's `newClass` + private[QuotesImpl] def validClassFlags: Flags = Private | Protected | PrivateLocal | Local | Final | Trait | Abstract | Open + + // Keep: aligned with Quote's 'newClass' + private[QuotesImpl] def validClassConstructorFlags: Flags = Synthetic | Method | Private | Protected | PrivateLocal | Local + + // Keep: aligned with Quotes's `newClass` + private[QuotesImpl] def validClassTypeParamFlags: Flags = Param | Deferred | Private | PrivateLocal | Local + + // Keep: aligned with Quotes's `newClass` + private[QuotesImpl] def validClassTermParamFlags: Flags = ParamAccessor | Private | Protected | PrivateLocal | Local + end Flags given FlagsMethods: FlagsMethods with diff --git a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala index 64a0ff9db9ec..7c7a688eccb2 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala @@ -1379,13 +1379,13 @@ object SourceCode { printTypeTree(bounds.low) else bounds.low match { - case Inferred() => + case Inferred() if bounds.low.tpe.typeSymbol == TypeRepr.of[Nothing].typeSymbol => case low => this += " >: " printTypeTree(low) } bounds.hi match { - case Inferred() => this + case Inferred() if bounds.hi.tpe.typeSymbol == TypeRepr.of[Any].typeSymbol => this case hi => this += " <: " printTypeTree(hi) diff --git a/compiler/test-resources/jars/MyLibrary.scala b/compiler/test-resources/jars/MyLibrary.scala new file mode 100644 index 000000000000..bd752d2df864 --- /dev/null +++ b/compiler/test-resources/jars/MyLibrary.scala @@ -0,0 +1,8 @@ +/** + * JAR used for testing repl :jar + * Generated using: mkdir out; scalac -d out MyLibrary.scala; jar cf mylibrary.jar -C out . + */ +package mylibrary + +object Utils: + def greet(name: String): String = s"Hello, $name!" diff --git a/compiler/test-resources/jars/MyLibrary2.scala b/compiler/test-resources/jars/MyLibrary2.scala new file mode 100644 index 000000000000..df5a944c0bcf --- /dev/null +++ b/compiler/test-resources/jars/MyLibrary2.scala @@ -0,0 +1,9 @@ +/** + * JAR used for testing repl :jar + * Generated using: mkdir out2; scalac -d out MyLibrary2.scala; jar cf mylibrary2.jar -C out2 . + */ +package mylibrary2 + +object Utils2: + def greet(name: String): String = s"Greetings, $name!" + diff --git a/compiler/test-resources/jars/mylibrary.jar b/compiler/test-resources/jars/mylibrary.jar new file mode 100644 index 000000000000..4537e10ee491 Binary files /dev/null and b/compiler/test-resources/jars/mylibrary.jar differ diff --git a/compiler/test-resources/jars/mylibrary2.jar b/compiler/test-resources/jars/mylibrary2.jar new file mode 100644 index 000000000000..1d6584bdf545 Binary files /dev/null and b/compiler/test-resources/jars/mylibrary2.jar differ diff --git a/compiler/test-resources/repl/i21655 b/compiler/test-resources/repl/i21655 new file mode 100644 index 000000000000..57b09bfad32d --- /dev/null +++ b/compiler/test-resources/repl/i21655 @@ -0,0 +1,2 @@ +scala>:kind +The :kind command is not currently supported. \ No newline at end of file diff --git a/compiler/test-resources/repl/i21657 b/compiler/test-resources/repl/i21657 new file mode 100644 index 000000000000..fa2eec3ac891 --- /dev/null +++ b/compiler/test-resources/repl/i21657 @@ -0,0 +1,2 @@ +scala>:sh +The :sh command is deprecated. Use `import scala.sys.process._` and `"command".!` instead. \ No newline at end of file diff --git a/compiler/test-resources/repl/jar-command b/compiler/test-resources/repl/jar-command new file mode 100644 index 000000000000..b0ded22d3654 --- /dev/null +++ b/compiler/test-resources/repl/jar-command @@ -0,0 +1,13 @@ +scala> val z = 1 +val z: Int = 1 + +scala>:jar sbt-test/source-dependencies/canon/actual/a.jar +Added 'sbt-test/source-dependencies/canon/actual/a.jar' to classpath. + +scala> import A.x + +scala> x +val res0: Int = 3 + +scala> z +val res1: Int = 1 diff --git a/compiler/test-resources/repl/jar-errors b/compiler/test-resources/repl/jar-errors new file mode 100644 index 000000000000..7d5720fcb233 --- /dev/null +++ b/compiler/test-resources/repl/jar-errors @@ -0,0 +1,11 @@ +scala>:jar path/does/not/exist +Cannot add "path/does/not/exist" to classpath. + +scala>:jar sbt-test/source-dependencies/canon/actual/a.jar +Added 'sbt-test/source-dependencies/canon/actual/a.jar' to classpath. + +scala>:jar sbt-test/source-dependencies/canon/actual/a.jar +The path 'sbt-test/source-dependencies/canon/actual/a.jar' cannot be loaded, it contains a classfile that already exists on the classpath: sbt-test/source-dependencies/canon/actual/a.jar(A.class) + +scala>:require sbt-test/source-dependencies/canon/actual/a.jar +:require is no longer supported, but has been replaced with :jar. Please use :jar \ No newline at end of file diff --git a/compiler/test-resources/repl/jar-multiple b/compiler/test-resources/repl/jar-multiple new file mode 100644 index 000000000000..453ccc40dbf6 --- /dev/null +++ b/compiler/test-resources/repl/jar-multiple @@ -0,0 +1,32 @@ +scala> val z = 1 +val z: Int = 1 + +scala>:jar compiler/test-resources/jars/mylibrary.jar +Added 'compiler/test-resources/jars/mylibrary.jar' to classpath. + +scala> import mylibrary.Utils + +scala> Utils.greet("Alice") +val res0: String = Hello, Alice! + +scala>:jar compiler/test-resources/jars/mylibrary2.jar +Added 'compiler/test-resources/jars/mylibrary2.jar' to classpath. + +scala> import mylibrary2.Utils2 + +scala> Utils2.greet("Alice") +val res1: String = Greetings, Alice! + +scala> Utils.greet("Alice") +val res2: String = Hello, Alice! + +scala> import mylibrary.Utils.greet + +scala> greet("Tom") +val res3: String = Hello, Tom! + +scala> Utils.greet("Alice") +val res4: String = Hello, Alice! + +scala> z +val res5: Int = 1 diff --git a/compiler/test/debug/Gen b/compiler/test/debug/Gen deleted file mode 100755 index 7212b9cbfb62..000000000000 --- a/compiler/test/debug/Gen +++ /dev/null @@ -1,13 +0,0 @@ -#! /usr/bin/env bash - -DIR="$( cd "$( dirname "$0" )" && pwd )" - -SOURCE=$DIR/Gen.scala -CLASS=./Gen.class - -if [ ! -e $CLASS ] || [ $SOURCE -nt $CLASS ]; then - ./bin/scalac $DIR/Gen.scala -fi - -./bin/scala Gen $@ - diff --git a/compiler/test/debug/Gen.scala b/compiler/test/debug/Gen.scala deleted file mode 100755 index 646dec189f10..000000000000 --- a/compiler/test/debug/Gen.scala +++ /dev/null @@ -1,174 +0,0 @@ -import scala.language.unsafeNulls - -import scala.io.Source -import scala.collection.mutable.ListBuffer -import scala.util.Using - -/** Automate testing debuggability of generated code using JDB and expect - * - * The debugging information is annotated as comments to the code in brackets: - * - * val x = f(3) // [break] [next: line=5] - * val y = 5 - * - * 1. A jdb command must be wrapped in brackets, like `[step]`. All jdb commands can be used. - * 2. To check output of jdb for a command, use `[cmd: expect]`. - * 3. If `expect` is wrapped in double quotes, regex is supported. - * 4. Break commands are collected and set globally. - * 5. Other commands will be send to jdb in the order they appear in the source file - * - * Note: jdb uses line number starts from 1 - */ - -object Gen { - val MainObject = "Test" - val CommandWait = 1 - - sealed trait Tree - - case class Break(line: Int) extends Tree - - case class Command(val name: String, val expect: Expect = EmptyExpect) extends Tree - - sealed trait Expect - - case object EmptyExpect extends Expect - - case class LitExpect(lit: String) extends Expect - - case class PatExpect(pat: String) extends Expect - - case class Program(breaks: Seq[Break], commands: Seq[Command]) - - def error(msg: String): Nothing = { - throw new Exception(msg) - } - - def parseCommand(command: String, lineNo: Int): Tree = { - val index = command.indexOf(':') - if (index == -1) { - // simple command - if (command == "break") Break(lineNo) - else Command(command) - } else { - val Seq(cmd, rhs) = command.split(":", 2).toSeq.map(_.trim) - if (rhs.startsWith("\"")) { - // regex match - val content = "\"(.+)\"".r - rhs match { - case content(expect) => Command(cmd, PatExpect(expect)) - case _ => error(s"""incorrect specification: `$rhs` for `$cmd` at line $lineNo. Ending " expected.""") - } - } else { - // literal match - Command(cmd, LitExpect(rhs)) - } - } - } - - def parse(file: String): Program = { - val lines = Using(Source.fromFile(file))(_.getLines().toBuffer).get - - val breaks = new ListBuffer[Break]() - val cmds = new ListBuffer[Command]() - lines.zipWithIndex.map { case (code, line) => - val comment = if (code.indexOf("//") != -1) code.split("//").last else "" - val regex = """(?<=\[).*?(?=\])""".r - for (p <- regex findAllIn comment) parseCommand(p.trim, line + 1) match { // jdb index from 0 - case b: Break => breaks += b - case c: Command => cmds += c - } - } - - Program(breaks.toList, cmds.toList) - } - - def generate(program: Program, source: String = "tests/debug/"): String = { - val Program(breaks, cmds) = program - val breakpoints = (breaks.map { - case Break(point) => - s"""|send "stop at $MainObject$$:$point\\r" - |sleep $CommandWait - |expect "breakpoint $MainObject$$:$point" - |expect -re $$ - """.stripMargin - }).mkString("\n\n") - - val commands = (cmds.map { - case Command(cmd, EmptyExpect) => - s"""|# send_user "send command `$cmd`\\n" - |send "$cmd\\r" - |sleep $CommandWait - |expect -re $$ - """.stripMargin - case Command(cmd, LitExpect(lit)) => - s"""|# send_user "send command `$cmd`\\n" - |send "$cmd\\r" - |sleep $CommandWait - |expect { - | "*$lit*" { send_user "success - $cmd : $lit \\n" } - | timeout { - | send_user "timeout while waiting for response: $cmd : $lit\\n" - | exit 1 - | } - |} - |expect -re $$ - |""".stripMargin - case Command(cmd, PatExpect(pat)) => - s"""|# send_user "send command `$cmd`\\n" - |send "$cmd\\r" - |sleep $CommandWait - |expect { - | -re {$pat} { send_user "success - $cmd : $pat \\n" } - | timeout { - | send_user "timeout while waiting for response: $cmd : $pat\\n" - | exit 1 - | } - |} - |expect -re $$ - |""".stripMargin - }).mkString("\n\n") - - s"""|#!/usr/bin/expect - | - |# log_user 1 - |# exp_internal 1 - |# set timeout 5 - | - |send_user "spawning job...\\n" - | - |spawn jdb -attach 5005 -sourcepath $source - | - |send_user "interacting...\\n" - | - |expect { - | "*VM Started*" { send_user "success - connected to server \\n" } - | timeout { - | send_user "timeout while waiting for: *VM Started*\\n" - | exit 1 - | } - |} - | - |send_user "setting breakpoints...\\n" - | - |# breakpoints - |$breakpoints - | - |# run - |send_user "run program...\\n" - |send "run\\r" - |expect "Breakpoint hit" - | - |# interactions - |$commands""".stripMargin - } - - def main(args: Array[String]): Unit = { - val prog = Gen.parse(args(0)) - // println("--------------------------------") - // println("prog:" + prog) - // println("\n\n\n scrip:") - // println("--------------------------------") - println(Gen.generate(prog)) - } -} diff --git a/compiler/test/debug/test b/compiler/test/debug/test deleted file mode 100755 index 6081862448e3..000000000000 --- a/compiler/test/debug/test +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash - -DIR="$( cd "$( dirname "$0" )" && pwd )" - -echo "start debug test..." -for file in tests/debug/*.scala; do - ./bin/scalac $file || exit 1 - ./bin/scala -d Test > /dev/null & - $DIR/Gen $file > robot - expect robot 2>&1 > /dev/null - - if [[ $? != 0 ]]; then - echo "debug test failed for file $file" - exit 1 - fi - - echo "$file -- success" -done - -echo "debug test success!" - diff --git a/compiler/test/dotc/neg-best-effort-pickling.blacklist b/compiler/test/dotc/neg-best-effort-pickling.excludelist similarity index 56% rename from compiler/test/dotc/neg-best-effort-pickling.blacklist rename to compiler/test/dotc/neg-best-effort-pickling.excludelist index 99a83a467f08..52371ecf17a5 100644 --- a/compiler/test/dotc/neg-best-effort-pickling.blacklist +++ b/compiler/test/dotc/neg-best-effort-pickling.excludelist @@ -13,11 +13,12 @@ curried-dependent-ift.scala i17121.scala illegal-match-types.scala i13780-1.scala -i20317a.scala -i11226.scala -i974.scala -i13864.scala + +i20317a.scala # recursion limit exceeded +i11226.scala # missing type +i974.scala # cyclic reference +i13864.scala # missing symbol in pickling +type-params.scala # recursion limit exceeded # semantic db generation fails in the first compilation -i1642.scala -i15158.scala +i15158.scala # cyclic reference - stack overflow diff --git a/compiler/test/dotc/neg-best-effort-unpickling.blacklist b/compiler/test/dotc/neg-best-effort-unpickling.excludelist similarity index 83% rename from compiler/test/dotc/neg-best-effort-unpickling.blacklist rename to compiler/test/dotc/neg-best-effort-unpickling.excludelist index 1e22d919f25a..d57f7e0176e8 100644 --- a/compiler/test/dotc/neg-best-effort-unpickling.blacklist +++ b/compiler/test/dotc/neg-best-effort-unpickling.excludelist @@ -15,3 +15,6 @@ overrideClass.scala # repeating on a top level type definition i18750.scala + +# Crash on invalid prefix ([A] =>> Int) +i22357a.scala diff --git a/compiler/test/dotc/neg-init-global-scala2-library-tasty.blacklist b/compiler/test/dotc/neg-init-global-scala2-library-tasty.excludelist similarity index 76% rename from compiler/test/dotc/neg-init-global-scala2-library-tasty.blacklist rename to compiler/test/dotc/neg-init-global-scala2-library-tasty.excludelist index 03b020db64d9..18a665e0119b 100644 --- a/compiler/test/dotc/neg-init-global-scala2-library-tasty.blacklist +++ b/compiler/test/dotc/neg-init-global-scala2-library-tasty.excludelist @@ -1,9 +1,6 @@ ## See #18882 patmat.scala t9312.scala -unapplySeq-implicit-arg.scala -unapplySeq-implicit-arg2.scala -unapplySeq-implicit-arg3.scala ScalaCheck.scala mutable-read8.scala TypeCast.scala diff --git a/compiler/test/dotc/neg-scala2-library-tasty.blacklist b/compiler/test/dotc/neg-scala2-library-tasty.excludelist similarity index 100% rename from compiler/test/dotc/neg-scala2-library-tasty.blacklist rename to compiler/test/dotc/neg-scala2-library-tasty.excludelist diff --git a/compiler/test/dotc/patmat-exhaustivity-scala2-library-tasty.blacklist b/compiler/test/dotc/patmat-exhaustivity-scala2-library-tasty.excludelist similarity index 100% rename from compiler/test/dotc/patmat-exhaustivity-scala2-library-tasty.blacklist rename to compiler/test/dotc/patmat-exhaustivity-scala2-library-tasty.excludelist diff --git a/compiler/test/dotc/pos-from-tasty.blacklist b/compiler/test/dotc/pos-from-tasty.excludelist similarity index 100% rename from compiler/test/dotc/pos-from-tasty.blacklist rename to compiler/test/dotc/pos-from-tasty.excludelist diff --git a/compiler/test/dotc/pos-init-global-scala2-library-tasty.blacklist b/compiler/test/dotc/pos-init-global-scala2-library-tasty.excludelist similarity index 100% rename from compiler/test/dotc/pos-init-global-scala2-library-tasty.blacklist rename to compiler/test/dotc/pos-init-global-scala2-library-tasty.excludelist diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.excludelist similarity index 100% rename from compiler/test/dotc/pos-test-pickling.blacklist rename to compiler/test/dotc/pos-test-pickling.excludelist diff --git a/compiler/test/dotc/run-from-tasty.blacklist b/compiler/test/dotc/run-from-tasty.excludelist similarity index 100% rename from compiler/test/dotc/run-from-tasty.blacklist rename to compiler/test/dotc/run-from-tasty.excludelist diff --git a/compiler/test/dotc/run-macros-scala2-library-tasty.blacklist b/compiler/test/dotc/run-macros-scala2-library-tasty.excludelist similarity index 100% rename from compiler/test/dotc/run-macros-scala2-library-tasty.blacklist rename to compiler/test/dotc/run-macros-scala2-library-tasty.excludelist diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.excludelist similarity index 100% rename from compiler/test/dotc/run-test-pickling.blacklist rename to compiler/test/dotc/run-test-pickling.excludelist diff --git a/compiler/test/dotty/tools/TestSources.scala b/compiler/test/dotty/tools/TestSources.scala index b2133b2fb182..a2fccd3b35e6 100644 --- a/compiler/test/dotty/tools/TestSources.scala +++ b/compiler/test/dotty/tools/TestSources.scala @@ -12,65 +12,65 @@ object TestSources { // pos tests lists - def posFromTastyBlacklistFile: String = "compiler/test/dotc/pos-from-tasty.blacklist" - def posTestPicklingBlacklistFile: String = "compiler/test/dotc/pos-test-pickling.blacklist" + def posFromTastyExcludelistFile: String = "compiler/test/dotc/pos-from-tasty.excludelist" + def posTestPicklingExcludelistFile: String = "compiler/test/dotc/pos-test-pickling.excludelist" def posTestRecheckExcludesFile: String = "compiler/test/dotc/pos-test-recheck.excludes" def posLazyValsAllowlistFile: String = "compiler/test/dotc/pos-lazy-vals-tests.allowlist" def posLintingAllowlistFile: String = "compiler/test/dotc/pos-linting.allowlist" - def posInitGlobalScala2LibraryTastyBlacklistFile: String = "compiler/test/dotc/pos-init-global-scala2-library-tasty.blacklist" + def posInitGlobalScala2LibraryTastyExcludelistFile: String = "compiler/test/dotc/pos-init-global-scala2-library-tasty.excludelist" - def posFromTastyBlacklisted: List[String] = loadList(posFromTastyBlacklistFile) - def posTestPicklingBlacklisted: List[String] = loadList(posTestPicklingBlacklistFile) + def posFromTastyExcludelisted: List[String] = loadList(posFromTastyExcludelistFile) + def posTestPicklingExcludelisted: List[String] = loadList(posTestPicklingExcludelistFile) def posTestRecheckExcluded: List[String] = loadList(posTestRecheckExcludesFile) def posLazyValsAllowlist: List[String] = loadList(posLazyValsAllowlistFile) def posLintingAllowlist: List[String] = loadList(posLintingAllowlistFile) - def posInitGlobalScala2LibraryTastyBlacklisted: List[String] = - if Properties.usingScalaLibraryTasty then loadList(posInitGlobalScala2LibraryTastyBlacklistFile) + def posInitGlobalScala2LibraryTastyExcludelisted: List[String] = + if Properties.usingScalaLibraryTasty then loadList(posInitGlobalScala2LibraryTastyExcludelistFile) else Nil // run tests lists - def runFromTastyBlacklistFile: String = "compiler/test/dotc/run-from-tasty.blacklist" - def runTestPicklingBlacklistFile: String = "compiler/test/dotc/run-test-pickling.blacklist" + def runFromTastyExcludelistFile: String = "compiler/test/dotc/run-from-tasty.excludelist" + def runTestPicklingExcludelistFile: String = "compiler/test/dotc/run-test-pickling.excludelist" def runTestRecheckExcludesFile: String = "compiler/test/dotc/run-test-recheck.excludes" def runLazyValsAllowlistFile: String = "compiler/test/dotc/run-lazy-vals-tests.allowlist" - def runMacrosScala2LibraryTastyBlacklistFile: String = "compiler/test/dotc/run-macros-scala2-library-tasty.blacklist" + def runMacrosScala2LibraryTastyExcludelistFile: String = "compiler/test/dotc/run-macros-scala2-library-tasty.excludelist" - def runFromTastyBlacklisted: List[String] = loadList(runFromTastyBlacklistFile) - def runTestPicklingBlacklisted: List[String] = loadList(runTestPicklingBlacklistFile) + def runFromTastyExcludelisted: List[String] = loadList(runFromTastyExcludelistFile) + def runTestPicklingExcludelisted: List[String] = loadList(runTestPicklingExcludelistFile) def runTestRecheckExcluded: List[String] = loadList(runTestRecheckExcludesFile) def runLazyValsAllowlist: List[String] = loadList(runLazyValsAllowlistFile) - def runMacrosScala2LibraryTastyBlacklisted: List[String] = - if Properties.usingScalaLibraryTasty then loadList(runMacrosScala2LibraryTastyBlacklistFile) + def runMacrosScala2LibraryTastyExcludelisted: List[String] = + if Properties.usingScalaLibraryTasty then loadList(runMacrosScala2LibraryTastyExcludelistFile) else Nil // neg tests lists - def negScala2LibraryTastyBlacklistFile: String = "compiler/test/dotc/neg-scala2-library-tasty.blacklist" - def negInitGlobalScala2LibraryTastyBlacklistFile: String = "compiler/test/dotc/neg-init-global-scala2-library-tasty.blacklist" + def negScala2LibraryTastyExcludelistFile: String = "compiler/test/dotc/neg-scala2-library-tasty.excludelist" + def negInitGlobalScala2LibraryTastyExcludelistFile: String = "compiler/test/dotc/neg-init-global-scala2-library-tasty.excludelist" - def negScala2LibraryTastyBlacklisted: List[String] = - if Properties.usingScalaLibraryTasty then loadList(negScala2LibraryTastyBlacklistFile) + def negScala2LibraryTastyExcludelisted: List[String] = + if Properties.usingScalaLibraryTasty then loadList(negScala2LibraryTastyExcludelistFile) else Nil - def negInitGlobalScala2LibraryTastyBlacklisted: List[String] = - if Properties.usingScalaLibraryTasty then loadList(negInitGlobalScala2LibraryTastyBlacklistFile) + def negInitGlobalScala2LibraryTastyExcludelisted: List[String] = + if Properties.usingScalaLibraryTasty then loadList(negInitGlobalScala2LibraryTastyExcludelistFile) else Nil // patmat tests lists - def patmatExhaustivityScala2LibraryTastyBlacklistFile: String = "compiler/test/dotc/patmat-exhaustivity-scala2-library-tasty.blacklist" + def patmatExhaustivityScala2LibraryTastyExcludelistFile: String = "compiler/test/dotc/patmat-exhaustivity-scala2-library-tasty.excludelist" - def patmatExhaustivityScala2LibraryTastyBlacklisted: List[String] = - if Properties.usingScalaLibraryTasty then loadList(patmatExhaustivityScala2LibraryTastyBlacklistFile) + def patmatExhaustivityScala2LibraryTastyExcludelisted: List[String] = + if Properties.usingScalaLibraryTasty then loadList(patmatExhaustivityScala2LibraryTastyExcludelistFile) else Nil // neg best effort tests lists - def negBestEffortPicklingBlacklistFile: String = "compiler/test/dotc/neg-best-effort-pickling.blacklist" - def negBestEffortUnpicklingBlacklistFile: String = "compiler/test/dotc/neg-best-effort-unpickling.blacklist" + def negBestEffortPicklingExcludelistFile: String = "compiler/test/dotc/neg-best-effort-pickling.excludelist" + def negBestEffortUnpicklingExcludelistFile: String = "compiler/test/dotc/neg-best-effort-unpickling.excludelist" - def negBestEffortPicklingBlacklisted: List[String] = loadList(negBestEffortPicklingBlacklistFile) - def negBestEffortUnpicklingBlacklisted: List[String] = loadList(negBestEffortUnpicklingBlacklistFile) + def negBestEffortPicklingExcludelisted: List[String] = loadList(negBestEffortPicklingExcludelistFile) + def negBestEffortUnpicklingExcludelisted: List[String] = loadList(negBestEffortUnpicklingExcludelistFile) // load lists diff --git a/compiler/test/dotty/tools/debug/DebugStepAssert.scala b/compiler/test/dotty/tools/debug/DebugStepAssert.scala new file mode 100644 index 000000000000..f3c3d8af405f --- /dev/null +++ b/compiler/test/dotty/tools/debug/DebugStepAssert.scala @@ -0,0 +1,139 @@ +package dotty.tools.debug + +import com.sun.jdi.Location +import dotty.tools.io.JPath +import dotty.tools.readLines + +import scala.annotation.tailrec + +/** + * A debug step and an associated assertion to validate the step. + * A sequence of DebugStepAssert is parsed from the check file in tests/debug + */ +private[debug] case class DebugStepAssert[T](step: DebugStep[T], assertion: T => Unit)( + using val location: CheckFileLocation +) + +/** A location in the check file */ +private[debug] case class CheckFileLocation(checkFile: JPath, line: Int): + override def toString: String = s"$checkFile:$line" + +/** When a DebugStepAssert fails it throws a DebugStepException */ +private[debug] case class DebugStepException(message: String, location: CheckFileLocation) extends Exception + +private[debug] enum DebugStep[T]: + case Break(className: String, line: Int) extends DebugStep[Location] + case Step extends DebugStep[Location] + case Next extends DebugStep[Location] + case Eval(expression: String) extends DebugStep[Either[String, String]] + +private[debug] object DebugStepAssert: + private val sym = "[a-zA-Z0-9$.]+" + private val line = raw"\d+" + private val trailing = raw" *(?://.*)?".r // empty or comment + private val break = s"break ($sym) ($line)$trailing".r + private val step = s"step ($sym|$line)$trailing".r + private val next = s"next ($sym|$line)$trailing".r + private val multiLineEval = s"eval$trailing".r + private val eval = s"eval (.*)".r + private val result = "result (.*)".r + private val error = "error (.*)".r + private val multiLineError = s"error$trailing".r + + import DebugStep.* + def parseCheckFile(checkFile: JPath): Seq[DebugStepAssert[?]] = + val allLines = readLines(checkFile.toFile) + + @tailrec + def loop(lines: List[String], acc: List[DebugStepAssert[?]]): List[DebugStepAssert[?]] = + given location: CheckFileLocation = CheckFileLocation(checkFile, allLines.size - lines.size + 1) + lines match + case Nil => acc.reverse + case break(className , lineStr) :: tail => + val breakpointLine = lineStr.toInt + val step = DebugStepAssert(Break(className, breakpointLine), checkClassAndLine(className, breakpointLine)) + loop(tail, step :: acc) + case step(pattern) :: tail => + val step = DebugStepAssert(Step, checkLineOrMethod(pattern)) + loop(tail, step :: acc) + case next(pattern) :: tail => + val step = DebugStepAssert(Next, checkLineOrMethod(pattern)) + loop(tail, step :: acc) + case eval(expr) :: tail0 => + val (assertion, tail1) = parseEvalAssertion(tail0) + val step = DebugStepAssert(Eval(expr), assertion) + loop(tail1, step :: acc) + case multiLineEval() :: tail0 => + val (exprLines, tail1) = tail0.span(_.startsWith(" ")) + val expr = exprLines.map(s => s.stripPrefix(" ")).mkString("\n") + val (assertion, tail2) = parseEvalAssertion(tail1) + val step = DebugStepAssert(Eval(expr), assertion) + loop(tail2, step :: acc) + case trailing() :: tail => loop(tail, acc) + case invalid :: tail => + throw new Exception(s"Cannot parse debug step: $invalid ($location)") + + def parseEvalAssertion(lines: List[String]): (Either[String, String] => Unit, List[String]) = + given location: CheckFileLocation = CheckFileLocation(checkFile, allLines.size - lines.size + 1) + lines match + case Nil => throw new Exception(s"Missing result or error") + case trailing() :: tail => parseEvalAssertion(tail) + case result(expected) :: tail => (checkResult(expected), tail) + case error(expected) :: tail => (checkError(Seq(expected)), tail) + case multiLineError() :: tail0 => + val (expected, tail1) = tail0.span(_.startsWith(" ")) + (checkError(expected.map(_.stripPrefix(" "))), tail1) + case invalid :: _ => + throw new Exception(s"Cannot parse as result or error: $invalid ($location)") + + loop(allLines, Nil) + end parseCheckFile + + private def checkClassAndLine(className: String, breakpointLine: Int)(using CheckFileLocation)(location: Location): Unit = + debugStepAssertEquals(location.declaringType.name, className) + checkLine(breakpointLine)(location) + + private def checkLineOrMethod(pattern: String)(using CheckFileLocation): Location => Unit = + pattern.toIntOption.map(checkLine).getOrElse(checkMethod(pattern)) + + private def checkLine(expected: Int)(using CheckFileLocation)(location: Location): Unit = + debugStepAssertEquals(location.lineNumber, expected) + + private def checkMethod(expected: String)(using CheckFileLocation)(location: Location): Unit = + debugStepAssertEquals(location.method.name, expected) + + private def checkResult(expected: String)(using CheckFileLocation)(obtained: Either[String, String]): Unit = + obtained match + case Left(obtained) => + debugStepFailed( + s"""|Evaluation failed: + |${obtained.replace("\n", "\n|")}""".stripMargin + ) + case Right(obtained) => debugStepAssertEquals(obtained, expected) + + private def checkError(expected: Seq[String])(using CheckFileLocation)(obtained: Either[String, String]): Unit = + obtained match + case Left(obtained) => + debugStepAssert( + expected.forall(e => e.r.findFirstMatchIn(obtained).isDefined), + s"""|Expected: + |${expected.mkString("\n|")} + |Obtained: + |${obtained.replace("\n", "\n|")}""".stripMargin + ) + case Right(obtained) => + debugStepFailed( + s"""|Evaluation succeeded but failure expected. + |Obtained: $obtained + |""".stripMargin + ) + + private def debugStepAssertEquals[T](obtained: T, expected: T)(using CheckFileLocation): Unit = + debugStepAssert(obtained == expected, s"Obtained $obtained, Expected: $expected") + + private def debugStepAssert(assertion: Boolean, message: String)(using CheckFileLocation): Unit = + if !assertion then debugStepFailed(message) + + private def debugStepFailed(message: String)(using location: CheckFileLocation): Unit = + throw DebugStepException(message, location) +end DebugStepAssert diff --git a/compiler/test/dotty/tools/debug/DebugTests.scala b/compiler/test/dotty/tools/debug/DebugTests.scala new file mode 100644 index 000000000000..e8f744286ba4 --- /dev/null +++ b/compiler/test/dotty/tools/debug/DebugTests.scala @@ -0,0 +1,138 @@ +package dotty.tools.debug + +import com.sun.jdi.* +import dotty.Properties +import dotty.tools.dotc.reporting.TestReporter +import dotty.tools.io.JFile +import dotty.tools.vulpix.* +import org.junit.AfterClass +import org.junit.Test + +import java.util.concurrent.TimeoutException +import scala.concurrent.duration.* +import scala.util.control.NonFatal + +class DebugTests: + import DebugTests.* + @Test def debug: Unit = + implicit val testGroup: TestGroup = TestGroup("debug") + CompilationTest.aggregateTests( + compileFile("tests/debug-custom-args/eval-explicit-nulls.scala", TestConfiguration.explicitNullsOptions), + compileFilesInDir("tests/debug", TestConfiguration.defaultOptions), + compileFilesInDir("tests/debug-preview", TestConfiguration.defaultOptions.and("-preview")) + ).checkDebug() + +object DebugTests extends ParallelTesting: + def maxDuration = + // Increase the timeout when the user is debugging the tests + if isUserDebugging then 3.hours else 45.seconds + def numberOfSlaves = Runtime.getRuntime().availableProcessors() + def safeMode = Properties.testsSafeMode + def isInteractive = SummaryReport.isInteractive + def testFilter = Properties.testsFilter + def updateCheckFiles: Boolean = Properties.testsUpdateCheckfile + def failedTests = TestReporter.lastRunFailedTests + override def debugMode = true + + implicit val summaryReport: SummaryReporting = new SummaryReport + @AfterClass def tearDown(): Unit = + super.cleanup() + summaryReport.echoSummary() + + extension (test: CompilationTest) + private def checkDebug()(implicit summaryReport: SummaryReporting): test.type = + import test.* + checkPass(new DebugTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput), "Debug") + + private final class DebugTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) + extends RunTest(testSources, times, threadLimit, suppressAllOutput): + + override def onSuccess(testSource: TestSource, reporters: Seq[TestReporter], logger: LoggedRunnable) = + verifyDebug(testSource.outDir, testSource, countWarnings(reporters), reporters, logger) + + private def verifyDebug(dir: JFile, testSource: TestSource, warnings: Int, reporters: Seq[TestReporter], logger: LoggedRunnable) = + if Properties.testsNoRun then addNoRunWarning() + else + val checkFile = testSource.checkFile.getOrElse(throw new Exception("Missing check file")).toPath + val debugSteps = DebugStepAssert.parseCheckFile(checkFile) + val expressionEvaluator = + ExpressionEvaluator(testSource.sourceFiles, testSource.flags, testSource.runClassPath, testSource.outDir) + try debugMain(testSource.runClassPath): debuggee => + val jdiPort = debuggee.readJdiPort() + val debugger = Debugger(jdiPort, expressionEvaluator, maxDuration/* , verbose = true */) + // configure the breakpoints before starting the debuggee + val breakpoints = debugSteps.map(_.step).collect { case b: DebugStep.Break => b }.distinct + for b <- breakpoints do debugger.configureBreakpoint(b.className, b.line) + try + debuggee.launch() + playDebugSteps(debugger, debugSteps/* , verbose = true */) + val status = debuggee.exit() + reportDebuggeeStatus(testSource, status) + finally + // closing the debugger must be done at the very end so that the + // 'Listening for transport dt_socket at address: ' message is ready to be read + // by the next DebugTest + debugger.dispose() + catch case DebugStepException(message, location) => + echo(s"\n[error] Debug step failed: $location\n" + message) + failTestSource(testSource) + end verifyDebug + + private def playDebugSteps(debugger: Debugger, steps: Seq[DebugStepAssert[?]], verbose: Boolean = false): Unit = + /** The DebugTests can only debug one thread at a time. It cannot handle breakpoints in concurrent threads. + * When thread is None, it means the JVM is running and no thread is waiting to be resumed. + * If thread is Some, it is waiting to be resumed by calling continue, step or next. + * While the thread is paused, it can be used for evaluation. + */ + var thread: Option[ThreadReference] = None + def location = thread.get.frame(0).location + def continueIfPaused(): Unit = + thread.foreach(debugger.continue) + thread = None + + for case step <- steps do + import DebugStep.* + try step match + case DebugStepAssert(Break(className, line), assertion) => + continueIfPaused() + thread = Some(debugger.break()) + if verbose then + println(s"break $location ${location.method.name}") + assertion(location) + case DebugStepAssert(Next, assertion) => + thread = Some(debugger.next(thread.get)) + if verbose then println(s"next $location ${location.method.name}") + assertion(location) + case DebugStepAssert(Step, assertion) => + thread = Some(debugger.step(thread.get)) + if verbose then println(s"step $location ${location.method.name}") + assertion(location) + case DebugStepAssert(Eval(expr), assertion) => + if verbose then println(s"eval $expr") + val result = debugger.evaluate(expr, thread.get) + if verbose then println(result.fold("error " + _, "result " + _)) + assertion(result) + catch + case _: TimeoutException => throw new DebugStepException("Timeout", step.location) + case e: DebugStepException => throw e + case NonFatal(e) => + throw new Exception(s"Debug step failed unexpectedly: ${step.location}", e) + end for + // let the debuggee finish its execution + continueIfPaused() + end playDebugSteps + + private def reportDebuggeeStatus(testSource: TestSource, status: Status): Unit = + status match + case Success(output) => () + case Failure(output) => + if output == "" then + echo(s"Test '${testSource.title}' failed with no output") + else + echo(s"Test '${testSource.title}' failed with output:") + echo(output) + failTestSource(testSource) + case Timeout => + echo("failed because test " + testSource.title + " timed out") + failTestSource(testSource, TimeoutFailure(testSource.title)) + end DebugTest diff --git a/compiler/test/dotty/tools/debug/Debugger.scala b/compiler/test/dotty/tools/debug/Debugger.scala new file mode 100644 index 000000000000..5826db133915 --- /dev/null +++ b/compiler/test/dotty/tools/debug/Debugger.scala @@ -0,0 +1,139 @@ +package dotty.tools.debug + +import com.sun.jdi.* +import com.sun.jdi.event.* +import com.sun.jdi.request.* + +import java.lang.ref.Reference +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference +import scala.concurrent.duration.Duration +import scala.jdk.CollectionConverters.* +import java.util.concurrent.TimeoutException + +class Debugger(vm: VirtualMachine, evaluator: ExpressionEvaluator, maxDuration: Duration, verbose: Boolean): + // For some JDI events that we receive, we wait for client actions. + // Example: On a BreakpointEvent, the client may want to inspect frames and variables, before it + // decides to step in or continue. + private val pendingEvents = new LinkedBlockingQueue[Event]() + + // When the debuggee is evaluating an expression coming from the debugger + // we should resume the thread after each BreakpointEvent + private var isEvaluating = false + + // Internal event subscriptions, to react to JDI events + // Example: add a Breakpoint on a ClassPrepareEvent + private val eventSubs = new AtomicReference(List.empty[PartialFunction[Event, Unit]]) + private val eventListener = startListeningVM() + + def configureBreakpoint(className: String, line: Int): Unit = + vm.classesByName(className).asScala.foreach(addBreakpoint(_, line)) + // watch class preparation and add breakpoint when the class is prepared + val request = vm.eventRequestManager.createClassPrepareRequest + request.addClassFilter(className) + subscribe: + case e: ClassPrepareEvent if e.request == request => addBreakpoint(e.referenceType, line) + request.enable() + + def break(): ThreadReference = receiveEvent { case e: BreakpointEvent => e.thread } + + def continue(thread: ThreadReference): Unit = thread.resume() + + def next(thread: ThreadReference): ThreadReference = + stepAndWait(thread, StepRequest.STEP_LINE, StepRequest.STEP_OVER) + + def step(thread: ThreadReference): ThreadReference = + stepAndWait(thread, StepRequest.STEP_LINE, StepRequest.STEP_INTO) + + def evaluate(expression: String, thread: ThreadReference): Either[String, String] = + try + isEvaluating = true + evaluator.evaluate(expression, thread) + finally + isEvaluating = false + + + /** stop listening and disconnect debugger */ + def dispose(): Unit = + eventListener.interrupt() + vm.dispose() + + private def addBreakpoint(refType: ReferenceType, line: Int): Unit = + try + for location <- refType.locationsOfLine(line).asScala do + if verbose then println(s"Adding breakpoint in $location") + val breakpoint = vm.eventRequestManager.createBreakpointRequest(location) + // suspend only the thread which triggered the event + breakpoint.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD) + // let's enable the breakpoint and forget about it + // we don't need to store it because we never remove any breakpoint + breakpoint.enable() + catch + case e: AbsentInformationException => + if verbose then println(s"AbsentInformationException on ${refType}") + + private def stepAndWait(thread: ThreadReference, size: Int, depth: Int): ThreadReference = + val request = vm.eventRequestManager.createStepRequest(thread, size, depth) + request.enable() + thread.resume() + // Because our debuggee is mono-threaded, we don't check that `e.request` is our step request. + // Indeed there can be only one step request per thread at a time. + val newThreadRef = receiveEvent { case e: StepEvent => e.thread } + request.disable() + newThreadRef + + private def subscribe(f: PartialFunction[Event, Unit]): Unit = + eventSubs.updateAndGet(f :: _) + + private def startListeningVM(): Thread = + val thread = Thread: () => + var isAlive = true + try while isAlive do + val eventSet = vm.eventQueue.remove() + val subscriptions = eventSubs.get + var shouldResume = true + for event <- eventSet.iterator.asScala.toSeq do + if verbose then println(formatEvent(event)) + for f <- subscriptions if f.isDefinedAt(event) do f(event) + event match + case e: (BreakpointEvent | StepEvent) => + if !isEvaluating then + shouldResume = false + pendingEvents.put(e) + case _: VMDisconnectEvent => isAlive = false + case _ => () + if shouldResume then eventSet.resume() + catch case _: InterruptedException => () + thread.start() + thread + end startListeningVM + + private def receiveEvent[T](f: PartialFunction[Event, T]): T = + // poll repeatedly until we get an event that matches the partial function or a timeout + Iterator.continually(pendingEvents.poll(maxDuration.toMillis, TimeUnit.MILLISECONDS)) + .map(e => if (e == null) throw new TimeoutException() else e) + .collect(f) + .next() + + private def formatEvent(event: Event): String = + event match + case e: ClassPrepareEvent => s"$e ${e.referenceType}" + case e => e.toString + +object Debugger: + // The socket JDI connector + private val connector = Bootstrap.virtualMachineManager + .attachingConnectors + .asScala + .find(_.getClass.getName == "com.sun.tools.jdi.SocketAttachingConnector") + .get + + def apply(jdiPort: Int, expressionEvaluator: ExpressionEvaluator, maxDuration: Duration, verbose: Boolean = false): Debugger = + val arguments = connector.defaultArguments() + arguments.get("hostname").setValue("localhost") + arguments.get("port").setValue(jdiPort.toString) + arguments.get("timeout").setValue(maxDuration.toMillis.toString) + val vm = connector.attach(arguments) + new Debugger(vm, expressionEvaluator, maxDuration, verbose) + diff --git a/compiler/test/dotty/tools/debug/ExpressionEvaluator.scala b/compiler/test/dotty/tools/debug/ExpressionEvaluator.scala new file mode 100644 index 000000000000..eeaf997871cf --- /dev/null +++ b/compiler/test/dotty/tools/debug/ExpressionEvaluator.scala @@ -0,0 +1,233 @@ +package dotty.tools.debug + +import com.sun.jdi.* +import dotty.tools.io.* +import dotty.tools.vulpix.TestFlags + +import scala.jdk.CollectionConverters.* + +class ExpressionEvaluator( + sources: Map[String, JPath], + options: Array[String], + classPath: String, + outputDir: JPath +): + private val compiler = ExpressionCompilerBridge() + private var uniqueID: Int = 1 + + private class EvaluationException(message: String, cause: InvocationException) + extends Exception(message, cause) + + /** returns the value of the evaluated expression or compiler errors */ + def evaluate(expression: String, thread: ThreadReference): Either[String, String] = + // We evaluate the expression at the top frame of the stack + val frame = thread.frame(0) + + // Extract everything from the frame now because, as soon as we start using the thread + // for remote execution, the frame becomes invalid + val localVariables = frame.visibleVariables.asScala.toSeq + val values = localVariables.map(frame.getValue) + val location = frame.location + val thisRef = frame.thisObject // null in a static context + + for expressionClassName <- compile(expression, location, localVariables) yield + // we don't need to create a new classloader because we compiled the expression class + // in the same outputDir as main classes + val classLoader = location.declaringType.classLoader + val expressionClass = thread.loadClass(classLoader, expressionClassName) + + val nameArray = thread.createArray( + "java.lang.String", + localVariables.map(v => thread.virtualMachine.mirrorOf(v.name)) + ) + val valueArray = thread.createArray("java.lang.Object", values.map(thread.boxIfPrimitive)) + val args = Seq(thisRef, nameArray, valueArray) + + val exprRef = thread.newInstance(expressionClass, args) + try + val output = + thread.invoke[ObjectReference](exprRef, "evaluate", "()Ljava/lang/Object;", Seq.empty) + updateVariables(thread, valueArray) + thread.invoke[StringReference](output, "toString", "()Ljava/lang/String;", Seq.empty).value + catch case e: EvaluationException => + // if expr.evaluate() throws an exception, we return exception.toString as the value + // to distinguish it from an evaluation error + // throwing an exception is a valid result of evaluation + e.getMessage + end evaluate + + /** compiles the expression and returns the new expression class name to load, or compiler errors */ + private def compile( + expression: String, + location: Location, + localVariables: Seq[LocalVariable] + ): Either[String, String] = + // We assume there is no 2 files with the same name + val sourceFile = sources(location.sourceName) + val packageName = getPackageName(location.declaringType) + val outputClassName = getUniqueClassName() + val errorBuilder = StringBuilder() + val config = ExpressionCompilerConfig( + packageName = packageName, + outputClassName = outputClassName, + breakpointLine = location.lineNumber, + expression = expression, + localVariables = localVariables.toSet.map(_.name).asJava, + errorReporter = errorMsg => errorBuilder.append(errorMsg), + testMode = true + ) + val success = compiler.run(outputDir, classPath, options, sourceFile, config) + val fullyQualifiedClassName = + if packageName.isEmpty then outputClassName else s"$packageName.$outputClassName" + if success then Right(fullyQualifiedClassName) else Left(errorBuilder.toString) + end compile + + private def updateVariables(thread: ThreadReference, valueArray: ArrayReference): Unit = + // the frame reference change after each remote execution + def frame = thread.frame(0) + frame + .visibleVariables + .asScala + .toSeq + .zip(valueArray.getValues.asScala) + .map: (variable, value) => + val preparedValue = + if variable.`type`.isInstanceOf[PrimitiveType] then thread.unboxIfPrimitive(value) + else value + frame.setValue(variable, preparedValue) + + private def getPackageName(tpe: ReferenceType): String = + tpe.name.split('.').dropRight(1).mkString(".") + + private def getUniqueClassName(): String = + val id = uniqueID + uniqueID += 1 + "Expression" + id + + extension (thread: ThreadReference) + private def boxIfPrimitive(value: Value): ObjectReference = + value match + case value: PrimitiveValue => box(value) + case ref: ObjectReference => ref + + private def unboxIfPrimitive(value: Value): Value = + import ExpressionEvaluator.unboxMethods + value match + case ref: ObjectReference if unboxMethods.contains(ref.referenceType.name) => + val (methodName, sig) = unboxMethods(ref.referenceType.name) + invoke(ref, methodName, sig, Seq.empty) + case _ => value + + private def box(value: PrimitiveValue): ObjectReference = + val (className, sig) = value match + case _: BooleanValue => ("java.lang.Boolean", "(Ljava/lang/String;)Ljava/lang/Boolean;") + case _: ByteValue => ("java.lang.Byte", "(Ljava/lang/String;)Ljava/lang/Byte;") + case _: CharValue => ("java.lang.Character", "(C)Ljava/lang/Character;") + case _: DoubleValue => ("java.lang.Double", "(Ljava/lang/String;)Ljava/lang/Double;") + case _: FloatValue => ("java.lang.Float", "(Ljava/lang/String;)Ljava/lang/Float;") + case _: IntegerValue => ("java.lang.Integer", "(Ljava/lang/String;)Ljava/lang/Integer;") + case _: LongValue => ("java.lang.Long", "(Ljava/lang/String;)Ljava/lang/Long;") + case _: ShortValue => ("java.lang.Short", "(Ljava/lang/String;)Ljava/lang/Short;") + val cls = getClass(className) + val args = value match + case c: CharValue => Seq(c) + case value => Seq(mirrorOf(value.toString)) + invokeStatic(cls, "valueOf", sig, args) + + private def createArray(arrayType: String, values: Seq[Value]): ArrayReference = + val arrayClass = getClass(arrayType) + val reflectArrayClass = loadClass(arrayClass.classLoader, "java.lang.reflect.Array") + val args = Seq(arrayClass.classObject, mirrorOf(values.size)) + val sig = "(Ljava/lang/Class;I)Ljava/lang/Object;" + val arrayRef = invokeStatic[ArrayReference](reflectArrayClass, "newInstance", sig, args) + arrayRef.setValues(values.asJava) + arrayRef + + /** Get the remote class if it is already loaded. Otherwise you should use loadClass. */ + private def getClass(className: String): ClassType = + thread.virtualMachine.classesByName(className).get(0).asInstanceOf[ClassType] + + private def loadClass(classLoader: ClassLoaderReference, className: String): ClassType = + // Calling classLoader.loadClass would create useless class object which throws + // ClassNotPreparedException. We use java.lang.Class.forName instead. + val classClass = getClass("java.lang.Class") + val args = Seq(mirrorOf(className), mirrorOf(true), classLoader) + val sig = "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;" + invokeStatic[ClassObjectReference](classClass, "forName", sig, args) + .reflectedType + .asInstanceOf[ClassType] + + private def invokeStatic[T <: Value]( + cls: ClassType, + methodName: String, + sig: String, + args: Seq[Value] + ): T = + val method = cls.methodsByName(methodName, sig).get(0) + remotely: + cls.invokeMethod(thread, method, args.asJava, ObjectReference.INVOKE_SINGLE_THREADED) + + // we assume there is a single constructor, otherwise we need to add sig as parameter + private def newInstance(cls: ClassType, args: Seq[Value]): ObjectReference = + val constructor = cls.methodsByName("").get(0) + remotely: + cls.newInstance(thread, constructor, args.asJava, ObjectReference.INVOKE_SINGLE_THREADED) + + private def invoke[T <: Value]( + ref: ObjectReference, + methodName: String, + sig: String, + args: Seq[Value] + ): T = + val method = ref.referenceType.methodsByName(methodName, sig).get(0) + remotely: + ref.invokeMethod(thread, method, args.asJava, ObjectReference.INVOKE_SINGLE_THREADED) + + /** wrapper for safe remote execution: + * - it catches InvocationException to extract the message of the remote exception + * - it disables GC on the returned value + */ + private def remotely[T <: Value](value: => Value): T = + val res = + try value + catch case invocationException: InvocationException => + val sig = "()Ljava/lang/String;" + val message = + invoke[StringReference](invocationException.exception, "toString", sig, List()) + throw new EvaluationException(message.value, invocationException) + // Prevent object created by the debugger to be garbage collected + // In theory we should re-enable collection later to avoid memory leak + // But maybe it is okay to have a few leaks in the tested debuggee + res match + case ref: ObjectReference => ref.disableCollection() + case _ => + res.asInstanceOf[T] + + private def mirrorOf(value: String): StringReference = thread.virtualMachine.mirrorOf(value) + private def mirrorOf(value: Int): IntegerValue = thread.virtualMachine.mirrorOf(value) + private def mirrorOf(value: Boolean): BooleanValue = thread.virtualMachine.mirrorOf(value) + end extension +end ExpressionEvaluator + +object ExpressionEvaluator: + private val unboxMethods = Map( + "java.lang.Boolean" -> ("booleanValue", "()Z"), + "java.lang.Byte" -> ("byteValue", "()B"), + "java.lang.Character" -> ("charValue", "()C"), + "java.lang.Double" -> ("doubleValue", "()D"), + "java.lang.Float" -> ("floatValue", "()F"), + "java.lang.Integer" -> ("intValue", "()I"), + "java.lang.Long" -> ("longValue", "()J"), + "java.lang.Short" -> ("shortValue", "(S)") + ) + + + def apply( + sources: Array[JFile], + flags: TestFlags, + classPath: String, + outputDir: JFile + ): ExpressionEvaluator = + val sourceMap = sources.map(s => s.getName -> s.toPath).toMap + val filteredOptions = flags.options.filterNot(_ == "-Ycheck:all") + new ExpressionEvaluator(sourceMap, filteredOptions, classPath, outputDir.toPath) diff --git a/compiler/test/dotty/tools/dotc/BestEffortOptionsTests.scala b/compiler/test/dotty/tools/dotc/BestEffortOptionsTests.scala index 1e7262f5fd8d..da5440331068 100644 --- a/compiler/test/dotty/tools/dotc/BestEffortOptionsTests.scala +++ b/compiler/test/dotty/tools/dotc/BestEffortOptionsTests.scala @@ -28,8 +28,8 @@ class BestEffortOptionsTests { implicit val testGroup: TestGroup = TestGroup("negTestFromBestEffortTasty") compileBestEffortTastyInDir(s"tests${JFile.separator}neg", bestEffortBaselineOptions, - picklingFilter = FileFilter.exclude(TestSources.negBestEffortPicklingBlacklisted), - unpicklingFilter = FileFilter.exclude(TestSources.negBestEffortUnpicklingBlacklisted) + picklingFilter = FileFilter.exclude(TestSources.negBestEffortPicklingExcludelisted), + unpicklingFilter = FileFilter.exclude(TestSources.negBestEffortUnpicklingExcludelisted) ).checkNoCrash() } diff --git a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala index 3b19f1d3d4bb..23980508f17d 100644 --- a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala @@ -113,7 +113,7 @@ class BootstrappedOnlyCompilationTests { @Test def runMacros: Unit = { implicit val testGroup: TestGroup = TestGroup("runMacros") - compileFilesInDir("tests/run-macros", defaultOptions.and("-Xcheck-macros"), FileFilter.exclude(TestSources.runMacrosScala2LibraryTastyBlacklisted)) + compileFilesInDir("tests/run-macros", defaultOptions.and("-Xcheck-macros"), FileFilter.exclude(TestSources.runMacrosScala2LibraryTastyExcludelisted)) .checkRuns() } diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 70f55e569784..e62c80d7bff7 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -79,9 +79,11 @@ class CompilationTests { compileFile("tests/rewrites/i20002.scala", defaultOptions.and("-indent", "-rewrite")), compileDir("tests/rewrites/annotation-named-pararamters", defaultOptions.and("-rewrite", "-source:3.6-migration")), compileFile("tests/rewrites/i21418.scala", unindentOptions.and("-rewrite", "-source:3.5-migration")), - compileFile("tests/rewrites/infix-named-args.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), - compileFile("tests/rewrites/ambigious-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), + compileFile("tests/rewrites/infix-named-args.scala", defaultOptions.and("-rewrite", "-source:3.7-migration")), + compileFile("tests/rewrites/ambiguous-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), compileFile("tests/rewrites/i21382.scala", defaultOptions.and("-indent", "-rewrite")), + compileFile("tests/rewrites/unused.scala", defaultOptions.and("-rewrite", "-Wunused:all")), + compileFile("tests/rewrites/i22440.scala", defaultOptions.and("-rewrite")) ).checkRewrites() } @@ -140,7 +142,7 @@ class CompilationTests { @Test def negAll: Unit = { implicit val testGroup: TestGroup = TestGroup("compileNeg") aggregateTests( - compileFilesInDir("tests/neg", defaultOptions, FileFilter.exclude(TestSources.negScala2LibraryTastyBlacklisted)), + compileFilesInDir("tests/neg", defaultOptions, FileFilter.exclude(TestSources.negScala2LibraryTastyExcludelisted)), compileFilesInDir("tests/neg-deep-subtype", allowDeepSubtypes), compileFilesInDir("tests/neg-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking")), compileFile("tests/neg-custom-args/sourcepath/outer/nested/Test1.scala", defaultOptions.and("-sourcepath", "tests/neg-custom-args/sourcepath")), @@ -183,8 +185,8 @@ class CompilationTests { @Test def pickling: Unit = { implicit val testGroup: TestGroup = TestGroup("testPickling") aggregateTests( - compileFilesInDir("tests/pos", picklingOptions, FileFilter.exclude(TestSources.posTestPicklingBlacklisted)), - compileFilesInDir("tests/run", picklingOptions, FileFilter.exclude(TestSources.runTestPicklingBlacklisted)) + compileFilesInDir("tests/pos", picklingOptions, FileFilter.exclude(TestSources.posTestPicklingExcludelisted)), + compileFilesInDir("tests/run", picklingOptions, FileFilter.exclude(TestSources.runTestPicklingExcludelisted)) ).checkCompile() } @@ -229,8 +231,12 @@ class CompilationTests { // initialization tests @Test def checkInitGlobal: Unit = { implicit val testGroup: TestGroup = TestGroup("checkInitGlobal") - compileFilesInDir("tests/init-global/warn", defaultOptions.and("-Ysafe-init-global"), FileFilter.exclude(TestSources.negInitGlobalScala2LibraryTastyBlacklisted)).checkWarnings() - compileFilesInDir("tests/init-global/pos", defaultOptions.and("-Ysafe-init-global", "-Xfatal-warnings"), FileFilter.exclude(TestSources.posInitGlobalScala2LibraryTastyBlacklisted)).checkCompile() + compileFilesInDir("tests/init-global/warn", defaultOptions.and("-Ysafe-init-global"), FileFilter.exclude(TestSources.negInitGlobalScala2LibraryTastyExcludelisted)).checkWarnings() + compileFilesInDir("tests/init-global/pos", defaultOptions.and("-Ysafe-init-global", "-Xfatal-warnings"), FileFilter.exclude(TestSources.posInitGlobalScala2LibraryTastyExcludelisted)).checkCompile() + if Properties.usingScalaLibraryTasty && !Properties.usingScalaLibraryCCTasty then + compileFilesInDir("tests/init-global/warn-tasty", defaultOptions.and("-Ysafe-init-global"), FileFilter.exclude(TestSources.negInitGlobalScala2LibraryTastyExcludelisted)).checkWarnings() + compileFilesInDir("tests/init-global/pos-tasty", defaultOptions.and("-Ysafe-init-global", "-Xfatal-warnings"), FileFilter.exclude(TestSources.posInitGlobalScala2LibraryTastyExcludelisted)).checkCompile() + end if } // initialization tests @@ -261,6 +267,29 @@ class CompilationTests { tests.foreach(_.delete()) } + + /* This tests for errors in the program's TASTy trees. + * The test consists of three files: (a) v1/A, (b) v1/B, and (c) v0/A. (a) and (b) are + * compatible, but (b) and (c) are not. If (b) and (c) are compiled together, there should be + * an error when reading the files' TASTy trees. */ + locally { + val tastyErrorGroup = TestGroup("checkInit/tasty-error") + val tastyErrorOptions = options.without("-Xfatal-warnings") + + val a0Dir = defaultOutputDir + tastyErrorGroup + "/A/v0/A" + val a1Dir = defaultOutputDir + tastyErrorGroup + "/A/v1/A" + val b1Dir = defaultOutputDir + tastyErrorGroup + "/B/v1/B" + + val tests = List( + compileFile("tests/init/tasty-error/v1/A.scala", tastyErrorOptions)(tastyErrorGroup), + compileFile("tests/init/tasty-error/v1/B.scala", tastyErrorOptions.withClasspath(a1Dir))(tastyErrorGroup), + compileFile("tests/init/tasty-error/v0/A.scala", tastyErrorOptions)(tastyErrorGroup), + ).map(_.keepOutput.checkCompile()) + + compileFile("tests/init/tasty-error/Main.scala", tastyErrorOptions.withClasspath(a0Dir).withClasspath(b1Dir))(tastyErrorGroup).checkExpectedErrors() + + tests.foreach(_.delete()) + } } // parallel backend tests diff --git a/compiler/test/dotty/tools/dotc/FromTastyTests.scala b/compiler/test/dotty/tools/dotc/FromTastyTests.scala index 1d46cbbce95c..874088fb618e 100644 --- a/compiler/test/dotty/tools/dotc/FromTastyTests.scala +++ b/compiler/test/dotty/tools/dotc/FromTastyTests.scala @@ -23,7 +23,7 @@ class FromTastyTests { implicit val testGroup: TestGroup = TestGroup("posTestFromTasty") compileTastyInDir(s"tests${JFile.separator}pos", defaultOptions, - fromTastyFilter = FileFilter.exclude(TestSources.posFromTastyBlacklisted) + fromTastyFilter = FileFilter.exclude(TestSources.posFromTastyExcludelisted) ).checkCompile() } @@ -35,7 +35,7 @@ class FromTastyTests { implicit val testGroup: TestGroup = TestGroup("runTestFromTasty") compileTastyInDir(s"tests${JFile.separator}run", defaultOptions, - fromTastyFilter = FileFilter.exclude(TestSources.runFromTastyBlacklisted) + fromTastyFilter = FileFilter.exclude(TestSources.runFromTastyExcludelisted) ).checkRuns() } } diff --git a/compiler/test/dotty/tools/dotc/config/PropertiesTest.scala b/compiler/test/dotty/tools/dotc/config/PropertiesTest.scala new file mode 100644 index 000000000000..e45ac1f3983f --- /dev/null +++ b/compiler/test/dotty/tools/dotc/config/PropertiesTest.scala @@ -0,0 +1,45 @@ +package dotty.tools.dotc.config + +import org.junit.Before +import org.junit.Test +import org.junit.Assert._ +import scala.language.unsafeNulls + +class PropertiesTest { + final val TestProperty = "dotty.tools.dotc.config.PropertiesTest.__test_property__" + + @Before + def beforeEach(): Unit = { + Properties.clearProp(TestProperty) + } + + @Test + def testPropOrNone(): Unit = { + assertEquals(Properties.propOrNone(TestProperty), None) + + Properties.setProp(TestProperty, "foo") + + assertEquals(Properties.propOrNone(TestProperty), Some("foo")) + } + + @Test + def testPropOrElse(): Unit = { + assertEquals(Properties.propOrElse(TestProperty, "bar"), "bar") + + Properties.setProp(TestProperty, "foo") + + var done = false + assertEquals(Properties.propOrElse(TestProperty, { done = true; "bar" }), "foo") + assertFalse("Does not evaluate alt if not needed", done) + } + + @Test + def testEnvOrElse(): Unit = { + assertEquals(Properties.envOrElse("_PropertiesTest_NOT_DEFINED", "test"), "test") + + var done = false + val envName = System.getenv().keySet().iterator().next() + assertNotEquals(Properties.envOrElse(envName, {done = true; "bar"}), "bar") + assertFalse("Does not evaluate alt if not needed", done) + } +} diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala index f6460180cab9..2aa811abd75c 100644 --- a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -110,9 +110,8 @@ class CoverageTests: if run then val path = if isDirectory then inputFile.toString else inputFile.getParent.toString val test = compileDir(path, options) - test.checkFilePaths.foreach { checkFilePath => - assert(checkFilePath.exists, s"Expected checkfile for $path $checkFilePath does not exist.") - } + test.checkFiles.foreach: checkFile => + assert(checkFile.exists, s"Expected checkfile for $path $checkFile does not exist.") test.checkRuns() else val test = diff --git a/compiler/test/dotty/tools/dotc/parsing/ScannerTest.scala b/compiler/test/dotty/tools/dotc/parsing/ScannerTest.scala index 659cd27e62f4..9f78d0778b41 100644 --- a/compiler/test/dotty/tools/dotc/parsing/ScannerTest.scala +++ b/compiler/test/dotty/tools/dotc/parsing/ScannerTest.scala @@ -10,7 +10,7 @@ import org.junit.Test class ScannerTest extends DottyTest { - val blackList = List( + val excluded = List( "/scaladoc/scala/tools/nsc/doc/html/page/Index.scala", "/scaladoc/scala/tools/nsc/doc/html/page/Template.scala" ) @@ -33,13 +33,13 @@ class ScannerTest extends DottyTest { def scanDir(path: String): Unit = scanDir(Directory(path)) def scanDir(dir: Directory): Unit = { - if (blackList exists (dir.jpath.toString endsWith _)) - println(s"blacklisted package: ${dir.toAbsolute.jpath}") + if (excluded exists (dir.jpath.toString endsWith _)) + println(s"excluded package: ${dir.toAbsolute.jpath}") else for (f <- dir.files) if (f.name.endsWith(".scala")) - if (blackList exists (f.jpath.toString endsWith _)) - println(s"blacklisted file: ${f.toAbsolute.jpath}") + if (excluded exists (f.jpath.toString endsWith _)) + println(s"excluded file: ${f.toAbsolute.jpath}") else scan(new PlainFile(f)) for (d <- dir.dirs) diff --git a/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala b/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala index 8a80a6978bdb..15522d61e31f 100644 --- a/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala +++ b/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala @@ -69,6 +69,9 @@ class PrintingTest { @Test def printing: Unit = testIn("tests/printing", "typer") + @Test + def posttyper: Unit = testIn("tests/printing/posttyper", "posttyper") + @Test def untypedPrinting: Unit = testIn("tests/printing/untyped", "parser") diff --git a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala index 4ed59db5c10e..7c6a27ea7422 100644 --- a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala +++ b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala @@ -63,14 +63,14 @@ class PatmatExhaustivityTest { @Test def patmatExhaustivity: Unit = { - val blacklisted = TestSources.patmatExhaustivityScala2LibraryTastyBlacklisted.toSet + val excluded = TestSources.patmatExhaustivityScala2LibraryTastyExcludelisted.toSet val res = Directory(testsDir).list.toList .filter(f => f.ext.isScala || f.isDirectory) .filter { f => val path = if f.isDirectory then f.path + "/" else f.path Properties.testsFilter.isEmpty || Properties.testsFilter.exists(path.contains) } - .filterNot(f => blacklisted.contains(f.name)) + .filterNot(f => excluded.contains(f.name)) .map(f => if f.isDirectory then compileDir(f.jpath) else compileFile(f.jpath)) val failed = res.filter(!_) diff --git a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala index d32b28647c32..0592cbbed1be 100644 --- a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala +++ b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala @@ -511,6 +511,16 @@ class ReplCompilerTests extends ReplTest: val all = lines() assertTrue(hints.forall(hint => all.exists(_.contains(hint)))) + @Test def `i22844 regression colon eol`: Unit = initially: + run: + """|println: + | "hello, world" + |""".stripMargin // outdent, but this test does not exercise the bug + assertEquals(List("hello, world"), lines()) + + @Test def `i22844b regression colon arrow eol`: Unit = contextually: + assertTrue(ParseResult.isIncomplete("List(42).map: x =>")) + 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 3925b61d7de0..9741a8dee450 100644 --- a/compiler/test/dotty/tools/repl/ReplTest.scala +++ b/compiler/test/dotty/tools/repl/ReplTest.scala @@ -98,10 +98,7 @@ extends ReplDriver(options, new PrintStream(out, true, StandardCharsets.UTF_8.na FileDiff.dump(checkFile.toPath.toString, actualOutput) println(s"Wrote updated script file to $checkFile") else - println("expected =========>") - println(expectedOutput.mkString(EOL)) - println("actual ===========>") - println(actualOutput.mkString(EOL)) + println(dotc.util.DiffUtil.mkColoredHorizontalLineDiff(actualOutput.mkString(EOL), expectedOutput.mkString(EOL))) fail(s"Error in script $name, expected output did not match actual") end if diff --git a/compiler/test/dotty/tools/repl/TabcompleteTests.scala b/compiler/test/dotty/tools/repl/TabcompleteTests.scala index 01b9d1109994..ad0f13b3a497 100644 --- a/compiler/test/dotty/tools/repl/TabcompleteTests.scala +++ b/compiler/test/dotty/tools/repl/TabcompleteTests.scala @@ -213,10 +213,14 @@ class TabcompleteTests extends ReplTest { ":exit", ":help", ":imports", + ":jar", + ":kind", ":load", ":quit", + ":require", ":reset", ":settings", + ":sh", ":silent", ":type" ), diff --git a/compiler/test/dotty/tools/scripting/BashExitCodeTests.scala b/compiler/test/dotty/tools/scripting/BashExitCodeTests.scala index 857f5ef378e7..0449113c0d62 100644 --- a/compiler/test/dotty/tools/scripting/BashExitCodeTests.scala +++ b/compiler/test/dotty/tools/scripting/BashExitCodeTests.scala @@ -32,10 +32,11 @@ class BashExitCodeTests: s"expected $expectedExitCode but got $exitCode${pp("out", stdout)}${pp("err", stderr)}" }, expectedExitCode, exitCode) - // Helpers for running scala, scalac, and scalac without the output directory ("raw") + // Helpers for running scala, scalac and repl without the output directory ("raw") def scala(args: String*) = verifyExit(scalaPath, ("--power" +: args :+ "--offline" :+ "--server=false")*) def scalacRaw(args: String*) = verifyExit(scalacPath, args*) def scalac(args: String*) = scalacRaw(("-d" +: tmpDir +: args)*) + def repl(args: String*) = verifyExit(scalaPath, ("--power" +: "repl" +: "--offline" +: "--" +: args)*) /** The path to the test file for this class. */ def f(body: String, suffix: String = ".scala"): String = @@ -72,6 +73,9 @@ class BashExitCodeTests: @Test def xPluginList = scala("-Xplugin-list")(0) @Test def vPhases = scala("-Vphases")(0) + @Test def replEval = + repl("--repl-quit-after-init", "--repl-init-script", "'val i = 2 * 2; val j = i + 2'")(0) + /** A utility for running two commands in a row, like you do in bash. */ extension (inline u1: Unit) inline def & (inline u2: Unit): Unit = { u1; u2 } end BashExitCodeTests diff --git a/compiler/test/dotty/tools/utils.scala b/compiler/test/dotty/tools/utils.scala index d17edbaa855e..c33310acf06e 100644 --- a/compiler/test/dotty/tools/utils.scala +++ b/compiler/test/dotty/tools/utils.scala @@ -57,25 +57,54 @@ def assertThrows[T <: Throwable: ClassTag](p: T => Boolean)(body: => Any): Unit case NonFatal(other) => throw AssertionError(s"Wrong exception: expected ${implicitly[ClassTag[T]]} but was ${other.getClass.getName}").tap(_.addSuppressed(other)) end assertThrows +enum TestPlatform: + case JVM, ScalaJS + override def toString: String = this match + case JVM => "jvm" + case ScalaJS => "scala-js" + +object TestPlatform: + def named(s: String): TestPlatform = s match + case "jvm" => TestPlatform.JVM + case "scala-js" => TestPlatform.ScalaJS + case _ => throw IllegalArgumentException(s) + /** Famous tool names in the ecosystem. Used for tool args in test files. */ enum ToolName: - case Scala, Scalac, Java, Javac, ScalaJS, Test + case Scala, Scalac, Java, Javac, ScalaJS, Test, Target object ToolName: def named(s: String): ToolName = values.find(_.toString.equalsIgnoreCase(s)).getOrElse(throw IllegalArgumentException(s)) type ToolArgs = Map[ToolName, List[String]] +type PlatformFiles = Map[TestPlatform, List[String]] /** Take a prefix of each file, extract tool args, parse, and combine. * Arg parsing respects quotation marks. Result is a map from ToolName to the combined tokens. */ def toolArgsFor(files: List[JPath], charset: Charset = UTF_8): ToolArgs = - files.foldLeft(Map.empty[ToolName, List[String]]) { (res, path) => + val (_, toolArgs) = platformAndToolArgsFor(files, charset) + toolArgs + +/** Take a prefix of each file, extract tool args, parse, and combine. + * Arg parsing respects quotation marks. Result is a map from ToolName to the combined tokens. + * If the ToolName is Target, then also accumulate the file name associated with the given platform. + */ +def platformAndToolArgsFor(files: List[JPath], charset: Charset = UTF_8): (PlatformFiles, ToolArgs) = + files.foldLeft(Map.empty[TestPlatform, List[String]] -> Map.empty[ToolName, List[String]]) { (res, path) => val toolargs = toolArgsParse(resource(Files.lines(path, charset))(_.limit(10).toScala(List)), Some(path.toString)) toolargs.foldLeft(res) { - case (acc, (tool, args)) => + case ((plat, acc), (tool, args)) => val name = ToolName.named(tool) val tokens = CommandLineParser.tokenize(args) - acc.updatedWith(name)(v0 => v0.map(_ ++ tokens).orElse(Some(tokens))) + + val plat1 = if name eq ToolName.Target then + val testPlatform = TestPlatform.named(tokens.head) + val fileName = path.toString + plat.updatedWith(testPlatform)(_.map(fileName :: _).orElse(Some(fileName :: Nil))) + else + plat + + plat1 -> acc.updatedWith(name)(v0 => v0.map(_ ++ tokens).orElse(Some(tokens))) } } @@ -94,6 +123,8 @@ private val toolArg = raw"(?://|/\*| \*) ?(?i:(${ToolName.values.mkString("|")}) /** Directive to specify to vulpix the options to pass to Dotty */ private val directiveOptionsArg = raw"//> using options (.*)".r.unanchored private val directiveJavacOptions = raw"//> using javacOpt (.*)".r.unanchored +private val directiveTargetOptions = raw"//> using target.platform (jvm|scala-js)".r.unanchored +private val directiveUnknown = raw"//> using (.*)".r.unanchored // Inspect the lines for compiler options of the form // `//> using options args`, `// scalajs: args`, `/* scalajs: args`, ` * scalajs: args` etc. @@ -109,6 +140,8 @@ def toolArgsParse(lines: List[String], filename: Option[String]): List[(String,S lines.flatMap { case directiveOptionsArg(args) => List(("scalac", args)) case directiveJavacOptions(args) => List(("javac", args)) + case directiveTargetOptions(platform) => List(("target", platform)) + case directiveUnknown(rest) => sys.error(s"Unknown directive: `//> using ${CommandLineParser.tokenize(rest).headOption.getOrElse("''")}`${filename.fold("")(f => s" in file $f")}") case _ => Nil } diff --git a/compiler/test/dotty/tools/vulpix/FileFilter.scala b/compiler/test/dotty/tools/vulpix/FileFilter.scala index 9f62a7db2fb6..7bb098e1903e 100644 --- a/compiler/test/dotty/tools/vulpix/FileFilter.scala +++ b/compiler/test/dotty/tools/vulpix/FileFilter.scala @@ -11,13 +11,13 @@ object FileFilter { exclude(file :: files.toList) def exclude(files: List[String]): FileFilter = new FileFilter { - private val blackList = files.toSet - def accept(file: String): Boolean = !blackList.contains(file) + private val excluded = files.toSet + def accept(file: String): Boolean = !excluded.contains(file) } def include(files: List[String]): FileFilter = new FileFilter { - private val whiteList = files.toSet - def accept(file: String): Boolean = whiteList.contains(file) + private val included = files.toSet + def accept(file: String): Boolean = included.contains(file) } object NoFilter extends FileFilter { diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index f565ac328d46..7656acb38f70 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -6,13 +6,14 @@ import scala.language.unsafeNulls import java.io.{File => JFile, IOException, PrintStream, ByteArrayOutputStream} import java.lang.System.{lineSeparator => EOL} +import java.lang.management.ManagementFactory import java.net.URL import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.nio.file.{Files, NoSuchFileException, Path, Paths} import java.nio.charset.{Charset, StandardCharsets} import java.text.SimpleDateFormat import java.util.{HashMap, Timer, TimerTask} -import java.util.concurrent.{ExecutionException, TimeUnit, TimeoutException, Executors => JExecutors} +import java.util.concurrent.{TimeUnit, TimeoutException, Executors => JExecutors} import scala.collection.mutable import scala.io.{Codec, Source} @@ -60,6 +61,8 @@ trait ParallelTesting extends RunnerOrchestration { self => /** Contains a list of failed tests to run, if list is empty no tests will run */ def failedTests: Option[List[String]] + protected def testPlatform: TestPlatform = TestPlatform.JVM + /** A test source whose files or directory of files is to be compiled * in a specific way defined by the `Test` */ @@ -68,6 +71,7 @@ trait ParallelTesting extends RunnerOrchestration { self => def outDir: JFile def flags: TestFlags def sourceFiles: Array[JFile] + def checkFile: Option[JFile] def runClassPath: String = outDir.getPath + JFile.pathSeparator + flags.runClassPath @@ -156,6 +160,11 @@ trait ParallelTesting extends RunnerOrchestration { self => } } } + + final override def toString: String = sourceFiles match { + case Array(f) => f.getPath + case _ => outDir.getPath.stripPrefix(defaultOutputDir).stripPrefix(name).stripPrefix("/") + } } private sealed trait FromTastyCompilationMode @@ -177,7 +186,9 @@ trait ParallelTesting extends RunnerOrchestration { self => ) extends TestSource { def sourceFiles: Array[JFile] = files.filter(isSourceFile) - override def toString() = sourceFiles match { case Array(f) => f.getPath case _ => outDir.getPath } + def checkFile: Option[JFile] = + sourceFiles.map(f => new JFile(f.getPath.replaceFirst("\\.(scala|java)$", ".check"))) + .find(_.exists()) } /** A test source whose files will be compiled separately according to their @@ -209,6 +220,12 @@ trait ParallelTesting extends RunnerOrchestration { self => .map { (g, f) => (g, f.sorted) } def sourceFiles = compilationGroups.map(_._2).flatten.toArray + + def checkFile: Option[JFile] = + val platform = + if allToolArgs.getOrElse(ToolName.Target, Nil).nonEmpty then s".$testPlatform" + else "" + Some(new JFile(dir.getPath + platform + ".check")).filter(_.exists) } protected def shouldSkipTestSource(testSource: TestSource): Boolean = false @@ -221,7 +238,7 @@ trait ParallelTesting extends RunnerOrchestration { self => rerun.exists(dir.getPath.contains) }) - private trait CompilationLogic { this: Test => + protected trait CompilationLogic { this: Test => def suppressErrors = false /** @@ -254,12 +271,6 @@ trait ParallelTesting extends RunnerOrchestration { self => final def countWarnings(reporters: Seq[TestReporter]) = countErrorsAndWarnings(reporters)._2 final def reporterFailed(r: TestReporter) = r.errorCount > 0 - /** - * For a given test source, returns a check file against which the result of the test run - * should be compared. Is used by implementations of this trait. - */ - final def checkFile(testSource: TestSource): Option[JFile] = (CompilationLogic.checkFilePath(testSource)).filter(_.exists) - /** * Checks if the given actual lines are the same as the ones in the check file. * If not, fails the test. @@ -335,22 +346,10 @@ trait ParallelTesting extends RunnerOrchestration { self => } } - object CompilationLogic { - private[ParallelTesting] def checkFilePath(testSource: TestSource) = testSource match { - case ts: JointCompilationSource => - ts.files.collectFirst { - case f if !f.isDirectory => - new JFile(f.getPath.replaceFirst("\\.(scala|java)$", ".check")) - } - case ts: SeparateCompilationSource => - Option(new JFile(ts.dir.getPath + ".check")) - } - } - /** Each `Test` takes the `testSources` and performs the compilation and assertions * according to the implementing class "neg", "run" or "pos". */ - private class Test(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit val summaryReport: SummaryReporting) extends CompilationLogic { test => + protected class Test(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit val summaryReport: SummaryReporting) extends CompilationLogic { test => import summaryReport._ @@ -461,7 +460,7 @@ trait ParallelTesting extends RunnerOrchestration { self => /** Print a progress bar for the current `Test` */ private def updateProgressMonitor(start: Long): Unit = - if testSourcesCompleted < sourceCount then + if testSourcesCompleted < sourceCount && !isUserDebugging then realStdout.print(s"\r${makeProgressBar(start)}") private def finishProgressMonitor(start: Long): Unit = @@ -499,7 +498,8 @@ trait ParallelTesting extends RunnerOrchestration { self => val files: Array[JFile] = files0.flatMap(flattenFiles) - val toolArgs = toolArgsFor(files.toList.map(_.toPath), getCharsetFromEncodingOpt(flags0)) + val (platformFiles, toolArgs) = + platformAndToolArgsFor(files.toList.map(_.toPath), getCharsetFromEncodingOpt(flags0)) val spec = raw"(\d+)(\+)?".r val testIsFiltered = toolArgs.get(ToolName.Test) match @@ -516,12 +516,6 @@ trait ParallelTesting extends RunnerOrchestration { self => .and("-d", targetDir.getPath) .withClasspath(targetDir.getPath) - def waitForJudiciously(process: Process): Int = - try process.waitFor() - catch case _: InterruptedException => - try if process.waitFor(5L, TimeUnit.MINUTES) then process.exitValue() else -2 - finally Thread.currentThread.interrupt() - def compileWithJavac(fs: Array[String]) = if (fs.nonEmpty) { val fullArgs = Array( "-encoding", StandardCharsets.UTF_8.name, @@ -530,7 +524,7 @@ trait ParallelTesting extends RunnerOrchestration { self => val process = Runtime.getRuntime.exec("javac" +: fullArgs) val output = Source.fromInputStream(process.getErrorStream).mkString - if waitForJudiciously(process) != 0 then Some(output) + if process.waitFor() != 0 then Some(output) else None } else None @@ -557,7 +551,17 @@ trait ParallelTesting extends RunnerOrchestration { self => // If a test contains a Java file that cannot be parsed by Dotty's Java source parser, its // name must contain the string "JAVA_ONLY". val dottyFiles = files.filterNot(_.getName.contains("JAVA_ONLY")).map(_.getPath) - driver.process(allArgs ++ dottyFiles, reporter = reporter) + + val dottyFiles0 = + if platformFiles.isEmpty then dottyFiles + else + val excludedFiles = platformFiles + .collect { case (plat, files) if plat != testPlatform => files } + .flatten + .toSet + dottyFiles.filterNot(excludedFiles) + + driver.process(allArgs ++ dottyFiles0, reporter = reporter) // todo a better mechanism than ONLY. test: -scala-only? val javaFiles = files.filter(_.getName.endsWith(".java")).filterNot(_.getName.contains("SCALA_ONLY")).map(_.getPath) @@ -721,7 +725,7 @@ trait ParallelTesting extends RunnerOrchestration { self => private def mkReporter = TestReporter.reporter(realStdout, logLevel = mkLogLevel) protected def diffCheckfile(testSource: TestSource, reporters: Seq[TestReporter], logger: LoggedRunnable) = - checkFile(testSource).foreach(diffTest(testSource, _, reporterOutputLines(reporters), reporters, logger)) + testSource.checkFile.foreach(diffTest(testSource, _, reporterOutputLines(reporters), reporters, logger)) private def reporterOutputLines(reporters: Seq[TestReporter]): List[String] = reporters.flatMap(_.consoleOutput.split("\n")).toList @@ -755,11 +759,7 @@ trait ParallelTesting extends RunnerOrchestration { self => for fut <- eventualResults do try fut.get() - catch - case ee: ExecutionException if ee.getCause.isInstanceOf[InterruptedException] => - System.err.println("Interrupted (probably running after shutdown)") - ee.printStackTrace() - case ex: Exception => + catch case ex: Exception => System.err.println(ex.getMessage) ex.printStackTrace() @@ -797,13 +797,14 @@ trait ParallelTesting extends RunnerOrchestration { self => diffCheckfile(testSource, reporters, logger) override def maybeFailureMessage(testSource: TestSource, reporters: Seq[TestReporter]): Option[String] = - lazy val (map, expCount) = getWarnMapAndExpectedCount(testSource.sourceFiles.toIndexedSeq) + lazy val (expected, expCount) = getWarnMapAndExpectedCount(testSource.sourceFiles.toIndexedSeq) lazy val obtCount = reporters.foldLeft(0)(_ + _.warningCount) - lazy val (expected, unexpected) = getMissingExpectedWarnings(map, reporters.iterator.flatMap(_.diagnostics)) - lazy val diagnostics = reporters.flatMap(_.diagnostics.toSeq.sortBy(_.pos.line).map(e => s" at ${e.pos.line + 1}: ${e.message}")) - def showLines(title: String, lines: Seq[String]) = if lines.isEmpty then "" else title + lines.mkString("\n", "\n", "") - def hasMissingAnnotations = expected.nonEmpty || unexpected.nonEmpty - def showDiagnostics = showLines("-> following the diagnostics:", diagnostics) + lazy val (unfulfilled, unexpected) = getMissingExpectedWarnings(expected, diagnostics.iterator) + lazy val diagnostics = reporters.flatMap(_.diagnostics.toSeq.sortBy(_.pos.line)) + lazy val messages = diagnostics.map(d => s" at ${d.pos.line + 1}: ${d.message}") + def showLines(title: String, lines: Seq[String]) = if lines.isEmpty then "" else lines.mkString(s"$title\n", "\n", "") + def hasMissingAnnotations = unfulfilled.nonEmpty || unexpected.nonEmpty + def showDiagnostics = showLines("-> following the diagnostics:", messages) Option: if reporters.exists(_.errorCount > 0) then s"""Compilation failed for: ${testSource.title} @@ -812,58 +813,63 @@ trait ParallelTesting extends RunnerOrchestration { self => else if expCount != obtCount then s"""|Wrong number of warnings encountered when compiling $testSource |expected: $expCount, actual: $obtCount - |${showLines("Unfulfilled expectations:", expected)} + |${showLines("Unfulfilled expectations:", unfulfilled)} |${showLines("Unexpected warnings:", unexpected)} |$showDiagnostics |""".stripMargin.trim.linesIterator.mkString("\n", "\n", "") - else if hasMissingAnnotations then s"\nWarnings found on incorrect row numbers when compiling $testSource\n$showDiagnostics" - else if !map.isEmpty then s"\nExpected warnings(s) have {=}: $map" + else if hasMissingAnnotations then + s"""|Warnings found on incorrect row numbers when compiling $testSource + |${showLines("Unfulfilled expectations:", unfulfilled)} + |${showLines("Unexpected warnings:", unexpected)} + |$showDiagnostics + |""".stripMargin.trim.linesIterator.mkString("\n", "\n", "") + else if !expected.isEmpty then s"\nExpected warnings(s) have {=}: $expected" else null end maybeFailureMessage def getWarnMapAndExpectedCount(files: Seq[JFile]): (HashMap[String, Integer], Int) = - val comment = raw"//( *)(nopos-)?warn".r - val map = new HashMap[String, Integer]() + val comment = raw"//(?: *)(nopos-)?warn".r + val map = HashMap[String, Integer]() var count = 0 def bump(key: String): Unit = map.get(key) match case null => map.put(key, 1) case n => map.put(key, n+1) count += 1 - files.filter(isSourceFile).foreach { file => - Using(Source.fromFile(file, StandardCharsets.UTF_8.name)) { source => - source.getLines.zipWithIndex.foreach { case (line, lineNbr) => - comment.findAllMatchIn(line).foreach { m => - m.group(2) match - case "nopos-" => - bump("nopos") - case _ => bump(s"${file.getPath}:${lineNbr+1}") - } - } - }.get - } + for file <- files if isSourceFile(file) do + Using.resource(Source.fromFile(file, StandardCharsets.UTF_8.name)) { source => + source.getLines.zipWithIndex.foreach: (line, lineNbr) => + comment.findAllMatchIn(line).foreach: + case comment("nopos-") => bump("nopos") + case _ => bump(s"${file.getPath}:${lineNbr+1}") + } + end for (map, count) - def getMissingExpectedWarnings(map: HashMap[String, Integer], reporterWarnings: Iterator[Diagnostic]): (List[String], List[String]) = - val unexpected, unpositioned = ListBuffer.empty[String] + // return unfulfilled expected warnings and unexpected diagnostics + def getMissingExpectedWarnings(expected: HashMap[String, Integer], reporterWarnings: Iterator[Diagnostic]): (List[String], List[String]) = + val unexpected = ListBuffer.empty[String] def relativize(path: String): String = path.split(JFile.separatorChar).dropWhile(_ != "tests").mkString(JFile.separator) def seenAt(key: String): Boolean = - map.get(key) match + expected.get(key) match case null => false - case 1 => map.remove(key) ; true - case n => map.put(key, n - 1) ; true + case 1 => expected.remove(key); true + case n => expected.put(key, n - 1); true def sawDiagnostic(d: Diagnostic): Unit = val srcpos = d.pos.nonInlined if srcpos.exists then val key = s"${relativize(srcpos.source.file.toString())}:${srcpos.line + 1}" if !seenAt(key) then unexpected += key else - if(!seenAt("nopos")) unpositioned += relativize(srcpos.source.file.toString()) + if !seenAt("nopos") then unexpected += relativize(srcpos.source.file.toString) reporterWarnings.foreach(sawDiagnostic) - (map.asScala.keys.toList, (unexpected ++ unpositioned).toList) + val splitter = raw"(?:[^:]*):(\d+)".r + val unfulfilled = expected.asScala.keys.toList.sortBy { case splitter(n) => n.toInt case _ => -1 } + (unfulfilled, unexpected.toList) end getMissingExpectedWarnings + end WarnTest private final class RewriteTest(testSources: List[TestSource], checkFiles: Map[JFile, JFile], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) extends Test(testSources, times, threadLimit, suppressAllOutput) { @@ -887,15 +893,15 @@ trait ParallelTesting extends RunnerOrchestration { self => verifyOutput(testSource, reporters, logger) } - private final class RunTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) + protected class RunTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) extends Test(testSources, times, threadLimit, suppressAllOutput) { private var didAddNoRunWarning = false - private def addNoRunWarning() = if (!didAddNoRunWarning) { + protected def addNoRunWarning() = if (!didAddNoRunWarning) { didAddNoRunWarning = true summaryReport.addStartingMessage { """|WARNING |------- - |Run tests were only compiled, not run - this is due to the `dotty.tests.norun` + |Run and debug tests were only compiled, not run - this is due to the `dotty.tests.norun` |property being set |""".stripMargin } @@ -922,7 +928,7 @@ trait ParallelTesting extends RunnerOrchestration { self => } override def onSuccess(testSource: TestSource, reporters: Seq[TestReporter], logger: LoggedRunnable) = - verifyOutput(checkFile(testSource), testSource.outDir, testSource, countWarnings(reporters), reporters, logger) + verifyOutput(testSource.checkFile, testSource.outDir, testSource, countWarnings(reporters), reporters, logger) } private final class NegTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) @@ -998,8 +1004,8 @@ trait ParallelTesting extends RunnerOrchestration { self => def seenAt(key: String): Boolean = errorMap.get(key) match case null => false - case 1 => errorMap.remove(key) ; true - case n => errorMap.put(key, n - 1) ; true + case 1 => errorMap.remove(key); true + case n => errorMap.put(key, n - 1); true def sawDiagnostic(d: Diagnostic): Unit = d.pos.nonInlined match case srcpos if srcpos.exists => @@ -1146,12 +1152,12 @@ trait ParallelTesting extends RunnerOrchestration { self => * `aggregateTests` in the companion, which will ensure that aggregation is allowed. */ final class CompilationTest private ( - private[ParallelTesting] val targets: List[TestSource], - private[ParallelTesting] val times: Int, - private[ParallelTesting] val shouldDelete: Boolean, - private[ParallelTesting] val threadLimit: Option[Int], - private[ParallelTesting] val shouldFail: Boolean, - private[ParallelTesting] val shouldSuppressOutput: Boolean + val targets: List[TestSource], + val times: Int, + val shouldDelete: Boolean, + val threadLimit: Option[Int], + val shouldFail: Boolean, + val shouldSuppressOutput: Boolean ) { import org.junit.Assert.fail @@ -1161,7 +1167,7 @@ trait ParallelTesting extends RunnerOrchestration { self => def this(targets: List[TestSource]) = this(targets, 1, true, None, false, false) - def checkFilePaths: List[JFile] = targets.map(CompilationLogic.checkFilePath).flatten + def checkFiles: List[JFile] = targets.flatMap(_.checkFile) def copy(targets: List[TestSource], times: Int = times, @@ -1186,7 +1192,7 @@ trait ParallelTesting extends RunnerOrchestration { self => * of betasty files. */ def checkNoBestEffortError()(implicit summaryReport: SummaryReporting): this.type = { - val test = new NoBestEffortErrorsTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() + val test = new NoBestEffortErrorsTest(targets, times, threadLimit, shouldFail).executeTestSuite() cleanup() @@ -1254,7 +1260,7 @@ trait ParallelTesting extends RunnerOrchestration { self => checkFail(test, "Rewrite") } - private def checkPass(test: Test, desc: String): this.type = + def checkPass(test: Test, desc: String): this.type = test.executeTestSuite() cleanup() @@ -1585,7 +1591,7 @@ trait ParallelTesting extends RunnerOrchestration { self => | | sbt "testCompilation --from-tasty $file" | - |This tests can be disabled by adding `${file.getName}` to `compiler${JFile.separator}test${JFile.separator}dotc${JFile.separator}$runOrPos-$listName.blacklist` + |This tests can be disabled by adding `${file.getName}` to `compiler${JFile.separator}test${JFile.separator}dotc${JFile.separator}$runOrPos-$listName.excludelist` | |""".stripMargin } @@ -1640,7 +1646,7 @@ trait ParallelTesting extends RunnerOrchestration { self => | | sbt "scalac $bestEffortFlag $semanticDbFlag $file" | - |These tests can be disabled by adding `${file.getName}` to `compiler${JFile.separator}test${JFile.separator}dotc${JFile.separator}neg-best-effort-pickling.blacklist` + |These tests can be disabled by adding `${file.getName}` to `compiler${JFile.separator}test${JFile.separator}dotc${JFile.separator}neg-best-effort-pickling.excludelist` |""".stripMargin } } @@ -1667,7 +1673,7 @@ trait ParallelTesting extends RunnerOrchestration { self => | sbt "scalac -Ybest-effort $file" | sbt "scalac --from-tasty -Ywith-best-effort-tasty $beTastyFilesString" | - |These tests can be disabled by adding `${file.getName}` to `compiler${JFile.separator}test${JFile.separator}dotc${JFile.separator}neg-best-effort-unpickling.blacklist` + |These tests can be disabled by adding `${file.getName}` to `compiler${JFile.separator}test${JFile.separator}dotc${JFile.separator}neg-best-effort-unpickling.excludelist` | |""".stripMargin } @@ -1735,7 +1741,7 @@ trait ParallelTesting extends RunnerOrchestration { self => val bestEffortDir = new JFile(step1OutDir, s"META-INF${JFile.separator}best-effort") val step2Compilation = JointCompilationSource( - testGroup.name, step2SourceFiles, flags.and(withBetastyFlag).and(semanticDbFlag), step2OutDir, fromTasty = WithBestEffortTasty(bestEffortDir) + testGroup.name, step2SourceFiles, flags.and(bestEffortFlag).and(withBetastyFlag).and(semanticDbFlag), step2OutDir, fromTasty = WithBestEffortTasty(bestEffortDir) ) (step1Compilation, step2Compilation, bestEffortDir) }.unzip3 @@ -1744,7 +1750,7 @@ trait ParallelTesting extends RunnerOrchestration { self => new CompilationTest(step1Targets).keepOutput, new CompilationTest(step2Targets).keepOutput, bestEffortDirs, - true + shouldDelete = true ) } @@ -1798,7 +1804,7 @@ trait ParallelTesting extends RunnerOrchestration { self => def noCrashWithCompilingDependencies()(implicit summaryReport: SummaryReporting): this.type = { step1.checkNoBestEffortError() // Compile all files to generate the class files with best effort tasty - step2.checkCompile() // Compile with best effort tasty + step2.checkNoBestEffortError() // Compile with best effort tasty this } @@ -1827,6 +1833,11 @@ trait ParallelTesting extends RunnerOrchestration { self => flags.options.sliding(2).collectFirst { case Array("-encoding", encoding) => Charset.forName(encoding) }.getOrElse(StandardCharsets.UTF_8) + + /** checks if the current process is being debugged */ + def isUserDebugging: Boolean = + val mxBean = ManagementFactory.getRuntimeMXBean + mxBean.getInputArguments.asScala.exists(_.contains("jdwp")) } object ParallelTesting { diff --git a/compiler/test/dotty/tools/vulpix/RunnerOrchestration.scala b/compiler/test/dotty/tools/vulpix/RunnerOrchestration.scala index 9047bb6737dc..a91b0de3e238 100644 --- a/compiler/test/dotty/tools/vulpix/RunnerOrchestration.scala +++ b/compiler/test/dotty/tools/vulpix/RunnerOrchestration.scala @@ -4,7 +4,7 @@ package vulpix import scala.language.unsafeNulls -import java.io.{ File => JFile, InputStreamReader, BufferedReader, PrintStream } +import java.io.{ File => JFile, InputStreamReader, IOException, BufferedReader, PrintStream } import java.nio.file.Paths import java.nio.charset.StandardCharsets import java.util.concurrent.atomic.AtomicBoolean @@ -48,10 +48,29 @@ trait RunnerOrchestration { /** Destroy and respawn process after each test */ def safeMode: Boolean - /** Running a `Test` class's main method from the specified `dir` */ + /** Open JDI connection for testing the debugger */ + def debugMode: Boolean = false + + /** Running a `Test` class's main method from the specified `classpath` */ def runMain(classPath: String, toolArgs: ToolArgs)(implicit summaryReport: SummaryReporting): Status = monitor.runMain(classPath) + /** Each method of Debuggee can be called only once, in the order of definition.*/ + trait Debuggee: + /** read the jdi port to connect the debugger */ + def readJdiPort(): Int + /** start the main method in the background */ + def launch(): Unit + /** wait until the end of the main method */ + def exit(): Status + + /** Provide a Debuggee for debugging the Test class's main method + * @param f the debugging flow: set breakpoints, launch main class, pause, step, evaluate, exit etc + */ + def debugMain(classPath: String)(f: Debuggee => Unit)(implicit summaryReport: SummaryReporting): Unit = + assert(debugMode, "debugMode is disabled") + monitor.debugMain(classPath)(f) + /** Kill all processes */ def cleanup() = monitor.killAll() @@ -70,10 +89,33 @@ trait RunnerOrchestration { def runMain(classPath: String)(implicit summaryReport: SummaryReporting): Status = withRunner(_.runMain(classPath)) - private class Runner(private var process: Process) { - private var childStdout: BufferedReader = uninitialized - private var childStdin: PrintStream = uninitialized + def debugMain(classPath: String)(f: Debuggee => Unit)(implicit summaryReport: SummaryReporting): Unit = + withRunner(_.debugMain(classPath)(f)) + + private class RunnerProcess(p: Process): + private val stdout = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8)) + private val stdin = new PrintStream(p.getOutputStream(), /* autoFlush = */ true) + + def readLine(): String = + stdout.readLine() match + case s"Listening for transport dt_socket at address: $port" => + throw new IOException( + s"Unexpected transport dt_socket message." + + " The port is going to be lost and no debugger will be able to connect." + ) + case line => line + + def printLine(line: String): Unit = stdin.println(line) + + def getJdiPort(): Int = + stdout.readLine() match + case s"Listening for transport dt_socket at address: $port" => port.toInt + case line => throw new IOException(s"Failed getting JDI port of child JVM: got $line") + + export p.{exitValue, isAlive, destroy} + end RunnerProcess + private class Runner(private var process: RunnerProcess): /** Checks if `process` is still alive * * When `process.exitValue()` is called on an active process the caught @@ -82,96 +124,92 @@ trait RunnerOrchestration { */ def isAlive: Boolean = try { process.exitValue(); false } - catch { case _: IllegalThreadStateException => true } + catch case _: IllegalThreadStateException => true /** Destroys the underlying process and kills IO streams */ - def kill(): Unit = { + def kill(): Unit = if (process ne null) process.destroy() process = null - childStdout = null - childStdin = null - } - - /** Did add hook to kill the child VMs? */ - private val didAddCleanupCallback = new AtomicBoolean(false) /** Blocks less than `maxDuration` while running `Test.main` from `dir` */ - def runMain(classPath: String)(implicit summaryReport: SummaryReporting): Status = { - if (didAddCleanupCallback.compareAndSet(false, true)) { - // If for some reason the test runner (i.e. sbt) doesn't kill the VM, we - // need to clean up ourselves. - summaryReport.addCleanup(() => killAll()) - } - assert(process ne null, - "Runner was killed and then reused without setting a new process") - - // Makes the encapsulating RunnerMonitor spawn a new runner - def respawn(): Unit = { - process.destroy() - process = createProcess - childStdout = null - childStdin = null - } - - if (childStdin eq null) - childStdin = new PrintStream(process.getOutputStream, /* autoFlush = */ true) - - // pass file to running process - childStdin.println(classPath) + def runMain(classPath: String): Status = + assert(process ne null, "Runner was killed and then reused without setting a new process") + awaitStatusOrRespawn(startMain(classPath)) + + def debugMain(classPath: String)(f: Debuggee => Unit): Unit = + assert(process ne null, "Runner was killed and then reused without setting a new process") + + val debuggee = new Debuggee: + private var mainFuture: Future[Status] = null + def readJdiPort(): Int = process.getJdiPort() + def launch(): Unit = mainFuture = startMain(classPath) + def exit(): Status = + awaitStatusOrRespawn(mainFuture) + + try f(debuggee) + catch case e: Throwable => + // if debugging failed it is safer to respawn a new process + respawn() + throw e + end debugMain + + private def startMain(classPath: String): Future[Status] = + // pass classpath to running process + process.printLine(classPath) // Create a future reading the object: - val readOutput = Future { + Future: val sb = new StringBuilder - if (childStdout eq null) - childStdout = new BufferedReader(new InputStreamReader(process.getInputStream, StandardCharsets.UTF_8)) - - var childOutput: String = childStdout.readLine() + var childOutput: String = process.readLine() // Discard all messages until the test starts while (childOutput != ChildJVMMain.MessageStart && childOutput != null) - childOutput = childStdout.readLine() - childOutput = childStdout.readLine() + childOutput = process.readLine() + childOutput = process.readLine() - while (childOutput != ChildJVMMain.MessageEnd && childOutput != null) { + while childOutput != ChildJVMMain.MessageEnd && childOutput != null do sb.append(childOutput).append(System.lineSeparator) - childOutput = childStdout.readLine() - } + childOutput = process.readLine() - if (process.isAlive && childOutput != null) Success(sb.toString) + if process.isAlive() && childOutput != null then Success(sb.toString) else Failure(sb.toString) - } + end startMain - // Await result for `maxDuration` and then timout and destroy the - // process: + // wait status of the main class execution, respawn if failure or timeout + private def awaitStatusOrRespawn(future: Future[Status]): Status = val status = - try Await.result(readOutput, maxDuration) - catch { case _: TimeoutException => Timeout } - - // Handle failure of the VM: - status match { - case _: Success if safeMode => respawn() - case _: Success => // no need to respawn sub process - case _: Failure => respawn() - case Timeout => respawn() - } + try Await.result(future, maxDuration) + catch case _: TimeoutException => Timeout + // handle failures + status match + case _: Success if !safeMode => () // no need to respawn + case _ => respawn() // safeMode, failure or timeout status - } - } + + // Makes the encapsulating RunnerMonitor spawn a new runner + private def respawn(): Unit = + process.destroy() + process = null + process = createProcess() + end Runner /** Create a process which has the classpath of the `ChildJVMMain` and the * scala library. */ - private def createProcess: Process = { + private def createProcess(): RunnerProcess = val url = classOf[ChildJVMMain].getProtectionDomain.getCodeSource.getLocation val cp = Paths.get(url.toURI).toString + JFile.pathSeparator + Properties.scalaLibrary val javaBin = Paths.get(sys.props("java.home"), "bin", "java").toString - new ProcessBuilder(javaBin, "-Dfile.encoding=UTF-8", "-Duser.language=en", "-Duser.country=US", "-Xmx1g", "-cp", cp, "dotty.tools.vulpix.ChildJVMMain") + val args = Seq("-Dfile.encoding=UTF-8", "-Duser.language=en", "-Duser.country=US", "-Xmx1g", "-cp", cp) ++ + (if debugMode then Seq("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,quiet=n") else Seq.empty) + val command = (javaBin +: args) :+ "dotty.tools.vulpix.ChildJVMMain" + val process = new ProcessBuilder(command*) .redirectErrorStream(true) .redirectInput(ProcessBuilder.Redirect.PIPE) .redirectOutput(ProcessBuilder.Redirect.PIPE) .start() - } + RunnerProcess(process) private val freeRunners = mutable.Queue.empty[Runner] private val busyRunners = mutable.Set.empty[Runner] @@ -180,7 +218,7 @@ trait RunnerOrchestration { while (freeRunners.isEmpty && busyRunners.size >= numberOfSlaves) wait() val runner = - if (freeRunners.isEmpty) new Runner(createProcess) + if (freeRunners.isEmpty) new Runner(createProcess()) else freeRunners.dequeue() busyRunners += runner @@ -194,12 +232,11 @@ trait RunnerOrchestration { notify() } - private def withRunner[T](op: Runner => T): T = { + private def withRunner[T](op: Runner => T)(using summaryReport: SummaryReporting): T = val runner = getRunner() val result = op(runner) freeRunner(runner) result - } def killAll(): Unit = { freeRunners.foreach(_.kill()) diff --git a/compiler/test/dotty/tools/vulpix/SummaryReport.scala b/compiler/test/dotty/tools/vulpix/SummaryReport.scala index 74612387015f..77bfdc063d08 100644 --- a/compiler/test/dotty/tools/vulpix/SummaryReport.scala +++ b/compiler/test/dotty/tools/vulpix/SummaryReport.scala @@ -30,9 +30,6 @@ trait SummaryReporting { /** Add a message that will be issued in the beginning of the summary */ def addStartingMessage(msg: String): Unit - /** Add a cleanup hook to be run upon completion */ - def addCleanup(f: () => Unit): Unit - /** Echo the summary report to the appropriate locations */ def echoSummary(): Unit @@ -51,7 +48,6 @@ final class NoSummaryReport extends SummaryReporting { def addFailedTest(msg: FailedTestInfo): Unit = () def addReproduceInstruction(instr: String): Unit = () def addStartingMessage(msg: String): Unit = () - def addCleanup(f: () => Unit): Unit = () def echoSummary(): Unit = () def echoToLog(msg: String): Unit = () def echoToLog(it: Iterator[String]): Unit = () @@ -67,7 +63,6 @@ final class SummaryReport extends SummaryReporting { private val startingMessages = new java.util.concurrent.ConcurrentLinkedDeque[String] private val failedTests = new java.util.concurrent.ConcurrentLinkedDeque[FailedTestInfo] private val reproduceInstructions = new java.util.concurrent.ConcurrentLinkedDeque[String] - private val cleanUps = new java.util.concurrent.ConcurrentLinkedDeque[() => Unit] private var passed = 0 private var failed = 0 @@ -87,9 +82,6 @@ final class SummaryReport extends SummaryReporting { def addStartingMessage(msg: String): Unit = startingMessages.add(msg) - def addCleanup(f: () => Unit): Unit = - cleanUps.add(f) - /** Both echoes the summary to stdout and prints to file */ def echoSummary(): Unit = { import SummaryReport._ @@ -131,9 +123,6 @@ final class SummaryReport extends SummaryReporting { if (!isInteractive) println(rep.toString) TestReporter.logPrintln(rep.toString) - - // Perform cleanup callback: - if (!cleanUps.isEmpty()) cleanUps.asScala.foreach(_.apply()) } private def removeColors(msg: String): String = diff --git a/docs/_docs/contributing/debugging/other-debugging.md b/docs/_docs/contributing/debugging/other-debugging.md index e8b72bcca656..db32a25dabd7 100644 --- a/docs/_docs/contributing/debugging/other-debugging.md +++ b/docs/_docs/contributing/debugging/other-debugging.md @@ -26,71 +26,6 @@ $ jdb -attach 5005 -sourcepath tests/debug/ You can run `help` for commands that supported by JDB. -## Debug Automatically with Expect - -### 1. Annotate the source code with debug information. - -Following file (`tests/debug/while.scala`) is an example of annotated source code: - -```scala -object Test { - - def main(args: Array[String]): Unit = { - var a = 1 + 2 - a = a + 3 - a = 4 + 5 // [break] [step: while] - - while (a * 8 < 100) { // [step: a += 1] - a += 1 // [step: while] [cont: print] - } - - print(a) // [break] [cont] - } -} -``` - -The debugging information is annotated as comments to the code in brackets: - -```scala -val x = f(3) // [break] [next: line=5] -val y = 5 -``` - -1. A JDB command must be wrapped in brackets, like `[step]`. All JDB commands can be used. -2. To check output of JDB for a command, use `[cmd: expect]`. -3. If `expect` is wrapped in double quotes, regex is supported. -4. Break commands are collected and set globally. -5. Other commands will be send to jdb in the order they appear in the source file - -Note that JDB uses line number starts from 1. - -### 2. Generate Expect File - -Now we can run the following command to generate an expect file: - -```shell -compiler/test/debug/Gen tests/debug/while.scala > robot -``` - -### 3. Run the Test - -First, compile the file `tests/debug/while.scala`: - -```shell -$ scalac tests/debug/while.scala -``` - -Second, run the compiled class with debugging enabled: - -```shell -$ scala -d Test -``` - -Finally, run the expect script: - -```shell -expect robot -``` ## Other tips ### Show for human readable output diff --git a/docs/_docs/contributing/testing.md b/docs/_docs/contributing/testing.md index 9ea02f071cb6..8b0ecfc217e2 100644 --- a/docs/_docs/contributing/testing.md +++ b/docs/_docs/contributing/testing.md @@ -41,6 +41,8 @@ of the `tests/` directory. A small selection of test categories include: - `tests/pos` – tests that should compile: pass if compiles successfully. - `tests/neg` – should not compile: pass if fails compilation. Useful, e.g., to test an expected compiler error. - `tests/run` – these tests not only compile but are also run. Must include at least a `@main def Test = ...`. +- `tests/debug` – these tests are compiled but also debugged. As for `tests/run` they must include at least a `@main def Test = ...` + See [Debug Tests](#debug-tests). ### Naming and Running a Test Case @@ -205,6 +207,136 @@ $ sbt > testCompilation --from-tasty ``` +## Debug Tests + +Debug tests are a variant of compilation tests located in `compiler/tests/debug`. +Similar to `tests/run`, each test case is executed. +However, instead of verifying the program's output, a debugger is attached to the running program to validate a predefined debug scenario. + +The debug scenario is specified in the `.check` file associated with each test case. +It consists of a sequence of debug steps that describe the debugger interactions and outcomes. + +**Example debug scenario**: +``` +// Pause on a breakpoint in class Test$ on line 5 +break Test$ 5 + +// Stepping in should go to line 10 +step 10 + +// Next should go to line 11 +next 11 + +// Evaluating the expression x should return 42 +eval x +result 42 +``` + +To run all the debug tests: +``` +sbt 'scala3-compiler/testOnly dotty.tools.debug.DebugTests' +``` + +### Debug Steps + +#### Breakpoint + +Syntax: + +``` +break ${runtime class} ${line number} +``` + +Examples: + +``` +break Test$ 5 +break example.A 10 +break example.A$B$1 12 +``` + +A breakpoint is defined by a fully-qualified class name and a source line. + +All breakpoints of a debug scenario are configured before the program starts. + +When the program pauses on a breakpoint, we check the class name and source line of the current frame. + +### Step in + +Syntax: +``` +step ${expected line number or method name} +``` + +Examples: +``` +step 10 +step println +``` + +A `step` request expects the program to enter into the called method or go to the next instruction. +After a step request, we check that the source line (or method name) of the current frame matches the expected one. + +Typically we use a source line when we stay in the same source file and a method name when we step in a library or JDK class. + +### Next + +A `next` request behaves similarly to `step` but jumps over a method call and stops on the next instruction. + +Syntax: +``` +next ${expected line number or method name} +``` + +Examples: +``` +next 10 +next println +``` + +### Evaluation + +Syntax: +``` +eval ${expression} +result ${expected output} + +// or in case an error is expected +eval ${expression} +error ${expected message} +``` + +It also supports multi-line expressions and multi-line error messages. + +Examples: +``` +eval fibonacci(2) +result 55 + +eval + def square(x: Int): Int = + x * x + square(2) +result 4 + +eval foo +error + :1:0 + 1 |foo + |^^^ + | Not found: foo +``` + +An `eval` request verifies that an expression can be evaluated by the `ExpressionCompiler` during a debugging session. +A `result` assertion checks the evaluation produced the expected output, while an `error` assertion checks the compilation failed with the expected error message. + +When the evaluation throws an exception, the exception is returned as a result, not an error. + +``` +eval throw new Exception("foo") +result java.lang.Exception: foo +``` + ## Unit Tests Unit tests cover the other areas of the compiler, such as interactions with the REPL, scripting tools and more. diff --git a/docs/_docs/internals/best-effort-compilation.md b/docs/_docs/internals/best-effort-compilation.md index 248203883a3c..a897aae49e6e 100644 --- a/docs/_docs/internals/best-effort-compilation.md +++ b/docs/_docs/internals/best-effort-compilation.md @@ -87,5 +87,5 @@ the previously created Best Effort TASTy, with `-Yread-tasty` and `-Ywith-best-e TreeUnpickler for those Best Effort TASTy files. One of the goals of this feature is to keep the maintainance cost low, and to not let this feature hinder the pace of the -overall development of the compiler. Because of that, the tests can be freely disabled in `compiler/neg-best-effort.blacklist` -(testing TreePickler) and `compiler/neg-best-effort-from-tasty.blacklist` (testing TreeUnpickler). +overall development of the compiler. Because of that, the tests can be freely disabled in `compiler/neg-best-effort.excludelist` +(testing TreePickler) and `compiler/neg-best-effort-from-tasty.excludelist` (testing TreeUnpickler). diff --git a/docs/_docs/reference/changed-features/compiler-plugins.md b/docs/_docs/reference/changed-features/compiler-plugins.md index c0bfccec8172..ee82dd533af0 100644 --- a/docs/_docs/reference/changed-features/compiler-plugins.md +++ b/docs/_docs/reference/changed-features/compiler-plugins.md @@ -18,7 +18,7 @@ For experimentation and research, Scala 3 introduces _research plugin_. Research are more powerful than Scala 2 analyzer plugins as they let plugin authors customize the whole compiler pipeline. One can easily replace the standard typer by a custom one or create a parser for a domain-specific language. However, research plugins are only -enabled with the `-experimental` compiler flag or in nightly/snapshot releases of Scala 3. +enabled for nightly or snaphot releases of Scala 3. Common plugins that add new phases to the compiler pipeline are called _standard plugins_ in Scala 3. In terms of features, they are similar to diff --git a/docs/_docs/reference/dropped-features/this-qualifier.md b/docs/_docs/reference/dropped-features/this-qualifier.md index f75d19356696..541c91e5cdfa 100644 --- a/docs/_docs/reference/dropped-features/this-qualifier.md +++ b/docs/_docs/reference/dropped-features/this-qualifier.md @@ -29,3 +29,15 @@ This can cause problems if a program tries to access the missing private field v // [C] needed if `field` is to be accessed through reflection val retained = field * field ``` + +Class parameters are normally inferred object-private, +so that members introduced by explicitly declaring them `val` or `var` are exempt from the rule described here. + +In particular, the following field is not excluded from variance checking: +```scala + class C[-T](private val t: T) // error +``` +And in contrast to the private field shown above, this field is not eliminated: +```scala + class C(private val c: Int) +``` diff --git a/docs/_docs/reference/experimental/modularity.md b/docs/_docs/reference/experimental/modularity.md index 66d4c0c23ede..1a3d47695861 100644 --- a/docs/_docs/reference/experimental/modularity.md +++ b/docs/_docs/reference/experimental/modularity.md @@ -108,7 +108,46 @@ This works as it should now. Without the addition of `tracked` to the parameter of `SetFunctor` typechecking would immediately lose track of the element type `T` after an `add`, and would therefore fail. -**Discussion** +**Syntax Change** + +``` +ClsParam ::= {Annotation} [{Modifier | ‘tracked’} (‘val’ | ‘var’)] Param +``` + +The (soft) `tracked` modifier is only allowed for `val` parameters of classes. + +### Tracked inference + +In some cases `tracked` can be infered and doesn't have to be written +explicitly. A common such case is when a class parameter is referenced in the +signatures of the public members of the class. e.g. +```scala 3 +class OrdSet(val ord: Ordering) { + type Set = List[ord.T] + def empty: Set = Nil + + implicit class helper(s: Set) { + def add(x: ord.T): Set = x :: remove(x) + def remove(x: ord.T): Set = s.filter(e => ord.compare(x, e) != 0) + def member(x: ord.T): Boolean = s.exists(e => ord.compare(x, e) == 0) + } +} +``` +In the example above, `ord` is referenced in the signatures of the public +members of `OrdSet`, so a `tracked` modifier will be inserted automatically. + +Another common case is when a context bound has an associated type (i.e. an abstract type member) e.g. +```scala 3 +trait TC: + type Self + type T + +class Klass[A: {TC as tc}] +``` + +Here, `tc` is a context bound with an associated type `T`, so `tracked` will be inferred for `tc`. + +### Discussion Since `tracked` is so useful, why not assume it by default? First, `tracked` makes sense only for `val` parameters. If a class parameter is not also a field declared using `val` then there's nothing to refine in the constructor result type. One could think of at least making all `val` parameters tracked by default, but that would be a backwards incompatible change. For instance, the following code would break: diff --git a/docs/_docs/reference/experimental/package-object-values.md b/docs/_docs/reference/experimental/package-object-values.md new file mode 100644 index 000000000000..1ca9c701970a --- /dev/null +++ b/docs/_docs/reference/experimental/package-object-values.md @@ -0,0 +1,40 @@ +--- +layout: doc-page +title: "Reference-able Package Objects" +redirectFrom: /docs/reference/experimental/package-object-values.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/package-object-values.html +--- + +One limitation with `package object`s is that we cannot currently assign them to values: `a.b` fails to compile when `b` is a `package object`, even though it succeeds when `b` is a normal `object`. The workaround is to call +```scala + a.b.`package` +``` +But this is ugly and non-obvious. Or one could use a normal `object`, which is not always possible. + +The `packageObjectValues` language extension drops this limitation. The extension is enabled by the language import `import scala.language.experimental.packageObjectValues` or by setting the command line option `-language:experimental.packageObjectValues`. + +The extension, turns the following into valid code: + +```scala +package a +package object b + +val z = a.b // Currently fails with "package is not a value" +``` + +Currently the workaround is to use a `.package` suffix: + +```scala +val z = a.b.`package` +``` + +With the extension, a reference such as `a.b` where `b` is a `package` containing a `package object`, expands to `a.b.package` automatically + +## Limitations + +* `a.b` only expands to `a.b.package` when used "standalone", i.e. not when part of a larger select chain `a.b.c` or equivalent postfix expression `a.b c`, prefix expression `!a.b`, or infix expression `a.b c d`. + +* `a.b` expands to `a.b.package` of the type `a.b.package.type`, and only contains the contents of the `package object`. It does not contain other things in the `package` `a.b` that are outside of the `package object` + +Both these requirements are necessary for backwards compatibility, and anyway do not impact the main goal of removing the irregularity between `package object`s and normal `object`s. + diff --git a/docs/_docs/reference/experimental/unrolled-defs.md b/docs/_docs/reference/experimental/unrolled-defs.md new file mode 100644 index 000000000000..f2e09e82bc18 --- /dev/null +++ b/docs/_docs/reference/experimental/unrolled-defs.md @@ -0,0 +1,156 @@ +--- +layout: doc-page +title: "Automatic Parameter Unrolling" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/unrolled-defs.html +--- + +Parameter unrolling enables new parameters to be added to methods and classes, +while still preserving backwards binary compatibility. An `@unroll` annotation, on a parameter with default value, will generate backwards compatible forwarders to a method or constructor. + +## Example +```scala +// V1 +final def foo( + s: String, + i: Int +): String = s + i +``` + +In the example above, assume version `V1` of a library defines the method `foo` with two parameters: `s` and `i`. +Assume a client library or application `C1` compiles against `V1` of `foo`. + +```scala +// V2 +final def foo( + s: String, + i: Int, + @unroll b: Boolean = true, + l: Long = 0L +): String = s + i + b + l + +// Generated automatically +`` final def foo( + s: String, + i: Int +) = foo(s, i, true, 0L) +``` + +In version `V2`, the library adds the `b` and `l` parameters to `foo`, along with default values. +To preserve compatibility with `V1`, `b` is annotated with `@unroll`, generating a forwarder with only the parameters that come before, i.e. it has the same signature as `foo` in `V1`. + +A client `C2` compiling against `V2` will only see `foo` with four parameters in the public API. +The generated forwarder is hidden from those clients. +However, `C1` remains compatible with `V2` of the library, and does not need to be recompiled. +At runtime, it will continue to link against the signature of the old `foo` method, and call the generated forwarder which is accessible in the binary API. + +## Specification + +### `@unroll` annotation + +The `scala.annotation.unroll` annotation can be applied to any term parameter of an effectively-final method: +- `def` in an `object` (i.e. `final` may be omitted) +- `final def` in a `class` or `trait` +- `class` parameters (i.e. primary constructors) +- `def this` in a `class` (i.e. secondary constructors) + +### Restrictions + +It is illegal for `@unroll` to be applied to any other definition (including `trait` parameters and local methods), or to annotate a type. + +`@unroll` may be applied to more than one parameter per method, but all occurrences must appear in the same parameter clause. + +The annotated parameter, and any parameters to the right in the same parameter clause, must have a default value. + +It is a compile-time error if any generated forwarder matches the signature of another declaration in the same class. + +## Code generation + +Expansion of `@unroll` parameters is performed before TASTy generation, so generated code will appear in TASTy. + +Below specifies the transformations that occur: + +For each method `m` of a template, there is a target method `t` which is checked for `@unroll`: +- for `fromProduct`, `copy`, and `apply` of the companion of case class `C`, then `t` is the primary constructor of `C`. +- otherwise `m` is `t`. + +if `t` has a single parameter list with `@unroll` annotations, then `m` is subject to code generation. There are two +possible transformations: +1. Forwarder generation +2. Reimplementation: for `fromProduct` of a case class companion + +### (1) Forwarder generation + +In a method `foo` with unrolled parameters in parameter list `i`: +each parameter `p` with an `@unroll` annotation causes the generation of exactly one forwarder method `f_p`. + +for a given method with generic signature + +```scala +final def foo[T](ps0...)(psX..., @unroll p, psY...)(psN...): T = + ... +``` +then `f_p` will take the form + +```scala +`` final def foo[T](ps0...)(psX...)(psN...): T = + foo(ps0...)(psX..., p_D, psY_D...)(psN...) +``` + +i.e. result type is preserved, parameter lists before and after `i` are unchanged, and within `i`: +- the parameters `psX...` to the left of `p` are preserved, +- the parameters `p` and `psY...` are dropped. + +In the body of `f_p`, parameters are passed positionally to the original `foo`, except for the dropped parameters, which are replaced by default arguments for those parameters (`p_D` for `p`, and `psY_D...` for `psY...`). + +Forwarders are generated after type checking, before pickling, and with the `Invisible` flag. +This means that while present in TASTy, they can not be resolved from other top-level classes. + +Forwarder method parameters do not have default values, and are never annotated with `@unroll`. + +### (2) Method reimplementation + +To preserve semantic compatibility of `fromProduct`, its body is replaced with a pattern match over the `productArity` of the parameter. +For each forwarder generated for the case class primary constructor, an equivalent case is generated in the pattern match. + +e.g. for a forwarder +```scala +`` def this(ps...) = this(ps..., ds...) +``` +then the following case is generated: +```scala +case n => new C(...p.productElement(n - 1), ds...) +``` +where `n` is an integer matching the number of parameters in `ps`. + +The pattern match will have a default wildcard case, which has the same body as the original `fromProduct` method. + +In all the complete transformation: + +```scala +case class C(ps0...) // ps0 has z parameters + +object C: + def fromProduct(p: Product): C = + p.productArity match + case ... => ... + case n => new C(...p.productElement(n - 1), ds...) + case _ => new C(...p.productElement(z - 1)) +``` + + +## Background Motivation + +The Scala language library ecosystem is based upon compatability of API's represented via both the TASTy format (TASTy compatibility), and the Java class file format (binary compatibility). + +Adding a parameter to a method or constructor is a binary backwards incompatible change: +clients compiled against the previous version will expect the old signature to exist, and cause a `LinkageError` to be thrown at runtime. +The correct solution to this problem, to preserve compatibility, is to duplicate the method before adding the new parameter. + +In practice, Scala users developed various techniques and disciplines for mitigating this problem when evolving APIs. +Either by forbidding certain features, such as case classes, or various code generation frameworks. Here are some well-known examples: + +1. [data-class](https://index.scala-lang.org/alexarchambault/data-class) +2. [SBT Contraband](https://www.scala-sbt.org/contraband/) +3. [Structural Data Structures](https://github.com/scala/docs.scala-lang/pull/2662) + +The `@unroll` annotation was proposed as an alternative to these disciplines that not not require learning a new meta-language on top of Scala. The standard data modelling techniques of `def`, `case class`, `enum`, `class` and `trait` are preserved, and the mistake-prone boilerplate is automated. diff --git a/docs/_docs/reference/other-new-features/creator-applications.md b/docs/_docs/reference/other-new-features/creator-applications.md index 8b1de02b2f25..f3b58b87bc48 100644 --- a/docs/_docs/reference/other-new-features/creator-applications.md +++ b/docs/_docs/reference/other-new-features/creator-applications.md @@ -39,8 +39,11 @@ The precise rules are as follows: - the class has a companion object (which might have been generated in step 1), and - that companion object does not already define a member named `apply`. - Each generated `apply` method forwards to one constructor of the class. It has the - same type and value parameters as the constructor. + Each generated `apply` method forwards to one constructor of the class. + It has the same type and value parameters as the constructor, + as well as the same access restriction as the class. + If the class is `protected`, then either the companion object must be `protected` + or the `apply` method will be made `protected`. Constructor proxy companions cannot be used as values by themselves. A proxy companion object must be selected with `apply` (or be applied to arguments, in which case the `apply` is implicitly diff --git a/docs/_docs/reference/other-new-features/kind-polymorphism.md b/docs/_docs/reference/other-new-features/kind-polymorphism.md index e452ee8384f9..4bb1e659dfe9 100644 --- a/docs/_docs/reference/other-new-features/kind-polymorphism.md +++ b/docs/_docs/reference/other-new-features/kind-polymorphism.md @@ -43,5 +43,4 @@ It is declared `abstract` and `final`, so it can be neither instantiated nor ext `AnyKind` plays a special role in Scala's subtype system: It is a supertype of all other types no matter what their kind is. It is also assumed to be kind-compatible with all other types. Furthermore, `AnyKind` is treated as a higher-kinded type (so it cannot be used as a type of values), but at the same time it has no type parameters (so it cannot be instantiated). -**Note:** This feature is considered experimental but stable and it can be disabled under compiler flag -(i.e. `-Yno-kind-polymorphism`). +**Note:** This feature is now stable. The compiler flag `-Yno-kind-polymorphism` is deprecated as of 3.7.0, has no effect (is ignored), and will be removed in a future version. diff --git a/docs/_docs/reference/other-new-features/matchable.md b/docs/_docs/reference/other-new-features/matchable.md index 234fdf03220c..f6c7673416eb 100644 --- a/docs/_docs/reference/other-new-features/matchable.md +++ b/docs/_docs/reference/other-new-features/matchable.md @@ -12,7 +12,7 @@ The Scala 3 standard library has a type [`IArray`](https://scala-lang.org/api/3. arrays that is defined like this: ```scala - opaque type IArray[+T] = Array[_ <: T] + opaque type IArray[+T] = Array[? <: T] ``` The `IArray` type offers extension methods for `length` and `apply`, but not for `update`; hence it seems values of type `IArray` cannot be updated. diff --git a/docs/_docs/reference/experimental/named-tuples.md b/docs/_docs/reference/other-new-features/named-tuples.md similarity index 98% rename from docs/_docs/reference/experimental/named-tuples.md rename to docs/_docs/reference/other-new-features/named-tuples.md index 27d74259725d..0d1145e83ba2 100644 --- a/docs/_docs/reference/experimental/named-tuples.md +++ b/docs/_docs/reference/other-new-features/named-tuples.md @@ -1,10 +1,11 @@ --- layout: doc-page title: "Named Tuples" -nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/named-tuples.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/other-new-features/named-tuples.html --- -The elements of a tuple can now be named. Example: +Starting in Scala 3.7, the elements of a tuple can be named. +Example: ```scala type Person = (name: String, age: Int) val Bob: Person = (name = "Bob", age = 33) diff --git a/docs/_docs/reference/other-new-features/preview-defs.md b/docs/_docs/reference/other-new-features/preview-defs.md new file mode 100644 index 000000000000..3fc25bc48f9e --- /dev/null +++ b/docs/_docs/reference/other-new-features/preview-defs.md @@ -0,0 +1,34 @@ +--- +layout: doc-page +title: "Preview Definitions" +nightlyOf: https://docs.scala-lang.org/scala3/reference/other-new-features/preview-defs.html +--- + +New Scala language features or standard library APIs are initially introduced as experimental, but once they become fully implemented and accepted by the [SIP](https://docs.scala-lang.org/sips/) these can become a preview features. +Preview language features and APIs are guaranteed to be standardized in some next Scala minor release, but allow the compiler team to introduce small, possibly binary incompatible, changes based on the community feedback. +These can be used by early adopters who can accept the possibility of binary compatibility breakage. For instance, preview features could be used in some internal tool or application. On the other hand, preview features are discouraged in publicly available libraries. + +Users can enable access to preview features and definitions by compiling with the `-preview` flag. The flag would enable all preview features and definitions. There is no scheme for enabling only a subset of preview features. + +The biggest difference of preview features compared to experimental features is their non-viral behavior. +A definition compiled in preview mode (using the `-preview` flag) is not marked as a preview definition itself. +This behavior allows to use preview features transitively in other compilation units without explicitly enabled preview mode, as long as it does not directly reference APIs or features marked as preview. + +The [`@preview`](https://scala-lang.org/api/3.x/scala/annotation/internal/preview.html) annotation is used to mark Scala 3 standard library APIs currently available under preview mode. +The rules for `@preview` are similar to [`@experimental`](https://scala-lang.org/api/3.x/scala/annotation/experimental.html) when it comes to accessing, subtyping, overriding or overloading definitions marked with this annotation - all of these can only be performed in compilation units that enable preview mode. + +```scala +//> using options -preview +package scala.stdlib +import scala.annotation.internal.preview + +@preview def previewFeature: Unit = () + +// Can be used in non-preview scope +def usePreviewFeature = previewFeature +``` + +```scala +def usePreviewFeatureTransitively = scala.stdlib.usePreviewFeature +def usePreviewFeatureDirectly = scala.stdlib.previewFeature // error - referring to preview definition outside preview scope +``` diff --git a/docs/_docs/reference/other-new-features/toplevel-definitions.md b/docs/_docs/reference/other-new-features/toplevel-definitions.md new file mode 100644 index 000000000000..b1793bd1941c --- /dev/null +++ b/docs/_docs/reference/other-new-features/toplevel-definitions.md @@ -0,0 +1,41 @@ +--- +layout: doc-page +title: "Toplevel Definitions" +nightlyOf: https://docs.scala-lang.org/scala3/reference/dropped-features/toplevel-definitions.html +--- + +All kind of definitions can now be written at the top-level. +Example: +```scala +package p +type Labelled[T] = (String, T) +val a: Labelled[Int] = ("count", 1) +def b = a._2 + +case class C() + +extension (x: C) def pair(y: C) = (x, y) +``` +Previously, `type`, `val` or `def` definitions had to be wrapped in a package object. Now, +there may be several source files in a package containing such top-level definitions, and source files can freely mix top-level value, method, and type definitions with classes and objects. + +The compiler generates synthetic objects that wrap top-level definitions falling into one of the following categories: + + - all pattern, value, method, and type definitions, + - implicit classes and objects, + - companion objects of opaque type aliases. + +If a source file `src.scala` contains such top-level definitions, they will be put in a synthetic object named `src$package`. The wrapping is transparent, however. The definitions in `src` can still be accessed as members of the enclosing package. The synthetic object will be placed last in the file, +after any other package clauses, imports, or object and class definitions. + +**Note:** This means that +1. The name of a source file containing wrapped top-level definitions is relevant for binary compatibility. If the name changes, so does the name of the generated object and its class. + +2. A top-level main method `def main(args: Array[String]): Unit = ...` is wrapped as any other method. If it appears +in a source file `src.scala`, it could be invoked from the command line using a command like `scala src$package`. Since the +"program name" is mangled it is recommended to always put `main` methods in explicitly named objects. + +3. The notion of `private` is independent of whether a definition is wrapped or not. A `private` top-level definition is always visible from everywhere in the enclosing package. + +4. If several top-level definitions are overloaded variants with the same name, +they must all come from the same source file. diff --git a/docs/_docs/reference/experimental/better-fors.md b/docs/_docs/reference/preview/better-fors.md similarity index 72% rename from docs/_docs/reference/experimental/better-fors.md rename to docs/_docs/reference/preview/better-fors.md index a4c42c9fb380..d5fd32da9a1e 100644 --- a/docs/_docs/reference/experimental/better-fors.md +++ b/docs/_docs/reference/preview/better-fors.md @@ -1,12 +1,10 @@ --- layout: doc-page title: "Better fors" -nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/better-fors.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/preview/better-fors.html --- -The `betterFors` language extension improves the usability of `for`-comprehensions. - -The extension is enabled by the language import `import scala.language.experimental.betterFors` or by setting the command line option `-language:experimental.betterFors`. +Starting in Scala `3.7` under `-preview` mode, the usability of `for`-comprehensions is improved. The biggest user facing change is the new ability to start `for`-comprehensions with aliases. This means that the following previously invalid code is now valid: @@ -30,11 +28,11 @@ for yield a + b ``` -Additionally this extension changes the way `for`-comprehensions are desugared. The desugaring is now done in a more intuitive way and the desugared code can be more efficient, because it avoids some unnecessary method calls. There are two main changes in the desugaring: +Additionally, this extension changes the way `for`-comprehensions are desugared. The desugaring is now done in a more intuitive way and the desugared code can be more efficient, because it avoids some unnecessary method calls. There are two main changes in the desugaring: 1. **Simpler Desugaring for Pure Aliases**: When an alias is not followed by a guard, the desugaring is simplified. The last generator and the aliases don't have to be wrapped in a tuple, and instead the aliases are simply introduced as local variables in a block with the next generator. - **Current Desugaring**: + **Previous Desugaring**: ```scala for { a <- doSth(arg) @@ -60,7 +58,7 @@ Additionally this extension changes the way `for`-comprehensions are desugared. This change makes the desugaring more intuitive and avoids unnecessary `map` calls, when an alias is not followed by a guard. 2. **Avoiding Redundant `map` Calls**: - When the result of the `for`-comprehension is the same expression as the last generator pattern, the desugaring avoids an unnecessary `map` call. but th eequality of the last pattern and the result has to be able to be checked syntactically, so it is either a variable or a tuple of variables. + When the result of the `for`-comprehension is the same expression as the last generator pattern, the desugaring avoids an unnecessary `map` call. But the equality of the last pattern and the result has to be able to be checked syntactically, so it is either a variable or a tuple of variables. There is also a special case for dropping the `map`, if its body is a constant function, that returns `()` (`Unit` constant). **Current Desugaring**: ```scala for { diff --git a/docs/_docs/reference/preview/overview.md b/docs/_docs/reference/preview/overview.md new file mode 100644 index 000000000000..ec8d36bdfd25 --- /dev/null +++ b/docs/_docs/reference/preview/overview.md @@ -0,0 +1,24 @@ +--- +layout: doc-page +title: "Preview" +nightlyOf: https://docs.scala-lang.org/scala3/reference/preview/overview.html +--- + +## Preview language features + +New Scala language features or standard library APIs are initially introduced as experimental, but once they become fully implemented and accepted by the [SIP](https://docs.scala-lang.org/sips/) these can become a preview features. + +Preview language features and APIs are guaranteed to be standardized in some next Scala minor release, but allow the compiler team to introduce small, possibly binary incompatible, changes based on the community feedback. +These can be used by early adopters who can accept the possibility of binary compatibility breakage. For instance, preview features could be used in some internal tool or application. On the other hand, preview features are discouraged in publicly available libraries. + +More information about preview featues can be found in [preview defintions guide](../other-new-features/preview-defs.md) + +### `-preview` compiler flag + +This flag enables the use of all preview language feature in the project. + + +## List of available preview features + +* [`better-fors`](./better-fors.md): Enables new for-comprehension behaviour under SIP-62 under `-source:3.7` or later + diff --git a/docs/_spec/03-types.md b/docs/_spec/03-types.md index 4b1293258495..5d4e205aace9 100644 --- a/docs/_spec/03-types.md +++ b/docs/_spec/03-types.md @@ -1071,7 +1071,7 @@ The following properties hold about ´⌈X⌉´ (we have paper proofs for those) The "lower-bound rule" states that ´S <: T´ if ´T = q.X´ and ´q.X´ is a non-class type designator and ´S <: L´ where ´L´ is the lower bound of the underlying type definition of ´q.X´". That rule is known to break transitivy of subtyping in Scala already. -Second, we define the relation ´⋔´ on *classes* (including traits and hidden classes of objects) as: +Second, we define the relation ´⋔´ on *classes* (including traits, hidden classes of objects, and enum terms) as: - ´C ⋔ D´ if `´C ∉´ baseClasses´(D)´` and ´D´ is `final` - ´C ⋔ D´ if `´D ∉´ baseClasses´(C)´` and ´C´ is `final` diff --git a/docs/sidebar.yml b/docs/sidebar.yml index a306d8bdf274..aecd974326ab 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -72,6 +72,7 @@ subsection: - page: reference/other-new-features/export.md - page: reference/other-new-features/opaques.md - page: reference/other-new-features/opaques-details.md + - page: reference/other-new-features/named-tuples.md - page: reference/other-new-features/open-classes.md - page: reference/other-new-features/parameter-untupling.md - page: reference/other-new-features/parameter-untupling-spec.md @@ -85,6 +86,7 @@ subsection: - page: reference/other-new-features/safe-initialization.md - page: reference/other-new-features/type-test.md - page: reference/other-new-features/experimental-defs.md + - page: reference/other-new-features/preview-defs.md - page: reference/other-new-features/binary-literals.md - title: Other Changed Features directory: changed-features @@ -123,7 +125,6 @@ subsection: - page: reference/dropped-features/type-projection.md - page: reference/dropped-features/do-while.md - page: reference/dropped-features/procedure-syntax.md - - page: reference/dropped-features/package-objects.md - page: reference/dropped-features/early-initializers.md - page: reference/dropped-features/class-shadowing.md - page: reference/dropped-features/class-shadowing-spec.md @@ -138,6 +139,11 @@ subsection: - page: reference/dropped-features/nonlocal-returns.md - page: reference/dropped-features/this-qualifier.md - page: reference/dropped-features/wildcard-init.md + - title: Preview Features + directory: preview + index: reference/preview/overview.md + subsection: + - page: reference/preview/better-fors.md - title: Experimental Features directory: experimental index: reference/experimental/overview.md @@ -158,11 +164,11 @@ subsection: - page: reference/experimental/cc.md - page: reference/experimental/purefuns.md - page: reference/experimental/tupled-function.md - - page: reference/experimental/named-tuples.md - page: reference/experimental/modularity.md - page: reference/experimental/typeclasses.md - page: reference/experimental/runtimeChecked.md - - page: reference/experimental/better-fors.md + - page: reference/experimental/unrolled-defs.md + - page: reference/experimental/package-object-values.md - page: reference/syntax.md - title: Language Versions index: reference/language-versions/language-versions.md diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index e878866be81e..9821822f6d66 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -611,7 +611,7 @@ class DottyLanguageServer extends LanguageServer private def inProjectsSeeing(baseDriver: InteractiveDriver, definitions: List[SourceTree], symbols: List[Symbol]): List[(InteractiveDriver, Context, List[Symbol])] = { - val projects = projectsSeeing(definitions)(baseDriver.currentCtx) + val projects = projectsSeeing(definitions)(using baseDriver.currentCtx) projects.toList.map { config => val remoteDriver = drivers(config) val ctx = remoteDriver.currentCtx diff --git a/language-server/src/dotty/tools/languageserver/worksheet/WorksheetService.scala b/language-server/src/dotty/tools/languageserver/worksheet/WorksheetService.scala index 601bf7e71557..53c7a180c406 100644 --- a/language-server/src/dotty/tools/languageserver/worksheet/WorksheetService.scala +++ b/language-server/src/dotty/tools/languageserver/worksheet/WorksheetService.scala @@ -24,7 +24,7 @@ trait WorksheetService { thisServer: DottyLanguageServer => val sendMessage = (pos: SourcePosition, msg: String) => client.publishOutput(WorksheetRunOutput(params.textDocument, range(pos).get, msg)) - runWorksheet(driver, uri, sendMessage, cancelChecker)(driver.currentCtx) + runWorksheet(driver, uri, sendMessage, cancelChecker)(using driver.currentCtx) cancelChecker.checkCanceled() WorksheetRunResult(success = true) } catch { diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala index 38deb4c40c0f..8f05d6ad11da 100644 --- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala +++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala @@ -1725,8 +1725,7 @@ class CompletionTest { .completion(m6, Set()) @Test def namedTupleCompletion: Unit = - code"""|import scala.language.experimental.namedTuples - | + code"""| |val person: (name: String, city: String) = | (name = "Jamie", city = "Lausanne") | diff --git a/language-server/test/dotty/tools/languageserver/DefinitionTest.scala b/language-server/test/dotty/tools/languageserver/DefinitionTest.scala index ddacd0c868e0..d6de3d971e2b 100644 --- a/language-server/test/dotty/tools/languageserver/DefinitionTest.scala +++ b/language-server/test/dotty/tools/languageserver/DefinitionTest.scala @@ -378,4 +378,38 @@ class DefinitionTest { .definition(m3 to m4, Nil) .definition(m5 to m6, Nil) .definition(m7 to m8, Nil) + + @Test def typeParam: Unit = { + code"""|class Foo[${m1}T${m2}]: + | def test: ${m3}T${m4}""" + .definition(m3 to m4, List(m1 to m2)) + } + + @Test def enumTypeParam: Unit = { + code"""|enum Test[${m1}T${m2}]: + | case EnumCase(value: ${m3}T${m4})""" + .definition(m3 to m4, List(m1 to m2)) + } + + @Test def extMethodTypeParam: Unit = { + code"""extension [${m1}T${m2}](string: String) def xxxx(y: ${m3}T${m4}) = ???""" + .definition(m3 to m4, List(m1 to m2)) + } + + @Test def typeParamCovariant: Unit = { + code"""|class Foo[+${m1}T${m2}]: + | def test: ${m3}T${m4}""" + .definition(m3 to m4, List(m1 to m2)) + } + + @Test def enumTypeParamCovariant: Unit = { + code"""|enum Test[+${m1}T${m2}]: + | case EnumCase(value: ${m3}T${m4})""" + .definition(m3 to m4, List(m1 to m2)) + } + + @Test def extMethodTypeParamCovariant: Unit = { + code"""extension [+${m1}T${m2}](string: String) def xxxx(y: ${m3}T${m4}) = ???""" + .definition(m3 to m4, List(m1 to m2)) + } } diff --git a/library-js/src/scala/scalajs/runtime/AnonFunctionXXL.scala b/library-js/src/scala/scalajs/runtime/AnonFunctionXXL.scala index 87208573eff9..aa08afdce323 100644 --- a/library-js/src/scala/scalajs/runtime/AnonFunctionXXL.scala +++ b/library-js/src/scala/scalajs/runtime/AnonFunctionXXL.scala @@ -1,8 +1,25 @@ package scala.scalajs.runtime -import scala.scalajs.js - -@inline -final class AnonFunctionXXL(f: js.Function1[IArray[Object], Object]) extends scala.runtime.FunctionXXL { - override def apply(xs: IArray[Object]): Object = f(xs) -} +/* Before Scala.js 1.19, this class was concrete. It had a 1-argument + * constructor taking a js.Function[Array[Object], Object], and its `apply()` + * method called that function. This was similar to the `AnonFunctionN` classes + * of the Scala.js library (shared between Scala 2 and 3). + * + * In Scala.js 1.19, we introduced `NewLambda` nodes, which superseded these + * specialized classes with a compilation mode that is more efficient on Wasm. + * However, libraries compiled with earlier versions still contain references + * to `AnonFunctionXXL`. + * + * The IR deserializer patches allocations of the form + * New(AnonFunctionXXL, ctor, closure :: Nil) + * into + * NewLambda(AnonFunctionXXL, ..., (xs: Array[Object]) => closure(xs)) + * + * When the `closure` is directly a JS `Closure` with the right signature + * (which is supposed to be always, as far as our codegens were concerned), + * it rewrites that as + * NewLambda(AnonFunctionXXL, ..., (closureParam: Array[Object]) => closureBody) + * which provides the best performance for old code. + */ +@deprecated("used by the codegen before Scala.js 1.19", since = "3.7.0") +sealed abstract class AnonFunctionXXL extends scala.runtime.FunctionXXL diff --git a/library/src/scala/NamedTuple.scala b/library/src/scala/NamedTuple.scala index 0d1deffce513..1e4d3c084a3c 100644 --- a/library/src/scala/NamedTuple.scala +++ b/library/src/scala/NamedTuple.scala @@ -1,8 +1,6 @@ package scala -import annotation.experimental import compiletime.ops.boolean.* -@experimental object NamedTuple: /** The type to which named tuples get mapped to. For instance, @@ -133,7 +131,6 @@ object NamedTuple: end NamedTuple /** Separate from NamedTuple object so that we can match on the opaque type NamedTuple. */ -@experimental object NamedTupleDecomposition: import NamedTuple.* extension [N <: Tuple, V <: Tuple](x: NamedTuple[N, V]) diff --git a/library/src/scala/annotation/internal/preview.scala b/library/src/scala/annotation/internal/preview.scala new file mode 100644 index 000000000000..a6e797d78e97 --- /dev/null +++ b/library/src/scala/annotation/internal/preview.scala @@ -0,0 +1,11 @@ +package scala.annotation +package internal + + +/** An annotation that can be used to mark a definition as preview. + * + * @see [[https://dotty.epfl.ch/docs/reference/other-new-features/preview-defs]] + * @syntax markdown + */ +private[scala] final class preview(message: String) extends StaticAnnotation: + def this() = this("") diff --git a/library/src/scala/annotation/publicInBinary.scala b/library/src/scala/annotation/publicInBinary.scala index 4990d266f892..a517f085dc7a 100644 --- a/library/src/scala/annotation/publicInBinary.scala +++ b/library/src/scala/annotation/publicInBinary.scala @@ -16,5 +16,4 @@ package scala.annotation * Adding this annotation to a non-public definition can also cause binary incompatibilities * if the definition is accessed in an inline definition (these can be checked using `-WunstableInlineAccessors`). */ -@experimental final class publicInBinary extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/unroll.scala b/library/src/scala/annotation/unroll.scala new file mode 100644 index 000000000000..c37b7903d605 --- /dev/null +++ b/library/src/scala/annotation/unroll.scala @@ -0,0 +1,12 @@ +package scala.annotation + +@experimental("under review as part of SIP-61") +/**The `@unroll` annotation is reserved for parameters of classes and methods. + * + * It enables to add new parameters while preserving backwards binary compatibility, + * through code generation of hidden forwarder methods (but visible in the binary API). + * + * Read more about parameter unrolling, and the usage of `@unroll` in the reference documentation: + * https://dotty.epfl.ch/docs/reference/experimental/unrolled-defs.html + */ +final class unroll extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/caps.scala b/library/src/scala/caps.scala deleted file mode 100644 index c35b3b55e813..000000000000 --- a/library/src/scala/caps.scala +++ /dev/null @@ -1,69 +0,0 @@ -package scala - -import annotation.{experimental, compileTimeOnly, retainsCap} - -@experimental object caps: - - trait Capability extends Any - - /** The universal capture reference */ - val cap: Capability = new Capability() {} - - /** The universal capture reference (deprecated) */ - @deprecated("Use `cap` instead") - val `*`: Capability = cap - - @deprecated("Use `Capability` instead") - type Cap = Capability - - /** Carrier trait for capture set type parameters */ - trait CapSet extends Any - - /** A type constraint expressing that the capture set `C` needs to contain - * the capability `R` - */ - sealed trait Contains[+C >: CapSet <: CapSet @retainsCap, R <: Singleton] - - /** The only implementation of `Contains`. The constraint that `{R} <: C` is - * added separately by the capture checker. - */ - given containsImpl[C >: CapSet <: CapSet @retainsCap, R <: Singleton]: Contains[C, R]() - - /** A wrapper indicating a type variable in a capture argument list of a - * @retains annotation. E.g. `^{x, Y^}` is represented as `@retains(x, capsOf[Y])`. - */ - @compileTimeOnly("Should be be used only internally by the Scala compiler") - def capsOf[CS >: CapSet <: CapSet @retainsCap]: Any = ??? - - /** Reach capabilities x* which appear as terms in @retains annotations are encoded - * as `caps.reachCapability(x)`. When converted to CaptureRef types in capture sets - * they are represented as `x.type @annotation.internal.reachCapability`. - */ - extension (x: Any) def reachCapability: Any = x - - /** A trait to allow expressing existential types such as - * - * (x: Exists) => A ->{x} B - */ - sealed trait Exists extends Capability - - /** This should go into annotations. For now it is here, so that we - * can experiment with it quickly between minor releases - */ - final class untrackedCaptures extends annotation.StaticAnnotation - - /** This should go into annotations. For now it is here, so that we - * can experiment with it quickly between minor releases - */ - final class use extends annotation.StaticAnnotation - - object unsafe: - - extension [T](x: T) - /** A specific cast operation to remove a capture set. - * If argument is of type `T^C`, assume it is of type `T` instead. - * Calls to this method are treated specially by the capture checker. - */ - def unsafeAssumePure: T = x - - end unsafe diff --git a/library/src/scala/caps/package.scala b/library/src/scala/caps/package.scala new file mode 100644 index 000000000000..3705a6137be8 --- /dev/null +++ b/library/src/scala/caps/package.scala @@ -0,0 +1,106 @@ +package scala +package caps + +import annotation.{experimental, compileTimeOnly, retainsCap} + +/** + * Base trait for classes that represent capabilities in the + * [object-capability model](https://en.wikipedia.org/wiki/Object-capability_model). + * + * A capability is a value representing a permission, access right, resource or effect. + * Capabilities are typically passed to code as parameters; they should not be global objects. + * Often, they come with access restrictions such as scoped lifetimes or limited sharing. + * + * An example is the [[scala.util.boundary.Label Label]] class in [[scala.util.boundary]]. + * It represents a capability in the sense that it gives permission to [[scala.util.boundary.break break]] + * to the enclosing boundary represented by the `Label`. It has a scoped lifetime, since breaking to + * a `Label` after the associated `boundary` was exited gives a runtime exception. + * + * [[Capability]] has a formal meaning when + * [[scala.language.experimental.captureChecking Capture Checking]] + * is turned on. + * But even without capture checking, extending this trait can be useful for documenting the intended purpose + * of a class. + */ +@experimental +trait Capability extends Any + +/** The universal capture reference. */ +@experimental +object cap extends Capability + +/** Carrier trait for capture set type parameters */ +@experimental +trait CapSet extends Any + +/** A type constraint expressing that the capture set `C` needs to contain + * the capability `R` + */ +@experimental +sealed trait Contains[+C >: CapSet <: CapSet @retainsCap, R <: Singleton] + +@experimental +object Contains: + /** The only implementation of `Contains`. The constraint that `{R} <: C` is + * added separately by the capture checker. + */ + @experimental + given containsImpl[C >: CapSet <: CapSet @retainsCap, R <: Singleton]: Contains[C, R]() + +/** An annotation on parameters `x` stating that the method's body makes + * use of the reach capability `x*`. Consequently, when calling the method + * we need to charge the deep capture set of the actual argiment to the + * environment. + * + * Note: This should go into annotations. For now it is here, so that we + * can experiment with it quickly between minor releases + */ +@experimental +final class use extends annotation.StaticAnnotation + +/** A trait to allow expressing existential types such as + * + * (x: Exists) => A ->{x} B + */ +@experimental +sealed trait Exists extends Capability + +@experimental +object internal: + + /** A wrapper indicating a type variable in a capture argument list of a + * @retains annotation. E.g. `^{x, Y^}` is represented as `@retains(x, capsOf[Y])`. + */ + @compileTimeOnly("Should be be used only internally by the Scala compiler") + def capsOf[CS >: CapSet <: CapSet @retainsCap]: Any = ??? + + /** Reach capabilities x* which appear as terms in @retains annotations are encoded + * as `caps.reachCapability(x)`. When converted to CaptureRef types in capture sets + * they are represented as `x.type @annotation.internal.reachCapability`. + */ + extension (x: Any) def reachCapability: Any = x + +@experimental +object unsafe: + /** + * Marks the constructor parameter as untracked. + * The capture set of this parameter will not be included in + * the capture set of the constructed object. + * + * @note This should go into annotations. For now it is here, so that we + * can experiment with it quickly between minor releases + */ + final class untrackedCaptures extends annotation.StaticAnnotation + + extension [T](x: T) + /** A specific cast operation to remove a capture set. + * If argument is of type `T^C`, assume it is of type `T` instead. + * Calls to this method are treated specially by the capture checker. + */ + def unsafeAssumePure: T = x + + /** A wrapper around code for which separation checks are suppressed. + */ + def unsafeAssumeSeparate(op: Any): op.type = op + +end unsafe diff --git a/library/src/scala/deriving/Mirror.scala b/library/src/scala/deriving/Mirror.scala index 57453a516567..a7477cf0fb2d 100644 --- a/library/src/scala/deriving/Mirror.scala +++ b/library/src/scala/deriving/Mirror.scala @@ -52,7 +52,7 @@ object Mirror { extension [T](p: ProductOf[T]) /** Create a new instance of type `T` with elements taken from product `a`. */ - def fromProductTyped[A <: scala.Product, Elems <: p.MirroredElemTypes](a: A)(using m: ProductOf[A] { type MirroredElemTypes = Elems }): T = + def fromProductTyped[A <: scala.Product, Elems <: p.MirroredElemTypes](a: A)(using ProductOf[A] { type MirroredElemTypes = Elems }): T = p.fromProduct(a) /** Create a new instance of type `T` with elements taken from tuple `t`. */ diff --git a/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index d1385a0193d6..bfb26023e2c8 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -280,4 +280,23 @@ object Expr { } } + /** Find a given instance of type `T` in the current scope, + * while excluding certain symbols from the initial implicit search. + * Return `Some` containing the expression of the implicit or + * `None` if implicit resolution failed. + * + * @tparam T type of the implicit parameter + * @param ignored Symbols ignored during the initial implicit search + * + * @note if the found given requires additional search for other given instances, + * this additional search will NOT exclude the symbols from the `ignored` list. + */ + def summonIgnoring[T](using Type[T])(using quotes: Quotes)(ignored: quotes.reflect.Symbol*): Option[Expr[T]] = { + import quotes.reflect._ + Implicits.searchIgnoring(TypeRepr.of[T])(ignored*) match { + case iss: ImplicitSearchSuccess => Some(iss.tree.asExpr.asInstanceOf[Expr[T]]) + case isf: ImplicitSearchFailure => None + } + } + } diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 7a98d6f6f761..a96785ce2741 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -1,7 +1,6 @@ package scala.quoted -import scala.annotation.experimental -import scala.annotation.implicitNotFound +import scala.annotation.{experimental, implicitNotFound, unused} import scala.reflect.TypeTest /** Current Quotes in scope @@ -907,10 +906,14 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * If `sym` refers to a class member `foo` in class `C`, * returns a tree representing `C.this.foo`. * + * If `sym` refers to an object member `foo` in object C, itself in prefix + * `pre` (which might include `.this`, if it contains a class), + * returns `pre.C.foo`. + * * If `sym` refers to a local definition `foo`, returns * a tree representing `foo`. * - * @note In both cases, the constructed tree should only + * @note In all cases, the constructed tree should only * be spliced into the places where such accesses make sense. * For example, it is incorrect to have `C.this.foo` outside * the class body of `C`, or have `foo` outside the lexical @@ -2545,6 +2548,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val SimpleSelector` */ trait SimpleSelectorModule { this: SimpleSelector.type => + @experimental def apply(name: String): SimpleSelector def unapply(x: SimpleSelector): Some[String] } @@ -2570,6 +2574,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val RenameSelector` */ trait RenameSelectorModule { this: RenameSelector.type => + @experimental def apply(fromName: String, toName: String): RenameSelector def unapply(x: RenameSelector): (String, String) } @@ -2597,6 +2602,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val OmitSelector` */ trait OmitSelectorModule { this: OmitSelector.type => + @experimental def apply(name: String): OmitSelector def unapply(x: OmitSelector): Some[String] } @@ -2621,6 +2627,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val GivenSelector` */ trait GivenSelectorModule { this: GivenSelector.type => + @experimental def apply(bound: Option[TypeTree]): GivenSelector def unapply(x: GivenSelector): Some[Option[TypeTree]] } @@ -3705,6 +3712,18 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * @param tpe type of the implicit parameter */ def search(tpe: TypeRepr): ImplicitSearchResult + + /** Find a given instance of type `T` in the current scope provided by the current enclosing splice, + * while excluding certain symbols from the initial implicit search. + * Return an `ImplicitSearchResult`. + * + * @param tpe type of the implicit parameter + * @param ignored Symbols ignored during the initial implicit search + * + * @note if an found given requires additional search for other given instances, + * this additional search will NOT exclude the symbols from the `ignored` list. + */ + def searchIgnoring(tpe: TypeRepr)(ignored: Symbol*): ImplicitSearchResult } /** Result of a given instance search */ @@ -3796,7 +3815,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** The class Symbol of a global class definition */ def classSymbol(fullName: String): Symbol - /** Generates a new class symbol for a class with a parameterless constructor. + /** Generates a new class symbol for a class with a public parameterless constructor. + * For more settings, look to the other newClass methods. * * Example usage: * ``` @@ -3824,7 +3844,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * } * ``` * - * @param parent The owner of the class + * @param owner The owner of the class * @param name The name of the class * @param parents The parent classes of the class. The first parent must not be a trait. * @param decls The member declarations of the class provided the symbol of this class @@ -3837,8 +3857,181 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be * direct or indirect children of the reflection context's owner. */ - // TODO: add flags and privateWithin - @experimental def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol + @experimental def newClass(owner: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol + + /** Generates a new class symbol for a class with a public single term clause constructor. + * + * Example usage: + * ``` + * val name = "myClass" + * def decls(cls: Symbol): List[Symbol] = + * List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]))) + * val parents = List(TypeTree.of[Object]) + * val cls = Symbol.newClass( + * Symbol.spliceOwner, + * name, + * parents = _ => parents.map(_.tpe), + * decls, + * selfType = None, + * clsFlags = Flags.EmptyFlags, + * Symbol.noSymbol, + * List(("idx", TypeRepr.of[Int]), ("str", TypeRepr.of[String])) + * ) + * + * val fooSym = cls.declaredMethod("foo").head + * val idxSym = cls.fieldMember("idx") + * val strSym = cls.fieldMember("str") + * val fooDef = DefDef(fooSym, argss => + * Some('{println(s"Foo method call with (${${Ref(idxSym).asExpr}}, ${${Ref(strSym).asExpr}})")}.asTerm) + * ) + * val clsDef = ClassDef(cls, parents, body = List(fooDef)) + * val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List('{0}.asTerm, '{string}.asTerm)) + * + * Block(List(clsDef), Apply(Select(newCls, cls.methodMember("foo")(0)), Nil)).asExprOf[Unit] + * ``` + * construct the equivalent to + * ``` + * '{ + * class myClass(idx: Int, str: String) extends Object { + * def foo() = + * println(s"Foo method call with $idx, $str") + * } + * new myClass(0, "string").foo() + * } + * ``` + * @param owner The owner of the class + * @param name The name of the class + * @param parents Function returning the parent classes of the class. The first parent must not be a trait. + * Takes the constructed class symbol as an argument. Calling `cls.typeRef.asType` as part of this function will lead to cyclic reference errors. + * @param clsFlags extra flags with which the class symbol should be constructed. + * @param clsPrivateWithin the symbol within which this new class symbol should be private. May be noSymbol. + * @param conParams constructor parameter pairs of names and types. + * + * Parameters assigned by the constructor can be obtained via `classSymbol.memberField`. + * This symbol starts without an accompanying definition. + * It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing + * this symbol to the ClassDef constructor. + * + * @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be + * direct or indirect children of the reflection context's owner. + */ + @experimental def newClass( + owner: Symbol, + name: String, + parents: Symbol => List[TypeRepr], + decls: Symbol => List[Symbol], + selfType: Option[TypeRepr], + clsFlags: Flags, + clsPrivateWithin: Symbol, + conParams: List[(String, TypeRepr)] + ): Symbol + + /** Generates a new class symbol with a constructor of the shape signified by a passed PolyOrMethod parameter. + * + * Example usage: + * ``` + * val name = "myClass" + * def decls(cls: Symbol): List[Symbol] = + * List(Symbol.newMethod(cls, "getParam", MethodType(Nil)(_ => Nil, _ => cls.typeMember("T").typeRef))) + * val conMethodType = + * (classType: TypeRepr) => PolyType(List("T"))(_ => List(TypeBounds.empty), polyType => + * MethodType(List("param"))((_: MethodType) => List(polyType.param(0)), (_: MethodType) => + * AppliedType(classType, List(polyType.param(0))) + * ) + * ) + * val cls = Symbol.newClass( + * Symbol.spliceOwner, + * name, + * parents = _ => List(TypeRepr.of[Object]), + * decls, + * selfType = None, + * clsFlags = Flags.EmptyFlags, + * clsPrivateWithin = Symbol.noSymbol, + * clsAnnotations = Nil, + * conMethodType, + * conFlags = Flags.EmptyFlags, + * conPrivateWithin = Symbol.noSymbol, + * conParamFlags = List(List(Flags.EmptyFlags), List(Flags.EmptyFlags)), + * conParamPrivateWithins = List(List(Symbol.noSymbol), List(Symbol.noSymbol)) + * ) + * + * val getParamSym = cls.declaredMethod("getParam").head + * def getParamRhs(): Option[Term] = + * val paramValue = This(cls).select(cls.fieldMember("param")).asExpr + * Some('{ println("Calling getParam"); $paramValue }.asTerm) + * val getParamDef = DefDef(getParamSym, _ => getParamRhs()) + * + * val clsDef = ClassDef(cls, List(TypeTree.of[Object]), body = List(getParamDef)) + * val newCls = + * Apply( + * Select( + * Apply( + * TypeApply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(TypeTree.of[String])), + * List(Expr("test").asTerm) + * ), + * cls.methodMember("getParam").head + * ), + * Nil + * ) + * + * Block(List(clsDef), newCls).asExpr + * ``` + * constructs the equivalent to + * ``` + * '{ + * class myClass[T](val param: T) extends Object { + * def getParam: T = + * println("Calling getParam") + * param + * } + * new myClass[String]("test").getParam() + * } + * ``` + * + * @param owner The owner of the class + * @param name The name of the class + * @param parents Function returning the parent classes of the class. The first parent must not be a trait + * Takes the constructed class symbol as an argument. Calling `cls.typeRef.asType` as part of this function will lead to cyclic reference errors. + * @param decls The member declarations of the class provided the symbol of this class + * @param selfType The self type of the class if it has one + * @param clsFlags extra flags with which the class symbol should be constructed. Can be `Private` | `Protected` | `PrivateLocal` | `Local` | `Final` | `Trait` | `Abstract` | `Open` + * @param clsPrivateWithin the symbol within which this new class symbol should be private. May be noSymbol + * @param clsAnnotations annotations of the class + * @param conMethodType Function returning MethodOrPoly type representing the type of the constructor. + * Takes the result type as parameter which must be returned from the innermost MethodOrPoly and have type parameters applied if those are used. + * PolyType may only represent the first clause of the constructor. + * @param conFlags extra flags with which the constructor symbol should be constructed. Can be `Synthetic` | `Method` | `Private` | `Protected` | `PrivateLocal` | `Local` + * @param conPrivateWithin the symbol within which the constructor for this new class symbol should be private. May be noSymbol. + * @param conParamFlags extra flags with which the constructor parameter symbols should be constructed. Must match the shape of `conMethodType`. + * For type parameters those can be `Param` | `Deferred` | `Private` | `PrivateLocal` | `Local`. + * For term parameters those can be `ParamAccessor` | `Private` | `Protected` | `PrivateLocal` | `Local` + * @param conParamPrivateWithins the symbols within which the constructor parameters should be private. Must match the shape of `conMethodType`. Can consist of noSymbol. + * + * Term and type parameters assigned by the constructor can be obtained via `classSymbol.memberField`/`classSymbol.memberType`. + * This symbol starts without an accompanying definition. + * It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing + * this symbol to the ClassDef constructor. + * + * @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be + * direct or indirect children of the reflection context's owner. + */ + // Keep doc aligned with QuotesImpl's validFlags: `clsFlags` with `validClassFlags`, `conFlags` with `validClassConstructorFlags`, + // conParamFlags with `validClassTypeParamFlags` and `validClassTermParamFlags` + @experimental def newClass( + owner: Symbol, + name: String, + parents: Symbol => List[TypeRepr], + decls: Symbol => List[Symbol], + selfType: Option[TypeRepr], + clsFlags: Flags, + clsPrivateWithin: Symbol, + clsAnnotations: List[Term], + conMethodType: TypeRepr => MethodOrPoly, + conFlags: Flags, + conPrivateWithin: Symbol, + conParamFlags: List[List[Flags]], + conParamPrivateWithins: List[List[Symbol]] + ): Symbol /** Generates a new module symbol with an associated module class symbol, * this is equivalent to an `object` declaration in source code. @@ -3855,7 +4048,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * def decls(cls: Symbol): List[Symbol] = * List(Symbol.newMethod(cls, "run", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]), Flags.EmptyFlags, Symbol.noSymbol)) * - * val mod = Symbol.newModule(Symbol.spliceOwner, moduleName, Flags.EmptyFlags, Flags.EmptyFlags, parents.map(_.tpe), decls, Symbol.noSymbol) + * val mod = Symbol.newModule(Symbol.spliceOwner, moduleName, Flags.EmptyFlags, Flags.EmptyFlags, _ => parents.map(_.tpe), decls, Symbol.noSymbol) * val cls = mod.moduleClass * val runSym = cls.declaredMethod("run").head * @@ -3883,7 +4076,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * @param name The name of the class * @param modFlags extra flags with which the module symbol should be constructed * @param clsFlags extra flags with which the module class symbol should be constructed - * @param parents The parent classes of the class. The first parent must not be a trait. + * @param parents A function that takes the symbol of the module class as input and returns the parent classes of the class. The first parent must not be a trait. * @param decls A function that takes the symbol of the module class as input and return the symbols of its declared members * @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. * @@ -3896,7 +4089,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * * @syntax markdown */ - @experimental def newModule(owner: Symbol, name: String, modFlags: Flags, clsFlags: Flags, parents: List[TypeRepr], decls: Symbol => List[Symbol], privateWithin: Symbol): Symbol + @experimental def newModule(owner: Symbol, name: String, modFlags: Flags, clsFlags: Flags, parents: Symbol => List[TypeRepr], decls: Symbol => List[Symbol], privateWithin: Symbol): Symbol /** Generates a new method symbol with the given parent, name and type. * @@ -4941,7 +5134,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => foldTree(foldTree(foldTree(x, cond)(owner), thenp)(owner), elsep)(owner) case While(cond, body) => foldTree(foldTree(x, cond)(owner), body)(owner) - case Closure(meth, tpt) => + case Closure(meth, _) => foldTree(x, meth)(owner) case Match(selector, cases) => foldTrees(foldTree(x, selector)(owner), cases)(owner) @@ -5019,7 +5212,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => def traverseTree(tree: Tree)(owner: Symbol): Unit = traverseTreeChildren(tree)(owner) - def foldTree(x: Unit, tree: Tree)(owner: Symbol): Unit = traverseTree(tree)(owner) + def foldTree(@unused x: Unit, tree: Tree)(owner: Symbol): Unit = traverseTree(tree)(owner) protected def traverseTreeChildren(tree: Tree)(owner: Symbol): Unit = foldOverTree((), tree)(owner) diff --git a/library/src/scala/runtime/Arrays.scala b/library/src/scala/runtime/Arrays.scala index 9f5bdd99a5f4..085b36c08a1f 100644 --- a/library/src/scala/runtime/Arrays.scala +++ b/library/src/scala/runtime/Arrays.scala @@ -1,5 +1,6 @@ package scala.runtime +import scala.annotation.unused import scala.reflect.ClassTag import java.lang.{reflect => jlr} @@ -26,6 +27,6 @@ object Arrays { /** Create an array of a reference type T. */ - def newArray[Arr](componentType: Class[?], returnType: Class[Arr], dimensions: Array[Int]): Arr = + def newArray[Arr](componentType: Class[?], @unused returnType: Class[Arr], dimensions: Array[Int]): Arr = jlr.Array.newInstance(componentType, dimensions*).asInstanceOf[Arr] } diff --git a/library/src/scala/runtime/LazyVals.scala b/library/src/scala/runtime/LazyVals.scala index 15220ea2410a..9959f99f6e17 100644 --- a/library/src/scala/runtime/LazyVals.scala +++ b/library/src/scala/runtime/LazyVals.scala @@ -96,13 +96,13 @@ object LazyVals { println(s"CAS($t, $offset, $e, $v, $ord)") val mask = ~(LAZY_VAL_MASK << ord * BITS_PER_LAZY_VAL) val n = (e & mask) | (v.toLong << (ord * BITS_PER_LAZY_VAL)) - unsafe.compareAndSwapLong(t, offset, e, n) + unsafe.compareAndSwapLong(t, offset, e, n): @nowarn("cat=deprecation") } def objCAS(t: Object, offset: Long, exp: Object, n: Object): Boolean = { if (debug) println(s"objCAS($t, $exp, $n)") - unsafe.compareAndSwapObject(t, offset, exp, n) + unsafe.compareAndSwapObject(t, offset, exp, n): @nowarn("cat=deprecation") } def setFlag(t: Object, offset: Long, v: Int, ord: Int): Unit = { @@ -147,7 +147,7 @@ object LazyVals { def get(t: Object, off: Long): Long = { if (debug) println(s"get($t, $off)") - unsafe.getLongVolatile(t, off) + unsafe.getLongVolatile(t, off): @nowarn("cat=deprecation") } // kept for backward compatibility diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 547710d55293..8899f734aece 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -97,6 +97,7 @@ object language: * @see [[https://dotty.epfl.ch/docs/reference/experimental/named-tuples]] */ @compileTimeOnly("`namedTuples` can only be used at compile time in import statements") + @deprecated("The experimental.namedTuples language import is no longer needed since the feature is now standard", since = "3.7") object namedTuples /** Experimental support for new features for better modularity, including @@ -139,7 +140,13 @@ object language: * @see [[https://github.com/scala/improvement-proposals/pull/79]] */ @compileTimeOnly("`betterFors` can only be used at compile time in import statements") + @deprecated("The `experimental.betterFors` language import no longer has any effect, the feature is being stablised and can be enabled using `-preview` flag", since = "3.7") object betterFors + + /** Experimental support for package object values + */ + @compileTimeOnly("`packageObjectValues` can only be used at compile time in import statements") + object packageObjectValues end experimental /** The deprecated object contains features that are no longer officially suypported in Scala. @@ -330,6 +337,20 @@ object language: @compileTimeOnly("`3.7` can only be used at compile time in import statements") object `3.7` + /** Set source version to 3.8-migration. + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.8-migration` can only be used at compile time in import statements") + object `3.8-migration` + + /** Set source version to 3.8 + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.8` can only be used at compile time in import statements") + object `3.8` + // !!! Keep in sync with dotty.tools.dotc.config.SourceVersion !!! // Also add tests in `tests/pos/source-import-3-x.scala` and `tests/pos/source-import-3-x-migration.scala` diff --git a/presentation-compiler/src/main/dotty/tools/pc/CompletionItemResolver.scala b/presentation-compiler/src/main/dotty/tools/pc/CompletionItemResolver.scala index 291ffe1fec30..ffd7377c8181 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/CompletionItemResolver.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/CompletionItemResolver.scala @@ -75,7 +75,7 @@ object CompletionItemResolver extends ItemResolver: else gsymDoc else val companionDoc = docs(companion) - if companionDoc.isEmpty() then gsymDoc + if companionDoc.isEmpty() || companionDoc == gsymDoc then gsymDoc else if gsymDoc.isEmpty() then companionDoc else List( diff --git a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala index fd363dbd37a2..3b2f4d2aa9b0 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala @@ -25,6 +25,8 @@ import dotty.tools.dotc.util.SourcePosition import dotty.tools.pc.printer.ShortenedTypePrinter import dotty.tools.pc.printer.ShortenedTypePrinter.IncludeDefaultParam import dotty.tools.pc.utils.InteractiveEnrichments.* +import dotty.tools.dotc.ast.untpd.InferredTypeTree +import dotty.tools.dotc.core.StdNames object HoverProvider: @@ -104,10 +106,10 @@ object HoverProvider: ) match case Nil => fallbackToDynamics(path, printer, contentType) - case (symbol, tpe) :: _ + case (symbol, tpe, _) :: _ if symbol.name == nme.selectDynamic || symbol.name == nme.applyDynamic => fallbackToDynamics(path, printer, contentType) - case symbolTpes @ ((symbol, tpe) :: _) => + case symbolTpes @ ((symbol, tpe, None) :: _) => val exprTpw = tpe.widenTermRefExpr.deepDealias val hoverString = tpw match @@ -131,7 +133,12 @@ object HoverProvider: .flatMap(symTpe => search.symbolDocumentation(symTpe._1, contentType)) .map(_.docstring()) .mkString("\n") - printer.expressionType(exprTpw) match + + val expresionTypeOpt = + if symbol.name == StdNames.nme.??? then + InferExpectedType(search, driver, params).infer() + else printer.expressionType(exprTpw) + expresionTypeOpt match case Some(expressionType) => val forceExpressionType = !pos.span.isZeroExtent || ( @@ -153,6 +160,21 @@ object HoverProvider: case _ => ju.Optional.empty().nn end match + case (_, tpe, Some(namedTupleArg)) :: _ => + val exprTpw = tpe.widenTermRefExpr.deepDealias + printer.expressionType(exprTpw) match + case Some(tpe) => + ju.Optional.of( + new ScalaHover( + expressionType = Some(tpe), + symbolSignature = Some(s"$namedTupleArg: $tpe"), + docstring = None, + forceExpressionType = false, + contextInfo = printer.getUsedRenamesInfo, + contentType = contentType + ) + ).nn + case _ => ju.Optional.empty().nn end match end if end hover @@ -165,23 +187,31 @@ object HoverProvider: printer: ShortenedTypePrinter, contentType: ContentType )(using Context): ju.Optional[HoverSignature] = path match - case SelectDynamicExtractor(sel, n, name) => + case SelectDynamicExtractor(sel, n, name, rest) => def findRefinement(tp: Type): Option[HoverSignature] = tp match - case RefinedType(_, refName, tpe) if name == refName.toString() => + case RefinedType(_, refName, tpe) if (name == refName.toString() || refName.toString() == nme.Fields.toString()) => + val resultType = + rest match + case Select(_, asInstanceOf) :: TypeApply(_, List(tpe)) :: _ if asInstanceOf == nme.asInstanceOfPM => + tpe.tpe.widenTermRefExpr.deepDealias + case _ if n == nme.selectDynamic => tpe.resultType + case _ => tpe + val tpeString = - if n == nme.selectDynamic then s": ${printer.tpe(tpe.resultType)}" - else printer.tpe(tpe) + if n == nme.selectDynamic then s": ${printer.tpe(resultType)}" + else printer.tpe(resultType) val valOrDef = - if n == nme.selectDynamic && !tpe.isInstanceOf[ExprType] - then "val" - else "def" + if refName.toString() == nme.Fields.toString() then "" + else if n == nme.selectDynamic && !tpe.isInstanceOf[ExprType] + then "val " + else "def " Some( new ScalaHover( expressionType = Some(tpeString), - symbolSignature = Some(s"$valOrDef $name$tpeString"), + symbolSignature = Some(s"$valOrDef$name$tpeString"), contextInfo = printer.getUsedRenamesInfo, contentType = contentType ) @@ -208,16 +238,16 @@ object SelectDynamicExtractor: case Select(_, _) :: Apply( Select(Apply(reflSel, List(sel)), n), List(Literal(Constant(name: String))) - ) :: _ + ) :: rest if (n == nme.selectDynamic || n == nme.applyDynamic) && nme.reflectiveSelectable == reflSel.symbol.name => - Some(sel, n, name) + Some(sel, n, name, rest) // tests `selectable`, `selectable2` and `selectable-full` in HoverScala3TypeSuite case Select(_, _) :: Apply( Select(sel, n), List(Literal(Constant(name: String))) - ) :: _ if n == nme.selectDynamic || n == nme.applyDynamic => - Some(sel, n, name) + ) :: rest if n == nme.selectDynamic || n == nme.applyDynamic => + Some(sel, n, name, rest) case _ => None end match end unapply diff --git a/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala index d8cdbcd8fe69..a0d726d5f382 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala @@ -112,8 +112,8 @@ final class InferredTypeProvider( def imports: List[TextEdit] = printer.imports(autoImportsGen) - def printType(tpe: Type): String = - printer.tpe(tpe) + def printTypeAscription(tpe: Type, spaceBefore: Boolean = false): String = + (if spaceBefore then " : " else ": ") + printer.tpe(tpe) path.headOption match /* `val a = 1` or `var b = 2` @@ -124,7 +124,7 @@ final class InferredTypeProvider( * turns into * `.map((a: Int) => a + a)` */ - case Some(vl @ ValDef(sym, tpt, rhs)) => + case Some(vl @ ValDef(name, tpt, rhs)) => val isParam = path match case head :: next :: _ if next.symbol.isAnonymousFunction => true case head :: (b @ Block(stats, expr)) :: next :: _ @@ -136,9 +136,11 @@ final class InferredTypeProvider( val endPos = findNamePos(sourceText, vl, keywordOffset).endPos.toLsp adjustOpt.foreach(adjust => endPos.setEnd(adjust.adjustedEndPos)) + val spaceBefore = name.isOperatorName + new TextEdit( endPos, - ": " + printType(optDealias(tpt.typeOpt)) + { + printTypeAscription(optDealias(tpt.typeOpt), spaceBefore) + { if withParens then ")" else "" } ) @@ -197,7 +199,7 @@ final class InferredTypeProvider( * turns into * `def a[T](param : Int): Int = param` */ - case Some(df @ DefDef(name, _, tpt, rhs)) => + case Some(df @ DefDef(name, paramss, tpt, rhs)) => def typeNameEdit = /* NOTE: In Scala 3.1.3, `List((1,2)).map((<>,b) => ...)` * turns into `List((1,2)).map((:Inta,b) => ...)`, @@ -208,10 +210,12 @@ final class InferredTypeProvider( if tpt.endPos.end > df.namePos.end then tpt.endPos.toLsp else df.namePos.endPos.toLsp + val spaceBefore = name.isOperatorName && paramss.isEmpty + adjustOpt.foreach(adjust => end.setEnd(adjust.adjustedEndPos)) new TextEdit( end, - ": " + printType(optDealias(tpt.typeOpt)) + printTypeAscription(optDealias(tpt.typeOpt), spaceBefore) ) end typeNameEdit @@ -239,9 +243,10 @@ final class InferredTypeProvider( */ case Some(bind @ Bind(name, body)) => def baseEdit(withParens: Boolean) = + val spaceBefore = name.isOperatorName new TextEdit( bind.endPos.toLsp, - ": " + printType(optDealias(body.typeOpt)) + { + printTypeAscription(optDealias(body.typeOpt), spaceBefore) + { if withParens then ")" else "" } ) @@ -272,9 +277,10 @@ final class InferredTypeProvider( * `for(t: Int <- 0 to 10)` */ case Some(i @ Ident(name)) => + val spaceBefore = name.isOperatorName val typeNameEdit = new TextEdit( i.endPos.toLsp, - ": " + printType(optDealias(i.typeOpt.widen)) + printTypeAscription(optDealias(i.typeOpt.widen), spaceBefore) ) typeNameEdit :: imports diff --git a/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala b/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala index 9a541ef69942..ef583ea2a225 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala @@ -5,12 +5,16 @@ import scala.annotation.tailrec import dotc.* import ast.*, tpd.* +import dotty.tools.dotc.core.Constants.* import core.*, Contexts.*, Flags.*, Names.*, Symbols.*, Types.* +import dotty.tools.dotc.core.StdNames.* import interactive.* import util.* import util.SourcePosition +import dotty.tools.pc.utils.InteractiveEnrichments.* object MetalsInteractive: + type NamedTupleArg = String def contextOfStat( stats: List[Tree], @@ -110,7 +114,7 @@ object MetalsInteractive: pos: SourcePosition, indexed: IndexedContext, skipCheckOnName: Boolean = false - ): List[(Symbol, Type)] = + ): List[(Symbol, Type, Option[String])] = import indexed.ctx path match // For a named arg, find the target `DefDef` and jump to the param @@ -118,59 +122,59 @@ object MetalsInteractive: val funSym = fn.symbol if funSym.is(Synthetic) && funSym.owner.is(CaseClass) then val sym = funSym.owner.info.member(name).symbol - List((sym, sym.info)) + List((sym, sym.info, None)) else val paramSymbol = for param <- funSym.paramSymss.flatten.find(_.name == name) yield param val sym = paramSymbol.getOrElse(fn.symbol) - List((sym, sym.info)) + List((sym, sym.info, None)) case (_: untpd.ImportSelector) :: (imp: Import) :: _ => importedSymbols(imp, _.span.contains(pos.span)).map(sym => - (sym, sym.info) + (sym, sym.info, None) ) case (imp: Import) :: _ => importedSymbols(imp, _.span.contains(pos.span)).map(sym => - (sym, sym.info) + (sym, sym.info, None) ) // wildcard param case head :: _ if (head.symbol.is(Param) && head.symbol.is(Synthetic)) => - List((head.symbol, head.typeOpt)) + List((head.symbol, head.typeOpt, None)) case (head @ Select(target, name)) :: _ if head.symbol.is(Synthetic) && name == StdNames.nme.apply => val sym = target.symbol if sym.is(Synthetic) && sym.is(Module) then - List((sym.companionClass, sym.companionClass.info)) - else List((target.symbol, target.typeOpt)) + List((sym.companionClass, sym.companionClass.info, None)) + else List((target.symbol, target.typeOpt, None)) // L@@ft(...) case (head @ ApplySelect(select)) :: _ if select.qualifier.sourcePos.contains(pos) && select.name == StdNames.nme.apply => - List((head.symbol, head.typeOpt)) + List((head.symbol, head.typeOpt, None)) // for Inlined we don't have a symbol, but it's needed to show proper type case (head @ Inlined(call, bindings, expansion)) :: _ => - List((call.symbol, head.typeOpt)) + List((call.symbol, head.typeOpt, None)) // for comprehension case (head @ ApplySelect(select)) :: _ if isForSynthetic(head) => // If the cursor is on the qualifier, return the symbol for it // `for { x <- List(1).head@@Option }` returns the symbol of `headOption` if select.qualifier.sourcePos.contains(pos) then - List((select.qualifier.symbol, select.qualifier.typeOpt)) + List((select.qualifier.symbol, select.qualifier.typeOpt, None)) // Otherwise, returns the symbol of for synthetics such as "withFilter" - else List((head.symbol, head.typeOpt)) + else List((head.symbol, head.typeOpt, None)) // f@@oo.bar case Select(target, _) :: _ if target.span.isSourceDerived && target.sourcePos.contains(pos) => - List((target.symbol, target.typeOpt)) + List((target.symbol, target.typeOpt, None)) /* In some cases type might be represented by TypeTree, however it's possible * that the type tree will not be marked properly as synthetic even if it doesn't @@ -185,7 +189,7 @@ object MetalsInteractive: */ case (tpt: TypeTree) :: parent :: _ if tpt.span != parent.span && !tpt.symbol.is(Synthetic) => - List((tpt.symbol, tpt.typeOpt)) + List((tpt.symbol, tpt.typeOpt, None)) /* TypeTest class https://dotty.epfl.ch/docs/reference/other-new-features/type-test.html * compiler automatically adds unapply if possible, we need to find the type symbol @@ -195,14 +199,28 @@ object MetalsInteractive: pat match case UnApply(fun, _, pats) => val tpeSym = pats.head.typeOpt.typeSymbol - List((tpeSym, tpeSym.info)) + List((tpeSym, tpeSym.info, None)) case _ => Nil + // Handle select on named tuples + case (Apply(Apply(TypeApply(fun, List(t1, t2)), List(ddef)), List(Literal(Constant(i: Int))))) :: _ + if fun.symbol.exists && fun.symbol.name == nme.apply && + fun.symbol.owner.exists && fun.symbol.owner == getModuleIfDefined("scala.NamedTuple").moduleClass => + def getIndex(t: Tree): Option[Type] = + t.tpe.dealias match + case AppliedType(_, args) => args.get(i) + case _ => None + val name = getIndex(t1) match + case Some(c: ConstantType) => c.value.stringValue + case _ => "" + val tpe = getIndex(t2).getOrElse(NoType) + List((ddef.symbol, tpe, Some(name))) + case path @ head :: tail => if head.symbol.is(Exported) then val sym = head.symbol.sourceSymbol - List((sym, sym.info)) + List((sym, sym.info, None)) else if head.symbol.is(Synthetic) then enclosingSymbolsWithExpressionType( tail, @@ -217,7 +235,7 @@ object MetalsInteractive: pos, indexed.ctx.source ) - then List((head.symbol, head.typeOpt)) + then List((head.symbol, head.typeOpt, None)) /* Type tree for List(1) has an Int type variable, which has span * but doesn't exist in code. * https://github.com/scala/scala3/issues/15937 @@ -234,7 +252,7 @@ object MetalsInteractive: indexed, skipCheckOnName ) - else recovered.map(sym => (sym, sym.info)) + else recovered.map(sym => (sym, sym.info, None)) end if case Nil => Nil end match diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala index 3b2284bef1d0..8ff43ba07358 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala @@ -1,5 +1,6 @@ package dotty.tools.pc +import java.net.URI import java.nio.file.Paths import java.util.ArrayList @@ -16,6 +17,7 @@ import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Flags.{Exported, ModuleClass} import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.interactive.Interactive +import dotty.tools.dotc.interactive.Interactive.Include import dotty.tools.dotc.interactive.InteractiveDriver import dotty.tools.dotc.util.SourceFile import dotty.tools.dotc.util.SourcePosition @@ -51,10 +53,10 @@ class PcDefinitionProvider( given ctx: Context = driver.localContext(params) val indexedContext = IndexedContext(ctx) val result = - if findTypeDef then findTypeDefinitions(path, pos, indexedContext) - else findDefinitions(path, pos, indexedContext) + if findTypeDef then findTypeDefinitions(path, pos, indexedContext, uri) + else findDefinitions(path, pos, indexedContext, uri) - if result.locations().nn.isEmpty() then fallbackToUntyped(pos)(using ctx) + if result.locations().nn.isEmpty() then fallbackToUntyped(pos, uri)(using ctx) else result end definitions @@ -70,24 +72,26 @@ class PcDefinitionProvider( * @param pos cursor position * @return definition result */ - private def fallbackToUntyped(pos: SourcePosition)( + private def fallbackToUntyped(pos: SourcePosition, uri: URI)( using ctx: Context ) = lazy val untpdPath = NavigateAST .untypedPath(pos.span) .collect { case t: untpd.Tree => t } - definitionsForSymbol(untpdPath.headOption.map(_.symbol).toList, pos) + definitionsForSymbol(untpdPath.headOption.map(_.symbol).toList, uri, pos) end fallbackToUntyped private def findDefinitions( path: List[Tree], pos: SourcePosition, - indexed: IndexedContext + indexed: IndexedContext, + uri: URI, ): DefinitionResult = import indexed.ctx definitionsForSymbol( MetalsInteractive.enclosingSymbols(path, pos, indexed), + uri, pos ) end findDefinitions @@ -95,37 +99,39 @@ class PcDefinitionProvider( private def findTypeDefinitions( path: List[Tree], pos: SourcePosition, - indexed: IndexedContext + indexed: IndexedContext, + uri: URI, ): DefinitionResult = import indexed.ctx val enclosing = path.expandRangeToEnclosingApply(pos) val typeSymbols = MetalsInteractive .enclosingSymbolsWithExpressionType(enclosing, pos, indexed) - .map { case (_, tpe) => + .map { case (_, tpe, _) => tpe.typeSymbol } typeSymbols match case Nil => path.headOption match case Some(value: Literal) => - definitionsForSymbol(List(value.typeOpt.widen.typeSymbol), pos) + definitionsForSymbol(List(value.typeOpt.widen.typeSymbol), uri, pos) case _ => DefinitionResultImpl.empty case _ => - definitionsForSymbol(typeSymbols, pos) + definitionsForSymbol(typeSymbols, uri, pos) end findTypeDefinitions private def definitionsForSymbol( symbols: List[Symbol], + uri: URI, pos: SourcePosition )(using ctx: Context): DefinitionResult = symbols match case symbols @ (sym :: other) => val isLocal = sym.source == pos.source if isLocal then + val include = Include.definitions | Include.local val (exportedDefs, otherDefs) = - Interactive.findDefinitions(List(sym), driver, false, false) - .filter(_.source == sym.source) + Interactive.findTreesMatching(driver.openedTrees(uri), include, sym) .partition(_.tree.symbol.is(Exported)) otherDefs.headOption.orElse(exportedDefs.headOption) match diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala index 9c0e6bcfa9d8..cf4929dfc91d 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala @@ -57,7 +57,7 @@ class PcInlayHintsProvider( .headOption .getOrElse(unit.tpdTree) .enclosedChildren(pos.span) - .flatMap(tpdTree => deepFolder(InlayHints.empty, tpdTree).result()) + .flatMap(tpdTree => deepFolder(InlayHints.empty(params.uri()), tpdTree).result()) private def adjustPos(pos: SourcePosition): SourcePosition = pos.adjust(text)._1 @@ -80,15 +80,15 @@ class PcInlayHintsProvider( LabelPart(")") :: Nil, InlayHintKind.Parameter, ) - case ImplicitParameters(symbols, pos, allImplicit) => - val labelParts = symbols.map(s => List(labelPart(s, s.decodedName))) - val label = - if allImplicit then labelParts.separated("(using ", ", ", ")") - else labelParts.separated(", ") + case ImplicitParameters(trees, pos) => inlayHints.add( adjustPos(pos).toLsp, - label, - InlayHintKind.Parameter, + ImplicitParameters.partsFromImplicitArgs(trees).map((label, maybeSymbol) => + maybeSymbol match + case Some(symbol) => labelPart(symbol, label) + case None => LabelPart(label) + ), + InlayHintKind.Parameter ) case ValueOf(label, pos) => inlayHints.add( @@ -221,12 +221,8 @@ object ImplicitParameters: case Apply(fun, args) if args.exists(isSyntheticArg) && !tree.sourcePos.span.isZeroExtent && !args.exists(isQuotes(_)) => val (implicitArgs, providedArgs) = args.partition(isSyntheticArg) - val allImplicit = providedArgs.isEmpty || providedArgs.forall { - case Ident(name) => name == nme.MISSING - case _ => false - } val pos = implicitArgs.head.sourcePos - Some(implicitArgs.map(_.symbol), pos, allImplicit) + Some(implicitArgs, pos) case _ => None } else None @@ -242,6 +238,67 @@ object ImplicitParameters: private def isQuotes(tree: Tree)(using Context) = tree.tpe.typeSymbol == defn.QuotesClass + def partsFromImplicitArgs(trees: List[Tree])(using Context): List[(String, Option[Symbol])] = { + @tailrec + def recurseImplicitArgs( + currentArgs: List[Tree], + remainingArgsLists: List[List[Tree]], + parts: List[(String, Option[Symbol])] + ): List[(String, Option[Symbol])] = + (currentArgs, remainingArgsLists) match { + case (Nil, Nil) => parts + case (Nil, headArgsList :: tailArgsList) => + if (headArgsList.isEmpty) { + recurseImplicitArgs( + headArgsList, + tailArgsList, + (")", None) :: parts + ) + } else { + recurseImplicitArgs( + headArgsList, + tailArgsList, + (", ", None) :: (")", None) :: parts + ) + } + case (arg :: remainingArgs, remainingArgsLists) => + arg match { + case Apply(fun, args) => + val applyLabel = (fun.symbol.decodedName, Some(fun.symbol)) + recurseImplicitArgs( + args, + remainingArgs :: remainingArgsLists, + ("(", None) :: applyLabel :: parts + ) + case t if t.isTerm => + val termLabel = (t.symbol.decodedName, Some(t.symbol)) + if (remainingArgs.isEmpty) + recurseImplicitArgs( + remainingArgs, + remainingArgsLists, + termLabel :: parts + ) + else + recurseImplicitArgs( + remainingArgs, + remainingArgsLists, + (", ", None) :: termLabel :: parts + ) + case _ => + recurseImplicitArgs( + remainingArgs, + remainingArgsLists, + parts + ) + } + } + ((")", None) :: recurseImplicitArgs( + trees, + Nil, + List(("(using ", None)) + )).reverse + } + end ImplicitParameters object ValueOf: diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProviderImpl.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProviderImpl.scala index bbba44d0d84f..fc4b53e60bbd 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProviderImpl.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProviderImpl.scala @@ -18,6 +18,7 @@ import dotty.tools.dotc.interactive.Interactive import dotty.tools.dotc.interactive.InteractiveDriver import dotty.tools.dotc.util.SourcePosition import dotty.tools.pc.utils.InteractiveEnrichments.* +import dotty.tools.pc.IndexedContext.Result import org.eclipse.lsp4j as l @@ -49,7 +50,9 @@ final class PcInlineValueProviderImpl( DefinitionTree(defn, pos) } .toRight(Errors.didNotFindDefinition) - symbols = symbolsUsedInDefn(definition.tree.rhs) + path = Interactive.pathTo(unit.tpdTree, definition.tree.rhs.span)(using newctx) + indexedContext = IndexedContext(Interactive.contextOfPath(path)(using newctx)) + symbols = symbolsUsedInDefn(definition.tree.rhs).filter(indexedContext.lookupSym(_) == Result.InScope) references <- getReferencesToInline(definition, allOccurences, symbols) yield val (deleteDefinition, refsEdits) = references @@ -111,27 +114,25 @@ final class PcInlineValueProviderImpl( val adjustedEnd = extend(pos.end - 1, ')', 1) + 1 text.slice(adjustedStart, adjustedEnd).mkString - private def symbolsUsedInDefn( - rhs: Tree - ): List[Symbol] = + private def symbolsUsedInDefn(rhs: Tree): Set[Symbol] = def collectNames( - symbols: List[Symbol], + symbols: Set[Symbol], tree: Tree - ): List[Symbol] = + ): Set[Symbol] = tree match case id: (Ident | Select) if !id.symbol.is(Synthetic) && !id.symbol.is(Implicit) => - tree.symbol :: symbols + symbols + tree.symbol case _ => symbols - val traverser = new DeepFolder[List[Symbol]](collectNames) - traverser(List(), rhs) + val traverser = new DeepFolder[Set[Symbol]](collectNames) + traverser(Set(), rhs) end symbolsUsedInDefn private def getReferencesToInline( definition: DefinitionTree, allOccurences: List[Occurence], - symbols: List[Symbol] + symbols: Set[Symbol] ): Either[String, (Boolean, List[Reference])] = val defIsLocal = definition.tree.symbol.ownersIterator .drop(1) @@ -156,7 +157,7 @@ final class PcInlineValueProviderImpl( private def makeRefsEdits( refs: List[Occurence], - symbols: List[Symbol] + symbols: Set[Symbol] ): Either[String, List[Reference]] = val newctx = driver.currentCtx.fresh.setCompilationUnit(unit) def buildRef(occurrence: Occurence): Either[String, Reference] = diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcRenameProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcRenameProvider.scala index 666ccf9c614f..467f331ea7dc 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcRenameProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcRenameProvider.scala @@ -18,11 +18,19 @@ final class PcRenameProvider( name: Option[String] ) extends WithSymbolSearchCollector[l.TextEdit](driver, params): private val forbiddenMethods = - Set("equals", "hashCode", "unapply", "unary_!", "!") + Set("equals", "hashCode", "unapply", "apply", "", "unary_!", "!") + + private val soughtSymbolNames = soughtSymbols match + case Some((symbols, _)) => + symbols.filterNot(_.isError).map(symbol => symbol.decodedName.toString) + case None => Set.empty[String] + def canRenameSymbol(sym: Symbol)(using Context): Boolean = - (!sym.is(Method) || !forbiddenMethods(sym.decodedName)) - && (sym.ownersIterator.drop(1).exists(ow => ow.is(Method)) - || sym.source.path.isWorksheet) + val decodedName = sym.decodedName + def isForbiddenMethod = sym.is(Method) && forbiddenMethods(decodedName) + def local = sym.ownersIterator.drop(1).exists(ow => ow.is(Method)) + def isInWorksheet = sym.source.path.isWorksheet + !isForbiddenMethod && (local || isInWorksheet) && soughtSymbolNames(decodedName) def prepareRename(): Option[l.Range] = soughtSymbols.flatMap((symbols, pos) => diff --git a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala index 218d92c38ffa..dc53525480c3 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala @@ -16,6 +16,7 @@ import scala.language.unsafeNulls import scala.meta.internal.metals.CompilerVirtualFileParams import scala.meta.internal.metals.EmptyCancelToken import scala.meta.internal.metals.EmptyReportContext +import scala.meta.internal.metals.PcQueryContext import scala.meta.internal.metals.ReportContext import scala.meta.internal.metals.ReportLevel import scala.meta.internal.metals.StdReportContext @@ -55,6 +56,14 @@ case class ScalaPresentationCompiler( completionItemPriority: CompletionItemPriority = (_: String) => 0, ) extends PresentationCompiler: + override def supportedCodeActions(): ju.List[String] = List( + CodeActionId.ConvertToNamedArguments, + CodeActionId.ImplementAbstractMembers, + CodeActionId.ExtractMethod, + CodeActionId.InlineValue, + CodeActionId.InsertInferredType + ).asJava + def this() = this("", None, Nil, Nil) val scalaVersion = BuildInfo.scalaVersion @@ -67,6 +76,38 @@ case class ScalaPresentationCompiler( .map(StdReportContext(_, _ => buildTargetName, reportsLevel)) .getOrElse(EmptyReportContext) + override def codeAction[T]( + params: OffsetParams, + codeActionId: String, + codeActionPayload: Optional[T] + ): CompletableFuture[ju.List[TextEdit]] = + (codeActionId, codeActionPayload.asScala) match + case ( + CodeActionId.ConvertToNamedArguments, + Some(argIndices: ju.List[_]) + ) => + val payload = + argIndices.asScala.collect { case i: Integer => i.toInt }.toSet + convertToNamedArguments(params, payload) + case (CodeActionId.ImplementAbstractMembers, _) => + implementAbstractMembers(params) + case (CodeActionId.InsertInferredType, _) => + insertInferredType(params) + case (CodeActionId.InlineValue, _) => + inlineValue(params) + case (CodeActionId.ExtractMethod, Some(extractionPos: OffsetParams)) => + params match { + case range: RangeParams => + extractMethod(range, extractionPos) + case _ => failedFuture(new IllegalArgumentException(s"Expected range parameters")) + } + case (id, _) => failedFuture(new IllegalArgumentException(s"Unsupported action id $id")) + + private def failedFuture[T](e: Throwable): CompletableFuture[T] = + val f = new CompletableFuture[T]() + f.completeExceptionally(e) + f + override def withCompletionItemPriority( priority: CompletionItemPriority ): PresentationCompiler = @@ -103,18 +144,18 @@ case class ScalaPresentationCompiler( override def semanticTokens( params: VirtualFileParams ): CompletableFuture[ju.List[Node]] = - compilerAccess.withInterruptableCompiler(Some(params))( + compilerAccess.withInterruptableCompiler( new ju.ArrayList[Node](), params.token() ) { access => val driver = access.compiler() new PcSemanticTokensProvider(driver, params).provide().asJava - } + }(params.toQueryContext) override def inlayHints( params: InlayHintsParams ): ju.concurrent.CompletableFuture[ju.List[l.InlayHint]] = - compilerAccess.withInterruptableCompiler(Some(params))( + compilerAccess.withInterruptableCompiler( new ju.ArrayList[l.InlayHint](), params.token(), ) { access => @@ -122,7 +163,7 @@ case class ScalaPresentationCompiler( new PcInlayHintsProvider(driver, params, search) .provide() .asJava - } + }(params.toQueryContext) override def getTasty( targetUri: URI, @@ -133,7 +174,7 @@ case class ScalaPresentationCompiler( } def complete(params: OffsetParams): CompletableFuture[l.CompletionList] = - compilerAccess.withInterruptableCompiler(Some(params))( + compilerAccess.withInterruptableCompiler( EmptyCompletionList(), params.token() ) { access => @@ -148,44 +189,43 @@ case class ScalaPresentationCompiler( folderPath, completionItemPriority ).completions() - - } + }(params.toQueryContext) def definition(params: OffsetParams): CompletableFuture[DefinitionResult] = - compilerAccess.withInterruptableCompiler(Some(params))( + compilerAccess.withInterruptableCompiler( DefinitionResultImpl.empty, params.token() ) { access => val driver = access.compiler() PcDefinitionProvider(driver, params, search).definitions() - } + }(params.toQueryContext) override def typeDefinition( params: OffsetParams ): CompletableFuture[DefinitionResult] = - compilerAccess.withInterruptableCompiler(Some(params))( + compilerAccess.withInterruptableCompiler( DefinitionResultImpl.empty, params.token() ) { access => val driver = access.compiler() PcDefinitionProvider(driver, params, search).typeDefinitions() - } + }(params.toQueryContext) def documentHighlight( params: OffsetParams ): CompletableFuture[ju.List[DocumentHighlight]] = - compilerAccess.withInterruptableCompiler(Some(params))( + compilerAccess.withInterruptableCompiler( List.empty[DocumentHighlight].asJava, params.token() ) { access => val driver = access.compiler() PcDocumentHighlightProvider(driver, params).highlights.asJava - } + }(params.toQueryContext) override def references( params: ReferencesRequest ): CompletableFuture[ju.List[ReferencesResult]] = - compilerAccess.withNonInterruptableCompiler(Some(params.file()))( + compilerAccess.withNonInterruptableCompiler( List.empty[ReferencesResult].asJava, params.file().token, ) { access => @@ -193,16 +233,16 @@ case class ScalaPresentationCompiler( PcReferencesProvider(driver, params) .references() .asJava - } + }(params.file().toQueryContext) def inferExpectedType(params: OffsetParams): CompletableFuture[ju.Optional[String]] = - compilerAccess.withInterruptableCompiler(Some(params))( + compilerAccess.withInterruptableCompiler( Optional.empty(), params.token, ) { access => val driver = access.compiler() new InferExpectedType(search, driver, params).infer().asJava - } + }(params.toQueryContext) def shutdown(): Unit = compilerAccess.shutdown() @@ -217,8 +257,6 @@ case class ScalaPresentationCompiler( symbol: String ): CompletableFuture[Optional[IPcSymbolInformation]] = compilerAccess.withNonInterruptableCompiler[Optional[IPcSymbolInformation]]( - None - )( Optional.empty(), EmptyCancelToken, ) { access => @@ -226,27 +264,27 @@ case class ScalaPresentationCompiler( .info(symbol) .map(_.asJava) .asJava - } + }(emptyQueryContext) def semanticdbTextDocument( filename: URI, code: String ): CompletableFuture[Array[Byte]] = val virtualFile = CompilerVirtualFileParams(filename, code) - compilerAccess.withNonInterruptableCompiler(Some(virtualFile))( + compilerAccess.withNonInterruptableCompiler( Array.empty[Byte], EmptyCancelToken ) { access => val driver = access.compiler() val provider = SemanticdbTextDocumentProvider(driver, folderPath) provider.textDocument(filename, code) - } + }(virtualFile.toQueryContext) def completionItemResolve( item: l.CompletionItem, symbol: String ): CompletableFuture[l.CompletionItem] = - compilerAccess.withNonInterruptableCompiler(None)( + compilerAccess.withNonInterruptableCompiler( item, EmptyCancelToken ) { access => @@ -254,7 +292,7 @@ case class ScalaPresentationCompiler( CompletionItemResolver.resolve(item, symbol, search, config)(using driver.currentCtx ) - } + }(emptyQueryContext) def autoImports( name: String, @@ -263,7 +301,7 @@ case class ScalaPresentationCompiler( ): CompletableFuture[ ju.List[scala.meta.pc.AutoImportsResult] ] = - compilerAccess.withNonInterruptableCompiler(Some(params))( + compilerAccess.withNonInterruptableCompiler( List.empty[scala.meta.pc.AutoImportsResult].asJava, params.token() ) { access => @@ -278,13 +316,13 @@ case class ScalaPresentationCompiler( ) .autoImports(isExtension) .asJava - } + }(params.toQueryContext) def implementAbstractMembers( params: OffsetParams ): CompletableFuture[ju.List[l.TextEdit]] = val empty: ju.List[l.TextEdit] = new ju.ArrayList[l.TextEdit]() - compilerAccess.withNonInterruptableCompiler(Some(params))( + compilerAccess.withNonInterruptableCompiler( empty, params.token() ) { pc => @@ -295,31 +333,31 @@ case class ScalaPresentationCompiler( search, config ) - } + }(params.toQueryContext) end implementAbstractMembers override def insertInferredType( params: OffsetParams ): CompletableFuture[ju.List[l.TextEdit]] = val empty: ju.List[l.TextEdit] = new ju.ArrayList[l.TextEdit]() - compilerAccess.withNonInterruptableCompiler(Some(params))( + compilerAccess.withNonInterruptableCompiler( empty, params.token() ) { pc => new InferredTypeProvider(params, pc.compiler(), config, search) .inferredTypeEdits() .asJava - } + }(params.toQueryContext) override def inlineValue( params: OffsetParams ): CompletableFuture[ju.List[l.TextEdit]] = val empty: Either[String, List[l.TextEdit]] = Right(List()) (compilerAccess - .withInterruptableCompiler(Some(params))(empty, params.token()) { pc => + .withInterruptableCompiler(empty, params.token()) { pc => new PcInlineValueProviderImpl(pc.compiler(), params) .getInlineTextEdits() - }) + }(params.toQueryContext)) .thenApply { case Right(edits: List[TextEdit]) => edits.asJava case Left(error: String) => throw new DisplayableException(error) @@ -331,7 +369,7 @@ case class ScalaPresentationCompiler( extractionPos: OffsetParams ): CompletableFuture[ju.List[l.TextEdit]] = val empty: ju.List[l.TextEdit] = new ju.ArrayList[l.TextEdit]() - compilerAccess.withInterruptableCompiler(Some(range))(empty, range.token()) { + compilerAccess.withInterruptableCompiler(empty, range.token()) { pc => new ExtractMethodProvider( range, @@ -342,22 +380,28 @@ case class ScalaPresentationCompiler( ) .extractMethod() .asJava - } + }(range.toQueryContext) end extractMethod override def convertToNamedArguments( params: OffsetParams, argIndices: ju.List[Integer] + ): CompletableFuture[ju.List[l.TextEdit]] = + convertToNamedArguments(params, argIndices.asScala.toSet.map(_.toInt)) + + def convertToNamedArguments( + params: OffsetParams, + argIndices: Set[Int] ): CompletableFuture[ju.List[l.TextEdit]] = val empty: Either[String, List[l.TextEdit]] = Right(List()) (compilerAccess - .withNonInterruptableCompiler(Some(params))(empty, params.token()) { pc => + .withNonInterruptableCompiler(empty, params.token()) { pc => new ConvertToNamedArgumentsProvider( pc.compiler(), params, - argIndices.asScala.map(_.toInt).toSet + argIndices ).convertToNamedArguments - }) + }(params.toQueryContext)) .thenApplyAsync { case Left(error: String) => throw new DisplayableException(error) case Right(edits: List[l.TextEdit]) => edits.asJava @@ -367,33 +411,33 @@ case class ScalaPresentationCompiler( params: ju.List[OffsetParams] ): CompletableFuture[ju.List[l.SelectionRange]] = CompletableFuture.completedFuture { - compilerAccess.withSharedCompiler(params.asScala.headOption)( + compilerAccess.withSharedCompiler( List.empty[l.SelectionRange].asJava ) { pc => new SelectionRangeProvider( pc.compiler(), params, ).selectionRange().asJava - } + }(params.asScala.headOption.map(_.toQueryContext).getOrElse(emptyQueryContext)) } end selectionRange def hover( params: OffsetParams ): CompletableFuture[ju.Optional[HoverSignature]] = - compilerAccess.withNonInterruptableCompiler(Some(params))( + compilerAccess.withNonInterruptableCompiler( ju.Optional.empty[HoverSignature](), params.token() ) { access => val driver = access.compiler() HoverProvider.hover(params, driver, search, config.hoverContentType()) - } + }(params.toQueryContext) end hover def prepareRename( params: OffsetParams ): CompletableFuture[ju.Optional[l.Range]] = - compilerAccess.withNonInterruptableCompiler(Some(params))( + compilerAccess.withNonInterruptableCompiler( Optional.empty[l.Range](), params.token() ) { access => @@ -401,19 +445,19 @@ case class ScalaPresentationCompiler( Optional.ofNullable( PcRenameProvider(driver, params, None).prepareRename().orNull ) - } + }(params.toQueryContext) def rename( params: OffsetParams, name: String ): CompletableFuture[ju.List[l.TextEdit]] = - compilerAccess.withNonInterruptableCompiler(Some(params))( + compilerAccess.withNonInterruptableCompiler( List[l.TextEdit]().asJava, params.token() ) { access => val driver = access.compiler() PcRenameProvider(driver, params, Some(name)).rename().asJava - } + }(params.toQueryContext) def newInstance( buildTargetIdentifier: String, @@ -427,13 +471,13 @@ case class ScalaPresentationCompiler( ) def signatureHelp(params: OffsetParams): CompletableFuture[l.SignatureHelp] = - compilerAccess.withNonInterruptableCompiler(Some(params))( + compilerAccess.withNonInterruptableCompiler( new l.SignatureHelp(), params.token() ) { access => val driver = access.compiler() SignatureHelpProvider.signatureHelp(driver, params, search) - } + }(params.toQueryContext) override def didChange( params: VirtualFileParams @@ -441,10 +485,10 @@ case class ScalaPresentationCompiler( CompletableFuture.completedFuture(Nil.asJava) override def didClose(uri: URI): Unit = - compilerAccess.withNonInterruptableCompiler(None)( + compilerAccess.withNonInterruptableCompiler( (), EmptyCancelToken - ) { access => access.compiler().close(uri) } + ) { access => access.compiler().close(uri) }(emptyQueryContext) override def withExecutorService( executorService: ExecutorService @@ -469,4 +513,19 @@ case class ScalaPresentationCompiler( override def isLoaded() = compilerAccess.isLoaded() + def additionalReportData() = + s"""|Scala version: $scalaVersion + |Classpath: + |${classpath + .map(path => s"$path [${if path.exists then "exists" else "missing"} ]") + .mkString(", ")} + |Options: + |${options.mkString(" ")} + |""".stripMargin + + extension (params: VirtualFileParams) + def toQueryContext = PcQueryContext(Some(params), additionalReportData) + + def emptyQueryContext = PcQueryContext(None, additionalReportData) + end ScalaPresentationCompiler diff --git a/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala index ccda618078b8..8bed605a87f8 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala @@ -55,7 +55,13 @@ class SymbolInformationProvider(using Context): val classOwner = sym.ownersIterator.drop(1).find(s => s.isClass || s.is(Flags.Module)) val overridden = sym.denot.allOverriddenSymbols.toList - val memberDefAnnots = sym.info.membersBasedOnFlags(Flags.Method, Flags.EmptyFlags).flatMap(_.allSymbols).flatMap(_.denot.annotations) + val memberDefAnnots = + if classSym.exists then + classSym.info + .membersBasedOnFlags(Flags.Method, Flags.EmptyFlags) + .flatMap(_.allSymbols) + .flatMap(_.denot.annotations) + else Nil val pcSymbolInformation = PcSymbolInformation( diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala index 6d89cb663b9c..40f1ccd2e797 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala @@ -23,7 +23,9 @@ case class CompletionPos( query: String, originalCursorPosition: SourcePosition, sourceUri: URI, - withCURSOR: Boolean + withCURSOR: Boolean, + hasLeadingBacktick: Boolean, + hasTrailingBacktick: Boolean ): def queryEnd: Int = originalCursorPosition.point def stripSuffixEditRange: l.Range = new l.Range(originalCursorPosition.offsetToPos(queryStart), originalCursorPosition.offsetToPos(identEnd)) @@ -38,16 +40,35 @@ object CompletionPos: adjustedPath: List[Tree], wasCursorApplied: Boolean )(using Context): CompletionPos = - val identEnd = adjustedPath match + def hasBacktickAt(offset: Int): Boolean = + sourcePos.source.content().lift(offset).contains('`') + + val (identEnd, hasTrailingBacktick) = adjustedPath match case (refTree: RefTree) :: _ if refTree.name.toString.contains(Cursor.value) => - refTree.span.end - Cursor.value.length - case (refTree: RefTree) :: _ => refTree.span.end - case _ => sourcePos.end + val refTreeEnd = refTree.span.end + val hasTrailingBacktick = hasBacktickAt(refTreeEnd - 1) + val identEnd = refTreeEnd - Cursor.value.length + (if hasTrailingBacktick then identEnd - 1 else identEnd, hasTrailingBacktick) + case (refTree: RefTree) :: _ => + val refTreeEnd = refTree.span.end + val hasTrailingBacktick = hasBacktickAt(refTreeEnd - 1) + (if hasTrailingBacktick then refTreeEnd - 1 else refTreeEnd, hasTrailingBacktick) + case _ => (sourcePos.end, false) val query = Completion.completionPrefix(adjustedPath, sourcePos) val start = sourcePos.end - query.length() + val hasLeadingBacktick = hasBacktickAt(start - 1) - CompletionPos(start, identEnd, query.nn, sourcePos, offsetParams.uri.nn, wasCursorApplied) + CompletionPos( + start, + identEnd, + query.nn, + sourcePos, + offsetParams.uri.nn, + wasCursorApplied, + hasLeadingBacktick, + hasTrailingBacktick + ) /** * Infer the indentation by counting the number of spaces in the given line. diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala index adaeadb12978..2a63d6a92a81 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala @@ -99,9 +99,9 @@ class CompletionProvider( * 4| $1$.sliding@@[Int](size, step) * */ - if qual.symbol.is(Flags.Synthetic) && qual.symbol.name.isInstanceOf[DerivedName] => + if qual.symbol.is(Flags.Synthetic) && qual.span.isZeroExtent && qual.symbol.name.isInstanceOf[DerivedName] => qual.symbol.defTree match - case valdef: ValDef => Select(valdef.rhs, name) :: tail + case valdef: ValDef if !valdef.rhs.isEmpty => Select(valdef.rhs, name) :: tail case _ => tpdPath0 case _ => tpdPath0 @@ -218,7 +218,9 @@ class CompletionProvider( // related issue https://github.com/lampepfl/scala3/issues/11941 lazy val kind: CompletionItemKind = underlyingCompletion.completionItemKind val description = underlyingCompletion.description(printer) - val label = underlyingCompletion.labelWithDescription(printer) + val label = + if config.isDetailIncludedInLabel then completion.labelWithDescription(printer) + else completion.label val ident = underlyingCompletion.insertText.getOrElse(underlyingCompletion.label) lazy val isInStringInterpolation = @@ -246,10 +248,17 @@ class CompletionProvider( range: Option[LspRange] = None ): CompletionItem = val oldText = params.text().nn.substring(completionPos.queryStart, completionPos.identEnd) - val editRange = if newText.startsWith(oldText) then completionPos.stripSuffixEditRange + val trimmedNewText = { + var nt = newText + if (completionPos.hasLeadingBacktick) nt = nt.stripPrefix("`") + if (completionPos.hasTrailingBacktick) nt = nt.stripSuffix("`") + nt + } + + val editRange = if trimmedNewText.startsWith(oldText) then completionPos.stripSuffixEditRange else completionPos.toEditRange - val textEdit = new TextEdit(range.getOrElse(editRange), wrapInBracketsIfRequired(newText)) + val textEdit = new TextEdit(range.getOrElse(editRange), wrapInBracketsIfRequired(trimmedNewText)) val item = new CompletionItem(label) item.setSortText(f"${idx}%05d") diff --git a/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala b/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala index 66080a363d51..b65f23fae40f 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala @@ -1,5 +1,7 @@ package dotty.tools.pc.utils +import java.util.Optional + import scala.annotation.tailrec import scala.meta.internal.jdk.CollectionConverters.* import scala.meta.internal.mtags.CommonMtagsEnrichments @@ -272,11 +274,14 @@ object InteractiveEnrichments extends CommonMtagsEnrichments: symbol.maybeOwner.companion, ).filter(_ != NoSymbol) ++ symbol.allOverriddenSymbols else symbol.allOverriddenSymbols - val documentation = search.documentation( - sym, - () => parentSymbols.iterator.map(toSemanticdbSymbol).toList.asJava, - contentType, - ) + val documentation = + if symbol.isLocal then Optional.empty + else + search.documentation( + sym, + () => parentSymbols.iterator.map(toSemanticdbSymbol).toList.asJava, + contentType, + ) documentation.nn.toScala end symbolDocumentation end extension diff --git a/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala index 5e13c07b9e5f..b2d837e2ff50 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala @@ -6,8 +6,10 @@ import dotty.tools.pc.ScalaPresentationCompiler import org.junit.{Before, Test} import scala.language.unsafeNulls -import scala.meta.internal.metals.EmptyCancelToken import scala.meta.internal.metals.CompilerOffsetParams +import scala.meta.internal.metals.EmptyCancelToken +import scala.meta.internal.metals.EmptyReportContext +import scala.meta.internal.metals.PcQueryContext import scala.meta.pc.OffsetParams import scala.concurrent.Future import scala.concurrent.Await @@ -26,20 +28,22 @@ class CompilerCachingSuite extends BasePCSuite: private def checkCompilationCount(expected: Int): Unit = presentationCompiler match case pc: ScalaPresentationCompiler => - val compilations = pc.compilerAccess.withNonInterruptableCompiler(None)(-1, EmptyCancelToken) { driver => + val compilations = pc.compilerAccess.withNonInterruptableCompiler(-1, EmptyCancelToken) { driver => driver.compiler().currentCtx.runId - }.get(timeout.length, timeout.unit) + }(emptyQueryContext).get(timeout.length, timeout.unit) assertEquals(expected, compilations, s"Expected $expected compilations but got $compilations") case _ => throw IllegalStateException("Presentation compiler should always be of type of ScalaPresentationCompiler") private def getContext(): Context = presentationCompiler match case pc: ScalaPresentationCompiler => - pc.compilerAccess.withNonInterruptableCompiler(None)(null, EmptyCancelToken) { driver => + pc.compilerAccess.withNonInterruptableCompiler(null, EmptyCancelToken) { driver => driver.compiler().currentCtx - }.get(timeout.length, timeout.unit) + }(emptyQueryContext).get(timeout.length, timeout.unit) case _ => throw IllegalStateException("Presentation compiler should always be of type of ScalaPresentationCompiler") + private def emptyQueryContext = PcQueryContext(None, () => "")(using EmptyReportContext) + @Before def beforeEach: Unit = presentationCompiler.restart() diff --git a/presentation-compiler/test/dotty/tools/pc/tests/SelectionRangeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/SelectionRangeSuite.scala index e277a67c466b..143d998a0ec1 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/SelectionRangeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/SelectionRangeSuite.scala @@ -123,27 +123,13 @@ class SelectionRangeSuite extends BaseSelectionRangeSuite: |}""".stripMargin ) ) + + @Test def `def - type params` = check( - """|object Main extends App { - | val func = (a@@: Int, b: Int) => - | 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<>region>>Type <: T1<>region>>Type <: T1, B<>region>>def foo[Type <: T1, B](hi: Int, b: Int, c:Int) = ???< Boolean): Boolean + |""".stripMargin + ) + + @Test def tupleAlias = + check( + """ + |trait Foo { + | def setup: List[Foo.TupleAliasResult] + |} + |object Foo { + | type TupleAliasResult = (String, String) + | val foo: Foo = ??? + | foo.setup.exist@@ + |}""".stripMargin, + """|exists(p: TupleAliasResult => Boolean): Boolean + |""".stripMargin + ) + + @Test def listAlias = + check( + """ + |trait Foo { + | def setup: List[Foo.ListAliasResult] + |} + |object Foo { + | type ListAliasResult = List[String] + | val foo: Foo = ??? + | foo.setup.exist@@ + |}""".stripMargin, + """|exists(p: ListAliasResult => Boolean): Boolean + |""".stripMargin + ) + @Ignore("This test should be handled by compiler fuzzy search") @Test def fuzzy = check( @@ -1988,8 +2032,7 @@ class CompletionSuite extends BaseCompletionSuite: @Test def `namedTuple completions` = check( - """|import scala.language.experimental.namedTuples - |import scala.NamedTuple.* + """|import scala.NamedTuple.* | |val person = (name = "Jamie", city = "Lausanne") | @@ -2000,8 +2043,7 @@ class CompletionSuite extends BaseCompletionSuite: @Test def `Selectable with namedTuple Fields member` = check( - """|import scala.language.experimental.namedTuples - |import scala.NamedTuple.* + """|import scala.NamedTuple.* | |class NamedTupleSelectable extends Selectable { | type Fields <: AnyNamedTuple @@ -2168,3 +2210,28 @@ class CompletionSuite extends BaseCompletionSuite: """|build: Unit |""".stripMargin, ) + + @Test def i7191 = + check( + """|val x = Some(3).map(_.@@) + |""".stripMargin, + """|!=(x: Byte): Boolean + |!=(x: Char): Boolean + |!=(x: Double): Boolean + |""".stripMargin, + topLines = Some(3) + ) + + @Test def `packageIssueIdent` = + check( + """package one@@ + |""".stripMargin, + "" + ) + + @Test def `packageIssueSelect` = + check( + """package one.two@@ + |""".stripMargin, + "" + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionWithoutDetailsSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionWithoutDetailsSuite.scala new file mode 100644 index 000000000000..f265b5393ccc --- /dev/null +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionWithoutDetailsSuite.scala @@ -0,0 +1,120 @@ +package dotty.tools.pc.tests.completion + +import dotty.tools.pc.base.BaseCompletionSuite + +import scala.meta.internal.pc.PresentationCompilerConfigImpl +import scala.meta.pc.PresentationCompilerConfig + +import org.junit.Test + +class CompletionWithoutDetailsSuite extends BaseCompletionSuite: + + override def config: PresentationCompilerConfig = + PresentationCompilerConfigImpl().copy( + isDetailIncludedInLabel = false + ) + + @Test def `scope` = + check( + """ + |object A { + | Lis@@ + |}""".stripMargin, + """|List + |List + |List + |List + |ListUI + |""".stripMargin, + includeDetail = false, + topLines = Some(5) + ) + + @Test def `scope-detail` = + check( + """ + |object A { + | Lis@@ + |}""".stripMargin, + """|List[A](elems: A*): List[A] + |List scala.collection.immutable + |List java.awt + |List java.util + |ListUI javax.swing.plaf + |""".stripMargin, + includeDetail = true, + topLines = Some(5) + ) + + @Test def member = + check( + """ + |object A { + | List.emp@@ + |}""".stripMargin, + """ + |empty + |""".stripMargin, + includeDetail = false + ) + + @Test def extension = + check( + """ + |object A { + | "".stripSu@@ + |}""".stripMargin, + """|stripSuffix + |""".stripMargin, + includeDetail = false + ) + + @Test def tparam = + check( + """ + |class Foo[A] { + | def identity[B >: A](a: B): B = a + |} + |object Foo { + | new Foo[Int].ident@@ + |}""".stripMargin, + """|identity + |""".stripMargin, + includeDetail = false + ) + + @Test def tparam1 = + check( + """ + |class Foo[A] { + | def identity(a: A): A = a + |} + |object Foo { + | new Foo[Int].ident@@ + |}""".stripMargin, + """|identity + |""".stripMargin, + includeDetail = false + ) + + @Test def tparam2 = + check( + """ + |object A { + | Map.empty[Int, String].getOrEl@@ + |} + |""".stripMargin, + """|getOrElse + |""".stripMargin, + includeDetail = false + ) + + @Test def pkg = + check( + """ + |import scala.collection.conc@@ + |""".stripMargin, + """|concurrent + |""".stripMargin, + includeDetail = false + ) \ No newline at end of file diff --git a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala index fab21ffdee0a..d76dd92c8845 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala @@ -478,6 +478,33 @@ class PcDefinitionSuite extends BasePcDefinitionSuite: |""".stripMargin ) + @Test def `enum-class-type-param` = + check( + """| + |enum Options[<>]: + | case Some(x: A@@A) + | case None extends Options[Nothing] + |""".stripMargin + ) + + @Test def `enum-class-type-param-covariant` = + check( + """| + |enum Options[+<>]: + | case Some(x: A@@A) + | case None extends Options[Nothing] + |""".stripMargin + ) + + @Test def `enum-class-type-param-duplicate` = + check( + """| + |enum Testing[AA]: + | case Some[<>](x: A@@A) extends Testing[AA] + | case None extends Testing[Nothing] + |""".stripMargin + ) + @Test def `derives-def` = check( """| @@ -504,3 +531,11 @@ class PcDefinitionSuite extends BasePcDefinitionSuite: |val a = MyIntOut(1).un@@even |""".stripMargin, ) + + @Test def `named-tuples` = + check( + """| + |val <> = (name = "Bob", age = 42, height = 1.9d) + |val foo_name = foo.na@@me + |""".stripMargin + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/edit/InlineValueSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/edit/InlineValueSuite.scala index 0cec3952a7ef..cee0aada9f86 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/edit/InlineValueSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/edit/InlineValueSuite.scala @@ -301,6 +301,47 @@ class InlineValueSuite extends BaseCodeActionSuite with CommonMtagsEnrichments: |}""".stripMargin ) + + @Test def `i6924` = + checkEdit( + """|object O { + | def test(n: Int) = { + | val isOne = n == 1 + | <>sOne + | } + |} + |""".stripMargin, + """|object O { + | def test(n: Int) = { + | n == 1 + | } + |} + |""".stripMargin + ) + + @Test def `i6924-2` = + checkEdit( + """|object O { + | def ==(o: O) = false + |} + |object P { + | def test() = { + | val isOne = O == O + | <>sOne + | } + |} + |""".stripMargin, + """|object O { + | def ==(o: O) = false + |} + |object P { + | def test() = { + | O == O + | } + |} + |""".stripMargin + ) + @Test def `scoping-packages` = checkError( """|package a @@ -346,6 +387,26 @@ class InlineValueSuite extends BaseCodeActionSuite with CommonMtagsEnrichments: InlineErrors.variablesAreShadowed("A.k") ) + @Test def `bad-scoping-3` = + checkError( + """|class T { + | val a = 1 + |} + | + |class O { + | val t = new T() + | import t._ + | val bb = a + a + | + | class Inner { + | val a = 123 + | val cc = <>b + | } + |} + |""".stripMargin, + InlineErrors.variablesAreShadowed("T.a") + ) + def checkEdit( original: String, expected: String, diff --git a/presentation-compiler/test/dotty/tools/pc/tests/edit/InsertInferredTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/edit/InsertInferredTypeSuite.scala index a96dd78be138..c1a84d6abb79 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/edit/InsertInferredTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/edit/InsertInferredTypeSuite.scala @@ -883,6 +883,93 @@ class InsertInferredTypeSuite extends BaseCodeActionSuite: |""".stripMargin ) + @Test def `operator-val` = + checkEdit( + """|object A { + | val <> = 1 + |} + |""".stripMargin, + """|object A { + | val ! : Int = 1 + |} + |""".stripMargin + ) + + @Test def `operator-def` = + checkEdit( + """|object A { + | def <> = 1 + |} + |""".stripMargin, + """|object A { + | def ! : Int = 1 + |} + |""".stripMargin + ) + + @Test def `operator-def-param` = + checkEdit( + """|object A { + | def <>[T] = 1 + |} + |""".stripMargin, + """|object A { + | def ![T]: Int = 1 + |} + |""".stripMargin + ) + + @Test def `operator-def-type-param` = + checkEdit( + """|object A { + | def <>(x: Int) = 1 + |} + |""".stripMargin, + """|object A { + | def !(x: Int): Int = 1 + |} + |""".stripMargin + ) + + @Test def `operator-for` = + checkEdit( + """|object A { + | def foo = for(<> <- List(1)) yield ! + |} + |""".stripMargin, + """|object A { + | def foo = for(! : Int <- List(1)) yield ! + |} + |""".stripMargin + ) + @Test def `operator-lambda` = + checkEdit( + """|object A { + | val foo: Int => Int = (<>) => ! + 1 + |} + |""".stripMargin, + """|object A { + | val foo: Int => Int = (! : Int) => ! + 1 + |} + |""".stripMargin + ) + + @Test def `operator-ident` = + checkEdit( + """|object A { + | def foo = + | val ! = 1 + | <> + |} + |""".stripMargin, + """|object A { + | def foo = + | val ! = 1 + | ! : Int + |} + |""".stripMargin + ) + def checkEdit( original: String, expected: String diff --git a/presentation-compiler/test/dotty/tools/pc/tests/edit/PcRenameSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/edit/PcRenameSuite.scala index 23c81fcf515a..3226018f88d4 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/edit/PcRenameSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/edit/PcRenameSuite.scala @@ -508,3 +508,29 @@ class PcRenameSuite extends BasePcRenameSuite: | ??? | end bar""".stripMargin ) + + @Test def `apply-rename` = + check( + """|object B { + | def locally = { + | object A{ def app@@ly(a: Int) = ??? } + | A(123) + | A.apply(123) + | } + |} + |""".stripMargin, + wrap = false + ) + + @Test def `constructor-rename` = + check( + """|object B { + | def locally = { + | class A(a : String){ def th@@is(a: Int) = this(a.toString) } + | A(123) + | A.apply(123) + | } + |} + |""".stripMargin, + wrap = false + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala index 5d9893f6a1c1..4809bc5cd5b8 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala @@ -1462,5 +1462,29 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite: |""".stripMargin ) + @Test def i3053 = + check( + s"""import Aaaa.* + | + |def classDef2(cdef: List[Int]): Int = { + | def aaa(ddef: Thicket2): List[Int] = ddef match { + | case Thicket2(_) => ??? + | } + |${("//" + "x" * 64 + "\n") * 64} + | 1 + |}.<>("aaa") + | + |case class Thicket2(trees: List[Int]) {} + | + |object Aaaa { + | extension [T](x: T) + | def <>[U](aaa: String): T = { + | x + | } + |} + | + |""".stripMargin + ) + end DocumentHighlightSuite diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverDefnSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverDefnSuite.scala index f4ce4473e60a..34aca39dcf50 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverDefnSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverDefnSuite.scala @@ -238,3 +238,27 @@ class HoverDefnSuite extends BaseHoverSuite: |""".stripMargin, "val foo: Int".hover ) + + @Test def `i22335` = + check( + """|def fromInt[T: Numeric as n@@um](t: Int): T = num.fromInt(t) + |""".stripMargin, + """|num: Numeric[T] + |""".stripMargin.hover + ) + + @Test def `i22335-2` = + check( + """|def showMax[X : {Numeric as nu@@m, Ordered as ord}](x: X, y: X): String = ??? + |""".stripMargin, + """|num: Numeric[X] + |""".stripMargin.hover + ) + + @Test def `i22335-3` = + check( + """|def showMax[X : {Nu@@meric as num, Ordered as ord}](x: X, y: X): String = ??? + |""".stripMargin, + """|type Numeric: Numeric + |""".stripMargin.hover + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverDocSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverDocSuite.scala index fc9b6835e319..a82fa73865c8 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverDocSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverDocSuite.scala @@ -254,3 +254,15 @@ class HoverDocSuite extends BaseHoverSuite: |Found documentation for _empty_/Alpha# |""".stripMargin, ) + + @Test def `i7093` = + check( + """|object O: + | /** Ooopsie daisy */ + | val computeLogicOwners: Unit = + | /** This is a comment */ + | <> + | ??? + |""".stripMargin, + """def logicOwners: Nothing""".hover.stripMargin + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverHoleSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverHoleSuite.scala new file mode 100644 index 000000000000..cc9bd9f39fac --- /dev/null +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverHoleSuite.scala @@ -0,0 +1,75 @@ +package dotty.tools.pc.tests.hover + +import dotty.tools.pc.base.BaseHoverSuite + +import org.junit.Test + +class HoverHoleSuite extends BaseHoverSuite: + @Test def basic = + check( + """object a { + | val x: Int = ?@@?? + |} + |""".stripMargin, + """|**Expression type**: + |```scala + |Int + |``` + |**Symbol signature**: + |```scala + |def ???: Nothing + |``` + |""".stripMargin + ) + + @Test def function = + check( + """object a { + | def m(i: Int) = ??? + | val x = m(??@@?) + |} + |""".stripMargin, + """|**Expression type**: + |```scala + |Int + |``` + |**Symbol signature**: + |```scala + |def ???: Nothing + |``` + |""".stripMargin + ) + + @Test def constructor = + check( + """object a { + | val x = List(1, ?@@??) + |} + |""".stripMargin, + """|**Expression type**: + |```scala + |Int + |``` + |**Symbol signature**: + |```scala + |def ???: Nothing + |``` + |""".stripMargin + ) + + @Test def bounds = + check( + """|trait Foo + |def foo[T <: Foo](a: T): Boolean = ??? + |val _ = foo(?@@??) + |""".stripMargin, + """|**Expression type**: + |```scala + |Foo + |``` + |**Symbol signature**: + |```scala + |def ???: Nothing + |``` + |""".stripMargin + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverNamedArgSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverNamedArgSuite.scala index 182f8e1e0644..9f9e11725447 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverNamedArgSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverNamedArgSuite.scala @@ -29,7 +29,6 @@ class HoverNamedArgSuite extends BaseHoverSuite: """|```scala |named: Int |``` - |Found documentation for a/b.foo().(named) |""".stripMargin ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala index ec1431187e56..8ed8c022cc3d 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala @@ -717,3 +717,30 @@ class HoverTermSuite extends BaseHoverSuite: |""".stripMargin, """def ???: Nothing""".stripMargin.hover ) + + @Test def `named-tuples`: Unit = + check( + """ + |val foo = (name = "Bob", age = 42, height = 1.9d) + |val foo_name = foo.na@@me + |""".stripMargin, + "name: String".hover + ) + + @Test def `named-tuples2`: Unit = + check( + """|import NamedTuple.* + | + |class NamedTupleSelectable extends Selectable { + | type Fields <: AnyNamedTuple + | def selectDynamic(name: String): Any = ??? + |} + | + |val person = new NamedTupleSelectable { + | type Fields = (name: String, city: String) + |} + | + |val person_name = person.na@@me + |""".stripMargin, + "name: String".hover + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/info/InfoSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/info/InfoSuite.scala index 8464c4d69da4..57ceb7a099d7 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/info/InfoSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/info/InfoSuite.scala @@ -8,6 +8,9 @@ import scala.meta.pc.PcSymbolInformation import dotty.tools.pc.base.BasePCSuite import scala.language.unsafeNulls import org.junit.Test +import scala.meta.internal.metals.CompilerOffsetParams +import java.nio.file.Paths +import scala.annotation.nowarn class InfoSuite extends BasePCSuite { @@ -53,4 +56,48 @@ class InfoSuite extends BasePCSuite { |scala/collection/IterableOnceOps#flatMap(). |""".stripMargin ) + + @Test def i7251 = + withSource( + """|package a + |sealed trait TA: + | type SomeType + |trait TB extends TA: + | type SomeType = Int + |""".stripMargin + ) + val info = presentationCompiler.info("a/TA#SomeType#").get() + assertNoDiff(info.get().symbol(), "a/TA#SomeType#") + + @Test def memberDefsAnnotations = + def assertMemberDefsAnnotations(symbol: String, expected: String) = + val info = presentationCompiler.info(symbol).get() + assertNoDiff(info.get().memberDefsAnnotations().asScala.mkString("\n"), expected, Some(symbol)) + withSource( + """|package a + |import scala.annotation.nowarn + |sealed trait TA: + | @nowarn + | def aaa = 1 + | + |object O: + | @nowarn + | def aaa = 1 + | + |class D: + | @nowarn + | def bbb = 1 + |""".stripMargin + ) + assertMemberDefsAnnotations("a/TA#", "scala.annotation.nowarn") + assertMemberDefsAnnotations("a/O.", "scala.annotation.nowarn") + assertMemberDefsAnnotations("a/D#", "scala.annotation.nowarn") + assertMemberDefsAnnotations("a/D#bbb().", "") + + // hacky way to add a source file to the presentation compiler sources + private def withSource(code: String) = + val filename = "Hover.scala" + val pcParams = CompilerOffsetParams(Paths.get(filename).toUri(), code, 0) + presentationCompiler.hover(pcParams).get() + } diff --git a/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala index fac30bc757b7..84002e54dcb7 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala @@ -898,7 +898,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { | import quotes.reflect.* | Type.of[T] match | case '[f] => - | val fr/*: TypeRepr<>*/ = TypeRepr.of[T]/*(using evidence$1<<(3:23)>>)*/ + | val fr/*: TypeRepr<>*/ = TypeRepr.of[T]/*(using evidence$1<<(3:27)>>)*/ |""".stripMargin ) @@ -937,7 +937,122 @@ class InlayHintsSuite extends BaseInlayHintsSuite { | given A = A() | implicit def bar(using a: A): B[A] = B[A]() | def foo(using b: B[A]): String = "aaa" - | val g: String = foo/*(using bar<<(5:15)>>)*/ + | val g: String = foo/*(using bar<<(5:15)>>(given_A<<(4:8)>>))*/ |""".stripMargin ) + + @Test def `multiple-params-list` = + check( + """|object Main { + | case class A() + | case class B() + | implicit val theA: A = A() + | def foo(b: B)(implicit a: A): String = "aaa" + | val g: String = foo(B()) + |} + |""".stripMargin, + """|object Main { + | case class A() + | case class B() + | implicit val theA: A = A() + | def foo(b: B)(implicit a: A): String = "aaa" + | val g: String = foo(B())/*(using theA<<(4:15)>>)*/ + |} + |""".stripMargin, + ) + + @Test def `implicit-chain` = + check( + """|object Main{ + | def hello()(implicit string: String, integer: Int, long: Long): String = { + | println(s"Hello $string, $long, $integer!") + | } + | implicit def theString(implicit i: Int): String = i.toString + | implicit def theInt(implicit l: Long): Int = l + | implicit val theLong: Long = 42 + | hello() + |} + |""".stripMargin, + """|object Main{ + | def hello()(implicit string: String, integer: Int, long: Long): String = { + | println(s"Hello $string, $long, $integer!") + | } + | implicit def theString(implicit i: Int): String = i.toString + | implicit def theInt(implicit l: Long): Int = l + | implicit val theLong: Long = 42 + | hello()/*(using theString<<(5:15)>>(theInt<<(6:15)>>(theLong<<(7:15)>>)), theInt<<(6:15)>>(theLong<<(7:15)>>), theLong<<(7:15)>>)*/ + |} + |""".stripMargin, + ) + + @Test def `implicit-parameterless-def` = + check( + """|object Main{ + | def hello()(implicit string: String, integer: Int, long: Long): String = { + | println(s"Hello $string, $long, $integer!") + | } + | implicit def theString(implicit i: Int): String = i.toString + | implicit def theInt: Int = 43 + | implicit def theLong: Long = 42 + | hello() + |} + |""".stripMargin, + """|object Main{ + | def hello()(implicit string: String, integer: Int, long: Long): String = { + | println(s"Hello $string, $long, $integer!") + | } + | implicit def theString(implicit i: Int): String = i.toString + | implicit def theInt: Int = 43 + | implicit def theLong: Long = 42 + | hello()/*(using theString<<(5:15)>>(theInt<<(6:15)>>), theInt<<(6:15)>>, theLong<<(7:15)>>)*/ + |} + |""".stripMargin, + ) + + @Test def `implicit-fn` = + check( + """|object Main{ + | implicit def stringLength(s: String): Int = s.length + | implicitly[String => Int] + | + | implicit val namedStringLength: String => Long = (s: String) => s.length.toLong + | implicitly[String => Long] + |} + |""".stripMargin, + """|object Main{ + | implicit def stringLength(s: String): Int = s.length + | implicitly[String => Int] + | + | implicit val namedStringLength: String => Long = (s: String) => s.length.toLong + | implicitly[String => Long]/*(using namedStringLength<<(5:15)>>)*/ + |} + |""".stripMargin, + ) + + @Test def `implicit-fn2` = + check( + """|object Main{ + | implicit def stringLength(s: String, i: Int): Int = s.length + | implicitly[(String, Int) => Int] + |} + |""".stripMargin, + """|object Main{ + | implicit def stringLength(s: String, i: Int): Int = s.length + | implicitly[(String, Int) => Int] + |} + |""".stripMargin, + ) + + @Test def `strip-margin` = + check( + """|object Main{ + | "".stripMargin + |} + |""".stripMargin, + """|package test + |object Main{ + | /*augmentString<>(*/""/*)*/.stripMargin + |} + |""".stripMargin + ) } diff --git a/presentation-compiler/test/dotty/tools/pc/tests/tokens/SemanticTokensSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/tokens/SemanticTokensSuite.scala index fd90a8dfaca0..200f74537591 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/tokens/SemanticTokensSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/tokens/SemanticTokensSuite.scala @@ -1,10 +1,14 @@ package dotty.tools.pc.tests.tokens import dotty.tools.pc.base.BaseSemanticTokensSuite +import java.nio.file.Path import org.junit.Test class SemanticTokensSuite extends BaseSemanticTokensSuite: + // -preview required for `for-comprehension` test + override protected def scalacOptions(classpath: Seq[Path]): Seq[String] = + super.scalacOptions(classpath) ++ Seq("-preview") @Test def `class, object, var, val(readonly), method, type, parameter, String(single-line)` = check( @@ -350,9 +354,9 @@ class SemanticTokensSuite extends BaseSemanticTokensSuite: | |object <>/*class*/ { | val <>/*variable,definition,readonly*/ = for { - | <>/*variable,definition,readonly*/ <- <>/*class*/("a", "b", "c") + | <>/*parameter,declaration,readonly*/ <- <>/*class*/("a", "b", "c") | <<_>>/*class,abstract*/ = <>/*method*/("print!") - | } yield <>/*variable,readonly*/ + | } yield <>/*parameter,readonly*/ |} |""".stripMargin ) diff --git a/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala b/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala index b9d3fd411dcc..5fb38ad88e19 100644 --- a/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala +++ b/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala @@ -3,8 +3,10 @@ package dotty.tools.pc.utils import scala.collection.mutable.ListBuffer import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.internal.pc.InlayHints import dotty.tools.pc.utils.InteractiveEnrichments.* +import com.google.gson.JsonElement import org.eclipse.lsp4j.InlayHint import org.eclipse.lsp4j.TextEdit import org.eclipse.{lsp4j => l} @@ -31,7 +33,7 @@ object TestInlayHints { case Right(labelParts) => labelParts.asScala.map(_.getValue()).toList } val data = - inlayHint.getData().asInstanceOf[Array[Any]] + InlayHints.fromData(inlayHint.getData().asInstanceOf[JsonElement])._2 buffer += "/*" labels.zip(data).foreach { case (label, data) => buffer += label.nn @@ -41,15 +43,13 @@ object TestInlayHints { 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 => + private def readData(data: Either[String, l.Position]): List[String] = + data match + case Left("") => Nil + case Left(data) => List("<<", data, ">>") + case Right(data) => val str = s"(${data.getLine()}:${data.getCharacter()})" List("<<", str, ">>") - } - } def applyInlayHints(text: String, inlayHints: List[InlayHint]): String = { val textEdits = inlayHints.map { hint => diff --git a/project/Build.scala b/project/Build.scala index 60f8e4d87be1..2d9242aa5d1a 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -6,10 +6,13 @@ import ScaladocGeneration._ import com.jsuereth.sbtpgp.PgpKeys import sbt.Keys.* import sbt.* +import sbt.nio.FileStamper +import sbt.nio.Keys.* import complete.DefaultParsers._ import pl.project13.scala.sbt.JmhPlugin import pl.project13.scala.sbt.JmhPlugin.JmhKeys.Jmh import com.gradle.develocity.agent.sbt.DevelocityPlugin.autoImport._ +import com.gradle.develocity.agent.sbt.api.experimental.buildcache import com.typesafe.sbt.packager.Keys._ import com.typesafe.sbt.packager.MappingsHelper.directory import com.typesafe.sbt.packager.universal.UniversalPlugin @@ -20,6 +23,8 @@ import sbt.Package.ManifestAttributes import sbt.PublishBinPlugin.autoImport._ import dotty.tools.sbtplugin.RepublishPlugin import dotty.tools.sbtplugin.RepublishPlugin.autoImport._ +import dotty.tools.sbtplugin.ScalaLibraryPlugin + import sbt.plugins.SbtPlugin import sbt.ScriptedPlugin.autoImport._ import xerial.sbt.Sonatype.autoImport._ @@ -98,7 +103,7 @@ object Build { * * Warning: Change of this variable needs to be consulted with `expectedTastyVersion` */ - val referenceVersion = "3.6.3" + val referenceVersion = "3.6.4" /** Version of the Scala compiler targeted in the current release cycle * Contains a version without RC/SNAPSHOT/NIGHTLY specific suffixes @@ -109,7 +114,7 @@ object Build { * * Warning: Change of this variable might require updating `expectedTastyVersion` */ - val developedVersion = "3.6.4" + val developedVersion = "3.7.0" /** The version of the compiler including the RC prefix. * Defined as common base before calculating environment specific suffixes in `dottyVersion` @@ -136,7 +141,7 @@ object Build { * - in release candidate branch is experimental if {patch == 0} * - in stable release is always non-experimetnal */ - val expectedTastyVersion = "28.6" + val expectedTastyVersion = "28.7" checkReleasedTastyVersion() /** Final version of Scala compiler, controlled by environment variables. */ @@ -184,9 +189,9 @@ object Build { val mimaPreviousLTSDottyVersion = "3.3.0" /** Version of Scala CLI to download */ - val scalaCliLauncherVersion = "1.5.4" + val scalaCliLauncherVersion = "1.7.1" /** Version of Coursier to download for initializing the local maven repo of Scala command */ - val coursierJarVersion = "2.1.18" + val coursierJarVersion = "2.1.24" object CompatMode { final val BinaryCompatible = 0 @@ -208,8 +213,8 @@ object Build { * scala-library. */ def stdlibVersion(implicit mode: Mode): String = mode match { - case NonBootstrapped => "2.13.15" - case Bootstrapped => "2.13.15" + case NonBootstrapped => "2.13.16" + case Bootstrapped => "2.13.16" } /** Version of the scala-library for which we will generate TASTy. @@ -219,7 +224,7 @@ object Build { * We can use nightly versions to tests the future compatibility in development. * Nightly versions: https://scala-ci.typesafe.com/ui/native/scala-integration/org/scala-lang */ - val stdlibBootstrappedVersion = "2.13.15" + val stdlibBootstrappedVersion = "2.13.16" val dottyOrganization = "org.scala-lang" val dottyGithubUrl = "https://github.com/scala/scala3" @@ -278,6 +283,8 @@ object Build { val fetchScalaJSSource = taskKey[File]("Fetch the sources of Scala.js") + val extraDevelocityCacheInputFiles = taskKey[Seq[Path]]("Extra input files for caching") + lazy val SourceDeps = config("sourcedeps") // Settings shared by the build (scoped in ThisBuild). Used in build.sbt @@ -292,7 +299,9 @@ object Build { "-deprecation", "-unchecked", //"-Wconf:cat=deprecation&msg=Unsafe:s", // example usage - "-Xfatal-warnings", // -Werror in modern usage + "-Werror", + //"-Wunused:all", + //"-rewrite", // requires -Werror:false since no rewrites are applied with errors "-encoding", "UTF8", "-language:implicitConversions", ), @@ -337,24 +346,30 @@ object Build { buildScan .withPublishing(Publishing.onlyIf(_.authenticated)) .withBackgroundUpload(!isInsideCI) - .tag(if (isInsideCI) "CI" else "Local") + .withTag(if (isInsideCI) "CI" else "Local") .withLinks(buildScan.links ++ GithubEnv.develocityLinks) .withValues(buildScan.values ++ GithubEnv.develocityValues) .withObfuscation(buildScan.obfuscation.withIpAddresses(_.map(_ => "0.0.0.0"))) ) .withBuildCache( buildCache - .withLocal(buildCache.local.withEnabled(false)) - .withRemote(buildCache.remote.withEnabled(false)) + .withLocal(buildCache.local.withEnabled(true).withStoreEnabled(true)) + .withRemote(buildCache.remote.withEnabled(true).withStoreEnabled(isInsideCI)) + .withRequireClean(!isInsideCI) ) - .withTestRetryConfiguration( - config.testRetryConfiguration + .withTestRetry( + config.testRetry .withFlakyTestPolicy(FlakyTestPolicy.Fail) .withMaxRetries(if (isInsideCI) 1 else 0) .withMaxFailures(10) .withClassesFilter((className, _) => !noRetryTestClasses.contains(className)) ) - } + }, + // Deactivate Develocity's test caching because it caches all tests or nothing. + // Also at the moment, it does not take compilation files as inputs. + Test / develocityBuildCacheClient := None, + extraDevelocityCacheInputFiles := Seq.empty, + extraDevelocityCacheInputFiles / outputFileStamper := FileStamper.Hash, ) // Settings shared globally (scoped in Global). Used in build.sbt @@ -435,7 +450,17 @@ object Build { Compile / packageBin / packageOptions += Package.ManifestAttributes( "Automatic-Module-Name" -> s"${dottyOrganization.replaceAll("-",".")}.${moduleName.value.replaceAll("-",".")}" - ) + ), + + // add extraDevelocityCacheInputFiles in cache key components + Compile / compile / buildcache.develocityTaskCacheKeyComponents += + (Compile / extraDevelocityCacheInputFiles / outputFileStamps).taskValue, + Test / test / buildcache.develocityTaskCacheKeyComponents += + (Test / extraDevelocityCacheInputFiles / outputFileStamps).taskValue, + Test / testOnly / buildcache.develocityInputTaskCacheKeyComponents += + (Test / extraDevelocityCacheInputFiles / outputFileStamps).taskValue, + Test / testQuick / buildcache.develocityInputTaskCacheKeyComponents += + (Test / extraDevelocityCacheInputFiles / outputFileStamps).taskValue ) // Settings used for projects compiled only with Java @@ -602,7 +627,10 @@ object Build { assert(docScalaInstance.loaderCompilerOnly == base.loaderCompilerOnly) docScalaInstance }, - Compile / doc / scalacOptions ++= scalacOptionsDocSettings() + Compile / doc / scalacOptions ++= scalacOptionsDocSettings(), + // force recompilation of bootstrapped modules when the compiler changes + Compile / extraDevelocityCacheInputFiles ++= + (`scala3-compiler` / Compile / fullClasspathAsJars).value.map(_.data.toPath) ) lazy val commonBenchmarkSettings = Seq( @@ -739,6 +767,13 @@ object Build { // Use source 3.3 to avoid fatal migration warnings on scalajs-ir scalacOptions ++= Seq("-source", "3.3"), + /* Ignore a deprecation warning about AnyRefMap in scalajs-ir. The latter + * cross-compiles for 2.12, and therefore AnyRefMap remains useful there + * for performance reasons. + * The build of Scala.js core does the same thing. + */ + scalacOptions += "-Wconf:cat=deprecation&origin=scala\\.collection\\.mutable\\.AnyRefMap.*:s", + // Generate compiler.properties, used by sbt (Compile / resourceGenerators) += Def.task { import java.util._ @@ -764,9 +799,9 @@ object Build { libraryDependencies ++= Seq( "org.scala-lang.modules" % "scala-asm" % "9.7.1-scala-1", // used by the backend Dependencies.compilerInterface, - "org.jline" % "jline-reader" % "3.27.1", // used by the REPL - "org.jline" % "jline-terminal" % "3.27.1", - "org.jline" % "jline-terminal-jni" % "3.27.1", // needed for Windows + "org.jline" % "jline-reader" % "3.29.0", // used by the REPL + "org.jline" % "jline-terminal" % "3.29.0", + "org.jline" % "jline-terminal-jni" % "3.29.0", // needed for Windows ("io.get-coursier" %% "coursier" % "2.0.16" % Test).cross(CrossVersion.for3Use2_13), ), @@ -1006,10 +1041,6 @@ object Build { sjsSources } (Set(scalaJSIRSourcesJar)).toSeq }.taskValue, - - // Develocity's Build Cache does not work with our compilation tests - // at the moment: it does not take compilation files as inputs. - Test / develocityBuildCacheClient := None, ) def insertClasspathInArgs(args: List[String], cp: String): List[String] = { @@ -1059,7 +1090,6 @@ object Build { // compiler is updated. // Then, the next step is to enable flexible types by default and reduce the use of // `unsafeNulls`. - scalacOptions ++= Seq("-Yno-flexible-types"), packageAll := { (`scala3-compiler` / packageAll).value ++ Seq( "scala3-compiler" -> (Compile / packageBin).value.getAbsolutePath, @@ -1104,10 +1134,12 @@ object Build { } // Settings shared between scala3-library, scala3-library-bootstrapped and scala3-library-bootstrappedJS - lazy val dottyLibrarySettings = Seq( + def dottyLibrarySettings(implicit mode: Mode) = Seq( + versionScheme := Some("semver-spec"), + libraryDependencies += "org.scala-lang" % "scala-library" % stdlibVersion, (Compile / scalacOptions) ++= Seq( // Needed so that the library sources are visible when `dotty.tools.dotc.core.Definitions#init` is called - "-sourcepath", (Compile / sourceDirectories).value.map(_.getAbsolutePath).distinct.mkString(File.pathSeparator), + "-sourcepath", (Compile / sourceDirectories).value.map(_.getCanonicalPath).distinct.mkString(File.pathSeparator), "-Yexplicit-nulls", ), (Compile / doc / scalacOptions) ++= ScaladocConfigs.DefaultGenerationSettings.value.settings, @@ -1197,9 +1229,9 @@ object Build { * This version of the library is not (yet) TASTy/binary compatible with the Scala 2 compiled library. */ lazy val `scala2-library-bootstrapped` = project.in(file("scala2-library-bootstrapped")). + enablePlugins(ScalaLibraryPlugin). withCommonSettings(Bootstrapped). dependsOn(dottyCompiler(Bootstrapped) % "provided; compile->runtime; test->test"). - settings(commonBootstrappedSettings). settings(scala2LibraryBootstrappedSettings). settings(moduleName := "scala2-library") // -Ycheck:all is set in project/scripts/scala2-library-tasty-mima.sh @@ -1211,7 +1243,6 @@ object Build { lazy val `scala2-library-cc` = project.in(file("scala2-library-cc")). withCommonSettings(Bootstrapped). dependsOn(dottyCompiler(Bootstrapped) % "provided; compile->runtime; test->test"). - settings(commonBootstrappedSettings). settings(scala2LibraryBootstrappedSettings). settings( moduleName := "scala2-library-cc", @@ -1225,8 +1256,8 @@ object Build { }, Compile / doc / scalacOptions += "-Ydocument-synthetic-types", scalacOptions += "-Ycompile-scala2-library", - scalacOptions += "-Yscala2Unpickler:never", - scalacOptions -= "-Xfatal-warnings", + scalacOptions += "-Yscala2-unpickler:never", + scalacOptions += "-Werror:false", Compile / compile / logLevel.withRank(KeyRanks.Invisible) := Level.Error, ivyConfigurations += SourceDeps.hide, transitiveClassifiers := Seq("sources"), @@ -1451,10 +1482,6 @@ object Build { .dependsOn(`scala3-compiler-bootstrapped`, `scala3-library-bootstrapped`, `scala3-presentation-compiler-testcases` % "test->test") .settings(presentationCompilerSettings) .settings(scala3PresentationCompilerBuildInfo) - .settings( - // Add `-Yno-flexible-types` flag for bootstrap, see comments for `bootstrappedDottyCompilerSettings` - Compile / scalacOptions += "-Yno-flexible-types" - ) def scala3PresentationCompilerBuildInfo = Seq( @@ -1479,7 +1506,7 @@ object Build { BuildInfoPlugin.buildInfoDefaultSettings lazy val presentationCompilerSettings = { - val mtagsVersion = "1.4.1" + val mtagsVersion = "1.5.1" Seq( libraryDependencies ++= Seq( "org.lz4" % "lz4-java" % "1.8.0", @@ -1489,7 +1516,7 @@ object Build { .exclude("org.eclipse.lsp4j","org.eclipse.lsp4j.jsonrpc"), "org.eclipse.lsp4j" % "org.eclipse.lsp4j" % "0.20.1", ), - libraryDependencies += ("org.scalameta" % "mtags-shared_2.13.15" % mtagsVersion % SourceDeps), + libraryDependencies += ("org.scalameta" % "mtags-shared_2.13.16" % mtagsVersion % SourceDeps), ivyConfigurations += SourceDeps.hide, transitiveClassifiers := Seq("sources"), scalacOptions ++= Seq("-source", "3.3"), // To avoid fatal migration warnings @@ -1597,7 +1624,7 @@ object Build { dependsOn(`scala3-library-bootstrappedJS`). settings( bspEnabled := false, - scalacOptions --= Seq("-Xfatal-warnings", "-deprecation"), + scalacOptions --= Seq("-Werror", "-deprecation"), // Required to run Scala.js tests. Test / fork := false, @@ -1665,10 +1692,10 @@ object Build { "compliantNullPointers" -> (sems.nullPointers == CheckedBehavior.Compliant), "compliantStringIndexOutOfBounds" -> (sems.stringIndexOutOfBounds == CheckedBehavior.Compliant), "compliantModuleInit" -> (sems.moduleInit == CheckedBehavior.Compliant), - "strictFloats" -> sems.strictFloats, "productionMode" -> sems.productionMode, "esVersion" -> linkerConfig.esFeatures.esVersion.edition, "useECMAScript2015Semantics" -> linkerConfig.esFeatures.useECMAScript2015Semantics, + "isWebAssembly" -> linkerConfig.experimentalUseWebAssembly, ) }.taskValue, @@ -1709,7 +1736,7 @@ object Build { ) }, - // A first blacklist of tests for those that do not compile or do not link + // A first excludelist of tests for those that do not compile or do not link (Test / managedSources) ++= { val dir = fetchScalaJSSource.value / "test-suite" @@ -2415,13 +2442,9 @@ object Build { settings(dottyCompilerSettings) def asDottyLibrary(implicit mode: Mode): Project = { - val base = - project.withCommonSettings. - settings( - versionScheme := Some("semver-spec"), - libraryDependencies += "org.scala-lang" % "scala-library" % stdlibVersion, - ). - settings(dottyLibrarySettings) + val base = project + .withCommonSettings + .settings(dottyLibrarySettings) if (mode == Bootstrapped) { base.settings( (Compile/doc) := { @@ -2505,6 +2528,7 @@ object Build { case NonBootstrapped => commonNonBootstrappedSettings case Bootstrapped => commonBootstrappedSettings }) + } /* Tests TASTy version invariants during NIGHLY, RC or Stable releases */ diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 427b53e45221..22871db0570a 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -28,5 +28,5 @@ object Dependencies { "com.vladsch.flexmark" % "flexmark-ext-yaml-front-matter" % flexmarkVersion, ) - val compilerInterface = "org.scala-sbt" % "compiler-interface" % "1.10.4" + val compilerInterface = "org.scala-sbt" % "compiler-interface" % "1.10.7" } diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 00e7153bcb83..3e6a36f62819 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -13,6 +13,13 @@ object MiMaFilters { ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.quotedPatternsWithPolymorphicFunctions"), ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$quotedPatternsWithPolymorphicFunctions$"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.Patterns.higherOrderHoleWithTypes"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.preview"), + ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.packageObjectValues"), + ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$packageObjectValues$"), + + // Scala.js-only class + ProblemFilters.exclude[FinalClassProblem]("scala.scalajs.runtime.AnonFunctionXXL"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.scalajs.runtime.AnonFunctionXXL.this"), ), // Additions since last LTS @@ -86,6 +93,13 @@ object MiMaFilters { // Breaking changes since last reference version Build.mimaPreviousDottyVersion -> // Seq.empty, // We should never break backwards compatibility Seq( + // Scala.js-only class, which is subject to IR deserializatiation hacks to preserve bincompat. + // It's OK. Scala.js did the same: + // https://github.com/scala-js/scala-js/blob/v1.19.0/project/BinaryIncompatibilities.scala#L66-L71 + ProblemFilters.exclude[AbstractClassProblem]("scala.scalajs.runtime.AnonFunctionXXL"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.scalajs.runtime.AnonFunctionXXL.this"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.scalajs.runtime.AnonFunctionXXL.apply"), + // `ReversedMissingMethodProblem`s are acceptable. See comment in `Breaking changes since last LTS`. ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.FlexibleType"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.FlexibleTypeTypeTest"), @@ -95,6 +109,7 @@ object MiMaFilters { ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeModule.apply"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeMethods.methodTypeKind"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeMethods.isContextual"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ImplicitsModule.searchIgnoring"), // Change `experimental` annotation to a final class ProblemFilters.exclude[FinalClassProblem]("scala.annotation.experimental"), ), diff --git a/project/Scala2LibraryBootstrappedMiMaFilters.scala b/project/Scala2LibraryBootstrappedMiMaFilters.scala index 102a2a50e9d4..dd0a885731b2 100644 --- a/project/Scala2LibraryBootstrappedMiMaFilters.scala +++ b/project/Scala2LibraryBootstrappedMiMaFilters.scala @@ -4,82 +4,44 @@ import com.typesafe.tools.mima.core._ object Scala2LibraryBootstrappedMiMaFilters { val BackwardsBreakingChanges: Map[String, Seq[ProblemFilter]] = Map( - Build.stdlibBootstrappedVersion -> { - Seq( - // Files that are not compiled in the bootstrapped library - ProblemFilters.exclude[MissingClassProblem]("scala.AnyVal"), - - // Scala language features - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.language."), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.language#experimental."), - ProblemFilters.exclude[FinalClassProblem]("scala.language$experimental$"), - ProblemFilters.exclude[FinalClassProblem]("scala.languageFeature$*$"), - - // trait $init$ - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.*.$init$"), - - // Value class extension methods - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.*$extension"), - - // Companion module class - ProblemFilters.exclude[FinalClassProblem]("scala.*$"), - - // Scala 2 intrinsic macros - ProblemFilters.exclude[FinalMethodProblem]("scala.StringContext.s"), - - // Specialization? - ProblemFilters.exclude[MissingFieldProblem]("scala.Tuple1._1"), // field _1 in class scala.Tuple1 does not have a correspondent in current version - ProblemFilters.exclude[MissingFieldProblem]("scala.Tuple2._1"), // field _1 in class scala.Tuple2 does not have a correspondent in current version - ProblemFilters.exclude[MissingFieldProblem]("scala.Tuple2._2"), // field _2 in class scala.Tuple2 does not have a correspondent in current version - - // Scala 2 specialization - ProblemFilters.exclude[MissingClassProblem]("scala.*$sp"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.*$sp"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.*#*#sp.$init$"), - ProblemFilters.exclude[MissingTypesProblem]("scala.collection.DoubleStepper"), - ProblemFilters.exclude[MissingTypesProblem]("scala.collection.immutable.DoubleVectorStepper"), - ProblemFilters.exclude[MissingTypesProblem]("scala.collection.immutable.IntVectorStepper"), - ProblemFilters.exclude[MissingTypesProblem]("scala.collection.immutable.LongVectorStepper"), - ProblemFilters.exclude[MissingTypesProblem]("scala.collection.IntStepper"), - ProblemFilters.exclude[MissingTypesProblem]("scala.collection.LongStepper"), - ProblemFilters.exclude[MissingTypesProblem]("scala.jdk.DoubleAccumulator"), - ProblemFilters.exclude[MissingTypesProblem]("scala.jdk.FunctionWrappers$*"), - ProblemFilters.exclude[MissingTypesProblem]("scala.jdk.IntAccumulator"), - ProblemFilters.exclude[MissingTypesProblem]("scala.jdk.LongAccumulator"), - ProblemFilters.exclude[FinalClassProblem]("scala.collection.ArrayOps$ReverseIterator"), - ProblemFilters.exclude[FinalClassProblem]("scala.Tuple1"), - ProblemFilters.exclude[FinalClassProblem]("scala.Tuple2"), - - // other - ProblemFilters.exclude[FinalMethodProblem]("scala.Enumeration.ValueOrdering"), - ProblemFilters.exclude[FinalMethodProblem]("scala.Enumeration.ValueSet"), - ProblemFilters.exclude[FinalMethodProblem]("scala.io.Source.NoPositioner"), - ProblemFilters.exclude[FinalMethodProblem]("scala.io.Source.RelaxedPosition"), - ProblemFilters.exclude[FinalMethodProblem]("scala.io.Source.RelaxedPositioner"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.immutable.SortedMapOps.coll"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.immutable.TreeMap.empty"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.immutable.TreeMap.fromSpecific"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.mutable.ArrayBuilder#ofUnit.addAll"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.mutable.TreeMap.empty"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.mutable.TreeMap.fromSpecific"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.reflect.ManifestFactory#NothingManifest.newArray"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.reflect.ManifestFactory#NullManifest.newArray"), - ProblemFilters.exclude[MissingFieldProblem]("scala.collection.ArrayOps#ReverseIterator.xs"), - ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.NonLocalReturnControl.value"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.collection.immutable.SortedMapOps.coll"), - ) ++ - Seq( // DirectMissingMethodProblem - "scala.collection.LinearSeqIterator#LazyCell.this", - "scala.collection.mutable.PriorityQueue#ResizableArrayAccess.this", - "scala.concurrent.BatchingExecutor#AbstractBatch.this", - "scala.concurrent.Channel#LinkedList.this", - "scala.Enumeration#ValueOrdering.this", - "scala.io.Source#RelaxedPosition.this", - "scala.collection.IterableOnceOps#Maximized.this", // New in 2.13.11: private inner class - "scala.util.Properties.", - "scala.util.Sorting.scala$util$Sorting$$mergeSort$default$5", - ).map(ProblemFilters.exclude[DirectMissingMethodProblem]) - } + Build.stdlibBootstrappedVersion -> Seq( + // Scala language features (not really a problem) + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.language."), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.language#experimental."), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.util.Properties."), + + // Companion module class (not really a problem) + ProblemFilters.exclude[FinalClassProblem]("scala.*$"), + ProblemFilters.exclude[FinalMethodProblem]("scala.io.Source.NoPositioner"), + ProblemFilters.exclude[FinalMethodProblem]("scala.io.Source.RelaxedPosition"), + ProblemFilters.exclude[FinalMethodProblem]("scala.io.Source.RelaxedPositioner"), + ProblemFilters.exclude[FinalMethodProblem]("scala.Enumeration.ValueOrdering"), + ProblemFilters.exclude[FinalMethodProblem]("scala.Enumeration.ValueSet"), + ProblemFilters.exclude[FinalMethodProblem]("scala.StringContext.s"), + + // Issue: https://github.com/scala/scala3/issues/22495 + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.ArrayOps.scala$collection$ArrayOps$$elemTag$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.ArrayOps.iterateUntilEmpty$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.StringOps.isLineBreak$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.StringOps.isLineBreak2$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.StringOps.linesSeparated$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.StringOps.escape$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.StringOps.toBooleanImpl$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.StringOps.unwrapArg$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.StringOps.iterateUntilEmpty$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuple2Zipped.coll1$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuple2Zipped.coll2$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuple3Zipped.coll1$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuple3Zipped.coll2$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuple3Zipped.coll3$extension"), + + // Issue: Scala 3 doesn't always outer pointers (not really a problem here) + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.IterableOnceOps#Maximized.this"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.LinearSeqIterator#LazyCell.this"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.PriorityQueue#ResizableArrayAccess.this"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.concurrent.BatchingExecutor#AbstractBatch.this"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.concurrent.Channel#LinkedList.this"), + ) ) val ForwardsBreakingChanges: Map[String, Seq[ProblemFilter]] = Map( diff --git a/project/ScalaLibraryPlugin.scala b/project/ScalaLibraryPlugin.scala new file mode 100644 index 000000000000..2eac7271644a --- /dev/null +++ b/project/ScalaLibraryPlugin.scala @@ -0,0 +1,108 @@ +package dotty.tools.sbtplugin + +import sbt.* +import sbt.Keys.* +import scala.jdk.CollectionConverters.* +import java.nio.file.Files + +object ScalaLibraryPlugin extends AutoPlugin { + + override def trigger = noTrigger + + val fetchScala2ClassFiles = taskKey[(Set[File], File)]("Fetch the files to use that were compiled with Scala 2") + //val scala2LibraryVersion = settingKey[String]("Version of the Scala 2 Standard Library") + + override def projectSettings = Seq ( + fetchScala2ClassFiles := { + val stream = streams.value + val cache = stream.cacheDirectory + val target = cache / "scala-library-classes" + val report = update.value + + val scalaLibraryBinaryJar = report.select( + configuration = configurationFilter(), + module = (_: ModuleID).name == "scala-library", + artifact = artifactFilter(`type` = "jar")).headOption.getOrElse { + sys.error(s"Could not fetch scala-library binary JAR") + } + + if (!target.exists()) { + IO.createDirectory(target) + } + + (FileFunction.cached(cache / "fetch-scala-library-classes", FilesInfo.lastModified, FilesInfo.exists) { _ => + stream.log.info(s"Unpacking scala-library binaries to persistent directory: ${target.getAbsolutePath}") + IO.unzip(scalaLibraryBinaryJar, target) + (target ** "*.class").get.toSet + } (Set(scalaLibraryBinaryJar)), target) + + }, + (Compile / compile) := { + val stream = streams.value + val target = (Compile / classDirectory).value + val (files, reference) = fetchScala2ClassFiles.value; + val analysis = (Compile / compile).value + stream.log.info(s"Copying files from Scala 2 Standard Library to $target") + for (file <- files; id <- file.relativeTo(reference).map(_.toString())) { + if (filesToCopy(id)) { + stream.log.debug(s"Copying file '${id}' to ${target / id}") + IO.copyFile(file, target / id) + } + } + + val overwrittenBinaries = Files.walk((Compile / classDirectory).value.toPath()) + .iterator() + .asScala + .map(_.toFile) + .map(_.relativeTo((Compile / classDirectory).value).get) + .toSet + val diff = files.filterNot(_.relativeTo(reference).exists(overwrittenBinaries)) + + IO.copy(diff.map { file => + file -> (Compile / classDirectory).value / file.relativeTo(reference).get.getPath + }) + + analysis + } + ) + + private lazy val filesToCopy = Set( + "scala/Tuple1.class", + "scala/Tuple2.class", + "scala/collection/DoubleStepper.class", + "scala/collection/IntStepper.class", + "scala/collection/LongStepper.class", + "scala/collection/immutable/DoubleVectorStepper.class", + "scala/collection/immutable/IntVectorStepper.class", + "scala/collection/immutable/LongVectorStepper.class", + "scala/jdk/DoubleAccumulator.class", + "scala/jdk/IntAccumulator.class", + "scala/jdk/LongAccumulator.class", + "scala/jdk/FunctionWrappers$FromJavaDoubleBinaryOperator.class", + "scala/jdk/FunctionWrappers$FromJavaBooleanSupplier.class", + "scala/jdk/FunctionWrappers$FromJavaDoubleConsumer.class", + "scala/jdk/FunctionWrappers$FromJavaDoublePredicate.class", + "scala/jdk/FunctionWrappers$FromJavaDoubleSupplier.class", + "scala/jdk/FunctionWrappers$FromJavaDoubleToIntFunction.class", + "scala/jdk/FunctionWrappers$FromJavaDoubleToLongFunction.class", + "scala/jdk/FunctionWrappers$FromJavaIntBinaryOperator.class", + "scala/jdk/FunctionWrappers$FromJavaDoubleUnaryOperator.class", + "scala/jdk/FunctionWrappers$FromJavaIntPredicate.class", + "scala/jdk/FunctionWrappers$FromJavaIntConsumer.class", + "scala/jdk/FunctionWrappers$FromJavaIntSupplier.class", + "scala/jdk/FunctionWrappers$FromJavaIntToDoubleFunction.class", + "scala/jdk/FunctionWrappers$FromJavaIntToLongFunction.class", + "scala/jdk/FunctionWrappers$FromJavaIntUnaryOperator.class", + "scala/jdk/FunctionWrappers$FromJavaLongBinaryOperator.class", + "scala/jdk/FunctionWrappers$FromJavaLongConsumer.class", + "scala/jdk/FunctionWrappers$FromJavaLongPredicate.class", + "scala/jdk/FunctionWrappers$FromJavaLongSupplier.class", + "scala/jdk/FunctionWrappers$FromJavaLongToDoubleFunction.class", + "scala/jdk/FunctionWrappers$FromJavaLongToIntFunction.class", + "scala/jdk/FunctionWrappers$FromJavaLongUnaryOperator.class", + "scala/collection/ArrayOps$ReverseIterator.class", + "scala/runtime/NonLocalReturnControl.class", + "scala/util/Sorting.class", "scala/util/Sorting$.class", // Contains @specialized annotation + ) + +} diff --git a/project/build.properties b/project/build.properties index db1723b08622..73df629ac1a7 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.10.5 +sbt.version=1.10.7 diff --git a/project/plugins.sbt b/project/plugins.sbt index 21d8826b6b24..da70069d8290 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -6,7 +6,7 @@ libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.19.0") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.21") @@ -22,4 +22,7 @@ addSbtPlugin("ch.epfl.scala" % "sbt-tasty-mima" % "1.0.0") addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.0") -addSbtPlugin("com.gradle" % "sbt-develocity" % "1.1.1") +resolvers += "Develocity Artifactory" at "https://repo.grdev.net/artifactory/public/" +addSbtPlugin("com.gradle" % "sbt-develocity" % "1.2-rc-2") + +addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") diff --git a/project/scripts/cmdScaladocTests b/project/scripts/cmdScaladocTests index b54789032ad2..e53ffd87ebff 100755 --- a/project/scripts/cmdScaladocTests +++ b/project/scripts/cmdScaladocTests @@ -16,7 +16,7 @@ DOTTY_NONBOOTSTRAPPED_VERSION=$(eval $DOTTY_NONBOOTSTRAPPED_VERSION_COMMAND | ta DOTTY_BOOTSTRAPPED_VERSION_COMMAND="$SBT \"eval println(Build.dottyVersion)\"" DOTTY_BOOTSTRAPPED_VERSION=$(eval $DOTTY_BOOTSTRAPPED_VERSION_COMMAND | tail -n 2 | head -n 1) -SOURCE_LINKS_REPOSITORY="scala/scala3" +SOURCE_LINKS_REPOSITORY="${GITHUB_REPOSITORY:-scala/scala3}" SOURCE_LINKS_VERSION="${GITHUB_SHA:-$DOTTY_BOOTSTRAPPED_VERSION}" "$SBT" "scaladoc/generateTestcasesDocumentation" > "$tmp" 2>&1 || echo "generated testcases project with sbt" diff --git a/sbt-test/scala2-compat/erasure/build.sbt b/sbt-test/scala2-compat/erasure/build.sbt index 694101e79388..f705299f549d 100644 --- a/sbt-test/scala2-compat/erasure/build.sbt +++ b/sbt-test/scala2-compat/erasure/build.sbt @@ -1,3 +1,5 @@ +ThisBuild / fork := true + lazy val scala2Lib = project.in(file("scala2Lib")) .settings( scalaVersion := sys.props("plugin.scala2Version") diff --git a/sbt-test/scala2-compat/erasure/dottyApp/Api.scala b/sbt-test/scala2-compat/erasure/dottyApp/Api.scala index 7ce908820390..154e5027d2d1 100644 --- a/sbt-test/scala2-compat/erasure/dottyApp/Api.scala +++ b/sbt-test/scala2-compat/erasure/dottyApp/Api.scala @@ -195,4 +195,10 @@ class Z { def objectARRAY_88(x: Array[Any]): Unit = {} def objectARRAY_89(x: Array[AnyRef]): Unit = {} def objectARRAY_90(x: Array[AnyVal]): Unit = {} + + def nothing$ARRAY_91(x: Array[Nothing]): Unit = {} + def null$ARRAY_92(x: Array[Null]): Unit = {} + def nothing$ARRAY_93(x: Array[_ <: Nothing]): Unit = {} + def null$ARRAY_94(x: Array[_ <: Null]): Unit = {} + } diff --git a/sbt-test/scala2-compat/erasure/dottyApp/Main.scala b/sbt-test/scala2-compat/erasure/dottyApp/Main.scala index be2c6c737316..48b9575302a0 100644 --- a/sbt-test/scala2-compat/erasure/dottyApp/Main.scala +++ b/sbt-test/scala2-compat/erasure/dottyApp/Main.scala @@ -53,10 +53,10 @@ object Main { z.c_40(dummy) z.c_41(dummy) z.c_42(dummy) - z.b_43(dummy) + //z.b_43(dummy) z.c_44(dummy) z.c_45(dummy) - z.b_46(dummy) + //z.b_46(dummy) z.c_47(dummy) // z.a_48(dummy) // z.c_49(dummy) @@ -101,6 +101,10 @@ object Main { z.objectARRAY_88(dummy) z.objectARRAY_89(dummy) z.objectARRAY_90(dummy) + z.objectARRAY_91(dummy) + z.objectARRAY_92(dummy) + z.objectARRAY_93(dummy) + z.objectARRAY_94(dummy) val methods = classOf[scala2Lib.Z].getDeclaredMethods.toList ++ classOf[dottyApp.Z].getDeclaredMethods.toList methods.foreach { m => diff --git a/sbt-test/scala2-compat/erasure/scala2Lib/Api.scala b/sbt-test/scala2-compat/erasure/scala2Lib/Api.scala index 2578f0556ecb..14a96b8e4004 100644 --- a/sbt-test/scala2-compat/erasure/scala2Lib/Api.scala +++ b/sbt-test/scala2-compat/erasure/scala2Lib/Api.scala @@ -186,4 +186,10 @@ class Z { def objectARRAY_88(x: Array[Any]): Unit = {} def objectARRAY_89(x: Array[AnyRef]): Unit = {} def objectARRAY_90(x: Array[AnyVal]): Unit = {} + + def objectARRAY_91(x: Array[Nothing]): Unit = {} + def objectARRAY_92(x: Array[Null]): Unit = {} + def objectARRAY_93(x: Array[_ <: Nothing]): Unit = {} + def objectARRAY_94(x: Array[_ <: Null]): Unit = {} + } diff --git a/sbt-test/scalajs/backward-compat/build.sbt b/sbt-test/scalajs/backward-compat/build.sbt new file mode 100644 index 000000000000..3f82b8529b16 --- /dev/null +++ b/sbt-test/scalajs/backward-compat/build.sbt @@ -0,0 +1,5 @@ +enablePlugins(ScalaJSPlugin) + +scalaVersion := sys.props("plugin.scalaVersion") + +libraryDependencies += "org.scalameta" %%% "munit" % "1.1.0" diff --git a/sbt-test/scalajs/backward-compat/project/plugins.sbt b/sbt-test/scalajs/backward-compat/project/plugins.sbt new file mode 100644 index 000000000000..b30c6e8c5ec8 --- /dev/null +++ b/sbt-test/scalajs/backward-compat/project/plugins.sbt @@ -0,0 +1,2 @@ + +addSbtPlugin("org.scala-js" % "sbt-scalajs" % sys.props("plugin.scalaJSVersion")) diff --git a/sbt-test/scalajs/backward-compat/src/test/scala/NewArrayIssue22794Test.scala b/sbt-test/scalajs/backward-compat/src/test/scala/NewArrayIssue22794Test.scala new file mode 100644 index 000000000000..0385b537bfe4 --- /dev/null +++ b/sbt-test/scalajs/backward-compat/src/test/scala/NewArrayIssue22794Test.scala @@ -0,0 +1,5 @@ +class NewArrayIssue22794Test extends munit.FunSuite { + test("foo") { + assert(2 + 2 == 4) + } +} diff --git a/sbt-test/scalajs/backward-compat/test b/sbt-test/scalajs/backward-compat/test new file mode 100644 index 000000000000..dfffb838b4b3 --- /dev/null +++ b/sbt-test/scalajs/backward-compat/test @@ -0,0 +1 @@ +> test diff --git a/sbt-test/tasty-compat/add-param-unroll/a-changes/A.scala b/sbt-test/tasty-compat/add-param-unroll/a-changes/A.scala new file mode 100644 index 000000000000..9cbb82b5dc30 --- /dev/null +++ b/sbt-test/tasty-compat/add-param-unroll/a-changes/A.scala @@ -0,0 +1,9 @@ +package a + +import scala.annotation.unroll + +object A { + + def foo(s: String, x: Int, @unroll b: Boolean = true): String = s + x + b + +} diff --git a/sbt-test/tasty-compat/add-param-unroll/a/A.scala b/sbt-test/tasty-compat/add-param-unroll/a/A.scala new file mode 100644 index 000000000000..b2b74e32a922 --- /dev/null +++ b/sbt-test/tasty-compat/add-param-unroll/a/A.scala @@ -0,0 +1,9 @@ +package a + +import scala.annotation.unroll + +object A { + + def foo(s: String, x: Int): String = s + x + +} diff --git a/sbt-test/tasty-compat/add-param-unroll/b/B.scala b/sbt-test/tasty-compat/add-param-unroll/b/B.scala new file mode 100644 index 000000000000..6afa8e347c54 --- /dev/null +++ b/sbt-test/tasty-compat/add-param-unroll/b/B.scala @@ -0,0 +1,7 @@ +package b + +import a.* + +object B { + transparent inline def caller = A.foo("abc", 2) +} diff --git a/sbt-test/tasty-compat/add-param-unroll/build.sbt b/sbt-test/tasty-compat/add-param-unroll/build.sbt new file mode 100644 index 000000000000..7ea07075632f --- /dev/null +++ b/sbt-test/tasty-compat/add-param-unroll/build.sbt @@ -0,0 +1,36 @@ +lazy val commonSettings = Seq( + scalacOptions += "-experimental", +) + +lazy val printSettings = Seq( + scalacOptions += "-Yprint-tasty", +) + +lazy val a = project.in(file("a")) + .settings(commonSettings) + .settings( + Compile / classDirectory := (ThisBuild / baseDirectory).value / "b-input" + ) + +lazy val b = project.in(file("b")) + .settings(commonSettings) + .settings( + Compile / unmanagedClasspath += (ThisBuild / baseDirectory).value / "b-input", + Compile / classDirectory := (ThisBuild / baseDirectory).value / "c-input" + ) + +lazy val `a-changes` = project.in(file("a-changes")) + .settings(commonSettings) + .settings( + Compile / classDirectory := (ThisBuild / baseDirectory).value / "c-input" + ) + +lazy val c = project.in(file("c")) + .settings(commonSettings) + .settings(printSettings) + .settings( + // scalacOptions ++= Seq("-from-tasty", "-Ycheck:readTasty", "-Xfatal-warnings", "-Xprint:readTasty", "-Xprint-types"), + // Compile / sources := Seq(new java.io.File("c-input/B.tasty")), + Compile / unmanagedClasspath += (ThisBuild / baseDirectory).value / "c-input", + Compile / classDirectory := (ThisBuild / baseDirectory).value / "c-output" + ) diff --git a/sbt-test/tasty-compat/add-param-unroll/c/C.scala b/sbt-test/tasty-compat/add-param-unroll/c/C.scala new file mode 100644 index 000000000000..6ce872a8730c --- /dev/null +++ b/sbt-test/tasty-compat/add-param-unroll/c/C.scala @@ -0,0 +1,5 @@ +import b.* + +object C { + val res = B.caller +} diff --git a/sbt-test/tasty-compat/add-param-unroll/project/DottyInjectedPlugin.scala b/sbt-test/tasty-compat/add-param-unroll/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/tasty-compat/add-param-unroll/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +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") + ) +} diff --git a/sbt-test/tasty-compat/add-param-unroll/test b/sbt-test/tasty-compat/add-param-unroll/test new file mode 100644 index 000000000000..3fa9e731fe40 --- /dev/null +++ b/sbt-test/tasty-compat/add-param-unroll/test @@ -0,0 +1,8 @@ +# compile library A +> a/compile +# compile library B, from source, against A +> b/compile +# add a new parameter in method to library A', using @unroll to generate a forwarder +> a-changes/compile +# compile B, from tasty, against A', it should still compile: the generated forwarder is resolved. +> c/compile diff --git a/scala2-library-bootstrapped/src/scala/Enumeration.scala b/scala2-library-bootstrapped/src/scala/Enumeration.scala new file mode 100644 index 000000000000..e5609f4273c3 --- /dev/null +++ b/scala2-library-bootstrapped/src/scala/Enumeration.scala @@ -0,0 +1,356 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala + +import scala.collection.{SpecificIterableFactory, StrictOptimizedIterableOps, View, immutable, mutable} +import java.lang.reflect.{Field => JField, Method => JMethod} + +import scala.annotation.{implicitNotFound, tailrec} +import scala.reflect.NameTransformer._ +import scala.util.matching.Regex + +/** Defines a finite set of values specific to the enumeration. Typically + * these values enumerate all possible forms something can take and provide + * a lightweight alternative to case classes. + * + * Each call to a `Value` method adds a new unique value to the enumeration. + * To be accessible, these values are usually defined as `val` members of + * the enumeration. + * + * All values in an enumeration share a common, unique type defined as the + * `Value` type member of the enumeration (`Value` selected on the stable + * identifier path of the enumeration instance). + * + * Values SHOULD NOT be added to an enumeration after its construction; + * doing so makes the enumeration thread-unsafe. If values are added to an + * enumeration from multiple threads (in a non-synchronized fashion) after + * construction, the behavior of the enumeration is undefined. + * + * @example {{{ + * // Define a new enumeration with a type alias and work with the full set of enumerated values + * object WeekDay extends Enumeration { + * type WeekDay = Value + * val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value + * } + * import WeekDay._ + * + * def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun) + * + * WeekDay.values filter isWorkingDay foreach println + * // output: + * // Mon + * // Tue + * // Wed + * // Thu + * // Fri + * }}} + * + * @example {{{ + * // Example of adding attributes to an enumeration by extending the Enumeration.Val class + * object Planet extends Enumeration { + * protected case class PlanetVal(mass: Double, radius: Double) extends super.Val { + * def surfaceGravity: Double = Planet.G * mass / (radius * radius) + * def surfaceWeight(otherMass: Double): Double = otherMass * surfaceGravity + * } + * import scala.language.implicitConversions + * implicit def valueToPlanetVal(x: Value): PlanetVal = x.asInstanceOf[PlanetVal] + * + * val G: Double = 6.67300E-11 + * val Mercury = PlanetVal(3.303e+23, 2.4397e6) + * val Venus = PlanetVal(4.869e+24, 6.0518e6) + * val Earth = PlanetVal(5.976e+24, 6.37814e6) + * val Mars = PlanetVal(6.421e+23, 3.3972e6) + * val Jupiter = PlanetVal(1.9e+27, 7.1492e7) + * val Saturn = PlanetVal(5.688e+26, 6.0268e7) + * val Uranus = PlanetVal(8.686e+25, 2.5559e7) + * val Neptune = PlanetVal(1.024e+26, 2.4746e7) + * } + * + * println(Planet.values.filter(_.radius > 7.0e6)) + * // output: + * // Planet.ValueSet(Jupiter, Saturn, Uranus, Neptune) + * }}} + * + * @param initial The initial value from which to count the integers that + * identifies values at run-time. + */ +@SerialVersionUID(8476000850333817230L) +abstract class Enumeration (initial: Int) extends Serializable { + thisenum => + + def this() = this(0) + + /* Note that `readResolve` cannot be private, since otherwise + the JVM does not invoke it when deserializing subclasses. */ + protected def readResolve(): AnyRef = thisenum.getClass.getField(MODULE_INSTANCE_NAME).get(null) + + /** The name of this enumeration. + */ + override def toString: String = + ((getClass.getName stripSuffix MODULE_SUFFIX_STRING split '.').last split + Regex.quote(NAME_JOIN_STRING)).last + + /** The mapping from the integer used to identify values to the actual + * values. */ + private val vmap: mutable.Map[Int, Value] = new mutable.HashMap + + /** The cache listing all values of this enumeration. */ + @transient private var vset: ValueSet = null + @transient @volatile private var vsetDefined = false + + /** The mapping from the integer used to identify values to their + * names. */ + private[this] val nmap: mutable.Map[Int, String] = new mutable.HashMap + + /** The values of this enumeration as a set. + */ + def values: ValueSet = { + if (!vsetDefined) { + vset = (ValueSet.newBuilder ++= vmap.values).result() + vsetDefined = true + } + vset + } + + /** The integer to use to identify the next created value. */ + protected var nextId: Int = initial + + /** The string to use to name the next created value. */ + protected var nextName: Iterator[String] = _ + + private def nextNameOrNull = + if (nextName != null && nextName.hasNext) nextName.next() else null + + /** The highest integer amongst those used to identify values in this + * enumeration. */ + private[this] var topId = initial + + /** The lowest integer amongst those used to identify values in this + * enumeration, but no higher than 0. */ + private[this] var bottomId = if(initial < 0) initial else 0 + + /** The one higher than the highest integer amongst those used to identify + * values in this enumeration. */ + final def maxId = topId + + /** The value of this enumeration with given id `x` + */ + final def apply(x: Int): Value = vmap(x) + + /** Return a `Value` from this `Enumeration` whose name matches + * the argument `s`. The names are determined automatically via reflection. + * + * @param s an `Enumeration` name + * @return the `Value` of this `Enumeration` if its name matches `s` + * @throws NoSuchElementException if no `Value` with a matching + * name is in this `Enumeration` + */ + final def withName(s: String): Value = values.byName.getOrElse(s, + throw new NoSuchElementException(s"No value found for '$s'")) + + /** Creates a fresh value, part of this enumeration. */ + protected final def Value: Value = Value(nextId) + + /** Creates a fresh value, part of this enumeration, identified by the + * integer `i`. + * + * @param i An integer that identifies this value at run-time. It must be + * unique amongst all values of the enumeration. + * @return Fresh value identified by `i`. + */ + protected final def Value(i: Int): Value = Value(i, nextNameOrNull) + + /** Creates a fresh value, part of this enumeration, called `name`. + * + * @param name A human-readable name for that value. + * @return Fresh value called `name`. + */ + protected final def Value(name: String): Value = Value(nextId, name) + + /** Creates a fresh value, part of this enumeration, called `name` + * and identified by the integer `i`. + * + * @param i An integer that identifies this value at run-time. It must be + * unique amongst all values of the enumeration. + * @param name A human-readable name for that value. + * @return Fresh value with the provided identifier `i` and name `name`. + */ + protected final def Value(i: Int, name: String): Value = new Val(i, name) + + private def populateNameMap(): Unit = { + @tailrec def getFields(clazz: Class[_], acc: Array[JField]): Array[JField] = { + if (clazz == null) + acc + else + getFields(clazz.getSuperclass, if (clazz.getDeclaredFields.isEmpty) acc else acc ++ clazz.getDeclaredFields) + } + val fields = getFields(getClass.getSuperclass, getClass.getDeclaredFields) + def isValDef(m: JMethod): Boolean = fields exists (fd => fd.getName == m.getName && fd.getType == m.getReturnType) + + // The list of possible Value methods: 0-args which return a conforming type + val methods: Array[JMethod] = getClass.getMethods filter (m => m.getParameterTypes.isEmpty && + classOf[Value].isAssignableFrom(m.getReturnType) && + m.getDeclaringClass != classOf[Enumeration] && + isValDef(m)) + methods foreach { m => + val name = m.getName + // invoke method to obtain actual `Value` instance + val value = m.invoke(this).asInstanceOf[Value] + // verify that outer points to the correct Enumeration: ticket #3616. + if (value.outerEnum eq thisenum) { + val id: Int = value.id + nmap += ((id, name)) + } + } + } + + /* Obtains the name for the value with id `i`. If no name is cached + * in `nmap`, it populates `nmap` using reflection. + */ + private def nameOf(i: Int): String = synchronized { nmap.getOrElse(i, { populateNameMap() ; nmap(i) }) } + + /** The type of the enumerated values. */ + @SerialVersionUID(7091335633555234129L) + abstract class Value extends Ordered[Value] with Serializable { + /** the id and bit location of this enumeration value */ + def id: Int + /** a marker so we can tell whose values belong to whom come reflective-naming time */ + private[Enumeration] val outerEnum = thisenum + + override def compare(that: Value): Int = + if (this.id < that.id) -1 + else if (this.id == that.id) 0 + else 1 + override def equals(other: Any): Boolean = other match { + case that: Enumeration#Value => (outerEnum eq that.outerEnum) && (id == that.id) + case _ => false + } + override def hashCode: Int = id.## + + /** Create a ValueSet which contains this value and another one */ + def + (v: Value): ValueSet = ValueSet(this, v) + } + + /** A class implementing the [[scala.Enumeration.Value]] type. This class + * can be overridden to change the enumeration's naming and integer + * identification behaviour. + */ + @SerialVersionUID(0 - 3501153230598116017L) + protected class Val(i: Int, name: String) extends Value with Serializable { + def this(i: Int) = this(i, nextNameOrNull) + def this(name: String) = this(nextId, name) + def this() = this(nextId) + + assert(!vmap.isDefinedAt(i), "Duplicate id: " + i) + vmap(i) = this + vsetDefined = false + nextId = i + 1 + if (nextId > topId) topId = nextId + if (i < bottomId) bottomId = i + def id: Int = i + override def toString(): String = + if (name != null) name + else try thisenum.nameOf(i) + catch { case _: NoSuchElementException => "" } + + protected def readResolve(): AnyRef = { + val enumeration = thisenum.readResolve().asInstanceOf[Enumeration] + if (enumeration.vmap == null) this + else enumeration.vmap(i) + } + } + + /** An ordering by id for values of this set */ + implicit object ValueOrdering extends Ordering[Value] { + + // IMPORTANT: + // Scala 3 removes unnecessary outer pointers while Scala 2 doesn't + // This is important to capture the outer pointer when compiling with + // dotc to maintain our binary compatibility requirements + private val _ = Enumeration.this + def compare(x: Value, y: Value): Int = x compare y + } + + /** A class for sets of values. + * Iterating through this set will yield values in increasing order of their ids. + * + * @param nnIds The set of ids of values (adjusted so that the lowest value does + * not fall below zero), organized as a `BitSet`. + * @define Coll `collection.immutable.SortedSet` + */ + @SerialVersionUID(7229671200427364242L) + class ValueSet private[ValueSet] (private[this] var nnIds: immutable.BitSet) + extends immutable.AbstractSet[Value] + with immutable.SortedSet[Value] + with immutable.SortedSetOps[Value, immutable.SortedSet, ValueSet] + with StrictOptimizedIterableOps[Value, immutable.Set, ValueSet] + with Serializable { + + implicit def ordering: Ordering[Value] = ValueOrdering + def rangeImpl(from: Option[Value], until: Option[Value]): ValueSet = + new ValueSet(nnIds.rangeImpl(from.map(_.id - bottomId), until.map(_.id - bottomId))) + + override def empty: ValueSet = ValueSet.empty + override def knownSize: Int = nnIds.size + override def isEmpty: Boolean = nnIds.isEmpty + def contains(v: Value): Boolean = nnIds contains (v.id - bottomId) + def incl (value: Value): ValueSet = new ValueSet(nnIds + (value.id - bottomId)) + def excl (value: Value): ValueSet = new ValueSet(nnIds - (value.id - bottomId)) + def iterator: Iterator[Value] = nnIds.iterator map (id => thisenum.apply(bottomId + id)) + override def iteratorFrom(start: Value): Iterator[Value] = nnIds iteratorFrom start.id map (id => thisenum.apply(bottomId + id)) + override def className: String = s"$thisenum.ValueSet" + /** Creates a bit mask for the zero-adjusted ids in this set as a + * new array of longs */ + def toBitMask: Array[Long] = nnIds.toBitMask + + override protected def fromSpecific(coll: IterableOnce[Value]): ValueSet = ValueSet.fromSpecific(coll) + override protected def newSpecificBuilder = ValueSet.newBuilder + + def map(f: Value => Value): ValueSet = fromSpecific(new View.Map(this, f)) + def flatMap(f: Value => IterableOnce[Value]): ValueSet = fromSpecific(new View.FlatMap(this, f)) + + // necessary for disambiguation: + override def map[B](f: Value => B)(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): immutable.SortedSet[B] = + super[SortedSet].map[B](f) + override def flatMap[B](f: Value => IterableOnce[B])(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): immutable.SortedSet[B] = + super[SortedSet].flatMap[B](f) + override def zip[B](that: IterableOnce[B])(implicit @implicitNotFound(ValueSet.zipOrdMsg) ev: Ordering[(Value, B)]): immutable.SortedSet[(Value, B)] = + super[SortedSet].zip[B](that) + override def collect[B](pf: PartialFunction[Value, B])(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): immutable.SortedSet[B] = + super[SortedSet].collect[B](pf) + + @transient private[Enumeration] lazy val byName: Map[String, Value] = iterator.map( v => v.toString -> v).toMap + } + + /** A factory object for value sets */ + @SerialVersionUID(3L) + object ValueSet extends SpecificIterableFactory[Value, ValueSet] { + private final val ordMsg = "No implicit Ordering[${B}] found to build a SortedSet[${B}]. You may want to upcast to a Set[Value] first by calling `unsorted`." + private final val zipOrdMsg = "No implicit Ordering[${B}] found to build a SortedSet[(Value, ${B})]. You may want to upcast to a Set[Value] first by calling `unsorted`." + + /** The empty value set */ + val empty: ValueSet = new ValueSet(immutable.BitSet.empty) + /** A value set containing all the values for the zero-adjusted ids + * corresponding to the bits in an array */ + def fromBitMask(elems: Array[Long]): ValueSet = new ValueSet(immutable.BitSet.fromBitMask(elems)) + /** A builder object for value sets */ + def newBuilder: mutable.Builder[Value, ValueSet] = new mutable.Builder[Value, ValueSet] { + private[this] val b = new mutable.BitSet + def addOne (x: Value) = { b += (x.id - bottomId); this } + def clear() = b.clear() + def result() = new ValueSet(b.toImmutable) + } + def fromSpecific(it: IterableOnce[Value]): ValueSet = + newBuilder.addAll(it).result() + } +} diff --git a/scala2-library-bootstrapped/src/scala/io/Source.scala b/scala2-library-bootstrapped/src/scala/io/Source.scala new file mode 100644 index 000000000000..57a4053a831e --- /dev/null +++ b/scala2-library-bootstrapped/src/scala/io/Source.scala @@ -0,0 +1,388 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala +package io + +import scala.collection.{AbstractIterator, BufferedIterator} +import java.io.{Closeable, FileInputStream, FileNotFoundException, InputStream, PrintStream, File => JFile} +import java.net.{URI, URL} + +import scala.annotation.nowarn + +/** This object provides convenience methods to create an iterable + * representation of a source file. + */ +object Source { + val DefaultBufSize = 2048 + + /** Creates a `Source` from System.in. + */ + def stdin = fromInputStream(System.in) + + /** Creates a Source from an Iterable. + * + * @param iterable the Iterable + * @return the Source + */ + def fromIterable(iterable: Iterable[Char]): Source = new Source { + val iter = iterable.iterator + } withReset(() => fromIterable(iterable)) + + /** Creates a Source instance from a single character. + */ + def fromChar(c: Char): Source = fromIterable(Array(c)) + + /** creates Source from array of characters, with empty description. + */ + def fromChars(chars: Array[Char]): Source = fromIterable(chars) + + /** creates Source from a String, with no description. + */ + def fromString(s: String): Source = fromIterable(s) + + /** creates Source from file with given name, setting its description to + * filename. + */ + def fromFile(name: String)(implicit codec: Codec): BufferedSource = + fromFile(new JFile(name))(codec) + + /** creates Source from file with given name, using given encoding, setting + * its description to filename. + */ + def fromFile(name: String, enc: String): BufferedSource = + fromFile(name)(Codec(enc)) + + /** creates `source` from file with given file `URI`. + */ + def fromFile(uri: URI)(implicit codec: Codec): BufferedSource = + fromFile(new JFile(uri))(codec) + + /** creates Source from file with given file: URI + */ + def fromFile(uri: URI, enc: String): BufferedSource = + fromFile(uri)(Codec(enc)) + + /** creates Source from file, using default character encoding, setting its + * description to filename. + */ + def fromFile(file: JFile)(implicit codec: Codec): BufferedSource = + fromFile(file, Source.DefaultBufSize)(codec) + + /** same as fromFile(file, enc, Source.DefaultBufSize) + */ + def fromFile(file: JFile, enc: String): BufferedSource = + fromFile(file)(Codec(enc)) + + def fromFile(file: JFile, enc: String, bufferSize: Int): BufferedSource = + fromFile(file, bufferSize)(Codec(enc)) + + /** Creates Source from `file`, using given character encoding, setting + * its description to filename. Input is buffered in a buffer of size + * `bufferSize`. + */ + def fromFile(file: JFile, bufferSize: Int)(implicit codec: Codec): BufferedSource = { + val inputStream = new FileInputStream(file) + + createBufferedSource( + inputStream, + bufferSize, + () => fromFile(file, bufferSize)(codec), + () => inputStream.close() + )(codec) withDescription s"file:${file.getAbsolutePath}" + } + + /** Create a `Source` from array of bytes, decoding + * the bytes according to codec. + * + * @return the created `Source` instance. + */ + def fromBytes(bytes: Array[Byte])(implicit codec: Codec): Source = + fromString(new String(bytes, codec.name)) + + def fromBytes(bytes: Array[Byte], enc: String): Source = + fromBytes(bytes)(Codec(enc)) + + /** Create a `Source` from array of bytes, assuming + * one byte per character (ISO-8859-1 encoding.) + */ + @deprecated("Use `fromBytes` and specify an encoding", since="2.13.9") + def fromRawBytes(bytes: Array[Byte]): Source = + fromString(new String(bytes, Codec.ISO8859.charSet)) + + /** creates `Source` from file with given file: URI + */ + def fromURI(uri: URI)(implicit codec: Codec): BufferedSource = + fromFile(new JFile(uri))(codec) + + /** same as fromURL(new URL(s))(Codec(enc)) + */ + def fromURL(s: String, enc: String): BufferedSource = + fromURL(s)(Codec(enc)) + + /** same as fromURL(new URL(s)) + */ + def fromURL(s: String)(implicit codec: Codec): BufferedSource = + fromURL(new URI(s).toURL)(codec) + + /** same as fromInputStream(url.openStream())(Codec(enc)) + */ + def fromURL(url: URL, enc: String): BufferedSource = + fromURL(url)(Codec(enc)) + + /** same as fromInputStream(url.openStream())(codec) + */ + def fromURL(url: URL)(implicit codec: Codec): BufferedSource = + fromInputStream(url.openStream())(codec) + + /** Reads data from inputStream with a buffered reader, using the encoding + * in implicit parameter codec. + * + * @param inputStream the input stream from which to read + * @param bufferSize buffer size (defaults to Source.DefaultBufSize) + * @param reset a () => Source which resets the stream (if unset, reset() will throw an Exception) + * @param close a () => Unit method which closes the stream (if unset, close() will do nothing) + * @param codec (implicit) a scala.io.Codec specifying behavior (defaults to Codec.default) + * @return the buffered source + */ + def createBufferedSource( + inputStream: InputStream, + bufferSize: Int = DefaultBufSize, + reset: () => Source = null, + close: () => Unit = null + )(implicit codec: Codec): BufferedSource = { + // workaround for default arguments being unable to refer to other parameters + val resetFn = if (reset == null) () => createBufferedSource(inputStream, bufferSize, reset, close)(codec) else reset + + new BufferedSource(inputStream, bufferSize)(codec) withReset resetFn withClose close + } + + def fromInputStream(is: InputStream, enc: String): BufferedSource = + fromInputStream(is)(Codec(enc)) + + def fromInputStream(is: InputStream)(implicit codec: Codec): BufferedSource = + createBufferedSource(is, reset = () => fromInputStream(is)(codec), close = () => is.close())(codec) + + /** Reads data from a classpath resource, using either a context classloader (default) or a passed one. + * + * @param resource name of the resource to load from the classpath + * @param classLoader classloader to be used, or context classloader if not specified + * @return the buffered source + */ + def fromResource(resource: String, classLoader: ClassLoader = Thread.currentThread().getContextClassLoader())(implicit codec: Codec): BufferedSource = + Option(classLoader.getResourceAsStream(resource)) match { + case Some(in) => fromInputStream(in) + case None => throw new FileNotFoundException(s"resource '$resource' was not found in the classpath from the given classloader.") + } + +} + +/** An iterable representation of source data. + * It may be reset with the optional [[reset]] method. + * + * Subclasses must supply [[scala.io.Source.iter the underlying iterator]]. + * + * Error handling may be customized by overriding the [[scala.io.Source.report report]] method. + * + * The [[scala.io.Source.ch current input]] and [[scala.io.Source.pos position]], + * as well as the [[scala.io.Source.next next character]] methods delegate to + * [[scala.io.Source#Positioner the positioner]]. + * + * The default positioner encodes line and column numbers in the position passed to [[report]]. + * This behavior can be changed by supplying a + * [[scala.io.Source.withPositioning(pos:* custom positioner]]. + * + */ +abstract class Source extends Iterator[Char] with Closeable { + /** the actual iterator */ + protected val iter: Iterator[Char] + + // ------ public values + + /** description of this source, default empty */ + var descr: String = "" + var nerrors = 0 + var nwarnings = 0 + + private def lineNum(line: Int): String = (getLines() drop (line - 1) take 1).mkString + + class LineIterator extends AbstractIterator[String] with Iterator[String] { + private[this] val sb = new StringBuilder + + lazy val iter: BufferedIterator[Char] = Source.this.iter.buffered + def isNewline(ch: Char): Boolean = ch == '\r' || ch == '\n' + def getc(): Boolean = iter.hasNext && { + val ch = iter.next() + if (ch == '\n') false + else if (ch == '\r') { + if (iter.hasNext && iter.head == '\n') + iter.next() + + false + } + else { + sb append ch + true + } + } + def hasNext: Boolean = iter.hasNext + def next(): String = { + sb.clear() + while (getc()) { } + sb.toString + } + } + + /** Returns an iterator who returns lines (NOT including newline character(s)). + * It will treat any of \r\n, \r, or \n as a line separator (longest match) - if + * you need more refined behavior you can subclass Source#LineIterator directly. + */ + def getLines(): Iterator[String] = new LineIterator() + + /** Returns `'''true'''` if this source has more characters. + */ + def hasNext: Boolean = iter.hasNext + + /** Returns next character. + */ + def next(): Char = positioner.next() + + @nowarn("cat=deprecation") + class Positioner(encoder: Position) { + def this() = this(RelaxedPosition) + /** the last character returned by next. */ + var ch: Char = _ + + /** position of last character returned by next */ + var pos = 0 + + /** current line and column */ + var cline = 1 + var ccol = 1 + + /** default col increment for tabs '\t', set to 4 initially */ + var tabinc = 4 + + def next(): Char = { + ch = iter.next() + pos = encoder.encode(cline, ccol) + ch match { + case '\n' => + ccol = 1 + cline += 1 + case '\t' => + ccol += tabinc + case _ => + ccol += 1 + } + ch + } + } + /** A Position implementation which ignores errors in + * the positions. + */ + @nowarn("cat=deprecation") + object RelaxedPosition extends Position { + + // IMPORTANT: + // Scala 3 removes unnecessary outer pointers while Scala 2 doesn't + // This is important to capture the outer pointer when compiling with + // dotc to maintain our binary compatibility requirements + private val _ = Source.this + + def checkInput(line: Int, column: Int): Unit = () + } + object RelaxedPositioner extends Positioner(RelaxedPosition) { } + object NoPositioner extends Positioner(Position) { + override def next(): Char = iter.next() + } + def ch: Char = positioner.ch + def pos: Int = positioner.pos + + /** Reports an error message to the output stream `out`. + * + * @param pos the source position (line/column) + * @param msg the error message to report + * @param out PrintStream to use (optional: defaults to `Console.err`) + */ + def reportError( + pos: Int, + msg: String, + out: PrintStream = Console.err): Unit = + { + nerrors += 1 + report(pos, msg, out) + } + + private def spaces(n: Int) = List.fill(n)(' ').mkString + /** + * @param pos the source position (line/column) + * @param msg the error message to report + * @param out PrintStream to use + */ + def report(pos: Int, msg: String, out: PrintStream): Unit = { + val line = Position line pos + val col = Position column pos + + out println "%s:%d:%d: %s%s%s^".format(descr, line, col, msg, lineNum(line), spaces(col - 1)) + } + + /** + * @param pos the source position (line/column) + * @param msg the warning message to report + * @param out PrintStream to use (optional: defaults to `Console.out`) + */ + def reportWarning( + pos: Int, + msg: String, + out: PrintStream = Console.out): Unit = + { + nwarnings += 1 + report(pos, "warning! " + msg, out) + } + + private[this] var resetFunction: () => Source = null + private[this] var closeFunction: () => Unit = null + private[this] var positioner: Positioner = RelaxedPositioner + + def withReset(f: () => Source): this.type = { + resetFunction = f + this + } + def withClose(f: () => Unit): this.type = { + closeFunction = f + this + } + def withDescription(text: String): this.type = { + descr = text + this + } + /** Change or disable the positioner. */ + def withPositioning(on: Boolean): this.type = { + positioner = if (on) RelaxedPositioner else NoPositioner + this + } + def withPositioning(pos: Positioner): this.type = { + positioner = pos + this + } + + /** The close() method closes the underlying resource. */ + def close(): Unit = { + if (closeFunction != null) closeFunction() + } + + /** The reset() method creates a fresh copy of this Source. */ + def reset(): Source = + if (resetFunction != null) resetFunction() + else throw new UnsupportedOperationException("Source's reset() method was not set.") +} diff --git a/scala2-library-cc/src/scala/collection/concurrent/TrieMap.scala b/scala2-library-cc/src/scala/collection/concurrent/TrieMap.scala index 3bcbc60c8744..9c150e19ab9c 100644 --- a/scala2-library-cc/src/scala/collection/concurrent/TrieMap.scala +++ b/scala2-library-cc/src/scala/collection/concurrent/TrieMap.scala @@ -1071,6 +1071,7 @@ object TrieMap extends MapFactory[TrieMap] { // non-final as an extension point for parallel collections private[collection] class TrieMapIterator[K, V](var level: Int, private var ct: TrieMap[K, V], mustInit: Boolean = true) extends AbstractIterator[(K, V)] { + this:TrieMapIterator[K, V]^ => private val stack = new Array[Array[BasicNode]](7) private val stackpos = new Array[Int](7) private var depth = -1 @@ -1161,7 +1162,7 @@ private[collection] class TrieMapIterator[K, V](var level: Int, private var ct: /** Returns a sequence of iterators over subsets of this iterator. * It's used to ease the implementation of splitters for a parallel version of the TrieMap. */ - protected def subdivide(): Seq[Iterator[(K, V)]] = if (subiter ne null) { + protected def subdivide(): Seq[Iterator[(K, V)]^{this}] = if (subiter ne null) { // the case where an LNode is being iterated val it = newIterator(level + 1, ct, _mustInit = false) it.depth = -1 diff --git a/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala b/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala index 28ce8da104aa..7a63fbb4f248 100644 --- a/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala +++ b/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala @@ -24,7 +24,7 @@ import scala.language.implicitConversions import scala.runtime.Statics import language.experimental.captureChecking import annotation.unchecked.uncheckedCaptures -import caps.untrackedCaptures +import caps.unsafe.untrackedCaptures /** This class implements an immutable linked list. We call it "lazy" * because it computes its elements only when they are needed. diff --git a/scala2-library-cc/src/scala/collection/immutable/Range.scala b/scala2-library-cc/src/scala/collection/immutable/Range.scala index 459591d1a9cb..11fed1d9b1af 100644 --- a/scala2-library-cc/src/scala/collection/immutable/Range.scala +++ b/scala2-library-cc/src/scala/collection/immutable/Range.scala @@ -643,6 +643,7 @@ private class RangeIterator( lastElement: Int, initiallyEmpty: Boolean ) extends AbstractIterator[Int] with Serializable { + this: RangeIterator^ => private[this] var _hasNext: Boolean = !initiallyEmpty private[this] var _next: Int = start override def knownSize: Int = if (_hasNext) (lastElement - _next) / step + 1 else 0 @@ -656,7 +657,7 @@ private class RangeIterator( value } - override def drop(n: Int): Iterator[Int] = { + override def drop(n: Int): Iterator[Int]^{this} = { if (n > 0) { val longPos = _next.toLong + step * n if (step > 0) { diff --git a/scala2-library-cc/src/scala/collection/mutable/UnrolledBuffer.scala b/scala2-library-cc/src/scala/collection/mutable/UnrolledBuffer.scala index cfb6d014ae9d..0b7da1430f38 100644 --- a/scala2-library-cc/src/scala/collection/mutable/UnrolledBuffer.scala +++ b/scala2-library-cc/src/scala/collection/mutable/UnrolledBuffer.scala @@ -18,6 +18,7 @@ import scala.collection.generic.DefaultSerializable import scala.reflect.ClassTag import scala.collection.immutable.Nil import language.experimental.captureChecking +import caps.unsafe.unsafeAssumePure /** A buffer that stores elements in an unrolled linked list. * @@ -259,13 +260,14 @@ object UnrolledBuffer extends StrictOptimizedClassTagSeqFactory[UnrolledBuffer] /** Unrolled buffer node. */ class Unrolled[T: ClassTag] private[collection] (var size: Int, var array: Array[T], var next: Unrolled[T], val buff: UnrolledBuffer[T] = null) { + //this: Unrolled[T]^ => private[collection] def this() = this(0, new Array[T](unrolledlength), null, null) private[collection] def this(b: UnrolledBuffer[T]) = this(0, new Array[T](unrolledlength), null, b) private def nextlength = if (buff eq null) unrolledlength else buff.calcNextLength(array.length) // adds and returns itself or the new unrolled if full - @tailrec final def append(elem: T): Unrolled[T] = if (size < array.length) { + @tailrec final def append(elem: T): Unrolled[T]^{this} = if (size < array.length) { array(size) = elem size += 1 this @@ -307,21 +309,21 @@ object UnrolledBuffer extends StrictOptimizedClassTagSeqFactory[UnrolledBuffer] if (idx < size) array(idx) else next.apply(idx - size) @tailrec final def update(idx: Int, newelem: T): Unit = if (idx < size) array(idx) = newelem else next.update(idx - size, newelem) - @tailrec final def locate(idx: Int): Unrolled[T] = + @tailrec final def locate(idx: Int): Unrolled[T]^{this} = if (idx < size) this else next.locate(idx - size) - def prepend(elem: T) = if (size < array.length) { + def prepend(elem: T): Unrolled[T] = if (size < array.length) { // shift the elements of the array right // then insert the element shiftright() array(0) = elem size += 1 - this + this.unsafeAssumePure } else { // allocate a new node and store element // then make it point to this val newhead = new Unrolled[T](buff) newhead append elem - newhead.next = this + newhead.next = this.unsafeAssumePure newhead } // shifts right assuming enough space @@ -340,7 +342,7 @@ object UnrolledBuffer extends StrictOptimizedClassTagSeqFactory[UnrolledBuffer] val r = array(idx) shiftleft(idx) size -= 1 - if (tryMergeWithNext()) buffer.lastPtr = this + if (tryMergeWithNext()) buffer.lastPtr = this.unsafeAssumePure r } else next.remove(idx - size, buffer) @@ -397,7 +399,7 @@ object UnrolledBuffer extends StrictOptimizedClassTagSeqFactory[UnrolledBuffer] curr.next = newnextnode // try to merge the last node of this with the newnextnode and fix tail pointer if needed - if (curr.tryMergeWithNext()) buffer.lastPtr = curr + if (curr.tryMergeWithNext()) buffer.lastPtr = curr.unsafeAssumePure else if (newnextnode.next eq null) buffer.lastPtr = newnextnode appended } diff --git a/scaladoc-testcases/src/tests/deprecated.scala b/scaladoc-testcases/src/tests/deprecated.scala index 983ff7e012c7..f02f1363e9ea 100644 --- a/scaladoc-testcases/src/tests/deprecated.scala +++ b/scaladoc-testcases/src/tests/deprecated.scala @@ -2,22 +2,61 @@ package tests package deprecated class A: - def defInt: Int = 1 + def defInt: Int + = 1 @deprecated(message = "1") - def def1: 1 = 1 + def def1: 1 + = 1 @deprecated("reason") - val valInt: Int = 1 - val val1: 1 = 1 - var varInt: Int = 1 - var var1: 1 = 1 + val valInt: Int + = 1 + val val1: 1 + = 1 + var varInt: Int + = 1 + var var1: 1 + = 1 class InnerA: - val innerVal: Int = 1 + val innerVal: Int + = 1 class B extends A: @deprecated(since = "1", message = "some reason") - def x: Int = 1 - val y: Int = 1 + def x: Int + = 1 + val y: Int + = 1 - -@java.lang.Deprecated -class JavaDeprecated +class C: + /** zero */ + @deprecated + def noInfo: Int + = 0 + /** one */ + @deprecated() + def noInfo2: Int + = 0 + /** two */ + @deprecated("without names", "2.10.0") + def noNames: Int + = 0 + /** three */ + @deprecated(message = "with names", since = "2.10.0") + def withNames: Int + = 1 + /** four */ + @deprecated(since = "2.10.0", message = "backwards names") + def backwardNames: Int + = 2 + /** five */ + @deprecated("only message") + def onlyUnnamedMessage: Int + = 0 + /** six */ + @deprecated(message = "only named message") + def onlyNamedMessage: Int + = 1 + /** seven */ + @deprecated(since = "2.10.0") + def onlyNamedSince: Int + = 2 \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index 8ff40644fac2..a6b5dfbb6933 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -143,7 +143,7 @@ object Signature: case class LinkToType(signature: Signature, dri: DRI, kind: Kind) case class HierarchyGraph(edges: Seq[(LinkToType, LinkToType)], sealedNodes: Set[LinkToType] = Set.empty): - def vertecies: Seq[LinkToType] = edges.flatten((a, b) => Seq(a, b)).distinct + def vertecies: Seq[LinkToType] = edges.flatten(using (a, b) => Seq(a, b)).distinct def verteciesWithId: Map[LinkToType, Int] = vertecies.zipWithIndex.toMap def +(edge: (LinkToType, LinkToType)): HierarchyGraph = this ++ Seq(edge) def ++(edges: Seq[(LinkToType, LinkToType)]): HierarchyGraph = diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala index 021fdb5eef37..af39870d87b5 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala @@ -80,9 +80,12 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext signatureRenderer.renderLink(stripQuotes(text), dri) case Annotation.UnresolvedParameter(_, value) => stripQuotes(value) + // named arguments might be used, so we can't always rely on the order of the parameters val (named, unnamed) = a.params.partition(_.name.nonEmpty) - val message = named.find(_.name.get == "message") - val since = named.find(_.name.get == "since") + val message: Option[Annotation.AnnotationParameter] = + named.find(_.name.get == "message").fold(unnamed.lift(0))(Some(_)) + val since: Option[Annotation.AnnotationParameter] = + named.find(_.name.get == "since").fold(unnamed.lift(1))(Some(_)) val content = ( Seq( diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index c37ff8fe0200..56a0f7f6d6b6 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -102,11 +102,9 @@ case class TemplateFile( ctx.layouts.getOrElse(name, throw new RuntimeException(s"No layouts named $name in ${ctx.layouts}")) ) - def asJavaElement(o: Object): Object = o match - case m: Map[?, ?] => m.transform { - case (k: String, v: Object) => asJavaElement(v) - }.asJava - case l: List[?] => l.map(x => asJavaElement(x.asInstanceOf[Object])).asJava + def asJavaElement(o: Any): Any = o match + case m: Map[?, ?] => m.transform { (k, v) => asJavaElement(v) }.asJava + case l: List[?] => l.map(asJavaElement).asJava case other => other // Library requires mutable maps.. diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala index a5e32c7332bd..81415377beeb 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala @@ -42,7 +42,7 @@ trait BasicSupport: def getAnnotations(): List[Annotation] = // Custom annotations should be documented only if annotated by @java.lang.annotation.Documented // We allow also some special cases - val fqNameWhitelist = Set( + val fqNameAllowlist = Set( "scala.specialized", "scala.throws", "scala.transient", @@ -56,7 +56,7 @@ trait BasicSupport: ) val documentedSymbol = summon[Quotes].reflect.Symbol.requiredClass("java.lang.annotation.Documented") val annotations = sym.annotations.filter { a => - a.tpe.typeSymbol.hasAnnotation(documentedSymbol) || fqNameWhitelist.contains(a.symbol.fullName) + a.tpe.typeSymbol.hasAnnotation(documentedSymbol) || fqNameAllowlist.contains(a.symbol.fullName) } annotations.map(parseAnnotation).reverse diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 15c3071c38c9..110ee498a3ac 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -76,7 +76,7 @@ trait TypesSupport: else inner(tpe) ++ plain(".").l ++ suffix case tpe => inner(tpe) - // TODO #23 add support for all types signatures that makes sense + // TODO #23 add support for all types signatures that make sense private def inner( using Quotes, )( @@ -88,7 +88,7 @@ trait TypesSupport: ): SSignature = import reflect._ def noSupported(name: String): SSignature = - println(s"WARN: Unsupported type: $name: ${tp.show}") + report.warning(s"Unsupported type: $name: ${tp.show}") plain(s"Unsupported[$name]").l tp match case OrType(left, right) => diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala index ff4405d3ec71..44a1c3630a5f 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala @@ -112,7 +112,7 @@ abstract class MarkupConversion[T](val repr: Repr)(using dctx: DocContext) { case None => sym.dri DocLink.ToDRI(dri, targetText) case None => - val txt = s"No DRI found for query" + val txt = s"Couldn't resolve a member for the given link query" val msg = s"$txt: $queryStr" if (!summon[DocContext].args.noLinkWarnings) then diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala index 26c4fb06dfdf..0dab4a88907e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala @@ -31,8 +31,8 @@ trait MemberLookup { def nearestPackage(sym: Symbol): Symbol = if sym.flags.is(Flags.Package) then sym else nearestPackage(sym.owner) - def nearestMembered(sym: Symbol): Symbol = - if sym.isClassDef || sym.flags.is(Flags.Package) then sym else nearestMembered(sym.owner) + def nearestMember(sym: Symbol): Symbol = + if sym.isClassDef || sym.flags.is(Flags.Package) then sym else nearestMember(sym.owner) val res: Option[(Symbol, String, Option[Symbol])] = { def toplevelLookup(querystrings: List[String]) = @@ -43,7 +43,7 @@ trait MemberLookup { ownerOpt match { case Some(owner) => - val nearest = nearestMembered(owner) + val nearest = nearestMember(owner) val nearestCls = nearestClass(owner) val nearestPkg = nearestPackage(owner) def relativeLookup(querystrings: List[String], owner: Symbol): Option[(Symbol, Option[Symbol])] = { diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Entities.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Entities.scala index f11e8095afe7..86e7298226ea 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Entities.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Entities.scala @@ -5,8 +5,7 @@ import scala.collection.{Seq => _, _} // import representations._ /** A body of text. A comment has a single body, which is composed of - * at least one block. Inside every body is exactly one summary (see - * [[scala.tools.nsc.doc.model.comment.Summary]]). */ + * at least one block. Inside every body is exactly one summary. */ final case class Body(blocks: Seq[Block]) { /** The summary text of the comment body. */ diff --git a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala index 4ed17662fff8..adb9397f1bcd 100644 --- a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala +++ b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala @@ -55,6 +55,8 @@ class GivenSignatures extends SignatureTest("givenSignatures", SignatureTest.all class Annotations extends SignatureTest("annotations", SignatureTest.all) +class Deprecated extends SignatureTest("deprecated", SignatureTest.all) + class InheritanceLoop extends SignatureTest("inheritanceLoop", SignatureTest.all) class InheritedMembers extends SignatureTest("inheritedMembers2", SignatureTest.all.filter(_ != "class"), diff --git a/sjs-compiler-tests/test/scala/dotty/tools/dotc/ScalaJSCompilationTests.scala b/sjs-compiler-tests/test/scala/dotty/tools/dotc/ScalaJSCompilationTests.scala index 0f4eb633b770..88de410d05db 100644 --- a/sjs-compiler-tests/test/scala/dotty/tools/dotc/ScalaJSCompilationTests.scala +++ b/sjs-compiler-tests/test/scala/dotty/tools/dotc/ScalaJSCompilationTests.scala @@ -10,14 +10,33 @@ import reporting.TestReporter import vulpix._ @Category(Array(classOf[ScalaJSCompilationTests])) -class ScalaJSCompilationTests extends ParallelTesting { +class ScalaJSCompilationTests { import ParallelTesting._ import TestConfiguration._ import ScalaJSCompilationTests._ import CompilationTest.aggregateTests - // Test suite configuration -------------------------------------------------- + // Negative tests ------------------------------------------------------------ + + @Test def negScalaJS: Unit = { + implicit val testGroup: TestGroup = TestGroup("negScalaJS") + aggregateTests( + compileFilesInDir("tests/neg-scalajs", scalaJSOptions), + ).checkExpectedErrors() + } + + @Test def runScalaJS: Unit = { + implicit val testGroup: TestGroup = TestGroup("runScalaJS") + aggregateTests( + compileFilesInDir("tests/run", scalaJSOptions), + ).checkRuns() + } +} +object ScalaJSCompilationTests extends ParallelTesting { + implicit val summaryReport: SummaryReporting = new SummaryReport + + // Test suite configuration -------------------------------------------------- def maxDuration = 60.seconds def numberOfSlaves = 5 def safeMode = Properties.testsSafeMode @@ -26,20 +45,17 @@ class ScalaJSCompilationTests extends ParallelTesting { def updateCheckFiles: Boolean = Properties.testsUpdateCheckfile def failedTests = TestReporter.lastRunFailedTests - // Negative tests ------------------------------------------------------------ - - @Test def negScalaJS: Unit = { - implicit val testGroup: TestGroup = TestGroup("negScalaJS") - aggregateTests( - compileFilesInDir("tests/neg-scalajs", scalaJSOptions), - ).checkExpectedErrors() - } + @AfterClass def tearDown(): Unit = + cleanup() + summaryReport.echoSummary() // Run tests ----------------------------------------------------------------- override protected def shouldSkipTestSource(testSource: TestSource): Boolean = testSource.allToolArgs.get(ToolName.ScalaJS).exists(_.contains("--skip")) + override protected def testPlatform: TestPlatform = TestPlatform.ScalaJS + override def runMain(classPath: String, toolArgs: ToolArgs)(implicit summaryReport: SummaryReporting): Status = import scala.concurrent.ExecutionContext.Implicits.global @@ -55,16 +71,4 @@ class ScalaJSCompilationTests extends ParallelTesting { t.printStackTrace(new java.io.PrintWriter(writer)) Failure(writer.toString()) end runMain - - @Test def runScalaJS: Unit = { - implicit val testGroup: TestGroup = TestGroup("runScalaJS") - aggregateTests( - compileFilesInDir("tests/run", scalaJSOptions), - ).checkRuns() - } -} - -object ScalaJSCompilationTests { - implicit val summaryReport: SummaryReporting = new SummaryReport - @AfterClass def cleanup(): Unit = summaryReport.echoSummary() } diff --git a/sjs-compiler-tests/test/scala/dotty/tools/dotc/ScalaJSLink.scala b/sjs-compiler-tests/test/scala/dotty/tools/dotc/ScalaJSLink.scala index 2560021aec99..60a21602860e 100644 --- a/sjs-compiler-tests/test/scala/dotty/tools/dotc/ScalaJSLink.scala +++ b/sjs-compiler-tests/test/scala/dotty/tools/dotc/ScalaJSLink.scala @@ -17,6 +17,10 @@ object ScalaJSLink: Semantics.Defaults .withAsInstanceOfs(CheckedBehavior.Compliant) .withArrayIndexOutOfBounds(CheckedBehavior.Compliant) + .withArrayStores(CheckedBehavior.Compliant) + .withNegativeArraySizes(CheckedBehavior.Compliant) + .withNullPointers(CheckedBehavior.Compliant) + .withStringIndexOutOfBounds(CheckedBehavior.Compliant) .withModuleInit(CheckedBehavior.Compliant) end compliantSemantics diff --git a/staging/src/scala/quoted/staging/QuoteDriver.scala b/staging/src/scala/quoted/staging/QuoteDriver.scala index 0131a56cd8aa..82e91f7d7888 100644 --- a/staging/src/scala/quoted/staging/QuoteDriver.scala +++ b/staging/src/scala/quoted/staging/QuoteDriver.scala @@ -43,7 +43,7 @@ private class QuoteDriver(appClassloader: ClassLoader) extends Driver: val compiledExpr = try - new QuoteCompiler().newRun(ctx).compileExpr(exprBuilder) + new QuoteCompiler().newRun(using ctx).compileExpr(exprBuilder) catch case ex: dotty.tools.FatalError => val enrichedMessage = s"""An unhandled exception was thrown in the staging compiler. diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 8da8879185f5..4c1453243450 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -227,7 +227,7 @@ Standard-Section: "ASTs" TopLevelStat* PARAMalias -- Parameter is alias of a superclass parameter EXPORTED -- An export forwarder OPEN -- an open class - INVISIBLE -- invisible during typechecking + INVISIBLE -- invisible during typechecking, except when resolving from TASTy TRACKED -- a tracked class parameter / a dependent class Annotation @@ -324,7 +324,7 @@ object TastyFormat { * compatibility, but remains backwards compatible, with all * preceding `MinorVersion`. */ - final val MinorVersion: Int = 6 + final val MinorVersion: Int = 7 /** Natural Number. The `ExperimentalVersion` allows for * experimentation with changes to TASTy without committing diff --git a/tasty/src/dotty/tools/tasty/TastyReader.scala b/tasty/src/dotty/tools/tasty/TastyReader.scala index b5aa29f16954..d4374a76ff99 100644 --- a/tasty/src/dotty/tools/tasty/TastyReader.scala +++ b/tasty/src/dotty/tools/tasty/TastyReader.scala @@ -100,7 +100,7 @@ class TastyReader(val bytes: Array[Byte], start: Int, end: Int, val base: Int = /** Read an uncompressed Long stored in 8 bytes in big endian format */ def readUncompressedLong(): Long = { var x: Long = 0 - for (i <- 0 to 7) + for (_ <- 0 to 7) x = (x << 8) | (readByte() & 0xff) x } diff --git a/tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala b/tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala index d2e62e1f9eb0..548399b233d6 100644 --- a/tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala +++ b/tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala @@ -9,10 +9,10 @@ import TastyBuffer._ class BuildTastyVersionTest { val CurrentTastyVersion = TastyVersion(TastyFormat.MajorVersion, TastyFormat.MinorVersion, TastyFormat.ExperimentalVersion) - + // Needs to be defined in build Test/envVars val ExpectedTastyVersionEnvVar = "EXPECTED_TASTY_VERSION" - + @Test def testBuildTastyVersion(): Unit = { val expectedVersion = sys.env.get(ExpectedTastyVersionEnvVar) .getOrElse(fail(s"Env variable $ExpectedTastyVersionEnvVar not defined")) diff --git a/tests/best-effort/compiler-semanticdb-crash/err/ClassNode.java b/tests/best-effort/compiler-semanticdb-crash/err/ClassNode.java new file mode 100644 index 000000000000..0a2aabc411d7 --- /dev/null +++ b/tests/best-effort/compiler-semanticdb-crash/err/ClassNode.java @@ -0,0 +1,11 @@ +package dotty.tools.backend.jvm; + +public class ClassNode { + + public ClassNode(int api) { + } + + public ClassNode visitMethod(int access) { + return null; + } +} diff --git a/tests/best-effort/compiler-semanticdb-crash/err/Main.scala b/tests/best-effort/compiler-semanticdb-crash/err/Main.scala new file mode 100644 index 000000000000..cfc81a297a8e --- /dev/null +++ b/tests/best-effort/compiler-semanticdb-crash/err/Main.scala @@ -0,0 +1,4 @@ +package dotty.tools.backend.jvm + +val errorGenerator: Int = "0" +def readClass(bytes: Array[Byte]): ClassNode = ??? diff --git a/tests/best-effort/compiler-semanticdb-crash/main/Test.scala b/tests/best-effort/compiler-semanticdb-crash/main/Test.scala new file mode 100644 index 000000000000..8e5aee732d3e --- /dev/null +++ b/tests/best-effort/compiler-semanticdb-crash/main/Test.scala @@ -0,0 +1 @@ +def c = dotty.tools.backend.jvm.readClass(Array()) diff --git a/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties index db1723b08622..73df629ac1a7 100644 --- a/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties +++ b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties @@ -1 +1 @@ -sbt.version=1.10.5 +sbt.version=1.10.7 diff --git a/tests/cmdTest-sbt-tests/sourcepath-with-inline/project/build.properties b/tests/cmdTest-sbt-tests/sourcepath-with-inline/project/build.properties index db1723b08622..73df629ac1a7 100644 --- a/tests/cmdTest-sbt-tests/sourcepath-with-inline/project/build.properties +++ b/tests/cmdTest-sbt-tests/sourcepath-with-inline/project/build.properties @@ -1 +1 @@ -sbt.version=1.10.5 +sbt.version=1.10.7 diff --git a/tests/debug-custom-args/eval-explicit-nulls.check b/tests/debug-custom-args/eval-explicit-nulls.check new file mode 100644 index 000000000000..1c83191f645c --- /dev/null +++ b/tests/debug-custom-args/eval-explicit-nulls.check @@ -0,0 +1,13 @@ +break Test$ 4 +eval msg.size +result 13 +eval msg = null +error Type Mismatch Error +eval + val x: String = null + println(x) +error Type Mismatch Error +eval + val x: String | Null = null + x.nn +result java.lang.NullPointerException: tried to cast away nullability, but value is null diff --git a/tests/debug-custom-args/eval-explicit-nulls.scala b/tests/debug-custom-args/eval-explicit-nulls.scala new file mode 100644 index 000000000000..921bdde7eb15 --- /dev/null +++ b/tests/debug-custom-args/eval-explicit-nulls.scala @@ -0,0 +1,4 @@ +object Test: + def main(args: Array[String]): Unit = + var msg = "Hello, world!" + println(msg) diff --git a/tests/debug-preview/eval-in-for-comprehension.check b/tests/debug-preview/eval-in-for-comprehension.check new file mode 100644 index 000000000000..6e91c891ebdb --- /dev/null +++ b/tests/debug-preview/eval-in-for-comprehension.check @@ -0,0 +1,27 @@ +break Test$ 5 // in main +eval list(0) +result 1 +// TODO can we remove debug line in adapted methods? +break Test$ 5 // in main$$anonfun$adapted$1 +break Test$ 6 // in main$$anonfun$1 +eval list(0) +result 1 +eval x +result 1 +break Test$ 7 // in main$$anonfun$1$$anonfun$1 +eval x + y +result 2 + +break Test$ 11 // in main$$anonfun$2 +eval x +result 1 + +break Test$ 13 // in main +eval list(0) +result 1 +break Test$ 13 // in main$$anonfun$4 + +break Test$ 14 // in main +eval list(0) +result 1 +break Test$ 14 // in main$$anonfun$5 diff --git a/tests/debug-preview/eval-in-for-comprehension.scala b/tests/debug-preview/eval-in-for-comprehension.scala new file mode 100644 index 000000000000..0ea86fbb0302 --- /dev/null +++ b/tests/debug-preview/eval-in-for-comprehension.scala @@ -0,0 +1,14 @@ +object Test: + def main(args: Array[String]): Unit = + val list = List(1) + for + x <- list + y <- list + z = x + y + yield x + for + x <- list + if x == 1 + yield x + for x <- list yield x + for x <- list do println(x) \ No newline at end of file diff --git a/tests/debug/eval-at-default-arg.check b/tests/debug/eval-at-default-arg.check new file mode 100644 index 000000000000..79885c035ab3 --- /dev/null +++ b/tests/debug/eval-at-default-arg.check @@ -0,0 +1,3 @@ +break Test$ 6 +eval x + 1 +result 4 diff --git a/tests/debug/eval-at-default-arg.scala b/tests/debug/eval-at-default-arg.scala new file mode 100644 index 000000000000..6ca064308bb4 --- /dev/null +++ b/tests/debug/eval-at-default-arg.scala @@ -0,0 +1,8 @@ +object Test: + def main(args: Array[String]): Unit = + foo(3)() + + def foo(x: Int)( + y: Int = x + 1 + ): Unit = + println("foo") diff --git a/tests/debug/eval-by-name-capture.check b/tests/debug/eval-by-name-capture.check new file mode 100644 index 000000000000..2a8c7940c9d4 --- /dev/null +++ b/tests/debug/eval-by-name-capture.check @@ -0,0 +1,4 @@ +break Test$ 5 // main +break Test$ 5 // main$$anonfun$1 +eval x +result hello diff --git a/tests/debug/eval-by-name-capture.scala b/tests/debug/eval-by-name-capture.scala new file mode 100644 index 000000000000..c74d97eddc54 --- /dev/null +++ b/tests/debug/eval-by-name-capture.scala @@ -0,0 +1,7 @@ +object Test: + def main(args: Array[String]): Unit = + val x = "hello" + m: + x + ", world!" + + def m(y: => String): Unit = println(y) diff --git a/tests/debug/eval-by-name.check b/tests/debug/eval-by-name.check new file mode 100644 index 000000000000..87e29543f230 --- /dev/null +++ b/tests/debug/eval-by-name.check @@ -0,0 +1,33 @@ +break Test$ 11 +eval x +result foo +eval m +result foofoo +eval A().m +result fo +eval this.m("bar") +result barbarba + +break Test$ 7 +eval x +result foo +eval m +result foofoo +eval A().m +result fo + +break Test$A$1 10 +eval x +result foo +eval m +result fo +eval A().m +result fo + +break A 14 +eval x +result bar +eval m +result bar +eval A("foo").m +result foo diff --git a/tests/debug/eval-by-name.scala b/tests/debug/eval-by-name.scala new file mode 100644 index 000000000000..dc97c07670ed --- /dev/null +++ b/tests/debug/eval-by-name.scala @@ -0,0 +1,14 @@ +object Test: + def main(args: Array[String]): Unit = + println(m("foo") + A("bar").m) + + def m(x: => String): String = + def m: String = + x + x + class A: + def m: String = + x.take(2) + m + A().m + +class A(x: => String): + def m: String = x diff --git a/tests/debug/eval-captured-value-class.check b/tests/debug/eval-captured-value-class.check new file mode 100644 index 000000000000..d8dcee0cdcdb --- /dev/null +++ b/tests/debug/eval-captured-value-class.check @@ -0,0 +1,23 @@ +break Test$ 14 +eval new A("foo") +result fo +eval m("bar") +result ba + +break Test$A$1 9 +eval size +result 2 +eval size.value +result 2 +eval new A("foo") +result fo + +break Test$ 12 +eval size +result 2 +eval size.value +result 2 +eval new A("foo") +result fo +eval m("bar") +result ba diff --git a/tests/debug/eval-captured-value-class.scala b/tests/debug/eval-captured-value-class.scala new file mode 100644 index 000000000000..842906469ba4 --- /dev/null +++ b/tests/debug/eval-captured-value-class.scala @@ -0,0 +1,15 @@ +class Size(val value: Int) extends AnyVal + +object Test: + def main(args: Array[String]): Unit = + val size = new Size(2) + + class A(msg: String): + override def toString: String = + msg.take(size.value) + + def m(msg: String): String = + msg.take(size.value) + + println(new A("foo")) + println(m("bar")) diff --git a/tests/debug/eval-captures.check b/tests/debug/eval-captures.check new file mode 100644 index 000000000000..a971b20fbefe --- /dev/null +++ b/tests/debug/eval-captures.check @@ -0,0 +1,57 @@ +break A 26 +eval (new B).m +result x1x2x3x4 + +break A$B$1 22 +eval x1 +result x1 +eval m // local def m +result x1x2x3x4 +eval (new B).m +result x1x2x3x4 +eval A.this.m // compiles but throws NoSuchFieldException +result java.lang.NoSuchFieldException: $outer + +break A$B$1 21 +eval x1 +result x1 +eval x2 +result x2 +eval m +result x1x2x3x4 +eval (new C).m +result x1x2x3x4 +eval (new B).m +result x1x2x3x4 + +break A$B$1$C$1 19 +eval x1 +result x1 +eval x2 +result x2 +eval x3 +result x3 +eval x4 +result x4 +eval m +result x1x2x3x4 +eval (new C).m +result x1x2x3x4 +eval (new B).m +result x1x2x3x4 + +break A$B$1$C$1 18 +eval x1 +result x1 +eval x2 +result x2 +eval x3 +result x3 +eval x4 +result x4 +eval m +result x1x2x3x4 +eval (new C).m +result x1x2x3x4 +eval (new B).m +result x1x2x3x4 diff --git a/tests/debug/eval-captures.scala b/tests/debug/eval-captures.scala new file mode 100644 index 000000000000..b417788404ef --- /dev/null +++ b/tests/debug/eval-captures.scala @@ -0,0 +1,26 @@ +object Test: + def main(args: Array[String]): Unit = + val a = new A + println(a.m) + +class A: + def m: String = + val x1 = "x1" + class B: + def m: String = + val x2 = "x2" + def m: String = + val x3 = "x3" + class C: + def m: String = + val x4 = "x4" + def m: String = + x1 + x2 + x3 + x4 + m + val c = new C + c.m + m + end m + end B + val b = new B + b.m diff --git a/tests/debug/eval-encoding.check b/tests/debug/eval-encoding.check new file mode 100644 index 000000000000..a37d965f3bf6 --- /dev/null +++ b/tests/debug/eval-encoding.check @@ -0,0 +1,3 @@ +break Test$ 4 +eval | + new <> + &(":") + ! +result |<>&(:)! diff --git a/tests/debug/eval-encoding.scala b/tests/debug/eval-encoding.scala new file mode 100644 index 000000000000..79e1286e1f70 --- /dev/null +++ b/tests/debug/eval-encoding.scala @@ -0,0 +1,8 @@ +object Test: + def main(args: Array[String]): Unit = + val ! = "!" + println(| + new <> + &(":") + !) + private val | = "|" + private class <> : + override def toString: String = "<>" + private def &(`:`: String): String = s"&(${`:`})" \ No newline at end of file diff --git a/tests/debug/eval-enum.check b/tests/debug/eval-enum.check new file mode 100644 index 000000000000..02faca9f5a9e --- /dev/null +++ b/tests/debug/eval-enum.check @@ -0,0 +1,18 @@ +break B$C 10 // B$C. +break Test$ 18 +eval A.A1.a +result 1 +eval A.A2.a +result 2 + +break B 13 +eval C.C1.m +result bb +eval C.C2("bb").m +result bbb + +break B$C 10 +eval C1.m +result bb +eval C2("bb").m +result bbb diff --git a/tests/debug/eval-enum.scala b/tests/debug/eval-enum.scala new file mode 100644 index 000000000000..3ce4747c9fbe --- /dev/null +++ b/tests/debug/eval-enum.scala @@ -0,0 +1,18 @@ +enum A(val a: Int) extends java.lang.Enum[A]: + case A1 extends A(1) + case A2 extends A(2) + +class B(b: String): + private enum C(c: String): + case C1 extends C(b) + case C2(x: String) extends C(x) + + def m: String = b + c + end C + + def bar: String = C.C1.m + +object Test: + def main(args: Array[String]): Unit = + val b = new B("b") + println(b.bar) diff --git a/tests/debug/eval-error-pos.check b/tests/debug/eval-error-pos.check new file mode 100644 index 000000000000..22937505e99e --- /dev/null +++ b/tests/debug/eval-error-pos.check @@ -0,0 +1,7 @@ +break Test$ 3 +eval foo +error + :1:0 + 1 |foo + |^^^ + | Not found: foo diff --git a/tests/debug/eval-error-pos.scala b/tests/debug/eval-error-pos.scala new file mode 100644 index 000000000000..04adae25f1c9 --- /dev/null +++ b/tests/debug/eval-error-pos.scala @@ -0,0 +1,3 @@ +object Test: + def main(args: Array[String]): Unit = + println("Hello, World!") diff --git a/tests/debug/eval-exception.check b/tests/debug/eval-exception.check new file mode 100644 index 000000000000..aaa776c41bca --- /dev/null +++ b/tests/debug/eval-exception.check @@ -0,0 +1,11 @@ +break Test$ 7 +eval throwException() +result java.lang.Exception: foo +eval throw new Exception("bar") +result java.lang.Exception: bar +eval + try throwException() + catch case e: Exception => "caught" +result caught +eval assert(false, "fizz") +result java.lang.AssertionError: assertion failed: fizz diff --git a/tests/debug/eval-exception.scala b/tests/debug/eval-exception.scala new file mode 100644 index 000000000000..bcd97b143c67 --- /dev/null +++ b/tests/debug/eval-exception.scala @@ -0,0 +1,7 @@ +object Test: + def main(args: Array[String]): Unit = + try throwException() + catch case e: Exception => () + + def throwException(): Unit = + throw new Exception("foo") diff --git a/tests/debug/eval-fields.check b/tests/debug/eval-fields.check new file mode 100644 index 000000000000..bbe5c124896c --- /dev/null +++ b/tests/debug/eval-fields.check @@ -0,0 +1,25 @@ +break Test$ 4 +eval a.a1 +result a.a1 +eval a.B.b1 +result a.B.b1 +eval new A("aa", 2).a1 +result aa.a1 +eval new A("aa", 2).B.b1 +result aa.B.b1 + +break A 17 +eval name +result a +eval this.n +result 1 +eval a2 +result a.a2 +eval new A("aa", 2).a2 +result aa.a2 +eval B.b1 +result a.B.b1 +eval C.c1 +result a.C.c1 +eval new A("aa", 2).C.c1 +result aa.C.c1 diff --git a/tests/debug/eval-fields.scala b/tests/debug/eval-fields.scala new file mode 100644 index 000000000000..8363b98d6b80 --- /dev/null +++ b/tests/debug/eval-fields.scala @@ -0,0 +1,17 @@ +object Test: + def main(args: Array[String]): Unit = + val a = new A("a", 1) + println(a) + +class A(name: String, val n: Int): + val a1 = s"$name.a1" + private val a2 = s"$name.a2" + + object B: + val b1 = s"$name.B.b1" + + private object C: + val c1 = s"$name.C.c1" + + override def toString: String = + name + a2 diff --git a/tests/debug/eval-i425.check b/tests/debug/eval-i425.check new file mode 100644 index 000000000000..d8e13c75c9bf --- /dev/null +++ b/tests/debug/eval-i425.check @@ -0,0 +1,7 @@ +break Test$ 7 +eval patch.span +result 0 +eval patch.span = Span(1) +result () +eval patch.span +result 1 diff --git a/tests/debug/eval-i425.scala b/tests/debug/eval-i425.scala new file mode 100644 index 000000000000..9dfe2b87ae26 --- /dev/null +++ b/tests/debug/eval-i425.scala @@ -0,0 +1,10 @@ +// https://github.com/scalacenter/scala-debug-adapter/issues/425 +object Test: + private class Patch(var span: Span) + + def main(args: Array[String]): Unit = + val patch = new Patch(new Span(0)) + println("ok") + +class Span(val start: Int) extends AnyVal: + def end: Int = start + 1 diff --git a/tests/debug/eval-i485.check b/tests/debug/eval-i485.check new file mode 100644 index 000000000000..a7a31fc230e0 --- /dev/null +++ b/tests/debug/eval-i485.check @@ -0,0 +1,11 @@ +break Test$ 16 +eval b.a1 +result a1 +eval b.a2 = 2; b.a2 +result 2 +eval b.m +result m +eval new b.D +result D +eval b.D +result D$ diff --git a/tests/debug/eval-i485.scala b/tests/debug/eval-i485.scala new file mode 100644 index 000000000000..5f7d7f9d09c5 --- /dev/null +++ b/tests/debug/eval-i485.scala @@ -0,0 +1,16 @@ +// https://github.com/scalacenter/scala-debug-adapter/issues/425 +class A: + val a1 = "a1" + var a2 = 1 + def m = "m" + class D: + override def toString: String = "D" + object D: + override def toString: String = "D$" + +object Test: + private class B extends A + + def main(args: Array[String]): Unit = + val b = new B + println("foo") diff --git a/tests/debug/eval-in-case-def.check b/tests/debug/eval-in-case-def.check new file mode 100644 index 000000000000..a91439c17b35 --- /dev/null +++ b/tests/debug/eval-in-case-def.check @@ -0,0 +1,3 @@ +break Test$ 6 +eval n + m +result 2 diff --git a/tests/debug/eval-in-case-def.scala b/tests/debug/eval-in-case-def.scala new file mode 100644 index 000000000000..ab502e36fc79 --- /dev/null +++ b/tests/debug/eval-in-case-def.scala @@ -0,0 +1,6 @@ +object Test: + def main(args: Array[String]): Unit = + val n = 1 + n match + case m => + println(n + m) diff --git a/tests/debug/eval-in-for-comprehension.check b/tests/debug/eval-in-for-comprehension.check new file mode 100644 index 000000000000..fb0d62135efb --- /dev/null +++ b/tests/debug/eval-in-for-comprehension.check @@ -0,0 +1,34 @@ +break Test$ 5 // in main +eval list(0) +result 1 +// TODO can we remove debug line in adapted methods? +break Test$ 5 // in main$$anonfun$adapted$1 +break Test$ 6 // in main$$anonfun$1 +eval list(0) +result 1 +eval x +result 1 +break Test$ 6 // in main$$anonfun$1$$anonfun$adapted$1 +break Test$ 7 // in main$$anonfun$1$$anonfun$1 +eval x + y +result 2 +// TODO this line position does not make any sense +break Test$ 6 // in main$$anonfun$1$$anonfun$1 +break Test$ 7 // in main$$anonfun$1$$anonfun$1 +break Test$ 6 // in main$$anonfun$1$$anonfun$2 +break Test$ 6 // in main$$anonfun$1$$anonfun$2 +break Test$ 7 // in main$$anonfun$1$$anonfun$2 + +break Test$ 11 // in main$$anonfun$2 +eval x +result 1 + +break Test$ 13 // in main +eval list(0) +result 1 +break Test$ 13 // in main$$anonfun$4 + +break Test$ 14 // in main +eval list(0) +result 1 +break Test$ 14 // in main$$anonfun$5 diff --git a/tests/debug/eval-in-for-comprehension.scala b/tests/debug/eval-in-for-comprehension.scala new file mode 100644 index 000000000000..0ea86fbb0302 --- /dev/null +++ b/tests/debug/eval-in-for-comprehension.scala @@ -0,0 +1,14 @@ +object Test: + def main(args: Array[String]): Unit = + val list = List(1) + for + x <- list + y <- list + z = x + y + yield x + for + x <- list + if x == 1 + yield x + for x <- list yield x + for x <- list do println(x) \ No newline at end of file diff --git a/tests/debug/eval-inline.check b/tests/debug/eval-inline.check new file mode 100644 index 000000000000..7c56eff72a67 --- /dev/null +++ b/tests/debug/eval-inline.check @@ -0,0 +1,17 @@ +break Test$ 4 +eval m1 +result 42 +eval m2(y) +result 2 +eval x + 1 +result 2 +eval test1(42) +result Test(42) +eval test2 +result 42 +eval m2(test2) +result 42 +eval + inline val x = 3 + x +result 3 diff --git a/tests/debug/eval-inline.scala b/tests/debug/eval-inline.scala new file mode 100644 index 000000000000..9a77d09956bb --- /dev/null +++ b/tests/debug/eval-inline.scala @@ -0,0 +1,12 @@ +object Test: + def main(args: Array[String]): Unit = + inline val y = 2 + println("Hello, World!") + + inline def m1: Int = 42 + inline def m2(inline x: Int): Int = x + private inline val x = 1 + + inline def test1(inline x: Int): Test = Test(x) + inline def test2: Int = test1(42).x + case class Test(x: Int) diff --git a/tests/debug/eval-inner-class.check b/tests/debug/eval-inner-class.check new file mode 100644 index 000000000000..305c3963046e --- /dev/null +++ b/tests/debug/eval-inner-class.check @@ -0,0 +1,27 @@ +break Test$ 4 +eval a1 +result A +eval a2(new A) +result a2 +eval a1.a3 +result a3 +eval (new A).a3("foo") +result a3(foo) +eval new test.B +result B +eval test.b2(test.b1) +result b2 +eval test.b1.b3 +result b3 +eval (new test.B).b3("foo") +result b3(foo) + +break Test 17 +eval new B +result B +eval b2(new this.B) +result b2 +eval (new B).b3 +result b3 +eval b1.b3("foo") +result b3(foo) diff --git a/tests/debug/eval-inner-class.scala b/tests/debug/eval-inner-class.scala new file mode 100644 index 000000000000..f17118434b59 --- /dev/null +++ b/tests/debug/eval-inner-class.scala @@ -0,0 +1,25 @@ +object Test: + def main(args: Array[String]): Unit = + val test = new Test + test.m() + + private def a1: A = new A + private def a2(a: A): String = "a2" + + class A: + val a3: String = "a3" + def a3(x: String): String = s"a3($x)" + override def toString: String = "A" +end Test + +class Test: + def m(): Unit = + println("test.m()") + + private def b1: B = new B + private def b2(b: B) = "b2" + + private class B: + val b3: String = "b3" + def b3(x: String): String = s"b3($x)" + override def toString: String = "B" diff --git a/tests/debug/eval-intersection-type.check b/tests/debug/eval-intersection-type.check new file mode 100644 index 000000000000..1091a87f5154 --- /dev/null +++ b/tests/debug/eval-intersection-type.check @@ -0,0 +1,3 @@ +break Test$ 8 +eval c.m +result m diff --git a/tests/debug/eval-intersection-type.scala b/tests/debug/eval-intersection-type.scala new file mode 100644 index 000000000000..901162cf40da --- /dev/null +++ b/tests/debug/eval-intersection-type.scala @@ -0,0 +1,8 @@ +class A +trait B: + def m: String = "m" + +object Test: + def main(args: Array[String]): Unit = + val c: A & B = new A with B {} + println(c.m) diff --git a/tests/debug/eval-java-protected-members.check b/tests/debug/eval-java-protected-members.check new file mode 100644 index 000000000000..96cddde25c8a --- /dev/null +++ b/tests/debug/eval-java-protected-members.check @@ -0,0 +1,13 @@ +break Test$ 3 +eval example.A.x1 +result x1 +eval example.A.x1 = "y1"; example.A.x1 +result y1 +eval example.A.m1() +result m1 +eval x2 +result x2 +eval x2 = "y2"; x2 +result y2 +eval m2() +result m2 diff --git a/tests/debug/eval-java-protected-members/A.java b/tests/debug/eval-java-protected-members/A.java new file mode 100644 index 000000000000..16ba0dd15fbf --- /dev/null +++ b/tests/debug/eval-java-protected-members/A.java @@ -0,0 +1,13 @@ +package example; + +public class A { + protected static String x1 = "x1"; + protected static String m1() { + return "m1"; + } + + protected String x2 = "x2"; + protected String m2() { + return "m2"; + } +} diff --git a/tests/debug/eval-java-protected-members/Test.scala b/tests/debug/eval-java-protected-members/Test.scala new file mode 100644 index 000000000000..e7cd0f4e1e42 --- /dev/null +++ b/tests/debug/eval-java-protected-members/Test.scala @@ -0,0 +1,3 @@ +object Test extends example.A: + def main(args: Array[String]): Unit = + println("Hello, World!") diff --git a/tests/debug/eval-lambdas.check b/tests/debug/eval-lambdas.check new file mode 100644 index 000000000000..b4f955687684 --- /dev/null +++ b/tests/debug/eval-lambdas.check @@ -0,0 +1,7 @@ +break Test$ 6 +eval List(1, 2, 3).map(_ * a * b * c).sum +result 36 + +break A 14 +eval List(1, 2, 3).map(_ * a * b * c).sum +result 36 diff --git a/tests/debug/eval-lambdas.scala b/tests/debug/eval-lambdas.scala new file mode 100644 index 000000000000..24a4c8a5a5f9 --- /dev/null +++ b/tests/debug/eval-lambdas.scala @@ -0,0 +1,14 @@ +object Test: + val a = 1 + private val b = 2 + def main(args: Array[String]): Unit = + val c = 3 + println(a + b + c) + (new A).m() + +class A: + val a = 1 + private val b = 2 + def m() = + val c = 3 + println(a + b + c) diff --git a/tests/debug/eval-lazy-val.check b/tests/debug/eval-lazy-val.check new file mode 100644 index 000000000000..90e8d69a823e --- /dev/null +++ b/tests/debug/eval-lazy-val.check @@ -0,0 +1,12 @@ +break A 9 +eval x +result 1 +eval y +// it is not supported because the compiler does not declare y as a local variable in the class file +error local lazy val not supported +eval A.z +result 3 +eval + lazy val z = 4 + z +result 4 diff --git a/tests/debug/eval-lazy-val.scala b/tests/debug/eval-lazy-val.scala new file mode 100644 index 000000000000..2acee77d7f20 --- /dev/null +++ b/tests/debug/eval-lazy-val.scala @@ -0,0 +1,12 @@ +object Test: + def main(args: Array[String]): Unit = + (new A).m + +class A: + private lazy val x = 1 + def m: Int = + lazy val y = 2 + x + y + A.z + +object A: + private lazy val z = 3 diff --git a/tests/debug/eval-local-class-in-value-class.check b/tests/debug/eval-local-class-in-value-class.check new file mode 100644 index 000000000000..a5c354bd7421 --- /dev/null +++ b/tests/debug/eval-local-class-in-value-class.check @@ -0,0 +1,15 @@ +break A$ 6 +eval this.m(1) +result f +eval (new B).m +result fo + +break A$B$1 5 +eval value +result foo +eval size +result 2 +eval m +result fo +eval A.this.m(1) +result f diff --git a/tests/debug/eval-local-class-in-value-class.scala b/tests/debug/eval-local-class-in-value-class.scala new file mode 100644 index 000000000000..0d415378a26d --- /dev/null +++ b/tests/debug/eval-local-class-in-value-class.scala @@ -0,0 +1,11 @@ +class A(val value: String) extends AnyVal: + def m(size: Int): String = + class B: + def m: String = + value.take(size) + (new B).m + +object Test: + def main(args: Array[String]): Unit = + val a = new A("foo") + println(a.m(2)) diff --git a/tests/debug/eval-local-class.check b/tests/debug/eval-local-class.check new file mode 100644 index 000000000000..7c7ce67cf08d --- /dev/null +++ b/tests/debug/eval-local-class.check @@ -0,0 +1,9 @@ +break A$B$1 9 +eval new B +result B +eval x1 + x2 +result x1x2 +eval A.this.x1 +result Ax1 +eval this.x2 +result Bx2 diff --git a/tests/debug/eval-local-class.scala b/tests/debug/eval-local-class.scala new file mode 100644 index 000000000000..05644c7f8bd9 --- /dev/null +++ b/tests/debug/eval-local-class.scala @@ -0,0 +1,17 @@ +class A: + val x1 = "Ax1" + def m(): Unit = + val x1 = "x1" + class B: + val x2 = "Bx2" + def m(): Unit = + val x2 = "x2" + println(x1 + A.this.x1) + override def toString: String = "B" + val b = new B + b.m() + +object Test: + def main(args: Array[String]): Unit = + val a = new A + a.m() diff --git a/tests/debug/eval-local-method-in-value-class.check b/tests/debug/eval-local-method-in-value-class.check new file mode 100644 index 000000000000..f503891dc916 --- /dev/null +++ b/tests/debug/eval-local-method-in-value-class.check @@ -0,0 +1,15 @@ +break A$ 5 +eval this.m(1) +result ff +eval m(3) +result fofofo + +break A$ 4 +eval value +result foo +eval size +result 2 +eval m(1) +result fo +eval this.m(3) +result foofoo diff --git a/tests/debug/eval-local-method-in-value-class.scala b/tests/debug/eval-local-method-in-value-class.scala new file mode 100644 index 000000000000..612616acdd83 --- /dev/null +++ b/tests/debug/eval-local-method-in-value-class.scala @@ -0,0 +1,10 @@ +class A(val value: String) extends AnyVal: + def m(size: Int): String = + def m(mul: Int): String = + value.take(size) * mul + m(2) + +object Test: + def main(args: Array[String]): Unit = + val a = new A("foo") + println(a.m(2)) diff --git a/tests/debug/eval-local-methods.check b/tests/debug/eval-local-methods.check new file mode 100644 index 000000000000..bd00091faa65 --- /dev/null +++ b/tests/debug/eval-local-methods.check @@ -0,0 +1,15 @@ +break Test$ 12 +eval m1("foo") +result m1(foo) +eval m3 +result A +eval m2(new A) +result m2(A) + +break B 23 +eval m1("foo") +result m1(foo) +eval m3 +result C +eval m2(new C) +result m2(C) diff --git a/tests/debug/eval-local-methods.scala b/tests/debug/eval-local-methods.scala new file mode 100644 index 000000000000..1bb5df5b84b8 --- /dev/null +++ b/tests/debug/eval-local-methods.scala @@ -0,0 +1,23 @@ +object Test: + private class A: + override def toString: String = "A" + + def main(args: Array[String]): Unit = + val x1 = 1 + def m1(x: String) = s"m$x1($x)" + def m2(a: A): String = s"m2($a)" + def m3: A = new A + println(m1("x") + m2(m3)) + val b = new B + b.m() + +class B: + val x1 = 1 + private class C: + override def toString: String = "C" + + def m(): Unit = + def m1(x: String) = s"m$x1($x)" + def m2(c: C): String = s"m2($c)" + def m3: C = new C + println(m1("x") + m2(m3)) diff --git a/tests/debug/eval-macro.check b/tests/debug/eval-macro.check new file mode 100644 index 000000000000..ae759090757d --- /dev/null +++ b/tests/debug/eval-macro.check @@ -0,0 +1,11 @@ +break Test$ 6 +eval showType(msg) +result java.lang.String +eval + type Foo = Int + showType(1: Foo) +result Foo +eval + class Bar + showType(new Bar) +result Bar diff --git a/tests/debug/eval-macro/Macro.scala b/tests/debug/eval-macro/Macro.scala new file mode 100644 index 000000000000..a4c65e218419 --- /dev/null +++ b/tests/debug/eval-macro/Macro.scala @@ -0,0 +1,8 @@ +import scala.quoted.* + +object Macro: + inline def showType(inline expr: Any): String = ${showType('expr)} + + private def showType(expr: Expr[Any])(using Quotes): Expr[String] = + import quotes.reflect.* + Expr(expr.asTerm.tpe.widen.show) diff --git a/tests/debug/eval-macro/Test.scala b/tests/debug/eval-macro/Test.scala new file mode 100644 index 000000000000..4b38a60024a7 --- /dev/null +++ b/tests/debug/eval-macro/Test.scala @@ -0,0 +1,6 @@ +import Macro.showType + +object Test: + def main(args: Array[String]): Unit = + val msg = "Hello, World!" + println(showType(msg)) diff --git a/tests/debug/eval-multi-line-expr.check b/tests/debug/eval-multi-line-expr.check new file mode 100644 index 000000000000..7f4df1b31a9e --- /dev/null +++ b/tests/debug/eval-multi-line-expr.check @@ -0,0 +1,7 @@ +break Test$ 2 // breaks in Test$. +break Test$ 2 +eval + val a = new A + val b = "b" + a.toString + b +result Ab diff --git a/tests/debug/eval-multi-line-expr.scala b/tests/debug/eval-multi-line-expr.scala new file mode 100644 index 000000000000..ee23f75b2ab6 --- /dev/null +++ b/tests/debug/eval-multi-line-expr.scala @@ -0,0 +1,5 @@ +object Test: + def main(args: Array[String]): Unit = println("Hello, World!") + +class A: + override def toString = "A" diff --git a/tests/debug/eval-mutable-value-class.check b/tests/debug/eval-mutable-value-class.check new file mode 100644 index 000000000000..a14af0b761fa --- /dev/null +++ b/tests/debug/eval-mutable-value-class.check @@ -0,0 +1,23 @@ +break Test$ 9 +eval x = A(2) +result () +eval y = x + A(1); y +result 3 +eval z += A(2); z +result 3 +eval xx() +result 3 +eval (new B).yy() +result 4 + +break Test$ 12 +eval x += A(1); x +result 5 +eval xx() +result 6 + +break Test$B$1 16 +eval y += A(1); y +result 6 +eval (new B).yy() +result 7 diff --git a/tests/debug/eval-mutable-value-class.scala b/tests/debug/eval-mutable-value-class.scala new file mode 100644 index 000000000000..2982c3fa336c --- /dev/null +++ b/tests/debug/eval-mutable-value-class.scala @@ -0,0 +1,19 @@ +class A(val value: Int) extends AnyVal: + def +(x: A) = new A(value + x.value) + +object Test: + def main(args: Array[String]): Unit = + var x: A = new A(1) + var y: A = new A(1) + var z: A = new A(1) + z += new A(1) + def xx(): A = + x += new A(1) + x + class B: + def yy(): A = + y += new A(1) + y + val b = new B + val res = xx() + b.yy() + z + println(res) diff --git a/tests/debug/eval-mutable-variables.check b/tests/debug/eval-mutable-variables.check new file mode 100644 index 000000000000..7a2e7c9a9e61 --- /dev/null +++ b/tests/debug/eval-mutable-variables.check @@ -0,0 +1,33 @@ +break A 12 +eval x = 2 +result () +eval x +result 2 +eval + u = 2 + u +result 2 +eval u +result 2 +eval y += 1 +result () +eval yy() +result 3 +eval (new B).zz() +result 2 + +break A 14 +eval y+=1; y +result 4 + +break A 15 +eval y +result 5 + +break A$B$1 18 +eval z += 1; z +result 3 + +break A$B$1 19 +eval z +result 4 diff --git a/tests/debug/eval-mutable-variables.scala b/tests/debug/eval-mutable-variables.scala new file mode 100644 index 000000000000..4ed669b7a614 --- /dev/null +++ b/tests/debug/eval-mutable-variables.scala @@ -0,0 +1,21 @@ +object Test: + def main(args: Array[String]): Unit = + val a = new A + println(a.m) + +class A: + private var x = 1 + def m: Int = + var y = 1 + var z = 1 + var u = 1 // not captured + x += 1 + def yy(): Int = + y += 1 + y + class B: + def zz(): Int = + z += 1 + z + val b = new B + x + yy() + b.zz() + u diff --git a/tests/debug/eval-outer-from-init.check b/tests/debug/eval-outer-from-init.check new file mode 100644 index 000000000000..a217912ff626 --- /dev/null +++ b/tests/debug/eval-outer-from-init.check @@ -0,0 +1,13 @@ +// TODO those debug step don't make any sense +break A$B 4 +next 3 +break A$B 4 +eval x +result x + +// TODO same here +break A$B$C 6 +next 5 +break A$B$C 6 +eval x +result x diff --git a/tests/debug/eval-outer-from-init.scala b/tests/debug/eval-outer-from-init.scala new file mode 100644 index 000000000000..a1c032c271a9 --- /dev/null +++ b/tests/debug/eval-outer-from-init.scala @@ -0,0 +1,12 @@ +class A: + val x = "x" + class B: + println(x) + class C: + println(x) + new C + new B + +object Test: + def main(args: Array[String]): Unit = + new A diff --git a/tests/debug/eval-outer.check b/tests/debug/eval-outer.check new file mode 100644 index 000000000000..6500043a8237 --- /dev/null +++ b/tests/debug/eval-outer.check @@ -0,0 +1,8 @@ +break A$B$C 6 +eval a + a +result aa + +break A$D 10 +// the compilation succeeds but the evaluation throws a NoSuchFieldException +eval a + a +result java.lang.NoSuchFieldException: $outer diff --git a/tests/debug/eval-outer.scala b/tests/debug/eval-outer.scala new file mode 100644 index 000000000000..1b5bbdc8bc49 --- /dev/null +++ b/tests/debug/eval-outer.scala @@ -0,0 +1,18 @@ +class A: + private val a = "a" + class B: + class C: + def m: String = + a + a + def d: String = (new D).m + private final class D: + def m: String = + "d" + +object Test: + def main(args: Array[String]): Unit = + val a = new A + val b = new a.B + val c = new b.C + println(c.m) + println(a.d) diff --git a/tests/debug/eval-overloads.check b/tests/debug/eval-overloads.check new file mode 100644 index 000000000000..288b1797d8e1 --- /dev/null +++ b/tests/debug/eval-overloads.check @@ -0,0 +1,25 @@ +break Test$ 3 +eval m() +result m +eval m(5) +result m(5: Int) +eval m(true) +result m(true: Boolean) +eval m("foo") +result m(foo: String) +eval m(new B) +result m(b: B) +eval m(new B: A) +result m(a: A) +eval m(Array(1, 2)) +result m(xs: Array[Int]) +eval m(Array[A](new B)) +result m(xs: Array[A]) +eval m(Array(Array(1), Array(2))) +result m(xs: Array[Array[Int]]) +eval m1(Seq(1, 2, 3)) +result List(1, 2, 3) +eval m1(Vector(1, 2, 3)) +result Vector(1, 2, 3) +eval m1(Seq(true, false, true)) +result 2 diff --git a/tests/debug/eval-overloads.scala b/tests/debug/eval-overloads.scala new file mode 100644 index 000000000000..26ed5c19b22d --- /dev/null +++ b/tests/debug/eval-overloads.scala @@ -0,0 +1,18 @@ +object Test: + def main(args: Array[String]): Unit = + println("Hello, World!") + + trait A + class B extends A + + private def m(): String = "m" + private def m(n: Int): String = s"m($n: Int)" + private def m(b: Boolean): String = s"m($b: Boolean)" + private def m(str: String): String = s"m($str: String)" + private def m(a: A): String = s"m(a: A)" + private def m(b: B): String = s"m(b: B)" + private def m(xs: Array[Int]): String = s"m(xs: Array[Int])" + private def m(xs: Array[A]): String = s"m(xs: Array[A])" + private def m(xs: Array[Array[Int]]): String = s"m(xs: Array[Array[Int]])" + private def m1(xs: Seq[Int]): String = xs.toString + private def m1(xs: Seq[Boolean]): Int = xs.count(identity) diff --git a/tests/debug/eval-private-members-in-parent.check b/tests/debug/eval-private-members-in-parent.check new file mode 100644 index 000000000000..b718066c440d --- /dev/null +++ b/tests/debug/eval-private-members-in-parent.check @@ -0,0 +1,22 @@ +break ParentA$ParentB 20 // in ParentA$ParentB. +break ParentA 17 +eval x + y + z +result xyz +eval y = "yy" +result () +eval m2 +result yyz +eval (new B).m +result x + +break ParentA$ParentB 20 +eval x + y + z +result xyyz +eval y = "yyy" +result () +eval m2 +result yyyz + +break TraitA 9 +eval u = "uu"; u +result uu diff --git a/tests/debug/eval-private-members-in-parent.scala b/tests/debug/eval-private-members-in-parent.scala new file mode 100644 index 000000000000..89720bf31eaa --- /dev/null +++ b/tests/debug/eval-private-members-in-parent.scala @@ -0,0 +1,23 @@ +object Test: + def main(args: Array[String]): Unit = + val a = new A + println(a.m1) + println(a.m2) + +trait TraitA: + private var u: String = "u" + def m2: String = u + +abstract class ParentA: + private val x: String = "x" + private var y: String = "y" + private lazy val z: String = "z" + def m1: String = + val b = new B + b.m + m2 + private def m2: String = y + z + private abstract class ParentB: + def m: String = x + private class B extends ParentB + +class A extends ParentA with TraitA diff --git a/tests/debug/eval-shaded-fields-and-values.check b/tests/debug/eval-shaded-fields-and-values.check new file mode 100644 index 000000000000..3a3056a74018 --- /dev/null +++ b/tests/debug/eval-shaded-fields-and-values.check @@ -0,0 +1,5 @@ +break A$B 9 +eval x1 + x2 + x3 +result Ax1Bx2x3 +eval x1 + A.this.x2 + this.x3 +result Ax1Ax2Bx3 diff --git a/tests/debug/eval-shaded-fields-and-values.scala b/tests/debug/eval-shaded-fields-and-values.scala new file mode 100644 index 000000000000..ff97ceda7f9e --- /dev/null +++ b/tests/debug/eval-shaded-fields-and-values.scala @@ -0,0 +1,15 @@ +class A: + val x1 = "Ax1" + val x2 = "Ax2" + class B: + val x2 = "Bx2" + val x3 = "Bx3" + def m(): Unit = + val x3 = "x3" + println(x1 + x2 + x3) + +object Test: + def main(args: Array[String]): Unit = + val a = new A() + val b = new a.B() + b.m() diff --git a/tests/debug/eval-static-fields.check b/tests/debug/eval-static-fields.check new file mode 100644 index 000000000000..42174f0092ed --- /dev/null +++ b/tests/debug/eval-static-fields.check @@ -0,0 +1,36 @@ +break example.A$ 8 +eval a1 +result a1 +eval this.a2 +result a2 +eval A.a3 +result a3 +eval B +result example.A.B +eval this.B.b1 +result b1 +eval A.B.b2 +error value b2 cannot be accessed +eval B.b3 +result b3 +eval C.c1 +result c1 +eval D.d1 +result d1 +eval E +result example.E +eval E.e1 +result e1 + +// eval static fields from private object +break example.A$B$ 16 +eval b1 +result b1 +eval b2 +error Cannot access local val b2 in method as field +eval a2 +result a2 +eval C.c1 +result c1 +eval E.e1 +result e1 diff --git a/tests/debug/eval-static-fields.scala b/tests/debug/eval-static-fields.scala new file mode 100644 index 000000000000..21297a76c747 --- /dev/null +++ b/tests/debug/eval-static-fields.scala @@ -0,0 +1,30 @@ +object Test: + def main(args: Array[String]): Unit = + example.A.m() + +package example: + object A: + def m(): Unit = + println("A.m()" + a2) + B.m() + + val a1 = "a1" + private val a2 = "a2" + private[example] val a3 = "a3" + + private object B: + def m(): Unit = println("B.m()") + val b1 = "b1" + private val b2 = "b2" + private[A] val b3 = "b3" + override def toString: String = "example.A.B" + + private[A] object C: + val c1 = "c1" + + object D: + val d1 = "d1" + + private object E: + val e1 = "e1" + override def toString: String = "example.E" diff --git a/tests/debug/eval-static-java-method.check b/tests/debug/eval-static-java-method.check new file mode 100644 index 000000000000..f27ba8cb7e19 --- /dev/null +++ b/tests/debug/eval-static-java-method.check @@ -0,0 +1,5 @@ +break Test$ 3 +eval + import java.nio.file.Paths + Paths.get(".") +result . diff --git a/tests/debug/eval-static-java-method.scala b/tests/debug/eval-static-java-method.scala new file mode 100644 index 000000000000..04adae25f1c9 --- /dev/null +++ b/tests/debug/eval-static-java-method.scala @@ -0,0 +1,3 @@ +object Test: + def main(args: Array[String]): Unit = + println("Hello, World!") diff --git a/tests/debug/eval-static-methods.check b/tests/debug/eval-static-methods.check new file mode 100644 index 000000000000..7d6c782a8ca1 --- /dev/null +++ b/tests/debug/eval-static-methods.check @@ -0,0 +1,36 @@ +break example.A$ 8 +eval a1("foo") +result a1: foo +eval this.a2("foo") +result a2: foo +eval B.b1 +result b1 +eval B.b1("foo") +result b1: foo +eval B.b2("foo") +result b2: foo +eval B.b3("foo") +error method b3 cannot be accessed +eval C.c1("foo") +result c1: foo +eval C.c2("foo") +result c2: foo + +// access static methods from private object +break example.A$B$ 16 +eval a1("foo") +result a1: foo +eval A.this.a2("foo") +result a2: foo +eval B.b1 +result b1 +eval B.b1("foo") +result b1: foo +eval B.b2("foo") +result b2: foo +eval B.b3("foo") +result b3: foo +eval C.c1("foo") +result c1: foo +eval C.c2("foo") +result c2: foo diff --git a/tests/debug/eval-static-methods.scala b/tests/debug/eval-static-methods.scala new file mode 100644 index 000000000000..33987fad60c4 --- /dev/null +++ b/tests/debug/eval-static-methods.scala @@ -0,0 +1,25 @@ +object Test: + def main(args: Array[String]): Unit = + example.A.m() + +package example: + object A: + def m(): Unit = + println("A.m()") + B.m() + + def a1(str: String) = s"a1: $str" + private def a2(str: String) = s"a2: $str" + + private object B: + def m(): Unit = + println("B.m()") + + val b1 = "b1" + def b1(str: String) = s"b1: $str" + private[A] def b2(str: String) = s"b2: $str" + private def b3(str: String) = s"b3: $str" + + object C: + def c1(str: String) = s"c1: $str" + private[example] def c2(str: String) = s"c2: $str" diff --git a/tests/debug/eval-tail-rec.check b/tests/debug/eval-tail-rec.check new file mode 100644 index 000000000000..d37f4475368a --- /dev/null +++ b/tests/debug/eval-tail-rec.check @@ -0,0 +1,14 @@ +break Test$ 3 +eval f(100) +result 25 + +break Test$ 8 +eval x +result 80 +eval f(x) +result 40 +eval + @scala.annotation.tailrec + def g(x: Int): Int = if x <= 42 then g(x * 2) else x + g(21) +result 84 diff --git a/tests/debug/eval-tail-rec.scala b/tests/debug/eval-tail-rec.scala new file mode 100644 index 000000000000..28a2b93cc11f --- /dev/null +++ b/tests/debug/eval-tail-rec.scala @@ -0,0 +1,9 @@ +object Test: + def main(args: Array[String]): Unit = + println(f(80)) + + @scala.annotation.tailrec + def f(x: Int): Int = + if x <= 42 then x + else f(x/2) + diff --git a/tests/debug/eval-tuple-extractor.check b/tests/debug/eval-tuple-extractor.check new file mode 100644 index 000000000000..5f3ad9e00727 --- /dev/null +++ b/tests/debug/eval-tuple-extractor.check @@ -0,0 +1,9 @@ +break Test$ 4 +eval t(0) +result 1 +eval t._2 +result 2 +eval + val (x, y) = t + y +result 2 diff --git a/tests/debug/eval-tuple-extractor.scala b/tests/debug/eval-tuple-extractor.scala new file mode 100644 index 000000000000..c49599c394a0 --- /dev/null +++ b/tests/debug/eval-tuple-extractor.scala @@ -0,0 +1,5 @@ +object Test: + def main(args: Array[String]): Unit = + val t = (1, 2) + val (x, y) = t + println(x + y) diff --git a/tests/debug/eval-value-class.check b/tests/debug/eval-value-class.check new file mode 100644 index 000000000000..4b622ba37841 --- /dev/null +++ b/tests/debug/eval-value-class.check @@ -0,0 +1,37 @@ +break Test$ 24 +eval b1 +result foo +eval size.value +result 2 +eval b2.take(size) +result ba +eval m(bar) +result B(bar) +eval new B("fizz") +result fizz +eval b1 + new B("buzz") +result foobuzz +eval new Msg(new Size(3)) +result Hel + +break B$ 6 +eval x +result foo +eval take(size) +result fo + +break Test$ 25 +eval b1 = new B("fizz") +result () +eval size = new Size(3) +result () + +break Test$ 29 +eval a +result B(fizzbar) +eval a.take(this.size) +result B(fiz) +eval a.asInstanceOf[B] + B("buzz") +result fizzbarbuzz + +break B$ 6 diff --git a/tests/debug/eval-value-class.scala b/tests/debug/eval-value-class.scala new file mode 100644 index 000000000000..73a2f33bdceb --- /dev/null +++ b/tests/debug/eval-value-class.scala @@ -0,0 +1,31 @@ +trait A extends Any: + def take(size: Size): A + +class B(val x: String) extends AnyVal with A: + def take(size: Size): B = + new B(x.take(size.value)) + + def +(b: B): B = + new B(x + b.x) + + override def toString: String = s"B($x)" + +class Size(val value: Int) + +class Msg(size: Size): + override def toString = "Hello, World!".take(size.value) + +object Test: + var b1 = new B("foo") + private var size = new Size(2) + + def main(args: Array[String]): Unit = + val b2 = bar + println(b1.take(size)) + println(m(b1 + b2)) + + def m(a: A): A = + val size = new Size(5) + a.take(size) + + def bar: B = new B("bar") diff --git a/tests/debug/for.check b/tests/debug/for.check new file mode 100644 index 000000000000..0591d66a4030 --- /dev/null +++ b/tests/debug/for.check @@ -0,0 +1,9 @@ +break Test$ 3 +step 4 +eval b +result 72 +eval + val c = b + 1 + c + 1 +result 74 +step 10 diff --git a/tests/debug/for.scala b/tests/debug/for.scala index b2287a988a23..e5592a281d59 100644 --- a/tests/debug/for.scala +++ b/tests/debug/for.scala @@ -1,16 +1,16 @@ object Test { def main(args: Array[String]): Unit = { - val b = 8 * 9 // [break] [step: f()] - f() // [step: val a] + val b = 8 * 9 + f() 20 + b - print(b) + println(b) } def f(): Unit = { - val a = for (i <- 1 to 5; j <- 10 to 20) // [cont] - yield (i, j) // Error: incorrect reaching this line + val a = for (i <- 1 to 5; j <- 10 to 20) + yield (i, j) for (i <- 1 to 5; j <- 10 to 20) - println(i + j) // TODO: i is renamed to i$2 --> reduce debuggability + println(i + j) } } \ No newline at end of file diff --git a/tests/debug/function.check b/tests/debug/function.check new file mode 100644 index 000000000000..af6aa0437806 --- /dev/null +++ b/tests/debug/function.check @@ -0,0 +1,9 @@ +break Test$ 4 +step 5 +step 10 +break Test$ 6 +step 7 +step 8 +next apply$mcIII$sp // specialized Lambda.apply +next 10 +next 11 diff --git a/tests/debug/function.scala b/tests/debug/function.scala index 644344414464..7e099dce7a3c 100644 --- a/tests/debug/function.scala +++ b/tests/debug/function.scala @@ -1,14 +1,14 @@ object Test { def main(args: Array[String]): Unit = { val a = 1 + 2 - val b = a * 9 // [break] [step: plus] [step: c = plus] - val plus = (x: Int, y: Int) => { // [cont: x * x] - val a = x * x // [break] [step: y * y] - val b = y * y // [step: a + b] - a + b // [next] [next] + val b = a * 9 + val plus = (x: Int, y: Int) => { + val a = x * x + val b = y * y + a + b } - val c = plus(a, b) // [next: print] - print(c) // [cont] + val c = plus(a, b) + println(c) } } diff --git a/tests/debug/if.check b/tests/debug/if.check new file mode 100644 index 000000000000..de253554d239 --- /dev/null +++ b/tests/debug/if.check @@ -0,0 +1,8 @@ +break Test$ 4 +step 5 +step 6 +step 8 +step 9 +step 13 +step 16 +step 18 diff --git a/tests/debug/if.scala b/tests/debug/if.scala index af598c1cd40d..8cedcfa5bed0 100644 --- a/tests/debug/if.scala +++ b/tests/debug/if.scala @@ -1,20 +1,20 @@ object Test { def main(args: Array[String]): Unit = { - var a = 1 + 2 // [break] [step: a + 3] - a = a + 3 // [step: 4 + 5] - a = 4 + 5 // [step: if] + var a = 1 + 2 + a = a + 3 + a = 4 + 5 - if (a * 8 > 20) // [step: 9 * 9] - a = 9 * 9 // [step: if] + if (a * 8 > 20) + a = 9 * 9 else a = 34 * 23 - if (a * 8 < 20) // [step: 34 * 23] + if (a * 8 < 20) a = 9 * 9 else - a = 34 * 23 // [step: print] + a = 34 * 23 - print(a) + println(a) } } diff --git a/tests/debug/method.check b/tests/debug/method.check new file mode 100644 index 000000000000..d715b4be63fd --- /dev/null +++ b/tests/debug/method.check @@ -0,0 +1,8 @@ +break Test$ 3 +step 4 +step 5 +step 10 +step 11 +step 12 +step 5 +step 6 diff --git a/tests/debug/method.scala b/tests/debug/method.scala index 9489b0088f3e..96d234a52d28 100644 --- a/tests/debug/method.scala +++ b/tests/debug/method.scala @@ -1,14 +1,14 @@ object Test { def main(args: Array[String]): Unit = { - val a = 1 + 2 // [break] [step: a * 9] - val b = a * 9 // [step: plus] - val c = plus(a, b) // [step: x * x] - print(c) + val a = 1 + 2 + val b = a * 9 + val c = plus(a, b) + println(c) } def plus(x: Int, y: Int) = { - val a = x * x // [step: y * y] - val b = y * y // [step: a + b] - a + b // [step: plus] [step: print] [cont] + val a = x * x + val b = y * y + a + b } } diff --git a/tests/debug/nested-method.check b/tests/debug/nested-method.check new file mode 100644 index 000000000000..60c2de53bc3a --- /dev/null +++ b/tests/debug/nested-method.check @@ -0,0 +1,8 @@ +break Test$ 3 +step 4 +step 12 +step 7 +step 8 +step 9 +step 12 +step 13 diff --git a/tests/debug/nested-method.scala b/tests/debug/nested-method.scala index fcc326ccba25..a27aae833a34 100644 --- a/tests/debug/nested-method.scala +++ b/tests/debug/nested-method.scala @@ -1,15 +1,15 @@ object Test { def main(args: Array[String]): Unit = { - val a = 1 + 2 // [break] [step: a * 9] - val b = a * 9 // [step: plus] [step: x * x] + val a = 1 + 2 + val b = a * 9 def plus(x: Int, y: Int) = { - val a = x * x // [step: y * y] - val b = y * y // [step: a + b] - a + b // [step: plus] + val a = x * x + val b = y * y + a + b } - val c = plus(a, b) // [step: print] [cont] - print(c) + val c = plus(a, b) + println(c) } } \ No newline at end of file diff --git a/tests/debug/sequence.check b/tests/debug/sequence.check new file mode 100644 index 000000000000..4816863d6798 --- /dev/null +++ b/tests/debug/sequence.check @@ -0,0 +1,7 @@ +break Test$ 3 +step 4 +step 5 +step 6 +step 7 +step 8 +step 9 diff --git a/tests/debug/sequence.scala b/tests/debug/sequence.scala index a6c1e90185b9..7e948c85c692 100644 --- a/tests/debug/sequence.scala +++ b/tests/debug/sequence.scala @@ -1,11 +1,11 @@ object Test { def main(args: Array[String]): Unit = { - var a = 1 + 2 // [break] [step: a + 3] - a = a + 3 // [step: 4 + 5] - a = 4 + 5 // [step: a * 8] - a = a * 8 // [step: 9 * 9] - a = 9 * 9 // [step: 34 * 23] - a = 34 * 23 // [step: print] - print(a) // [cont] + var a = 1 + 2 + a = a + 3 + a = 4 + 5 + a = a * 8 + a = 9 * 9 + a = 34 * 23 + println(a) } } \ No newline at end of file diff --git a/tests/debug/tailrec.check b/tests/debug/tailrec.check new file mode 100644 index 000000000000..713880f8d234 --- /dev/null +++ b/tests/debug/tailrec.check @@ -0,0 +1,10 @@ +break Test$ 12 +step 13 +step 3 +step 6 +step 3 +break Test$ 14 +step 3 +step 4 +step 14 +step 15 diff --git a/tests/debug/tailrec.scala b/tests/debug/tailrec.scala index f79514fa3a99..07e63728628e 100644 --- a/tests/debug/tailrec.scala +++ b/tests/debug/tailrec.scala @@ -3,15 +3,15 @@ object Test { if (x == 0) 1 else - x * fact(x - 1) // TODO: incorrect this line when x = 0 + x * fact(x - 1) } def main(args: Array[String]): Unit = { val a = 1 + 2 - val b = a * 9 // [break] [step: fact] - val c = fact(a) // [step: x == 0] [step: fact(x - 1)] [step: x == 0] [cont] - fact(0) // [break] [step: x == 0] [step: 1] [step: fact(x - 1)] [step: print] - print(c) // [cont] + val b = a * 9 + val c = fact(a) + fact(0) + println(c) } } \ No newline at end of file diff --git a/tests/debug/while.check b/tests/debug/while.check new file mode 100644 index 000000000000..2e051cee0dfe --- /dev/null +++ b/tests/debug/while.check @@ -0,0 +1,5 @@ +break Test$ 6 +step 8 +step 9 +step 8 +break Test$ 12 diff --git a/tests/debug/while.scala b/tests/debug/while.scala index 0e5f8f8b0b9c..16f4675824e6 100644 --- a/tests/debug/while.scala +++ b/tests/debug/while.scala @@ -3,12 +3,12 @@ object Test { def main(args: Array[String]): Unit = { var a = 1 + 2 a = a + 3 - a = 4 + 5 // [break] [step: while] + a = 4 + 5 - while (a * 8 < 100) { // [step: a += 1] - a += 1 // [step: while] [cont: print] + while (a * 8 < 100) { + a += 1 } - print(a) // [break] [cont] + println(a) } } diff --git a/tests/disabled/neg-custom-args/captures/capt-wf.scala b/tests/disabled/neg-custom-args/captures/capt-wf.scala index bfe349747776..302202064ac0 100644 --- a/tests/disabled/neg-custom-args/captures/capt-wf.scala +++ b/tests/disabled/neg-custom-args/captures/capt-wf.scala @@ -1,7 +1,7 @@ // No longer valid class C -type Cap = C @retains(caps.*) -type Top = Any @retains(caps.*) +type Cap = C @retains(caps.cap) +type Top = Any @retains(caps.cap) type T = (x: Cap) => List[String @retains(x)] => Unit // error val x: (x: Cap) => Array[String @retains(x)] = ??? // error diff --git a/tests/disabled/neg-custom-args/captures/try2.scala b/tests/disabled/neg-custom-args/captures/try2.scala index 876dc1ec12f1..43e17d8c9eef 100644 --- a/tests/disabled/neg-custom-args/captures/try2.scala +++ b/tests/disabled/neg-custom-args/captures/try2.scala @@ -5,7 +5,7 @@ import annotation.ability @ability erased val canThrow: * = ??? class CanThrow[E <: Exception] extends Retains[canThrow.type] -type Top = Any @retains(caps.*) +type Top = Any @retains(caps.cap) infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?=> R diff --git a/tests/init-global/pos/array-size-zero.scala b/tests/init-global/pos-tasty/array-size-zero.scala similarity index 100% rename from tests/init-global/pos/array-size-zero.scala rename to tests/init-global/pos-tasty/array-size-zero.scala diff --git a/tests/init-global/pos/arithmetic.scala b/tests/init-global/pos/arithmetic.scala new file mode 100644 index 000000000000..0b4c9e3bb850 --- /dev/null +++ b/tests/init-global/pos/arithmetic.scala @@ -0,0 +1,5 @@ +object A: + val a = f(10) + val b = -a + val c = b.toDouble + def f(x: Int) = x * 2 + 5 \ No newline at end of file diff --git a/tests/init/pos/byname.scala b/tests/init-global/pos/byname.scala similarity index 60% rename from tests/init/pos/byname.scala rename to tests/init-global/pos/byname.scala index fdfbd101cc93..65dddf51512d 100644 --- a/tests/init/pos/byname.scala +++ b/tests/init-global/pos/byname.scala @@ -5,12 +5,12 @@ class A extends T: override def bar(i: => Int): Int = i + 1 class B extends T: - override def bar(i: => Int): Int = i + 2 + override def bar(i: => Int): Int = i object A: val a: T = if ??? then new A else new B - def foo(b: List[Int]) = a.bar(b match { - case x :: xs => 1 + def foo(b: List[Int]): Int = a.bar(b match { + case head :: rest => head + foo(rest) + a.bar(head) case Nil => 0 }) diff --git a/tests/init-global/pos/packageObjectStringInterpolator.scala b/tests/init-global/pos/packageObjectStringInterpolator.scala new file mode 100644 index 000000000000..21b16c81269f --- /dev/null +++ b/tests/init-global/pos/packageObjectStringInterpolator.scala @@ -0,0 +1,16 @@ +package p +package object a { + val b = 10 + implicit class CI(s: StringContext) { + def ci(args: Any*) = 10 + } +} + +import p.a._ + +object A: + val f = b // p.a(ObjectRef(p.a)).b + def foo(s: String): String = s + val f1 = ci"a" // => p.a(Package(p).select(a)).CI(StringContext"a").ci() + + diff --git a/tests/init-global/warn/patmat.check b/tests/init-global/warn-tasty/patmat.check similarity index 100% rename from tests/init-global/warn/patmat.check rename to tests/init-global/warn-tasty/patmat.check diff --git a/tests/init-global/warn/patmat.scala b/tests/init-global/warn-tasty/patmat.scala similarity index 100% rename from tests/init-global/warn/patmat.scala rename to tests/init-global/warn-tasty/patmat.scala diff --git a/tests/init-global/warn/unapplySeq-implicit-arg.check b/tests/init-global/warn-tasty/unapplySeq-implicit-arg.check similarity index 77% rename from tests/init-global/warn/unapplySeq-implicit-arg.check rename to tests/init-global/warn-tasty/unapplySeq-implicit-arg.check index ec08187f058f..92bd3871c5d2 100644 --- a/tests/init-global/warn/unapplySeq-implicit-arg.check +++ b/tests/init-global/warn-tasty/unapplySeq-implicit-arg.check @@ -1,4 +1,4 @@ --- Warning: tests/init-global/warn/unapplySeq-implicit-arg.scala:11:20 ------------------------------------------------- +-- Warning: tests/init-global/warn-tasty/unapplySeq-implicit-arg.scala:11:20 ------------------------------------------- 11 | val i2: Int = Seq(i2) match // warn | ^^ | Access uninitialized field value i2. Calling trace: diff --git a/tests/init-global/warn/unapplySeq-implicit-arg.scala b/tests/init-global/warn-tasty/unapplySeq-implicit-arg.scala similarity index 100% rename from tests/init-global/warn/unapplySeq-implicit-arg.scala rename to tests/init-global/warn-tasty/unapplySeq-implicit-arg.scala diff --git a/tests/init-global/warn/unapplySeq-implicit-arg2.check b/tests/init-global/warn-tasty/unapplySeq-implicit-arg2.check similarity index 80% rename from tests/init-global/warn/unapplySeq-implicit-arg2.check rename to tests/init-global/warn-tasty/unapplySeq-implicit-arg2.check index e75b66495d4b..07a9bd97001c 100644 --- a/tests/init-global/warn/unapplySeq-implicit-arg2.check +++ b/tests/init-global/warn-tasty/unapplySeq-implicit-arg2.check @@ -1,4 +1,4 @@ --- Warning: tests/init-global/warn/unapplySeq-implicit-arg2.scala:4:9 -------------------------------------------------- +-- Warning: tests/init-global/warn-tasty/unapplySeq-implicit-arg2.scala:4:9 -------------------------------------------- 4 | Some(i1 +: seqi) // warn | ^^ |Access uninitialized field value i1. Calling trace: diff --git a/tests/init-global/warn/unapplySeq-implicit-arg2.scala b/tests/init-global/warn-tasty/unapplySeq-implicit-arg2.scala similarity index 100% rename from tests/init-global/warn/unapplySeq-implicit-arg2.scala rename to tests/init-global/warn-tasty/unapplySeq-implicit-arg2.scala diff --git a/tests/init-global/warn/unapplySeq-implicit-arg3.check b/tests/init-global/warn-tasty/unapplySeq-implicit-arg3.check similarity index 84% rename from tests/init-global/warn/unapplySeq-implicit-arg3.check rename to tests/init-global/warn-tasty/unapplySeq-implicit-arg3.check index 7674298a3665..ca8e4ed83f8d 100644 --- a/tests/init-global/warn/unapplySeq-implicit-arg3.check +++ b/tests/init-global/warn-tasty/unapplySeq-implicit-arg3.check @@ -1,4 +1,4 @@ --- Warning: tests/init-global/warn/unapplySeq-implicit-arg3.scala:3:27 ------------------------------------------------- +-- Warning: tests/init-global/warn-tasty/unapplySeq-implicit-arg3.scala:3:27 ------------------------------------------- 3 | def m(seq: Seq[Int]) = i1 +: seq // warn | ^^ |Access uninitialized field value i1. Calling trace: diff --git a/tests/init-global/warn/unapplySeq-implicit-arg3.scala b/tests/init-global/warn-tasty/unapplySeq-implicit-arg3.scala similarity index 100% rename from tests/init-global/warn/unapplySeq-implicit-arg3.scala rename to tests/init-global/warn-tasty/unapplySeq-implicit-arg3.scala diff --git a/tests/init-global/warn/lazy-local-val.scala b/tests/init-global/warn/lazy-local-val.scala index 30ae864ab169..8a8ca90e4ed8 100644 --- a/tests/init-global/warn/lazy-local-val.scala +++ b/tests/init-global/warn/lazy-local-val.scala @@ -15,5 +15,5 @@ object B: lazy val b = a Box(b) - val box = f(n) // warn + val box = f(n) val n = 10 diff --git a/tests/init-global/warn/mutable-array.check b/tests/init-global/warn/mutable-array.check index 7618f3470433..1bc9146ceb4d 100644 --- a/tests/init-global/warn/mutable-array.check +++ b/tests/init-global/warn/mutable-array.check @@ -1,11 +1,11 @@ --- Warning: tests/init-global/warn/mutable-array.scala:8:19 ------------------------------------------------------------ -8 | val x: Int = box.value // warn +-- Warning: tests/init-global/warn/mutable-array.scala:9:19 ------------------------------------------------------------ +9 | val x: Int = box.value // warn | ^^^^^^^^^ |Reading mutable state of object A during initialization of object B. |Reading mutable state of other static objects is forbidden as it breaks initialization-time irrelevance. Calling trace: |├── object B: [ mutable-array.scala:5 ] |│ ^ - |└── val x: Int = box.value // warn [ mutable-array.scala:8 ] + |└── val x: Int = box.value // warn [ mutable-array.scala:9 ] | ^^^^^^^^^ |The mutable state is created through: |├── object A: [ mutable-array.scala:1 ] diff --git a/tests/init-global/warn/mutable-array.scala b/tests/init-global/warn/mutable-array.scala index a0ea2ea0f465..43556225a670 100644 --- a/tests/init-global/warn/mutable-array.scala +++ b/tests/init-global/warn/mutable-array.scala @@ -3,6 +3,7 @@ object A: val box: Box = new Box(0) object B: - val boxes: Array[A.Box] = Array(A.box) + val boxes = new Array[A.Box](2) + boxes(0) = A.box val box: A.Box = boxes(0) val x: Int = box.value // warn diff --git a/tests/init-global/warn/widen.check b/tests/init-global/warn/widen.check new file mode 100644 index 000000000000..b3191d023110 --- /dev/null +++ b/tests/init-global/warn/widen.check @@ -0,0 +1,20 @@ +-- Warning: tests/init-global/warn/widen.scala:13:13 ------------------------------------------------------------------- +13 | t.foo() // warn + | ^^^^^^^ + | Value is unknown to the checker due to widening. Calling trace: + | ├── object O: [ widen.scala:9 ] + | │ ^ + | ├── val a = bar(new C) [ widen.scala:20 ] + | │ ^^^^^^^^^^ + | ├── def bar(t: T) = { [ widen.scala:10 ] + | │ ^ + | ├── new A [ widen.scala:18 ] + | │ ^^^^^ + | ├── class A { [ widen.scala:11 ] + | │ ^ + | ├── val b = new B [ widen.scala:16 ] + | │ ^^^^^ + | ├── class B { [ widen.scala:12 ] + | │ ^ + | └── t.foo() // warn [ widen.scala:13 ] + | ^^^^^^^ diff --git a/tests/init-global/warn/widen.scala b/tests/init-global/warn/widen.scala new file mode 100644 index 000000000000..157434a0f3e4 --- /dev/null +++ b/tests/init-global/warn/widen.scala @@ -0,0 +1,20 @@ +trait T { + def foo(): Unit +} + +class C extends T { + def foo(): Unit = println("Calling foo on an instance of C!") +} + +object O: + def bar(t: T) = { + class A { + class B { + t.foo() // warn + } + + val b = new B + } + new A + } + val a = bar(new C) \ No newline at end of file diff --git a/tests/init/tasty-error/Main.scala b/tests/init/tasty-error/Main.scala new file mode 100644 index 000000000000..2b27dd2b0d1f --- /dev/null +++ b/tests/init/tasty-error/Main.scala @@ -0,0 +1 @@ +class Main extends B{} // anypos-error \ No newline at end of file diff --git a/tests/init/tasty-error/v0/A.scala b/tests/init/tasty-error/v0/A.scala new file mode 100644 index 000000000000..be8c7d214378 --- /dev/null +++ b/tests/init/tasty-error/v0/A.scala @@ -0,0 +1,3 @@ +class A { + def fail(a: Int, b: Int): Int = a +} \ No newline at end of file diff --git a/tests/init/tasty-error/v1/A.scala b/tests/init/tasty-error/v1/A.scala new file mode 100644 index 000000000000..fa2956d1de7e --- /dev/null +++ b/tests/init/tasty-error/v1/A.scala @@ -0,0 +1,3 @@ +class A { + def fail(a: Int): Int = a +} \ No newline at end of file diff --git a/tests/init/tasty-error/v1/B.scala b/tests/init/tasty-error/v1/B.scala new file mode 100644 index 000000000000..3059f487db64 --- /dev/null +++ b/tests/init/tasty-error/v1/B.scala @@ -0,0 +1,4 @@ +class B { + val a: A = new A + val x = a.fail(0) +} \ No newline at end of file diff --git a/tests/neg-custom-args/captures/boundschecks2.scala b/tests/neg-custom-args/captures/boundschecks2.scala index 923758d722f9..99366a8e7aff 100644 --- a/tests/neg-custom-args/captures/boundschecks2.scala +++ b/tests/neg-custom-args/captures/boundschecks2.scala @@ -8,6 +8,6 @@ object test { val foo: C[Tree^] = ??? // error type T = C[Tree^] // error - val bar: T -> T = ??? + //val bar: T -> T = ??? // --> boundschecks3.scala for what happens if we uncomment val baz: C[Tree^] -> Unit = ??? // error } diff --git a/tests/neg-custom-args/captures/boundschecks3.check b/tests/neg-custom-args/captures/boundschecks3.check new file mode 100644 index 000000000000..36e1336e8f05 --- /dev/null +++ b/tests/neg-custom-args/captures/boundschecks3.check @@ -0,0 +1,4 @@ +-- Error: tests/neg-custom-args/captures/boundschecks3.scala:11:13 ----------------------------------------------------- +11 | val bar: T -> T = ??? // error, since `T` is expanded here. But the msg is not very good. + | ^^^^^^ + | test.C[box test.Tree^] captures the root capability `cap` in invariant position diff --git a/tests/neg-custom-args/captures/boundschecks3.scala b/tests/neg-custom-args/captures/boundschecks3.scala new file mode 100644 index 000000000000..f5e9652c0913 --- /dev/null +++ b/tests/neg-custom-args/captures/boundschecks3.scala @@ -0,0 +1,13 @@ +object test { + + class Tree + + def f[X <: Tree](x: X): Unit = () + + class C[X <: Tree](x: X) + + val foo: C[Tree^] = ??? // hidden error + type T = C[Tree^] // hidden error + val bar: T -> T = ??? // error, since `T` is expanded here. But the msg is not very good. + val baz: C[Tree^] -> Unit = ??? // hidden error +} diff --git a/tests/neg-custom-args/captures/box-adapt-cases.check b/tests/neg-custom-args/captures/box-adapt-cases.check new file mode 100644 index 000000000000..7ff185c499a5 --- /dev/null +++ b/tests/neg-custom-args/captures/box-adapt-cases.check @@ -0,0 +1,14 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/box-adapt-cases.scala:14:10 ------------------------------ +14 | x.value(cap => cap.use()) // error + | ^^^^^^^^^^^^^^^^ + | Found: (cap: box Cap^?) ->{io} Int + | Required: (cap: box Cap^{io}) -> Int + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/box-adapt-cases.scala:28:10 ------------------------------ +28 | x.value(cap => cap.use()) // error + | ^^^^^^^^^^^^^^^^ + | Found: (cap: box Cap^?) ->{io, fs} Int + | Required: (cap: box Cap^{io, fs}) ->{io} Int + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/box-adapt-cases.scala b/tests/neg-custom-args/captures/box-adapt-cases.scala index d9ec0f80a548..8f7d7a0a6667 100644 --- a/tests/neg-custom-args/captures/box-adapt-cases.scala +++ b/tests/neg-custom-args/captures/box-adapt-cases.scala @@ -1,29 +1,29 @@ trait Cap { def use(): Int } def test1(): Unit = { - type Id[X] = [T] -> (op: X => T) -> T + class Id[X](val value: [T] -> (op: X => T) -> T) val x: Id[Cap^] = ??? - x(cap => cap.use()) + x.value(cap => cap.use()) } def test2(io: Cap^): Unit = { - type Id[X] = [T] -> (op: X -> T) -> T + class Id[X](val value: [T] -> (op: X -> T) -> T) val x: Id[Cap^{io}] = ??? - x(cap => cap.use()) // error + x.value(cap => cap.use()) // error } def test3(io: Cap^): Unit = { - type Id[X] = [T] -> (op: X ->{io} T) -> T + class Id[X](val value: [T] -> (op: X ->{io} T) -> T) val x: Id[Cap^{io}] = ??? - x(cap => cap.use()) // ok + x.value(cap => cap.use()) // ok } def test4(io: Cap^, fs: Cap^): Unit = { - type Id[X] = [T] -> (op: X ->{io} T) -> T + class Id[X](val value: [T] -> (op: X ->{io} T) -> T) val x: Id[Cap^{io, fs}] = ??? - x(cap => cap.use()) // error + x.value(cap => cap.use()) // error } diff --git a/tests/neg-custom-args/captures/box-adapt-cov.scala b/tests/neg-custom-args/captures/box-adapt-cov.scala index 2c1f15a5c77f..e1e6dd4cec1a 100644 --- a/tests/neg-custom-args/captures/box-adapt-cov.scala +++ b/tests/neg-custom-args/captures/box-adapt-cov.scala @@ -1,14 +1,14 @@ trait Cap def test1(io: Cap^) = { - type Op[X] = [T] -> Unit -> X + class Op[+X](val value: [T] -> Unit -> X) val f: Op[Cap^{io}] = ??? - val x: [T] -> Unit -> Cap^{io} = f // error + val x: [T] -> Unit -> Cap^{io} = f.value // error } def test2(io: Cap^) = { - type Op[X] = [T] -> Unit -> X^{io} + class Op[+X](val value: [T] -> Unit -> X^{io}) val f: Op[Cap^{io}] = ??? - val x: Unit -> Cap^{io} = f[Unit] // error - val x1: Unit ->{io} Cap^{io} = f[Unit] // ok + val x: Unit -> Cap^{io} = f.value[Unit] // error + val x1: Unit ->{io} Cap^{io} = f.value[Unit] // ok } diff --git a/tests/neg-custom-args/captures/box-adapt-depfun.scala b/tests/neg-custom-args/captures/box-adapt-depfun.scala index d1c1c73f8207..f673c657f753 100644 --- a/tests/neg-custom-args/captures/box-adapt-depfun.scala +++ b/tests/neg-custom-args/captures/box-adapt-depfun.scala @@ -1,23 +1,23 @@ trait Cap { def use(): Int } def test1(io: Cap^): Unit = { - type Id[X] = [T] -> (op: X ->{io} T) -> T + class Id[X](val value: [T] -> (op: X ->{io} T) -> T) val x: Id[Cap]^{io} = ??? - x(cap => cap.use()) // ok + x.value(cap => cap.use()) // ok } def test2(io: Cap^): Unit = { - type Id[X] = [T] -> (op: (x: X) ->{io} T) -> T + class Id[X](val value: [T] -> (op: (x: X) ->{io} T) -> T) val x: Id[Cap^{io}] = ??? - x(cap => cap.use()) + x.value(cap => cap.use()) // should work when the expected type is a dependent function } def test3(io: Cap^): Unit = { - type Id[X] = [T] -> (op: (x: X) ->{} T) -> T + class Id[X](val value: [T] -> (op: (x: X) ->{} T) -> T) val x: Id[Cap^{io}] = ??? - x(cap => cap.use()) // error + x.value(cap => cap.use()) // error } diff --git a/tests/neg-custom-args/captures/box-adapt-typefun.scala b/tests/neg-custom-args/captures/box-adapt-typefun.scala index 175acdda1c8f..76da047f42a9 100644 --- a/tests/neg-custom-args/captures/box-adapt-typefun.scala +++ b/tests/neg-custom-args/captures/box-adapt-typefun.scala @@ -1,13 +1,13 @@ trait Cap { def use(): Int } def test1(io: Cap^): Unit = { - type Op[X] = [T] -> X -> Unit + class Op[X](val value: [T] -> X -> Unit) val f: [T] -> (Cap^{io}) -> Unit = ??? - val op: Op[Cap^{io}] = f // error + val op: Op[Cap^{io}] = Op(f) // was error, now ok } def test2(io: Cap^): Unit = { - type Lazy[X] = [T] -> Unit -> X + class Lazy[X](val value: [T] -> Unit -> X) val f: Lazy[Cap^{io}] = ??? - val test: [T] -> Unit -> (Cap^{io}) = f // error + val test: [T] -> Unit -> (Cap^{io}) = f.value // error } diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check index f63c55ca48c4..15fe32fc7653 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -36,15 +36,15 @@ -- Error: tests/neg-custom-args/captures/capt1.scala:34:16 ------------------------------------------------------------- 34 | val z2 = h[() -> Cap](() => x) // error // error | ^^^^^^^^^ - | Type variable X of method h cannot be instantiated to () -> box C^ since - | the part box C^ of that type captures the root capability `cap`. + | Type variable X of method h cannot be instantiated to () -> (ex$15: scala.caps.Exists) -> C^{ex$15} since + | the part C^{ex$15} of that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/capt1.scala:34:30 ------------------------------------------------------------- 34 | val z2 = h[() -> Cap](() => x) // error // error | ^ - | reference (x : C^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> box C^ + | reference (x : C^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> (ex$15: scala.caps.Exists) -> C^{ex$15} -- Error: tests/neg-custom-args/captures/capt1.scala:36:13 ------------------------------------------------------------- 36 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error | ^^^^^^^^^^^^^^^^^^^^^^^ - | Type variable X of method h cannot be instantiated to box () ->{x} Cap since - | the part Cap of that type captures the root capability `cap`. + |Type variable X of method h cannot be instantiated to box () ->{x} (ex$20: scala.caps.Exists) -> C^{ex$20} since + |the part C^{ex$20} of that type captures the root capability `cap`. diff --git a/tests/neg-custom-args/captures/capture-vars-subtyping.scala b/tests/neg-custom-args/captures/capture-vars-subtyping.scala new file mode 100644 index 000000000000..1986a0aa33fc --- /dev/null +++ b/tests/neg-custom-args/captures/capture-vars-subtyping.scala @@ -0,0 +1,48 @@ +import language.experimental.captureChecking +import caps.* + +def test[C^] = + val a: C = ??? + val b: CapSet^{C^} = a + val c: C = b + val d: CapSet^{C^, c} = a + +// TODO: make "CapSet-ness" of type variables somehow contagious? +// Then we don't have to spell out the bounds explicitly... +def testTrans[C^, D >: CapSet <: C, E >: CapSet <: D, F >: C <: CapSet^] = + val d1: D = ??? + val d2: CapSet^{D^} = d1 + val d3: D = d2 + val e1: E = ??? + val e2: CapSet^{E^} = e1 + val e3: E = e2 + val d4: D = e1 + val c1: C = d1 + val c2: C = e1 + val f1: F = c1 + val d_e_f1: CapSet^{D^,E^,F^} = d1 + val d_e_f2: CapSet^{D^,E^,F^} = e1 + val d_e_f3: CapSet^{D^,E^,F^} = f1 + val f2: F = d_e_f1 + val c3: C = d_e_f1 // error + val c4: C = f1 // error + val e4: E = f1 // error + val e5: E = d1 // error + val c5: CapSet^{C^} = e1 + + +trait A[+T] + +trait B[-C] + +def testCong[C^, D^] = + val a: A[C] = ??? + val b: A[CapSet^{C^}] = a + val c: A[CapSet^{D^}] = a // error + val d: A[CapSet^{C^,D^}] = a + val e: A[C] = d // error + val f: B[C] = ??? + val g: B[CapSet^{C^}] = f + val h: B[C] = g + val i: B[CapSet^{C^,D^}] = h // error + val j: B[C] = i diff --git a/tests/neg-custom-args/captures/capture-vars-subtyping2.scala b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala new file mode 100644 index 000000000000..205451ee41ed --- /dev/null +++ b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala @@ -0,0 +1,44 @@ +import language.experimental.captureChecking +import caps.* + +trait BoundsTest: + + trait Bar { val f: () => Unit } + def bar(x: Bar^, y: () ->{x.f} Unit): Unit = ??? + + val b: Bar^ = ??? + + def testTransMixed[A^, + B >: CapSet <: A, + C >: CapSet <: CapSet^{B^}, + D >: CapSet <: C, + E >: CapSet <: CapSet^{D^}, + F >: CapSet <: CapSet^{A^,b}, + X >: CapSet <: CapSet^{F^,D^}, + Y >: CapSet^{F^} <: CapSet^{F^,A^,b}, + Z >: CapSet^{b} <: CapSet^{b,Y^}] = + val e: E = ??? + val e2: CapSet^{E^} = e + val ed: D = e + val ed2: CapSet^{D^} = e + val ec: C = e + val ec2: CapSet^{C^} = e + val eb: B = e + val eb2: CapSet^{B^} = e + val ea: A = e + val ea2: CapSet^{A^} = e + val ex: X = e // error + val ex2: CapSet^{X^} = e // error + val f: F = ??? + val f2: CapSet^{F^} = f + val y: Y = f + val y2: CapSet^{Y^} = f + val cb: CapSet^{b} = ??? + val z: Z = cb + val z2: CapSet^{Z^} = cb + + def callTransMixed = + val x, y, z: Bar^ = ??? + testTransMixed[CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{b,x,y,z}] + testTransMixed[CapSet^{x,y,z}, CapSet^{x,y}, CapSet^{x,y}, CapSet^{x}, CapSet^{}, CapSet^{b,x}, CapSet^{b}, CapSet^{b,x}, CapSet^{b}] + testTransMixed[CapSet^{x,y,z}, CapSet^{x,y}, CapSet^{x,y}, CapSet^{x}, CapSet^{}, CapSet^{b,x}, CapSet^{b}, CapSet^{b,x}, CapSet^{b,x,y,z}] // error diff --git a/tests/neg-custom-args/captures/cc-ex-conformance.scala b/tests/neg-custom-args/captures/cc-ex-conformance.scala index a953466daa9a..16e13376c5b3 100644 --- a/tests/neg-custom-args/captures/cc-ex-conformance.scala +++ b/tests/neg-custom-args/captures/cc-ex-conformance.scala @@ -21,5 +21,5 @@ def Test = val ex3: EX3 = ??? val ex4: EX4 = ??? val _: EX4 = ex3 // ok - val _: EX4 = ex4 + val _: EX4 = ex4 // error (???) Probably since we also introduce existentials on expansion val _: EX3 = ex4 // error diff --git a/tests/neg-custom-args/captures/cc-poly-1.check b/tests/neg-custom-args/captures/cc-poly-1.check index ea486f55a61f..0ac35b532916 100644 --- a/tests/neg-custom-args/captures/cc-poly-1.check +++ b/tests/neg-custom-args/captures/cc-poly-1.check @@ -1,12 +1,12 @@ -- [E057] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-1.scala:12:6 ------------------------------------- 12 | f[Any](D()) // error | ^ - | Type argument Any does not conform to upper bound caps.CapSet^ + | Type argument Any does not conform to upper bound scala.caps.CapSet^ | | longer explanation available when compiling with `-explain` -- [E057] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-1.scala:13:6 ------------------------------------- 13 | f[String](D()) // error | ^ - | Type argument String does not conform to upper bound caps.CapSet^ + | Type argument String does not conform to upper bound scala.caps.CapSet^ | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/cc-this2.check b/tests/neg-custom-args/captures/cc-this2.check index 6cb3010d6174..dc61fe2e0396 100644 --- a/tests/neg-custom-args/captures/cc-this2.check +++ b/tests/neg-custom-args/captures/cc-this2.check @@ -2,7 +2,7 @@ -- Error: tests/neg-custom-args/captures/cc-this2/D_2.scala:3:8 -------------------------------------------------------- 3 | this: D^ => // error | ^^ - |reference (caps.cap : caps.Capability) captured by this self type is not included in the allowed capture set {} of pure base class class C + | reference cap captured by this self type is not included in the allowed capture set {} of pure base class class C -- [E058] Type Mismatch Error: tests/neg-custom-args/captures/cc-this2/D_2.scala:2:6 ----------------------------------- 2 |class D extends C: // error | ^ diff --git a/tests/neg-custom-args/captures/exception-definitions.check b/tests/neg-custom-args/captures/exception-definitions.check index 3f2b15f312b9..67f0f3b72cbb 100644 --- a/tests/neg-custom-args/captures/exception-definitions.check +++ b/tests/neg-custom-args/captures/exception-definitions.check @@ -1,7 +1,7 @@ -- Error: tests/neg-custom-args/captures/exception-definitions.scala:3:8 ----------------------------------------------- 3 | self: Err^ => // error | ^^^^ - |reference (caps.cap : caps.Capability) captured by this self type is not included in the allowed capture set {} of pure base class class Throwable + |reference cap captured by this self type is not included in the allowed capture set {} of pure base class class Throwable -- Error: tests/neg-custom-args/captures/exception-definitions.scala:7:12 ---------------------------------------------- 7 | val x = c // error | ^ diff --git a/tests/neg-custom-args/captures/existential-mapping.check b/tests/neg-custom-args/captures/existential-mapping.check index cd71337868e1..43f4e97e6a2a 100644 --- a/tests/neg-custom-args/captures/existential-mapping.check +++ b/tests/neg-custom-args/captures/existential-mapping.check @@ -5,84 +5,84 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:9:25 --------------------------- 9 | val _: (x: C^) -> C = x1 // error | ^^ - | Found: (x1 : (x: C^) -> (ex$3: caps.Exists) -> C^{ex$3}) + | Found: (x1 : (x: C^) -> (ex$3: scala.caps.Exists) -> C^{ex$3}) | Required: (x: C^) -> C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:12:20 -------------------------- 12 | val _: C^ -> C = x2 // error | ^^ - | Found: (x2 : C^ -> (ex$7: caps.Exists) -> C^{ex$7}) + | Found: (x2 : C^ -> (ex$7: scala.caps.Exists) -> C^{ex$7}) | Required: C^ -> C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:15:30 -------------------------- 15 | val _: A^ -> (x: C^) -> C = x3 // error | ^^ - | Found: (x3 : A^ -> (x: C^) -> (ex$11: caps.Exists) -> C^{ex$11}) + | Found: (x3 : A^ -> (x: C^) -> (ex$11: scala.caps.Exists) -> C^{ex$11}) | Required: A^ -> (x: C^) -> C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:18:25 -------------------------- 18 | val _: A^ -> C^ -> C = x4 // error | ^^ - | Found: (x4 : A^ -> C^ -> (ex$19: caps.Exists) -> C^{ex$19}) + | Found: (x4 : A^ -> C^ -> (ex$19: scala.caps.Exists) -> C^{ex$19}) | Required: A^ -> C^ -> C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:21:30 -------------------------- 21 | val _: A^ -> (x: C^) -> C = x5 // error | ^^ - | Found: (x5 : A^ -> (ex$27: caps.Exists) -> Fun[C^{ex$27}]) + | Found: (x5 : A^ -> (x: C^) -> (ex$27: scala.caps.Exists) -> C^{ex$27}) | Required: A^ -> (x: C^) -> C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:24:30 -------------------------- 24 | val _: A^ -> (x: C^) => C = x6 // error | ^^ - | Found: (x6 : A^ -> (ex$33: caps.Exists) -> IFun[C^{ex$33}]) - | Required: A^ -> (ex$36: caps.Exists) -> (x: C^) ->{ex$36} C + | Found: (x6 : A^ -> (ex$36: scala.caps.Exists) -> (x: C^) ->{ex$36} (ex$35: scala.caps.Exists) -> C^{ex$35}) + | Required: A^ -> (ex$39: scala.caps.Exists) -> (x: C^) ->{ex$39} C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:27:25 -------------------------- 27 | val _: (x: C^) => C = y1 // error | ^^ - | Found: (y1 : (x: C^) => (ex$38: caps.Exists) -> C^{ex$38}) + | Found: (y1 : (x: C^) => (ex$41: scala.caps.Exists) -> C^{ex$41}) | Required: (x: C^) => C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:30:20 -------------------------- 30 | val _: C^ => C = y2 // error | ^^ - | Found: (y2 : C^ => (ex$42: caps.Exists) -> C^{ex$42}) + | Found: (y2 : C^ => (ex$45: scala.caps.Exists) -> C^{ex$45}) | Required: C^ => C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:33:30 -------------------------- 33 | val _: A^ => (x: C^) => C = y3 // error | ^^ - | Found: (y3 : A^ => (ex$47: caps.Exists) -> (x: C^) ->{ex$47} (ex$46: caps.Exists) -> C^{ex$46}) - | Required: A^ => (ex$50: caps.Exists) -> (x: C^) ->{ex$50} C + | Found: (y3 : A^ => (ex$50: scala.caps.Exists) -> (x: C^) ->{ex$50} (ex$49: scala.caps.Exists) -> C^{ex$49}) + | Required: A^ => (ex$53: scala.caps.Exists) -> (x: C^) ->{ex$53} C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:36:25 -------------------------- 36 | val _: A^ => C^ => C = y4 // error | ^^ - | Found: (y4 : A^ => (ex$53: caps.Exists) -> C^ ->{ex$53} (ex$52: caps.Exists) -> C^{ex$52}) - | Required: A^ => (ex$56: caps.Exists) -> C^ ->{ex$56} C + | Found: (y4 : A^ => (ex$56: scala.caps.Exists) -> C^ ->{ex$56} (ex$55: scala.caps.Exists) -> C^{ex$55}) + | Required: A^ => (ex$59: scala.caps.Exists) -> C^ ->{ex$59} C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:39:30 -------------------------- 39 | val _: A^ => (x: C^) -> C = y5 // error | ^^ - | Found: (y5 : A^ => (ex$58: caps.Exists) -> Fun[C^{ex$58}]) + | Found: (y5 : A^ => (x: C^) -> (ex$61: scala.caps.Exists) -> C^{ex$61}) | Required: A^ => (x: C^) -> C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:42:30 -------------------------- 42 | val _: A^ => (x: C^) => C = y6 // error | ^^ - | Found: (y6 : A^ => (ex$64: caps.Exists) -> IFun[C^{ex$64}]) - | Required: A^ => (ex$67: caps.Exists) -> (x: C^) ->{ex$67} C + | Found: (y6 : A^ => (ex$70: scala.caps.Exists) -> (x: C^) ->{ex$70} (ex$69: scala.caps.Exists) -> C^{ex$69}) + | Required: A^ => (ex$73: scala.caps.Exists) -> (x: C^) ->{ex$73} C | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i15922.scala b/tests/neg-custom-args/captures/i15922.scala index 848a22fe5341..b8bcc346ef81 100644 --- a/tests/neg-custom-args/captures/i15922.scala +++ b/tests/neg-custom-args/captures/i15922.scala @@ -1,8 +1,8 @@ trait Cap { def use(): Int } -type Id[X] = [T] -> (op: X => T) -> T -def mkId[X](x: X): Id[X] = [T] => (op: X => T) => op(x) +class Id[+X](val value: [T] -> (op: X => T) -> T) +def mkId[X](x: X): Id[X] = Id([T] => (op: X => T) => op(x)) def withCap[X](op: (Cap^) => X): X = { val cap: Cap^ = new Cap { def use() = { println("cap is used"); 0 } } diff --git a/tests/neg-custom-args/captures/i16725.scala b/tests/neg-custom-args/captures/i16725.scala index 1accf197c626..96cf44e72f3c 100644 --- a/tests/neg-custom-args/captures/i16725.scala +++ b/tests/neg-custom-args/captures/i16725.scala @@ -3,12 +3,12 @@ class IO extends caps.Capability: def brewCoffee(): Unit = ??? def usingIO[T](op: IO => T): T = ??? -type Wrapper[T] = [R] -> (f: T => R) -> R -def mk[T](x: T): Wrapper[T] = [R] => f => f(x) +class Wrapper[T](val value: [R] -> (f: T => R) -> R) +def mk[T](x: T): Wrapper[T] = Wrapper([R] => f => f(x)) def useWrappedIO(wrapper: Wrapper[IO]): () -> Unit = () => - wrapper: io => // error + wrapper.value: io => // error io.brewCoffee() def main(): Unit = - val escaped = usingIO(io => useWrappedIO(mk(io))) + val escaped = usingIO(io => useWrappedIO(mk(io))) // error escaped() // boom diff --git a/tests/neg-custom-args/captures/i19330.check b/tests/neg-custom-args/captures/i19330.check new file mode 100644 index 000000000000..a8925b117611 --- /dev/null +++ b/tests/neg-custom-args/captures/i19330.check @@ -0,0 +1,5 @@ +-- Error: tests/neg-custom-args/captures/i19330.scala:15:29 ------------------------------------------------------------ +15 | val leaked = usingLogger[x.T]: l => // error + | ^^^ + | Type variable T of method usingLogger cannot be instantiated to x.T since + | the part () => Logger^ of that type captures the root capability `cap`. diff --git a/tests/neg-custom-args/captures/i21313.check b/tests/neg-custom-args/captures/i21313.check index 37b944a97d68..f76f4bc6871e 100644 --- a/tests/neg-custom-args/captures/i21313.check +++ b/tests/neg-custom-args/captures/i21313.check @@ -5,7 +5,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21313.scala:15:12 --------------------------------------- 15 | ac1.await(src2) // error | ^^^^ - | Found: (src2 : Source[Int, caps.CapSet^{ac2}]^?) - | Required: Source[Int, caps.CapSet^{ac1}]^ + | Found: (src2 : Source[Int, scala.caps.CapSet^{ac2}]^?) + | Required: Source[Int, scala.caps.CapSet^{ac1}]^ | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21401.check b/tests/neg-custom-args/captures/i21401.check index 679c451949bd..05378bf57f23 100644 --- a/tests/neg-custom-args/captures/i21401.check +++ b/tests/neg-custom-args/captures/i21401.check @@ -11,8 +11,8 @@ -- Error: tests/neg-custom-args/captures/i21401.scala:16:66 ------------------------------------------------------------ 16 | val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error | ^^^ - | Type variable R of method usingIO cannot be instantiated to Res since - | the part box IO^ of that type captures the root capability `cap`. + | Type variable R of method usingIO cannot be instantiated to [R, X <: Boxed[box IO^] -> R] => (op: X) -> R since + | the part box IO^ of that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/i21401.scala:17:29 ------------------------------------------------------------ 17 | val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error | ^^^^^^^^^^ @@ -21,8 +21,8 @@ -- Error: tests/neg-custom-args/captures/i21401.scala:17:52 ------------------------------------------------------------ 17 | val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error | ^^^^^^^^^^^^^^^^^^^^^^^^ - |Type variable X of value leaked cannot be instantiated to Boxed[box IO^] -> (ex$18: caps.Exists) -> Boxed[box IO^{ex$18}] since - |the part box IO^{ex$18} of that type captures the root capability `cap`. + |Type variable X of value leaked cannot be instantiated to Boxed[box IO^] -> (ex$20: scala.caps.Exists) -> Boxed[box IO^{ex$20}] since + |the part box IO^{ex$20} of that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/i21401.scala:18:21 ------------------------------------------------------------ 18 | val y: IO^{x*} = x.unbox // error | ^^^^^^^ diff --git a/tests/neg-custom-args/captures/i21614.check b/tests/neg-custom-args/captures/i21614.check index f4967253455f..2d1de6c57a0b 100644 --- a/tests/neg-custom-args/captures/i21614.check +++ b/tests/neg-custom-args/captures/i21614.check @@ -8,10 +8,10 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:15:12 --------------------------------------- 15 | files.map(new Logger(_)) // error, Q: can we improve the error message? | ^^^^^^^^^^^^^ - | Found: (_$1: box File^{files*}) ->{files*} (ex$16: caps.Exists) -> box Logger{val f: File^{_$1}}^{ex$16} - | Required: (_$1: box File^{files*}) => box Logger{val f: File^?}^? + |Found: (_$1: box File^{files*}) ->{files*} (ex$16: scala.caps.Exists) -> box Logger{val f: File^{_$1}}^{ex$16} + |Required: (_$1: box File^{files*}) => box Logger{val f: File^?}^? | - | Note that the universal capability `cap` - | cannot be included in capture set ? + |Note that the universal capability `cap` + |cannot be included in capture set ? | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index be11aedd74ae..d6b9c46f71e0 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -1,10 +1,10 @@ -- Error: tests/neg-custom-args/captures/leaked-curried.scala:14:20 ---------------------------------------------------- 14 | () => () => io // error | ^^ - | reference (io : Cap^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> () ->{io} (ex$7: caps.Exists) -> Cap^{ex$7} + | reference (io : Cap^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> () ->{io} (ex$7: scala.caps.Exists) -> Cap^{ex$7} -- Error: tests/neg-custom-args/captures/leaked-curried.scala:17:20 ---------------------------------------------------- 17 | () => () => io // error | ^^ - | reference (io : Cap^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> () ->{io} (ex$15: caps.Exists) -> Cap^{ex$15} + | reference (io : Cap^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> () ->{io} (ex$15: scala.caps.Exists) -> Cap^{ex$15} diff --git a/tests/neg-custom-args/captures/outer-var.check b/tests/neg-custom-args/captures/outer-var.check index 72af842728a1..b24579b7a69f 100644 --- a/tests/neg-custom-args/captures/outer-var.check +++ b/tests/neg-custom-args/captures/outer-var.check @@ -1,7 +1,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:11:8 ------------------------------------- 11 | x = q // error | ^ - | Found: (q : Proc) + | Found: (q : () => Unit) | Required: () ->{p, q²} Unit | | where: q is a parameter in method inner @@ -11,14 +11,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:12:9 ------------------------------------- 12 | x = (q: Proc) // error | ^^^^^^^ - | Found: Proc + | Found: () => Unit | Required: () ->{p, q} Unit | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:13:9 ------------------------------------- 13 | y = (q: Proc) // error | ^^^^^^^ - | Found: Proc + | Found: () => Unit | Required: () ->{p} Unit | | Note that the universal capability `cap` @@ -28,10 +28,10 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:14:8 ------------------------------------- 14 | y = q // error, was OK under unsealed | ^ - | Found: (q : Proc) + | Found: (q : () => Unit) | Required: () ->{p} Unit | - | Note that reference (q : Proc), defined in method inner + | Note that reference (q : () => Unit), defined in method inner | cannot be included in outer capture set {p} of variable y | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index 72604451472c..23c1b056c659 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -6,8 +6,8 @@ -- Error: tests/neg-custom-args/captures/try.scala:30:65 --------------------------------------------------------------- 30 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error | ^ - | reference (x : CanThrow[Exception]) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () ->? Nothing + | reference (x : CT[Exception]^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () ->? Nothing -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:52:2 ------------------------------------------- 47 |val global: () -> Int = handle { 48 | (x: CanThrow[Exception]) => diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index e4b1e71a2000..db5c8083e3b7 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -1,9 +1,9 @@ -- Error: tests/neg-custom-args/captures/vars.scala:24:14 -------------------------------------------------------------- 24 | a = x => g(x) // error | ^^^^ - | reference (cap3 : Cap) is not included in the allowed capture set {cap1} of variable a + | reference (cap3 : CC^) is not included in the allowed capture set {cap1} of variable a | - | Note that reference (cap3 : Cap), defined in method scope + | Note that reference (cap3 : CC^), defined in method scope | cannot be included in outer capture set {cap1} of variable a -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:25:8 ------------------------------------------ 25 | a = g // error @@ -11,7 +11,7 @@ | Found: (x: String) ->{cap3} String | Required: (x$0: String) ->{cap1} String | - | Note that reference (cap3 : Cap), defined in method scope + | Note that reference (cap3 : CC^), defined in method scope | cannot be included in outer capture set {cap1} of variable a | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-macros/i19842-a.check b/tests/neg-macros/i19842-a.check index 30b295cd05a5..af628c566c15 100644 --- a/tests/neg-macros/i19842-a.check +++ b/tests/neg-macros/i19842-a.check @@ -9,8 +9,8 @@ | | at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8) | at dotty.tools.dotc.transform.TreeChecker$.checkParents(TreeChecker.scala:210) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:257) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:256) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:285) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:284) | at Macros$.makeSerializer(Macro.scala:25) | |--------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i19842-a/Macro.scala b/tests/neg-macros/i19842-a/Macro.scala index 18a1bc16045f..14b7c3f24a1a 100644 --- a/tests/neg-macros/i19842-a/Macro.scala +++ b/tests/neg-macros/i19842-a/Macro.scala @@ -16,7 +16,7 @@ object Macros { name, Flags.Implicit, Flags.EmptyFlags, - List(TypeRepr.of[Object], TypeRepr.of[Serializer[T]]), + _ => List(TypeRepr.of[Object], TypeRepr.of[Serializer[T]]), _ => Nil, Symbol.noSymbol ) diff --git a/tests/neg-macros/i19842-b.check b/tests/neg-macros/i19842-b.check index d84d916acb66..b402006c2d4b 100644 --- a/tests/neg-macros/i19842-b.check +++ b/tests/neg-macros/i19842-b.check @@ -9,8 +9,8 @@ | | at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8) | at dotty.tools.dotc.transform.TreeChecker$.checkParents(TreeChecker.scala:210) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:257) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:256) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:285) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:284) | at Macros$.makeSerializer(Macro.scala:27) | |--------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i19842-b/Macro.scala b/tests/neg-macros/i19842-b/Macro.scala index f1399d328f49..8b43faab76dd 100644 --- a/tests/neg-macros/i19842-b/Macro.scala +++ b/tests/neg-macros/i19842-b/Macro.scala @@ -18,7 +18,7 @@ object Macros { name, Flags.Implicit, Flags.EmptyFlags, - List(TypeRepr.of[Object], TypeRepr.of[Serializer[T]]), + _ => List(TypeRepr.of[Object], TypeRepr.of[Serializer[T]]), _ => Nil, Symbol.noSymbol ) diff --git a/tests/neg-macros/i21916.check b/tests/neg-macros/i21916.check new file mode 100644 index 000000000000..58a8ac43782a --- /dev/null +++ b/tests/neg-macros/i21916.check @@ -0,0 +1,15 @@ + +-- Error: tests/neg-macros/i21916/Test_2.scala:3:27 -------------------------------------------------------------------- +3 |@main def Test = Macro.test() // error + | ^^^^^^^^^^^^ + | Exception occurred while executing macro expansion. + | java.lang.AssertionError: Illegal empty Array type constructor. Please supply a type parameter. + | at Macro$.testImpl(Macro_1.scala:8) + | + |--------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from Macro_1.scala:3 +3 | inline def test() = ${testImpl} + | ^^^^^^^^^^^ + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i21916/Macro_1.scala b/tests/neg-macros/i21916/Macro_1.scala new file mode 100644 index 000000000000..78af2cde2963 --- /dev/null +++ b/tests/neg-macros/i21916/Macro_1.scala @@ -0,0 +1,9 @@ +import scala.quoted._ +object Macro: + inline def test() = ${testImpl} + def testImpl(using Quotes): Expr[Any] = { + import quotes.reflect._ + val tpe = TypeRepr.of[Array[Byte]] match + case AppliedType(tycons, _) => tycons + Literal(ClassOfConstant(tpe)).asExpr + } diff --git a/tests/neg-macros/i21916/Test_2.scala b/tests/neg-macros/i21916/Test_2.scala new file mode 100644 index 000000000000..103bced0c04e --- /dev/null +++ b/tests/neg-macros/i21916/Test_2.scala @@ -0,0 +1,3 @@ +// lack of type ascription is on purpose, +// as that was part of what would cause the crash before. +@main def Test = Macro.test() // error diff --git a/tests/neg-macros/newClassParamsMissingArgument.check b/tests/neg-macros/newClassParamsMissingArgument.check new file mode 100644 index 000000000000..2a6b53bd2d79 --- /dev/null +++ b/tests/neg-macros/newClassParamsMissingArgument.check @@ -0,0 +1,32 @@ + +-- Error: tests/neg-macros/newClassParamsMissingArgument/Test_2.scala:4:2 ---------------------------------------------- +4 | makeClass("foo") // error // error + | ^^^^^^^^^^^^^^^^ + |wrong number of arguments at inlining (while expanding macro) for (idx: Int): foo: (foo# : (idx: Int): foo), expected: 1, found: 0 +-- Error: tests/neg-macros/newClassParamsMissingArgument/Test_2.scala:4:11 --------------------------------------------- +4 | makeClass("foo") // error // error + | ^^^^^^^^^^^^^^^^ + |Malformed tree was found while expanding macro with -Xcheck-macros. + |The tree does not conform to the compiler's tree invariants. + | + |Macro was: + |scala.quoted.runtime.Expr.splice[java.lang.Object](((contextual$1: scala.quoted.Quotes) ?=> Macro_1$package.inline$makeClassExpr(scala.quoted.runtime.Expr.quote[scala.Predef.String]("foo").apply(using contextual$1))(contextual$1))) + | + |The macro returned: + |{ + | class foo(val idx: scala.Int) extends java.lang.Object + | + | (new foo(): java.lang.Object) + |} + | + |Error: + |missing argument for parameter idx of constructor foo in class foo: (idx: Int): foo + | + |stacktrace available when compiling with `-Ydebug` + |--------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from Macro_1.scala:5 +5 |inline def makeClass(inline name: String): Object = ${ makeClassExpr('name) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala b/tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala new file mode 100644 index 000000000000..a0ee5a899e62 --- /dev/null +++ b/tests/neg-macros/newClassParamsMissingArgument/Macro_1.scala @@ -0,0 +1,24 @@ +//> using options -experimental + +import scala.quoted._ + +inline def makeClass(inline name: String): Object = ${ makeClassExpr('name) } +private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Object] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + val parents = List(TypeTree.of[Object]) + def decls(cls: Symbol): List[Symbol] = Nil + + val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = _ => parents.map(_.tpe), decls, selfType = None, Flags.EmptyFlags, Symbol.noSymbol, List(("idx", TypeRepr.of[Int]))) + + val clsDef = ClassDef(cls, parents, body = Nil) + val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Object]) + + Block(List(clsDef), newCls).asExprOf[Object] + + // '{ + // class `name`(idx: Int) + // new `name` + // } +} diff --git a/tests/neg-macros/newClassParamsMissingArgument/Test_2.scala b/tests/neg-macros/newClassParamsMissingArgument/Test_2.scala new file mode 100644 index 000000000000..8d7142f7bd72 --- /dev/null +++ b/tests/neg-macros/newClassParamsMissingArgument/Test_2.scala @@ -0,0 +1,5 @@ +//> using options -experimental + +@main def Test: Unit = { + makeClass("foo") // error // error +} diff --git a/tests/neg/16463.scala b/tests/neg/16463.scala index 80a84cf02bc8..6011678552b7 100644 --- a/tests/neg/16463.scala +++ b/tests/neg/16463.scala @@ -1,4 +1,4 @@ -//> using scala "3.2.1" +//!> using scala "3.2.1" import scala.compiletime.ops.int._ diff --git a/tests/neg/22145.check b/tests/neg/22145.check new file mode 100644 index 000000000000..4592c42e9e7f --- /dev/null +++ b/tests/neg/22145.check @@ -0,0 +1,4 @@ +-- [E008] Not Found Error: tests/neg/22145.scala:5:7 ------------------------------------------------------------------- +5 | base.foo() // error + | ^^^^^^^^ + | value foo is not a member of foo.Collection diff --git a/tests/neg/22145.scala b/tests/neg/22145.scala new file mode 100644 index 000000000000..59d58b167ab4 --- /dev/null +++ b/tests/neg/22145.scala @@ -0,0 +1,8 @@ +package foo + +trait Collection: + val base: Collection = ??? + base.foo() // error + + object O extends Collection: + def foo(): Int = ??? diff --git a/tests/neg/22145b.check b/tests/neg/22145b.check new file mode 100644 index 000000000000..de605ce24276 --- /dev/null +++ b/tests/neg/22145b.check @@ -0,0 +1,36 @@ +-- [E008] Not Found Error: tests/neg/22145b.scala:15:19 ---------------------------------------------------------------- +15 | require(base.isWithin(p, start, end), "position is out of bounds") // error + | ^^^^^^^^^^^^^ + | value isWithin is not a member of Collection.this.Self +-- [E008] Not Found Error: tests/neg/22145b.scala:28:59 ---------------------------------------------------------------- +28 | def positionAfter(p: Position): Position = self.base.positionAfter(p) // error + | ^^^^^^^^^^^^^^^^^^^^^^^ + |value positionAfter is not a member of Collection.this.Self. + |An extension method was tried, but could not be fully constructed: + | + | this.positionAfter(self.base) + | + | failed with: + | + | Found: (self.base : Collection.this.Self) + | Required: foo.Collection.given_is_Slice_Collection.Self² + | + | where: Self is a type in trait Collection + | Self² is a type in object given_is_Slice_Collection which is an alias of Collection.this.Slice + | +-- [E008] Not Found Error: tests/neg/22145b.scala:29:50 ---------------------------------------------------------------- +29 | def apply(p: Position): Element = self.base.apply(p) // error + | ^^^^^^^^^^^^^^^ + |value apply is not a member of Collection.this.Self. + |An extension method was tried, but could not be fully constructed: + | + | this.apply(self.base) + | + | failed with: + | + | Found: (self.base : Collection.this.Self) + | Required: foo.Collection.given_is_Slice_Collection.Self² + | + | where: Self is a type in trait Collection + | Self² is a type in object given_is_Slice_Collection which is an alias of Collection.this.Slice + | diff --git a/tests/neg/22145b.scala b/tests/neg/22145b.scala new file mode 100644 index 000000000000..5b8de5672fba --- /dev/null +++ b/tests/neg/22145b.scala @@ -0,0 +1,40 @@ +package foo + +import language.experimental.modularity + +trait Collection: + me => + + type Self + type Position + type Element + + final class Slice(private[Collection] val base: Self, val start: Position, val end: Position): + + final def apply(p: Position): Element = + require(base.isWithin(p, start, end), "position is out of bounds") // error + base.apply(p) + + end Slice + + given Slice is Collection: + + type Position = me.Position + type Element = me.Element + + extension (self: Self) + def start: Position = self.start + def end: Position = self.end + def positionAfter(p: Position): Position = self.base.positionAfter(p) // error + def apply(p: Position): Element = self.base.apply(p) // error + + end given + + extension (self: Self) + + def start: Position + def end: Position + def positionAfter(p: Position): Position + def apply(p: Position): Element + + end extension diff --git a/tests/neg/22145c.check b/tests/neg/22145c.check new file mode 100644 index 000000000000..ddfb6b9daf1d --- /dev/null +++ b/tests/neg/22145c.check @@ -0,0 +1,4 @@ +-- [E008] Not Found Error: tests/neg/22145c.scala:4:35 ----------------------------------------------------------------- +4 | def bar(base: Collection) = base.foo // error + | ^^^^^^^^ + | value foo is not a member of foo.Collection diff --git a/tests/neg/22145c.scala b/tests/neg/22145c.scala new file mode 100644 index 000000000000..7776e57e7906 --- /dev/null +++ b/tests/neg/22145c.scala @@ -0,0 +1,8 @@ +package foo + +trait Collection: + def bar(base: Collection) = base.foo // error + object a extends Collection: + def foo: Int = 0 + object b extends Collection: + def foo: Int = 1 diff --git a/tests/neg/22145d.check b/tests/neg/22145d.check new file mode 100644 index 000000000000..ac6469c10b82 --- /dev/null +++ b/tests/neg/22145d.check @@ -0,0 +1,9 @@ +-- [E008] Not Found Error: tests/neg/22145d.scala:10:4 ----------------------------------------------------------------- +10 | 2.f() // error + | ^^^ + | value f is not a member of Int, but could be made available as an extension method. + | + | The following import might fix the problem: + | + | import foo.O2.f + | diff --git a/tests/neg/22145d.scala b/tests/neg/22145d.scala new file mode 100644 index 000000000000..bfb68e088322 --- /dev/null +++ b/tests/neg/22145d.scala @@ -0,0 +1,10 @@ +package foo + +class C[T]: + extension (x: T) def f(): Int = 1 + +object O1 extends C[String] +object O2 extends C[Int] + +def main = + 2.f() // error diff --git a/tests/neg/22145e.check b/tests/neg/22145e.check new file mode 100644 index 000000000000..e1c34e59f239 --- /dev/null +++ b/tests/neg/22145e.check @@ -0,0 +1,9 @@ +-- [E008] Not Found Error: tests/neg/22145e.scala:11:4 ----------------------------------------------------------------- +11 | 2.f() // error + | ^^^ + | value f is not a member of Int, but could be made available as an extension method. + | + | The following import might fix the problem: + | + | import foo.O2.Ext.f + | diff --git a/tests/neg/22145e.scala b/tests/neg/22145e.scala new file mode 100644 index 000000000000..579fd65c685e --- /dev/null +++ b/tests/neg/22145e.scala @@ -0,0 +1,11 @@ +package foo + +class C[T]: + object Ext: + extension (x: T) def f(): Int = 1 + +object O1 extends C[String] +object O2 extends C[Int] + +def main = + 2.f() // error diff --git a/tests/neg/22145f.check b/tests/neg/22145f.check new file mode 100644 index 000000000000..b870eb21057a --- /dev/null +++ b/tests/neg/22145f.check @@ -0,0 +1,10 @@ +-- [E008] Not Found Error: tests/neg/22145f.scala:11:6 ----------------------------------------------------------------- +11 | 2.f() // error + | ^^^ + | value f is not a member of Int, but could be made available as an extension method. + | + | One of the following imports might fix the problem: + | + | import C.this.O1.O2.Ext.f + | import C.this.O2.Ext.f + | diff --git a/tests/neg/22145f.scala b/tests/neg/22145f.scala new file mode 100644 index 000000000000..97f1336d1b1b --- /dev/null +++ b/tests/neg/22145f.scala @@ -0,0 +1,11 @@ +package foo + +class C[T]: + object Ext: + extension (x: T) def f(): Int = 1 + + object O1 extends C[String] + object O2 extends C[Int] + + def g = + 2.f() // error diff --git a/tests/neg/22145g.check b/tests/neg/22145g.check new file mode 100644 index 000000000000..175949ac113f --- /dev/null +++ b/tests/neg/22145g.check @@ -0,0 +1,4 @@ +-- [E008] Not Found Error: tests/neg/22145g.scala:10:4 ----------------------------------------------------------------- +10 | 2.f() // error + | ^^^ + | value f is not a member of Int diff --git a/tests/neg/22145g.scala b/tests/neg/22145g.scala new file mode 100644 index 000000000000..8b888516c044 --- /dev/null +++ b/tests/neg/22145g.scala @@ -0,0 +1,10 @@ +package foo + +class C[T]: + extension (x: T) def f(): Int = 1 + +object O: + val c0: C[String] = new C[String] + val c1: C[Int] = new C[Int] + // Currently no import suggestions here + 2.f() // error diff --git a/tests/neg/exports3.scala b/tests/neg/exports3.scala new file mode 100644 index 000000000000..eaea93d9f7ce --- /dev/null +++ b/tests/neg/exports3.scala @@ -0,0 +1,41 @@ +trait P: + def foo: Int + +class A extends P: + export this.foo // error + +trait Q extends P: + def bar: Int + +trait R extends P: + def baz: Int + val a1: A + val a2: A + +abstract class B extends R: + self => + export this.baz // error + export self.bar // error + export this.a1.foo + export self.a2.foo // error + export a2.foo // error + +abstract class D extends P: + val p: P + export p.foo + +abstract class E: + self: P => + export self.foo // error + +abstract class F: + self: P => + export this.foo // error + +class G(p: P): + self: P => + export p.foo + +class H(p: P): + self: P => + export this.p.foo \ No newline at end of file diff --git a/tests/neg/given-ambiguous-default-1.check b/tests/neg/given-ambiguous-default-1.check index 1a5006c23055..97ef6126909b 100644 --- a/tests/neg/given-ambiguous-default-1.check +++ b/tests/neg/given-ambiguous-default-1.check @@ -1,9 +1,9 @@ -- [E172] Type Error: tests/neg/given-ambiguous-default-1.scala:18:23 -------------------------------------------------- 18 |def f: Unit = summon[B] // error: Ambiguous given instances | ^ - | No best given instance of type B was found for parameter x of method summon in object Predef. - | I found: + | No best given instance of type B was found for parameter x of method summon in object Predef. + | I found: | - | given_B(a = /* ambiguous: both given instance a1 and given instance a2 match type A */summon[A]) + | given_B(/* ambiguous: both given instance a1 and given instance a2 match type A */summon[A]) | - | But both given instance a1 and given instance a2 match type A. + | But both given instance a1 and given instance a2 match type A. diff --git a/tests/neg/i18123.check b/tests/neg/i18123.check index d784c4d12673..a36ed8822de8 100644 --- a/tests/neg/i18123.check +++ b/tests/neg/i18123.check @@ -1,7 +1,7 @@ --- [E172] Type Error: tests/neg/i18123.scala:25:33 --------------------------------------------------------------------- +-- [E172] Type Error: tests/neg/i18123.scala:25:48 --------------------------------------------------------------------- 25 | (charClassIntersection.rep() | classItem.rep()) // error - | ^^^^^^^^^^^^^^^ - |No given instance of type pkg.Implicits.Repeater[pkg.RegexTree, V] was found. + | ^ + |No given instance of type pkg.Implicits.Repeater[pkg.RegexTree, V] was found for parameter repeater of method rep in package pkg. |I found: | | pkg.Implicits.Repeater.GenericRepeaterImplicit[T] diff --git a/tests/neg/i18545.check b/tests/neg/i18545.check index 95edeacc0c95..1b336b0cc5ab 100644 --- a/tests/neg/i18545.check +++ b/tests/neg/i18545.check @@ -1,16 +1,26 @@ --- [E173] Reference Error: tests/neg/i18545.scala:13:20 ---------------------------------------------------------------- -13 | def test: IOLocal.IOLocalImpl[Int] = // error +-- [E173] Reference Error: tests/neg/i18545.scala:16:20 ---------------------------------------------------------------- +16 | def test: IOLocal.IOLocalImpl[Int] = // error | ^^^^^^^^^^^^^^^^^^^ |class IOLocalImpl cannot be accessed as a member of iolib.IOLocal.type from the top-level definitions in package tests. | private[IOLocal] class IOLocalImpl can only be accessed from object IOLocal in package iolib. --- [E173] Reference Error: tests/neg/i18545.scala:14:24 ---------------------------------------------------------------- -14 | IOLocal.IOLocalImpl.apply(42) // error +-- [E173] Reference Error: tests/neg/i18545.scala:17:24 ---------------------------------------------------------------- +17 | IOLocal.IOLocalImpl.apply(42) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^ |method apply cannot be accessed as a member of iolib.IOLocal.IOLocalImpl.type from the top-level definitions in package tests. | private[IOLocal] method apply can only be accessed from object IOLocal in package iolib. --- [E050] Type Error: tests/neg/i18545.scala:15:22 --------------------------------------------------------------------- -15 | def test2 = IOLocal.IOLocalImpl(42) // error +-- [E050] Type Error: tests/neg/i18545.scala:18:22 --------------------------------------------------------------------- +18 | def test2 = IOLocal.IOLocalImpl(42) // error | ^^^^^^^^^^^^^^^^^^^ | object IOLocalImpl in object IOLocal does not take parameters | | longer explanation available when compiling with `-explain` +-- [E173] Reference Error: tests/neg/i18545.scala:19:22 ---------------------------------------------------------------- +19 | def test3 = IOLocal.AltIOLocalImpl.apply(42) // error + | ^^^^^^^^^^^^^^^^^^^^^^ + |object AltIOLocalImpl cannot be accessed as a member of iolib.IOLocal.type from the top-level definitions in package tests. + | private[IOLocal] object AltIOLocalImpl can only be accessed from object IOLocal in package iolib. +-- [E173] Reference Error: tests/neg/i18545.scala:20:22 ---------------------------------------------------------------- +20 | def test4 = IOLocal.AltIOLocalImpl(42) // error + | ^^^^^^^^^^^^^^^^^^^^^^ + |object AltIOLocalImpl cannot be accessed as a member of iolib.IOLocal.type from the top-level definitions in package tests. + | private[IOLocal] object AltIOLocalImpl can only be accessed from object IOLocal in package iolib. diff --git a/tests/neg/i18545.scala b/tests/neg/i18545.scala index 330482df11ae..86f18cd60771 100644 --- a/tests/neg/i18545.scala +++ b/tests/neg/i18545.scala @@ -7,9 +7,14 @@ package iolib: def apply[A](default: A): IO[IOLocal[A]] = IO(new IOLocalImpl(default)) private[IOLocal] final class IOLocalImpl[A](default: A) extends IOLocal[A] + object IOLocalImpl + + private[IOLocal] final class AltIOLocalImpl[A](default: A) extends IOLocal[A] package tests: import iolib.IOLocal def test: IOLocal.IOLocalImpl[Int] = // error IOLocal.IOLocalImpl.apply(42) // error def test2 = IOLocal.IOLocalImpl(42) // error + def test3 = IOLocal.AltIOLocalImpl.apply(42) // error + def test4 = IOLocal.AltIOLocalImpl(42) // error diff --git a/tests/neg/i19320.scala b/tests/neg/i19320.scala index e802215a1f10..3597f02cf431 100644 --- a/tests/neg/i19320.scala +++ b/tests/neg/i19320.scala @@ -1,7 +1,7 @@ -//> using scala "3.3.1" -//> using dep org.http4s::http4s-ember-client:1.0.0-M40 -//> using dep org.http4s::http4s-ember-server:1.0.0-M40 -//> using dep org.http4s::http4s-dsl:1.0.0-M40 +//!> using scala "3.3.1" +//!> using dep org.http4s::http4s-ember-client:1.0.0-M40 +//!> using dep org.http4s::http4s-ember-server:1.0.0-M40 +//!> using dep org.http4s::http4s-dsl:1.0.0-M40 //import cats.effect.* //import cats.implicits.* diff --git a/tests/neg/i19594.check b/tests/neg/i19594.check index bb9ff3fc68af..732721c544ce 100644 --- a/tests/neg/i19594.check +++ b/tests/neg/i19594.check @@ -1,8 +1,20 @@ --- [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?! +-- [E172] Type Error: tests/neg/i19594.scala:15:50 --------------------------------------------------------------------- +15 | assertEquals(true, 1, "values are not the same") // error + | ^ + | Can you see me?! +-- [E172] Type Error: tests/neg/i19594.scala:16:23 --------------------------------------------------------------------- +16 | assertEquals(true, 1) // error + | ^ + | Can you see me?! +-- [E172] Type Error: tests/neg/i19594.scala:20:12 --------------------------------------------------------------------- +20 | f(true, 1) // error + | ^ + | Can you see me?! +-- [E172] Type Error: tests/neg/i19594.scala:23:39 --------------------------------------------------------------------- +23 | g(true, 1, "values are not the same") // error + | ^ + | Can you see me?! +-- [E172] Type Error: tests/neg/i19594.scala:26:3 ---------------------------------------------------------------------- +26 | h(true, 1) // error + | ^^^^^^^^^^ + | No given instance of type String was found diff --git a/tests/neg/i19594.scala b/tests/neg/i19594.scala index a559da8d9250..6f5453dcdf23 100644 --- a/tests/neg/i19594.scala +++ b/tests/neg/i19594.scala @@ -1,7 +1,10 @@ import scala.annotation.implicitNotFound @implicitNotFound("Can you see me?!") -trait Compare[A, B] +trait Compare[-A, -B] + +object Compare: + val any: Compare[Any, Any] = new Compare {} object example extends App: @@ -11,3 +14,13 @@ object example extends App: assertEquals(true, 1, "values are not the same") // error assertEquals(true, 1) // error + +object updated: + def f[A, B](a: A, b: B)(using Compare[A, B]) = () + f(true, 1) // error + + def g[A, B](a: A, b: B, clue: => Any)(implicit comp: Compare[A, B]) = () + g(true, 1, "values are not the same") // error + + def h[A, B](a: A, b: B)(using c: Compare[A, B] = Compare.any, s: String) = () + h(true, 1) // error diff --git a/tests/neg/i20245.check b/tests/neg/i20245.check index 565bde7678b7..49d08c646f99 100644 --- a/tests/neg/i20245.check +++ b/tests/neg/i20245.check @@ -15,3 +15,17 @@ | Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace. | | longer explanation available when compiling with `-explain` +-- [E046] Cyclic Error: tests/neg/i20245/Typer_2.scala:10:7 ------------------------------------------------------------ +10 |import effekt.source.{ resolve } // error + | ^ + | Cyclic reference involving class Context + | + | The error occurred while trying to compute the base classes of class Context + | which required to compute the base classes of trait TyperOps + | which required to compute the signature of trait TyperOps + | which required to elaborate the export clause export unification.requireSubtype + | which required to compute the base classes of class Context + | + | Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i20245/Typer_2.scala b/tests/neg/i20245/Typer_2.scala index ed7f05de80d0..bf4718de759c 100644 --- a/tests/neg/i20245/Typer_2.scala +++ b/tests/neg/i20245/Typer_2.scala @@ -7,7 +7,7 @@ import effekt.util.messages.ErrorReporter import effekt.context.{ Context } // This import is also NECESSARY for the cyclic error -import effekt.source.{ resolve } +import effekt.source.{ resolve } // error trait TyperOps extends ErrorReporter { self: Context => diff --git a/tests/neg/i20265-1.check b/tests/neg/i20265-1.check new file mode 100644 index 000000000000..6eb9f4cfe5c6 --- /dev/null +++ b/tests/neg/i20265-1.check @@ -0,0 +1,5 @@ +-- Error: tests/neg/i20265-1.scala:4:6 --------------------------------------------------------------------------------- +4 | def apply(args: Tuple.Map[m.MirroredElemTypes, Expr]): Expr[T] = ??? // error + | ^ + | non-private method apply in trait Ops refers to private given instance m + | in its type signature (args: Tuple.Map[Ops.this.m.MirroredElemTypes, Expr]): Expr[T] diff --git a/tests/neg/i20265-1.scala b/tests/neg/i20265-1.scala new file mode 100644 index 000000000000..085d387ae901 --- /dev/null +++ b/tests/neg/i20265-1.scala @@ -0,0 +1,9 @@ +trait Expr[T] + +trait Ops[T](using m: scala.deriving.Mirror.ProductOf[T]) { + def apply(args: Tuple.Map[m.MirroredElemTypes, Expr]): Expr[T] = ??? // error +} + +case class P(a: Int) +object P extends Ops[P] + diff --git a/tests/neg/i20265.check b/tests/neg/i20265.check new file mode 100644 index 000000000000..607c2c2fde45 --- /dev/null +++ b/tests/neg/i20265.check @@ -0,0 +1,28 @@ +-- [E172] Type Error: tests/neg/i20265.scala:22:95 --------------------------------------------------------------------- +22 | println(summon[((String --> Unit) * (String --> Unit)) =:= Hinze[(String + String) --> Unit]]) // error + | ^ + | Cannot prove that (String --> Unit) * (String --> Unit) =:= Hinze[String + String --> Unit]. + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Hinze[String + String --> Unit] + | failed since selector (String + String --> Unit)#unfix + | does not match case k1 + k2 --> v => Hinze[k1 --> v] * Hinze[k2 --> v] + | and cannot be shown to be disjoint from it either. + | Therefore, reduction cannot advance to the remaining case + | + | case k1 * k2 --> v => k1 --> Hinze[k2 --> v] +-- [E172] Type Error: tests/neg/i20265.scala:23:66 --------------------------------------------------------------------- +23 | println(summon[String =:= Hinze[Fix[Lambda[String]#L] --> Unit]]) // error + | ^ + | Cannot prove that String =:= Hinze[Fix[[X] =>> String + String * X + X * X] --> Unit]. + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Hinze[Fix[[X] =>> String + String * X + X * X] --> Unit] + | failed since selector (Fix[[X] =>> String + String * X + X * X] --> Unit)#unfix + | does not match case k1 + k2 --> v => Hinze[k1 --> v] * Hinze[k2 --> v] + | and cannot be shown to be disjoint from it either. + | Therefore, reduction cannot advance to the remaining case + | + | case k1 * k2 --> v => k1 --> Hinze[k2 --> v] diff --git a/tests/neg/i20265.scala b/tests/neg/i20265.scala new file mode 100644 index 000000000000..1d312188ae99 --- /dev/null +++ b/tests/neg/i20265.scala @@ -0,0 +1,23 @@ +//> using options -source:3.3 + +trait Poly +trait -->[X, Y] extends Poly +trait +[X, Y] extends Poly +trait *[X, Y] extends Poly + +type Hinze[X <: Fix[?]] = X#unfix match + case (k1 + k2) --> v => Hinze[(k1 --> v)] * Hinze[(k2 --> v)] + case (k1 * k2) --> v => k1 --> Hinze[(k2 --> v)] + +trait Lambda[V]: + type Abs[X] = V * X + type App[X] = X * X + type L[X] = V + Abs[X] + App[X] + +trait Fix[F[X]]: + type unfix = F[Fix[F]] + +@main +def m = + println(summon[((String --> Unit) * (String --> Unit)) =:= Hinze[(String + String) --> Unit]]) // error + println(summon[String =:= Hinze[Fix[Lambda[String]#L] --> Unit]]) // error diff --git a/tests/neg/i20517.check b/tests/neg/i20517.check index 55aeff46572b..119c34025ee0 100644 --- a/tests/neg/i20517.check +++ b/tests/neg/i20517.check @@ -1,7 +1,7 @@ --- [E007] Type Mismatch Error: tests/neg/i20517.scala:10:43 ------------------------------------------------------------ -10 | def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error - | ^^^^^^^^^^^ - | Found: (elem : String) - | Required: NamedTuple.From[(foo : Foo[Any])] - | - | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/i20517.scala:9:43 ------------------------------------------------------------- +9 | def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error + | ^^^^^^^^^^^ + | Found: (elem : String) + | Required: NamedTuple.From[(foo : Foo[Any])] + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i20517.scala b/tests/neg/i20517.scala index 11c4432434dd..342a7d86ca7e 100644 --- a/tests/neg/i20517.scala +++ b/tests/neg/i20517.scala @@ -1,4 +1,3 @@ -import scala.language.experimental.namedTuples import NamedTuple.From case class Foo[+T](elem: T) diff --git a/tests/neg/i21696.check b/tests/neg/i21696.check index 9195263040b3..ce4844782107 100644 --- a/tests/neg/i21696.check +++ b/tests/neg/i21696.check @@ -5,9 +5,9 @@ |--------------------------------------------------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | Referencing `T` inside a quoted expression requires a `scala.quoted.Type[T]` to be in scope. + | Referencing `T` inside a quoted expression requires a `scala.quoted.Type[T]` to be in scope. | Since Scala is subject to erasure at runtime, the type information will be missing during the execution of the code. - | `scala.quoted.Type[T]` is therefore needed to carry `T`'s type information into the quoted code. - | Without an implicit `scala.quoted.Type[T]`, the type `T` cannot be properly referenced within the expression. + | `scala.quoted.Type[T]` is therefore needed to carry `T`'s type information into the quoted code. + | Without an implicit `scala.quoted.Type[T]`, the type `T` cannot be properly referenced within the expression. | To resolve this, ensure that a `scala.quoted.Type[T]` is available, either through a context-bound or explicitly. --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i22192.check b/tests/neg/i22192.check index 35ed426ccb11..77dffb4ae56a 100644 --- a/tests/neg/i22192.check +++ b/tests/neg/i22192.check @@ -1,30 +1,30 @@ --- Error: tests/neg/i22192.scala:6:12 ---------------------------------------------------------------------------------- -6 | case City(iam = n, confused = p) => // error // error +-- Error: tests/neg/i22192.scala:4:12 ---------------------------------------------------------------------------------- +4 | case City(iam = n, confused = p) => // error // error | ^^^^^^^ | No element named `iam` is defined in selector type City --- Error: tests/neg/i22192.scala:6:21 ---------------------------------------------------------------------------------- -6 | case City(iam = n, confused = p) => // error // error +-- Error: tests/neg/i22192.scala:4:21 ---------------------------------------------------------------------------------- +4 | case City(iam = n, confused = p) => // error // error | ^^^^^^^^^^^^ | No element named `confused` is defined in selector type City --- [E006] Not Found Error: tests/neg/i22192.scala:7:7 ------------------------------------------------------------------ -7 | s"$n has a population of $p" // error // error +-- [E006] Not Found Error: tests/neg/i22192.scala:5:7 ------------------------------------------------------------------ +5 | s"$n has a population of $p" // error // error | ^ | Not found: n | | longer explanation available when compiling with `-explain` --- [E006] Not Found Error: tests/neg/i22192.scala:7:30 ----------------------------------------------------------------- -7 | s"$n has a population of $p" // error // error +-- [E006] Not Found Error: tests/neg/i22192.scala:5:30 ----------------------------------------------------------------- +5 | s"$n has a population of $p" // error // error | ^ | Not found: p | | longer explanation available when compiling with `-explain` --- Error: tests/neg/i22192.scala:10:12 --------------------------------------------------------------------------------- -10 | case Some(iam = n) => // error - | ^^^^^^^ - | No element named `iam` is defined in selector type City --- [E006] Not Found Error: tests/neg/i22192.scala:11:4 ----------------------------------------------------------------- -11 | n // error - | ^ - | Not found: n - | - | longer explanation available when compiling with `-explain` +-- Error: tests/neg/i22192.scala:8:12 ---------------------------------------------------------------------------------- +8 | case Some(iam = n) => // error + | ^^^^^^^ + | No element named `iam` is defined in selector type City +-- [E006] Not Found Error: tests/neg/i22192.scala:9:4 ------------------------------------------------------------------ +9 | n // error + | ^ + | Not found: n + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i22192.scala b/tests/neg/i22192.scala index 16eeb643dee0..4f7fd77eaf6c 100644 --- a/tests/neg/i22192.scala +++ b/tests/neg/i22192.scala @@ -1,5 +1,3 @@ -import scala.language.experimental.namedTuples - case class City(name: String, population: Int) def getCityInfo(city: City) = city match diff --git a/tests/neg/i22192a.check b/tests/neg/i22192a.check index 8bfbcb127c54..7f973813c46c 100644 --- a/tests/neg/i22192a.check +++ b/tests/neg/i22192a.check @@ -1,19 +1,19 @@ --- Error: tests/neg/i22192a.scala:6:12 --------------------------------------------------------------------------------- -6 | case Some(iam = n) => // error +-- Error: tests/neg/i22192a.scala:4:12 --------------------------------------------------------------------------------- +4 | case Some(iam = n) => // error | ^^^^^^^ | No element named `iam` is defined in selector type (name : String) --- [E006] Not Found Error: tests/neg/i22192a.scala:7:4 ----------------------------------------------------------------- -7 | n // error +-- [E006] Not Found Error: tests/neg/i22192a.scala:5:4 ----------------------------------------------------------------- +5 | n // error | ^ | Not found: n | | longer explanation available when compiling with `-explain` --- Error: tests/neg/i22192a.scala:11:12 -------------------------------------------------------------------------------- -11 | case Some(iam = n) => // error - | ^^^^^^^ - | No element named `iam` is defined in selector type (name : String, population : Int) --- [E006] Not Found Error: tests/neg/i22192a.scala:12:4 ---------------------------------------------------------------- -12 | n // error +-- Error: tests/neg/i22192a.scala:9:12 --------------------------------------------------------------------------------- +9 | case Some(iam = n) => // error + | ^^^^^^^ + | No element named `iam` is defined in selector type (name : String, population : Int) +-- [E006] Not Found Error: tests/neg/i22192a.scala:10:4 ---------------------------------------------------------------- +10 | n // error | ^ | Not found: n | diff --git a/tests/neg/i22192a.scala b/tests/neg/i22192a.scala index 6460e613d517..12d41f06b19f 100644 --- a/tests/neg/i22192a.scala +++ b/tests/neg/i22192a.scala @@ -1,5 +1,3 @@ -import scala.language.experimental.namedTuples - type City = (name: String) def getCityInfo(city: Option[City]) = city match diff --git a/tests/neg/i22193.scala b/tests/neg/i22193.scala new file mode 100644 index 000000000000..f7ee5b1cf5e1 --- /dev/null +++ b/tests/neg/i22193.scala @@ -0,0 +1,32 @@ + +def fn2(arg: String, arg2: String)(f: String => Unit): Unit = f(arg) + +def fn3(arg: String, arg2: String)(f: => Unit): Unit = f + +def test1() = + + // ok baseline + fn2(arg = "blue sleeps faster than tuesday", arg2 = "the quick brown fox jumped over the lazy dog"): env => + val x = env + println(x) + + fn2( // error not a legal formal parameter for a function literal + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): env => + val x = env // error + println(x) + + fn2( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + env => // error indented definitions expected, identifier env found + val x = env + println(x) + +def test2() = + + fn3( // error missing argument list for value of type (=> Unit) => Unit + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + val x = "Hello" // error + println(x) // error diff --git a/tests/neg/i22357.check b/tests/neg/i22357.check new file mode 100644 index 000000000000..213782a7fc6c --- /dev/null +++ b/tests/neg/i22357.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/i22357.scala:1:0 ----------------------------------------------------------------------------------- +1 |@([A] =>> Int) // error + |^^^^^^^^^^^^^^ + |[A] =>> Int does not have a constructor diff --git a/tests/neg/i22357.scala b/tests/neg/i22357.scala new file mode 100644 index 000000000000..d572c150fb81 --- /dev/null +++ b/tests/neg/i22357.scala @@ -0,0 +1,2 @@ +@([A] =>> Int) // error +def i = 1 diff --git a/tests/neg/i22357a.check b/tests/neg/i22357a.check new file mode 100644 index 000000000000..9b2bcd2510d4 --- /dev/null +++ b/tests/neg/i22357a.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/i22357a.scala:2:6 ---------------------------------------------------------------------------------- +2 | new ([A] =>> Int)(2) // error + | ^^^^^^^^^^^^^ + | [A] =>> Int does not have a constructor diff --git a/tests/neg/i22357a.scala b/tests/neg/i22357a.scala new file mode 100644 index 000000000000..b6c9c04fb268 --- /dev/null +++ b/tests/neg/i22357a.scala @@ -0,0 +1,2 @@ +def main = + new ([A] =>> Int)(2) // error diff --git a/tests/neg/i22367.check b/tests/neg/i22367.check new file mode 100644 index 000000000000..7a6333d0b8bb --- /dev/null +++ b/tests/neg/i22367.check @@ -0,0 +1,8 @@ +-- Error: tests/neg/i22367.scala:3:36 ---------------------------------------------------------------------------------- +3 |@annotation.implicitAmbiguous("cba".reverse) // error + | ^^^^^^^^^^^^^ + | @implicitAmbiguous requires constant expressions as a parameter +-- Error: tests/neg/i22367.scala:6:47 ---------------------------------------------------------------------------------- +6 |def f(using @annotation.implicitNotFound("cba".reverse) e: E[Int]): Unit = () // error + | ^^^^^^^^^^^^^ + | @implicitNotFound requires constant expressions as a parameter diff --git a/tests/neg/i22367.scala b/tests/neg/i22367.scala new file mode 100644 index 000000000000..076e13baaf55 --- /dev/null +++ b/tests/neg/i22367.scala @@ -0,0 +1,6 @@ +trait E[T] + +@annotation.implicitAmbiguous("cba".reverse) // error +given E[Int] = ??? + +def f(using @annotation.implicitNotFound("cba".reverse) e: E[Int]): Unit = () // error diff --git a/tests/neg/i22439.check b/tests/neg/i22439.check new file mode 100644 index 000000000000..471ed68d81d1 --- /dev/null +++ b/tests/neg/i22439.check @@ -0,0 +1,26 @@ +-- [E171] Type Error: tests/neg/i22439.scala:7:3 ----------------------------------------------------------------------- +7 | f() // error f() missing arg + | ^^^ + | missing argument for parameter i of method f: (implicit i: Int, j: Int): Int +-- [E050] Type Error: tests/neg/i22439.scala:8:2 ----------------------------------------------------------------------- +8 | g() // error g(given_Int, given_Int)() doesn't take more params + | ^ + | method g does not take more parameters + | + | longer explanation available when compiling with `-explain` +-- [E171] Type Error: tests/neg/i22439.scala:11:3 ---------------------------------------------------------------------- +11 | f(j = 27) // error missing argument for parameter i of method f + | ^^^^^^^^^ + | missing argument for parameter i of method f: (implicit i: Int, j: Int): Int +-- [E172] Type Error: tests/neg/i22439.scala:16:3 ---------------------------------------------------------------------- +16 | h // error + | ^ + | No given instance of type String was found for parameter s of method h +-- [E172] Type Error: tests/neg/i22439.scala:17:3 ---------------------------------------------------------------------- +17 | h(using i = 17) // error + | ^^^^^^^^^^^^^^^ + | No given instance of type String was found +-- [E171] Type Error: tests/neg/i22439.scala:21:25 --------------------------------------------------------------------- +21 | val (ws, zs) = vs.unzip() // error! + | ^^^^^^^^^^ + |missing argument for parameter asPair of method unzip in trait StrictOptimizedIterableOps: (implicit asPair: ((Int, Int)) => (A1, A2)): (List[A1], List[A2]) diff --git a/tests/neg/i22439.scala b/tests/neg/i22439.scala new file mode 100644 index 000000000000..4ffc1796116c --- /dev/null +++ b/tests/neg/i22439.scala @@ -0,0 +1,21 @@ + +@main def test() = println: + given Int = 42 + def f(implicit i: Int, j: Int) = i + j + def g(using i: Int, j: Int) = i + j + val x: Int = f + f() // error f() missing arg + g() // error g(given_Int, given_Int)() doesn't take more params + f // ok implicits + g // ok implicits + f(j = 27) // error missing argument for parameter i of method f + f(using j = 27) // ok, explicit supplemented by implicit + g(using j = 27) // ok, explicit supplemented by implicit + + def h(using i: Int, s: String) = s * i + h // error + h(using i = 17) // error + + val vs = List((42, 27)) + val (xs, ys) = vs.unzip + val (ws, zs) = vs.unzip() // error! diff --git a/tests/neg/i22440.check b/tests/neg/i22440.check new file mode 100644 index 000000000000..699d70f343c3 --- /dev/null +++ b/tests/neg/i22440.check @@ -0,0 +1,7 @@ +-- Error: tests/neg/i22440.scala:4:12 ---------------------------------------------------------------------------------- +4 |val _ = foo(1) // error + | ^ + | Implicit parameters should be provided with a `using` clause. + | This code can be rewritten automatically under -rewrite -source 3.7-migration. + | To disable the warning, please use the following option: + | "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s" diff --git a/tests/neg/i22440.scala b/tests/neg/i22440.scala new file mode 100644 index 000000000000..79de3b71d2ec --- /dev/null +++ b/tests/neg/i22440.scala @@ -0,0 +1,4 @@ +//> using options -source future + +def foo(implicit x: Int) = x +val _ = foo(1) // error diff --git a/tests/neg/i22498.check b/tests/neg/i22498.check new file mode 100644 index 000000000000..ed57392e975d --- /dev/null +++ b/tests/neg/i22498.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/i22498.scala:5:32 ---------------------------------------------------------------------------------- +5 | inline def proxy: Foo = new Foo(0) // error + | ^^^ + | Private constructors used in inline methods require @publicInBinary diff --git a/tests/neg/i22498.scala b/tests/neg/i22498.scala new file mode 100644 index 000000000000..54c59d2bb101 --- /dev/null +++ b/tests/neg/i22498.scala @@ -0,0 +1,5 @@ +import scala.annotation.publicInBinary + +class Foo: + private def this(x: Int) = this() + inline def proxy: Foo = new Foo(0) // error diff --git a/tests/neg/i22560.scala b/tests/neg/i22560.scala new file mode 100644 index 000000000000..2957ac4a1bf1 --- /dev/null +++ b/tests/neg/i22560.scala @@ -0,0 +1,22 @@ + +class A: + protected class B + +// This fails to compile, as expected +val x = A().B() // error + +object C: + protected val p = "protected" + protected def getString() = "Hello!" + protected class D: + def d = D() // ok + +// This fails to compile +// val y = C.p + +// And this also fails to compile +// val z = C.getString() + +// However, this compiles just fine. +val alpha = C.D() // error +val beta = new C.D() // error diff --git a/tests/neg/i22560b.scala b/tests/neg/i22560b.scala new file mode 100644 index 000000000000..58b41b6f8766 --- /dev/null +++ b/tests/neg/i22560b.scala @@ -0,0 +1,20 @@ + +class Enumeration: + protected class Val(i: Int): + def this() = this(42) + object Val + +class Test extends Enumeration: + val Hearts = Val(27) // error + val Diamonds = Val() // error + +package p: + private[p] class C(i: Int) // ctor proxy gets privateWithin of class + private[p] class D(i: Int) + object D + private class E(i: Int) + +package q: + def f() = p.C(42) // error + def g() = p.D(42) // error + def h() = p.E(42) // error diff --git a/tests/neg/i22560c/client_2.scala b/tests/neg/i22560c/client_2.scala new file mode 100644 index 000000000000..c04720cd207b --- /dev/null +++ b/tests/neg/i22560c/client_2.scala @@ -0,0 +1,11 @@ + +package i22560: + val alpha = C.D() // error + + class Test extends Enumeration: + val Hearts = Val(27) // error + val Diamonds = Val() // error + +package q: + def f() = p.C(42) // error + def g() = p.D(42) // error diff --git a/tests/neg/i22560c/lib_1.scala b/tests/neg/i22560c/lib_1.scala new file mode 100644 index 000000000000..2d9d24962b8d --- /dev/null +++ b/tests/neg/i22560c/lib_1.scala @@ -0,0 +1,16 @@ + +package i22560: + + object C: + protected class D + + class Enumeration: + protected class Val(i: Int): + def this() = this(42) + object Val + +package p: + private[p] class C(i: Int) // companion gets privateWithin of class + private[p] class D(i: Int) // ctor proxy gets privateWithin of class + object D + diff --git a/tests/neg/i22592.scala b/tests/neg/i22592.scala new file mode 100644 index 000000000000..6a9a89cacf2a --- /dev/null +++ b/tests/neg/i22592.scala @@ -0,0 +1,14 @@ +import scala.quoted.* + +trait Foo: + def inherited = () + +class Bar extends Foo: + def local = () + def localArg(arg: Any) = () + + def macro1(using Quotes): Expr[Unit] = '{ local } // error + def macro3(using Quotes): Expr[Unit] = '{ inherited } // error + def macro4(using Quotes): Expr[Unit] = '{ this.local } // error + def macro5(using Quotes): Expr[Unit] = '{ this.inherited } // error + def macro6(using Quotes): Expr[Unit] = '{ localArg(this) } // error // error diff --git a/tests/neg/i22620.scala b/tests/neg/i22620.scala new file mode 100644 index 000000000000..97d1d55e3302 --- /dev/null +++ b/tests/neg/i22620.scala @@ -0,0 +1,4 @@ + +import scala.collection.mutable.ArrayBuffer + +class PrivateTest[-M](private val v: ArrayBuffer[M]) // error diff --git a/tests/neg/i22721.check b/tests/neg/i22721.check new file mode 100644 index 000000000000..44b0749f3262 --- /dev/null +++ b/tests/neg/i22721.check @@ -0,0 +1,6 @@ +-- [E161] Naming Error: tests/neg/i22721/test2.scala:3:12 -------------------------------------------------------------- +3 |private def foobar: Int = 0 // error + |^^^^^^^^^^^^^^^^^^^^^^^^^^^ + |foobar is already defined as method foobar in tests/neg/i22721/test1.scala + | + |Note that overloaded methods must all be defined in the same group of toplevel definitions diff --git a/tests/neg/i22721/test1.scala b/tests/neg/i22721/test1.scala new file mode 100644 index 000000000000..b3f1b1fba690 --- /dev/null +++ b/tests/neg/i22721/test1.scala @@ -0,0 +1,4 @@ +package example + +private def foobar: String = "foo" +object test1 { def x = foobar } diff --git a/tests/neg/i22721/test2.scala b/tests/neg/i22721/test2.scala new file mode 100644 index 000000000000..c70a42894dcd --- /dev/null +++ b/tests/neg/i22721/test2.scala @@ -0,0 +1,4 @@ +package example + +private def foobar: Int = 0 // error +object test2 { def x = foobar } diff --git a/tests/neg/i22890.check b/tests/neg/i22890.check new file mode 100644 index 000000000000..f5e37b10dac2 --- /dev/null +++ b/tests/neg/i22890.check @@ -0,0 +1,7 @@ +-- [E161] Naming Error: tests/neg/i22890/caps_1.java:3:0 --------------------------------------------------------------- +3 |class caps { // error: caps is already defined as package caps + |^ + |caps is already defined as package scala.caps +package scala contains object and package with same name: caps. +This indicates that there are several versions of the Scala standard library on the classpath. +The build should be reconfigured so that only one version of the standard library is on the classpath. diff --git a/tests/neg/i22890/Test_2.scala b/tests/neg/i22890/Test_2.scala new file mode 100644 index 000000000000..4545d5ab94ae --- /dev/null +++ b/tests/neg/i22890/Test_2.scala @@ -0,0 +1,3 @@ +@main def Test = + println("hello") +// nopos-warn diff --git a/tests/neg/i22890/caps_1.java b/tests/neg/i22890/caps_1.java new file mode 100644 index 000000000000..9bc804829932 --- /dev/null +++ b/tests/neg/i22890/caps_1.java @@ -0,0 +1,5 @@ +package scala; + +class caps { // error: caps is already defined as package caps + static public void foo() {} +} \ No newline at end of file diff --git a/tests/neg/i4368.scala b/tests/neg/i4368.scala index 95c342d5fe2a..6718aece4eba 100644 --- a/tests/neg/i4368.scala +++ b/tests/neg/i4368.scala @@ -98,7 +98,7 @@ object Test6 { object Test7 { class Fix[F[_]] { - class Foo { type R >: F[T] <: F[T] } // error: cyclic + class Foo { type R >: F[T] <: F[T] } // error type T = F[Foo#R] } @@ -149,9 +149,9 @@ object Test9 { object i4369 { trait X { self => type R <: Z - type Z >: X { type R = self.R; type Z = self.R } // error: cyclic // error: cyclic // error: cyclic + type Z >: X { type R = self.R; type Z = self.R } // error: cyclic } - class Foo extends X { type R = Foo; type Z = Foo } + class Foo extends X { type R = Foo; type Z = Foo } // error } object i4370 { class Foo { type R = A } diff --git a/tests/neg/i4369c.scala b/tests/neg/i4369c.scala index a2bfbcb598d6..06a77d51cdbe 100644 --- a/tests/neg/i4369c.scala +++ b/tests/neg/i4369c.scala @@ -1,5 +1,18 @@ trait X { self => type R <: Z - type Z >: X { type R = self.R; type Z = self.R } // error // error // error + type Z >: + X { // error + type R = // was-error + self.R + type Z = // was-error + self.R + } +} + +class Foo // error + extends X { + type R = + Foo + type Z = + Foo } -class Foo extends X { type R = Foo; type Z = Foo } diff --git a/tests/neg/i8069.scala b/tests/neg/i8069.scala deleted file mode 100644 index 50f8b7a3480e..000000000000 --- a/tests/neg/i8069.scala +++ /dev/null @@ -1,8 +0,0 @@ -trait A: - type B - -enum Test: - case Test(a: A, b: a.B) // error: Implementation restriction: case classes cannot have dependencies between parameters - -case class Test2(a: A, b: a.B) // error: Implementation restriction: case classes cannot have dependencies between parameters - diff --git a/tests/neg/infer-tracked-explicit-witness.scala b/tests/neg/infer-tracked-explicit-witness.scala new file mode 100644 index 000000000000..853cec748b03 --- /dev/null +++ b/tests/neg/infer-tracked-explicit-witness.scala @@ -0,0 +1,18 @@ +import scala.language.experimental.modularity + +trait T: + type Self + type X + def foo: Self + +class D[C](using wd: C is T) +class E(using we: Int is T) + +def Test = + given w: Int is T: + def foo: Int = 42 + type X = Long + val d = D(using w) + summon[d.wd.X =:= Long] // error + val e = E(using w) + summon[e.we.X =:= Long] // error diff --git a/tests/neg/infix-named-args.check b/tests/neg/infix-named-args.check index d960892a9624..d9b7998f4dec 100644 --- a/tests/neg/infix-named-args.check +++ b/tests/neg/infix-named-args.check @@ -1,5 +1,5 @@ --- [E134] Type Error: tests/neg/infix-named-args.scala:4:13 ------------------------------------------------------------ -4 | def f = 42 + (x = 1) // error // werror +-- [E134] Type Error: tests/neg/infix-named-args.scala:2:13 ------------------------------------------------------------ +2 | def f = 42 + (x = 1) // error // werror | ^^^^ | None of the overloaded alternatives of method + in class Int with types | (x: Double): Double @@ -11,27 +11,27 @@ | (x: Byte): Int | (x: String): String | match arguments ((x : Int)) (a named tuple) --- [E204] Syntax Warning: tests/neg/infix-named-args.scala:4:15 -------------------------------------------------------- -4 | def f = 42 + (x = 1) // error // werror +-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:2:15 -------------------------------------------------------- +2 | def f = 42 + (x = 1) // error // werror | ^^^^^^^ - |Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument. + |Deprecated syntax: infix named arguments lists are deprecated; since 3.7 it is interpreted as a single name tuple argument. |To avoid this warning, either remove the argument names or use dotted selection. - |This can be rewritten automatically under -rewrite -source 3.6-migration. --- [E204] Syntax Warning: tests/neg/infix-named-args.scala:7:26 -------------------------------------------------------- -7 | def g = new C() `multi` (x = 42, y = 27) // werror + |This can be rewritten automatically under -rewrite -source 3.7-migration. +-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:5:26 -------------------------------------------------------- +5 | def g = new C() `multi` (x = 42, y = 27) // werror | ^^^^^^^^^^^^^^^^ - |Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument. + |Deprecated syntax: infix named arguments lists are deprecated; since 3.7 it is interpreted as a single name tuple argument. |To avoid this warning, either remove the argument names or use dotted selection. - |This can be rewritten automatically under -rewrite -source 3.6-migration. --- [E204] Syntax Warning: tests/neg/infix-named-args.scala:8:21 -------------------------------------------------------- -8 | def h = new C() ** (x = 42, y = 27) // werror + |This can be rewritten automatically under -rewrite -source 3.7-migration. +-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:6:21 -------------------------------------------------------- +6 | def h = new C() ** (x = 42, y = 27) // werror | ^^^^^^^^^^^^^^^^ - |Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument. + |Deprecated syntax: infix named arguments lists are deprecated; since 3.7 it is interpreted as a single name tuple argument. |To avoid this warning, either remove the argument names or use dotted selection. - |This can be rewritten automatically under -rewrite -source 3.6-migration. --- [E204] Syntax Warning: tests/neg/infix-named-args.scala:15:18 ------------------------------------------------------- -15 | def f = this ** (x = 2) // werror + |This can be rewritten automatically under -rewrite -source 3.7-migration. +-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:13:18 ------------------------------------------------------- +13 | def f = this ** (x = 2) // werror | ^^^^^^^ - |Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument. + |Deprecated syntax: infix named arguments lists are deprecated; since 3.7 it is interpreted as a single name tuple argument. |To avoid this warning, either remove the argument names or use dotted selection. - |This can be rewritten automatically under -rewrite -source 3.6-migration. + |This can be rewritten automatically under -rewrite -source 3.7-migration. diff --git a/tests/neg/infix-named-args.scala b/tests/neg/infix-named-args.scala index b0ef555cf965..d8616899540c 100644 --- a/tests/neg/infix-named-args.scala +++ b/tests/neg/infix-named-args.scala @@ -1,5 +1,3 @@ -import scala.language.experimental.namedTuples - class C: def f = 42 + (x = 1) // error // werror def multi(x: Int, y: Int): Int = x + y diff --git a/tests/neg/inline-unstable-accessors.scala b/tests/neg/inline-unstable-accessors.scala index c02097f1921a..a7fa75c2c6c9 100644 --- a/tests/neg/inline-unstable-accessors.scala +++ b/tests/neg/inline-unstable-accessors.scala @@ -1,4 +1,4 @@ -//> using options -experimental -Werror -WunstableInlineAccessors -explain +//> using options -Werror -WunstableInlineAccessors -explain package foo import scala.annotation.publicInBinary diff --git a/tests/neg/mt-deskolemize-2.scala b/tests/neg/mt-deskolemize-2.scala index 505e47637ac4..9355b5eaf3b8 100644 --- a/tests/neg/mt-deskolemize-2.scala +++ b/tests/neg/mt-deskolemize-2.scala @@ -19,8 +19,8 @@ trait Description: type Elem <: Tuple class PrimBroken extends Expr: - type Value = Alias - type Alias = Value // error + type Value = Alias // error + type Alias = Value class Prim extends Expr: type Value = BigInt diff --git a/tests/neg/named-tuple-selectable.scala b/tests/neg/named-tuple-selectable.scala index 5cf7e68654ef..c81eba1237ff 100644 --- a/tests/neg/named-tuple-selectable.scala +++ b/tests/neg/named-tuple-selectable.scala @@ -1,5 +1,3 @@ -import scala.language.experimental.namedTuples - class FromFields extends Selectable: type Fields = (i: Int) def selectDynamic(key: String) = diff --git a/tests/neg/named-tuples-2.check b/tests/neg/named-tuples-2.check index 0a52d5f3989b..daa1c0d69069 100644 --- a/tests/neg/named-tuples-2.check +++ b/tests/neg/named-tuples-2.check @@ -1,8 +1,8 @@ --- Error: tests/neg/named-tuples-2.scala:5:9 --------------------------------------------------------------------------- -5 | case (name, age) => () // error +-- Error: tests/neg/named-tuples-2.scala:4:9 --------------------------------------------------------------------------- +4 | case (name, age) => () // error | ^ | this case is unreachable since type (String, Int, Boolean) is not a subclass of class Tuple2 --- Error: tests/neg/named-tuples-2.scala:6:9 --------------------------------------------------------------------------- -6 | case (n, a, m, x) => () // error +-- Error: tests/neg/named-tuples-2.scala:5:9 --------------------------------------------------------------------------- +5 | case (n, a, m, x) => () // error | ^ | this case is unreachable since type (String, Int, Boolean) is not a subclass of class Tuple4 diff --git a/tests/neg/named-tuples-2.scala b/tests/neg/named-tuples-2.scala index 0507891e0549..b3917d9ad57c 100644 --- a/tests/neg/named-tuples-2.scala +++ b/tests/neg/named-tuples-2.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples def Test = val person = (name = "Bob", age = 33, married = true) person match diff --git a/tests/neg/named-tuples-3.check b/tests/neg/named-tuples-3.check index 2091c36191c0..2809836b4803 100644 --- a/tests/neg/named-tuples-3.check +++ b/tests/neg/named-tuples-3.check @@ -1,5 +1,5 @@ --- [E007] Type Mismatch Error: tests/neg/named-tuples-3.scala:7:16 ----------------------------------------------------- -7 |val p: Person = f // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples-3.scala:5:16 ----------------------------------------------------- +5 |val p: Person = f // error | ^ | Found: NamedTuple.NamedTuple[(Int, Any), (Int, String)] | Required: Person diff --git a/tests/neg/named-tuples-3.scala b/tests/neg/named-tuples-3.scala index 0f1215338b0a..21e6ed9b3741 100644 --- a/tests/neg/named-tuples-3.scala +++ b/tests/neg/named-tuples-3.scala @@ -1,5 +1,3 @@ -import language.experimental.namedTuples - def f: NamedTuple.NamedTuple[(Int, Any), (Int, String)] = ??? type Person = (name: Int, age: String) diff --git a/tests/neg/named-tuples-mirror.check b/tests/neg/named-tuples-mirror.check new file mode 100644 index 000000000000..df6d570efa9f --- /dev/null +++ b/tests/neg/named-tuples-mirror.check @@ -0,0 +1,8 @@ +-- [E172] Type Error: tests/neg/named-tuples-mirror.scala:5:47 --------------------------------------------------------- +5 | summon[Mirror.SumOf[(foo: Int, bla: String)]] // error + | ^ + |No given instance of type scala.deriving.Mirror.SumOf[(foo : Int, bla : String)] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type scala.deriving.Mirror.SumOf[(foo : Int, bla : String)]: type `(foo : Int, bla : String)` is not a generic sum because named tuples are not sealed classes +-- Error: tests/neg/named-tuples-mirror.scala:8:4 ---------------------------------------------------------------------- +8 | }]// error + | ^ + |MirroredElemLabels mismatch, expected: (("foo" : String), ("bla" : String)), found: (("foo" : String), ("ba" : String)). diff --git a/tests/neg/named-tuples-mirror.scala b/tests/neg/named-tuples-mirror.scala new file mode 100644 index 000000000000..381eaf839262 --- /dev/null +++ b/tests/neg/named-tuples-mirror.scala @@ -0,0 +1,9 @@ +import scala.deriving.* +import scala.compiletime.* + +@main def Test = + summon[Mirror.SumOf[(foo: Int, bla: String)]] // error + val namedTuple = summon[Mirror.Of[(foo: Int, bla: String)]{ + type MirroredElemLabels = ("foo", "ba") + }]// error + diff --git a/tests/neg/named-tuples.check b/tests/neg/named-tuples.check index db3cc703722f..8ec958b6a75d 100644 --- a/tests/neg/named-tuples.check +++ b/tests/neg/named-tuples.check @@ -1,101 +1,101 @@ --- Error: tests/neg/named-tuples.scala:9:19 ---------------------------------------------------------------------------- -9 | val illformed = (_2 = 2) // error +-- Error: tests/neg/named-tuples.scala:8:19 ---------------------------------------------------------------------------- +8 | val illformed = (_2 = 2) // error | ^^^^^^ | _2 cannot be used as the name of a tuple element because it is a regular tuple selector --- Error: tests/neg/named-tuples.scala:10:20 --------------------------------------------------------------------------- -10 | type Illformed = (_1: Int) // error - | ^^^^^^^ - | _1 cannot be used as the name of a tuple element because it is a regular tuple selector --- Error: tests/neg/named-tuples.scala:11:40 --------------------------------------------------------------------------- -11 | val illformed2 = (name = "", age = 0, name = true) // error +-- Error: tests/neg/named-tuples.scala:9:20 ---------------------------------------------------------------------------- +9 | type Illformed = (_1: Int) // error + | ^^^^^^^ + | _1 cannot be used as the name of a tuple element because it is a regular tuple selector +-- Error: tests/neg/named-tuples.scala:10:40 --------------------------------------------------------------------------- +10 | val illformed2 = (name = "", age = 0, name = true) // error | ^^^^^^^^^^^ | Duplicate tuple element name --- Error: tests/neg/named-tuples.scala:12:45 --------------------------------------------------------------------------- -12 | type Illformed2 = (name: String, age: Int, name: Boolean) // error +-- Error: tests/neg/named-tuples.scala:11:45 --------------------------------------------------------------------------- +11 | type Illformed2 = (name: String, age: Int, name: Boolean) // error | ^^^^^^^^^^^^^ | Duplicate tuple element name --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:20:20 ------------------------------------------------------ -20 | val _: NameOnly = person // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:19:20 ------------------------------------------------------ +19 | val _: NameOnly = person // error | ^^^^^^ | Found: (Test.person : (name : String, age : Int)) | Required: Test.NameOnly | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:21:18 ------------------------------------------------------ -21 | val _: Person = nameOnly // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:20:18 ------------------------------------------------------ +20 | val _: Person = nameOnly // error | ^^^^^^^^ | Found: (Test.nameOnly : (name : String)) | Required: Test.Person | | longer explanation available when compiling with `-explain` --- [E172] Type Error: tests/neg/named-tuples.scala:22:41 --------------------------------------------------------------- -22 | val _: Person = (name = "") ++ nameOnly // error +-- [E172] Type Error: tests/neg/named-tuples.scala:21:41 --------------------------------------------------------------- +21 | val _: Person = (name = "") ++ nameOnly // error | ^ | Cannot prove that Tuple.Disjoint[Tuple1[("name" : String)], Tuple1[("name" : String)]] =:= (true : Boolean). --- [E008] Not Found Error: tests/neg/named-tuples.scala:23:9 ----------------------------------------------------------- -23 | person._1 // error +-- [E008] Not Found Error: tests/neg/named-tuples.scala:22:9 ----------------------------------------------------------- +22 | person._1 // error | ^^^^^^^^^ | value _1 is not a member of (name : String, age : Int) --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:25:36 ------------------------------------------------------ -25 | val _: (age: Int, name: String) = person // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:24:36 ------------------------------------------------------ +24 | val _: (age: Int, name: String) = person // error | ^^^^^^ | Found: (Test.person : (name : String, age : Int)) | Required: (age : Int, name : String) | | longer explanation available when compiling with `-explain` --- Error: tests/neg/named-tuples.scala:27:17 --------------------------------------------------------------------------- -27 | val (name = x, agee = y) = person // error +-- Error: tests/neg/named-tuples.scala:26:17 --------------------------------------------------------------------------- +26 | val (name = x, agee = y) = person // error | ^^^^^^^^ | No element named `agee` is defined in selector type (name : String, age : Int) --- Error: tests/neg/named-tuples.scala:30:10 --------------------------------------------------------------------------- -30 | case (name = n, age = a) => () // error // error +-- Error: tests/neg/named-tuples.scala:29:10 --------------------------------------------------------------------------- +29 | case (name = n, age = a) => () // error // error | ^^^^^^^^ | No element named `name` is defined in selector type (String, Int) --- Error: tests/neg/named-tuples.scala:30:20 --------------------------------------------------------------------------- -30 | case (name = n, age = a) => () // error // error +-- Error: tests/neg/named-tuples.scala:29:20 --------------------------------------------------------------------------- +29 | case (name = n, age = a) => () // error // error | ^^^^^^^ | No element named `age` is defined in selector type (String, Int) --- [E172] Type Error: tests/neg/named-tuples.scala:32:27 --------------------------------------------------------------- -32 | val pp = person ++ (1, 2) // error +-- [E172] Type Error: tests/neg/named-tuples.scala:31:27 --------------------------------------------------------------- +31 | val pp = person ++ (1, 2) // error | ^ | Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), Tuple] =:= (true : Boolean). --- [E172] Type Error: tests/neg/named-tuples.scala:35:18 --------------------------------------------------------------- -35 | person ++ (1, 2) match // error +-- [E172] Type Error: tests/neg/named-tuples.scala:34:18 --------------------------------------------------------------- +34 | person ++ (1, 2) match // error | ^ | Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), Tuple] =:= (true : Boolean). --- Error: tests/neg/named-tuples.scala:38:17 --------------------------------------------------------------------------- -38 | val bad = ("", age = 10) // error +-- Error: tests/neg/named-tuples.scala:37:17 --------------------------------------------------------------------------- +37 | val bad = ("", age = 10) // error | ^^^^^^^^ | Illegal combination of named and unnamed tuple elements --- Error: tests/neg/named-tuples.scala:41:20 --------------------------------------------------------------------------- -41 | case (name = n, age) => () // error +-- Error: tests/neg/named-tuples.scala:40:20 --------------------------------------------------------------------------- +40 | case (name = n, age) => () // error | ^^^ | Illegal combination of named and unnamed tuple elements --- Error: tests/neg/named-tuples.scala:42:16 --------------------------------------------------------------------------- -42 | case (name, age = a) => () // error +-- Error: tests/neg/named-tuples.scala:41:16 --------------------------------------------------------------------------- +41 | case (name, age = a) => () // error | ^^^^^^^ | Illegal combination of named and unnamed tuple elements --- Error: tests/neg/named-tuples.scala:45:10 --------------------------------------------------------------------------- -45 | case (age = x) => // error +-- Error: tests/neg/named-tuples.scala:44:10 --------------------------------------------------------------------------- +44 | case (age = x) => // error | ^^^^^^^ | No element named `age` is defined in selector type Tuple --- [E172] Type Error: tests/neg/named-tuples.scala:47:27 --------------------------------------------------------------- -47 | val p2 = person ++ person // error +-- [E172] Type Error: tests/neg/named-tuples.scala:46:27 --------------------------------------------------------------- +46 | val p2 = person ++ person // error | ^ |Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), (("name" : String), ("age" : String))] =:= (true : Boolean). --- [E172] Type Error: tests/neg/named-tuples.scala:48:43 --------------------------------------------------------------- -48 | val p3 = person ++ (first = 11, age = 33) // error +-- [E172] Type Error: tests/neg/named-tuples.scala:47:43 --------------------------------------------------------------- +47 | val p3 = person ++ (first = 11, age = 33) // error | ^ |Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), (("first" : String), ("age" : String))] =:= (true : Boolean). --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:50:22 ------------------------------------------------------ -50 | val p5 = person.zip((first = 11, age = 33)) // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:49:22 ------------------------------------------------------ +49 | val p5 = person.zip((first = 11, age = 33)) // error | ^^^^^^^^^^^^^^^^^^^^^^ | Found: (first : Int, age : Int) | Required: NamedTuple.NamedTuple[(("name" : String), ("age" : String)), Tuple] | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:61:32 ------------------------------------------------------ -61 | val typo: (name: ?, age: ?) = (name = "he", ag = 1) // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:60:32 ------------------------------------------------------ +60 | val typo: (name: ?, age: ?) = (name = "he", ag = 1) // error | ^^^^^^^^^^^^^^^^^^^^^ | Found: (name : String, ag : Int) | Required: (name : ?, age : ?) diff --git a/tests/neg/named-tuples.scala b/tests/neg/named-tuples.scala index 8f78f7915206..daae6e26bac2 100644 --- a/tests/neg/named-tuples.scala +++ b/tests/neg/named-tuples.scala @@ -1,7 +1,6 @@ import annotation.experimental -import language.experimental.namedTuples -@experimental object Test: +object Test: type Person = (name: String, age: Int) val person = (name = "Bob", age = 33): (name: String, age: Int) diff --git a/tests/neg/no-kind-polymorphism-anykind.scala b/tests/neg/no-kind-polymorphism-anykind.scala deleted file mode 100644 index b14491468d00..000000000000 --- a/tests/neg/no-kind-polymorphism-anykind.scala +++ /dev/null @@ -1,3 +0,0 @@ -//> using options -Yno-kind-polymorphism - -trait Foo[T <: AnyKind] // error: Not found: type AnyKind diff --git a/tests/neg/nowarn.check b/tests/neg/nowarn.check index 636cabd44d07..ff01de1788bd 100644 --- a/tests/neg/nowarn.check +++ b/tests/neg/nowarn.check @@ -24,9 +24,9 @@ | ^^^^^ | A try without catch or finally is equivalent to putting | its body in a block; no exceptions are handled. -Matching filters for @nowarn or -Wconf: - - id=E2 - - name=EmptyCatchAndFinallyBlock + |Matching filters for @nowarn or -Wconf: + | - id=E2 + | - name=EmptyCatchAndFinallyBlock | | longer explanation available when compiling with `-explain` -- [E129] Potential Issue Warning: tests/neg/nowarn.scala:15:11 -------------------------------------------------------- diff --git a/tests/neg/preview-message.check b/tests/neg/preview-message.check new file mode 100644 index 000000000000..c3478c27fd46 --- /dev/null +++ b/tests/neg/preview-message.check @@ -0,0 +1,18 @@ +-- Error: tests/neg/preview-message.scala:15:2 ------------------------------------------------------------------------- +15 | f1() // error + | ^^ + | method f1 is marked @preview + | + | Preview definition may only be used when compiling with the `-preview` compiler flag +-- Error: tests/neg/preview-message.scala:16:2 ------------------------------------------------------------------------- +16 | f2() // error + | ^^ + | method f2 is marked @preview + | + | Preview definition may only be used when compiling with the `-preview` compiler flag +-- Error: tests/neg/preview-message.scala:17:2 ------------------------------------------------------------------------- +17 | f3() // error + | ^^ + | method f3 is marked @preview: not yet stable + | + | Preview definition may only be used when compiling with the `-preview` compiler flag diff --git a/tests/neg/preview-message.scala b/tests/neg/preview-message.scala new file mode 100644 index 000000000000..99bdd72cd936 --- /dev/null +++ b/tests/neg/preview-message.scala @@ -0,0 +1,17 @@ +package scala // @preview is private[scala] + +import scala.annotation.internal.preview + +@preview +def f1() = ??? + +@preview() +def f2() = ??? + +@preview("not yet stable") +def f3() = ??? + +def g() = + f1() // error + f2() // error + f3() // error diff --git a/tests/neg/preview-non-viral/defs_1.scala b/tests/neg/preview-non-viral/defs_1.scala new file mode 100644 index 000000000000..434f39e13c94 --- /dev/null +++ b/tests/neg/preview-non-viral/defs_1.scala @@ -0,0 +1,7 @@ +//> using options -preview +package scala // @preview is private[scala] +import scala.annotation.internal.preview + +@preview def previewFeature = 42 + +def usePreviewFeature = previewFeature diff --git a/tests/neg/preview-non-viral/usage_2.scala b/tests/neg/preview-non-viral/usage_2.scala new file mode 100644 index 000000000000..50404e582dff --- /dev/null +++ b/tests/neg/preview-non-viral/usage_2.scala @@ -0,0 +1,2 @@ +def usePreviewFeatureTransitively = scala.usePreviewFeature +def usePreviewFeatureDirectly = scala.previewFeature // error diff --git a/tests/neg/previewOverloads.scala b/tests/neg/previewOverloads.scala new file mode 100644 index 000000000000..e324bc535772 --- /dev/null +++ b/tests/neg/previewOverloads.scala @@ -0,0 +1,13 @@ +package scala // @preview is private[scala] + +import scala.annotation.internal.preview + +trait A: + def f: Int + def g: Int = 3 +trait B extends A: + @preview + def f: Int = 4 // error + + @preview + override def g: Int = 5 // error diff --git a/tests/neg/previewOverride.scala b/tests/neg/previewOverride.scala new file mode 100644 index 000000000000..4a772506f7b6 --- /dev/null +++ b/tests/neg/previewOverride.scala @@ -0,0 +1,41 @@ +package scala // @preview is private[scala] + +import scala.annotation.internal.preview + +@preview +class A: + def f() = 1 + +@preview +class B extends A: + override def f() = 2 + +class C: + @preview + def f() = 1 + +class D extends C: + override def f() = 2 + +trait A2: + @preview + def f(): Int + +trait B2: + def f(): Int + +class C2 extends A2, B2: + def f(): Int = 1 + +def test: Unit = + val a: A = ??? // error + val b: B = ??? // error + val c: C = ??? + val d: D = ??? + val c2: C2 = ??? + a.f() // error + b.f() // error + c.f() // error + d.f() // ok because D.f is a stable API + c2.f() // ok because B2.f is a stable API + () diff --git a/tests/neg/publicInBinaryOverride.check b/tests/neg/publicInBinaryOverride.check index 73c60fa55d6a..e44692c78525 100644 --- a/tests/neg/publicInBinaryOverride.check +++ b/tests/neg/publicInBinaryOverride.check @@ -1,5 +1,5 @@ --- [E164] Declaration Error: tests/neg/publicInBinaryOverride.scala:10:15 ---------------------------------------------- -10 | override def f(): Unit = () // error - | ^ - | error overriding method f in class A of type (): Unit; - | method f of type (): Unit also needs to be declared with @publicInBinary +-- [E164] Declaration Error: tests/neg/publicInBinaryOverride.scala:8:15 ----------------------------------------------- +8 | override def f(): Unit = () // error + | ^ + | error overriding method f in class A of type (): Unit; + | method f of type (): Unit also needs to be declared with @publicInBinary diff --git a/tests/neg/publicInBinaryOverride.scala b/tests/neg/publicInBinaryOverride.scala index 6529bf09736a..4b9144d27540 100644 --- a/tests/neg/publicInBinaryOverride.scala +++ b/tests/neg/publicInBinaryOverride.scala @@ -1,5 +1,3 @@ -//> using options -experimental - import scala.annotation.publicInBinary class A: diff --git a/tests/neg/toplevel-cyclic/defs_1.scala b/tests/neg/toplevel-cyclic/defs_1.scala index 34b0475066b0..f40fa5bdebdd 100644 --- a/tests/neg/toplevel-cyclic/defs_1.scala +++ b/tests/neg/toplevel-cyclic/defs_1.scala @@ -1 +1 @@ -type A = B +type A = B // error: recursion limit exceeded diff --git a/tests/neg/toplevel-cyclic/moredefs_1.scala b/tests/neg/toplevel-cyclic/moredefs_1.scala index 3c8c3de93aa7..91360d0c0f4d 100644 --- a/tests/neg/toplevel-cyclic/moredefs_1.scala +++ b/tests/neg/toplevel-cyclic/moredefs_1.scala @@ -1 +1 @@ -type B = A // error: recursion limit exceeded +type B = A diff --git a/tests/neg/toplevel-overload/moredefs_1.scala b/tests/neg/toplevel-overload/moredefs_1.scala index 5ba8cfc52078..74cdd3ac1bb6 100644 --- a/tests/neg/toplevel-overload/moredefs_1.scala +++ b/tests/neg/toplevel-overload/moredefs_1.scala @@ -2,4 +2,4 @@ trait B def f(x: B) = s"B" // error: has already been compiled -private def g(): Unit = () // OK, since it is private \ No newline at end of file +private def g(): Unit = () // error: has already been compiled (def is visible in package) \ No newline at end of file diff --git a/tests/neg/type-params.check b/tests/neg/type-params.check new file mode 100644 index 000000000000..e1eefb4c6fe9 --- /dev/null +++ b/tests/neg/type-params.check @@ -0,0 +1,103 @@ +-- [E053] Type Error: tests/neg/type-params.scala:14:13 ---------------------------------------------------------------- +14 | type t = x[x] // error + | ^^^^ + | x does not take type parameters + | + | longer explanation available when compiling with `-explain` +-- [E053] Type Error: tests/neg/type-params.scala:16:13 ---------------------------------------------------------------- +16 | val foo: s[Int] // error + | ^^^^^^ + | s does not take type parameters + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg/type-params.scala:21:26 ---------------------------------------------------------------------------- +21 | object mp extends Monad[Tuple2] // error + | ^^^^^^ + | Type argument [T1, T2] =>> (T1, T2) does not have the same kind as its bound [x] +-- Error: tests/neg/type-params.scala:24:26 ---------------------------------------------------------------------------- +24 | trait ms1 extends Monad[String] // error + | ^^^^^^ + | Type argument String does not have the same kind as its bound [x] +-- Error: tests/neg/type-params.scala:25:29 ---------------------------------------------------------------------------- +25 | trait ms2[t] extends Monad[t] // error + | ^ + | Type argument t does not have the same kind as its bound [x] +-- Error: tests/neg/type-params.scala:26:35 ---------------------------------------------------------------------------- +26 | trait ms3[m[_], t] extends Monad[m[t]] // error -- added to check regression on bug + | ^^^^ + | Type argument m[t] does not have the same kind as its bound [x] +-- Error: tests/neg/type-params.scala:31:31 ---------------------------------------------------------------------------- +31 | trait Bar2[m[_]] extends Foo[m] // error check that m is properly recognized as kind *->*, while * is expected + | ^ + | Type argument m does not have the same kind as its bound +-- [E053] Type Error: tests/neg/type-params.scala:37:20 ---------------------------------------------------------------- +37 |class t1701 extends java.lang.Cloneable[String, Option, Int] // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Cloneable does not take type parameters + | + | longer explanation available when compiling with `-explain` +-- [E053] Type Error: tests/neg/type-params.scala:39:24 ---------------------------------------------------------------- +39 |trait t0842[T] { def m: this.type[T] = this } // error + | ^^^^^^^^^^^^ + | (t0842.this : t0842[T]) does not take type parameters + | + | longer explanation available when compiling with `-explain` +-- [E134] Type Error: tests/neg/type-params.scala:45:10 ---------------------------------------------------------------- +45 | println(a[A]) // error + | ^ + | None of the overloaded alternatives of method apply in trait Function1 with types + | (v1: t278.this.A): Unit + | (): Unit + | match type arguments [t278.this.A] and expected type Any +-- [E120] Naming Error: tests/neg/type-params.scala:44:6 --------------------------------------------------------------- +44 | def a = (p: A) => () // error + | ^ + | Double definition: + | def a: () => Unit in trait t278 at line 43 and + | def a: t278.this.A => Unit in trait t278 at line 44 + | have the same type after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. +-- Error: tests/neg/type-params.scala:4:7 ------------------------------------------------------------------------------ +4 | "".==[Int] // error + | ^^^^^^^^^^ + | method == in class Any does not take type parameters +-- Error: tests/neg/type-params.scala:5:17 ----------------------------------------------------------------------------- +5 | ("": AnyRef).==[Int] // error + | ^^^^^^^^^^^^^^^^^^^^ + | method == in class Any does not take type parameters +-- Error: tests/neg/type-params.scala:6:17 ----------------------------------------------------------------------------- +6 | ("": Object).==[Int] // error + | ^^^^^^^^^^^^^^^^^^^^ + | method == in class Any does not take type parameters +-- Error: tests/neg/type-params.scala:9:14 ----------------------------------------------------------------------------- +9 | classOf[Int][Int] // error + | ^^^^^^^^^^^^^^^^^ + | illegal repeated type application + | You might have meant something like: + | classOf[Int, Int] +-- [E140] Cyclic Error: tests/neg/type-params.scala:34:11 -------------------------------------------------------------- +34 | def g[X, A[X] <: A[X]](x: A[X]) = x // error // error + | ^ + | illegal cyclic type reference: upper bound ... of type A refers back to the type itself + | + | Run with -explain-cyclic for more details. +-- [E053] Type Error: tests/neg/type-params.scala:34:28 ---------------------------------------------------------------- +34 | def g[X, A[X] <: A[X]](x: A[X]) = x // error // error + | ^^^^ + | A does not take type parameters + | + | longer explanation available when compiling with `-explain` +-- [E140] Cyclic Error: tests/neg/type-params.scala:35:8 --------------------------------------------------------------- +35 | def f[C[X] <: C[X]](l: C[_]) = l.x // error // error + | ^ + | illegal cyclic type reference: upper bound ... of type C refers back to the type itself + | + | Run with -explain-cyclic for more details. +-- [E053] Type Error: tests/neg/type-params.scala:35:25 ---------------------------------------------------------------- +35 | def f[C[X] <: C[X]](l: C[_]) = l.x // error // error + | ^^^^ + | C does not take type parameters + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/type-params.scala b/tests/neg/type-params.scala new file mode 100644 index 000000000000..2803141f3b85 --- /dev/null +++ b/tests/neg/type-params.scala @@ -0,0 +1,45 @@ + +def `t8219 Any == is not overloaded in error message`: Unit = + // Scala 2 once defined AnyRef == and Any == as overloaded. Spec defines only Any ==. + "".==[Int] // error + ("": AnyRef).==[Int] // error + ("": Object).==[Int] // error + +def `extra type arg to class literal` = + classOf[Int][Int] // error + +class `various ancient kindedness`: + + class WellKinded[x]: + type t = x[x] // error + trait WellKindedBounded[s <: Throwable]: + val foo: s[Int] // error + class WellKindedWrongSyntax[s <: List] // must be s[x] <: List[x] ? + + class Monad[m[x]] + + object mp extends Monad[Tuple2] // error + + // expecting types of kind *->* + trait ms1 extends Monad[String] // error + trait ms2[t] extends Monad[t] // error + trait ms3[m[_], t] extends Monad[m[t]] // error -- added to check regression on bug + + // expecting types of kind * + trait Foo[x] + trait Bar1[m[_]] extends Foo[m[Int]] // check that m[Int] is properly recognized as kind-* + trait Bar2[m[_]] extends Foo[m] // error check that m is properly recognized as kind *->*, while * is expected + +def `t2918 t5093 detect cyclic error` = + def g[X, A[X] <: A[X]](x: A[X]) = x // error // error + def f[C[X] <: C[X]](l: C[_]) = l.x // error // error + +class t1701 extends java.lang.Cloneable[String, Option, Int] // error + +trait t0842[T] { def m: this.type[T] = this } // error + +trait t278: + class A + def a = () => () + def a = (p: A) => () // error + println(a[A]) // error diff --git a/tests/neg/unroll-abstractMethod.check b/tests/neg/unroll-abstractMethod.check new file mode 100644 index 000000000000..d0874c8a44d8 --- /dev/null +++ b/tests/neg/unroll-abstractMethod.check @@ -0,0 +1,8 @@ +-- [E207] Declaration Error: tests/neg/unroll-abstractMethod.scala:6:41 ------------------------------------------------ +6 | def foo(s: String, n: Int = 1, @unroll b: Boolean = true): String // error + | ^ + | Cannot unroll parameters of method foo: it must not be abstract +-- [E207] Declaration Error: tests/neg/unroll-abstractMethod.scala:10:41 ----------------------------------------------- +10 | def foo(s: String, n: Int = 1, @unroll b: Boolean = true): String // error + | ^ + | Cannot unroll parameters of method foo: it must not be abstract diff --git a/tests/neg/unroll-abstractMethod.scala b/tests/neg/unroll-abstractMethod.scala new file mode 100644 index 000000000000..8f30fb63b20f --- /dev/null +++ b/tests/neg/unroll-abstractMethod.scala @@ -0,0 +1,11 @@ +//> using options -experimental + +import scala.annotation.unroll + +trait Unrolled { + def foo(s: String, n: Int = 1, @unroll b: Boolean = true): String // error +} + +abstract class UnrolledBase { + def foo(s: String, n: Int = 1, @unroll b: Boolean = true): String // error +} diff --git a/tests/neg/unroll-clash.check b/tests/neg/unroll-clash.check new file mode 100644 index 000000000000..a398c550c461 --- /dev/null +++ b/tests/neg/unroll-clash.check @@ -0,0 +1,12 @@ +-- Error: tests/neg/unroll-clash.scala:7:6 ----------------------------------------------------------------------------- +7 | def foo(x: Int) = { // error + | ^ + | Unrolled method foo clashes with existing declaration. + | Please remove the clashing definition, or the @unroll annotation. + | Unrolled from final def foo(x: Int, y: Int): Int in class Foo at line 6 +-- Error: tests/neg/unroll-clash.scala:13:20 --------------------------------------------------------------------------- +13 |class UnrolledClass2(s: String) { // error + | ^ + | Unrolled constructor UnrolledClass2 clashes with existing declaration. + | Please remove the clashing definition, or the @unroll annotation. + | Unrolled from def (s: String, y: Boolean): UnrolledClass2 in class UnrolledClass2 at line 15 diff --git a/tests/neg/unroll-clash.scala b/tests/neg/unroll-clash.scala new file mode 100644 index 000000000000..79a75c2ba785 --- /dev/null +++ b/tests/neg/unroll-clash.scala @@ -0,0 +1,18 @@ +//> using options -experimental + +import scala.annotation.unroll + +class Foo { + final def foo(x: Int, @unroll y: Int = 0) = x + y + def foo(x: Int) = { // error + println("Not binary compatible!") + -1 + } +} + +class UnrolledClass2(s: String) { // error + + def this(s: String, @unroll y: Boolean = true) = this(s + y) + + override def toString = s"UnrolledClass2($s)" +} diff --git a/tests/neg/unroll-clause-interleaving.check b/tests/neg/unroll-clause-interleaving.check new file mode 100644 index 000000000000..eea8a7383e09 --- /dev/null +++ b/tests/neg/unroll-clause-interleaving.check @@ -0,0 +1,7 @@ +-- Error: tests/neg/unroll-clause-interleaving.scala:6:12 -------------------------------------------------------------- +6 | final def foo(@unroll x: Int = 0)[T](// error + | ^ + | Cannot have multiple parameter lists containing `@unroll` annotation +7 | s: T, +8 | @unroll y: Boolean = true, +9 | ): String = "" + x + s + y diff --git a/tests/neg/unroll-clause-interleaving.scala b/tests/neg/unroll-clause-interleaving.scala new file mode 100644 index 000000000000..c40941320db1 --- /dev/null +++ b/tests/neg/unroll-clause-interleaving.scala @@ -0,0 +1,10 @@ +//> using options -experimental + +import scala.annotation.unroll + +class Unrolled { + final def foo(@unroll x: Int = 0)[T](// error + s: T, + @unroll y: Boolean = true, + ): String = "" + x + s + y +} diff --git a/tests/neg/unroll-duped.check b/tests/neg/unroll-duped.check new file mode 100644 index 000000000000..9ec09672566b --- /dev/null +++ b/tests/neg/unroll-duped.check @@ -0,0 +1,12 @@ +-- [E207] Declaration Error: tests/neg/unroll-duped.scala:11:45 -------------------------------------------------------- +11 | final def copy(s: String = this.s, @unroll y: Boolean = this.y): UnrolledCase = // error + | ^ + | Cannot unroll parameters of method copy of a case class: please annotate the class constructor instead +-- [E207] Declaration Error: tests/neg/unroll-duped.scala:18:12 -------------------------------------------------------- +18 | @unroll y: Boolean = true // error + | ^ + |Cannot unroll parameters of method apply of a case class companion object: please annotate the class constructor instead +-- [E207] Declaration Error: tests/neg/unroll-duped.scala:22:26 -------------------------------------------------------- +22 | def fromProduct(@unroll p: Product = EmptyTuple): UnrolledCase = { // error + | ^ + |Cannot unroll parameters of method fromProduct of a case class companion object: please annotate the class constructor instead diff --git a/tests/neg/unroll-duped.scala b/tests/neg/unroll-duped.scala new file mode 100644 index 000000000000..a578fc837628 --- /dev/null +++ b/tests/neg/unroll-duped.scala @@ -0,0 +1,27 @@ +//> using options -experimental + +import scala.annotation.unroll + +case class UnrolledCase( + s: String, + y: Boolean = true, +) { + def foo: String = s + y + + final def copy(s: String = this.s, @unroll y: Boolean = this.y): UnrolledCase = // error + new UnrolledCase(s, y) +} + +object UnrolledCase { + def apply( + s: String, + @unroll y: Boolean = true // error + ): UnrolledCase = + new UnrolledCase(s, y) + + def fromProduct(@unroll p: Product = EmptyTuple): UnrolledCase = { // error + val s = p.productElement(0).asInstanceOf[String] + val y = p.productElement(1).asInstanceOf[Boolean] + UnrolledCase(s, y) + } +} diff --git a/tests/neg/unroll-illegal.check b/tests/neg/unroll-illegal.check new file mode 100644 index 000000000000..96d7528ac338 --- /dev/null +++ b/tests/neg/unroll-illegal.check @@ -0,0 +1,36 @@ +-- [E207] Declaration Error: tests/neg/unroll-illegal.scala:6:6 -------------------------------------------------------- +6 |class UnrollClass // error + | ^ + | @unroll is only allowed on a method parameter +-- [E207] Declaration Error: tests/neg/unroll-illegal.scala:9:6 -------------------------------------------------------- +9 |trait UnrollTrait // error + | ^ + | @unroll is only allowed on a method parameter +-- [E207] Declaration Error: tests/neg/unroll-illegal.scala:12:7 ------------------------------------------------------- +12 |object UnrollObject // error + | ^ + | @unroll is only allowed on a method parameter +-- [E207] Declaration Error: tests/neg/unroll-illegal.scala:18:5 ------------------------------------------------------- +18 |enum UnrollEnum { case X } // error + | ^ + | @unroll is only allowed on a method parameter +-- [E207] Declaration Error: tests/neg/unroll-illegal.scala:21:25 ------------------------------------------------------ +21 | val annotExpr: Int = 23: @unroll // error + | ^ + | @unroll is only allowed on a method parameter +-- [E207] Declaration Error: tests/neg/unroll-illegal.scala:22:19 ------------------------------------------------------ +22 | type annotType = Int @unroll // error + | ^^^^^^^^^^^ + | @unroll is only allowed on a method parameter +-- [E207] Declaration Error: tests/neg/unroll-illegal.scala:25:6 ------------------------------------------------------- +25 | val unrollVal: Int = 23 // error + | ^ + | @unroll is only allowed on a method parameter +-- [E207] Declaration Error: tests/neg/unroll-illegal.scala:28:6 ------------------------------------------------------- +28 | def unrollDef: Int = 23 // error + | ^ + | @unroll is only allowed on a method parameter +-- [E207] Declaration Error: tests/neg/unroll-illegal.scala:15:5 ------------------------------------------------------- +15 |type UnrollType = Int // error + | ^ + | @unroll is only allowed on a method parameter diff --git a/tests/neg/unroll-illegal.scala b/tests/neg/unroll-illegal.scala new file mode 100644 index 000000000000..0b40a1c33c03 --- /dev/null +++ b/tests/neg/unroll-illegal.scala @@ -0,0 +1,29 @@ +//> using options -experimental + +import scala.annotation.unroll + +@unroll +class UnrollClass // error + +@unroll +trait UnrollTrait // error + +@unroll +object UnrollObject // error + +@unroll +type UnrollType = Int // error + +@unroll +enum UnrollEnum { case X } // error + +object wrap { + val annotExpr: Int = 23: @unroll // error + type annotType = Int @unroll // error + + @unroll + val unrollVal: Int = 23 // error + + @unroll + def unrollDef: Int = 23 // error +} diff --git a/tests/neg/unroll-illegal2.check b/tests/neg/unroll-illegal2.check new file mode 100644 index 000000000000..e2ead2ee3fe7 --- /dev/null +++ b/tests/neg/unroll-illegal2.check @@ -0,0 +1,4 @@ +-- [E200] Syntax Error: tests/neg/unroll-illegal2.scala:7:10 ----------------------------------------------------------- +7 | final def foo(s: String, @unroll y: Boolean) = s + y // error + | ^^^ + | The final modifier is not allowed on local definitions diff --git a/tests/neg/unroll-illegal2.scala b/tests/neg/unroll-illegal2.scala new file mode 100644 index 000000000000..ad7284506bbf --- /dev/null +++ b/tests/neg/unroll-illegal2.scala @@ -0,0 +1,9 @@ +//> using options -experimental + +import scala.annotation.unroll + +class wrap { + locally { + final def foo(s: String, @unroll y: Boolean) = s + y // error + } +} diff --git a/tests/neg/unroll-illegal3.check b/tests/neg/unroll-illegal3.check new file mode 100644 index 000000000000..6201a7d815cd --- /dev/null +++ b/tests/neg/unroll-illegal3.check @@ -0,0 +1,12 @@ +-- [E207] Declaration Error: tests/neg/unroll-illegal3.scala:7:31 ------------------------------------------------------ +7 | def foo(s: String, @unroll y: Boolean) = s + y // error + | ^ + | Cannot unroll parameters of method foo: it is not final +-- [E207] Declaration Error: tests/neg/unroll-illegal3.scala:12:29 ----------------------------------------------------- +12 | def foo(s: String, @unroll y: Boolean) = s + y // error + | ^ + | Cannot unroll parameters of method foo: it is not final +-- [E207] Declaration Error: tests/neg/unroll-illegal3.scala:16:29 ----------------------------------------------------- +16 | def foo(s: String, @unroll y: Boolean): String // error + | ^ + | Cannot unroll parameters of method foo: it must not be abstract diff --git a/tests/neg/unroll-illegal3.scala b/tests/neg/unroll-illegal3.scala new file mode 100644 index 000000000000..22a53bd04de6 --- /dev/null +++ b/tests/neg/unroll-illegal3.scala @@ -0,0 +1,17 @@ +//> using options -experimental + +import scala.annotation.unroll + +object wrap { + locally { + def foo(s: String, @unroll y: Boolean) = s + y // error + } +} + +class UnrolledCls { + def foo(s: String, @unroll y: Boolean) = s + y // error +} + +trait UnrolledTrait { + def foo(s: String, @unroll y: Boolean): String // error +} diff --git a/tests/neg/unroll-multipleParams.check b/tests/neg/unroll-multipleParams.check new file mode 100644 index 000000000000..f8f1bc22510c --- /dev/null +++ b/tests/neg/unroll-multipleParams.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/unroll-multipleParams.scala:6:12 ------------------------------------------------------------------- +6 | final def foo(x: Int, @unroll y: Int = 0)(@unroll z: Int = 0) = x + y + z // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Cannot have multiple parameter lists containing `@unroll` annotation diff --git a/tests/neg/unroll-multipleParams.scala b/tests/neg/unroll-multipleParams.scala new file mode 100644 index 000000000000..80371fec74c4 --- /dev/null +++ b/tests/neg/unroll-multipleParams.scala @@ -0,0 +1,7 @@ +//> using options -experimental + +import scala.annotation.unroll + +class Foo { + final def foo(x: Int, @unroll y: Int = 0)(@unroll z: Int = 0) = x + y + z // error +} diff --git a/tests/neg/unroll-no-default.check b/tests/neg/unroll-no-default.check new file mode 100644 index 000000000000..d16a94ce0527 --- /dev/null +++ b/tests/neg/unroll-no-default.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/unroll-no-default.scala:6:32 ----------------------------------------------------------------------- +6 | final def foo(x: Int, @unroll y: Int) = x + y // error + | ^^^^^^^^^^^^^^ + | Cannot unroll method foo in class Foo at line 6 because parameter y needs a default value diff --git a/tests/neg/unroll-no-default.scala b/tests/neg/unroll-no-default.scala new file mode 100644 index 000000000000..1058f34087e3 --- /dev/null +++ b/tests/neg/unroll-no-default.scala @@ -0,0 +1,7 @@ +//> using options -experimental -Xprint:unrollDefs + +import scala.annotation.unroll + +class Foo { + final def foo(x: Int, @unroll y: Int) = x + y // error +} diff --git a/tests/neg/unroll-traitConstructor.check b/tests/neg/unroll-traitConstructor.check new file mode 100644 index 000000000000..9bb74d2559bf --- /dev/null +++ b/tests/neg/unroll-traitConstructor.check @@ -0,0 +1,4 @@ +-- [E207] Declaration Error: tests/neg/unroll-traitConstructor.scala:5:32 ---------------------------------------------- +5 |trait Unroll(a: String, @unroll b: Boolean = true): // error + | ^ + | implementation restriction: Cannot unroll parameters of a trait constructor diff --git a/tests/neg/unroll-traitConstructor.scala b/tests/neg/unroll-traitConstructor.scala new file mode 100644 index 000000000000..3c48852d8303 --- /dev/null +++ b/tests/neg/unroll-traitConstructor.scala @@ -0,0 +1,8 @@ +//> using options -experimental + +import scala.annotation.unroll + +trait Unroll(a: String, @unroll b: Boolean = true): // error + def show: String = a + b + +class Bar(arg: String, bool: Boolean) extends Unroll(arg, bool) diff --git a/tests/new/test.scala b/tests/new/test.scala index 18644422ab06..dc1891f3525c 100644 --- a/tests/new/test.scala +++ b/tests/new/test.scala @@ -1,5 +1,3 @@ -import language.experimental.namedTuples - type Person = (name: String, age: Int) trait A: diff --git a/tests/patmat/i7186.scala b/tests/patmat/i7186.scala index d828cc78b070..f398eb39da77 100644 --- a/tests/patmat/i7186.scala +++ b/tests/patmat/i7186.scala @@ -92,7 +92,7 @@ object printMips { def apply(nodes: List[Assembler]): Unit = { var symbCount = 0L - val symbols = new scala.collection.mutable.AnyRefMap[Scoped,Long]() + val symbols = new scala.collection.mutable.HashMap[Scoped,Long]() print(mipsNode(nodes, " ")) diff --git a/tests/pos-custom-args/captures/boxmap-paper.scala b/tests/pos-custom-args/captures/boxmap-paper.scala index 9d5bb49af25d..20282d5813f9 100644 --- a/tests/pos-custom-args/captures/boxmap-paper.scala +++ b/tests/pos-custom-args/captures/boxmap-paper.scala @@ -1,7 +1,13 @@ -type Cell[+T] = [K] -> (T => K) -> K +type Cell_orig[+T] = [K] -> (T => K) -> K -def cell[T](x: T): Cell[T] = +def cell_orig[T](x: T): Cell_orig[T] = + [K] => (k: T => K) => k(x) + +class Cell[+T](val value: [K] -> (T => K) -> K): + def apply[K]: (T => K) -> K = value[K] + +def cell[T](x: T): Cell[T] = Cell: [K] => (k: T => K) => k(x) def get[T](c: Cell[T]): T = c[T](identity) @@ -22,6 +28,10 @@ def test(io: IO^) = val loggedOne: () ->{io} Int = () => { io.print("1"); 1 } + // We have a leakage of io because type arguments to alias type `Cell` are not boxed. + val c_orig: Cell[() ->{io} Int]^{io} + = cell[() ->{io} Int](loggedOne) + val c: Cell[() ->{io} Int] = cell[() ->{io} Int](loggedOne) diff --git a/tests/pos-custom-args/captures/cc-cast.scala b/tests/pos-custom-args/captures/cc-cast.scala new file mode 100644 index 000000000000..cfd96d63bee7 --- /dev/null +++ b/tests/pos-custom-args/captures/cc-cast.scala @@ -0,0 +1,12 @@ +import annotation.unchecked.uncheckedCaptures +import compiletime.uninitialized + +def foo(x: Int => Int) = () + + +object Test: + def test(x: Object) = + foo(x.asInstanceOf[Int => Int]) + + @uncheckedCaptures var x1: Object^ = uninitialized + @uncheckedCaptures var x2: Object^ = _ diff --git a/tests/pos-custom-args/captures/cc-poly-varargs.scala b/tests/pos-custom-args/captures/cc-poly-varargs.scala index 7f04ed987b28..8bd0dc89bc7a 100644 --- a/tests/pos-custom-args/captures/cc-poly-varargs.scala +++ b/tests/pos-custom-args/captures/cc-poly-varargs.scala @@ -1,9 +1,7 @@ -abstract class Source[+T, Cap^]: - def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{this, f} = ??? +abstract class Source[+T, Cap^] -// TODO: The extension version of `transformValuesWith` doesn't work currently. -// extension[T, Cap^](src: Source[T, Cap]^) -// def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{src, f} = ??? +extension[T, Cap^](src: Source[T, Cap]^) + def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{src, f} = ??? def race[T, Cap^](sources: Source[T, Cap]^{Cap^}*): Source[T, Cap]^{Cap^} = ??? @@ -12,8 +10,4 @@ def either[T1, T2, Cap^]( src2: Source[T2, Cap]^{Cap^}): Source[Either[T1, T2], Cap]^{Cap^} = val left = src1.transformValuesWith(Left(_)) val right = src2.transformValuesWith(Right(_)) - race[Either[T1, T2], Cap](left, right) - // Explicit type arguments are required here because the second argument - // is inferred as `CapSet^{Cap^}` instead of `Cap`. - // Although `CapSet^{Cap^}` subsumes `Cap` in terms of capture sets, - // `Cap` is not a subtype of `CapSet^{Cap^}` in terms of subtyping. + race(left, right) diff --git a/tests/pos/cc-ex-unpack.scala b/tests/pos-custom-args/captures/ex-fun-aliases.scala.disabled similarity index 79% rename from tests/pos/cc-ex-unpack.scala rename to tests/pos-custom-args/captures/ex-fun-aliases.scala.disabled index ae9b4ea5d805..ff86927b874c 100644 --- a/tests/pos/cc-ex-unpack.scala +++ b/tests/pos-custom-args/captures/ex-fun-aliases.scala.disabled @@ -11,8 +11,12 @@ type EX3 = () -> (c: Exists) -> () -> C^{c} type EX4 = () -> () -> (c: Exists) -> C^{c} +type FUN1 = (c: C^) -> (C^{c}, C^{c}) + def Test = def f = val ex1: EX1 = ??? val c1 = ex1 + val fun1: FUN1 = c => (c, c) + val fun2 = fun1 c1 diff --git a/tests/neg-custom-args/captures/i15749a.scala b/tests/pos-custom-args/captures/i15749a.scala similarity index 51% rename from tests/neg-custom-args/captures/i15749a.scala rename to tests/pos-custom-args/captures/i15749a.scala index d3c1fce13322..184f980d6d70 100644 --- a/tests/neg-custom-args/captures/i15749a.scala +++ b/tests/pos-custom-args/captures/i15749a.scala @@ -6,19 +6,17 @@ object u extends Unit type Top = Any^ -type Wrapper[+T] = [X] -> (op: T ->{cap} X) -> X +class Wrapper[+T](val value: [X] -> (op: T ->{cap} X) -> X) def test = - def wrapper[T](x: T): Wrapper[T] = + def wrapper[T](x: T): Wrapper[T] = Wrapper: [X] => (op: T ->{cap} X) => op(x) def strictMap[A <: Top, B <: Top](mx: Wrapper[A])(f: A ->{cap} B): Wrapper[B] = - mx((x: A) => wrapper(f(x))) + mx.value((x: A) => wrapper(f(x))) def force[A](thunk: Unit ->{cap} A): A = thunk(u) def forceWrapper[A](@use mx: Wrapper[Unit ->{cap} A]): Wrapper[A] = - // Γ ⊢ mx: Wrapper[□ {cap} Unit => A] - // `force` should be typed as ∀(□ {cap} Unit -> A) A, but it can not - strictMap[Unit ->{mx*} A, A](mx)(t => force[A](t)) // error // should work + strictMap[Unit ->{mx*} A, A](mx)(t => force[A](t)) // was error when Wrapper was an alias type diff --git a/tests/pos-custom-args/captures/i20237-explicit.scala b/tests/pos-custom-args/captures/i20237-explicit.scala new file mode 100644 index 000000000000..0999d4acd50e --- /dev/null +++ b/tests/pos-custom-args/captures/i20237-explicit.scala @@ -0,0 +1,15 @@ +import language.experimental.captureChecking + +class Cap extends caps.Capability: + def use[T](body: Cap => T) = body(this) + +class Box[T](body: Cap => T): + def open(cap: Cap) = cap.use(body) + +object Box: + def make[T](body: Cap => T)(cap: Cap): Box[T]^{body} = Box(x => body(x)) + +def main = + val givenCap: Cap = new Cap + val xx: Cap => Int = y => 1 + val box = Box.make[Int](xx)(givenCap).open \ No newline at end of file diff --git a/tests/pos/i20237.scala b/tests/pos-custom-args/captures/i20237.scala similarity index 100% rename from tests/pos/i20237.scala rename to tests/pos-custom-args/captures/i20237.scala diff --git a/tests/pos-custom-args/captures/i21347.scala b/tests/pos-custom-args/captures/i21347.scala index e74c15bff8c1..a965b7e4f26b 100644 --- a/tests/pos-custom-args/captures/i21347.scala +++ b/tests/pos-custom-args/captures/i21347.scala @@ -1,4 +1,4 @@ -//> using scala 3.6.0-RC1-bin-SNAPSHOT +//!> using scala 3.6.0-RC1-bin-SNAPSHOT import language.experimental.captureChecking @@ -8,4 +8,4 @@ def run[Cap^](f: Box[Cap]^{Cap^} => Unit): Box[Cap]^{Cap^} = ??? def main() = val b = run(_ => ()) - // val b = run[caps.CapSet](_ => ()) // this compiles \ No newline at end of file + // val b = run[caps.CapSet](_ => ()) // this compiles diff --git a/tests/pos-custom-args/captures/open-existential.scala b/tests/pos-custom-args/captures/open-existential.scala new file mode 100644 index 000000000000..8b43f27a051c --- /dev/null +++ b/tests/pos-custom-args/captures/open-existential.scala @@ -0,0 +1,15 @@ +trait Async extends caps.Capability + +class Future[+T](x: () => T)(using val a: Async) + +class Collector[T](val futs: Seq[Future[T]^]): + def add(fut: Future[T]^{futs*}) = ??? + +def main() = + given async: Async = ??? + val futs = (1 to 20).map(x => Future(() => x)) + val col = Collector(futs) + val col1: Collector[Int] { val futs: Seq[Future[Int]^{async}] } + = Collector(futs) + + diff --git a/tests/pos-custom-args/captures/setup/a_1.scala b/tests/pos-custom-args/captures/setup/a_1.scala new file mode 100644 index 000000000000..21afde8be3ea --- /dev/null +++ b/tests/pos-custom-args/captures/setup/a_1.scala @@ -0,0 +1,6 @@ +// a.scala +import language.experimental.captureChecking +import scala.caps.CapSet + +trait A: + def f[C^](x: AnyRef^{C^}): Unit diff --git a/tests/pos-custom-args/captures/setup/b_1.scala b/tests/pos-custom-args/captures/setup/b_1.scala new file mode 100644 index 000000000000..d5ba925970ba --- /dev/null +++ b/tests/pos-custom-args/captures/setup/b_1.scala @@ -0,0 +1,5 @@ +import language.experimental.captureChecking +import scala.caps.CapSet + +class B extends A: + def f[C^](x: AnyRef^{C^}): Unit = ??? diff --git a/tests/pos-custom-args/captures/setup/b_2.scala b/tests/pos-custom-args/captures/setup/b_2.scala new file mode 100644 index 000000000000..d5ba925970ba --- /dev/null +++ b/tests/pos-custom-args/captures/setup/b_2.scala @@ -0,0 +1,5 @@ +import language.experimental.captureChecking +import scala.caps.CapSet + +class B extends A: + def f[C^](x: AnyRef^{C^}): Unit = ??? diff --git a/tests/pos-custom-args/captures/untracked-captures.scala b/tests/pos-custom-args/captures/untracked-captures.scala index 7a090a5dd24f..f02dee607c01 100644 --- a/tests/pos-custom-args/captures/untracked-captures.scala +++ b/tests/pos-custom-args/captures/untracked-captures.scala @@ -1,4 +1,5 @@ -import caps.untrackedCaptures +import caps.unsafe.untrackedCaptures + class LL[+A] private (@untrackedCaptures lazyState: () => LL.State[A]^): private val res = lazyState() diff --git a/tests/pos-macros/i15413/Macro_1.scala b/tests/pos-macros/i15413/Macro_1.scala index f451742dff9e..56fd4f0f0887 100644 --- a/tests/pos-macros/i15413/Macro_1.scala +++ b/tests/pos-macros/i15413/Macro_1.scala @@ -1,4 +1,4 @@ -//> using options -experimental -Werror -WunstableInlineAccessors +//> using options -Werror -WunstableInlineAccessors import scala.quoted.* import scala.annotation.publicInBinary diff --git a/tests/pos-macros/i15413b/Macro_1.scala b/tests/pos-macros/i15413b/Macro_1.scala index df27b6267915..c1e9bab422f8 100644 --- a/tests/pos-macros/i15413b/Macro_1.scala +++ b/tests/pos-macros/i15413b/Macro_1.scala @@ -1,4 +1,4 @@ -//> using options -experimental -Werror -WunstableInlineAccessors +//> using options -Werror -WunstableInlineAccessors package bar diff --git a/tests/pos-macros/i18409.scala b/tests/pos-macros/i18409.scala index 800e192b81bb..d1806b1d4d83 100644 --- a/tests/pos-macros/i18409.scala +++ b/tests/pos-macros/i18409.scala @@ -1,4 +1,4 @@ -//> using options -Werror -Wunused:all +//> using options -Werror -Wunused:imports import scala.quoted.* diff --git a/tests/pos-macros/i19526b/Test.scala b/tests/pos-macros/i19526b/Test.scala index 96274091218f..1cc037298e01 100644 --- a/tests/pos-macros/i19526b/Test.scala +++ b/tests/pos-macros/i19526b/Test.scala @@ -1,5 +1,3 @@ -//> using options -experimental - package crash.test case class Stack private[crash] ( diff --git a/tests/pos-macros/i20349a/Macro_1.scala b/tests/pos-macros/i20349a/Macro_1.scala new file mode 100644 index 000000000000..41c5083b328c --- /dev/null +++ b/tests/pos-macros/i20349a/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.quoted.* + +object Macros { + def valuesImpl[A: Type](using Quotes): Expr[Any] = { + import quotes.reflect.* + val symbol = TypeRepr.of[A].typeSymbol.fieldMember("value") + Ref(symbol).asExprOf[Any] + } + + transparent inline def values[A]: Any = ${ valuesImpl[A] } +} diff --git a/tests/pos-macros/i20349a/Test_2.scala b/tests/pos-macros/i20349a/Test_2.scala new file mode 100644 index 000000000000..4569c501e055 --- /dev/null +++ b/tests/pos-macros/i20349a/Test_2.scala @@ -0,0 +1,16 @@ + +class Cls{ + object a { + object domain { + val value = "" + } + } + Macros.values[a.domain.type] +} + +object Test { + lazy val script = new Cls() + def main(args: Array[String]): Unit = + val _ = script.hashCode() + ??? +} diff --git a/tests/pos-macros/i20349b/Macro_1.scala b/tests/pos-macros/i20349b/Macro_1.scala new file mode 100644 index 000000000000..413274b4b516 --- /dev/null +++ b/tests/pos-macros/i20349b/Macro_1.scala @@ -0,0 +1,43 @@ +import scala.quoted.* + +object Macros { + + + def valuesImpl[A: Type](using quotes: Quotes): Expr[List[A]] = { + import quotes.*, quotes.reflect.* + + extension (sym: Symbol) + def isPublic: Boolean = !sym.isNoSymbol && + !(sym.flags.is(Flags.Private) || sym.flags.is(Flags.PrivateLocal) || sym.flags.is(Flags.Protected) || + sym.privateWithin.isDefined || sym.protectedWithin.isDefined) + + def isSealed[A: Type]: Boolean = + TypeRepr.of[A].typeSymbol.flags.is(Flags.Sealed) + + def extractSealedSubtypes[A: Type]: List[Type[?]] = { + def extractRecursively(sym: Symbol): List[Symbol] = + if sym.flags.is(Flags.Sealed) then sym.children.flatMap(extractRecursively) + else if sym.flags.is(Flags.Enum) then List(sym.typeRef.typeSymbol) + else if sym.flags.is(Flags.Module) then List(sym.typeRef.typeSymbol.moduleClass) + else List(sym) + + extractRecursively(TypeRepr.of[A].typeSymbol).distinct.map(typeSymbol => + typeSymbol.typeRef.asType + ) + } + + if isSealed[A] then { + val refs = extractSealedSubtypes[A].flatMap { tpe => + val sym = TypeRepr.of(using tpe).typeSymbol + val isCaseVal = sym.isPublic && sym.flags + .is(Flags.Case | Flags.Enum) && (sym.flags.is(Flags.JavaStatic) || sym.flags.is(Flags.StableRealizable)) + + if (isCaseVal) then List(Ref(sym).asExprOf[A]) + else Nil + } + Expr.ofList(refs) + } else '{ Nil } + } + + inline def values[A]: List[A] = ${ valuesImpl[A] } +} diff --git a/tests/pos-macros/i20349b/Test_2.scala b/tests/pos-macros/i20349b/Test_2.scala new file mode 100644 index 000000000000..a392a636dc44 --- /dev/null +++ b/tests/pos-macros/i20349b/Test_2.scala @@ -0,0 +1,14 @@ +class Test { + object domain { + enum PaymentMethod: + case PayPal(email: String) + case Card(digits: Long, name: String) + case Cash + } + println(Macros.values[domain.PaymentMethod]) +} +object Test { + lazy val script = new Test() + def main(args: Array[String]): Unit = + val _ = script.hashCode() +} diff --git a/tests/pos-macros/i20449/Macro.scala b/tests/pos-macros/i20449/Macro.scala new file mode 100644 index 000000000000..56c9a625e894 --- /dev/null +++ b/tests/pos-macros/i20449/Macro.scala @@ -0,0 +1,3 @@ +import scala.quoted.* +transparent inline def getTypeInfo[T]() = ${ getTypeInfoImpl[T] } +def getTypeInfoImpl[T: Type](using ctx: Quotes): Expr[Unit] = '{ () } diff --git a/tests/pos-macros/i20449/Main.scala b/tests/pos-macros/i20449/Main.scala new file mode 100644 index 000000000000..c6f5c0b05b69 --- /dev/null +++ b/tests/pos-macros/i20449/Main.scala @@ -0,0 +1,6 @@ + +class Wrapper1[A] +val a = { + getTypeInfo[Any]() + val wrapper2 = Wrapper1[Any]() +} diff --git a/tests/pos-macros/i21721-a/Macro.scala b/tests/pos-macros/i21721-a/Macro.scala new file mode 100644 index 000000000000..dfdbff6a659b --- /dev/null +++ b/tests/pos-macros/i21721-a/Macro.scala @@ -0,0 +1,12 @@ +import quoted.* + +object Macro: + inline def impl(inline expr: Any): Any = + ${implImpl('expr)} + + def implImpl(expr: Expr[Any])(using q: Quotes): Expr[Any] = + import q.reflect.* + expr.asTerm.asInstanceOf[Inlined].body match + // this should not fail with a MatchError + case TypeBlock(_, _) => '{ "TypeBlock" } + case _ => '{ "Nothing" } diff --git a/tests/pos-macros/i21721-a/Test.scala b/tests/pos-macros/i21721-a/Test.scala new file mode 100644 index 000000000000..ebe91bae9ef4 --- /dev/null +++ b/tests/pos-macros/i21721-a/Test.scala @@ -0,0 +1,5 @@ +object Test: + // give a Block(...) to the macro + Macro.impl: + val a = 3 + a diff --git a/tests/pos-macros/i21721-b/Macro_1.scala b/tests/pos-macros/i21721-b/Macro_1.scala new file mode 100644 index 000000000000..aad7d6232c8f --- /dev/null +++ b/tests/pos-macros/i21721-b/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +inline def test(): Any = ${ testImpl } + +def testImpl(using Quotes): Expr[Any] = + import quotes.reflect._ + TypeBlock(Nil, TypeTree.of[Int]) match + case Block(_, res) => + res.asExpr // unexpected case - would crash here, as res of TypeBlock is not a term + case _ => + '{()} // expected case diff --git a/tests/pos-macros/i21721-b/Test_2.scala b/tests/pos-macros/i21721-b/Test_2.scala new file mode 100644 index 000000000000..9b75f0ca5777 --- /dev/null +++ b/tests/pos-macros/i21721-b/Test_2.scala @@ -0,0 +1 @@ +@main def Test() = test() diff --git a/tests/pos-macros/i22424/Macro_1.scala b/tests/pos-macros/i22424/Macro_1.scala new file mode 100644 index 000000000000..634cfb0055c7 --- /dev/null +++ b/tests/pos-macros/i22424/Macro_1.scala @@ -0,0 +1,25 @@ + +import scala.quoted.* + +object MockMaker: + inline def inlineMock[T]: Unit = ${instance[T]} + transparent inline def transparentInlineMock[T]: Unit = ${instance[T]} + + def instance[T: Type](using quotes: Quotes): Expr[Unit] = + import quotes.reflect._ + val tpe = TypeRepr.of[T] + val symbol = tpe.typeSymbol.methodMember("innerTraitInOptions").head + tpe.memberType(symbol) match + case mt @ MethodType(_, args, _) => + assert(args.head.typeSymbol != TypeRepr.of[Nothing].typeSymbol, "argument is incorrectly approximated") + val shownType = mt.show + val expectedType = "(x: m.Embedded#ATrait[scala.Predef.String, scala.Int])m.Embedded#ATrait[scala.Predef.String, scala.Int]" + assert(shownType == expectedType, s"Incorrect type shown. Obtained: $shownType, Expected: $expectedType") + '{()} + +trait PolymorphicTrait { + trait Embedded { + trait ATrait[A, B] + def innerTraitInOptions(x: ATrait[String, Int]): ATrait[String, Int] + } +} diff --git a/tests/pos-macros/i22424/Test_2.scala b/tests/pos-macros/i22424/Test_2.scala new file mode 100644 index 000000000000..0a231d820381 --- /dev/null +++ b/tests/pos-macros/i22424/Test_2.scala @@ -0,0 +1,4 @@ +@main def Test = + val m = new PolymorphicTrait {} + MockMaker.inlineMock[m.Embedded] + MockMaker.transparentInlineMock[m.Embedded] diff --git a/tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala b/tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala new file mode 100644 index 000000000000..569d8e0c25be --- /dev/null +++ b/tests/pos-macros/newClassExtendsWithSymbolInParent/Macro_1.scala @@ -0,0 +1,40 @@ +//> using options -experimental + +import scala.quoted.* + +transparent inline def makeClass(inline name: String): Foo[_] = ${ makeClassExpr('name) } +private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Foo[_]] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + + // using asType on the passed Symbol leads to cyclic reference errors + def parents(cls: Symbol) = + List(AppliedType(TypeRepr.typeConstructorOf(Class.forName("Foo")), List(TypeIdent(cls).tpe))) + def decls(cls: Symbol): List[Symbol] = Nil + + val cls = Symbol.newClass(Symbol.spliceOwner, name, parents, decls, selfType = None, Flags.EmptyFlags, Symbol.noSymbol, conParams = Nil) + + val parentsWithSym = + cls.typeRef.asType match + case '[t] => + List(Apply(TypeApply(Select(New(TypeTree.of[Foo[t]]), TypeRepr.of[Foo[t]].typeSymbol.primaryConstructor), List(TypeTree.of[t])), List())) + val clsDef = ClassDef(cls, parentsWithSym, body = Nil) + + val newCls = cls.typeRef.asType match + case '[t] => + Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Foo[t]]) + + cls.typeRef.asType match + case '[t] => + Block(List(clsDef), newCls).asExprOf[Foo[t]] + + // '{ + // class Name() extends Foo[Name.type]() + // new Name() + // } +} + +class Foo[X]() { self: X => + def getSelf: X = self +} diff --git a/tests/pos-macros/newClassExtendsWithSymbolInParent/Test_2.scala b/tests/pos-macros/newClassExtendsWithSymbolInParent/Test_2.scala new file mode 100644 index 000000000000..bb48bfadd96b --- /dev/null +++ b/tests/pos-macros/newClassExtendsWithSymbolInParent/Test_2.scala @@ -0,0 +1,6 @@ +//> using options -experimental + +@main def Test: Unit = { + val foo = makeClass("Bar") + foo.getSelf +} diff --git a/tests/pos/22219.scala b/tests/pos/22219.scala new file mode 100644 index 000000000000..fd455d5dc902 --- /dev/null +++ b/tests/pos/22219.scala @@ -0,0 +1,5 @@ +type MonthNumber = 1 | 2 + +def main = + val x = 1: MonthNumber + val y: MonthNumber = x diff --git a/tests/pos/22219b.scala b/tests/pos/22219b.scala new file mode 100644 index 000000000000..8046ef802845 --- /dev/null +++ b/tests/pos/22219b.scala @@ -0,0 +1,20 @@ +type MonthNumber = + 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 + +def main = + List[(String, MonthNumber)]( + "January" -> 1, + "February" -> 2, + "March" -> 3, + "April" -> 4, + "May" -> 5, + "June"-> 6, + "July" -> 7, + "August" -> 8, + "September" -> 9, + "October" -> 10, + "November" -> 11, + "December" -> 12 + ).foreach { (name, number) => + summon[number.type <:< MonthNumber] + } diff --git a/tests/pos/22359a.scala b/tests/pos/22359a.scala new file mode 100644 index 000000000000..a3b9ef63257a --- /dev/null +++ b/tests/pos/22359a.scala @@ -0,0 +1,15 @@ +opaque type NT[N <: Tuple, V <: Tuple] = V +opaque type System = NT[Tuple1["wires"], Tuple1[Any]] + +extension [N <: Tuple, V <: Tuple] (x: NT[N, V]) { + inline def apply(n: Int): Any = + x.productElement(n) +} + +extension (system: System) { + inline def foo = + system.apply(0) +} + +val simulation: System = ??? +val _ = simulation.foo diff --git a/tests/pos/22359b.scala b/tests/pos/22359b.scala new file mode 100644 index 000000000000..f6b7cbb462c1 --- /dev/null +++ b/tests/pos/22359b.scala @@ -0,0 +1,17 @@ +object Obj2: + opaque type NT[N <: Tuple, V <: Tuple] = V + + extension [N <: Tuple, V <: Tuple] (x: NT[N, V]) { + inline def apply(n: Int): Any = + x.productElement(n) + } + +object Obj: + opaque type System = Obj2.NT[Tuple1["wires"], Tuple1[Any]] + + extension (system: System) { + inline def foo = system.apply(0) + } +import Obj._ +val simulation: System = ??? +val _ = simulation.foo diff --git a/tests/pos/annot-main-22364.scala b/tests/pos/annot-main-22364.scala new file mode 100644 index 000000000000..205589b525bc --- /dev/null +++ b/tests/pos/annot-main-22364.scala @@ -0,0 +1,5 @@ +def id[T](x: T): T = x + +class ann(x: Int) extends annotation.Annotation + +@ann(id(22)) @main def blop = () diff --git a/tests/pos/annot-main-22364b.scala b/tests/pos/annot-main-22364b.scala new file mode 100644 index 000000000000..c4e3067d7325 --- /dev/null +++ b/tests/pos/annot-main-22364b.scala @@ -0,0 +1,6 @@ +import util.chaining.* + +class ann(x: Int = 1, y: Int) extends annotation.Annotation + +@ann(y = 22.tap(println)) @main def blop = () + diff --git a/tests/pos/annot-main-22364c.scala b/tests/pos/annot-main-22364c.scala new file mode 100644 index 000000000000..53a6abe1a56b --- /dev/null +++ b/tests/pos/annot-main-22364c.scala @@ -0,0 +1,10 @@ +package p + +object P1: + class ann(x: Int) extends annotation.Annotation + +object P2: + def id[T](x: T): T = x + +object P3: + @P1.ann(P2.id(22)) @main def blop = () diff --git a/tests/pos/better-fors-given.scala b/tests/pos/better-fors-given.scala new file mode 100644 index 000000000000..6f70c5549469 --- /dev/null +++ b/tests/pos/better-fors-given.scala @@ -0,0 +1,70 @@ +//> using options -preview + +@main def Test: Unit = + for + x <- Option(23 -> "abc") + (a @ given Int, b @ given String) = x + _ <- Option(1) + yield + assert(summon[Int] == 23) + + for + x <- Option((1.3, 23 -> "abc")) + (_, (a @ given Int, b @ given String)) = x + _ <- Option(1) + yield + assert(summon[Int] == 23) + + for + x <- Option(Some(23 -> "abc")) + Some(a @ given Int, b @ given String) = x + _ <- Option(1) + yield + assert(summon[Int] == 23) + + for + x <- Option(Some(23)) + Some(a @ given Int) = x + _ <- Option(1) + yield + assert(summon[Int] == 23) + + for + x <- Option(23) + a @ given Int = x + yield + assert(summon[Int] == 23) + + for + x <- Option(23) + _ @ given Int = x + yield + assert(summon[Int] == 23) + + for + x <- Option(23) + given Int = x + yield + assert(summon[Int] == 23) + + for + x <- Option(23) + given Int = x + _ <- Option(1) + yield + assert(summon[Int] == 23) + + for + a @ given Int <- Option(23) + yield + assert(summon[Int] == 23) + + for + _ @ given Int <- Option(23) + yield + assert(summon[Int] == 23) + + for + given Int <- Option(23) + yield + assert(summon[Int] == 23) \ No newline at end of file diff --git a/tests/pos/better-fors-i21804.scala b/tests/pos/better-fors-i21804.scala new file mode 100644 index 000000000000..85ffb87b0296 --- /dev/null +++ b/tests/pos/better-fors-i21804.scala @@ -0,0 +1,14 @@ +//> using options -preview +// import scala.language.experimental.betterFors + +case class Container[A](val value: A) { + def map[B](f: A => B): Container[B] = Container(f(value)) +} + +sealed trait Animal +case class Dog() extends Animal + +def opOnDog(dog: Container[Dog]): Container[Animal] = + for + v <- dog + yield v diff --git a/tests/pos/boxmap-paper.scala b/tests/pos/boxmap-paper.scala deleted file mode 100644 index aa983114ed8a..000000000000 --- a/tests/pos/boxmap-paper.scala +++ /dev/null @@ -1,38 +0,0 @@ -import language.experimental.captureChecking - -type Cell[+T] = [K] -> (T => K) -> K - -def cell[T](x: T): Cell[T] = - [K] => (k: T => K) => k(x) - -def get[T](c: Cell[T]): T = c[T](identity) - -def map[A, B](c: Cell[A])(f: A => B): Cell[B] - = c[Cell[B]]((x: A) => cell(f(x))) - -def pureMap[A, B](c: Cell[A])(f: A -> B): Cell[B] - = c[Cell[B]]((x: A) => cell(f(x))) - -def lazyMap[A, B](c: Cell[A])(f: A => B): () ->{f} Cell[B] - = () => c[Cell[B]]((x: A) => cell(f(x))) - -trait IO: - def print(s: String): Unit - -def test(io: IO^) = - - val loggedOne: () ->{io} Int = () => { io.print("1"); 1 } - - val c: Cell[() ->{io} Int] - = cell[() ->{io} Int](loggedOne) - - val g = (f: () ->{io} Int) => - val x = f(); io.print(" + ") - val y = f(); io.print(s" = ${x + y}") - - val r = lazyMap[() ->{io} Int, Unit](c)(f => g(f)) - val r2 = lazyMap[() ->{io} Int, Unit](c)(g) - val r3 = lazyMap(c)(g) - val _ = r() - val _ = r2() - val _ = r3() diff --git a/tests/pos/caps-universal.scala b/tests/pos/caps-universal.scala index 014955caaa87..3451866ed8f3 100644 --- a/tests/pos/caps-universal.scala +++ b/tests/pos/caps-universal.scala @@ -3,7 +3,7 @@ import annotation.retains val id: Int -> Int = (x: Int) => x val foo: Int => Int = id -val bar: (Int -> Int) @retains(caps.*) = foo +val bar: (Int -> Int) @retains(caps.cap) = foo diff --git a/tests/pos/deprecated-no-kind-polymorphism-anykind.scala b/tests/pos/deprecated-no-kind-polymorphism-anykind.scala new file mode 100644 index 000000000000..1697acaca6f9 --- /dev/null +++ b/tests/pos/deprecated-no-kind-polymorphism-anykind.scala @@ -0,0 +1,5 @@ +// This test is kept as a placeholder for historical reasons. +// The -Yno-kind-polymorphism flag is now deprecated and has no effect. +// Kind polymorphism with AnyKind is always enabled. + +trait Foo[T <: AnyKind] // This now works as AnyKind is always defined diff --git a/tests/pos/enum-refinement.scala b/tests/pos/enum-refinement.scala new file mode 100644 index 000000000000..e357125489cd --- /dev/null +++ b/tests/pos/enum-refinement.scala @@ -0,0 +1,12 @@ +enum Enum: + case EC(val x: Int) + +val a: Enum.EC { val x: 1 } = Enum.EC(1).asInstanceOf[Enum.EC { val x: 1 }] + +import scala.language.experimental.modularity + +enum EnumT: + case EC(tracked val x: Int) + +val b: EnumT.EC { val x: 1 } = EnumT.EC(1) + diff --git a/tests/pos/fieldsOf.scala b/tests/pos/fieldsOf.scala index 2594dae2cbf7..08f20a1f7e8e 100644 --- a/tests/pos/fieldsOf.scala +++ b/tests/pos/fieldsOf.scala @@ -1,5 +1,3 @@ -import language.experimental.namedTuples - case class Person(name: String, age: Int) type PF = NamedTuple.From[Person] diff --git a/tests/pos/i11729.scala b/tests/pos/i11729.scala index e97b285ac6a2..79d3174dc2e9 100644 --- a/tests/pos/i11729.scala +++ b/tests/pos/i11729.scala @@ -6,7 +6,7 @@ type Return[X] = X match object Return: def apply[A](a:A):Return[A] = a match - case a: List[t] => a + case a: List[?] => a case a: Any => List(a) object Test1: @@ -18,7 +18,7 @@ type Boxed[X] = X match case Any => Box[X] def box[X](x: X): Boxed[X] = x match - case b: Box[t] => b + case b: Box[?] => b case x: Any => Box(x) case class Box[A](a:A): diff --git a/tests/pos/i17243.scala b/tests/pos/i17243.scala new file mode 100644 index 000000000000..3d42495b26b0 --- /dev/null +++ b/tests/pos/i17243.scala @@ -0,0 +1,17 @@ +object Opaque: + opaque type A = Int + + val va: A = 1 + + inline def a(x: A) = + x + 1 + +object Opaque2: + opaque type B = Opaque.A + + val vb: B = Opaque.va + + inline def b(x: B) = Opaque.a(x) + +@main def Test() = + print(Opaque2.b(Opaque2.vb)) diff --git a/tests/pos/i17631.scala b/tests/pos/i17631.scala index 7b8a064493df..ddcb71354968 100644 --- a/tests/pos/i17631.scala +++ b/tests/pos/i17631.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings -Wunused:all -deprecation -feature +//> using options -Werror -Wunused:all -deprecation -feature object foo { type Bar @@ -32,3 +32,23 @@ object Main { (bad1, bad2) } } + +def `i18388`: Unit = + def func(pred: [A] => A => Boolean): Unit = + val _ = pred + () + val _ = func + +trait L[T]: + type E + +def `i19748` = + type Warn1 = [T] => (l: L[T]) => T => l.E + type Warn2 = [T] => L[T] => T + type Warn3 = [T] => T => T + def use(x: (Warn1, Warn2, Warn3)) = x + use + +type NoWarning1 = [T] => (l: L[T]) => T => l.E +type NoWarning2 = [T] => L[T] => T +type NoWarning3 = [T] => T => T diff --git a/tests/pos/i18366.scala b/tests/pos/i18366.scala deleted file mode 100644 index 698510ad13a2..000000000000 --- a/tests/pos/i18366.scala +++ /dev/null @@ -1,10 +0,0 @@ -//> using options -Xfatal-warnings -Wunused:all - -trait Builder { - def foo(): Unit -} - -def repro = - val builder: Builder = ??? - import builder.{foo => bar} - bar() \ No newline at end of file diff --git a/tests/pos/i19642/Valid.java b/tests/pos/i19642/Valid.java new file mode 100644 index 000000000000..17e0e1173726 --- /dev/null +++ b/tests/pos/i19642/Valid.java @@ -0,0 +1,17 @@ +package lib; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE }) +@Retention(RUNTIME) +@Documented +public @interface Valid {} diff --git a/tests/pos/i19642/i19642.java b/tests/pos/i19642/i19642.java new file mode 100644 index 000000000000..c7d8ba9f0e72 --- /dev/null +++ b/tests/pos/i19642/i19642.java @@ -0,0 +1,9 @@ +package app; + +import java.util.Optional; +import lib.*; + +public class i19642 { + private String @lib.Valid [] flatArray; + private String @lib.Valid [] @lib.Valid [] nestedArray; +} diff --git a/tests/pos/i20377.scala b/tests/pos/i20377.scala index 661fa7adfca9..a555e01867ab 100644 --- a/tests/pos/i20377.scala +++ b/tests/pos/i20377.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples import NamedTuple.{NamedTuple, AnyNamedTuple} // Repros for bugs or questions diff --git a/tests/pos/i20901/Foo.tastycheck b/tests/pos/i20901/Foo.tastycheck index 583595a7eb0a..82e95946f96c 100644 --- a/tests/pos/i20901/Foo.tastycheck +++ b/tests/pos/i20901/Foo.tastycheck @@ -92,7 +92,7 @@ Trees (98 bytes, starting from ): 96: STRINGconst 32 [] 98: -Positions (73 bytes, starting from ): +Positions (75 bytes, starting from ): lines: 7 line sizes: 38, 0, 23, 0, 10, 41, 0 diff --git a/tests/pos/i21300.scala b/tests/pos/i21300.scala index 22859482ef98..e7c7965b0e9a 100644 --- a/tests/pos/i21300.scala +++ b/tests/pos/i21300.scala @@ -1,17 +1,15 @@ -import scala.language.experimental.namedTuples - class Test[S <: String & Singleton](name: S): type NT = NamedTuple.NamedTuple[(S, "foo"), (Int, Long)] def nt: NT = ??? type Name = S - + type NT2 = NamedTuple.NamedTuple[(Name, "foo"), (Int, Long)] def nt2: NT2 = ??? def test = val foo = new Test("bar") - + foo.nt.bar foo.nt2.bar diff --git a/tests/pos/i21413.scala b/tests/pos/i21413.scala index 72b5c6d59d8d..d2dc52e34630 100644 --- a/tests/pos/i21413.scala +++ b/tests/pos/i21413.scala @@ -1,4 +1,2 @@ -import scala.language.experimental.namedTuples - val x = (aaa = 1).aaa //val y = x.aaa \ No newline at end of file diff --git a/tests/pos/i21433.scala b/tests/pos/i21433.scala new file mode 100644 index 000000000000..0efc4ac197ae --- /dev/null +++ b/tests/pos/i21433.scala @@ -0,0 +1,6 @@ +trait A[T]: + type R = T ?=> Unit + def f: R = () + +class B extends A[Int]: + override def f: R = () diff --git a/tests/pos/i21931.scala b/tests/pos/i21931.scala new file mode 100644 index 000000000000..d4deb93f4c2e --- /dev/null +++ b/tests/pos/i21931.scala @@ -0,0 +1,18 @@ +object Test { + def f() = { + val NotFound: Char = 'a' + class crashing() { + class issue() { + NotFound + } + class Module() { + val obligatory = { + def anonIssue = { + issue() + } + anonIssue + } + } + } + } +} diff --git a/tests/pos/i21981.alt.scala b/tests/pos/i21981.alt.scala new file mode 100644 index 000000000000..0662575757a8 --- /dev/null +++ b/tests/pos/i21981.alt.scala @@ -0,0 +1,24 @@ +trait Ops[F[_], A]: + def map0[B](f0: A => B): F[B] = ??? + +trait Functor1[G[_]] + +trait Functor2[H[_]] + +trait Ref[I[_], +E] + +class Test: + given [J[_]](using J: Functor1[J]): Functor2[J] with + extension [K1, K2](jk: J[(K1, K2)]) + def map2[L](f2: (K1, K2) => L): J[L] = ??? + + def t1[ + M[_[t]], + N[_], + ](using N: Functor1[N]): Unit = + + val x3: Ops[N, M[[t] =>> Ref[N, t]]] = ??? + + val x2: N[(M[N], M[[t] =>> Ref[N, t]])] = x3 + .map0 { refs => (???, refs) } + .map2 { case (not, refs) => (???, refs) } diff --git a/tests/pos/i21981.contrak.scala b/tests/pos/i21981.contrak.scala new file mode 100644 index 000000000000..6ce64e6d3ddf --- /dev/null +++ b/tests/pos/i21981.contrak.scala @@ -0,0 +1,27 @@ +case class Inv[T](x: T) +class Contra[-ContraParam](x: ContraParam) + +trait Ops[F[_], A]: + def map0[B](f0: A => Contra[B]): F[B] = ??? + +trait Functor1[G[_]] + +trait Functor2[H[_]] + +trait Ref[I[_], +E] + +class Test: + given [J[_]](using J: Functor1[J]): Functor2[J] with + extension [K](jk: J[Contra[K]]) + def map2[L](f2: K => L): J[L] = ??? + + def t1[ + M[_[t]], + N[_], + ](using N: Functor1[N]): Unit = + + val x3: Ops[N, M[[t] =>> Ref[N, t]]] = ??? + + val x2: N[(M[N], M[[t] =>> Ref[N, t]])] = x3 + .map0 { refs => Contra[Contra[(Nothing, M[[t] =>> Ref[N, t]])]](???) } + .map2 { case (not, refs) => (???, refs) } diff --git a/tests/pos/i21981.orig.scala b/tests/pos/i21981.orig.scala new file mode 100644 index 000000000000..c02eb4848649 --- /dev/null +++ b/tests/pos/i21981.orig.scala @@ -0,0 +1,29 @@ +object internal: + trait Functor[F[_]] { + extension [T](ft: F[T]) def map[T1](f: T => T1): F[T1] + } + +object cats: + trait Functor[F[_]] + object Functor: + trait Ops[F[_], A]: + def map[B](f: A => B): F[B] = ??? + def toAllFunctorOps[F[_], A](target: F[A])(using Functor[F]): Ops[F, A] = ??? + +given [F[_]](using cf: cats.Functor[F]): internal.Functor[F] with { + extension [T](ft: F[T]) def map[T1](f: T => T1): F[T1] = ??? +} + +trait Ref[F[_], +T] +class MemoizingEvaluator[Input[_[_]], Output[_[_]], F[_]: cats.Functor] { + type OptionRef[T] = Ref[F, Option[T]] + + def sequence[CaseClass[_[_]], G[_], H[_]](instance: CaseClass[[t] =>> G[H[t]]]): G[CaseClass[H]] = ??? + def collectValues(input: Input[F]): F[(Input[F], Input[OptionRef])] = { + val refsF: Input[[t] =>> F[OptionRef[t]]] = ??? + for { + refs <- cats.Functor.toAllFunctorOps(sequence[Input, F, OptionRef](refsF)) + updating = ??? + } yield (updating, refs) + } +} diff --git a/tests/pos/i21981.scala b/tests/pos/i21981.scala new file mode 100644 index 000000000000..62962c7f1c56 --- /dev/null +++ b/tests/pos/i21981.scala @@ -0,0 +1,24 @@ +trait Ops[F[_], A]: + def map0[B](f0: A => B): F[B] = ??? + +trait Functor1[G[_]] + +trait Functor2[H[_]] + +trait Ref[I[_], +E] + +class Test: + given [J[_]](using J: Functor1[J]): Functor2[J] with + extension [K](jk: J[K]) + def map2[L](f2: K => L): J[L] = ??? + + def t1[ + M[_[t]], + N[_], + ](using N: Functor1[N]): Unit = + + val x3: Ops[N, M[[t] =>> Ref[N, t]]] = ??? + + val x2: N[(M[N], M[[t] =>> Ref[N, t]])] = x3 + .map0 { refs => (???, refs) } + .map2 { case (not, refs) => (???, refs) } diff --git a/tests/pos/i22018.scala b/tests/pos/i22018.scala index 14f4733732be..05e5c8279144 100644 --- a/tests/pos/i22018.scala +++ b/tests/pos/i22018.scala @@ -1,5 +1,3 @@ -import scala.language.experimental.namedTuples - class SelectableNT[A <: NamedTuple.AnyNamedTuple](val nt: A) extends Selectable: type Fields = A def selectDynamic(x: String) = ??? diff --git a/tests/pos/i22036.scala b/tests/pos/i22036.scala new file mode 100644 index 000000000000..065970e4db9b --- /dev/null +++ b/tests/pos/i22036.scala @@ -0,0 +1,23 @@ +type Foo[T] = T +val x: NamedTuple.From[Tuple.Map[(Int, Int), Foo]] = ??? +val res = x._1 + +type Z = NamedTuple.From[(Foo[Int], Foo[Int])] +val x2: Z = ??? +val res2 = x2._1 + +val x3: Foo[NamedTuple.From[Tuple.Map[(Int, Int), Foo]]] = ??? +val res3 = x3._1 + +def foo[T <: (Int, String)](tup: T): Int = + tup._1 + +def union[T](tup: (Int, String) +|(Int, String) +): Int = + tup._1 + +def intersect[T](tup: (Int, String) +& T +): Int = + tup._1 \ No newline at end of file diff --git a/tests/pos/i22192.scala b/tests/pos/i22192.scala index 4214a56f4b38..e97b04fb5763 100644 --- a/tests/pos/i22192.scala +++ b/tests/pos/i22192.scala @@ -1,5 +1,3 @@ -import scala.language.experimental.namedTuples - case class City(name: String, population: Int) def getCityInfo(city: City) = city match diff --git a/tests/pos/i22193.scala b/tests/pos/i22193.scala new file mode 100644 index 000000000000..2ba7f920fbd7 --- /dev/null +++ b/tests/pos/i22193.scala @@ -0,0 +1,141 @@ + +def fn2(arg: String, arg2: String)(f: String => Unit): Unit = f(arg) + +def fn3(arg: String, arg2: String)(f: => Unit): Unit = f + +def test() = + + fn2(arg = "blue sleeps faster than tuesday", arg2 = "the quick brown fox jumped over the lazy dog"): env => + val x = env + println(x) + + // doesn't compile + fn2( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): env => + val x = env + println(x) + + fn2( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): env => + val x = env + println(x) + + fn2( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): env => + val x = env + println(x) + + // does compile + fn2( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + env => + val x = env + println(x) + + // does compile + fn2( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog" + ): env => + val x = env + println(x) + + fn2( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog" + ): env => + val x = env + println(x) + + fn3( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + val x = "Hello" + println(x) + + fn3( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + val x = "Hello" + println(x) + + fn3( // arg at 3, body at 3 + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + val x = "Hello" + println(x) + + fn3( // arg at 3, body at 1: not sure if sig indent of 1 is allowed, saw some comments from odersky + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + val x = "Hello" + println(x) + + fn3( // arg at 3, body at 2: even if sig indent of 1 is not allowed, body is at fn3+2, not arg2-1 + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + val x = "Hello" + println(x) + + fn3( // arg at 3, body at 4 + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + val x = "Hello" + println(x) + +// don't turn innocent empty cases into functions +def regress(x: Int) = + x match + case 42 => + case _ => + +// previously lookahead calculated indent width at the colon +def k(xs: List[Int]) = + xs.foldLeft( + 0) + : (acc, x) => + acc + x + +def `test kit`(xs: List[Int]): Unit = + def addOne(i: Int): Int = i + 1 + def isPositive(i: Int): Boolean = i > 0 + // doesn't compile but would be nice + // first body is indented "twice", or, rather, first outdent establishes an intermediate indentation level + xs.map: x => + x + 1 + .filter: x => + x > 0 + xs.map: + addOne + .filter: + isPositive + + // does compile + xs + .map: x => + x + 1 + .filter: x => + x > 0 + + // does compile but doesn't look good, at least, to some people + xs.map: x => + x + 1 + .filter: x => + x > 0 + +def `tested kit`(xs: List[Int]): Unit = + { + def addOne(i: Int): Int = i.+(1) + def isPositive(i: Int): Boolean = i.>(0) + xs.map[Int]((x: Int) => x.+(1)).filter((x: Int) => x.>(0)) + xs.map[Int]((i: Int) => addOne(i)).filter((i: Int) => isPositive(i)) + xs.map[Int]((x: Int) => x.+(1)).filter((x: Int) => x.>(0)) + { + xs.map[Int]((x: Int) => x.+(1)).filter((x: Int) => x.>(0)) + () + } + } diff --git a/tests/pos/i22257.fixed.scala b/tests/pos/i22257.fixed.scala new file mode 100644 index 000000000000..d0fa1a0e81cb --- /dev/null +++ b/tests/pos/i22257.fixed.scala @@ -0,0 +1,52 @@ + +object Scaffold { + + trait Arrow + object Arrow { + trait Outbound extends Arrow + } + + trait NodeKOrGraphK {} + + trait NodeK extends NodeKOrGraphK { + + type FBound <: Induction + + protected def getInduction: Seq[FBound] + } + + trait Induction { + def arrow: Arrow + def node: NodeK + } + + object Induction { + + trait FP[+N <: NodeK] extends Induction { // short for "fixed point" + def node: N + } + } + + trait GraphK extends NodeKOrGraphK { + + type Batch[+T] <: Iterable[T] + + type _Node <: NodeK + + def entries: Batch[_Node] + } + + trait Topology { + + type Node = NodeK { type FBound <: Topology.this.FBound } + trait Node_ extends NodeK { + type FBound = Topology.this.FBound + } + + type FP = Induction.FP[Node] + type FBound <: FP + + type Graph = GraphK { type _Node <: Node } + } + +} diff --git a/tests/pos/i22257.orig.scala b/tests/pos/i22257.orig.scala new file mode 100644 index 000000000000..0be8d02d5a85 --- /dev/null +++ b/tests/pos/i22257.orig.scala @@ -0,0 +1,52 @@ + +object Scaffold { + + trait Arrow + object Arrow { + trait Outbound extends Arrow + } + + trait NodeKOrGraphK {} + + trait NodeK extends NodeKOrGraphK { + + type FBound <: Induction + + protected def getInduction: Seq[FBound] + } + + trait Induction { + def arrow: Arrow + def node: NodeK + } + + object Induction { + + trait FP[+N <: NodeK] extends Induction { // short for "fixed point" + def node: N + } + } + + trait GraphK extends NodeKOrGraphK { + + type Batch[+T] <: Iterable[T] + + type _Node <: NodeK + + def entries: Batch[_Node] + } + + trait Topology { + + type FP = Induction.FP[Node] + type FBound <: FP + + type Node = NodeK { type FBound <: Topology.this.FBound } + trait Node_ extends NodeK { + type FBound = Topology.this.FBound + } + + type Graph = GraphK { type _Node <: Node } + } + +} diff --git a/tests/pos/i22257.scala b/tests/pos/i22257.scala new file mode 100644 index 000000000000..8cd797529097 --- /dev/null +++ b/tests/pos/i22257.scala @@ -0,0 +1,26 @@ +trait NodeK { type FBound } +trait Fixed[+N <: NodeK] + +type Bound1 <: FP1 +type FP1 = Fixed[Node1] +type Node1 = NodeK { type FBound <: Bound1 } // was-error + +type FP2 = Fixed[Node2] // was-error +type Bound2 <: FP2 +type Node2 = NodeK { type FBound <: Bound2 } + +type Node3 = NodeK { type FBound <: Bound3 } +type FP3 = Fixed[Node3] +type Bound3 <: FP3 + +type Bound4 <: FP4 +type Node4 = NodeK { type FBound <: Bound4 } // was-error +type FP4 = Fixed[Node4] + +type FP5 = Fixed[Node5] // was-error +type Node5 = NodeK { type FBound <: Bound5 } +type Bound5 <: FP5 + +type Node6 = NodeK { type FBound <: Bound6 } +type Bound6 <: FP6 +type FP6 = Fixed[Node6] diff --git a/tests/pos/i22266.scala b/tests/pos/i22266.scala new file mode 100644 index 000000000000..2ecdf9492a93 --- /dev/null +++ b/tests/pos/i22266.scala @@ -0,0 +1,22 @@ +sealed trait NonPolygon +sealed trait Polygon + +sealed trait SymmetryAspect +sealed trait RotationalSymmetry extends SymmetryAspect +sealed trait MaybeRotationalSymmetry extends SymmetryAspect + +enum Shape: + case Circle extends Shape with NonPolygon with RotationalSymmetry + case Triangle extends Shape with Polygon with MaybeRotationalSymmetry + case Square extends Shape with Polygon with RotationalSymmetry + +object Shape: + + def hasPolygon( + rotationalSyms: Vector[Shape & RotationalSymmetry], + maybeSyms: Vector[Shape & MaybeRotationalSymmetry] + ): Boolean = + val all = rotationalSyms.concat(maybeSyms) + all.exists: + case _: Polygon => true + case _ => false diff --git a/tests/pos/i22266.unenum.scala b/tests/pos/i22266.unenum.scala new file mode 100644 index 000000000000..e7529b7edabe --- /dev/null +++ b/tests/pos/i22266.unenum.scala @@ -0,0 +1,22 @@ +sealed trait NonPolygon +sealed trait Polygon + +sealed trait SymmetryAspect +sealed trait RotationalSymmetry extends SymmetryAspect +sealed trait MaybeRotationalSymmetry extends SymmetryAspect + +sealed abstract class Shape + +object Shape: + case object Circle extends Shape with NonPolygon with RotationalSymmetry + case object Triangle extends Shape with Polygon with MaybeRotationalSymmetry + case object Square extends Shape with Polygon with RotationalSymmetry + + def hasPolygon( + rotationalSyms: Vector[Shape & RotationalSymmetry], + maybeSyms: Vector[Shape & MaybeRotationalSymmetry] + ): Boolean = + val all = rotationalSyms.concat(maybeSyms) + all.exists: + case _: Polygon => true + case _ => false diff --git a/tests/pos/i22324.scala b/tests/pos/i22324.scala index b35f82d52ac9..96ea1d54afc5 100644 --- a/tests/pos/i22324.scala +++ b/tests/pos/i22324.scala @@ -1,5 +1,3 @@ -import scala.language.experimental.namedTuples - opaque type System = (wires: Any) extension (system: System) diff --git a/tests/pos/i22332.scala b/tests/pos/i22332.scala new file mode 100644 index 000000000000..1b0b6a370329 --- /dev/null +++ b/tests/pos/i22332.scala @@ -0,0 +1,5 @@ + +object Foo: + val foo = 42 + // one space + \ No newline at end of file diff --git a/tests/pos/i22408.scala b/tests/pos/i22408.scala new file mode 100644 index 000000000000..17fd0fbb474d --- /dev/null +++ b/tests/pos/i22408.scala @@ -0,0 +1,11 @@ +object Obj: + @scala.annotation.static + val some_static_value: Int = { + val some_local_value: Int = { + val some_local_value_1 = ??? + some_local_value_1 + } + some_local_value + } + +class Obj diff --git a/tests/pos/i22440.scala b/tests/pos/i22440.scala new file mode 100644 index 000000000000..f72bb25d569f --- /dev/null +++ b/tests/pos/i22440.scala @@ -0,0 +1,4 @@ +//> using options "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s" + +def foo(implicit x: Int) = x +val _ = foo(1) // warn \ No newline at end of file diff --git a/tests/pos/i22456.scala b/tests/pos/i22456.scala new file mode 100644 index 000000000000..ed1241bc4b39 --- /dev/null +++ b/tests/pos/i22456.scala @@ -0,0 +1,4 @@ +import language.experimental.modularity + +class T(tracked val y: Int) +class C(tracked val x: Int) extends T(x + 1) diff --git a/tests/pos/i22468.scala b/tests/pos/i22468.scala new file mode 100644 index 000000000000..265d6d3a89b7 --- /dev/null +++ b/tests/pos/i22468.scala @@ -0,0 +1,10 @@ +import Result.* +opaque type Result[+E, +A] = Success[A] | Error[E] + +object Result: + opaque type Success[+A] = A + sealed abstract class Error[+E] + + extension [E, A](self: Result[E, A]) + inline def transform[B]: B = ??? + def problem: Boolean = transform[Boolean] diff --git a/tests/pos/i22470.scala b/tests/pos/i22470.scala new file mode 100644 index 000000000000..83599f2564fc --- /dev/null +++ b/tests/pos/i22470.scala @@ -0,0 +1,17 @@ +trait A +trait OuterClass +trait MidClass +trait InnerClass + +object Obj: + def outerDef(a: A) = + new OuterClass { + def midDef(): Unit = { + new MidClass { + val valdef = new InnerClass { + def innerDef() = + println(a) + } + } + } + } diff --git a/tests/pos/i22518.scala b/tests/pos/i22518.scala new file mode 100644 index 000000000000..d530159701c4 --- /dev/null +++ b/tests/pos/i22518.scala @@ -0,0 +1,9 @@ +sealed trait Foo[T] +class Bar extends Foo[?] + +def mkFoo[T]: Foo[T] = + ??? + +def test: Unit = + mkFoo match + case _ => () diff --git a/tests/pos/i22548.scala b/tests/pos/i22548.scala new file mode 100644 index 000000000000..beb878d92670 --- /dev/null +++ b/tests/pos/i22548.scala @@ -0,0 +1,2 @@ +trait Bar[T] +class Foo[T <: Bar[T]] (private val buffer: Any) extends AnyVal diff --git a/tests/pos/i22560.scala b/tests/pos/i22560.scala new file mode 100644 index 000000000000..af4382ba5a15 --- /dev/null +++ b/tests/pos/i22560.scala @@ -0,0 +1,32 @@ + +package companionless: + + class Enumeration: + protected class Val(i: Int): + def this() = this(42) + + class Test extends Enumeration: + val Hearts = Val(27) + val Diamonds = Val() + + +package companioned: + + class Enumeration: + protected class Val(i: Int): + def this() = this(42) + protected object Val + + class Test extends Enumeration: + val Hearts = Val(27) + val Diamonds = Val() + +package p: + + package internal: + + protected[p] class P(i : Int) + private[p] class Q(i : Int) + + def f = internal.P(42) + def g = internal.Q(42) diff --git a/tests/pos/i22560b/client_2.scala b/tests/pos/i22560b/client_2.scala new file mode 100644 index 000000000000..bb57276c12c6 --- /dev/null +++ b/tests/pos/i22560b/client_2.scala @@ -0,0 +1,17 @@ + +package companionless: + + class Test extends Enumeration: + val Hearts = Val(27) + val Diamonds = Val() + + +package companioned: + + class Test extends Enumeration: + val Hearts = Val(27) + val Diamonds = Val() + +package p: + + def f = internal.P(42) diff --git a/tests/pos/i22560b/lib_1.scala b/tests/pos/i22560b/lib_1.scala new file mode 100644 index 000000000000..d247d63ec9cf --- /dev/null +++ b/tests/pos/i22560b/lib_1.scala @@ -0,0 +1,19 @@ + +package companionless: + + class Enumeration: + protected class Val(i: Int): + def this() = this(42) + +package companioned: + + class Enumeration: + protected class Val(i: Int): + def this() = this(42) + protected object Val + +package p: + + package internal: + + protected[p] class P(i : Int) diff --git a/tests/pos/i22592.scala b/tests/pos/i22592.scala new file mode 100644 index 000000000000..f6a1f2eff696 --- /dev/null +++ b/tests/pos/i22592.scala @@ -0,0 +1,15 @@ +import scala.quoted.* + +trait Foo: + def inherited = () + +object Bar extends Foo: + def local = () + def localArg(arg: Any) = () + + def macro1(using Quotes): Expr[Unit] = '{ local } + def macro2(using Quotes): Expr[Unit] = '{ Bar.inherited } + def macro3(using Quotes): Expr[Unit] = '{ inherited } + def macro4(using Quotes): Expr[Unit] = '{ this.local } + def macro5(using Quotes): Expr[Unit] = '{ this.inherited } + def macro6(using Quotes): Expr[Unit] = '{ localArg(this) } diff --git a/tests/pos/i22608.scala b/tests/pos/i22608.scala new file mode 100644 index 000000000000..e4b49e87769f --- /dev/null +++ b/tests/pos/i22608.scala @@ -0,0 +1,48 @@ + +def f(i: Int) = i +def g(i: Int, j: Int) = i+j + +def t = + val y = f( + if (true)// then + val x = 1 + 5 + else 7 + ) + y + +def u(j: Int) = + val y = g( + if (true)// then + val x = 1 + 5 + else 7, + j + ) + y + +def b(k: Boolean): Int = + f( + if ( + k + && b(!k) > 0 + ) then 27 + else 42 + ) + +def p(b: Boolean) = + import collection.mutable.ListBuffer + val xs, ys = ListBuffer.empty[String] + (if (b) + xs + else + ys) += "hello, world" + (xs.toString, ys.toString) + +def q(b: Boolean) = + import collection.mutable.ListBuffer + val xs, ys = ListBuffer.empty[String] + (if (b) + then xs + else ys) += "hello, world" + (xs.toString, ys.toString) diff --git a/tests/pos/i3323.scala b/tests/pos/i3323.scala deleted file mode 100644 index 94d072d4a2fc..000000000000 --- a/tests/pos/i3323.scala +++ /dev/null @@ -1,9 +0,0 @@ -//> using options -Xfatal-warnings -deprecation -feature - -class Foo { - def foo[A](lss: List[List[A]]): Unit = { - lss match { - case xss: List[List[A]] => - } - } -} diff --git a/tests/pos/infer-tracked-1.scala b/tests/pos/infer-tracked-1.scala new file mode 100644 index 000000000000..b4976a963074 --- /dev/null +++ b/tests/pos/infer-tracked-1.scala @@ -0,0 +1,34 @@ +import scala.language.experimental.modularity +import scala.language.future + +trait Ordering { + type T + def compare(t1:T, t2: T): Int +} + +class SetFunctor(val ord: Ordering) { + type Set = List[ord.T] + def empty: Set = Nil + + implicit class helper(s: Set) { + def add(x: ord.T): Set = x :: remove(x) + def remove(x: ord.T): Set = s.filter(e => ord.compare(x, e) != 0) + def member(x: ord.T): Boolean = s.exists(e => ord.compare(x, e) == 0) + } +} + +object Test { + val orderInt = new Ordering { + type T = Int + def compare(t1: T, t2: T): Int = t1 - t2 + } + + val IntSet = new SetFunctor(orderInt) + import IntSet.* + + def main(args: Array[String]) = { + val set = IntSet.empty.add(6).add(8).add(23) + assert(!set.member(7)) + assert(set.member(8)) + } +} diff --git a/tests/pos/infer-tracked-explicit-witness.scala b/tests/pos/infer-tracked-explicit-witness.scala new file mode 100644 index 000000000000..7326919b0d6a --- /dev/null +++ b/tests/pos/infer-tracked-explicit-witness.scala @@ -0,0 +1,18 @@ +import scala.language.experimental.modularity + +trait T: + type Self + type X + def foo: Self + +class D[C](using val wd: C is T) +class E(using val we: Int is T) + +def Test = + given w: Int is T: + def foo: Int = 42 + type X = Long + val d = D(using w) + summon[d.wd.X =:= Long] + val e = E(using w) + summon[e.we.X =:= Long] diff --git a/tests/pos/infer-tracked-parent-refinements.scala b/tests/pos/infer-tracked-parent-refinements.scala new file mode 100644 index 000000000000..0d71d7cc2897 --- /dev/null +++ b/tests/pos/infer-tracked-parent-refinements.scala @@ -0,0 +1,8 @@ +import scala.language.experimental.modularity +import scala.language.future + +trait WithValue { type Value = Int } + +case class Year(value: Int) extends WithValue { + val x: Value = 2 +} diff --git a/tests/pos/infer-tracked-parsercombinators-expanded.scala b/tests/pos/infer-tracked-parsercombinators-expanded.scala new file mode 100644 index 000000000000..63c6aec9e84a --- /dev/null +++ b/tests/pos/infer-tracked-parsercombinators-expanded.scala @@ -0,0 +1,65 @@ +import scala.language.experimental.modularity +import scala.language.future + +import collection.mutable + +/// A parser combinator. +trait Combinator[T]: + + /// The context from which elements are being parsed, typically a stream of tokens. + type Context + /// The element being parsed. + type Element + + extension (self: T) + /// Parses and returns an element from `context`. + def parse(context: Context): Option[Element] +end Combinator + +final case class Apply[C, E](action: C => Option[E]) +final case class Combine[A, B](first: A, second: B) + +object test: + + class apply[C, E] extends Combinator[Apply[C, E]]: + type Context = C + type Element = E + extension(self: Apply[C, E]) + def parse(context: C): Option[E] = self.action(context) + + def apply[C, E]: apply[C, E] = new apply[C, E] + + class combine[A, B]( + val f: Combinator[A], + val s: Combinator[B] { type Context = f.Context} + ) extends Combinator[Combine[A, B]]: + type Context = f.Context + type Element = (f.Element, s.Element) + extension(self: Combine[A, B]) + def parse(context: Context): Option[Element] = ??? + + def combine[A, B]( + _f: Combinator[A], + _s: Combinator[B] { type Context = _f.Context} + ) = new combine[A, B](_f, _s) + // cast is needed since the type of new combine[A, B](_f, _s) + // drops the required refinement. + + extension [A] (buf: mutable.ListBuffer[A]) def popFirst() = + if buf.isEmpty then None + else try Some(buf.head) finally buf.remove(0) + + @main def hello: Unit = { + val source = (0 to 10).toList + val stream = source.to(mutable.ListBuffer) + + val n = Apply[mutable.ListBuffer[Int], Int](s => s.popFirst()) + val m = Combine(n, n) + + val c = combine( + apply[mutable.ListBuffer[Int], Int], + apply[mutable.ListBuffer[Int], Int] + ) + val r = c.parse(m)(stream) // was type mismatch, now OK + val rc: Option[(Int, Int)] = r + } diff --git a/tests/pos/infer-tracked.scala b/tests/pos/infer-tracked.scala new file mode 100644 index 000000000000..08caac1c46c1 --- /dev/null +++ b/tests/pos/infer-tracked.scala @@ -0,0 +1,60 @@ +import scala.language.experimental.modularity +import scala.language.future + +abstract class C: + type T + def foo: T + +class F(val x: C): + val result: x.T = x.foo + +class G(override val x: C) extends F(x) + +class H(val x: C): + type T1 = x.T + val result: T1 = x.foo + +class I(val c: C, val t: c.T) + +case class J(c: C): + val result: c.T = c.foo + +case class K(c: C): + def result[B >: c.T]: B = c.foo + +case class L(c: C): + type T = c.T + +class M + +given mInst: (c: C) => M: + def foo: c.T = c.foo + +def Test = + val c = new C: + type T = Int + def foo = 42 + + val f = new F(c) + val _: Int = f.result + + // val g = new G(c) // current limitation of infering in Namer, should emit a lint + // val _: Int = g.result + + val h = new H(c) + val _: Int = h.result + + val i = new I(c, c.foo) + val _: Int = i.t + + val j = J(c) + val _: Int = j.result + + val k = K(c) + val _: Int = k.result + + val l = L(c) + summon[l.T =:= Int] + + // val m = mInst(using c) // current limitation, we infer tracked after this desugaring + // val _: Int = m.foo diff --git a/tests/pos/interleavingExperimental.scala b/tests/pos/interleavingExperimental.scala deleted file mode 100644 index 63227ef1ebfe..000000000000 --- a/tests/pos/interleavingExperimental.scala +++ /dev/null @@ -1,5 +0,0 @@ -//> using options --source 3.5 - -import scala.language.experimental.clauseInterleaving - -def ba[A](x: A)[B](using B): B = summon[B] \ No newline at end of file diff --git a/tests/pos/match-type-disjoint-22076.scala b/tests/pos/match-type-disjoint-22076.scala new file mode 100644 index 000000000000..c10555501b97 --- /dev/null +++ b/tests/pos/match-type-disjoint-22076.scala @@ -0,0 +1,8 @@ +trait Foo[CP <: NonEmptyTuple]: + type EndNode = Tuple.Last[CP] + +def f(end: Foo[?]): end.EndNode = + ??? + +trait Bar[CP <: NonEmptyTuple] extends Foo[CP]: + val v: EndNode = f(this) diff --git a/tests/pos/named-tuple-combinators.scala b/tests/pos/named-tuple-combinators.scala index a5134b2e7d26..c027ba688d02 100644 --- a/tests/pos/named-tuple-combinators.scala +++ b/tests/pos/named-tuple-combinators.scala @@ -1,4 +1,3 @@ -import scala.language.experimental.namedTuples object Test: // original code from issue https://github.com/scala/scala3/issues/20427 diff --git a/tests/pos/named-tuple-downcast.scala b/tests/pos/named-tuple-downcast.scala index b9876623faf2..239089b60c3d 100644 --- a/tests/pos/named-tuple-downcast.scala +++ b/tests/pos/named-tuple-downcast.scala @@ -1,5 +1,3 @@ -import scala.language.experimental.namedTuples - type Person = (name: String, age: Int) val Bob: Person = (name = "Bob", age = 33) diff --git a/tests/pos/named-tuple-selectable.scala b/tests/pos/named-tuple-selectable.scala index be5f0400e58c..0e1324f70ae6 100644 --- a/tests/pos/named-tuple-selectable.scala +++ b/tests/pos/named-tuple-selectable.scala @@ -1,4 +1,3 @@ -import scala.language.experimental.namedTuples class FromFields extends Selectable: type Fields = (xs: List[Int], poly: [T] => (x: List[T]) => Option[T]) diff --git a/tests/pos/named-tuple-selections.scala b/tests/pos/named-tuple-selections.scala index c3569f21b323..7b73daad2e72 100644 --- a/tests/pos/named-tuple-selections.scala +++ b/tests/pos/named-tuple-selections.scala @@ -1,4 +1,3 @@ -import scala.language.experimental.namedTuples object Test1: // original code from issue https://github.com/scala/scala3/issues/20439 diff --git a/tests/pos/named-tuple-unstable.scala b/tests/pos/named-tuple-unstable.scala index 6a6a36732a14..d15bdc578a3a 100644 --- a/tests/pos/named-tuple-unstable.scala +++ b/tests/pos/named-tuple-unstable.scala @@ -1,4 +1,3 @@ -import scala.language.experimental.namedTuples import NamedTuple.{AnyNamedTuple, NamedTuple} trait Foo extends Selectable: diff --git a/tests/pos/named-tuple-widen.scala b/tests/pos/named-tuple-widen.scala index 410832e04c17..cc12a5f09b16 100644 --- a/tests/pos/named-tuple-widen.scala +++ b/tests/pos/named-tuple-widen.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples class A class B diff --git a/tests/pos/named-tuples-i22245.scala b/tests/pos/named-tuples-i22245.scala new file mode 100644 index 000000000000..d5c852c0c435 --- /dev/null +++ b/tests/pos/named-tuples-i22245.scala @@ -0,0 +1,6 @@ +import language.experimental.namedTuples + +def find(explore: List[(seen: Set[Int], x: Int, y: Int)]): Any = + explore match + case Nil => ??? + case (seen = s, x = x, y = y) :: rest => ??? \ No newline at end of file diff --git a/tests/pos/named-tuples-ops-mirror.scala b/tests/pos/named-tuples-ops-mirror.scala index f66eb89534fb..b8745cf785d5 100644 --- a/tests/pos/named-tuples-ops-mirror.scala +++ b/tests/pos/named-tuples-ops-mirror.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples import NamedTuple.* @FailsWith[HttpError] diff --git a/tests/pos/named-tuples1.scala b/tests/pos/named-tuples1.scala index 58e3fc065e61..532f1df7efd4 100644 --- a/tests/pos/named-tuples1.scala +++ b/tests/pos/named-tuples1.scala @@ -1,5 +1,4 @@ import annotation.experimental -import language.experimental.namedTuples @main def Test = val bob = (name = "Bob", age = 33): (name: String, age: Int) diff --git a/tests/pos/namedtuple-src-incompat.scala b/tests/pos/namedtuple-src-incompat.scala index 57451a4321b7..76eb5e4aa850 100644 --- a/tests/pos/namedtuple-src-incompat.scala +++ b/tests/pos/namedtuple-src-incompat.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples var age = 22 val x = (age = 1) val _: (age: Int) = x diff --git a/tests/pos/preview-flag.scala b/tests/pos/preview-flag.scala new file mode 100644 index 000000000000..dbcfcaaff5b3 --- /dev/null +++ b/tests/pos/preview-flag.scala @@ -0,0 +1,18 @@ +//> using options -preview +package scala // @preview is private[scala] +import scala.annotation.internal.preview + +@preview def previewDef: Int = 42 + +class Foo: + def foo: Int = previewDef + +class Bar: + def bar: Int = previewDef +object Bar: + def bar: Int = previewDef + +object Baz: + def bar: Int = previewDef + +def toplevelMethod: Int = previewDef diff --git a/tests/pos/switches.scala b/tests/pos/switches.scala index bd7e44f1c8cf..beb378debb40 100644 --- a/tests/pos/switches.scala +++ b/tests/pos/switches.scala @@ -42,6 +42,7 @@ class Test { case IntAnyVal(100) => 2 case IntAnyVal(1000) => 3 case IntAnyVal(10000) => 4 + case _ => -1 } } diff --git a/tests/pos/t10350/Bar.scala b/tests/pos/t10350/Bar.scala new file mode 100644 index 000000000000..509d5af2b686 --- /dev/null +++ b/tests/pos/t10350/Bar.scala @@ -0,0 +1,6 @@ + +package bar + +object Bar { + def xxx(s: String): foo.Foo = foo.Foo.create(s) +} diff --git a/tests/pos/t10350/Baz.java b/tests/pos/t10350/Baz.java new file mode 100644 index 000000000000..c11c3875c3cb --- /dev/null +++ b/tests/pos/t10350/Baz.java @@ -0,0 +1,5 @@ + +package foo.java; + +interface Baz { +} diff --git a/tests/pos/t10350/Foo.java b/tests/pos/t10350/Foo.java new file mode 100644 index 000000000000..ea4bba7643e7 --- /dev/null +++ b/tests/pos/t10350/Foo.java @@ -0,0 +1,14 @@ + +package foo; + +public interface Foo { + static Foo create(java.lang.String v) { + return null; + } +} + +/* +5 | static Foo create(java.lang.String v) { + | ^^^^^^^^^ + | value lang is not a member of foo.java + */ diff --git a/tests/pos/t11788/Bar.scala b/tests/pos/t11788/Bar.scala new file mode 100644 index 000000000000..f3d61e4f468f --- /dev/null +++ b/tests/pos/t11788/Bar.scala @@ -0,0 +1,5 @@ +package p + +object Bar extends App { + println(new Foo().test()) +} diff --git a/tests/pos/t11788/Foo.java b/tests/pos/t11788/Foo.java new file mode 100644 index 000000000000..5b2224b6e00c --- /dev/null +++ b/tests/pos/t11788/Foo.java @@ -0,0 +1,11 @@ +package p; + +public class Foo { + private String java; + + // java is the rooted package, not the field + public java.lang.Integer test() { + //return Integer.valueOf(42); + throw null; + } +} diff --git a/tests/pos/t11788b/Bar.scala b/tests/pos/t11788b/Bar.scala new file mode 100644 index 000000000000..f3d61e4f468f --- /dev/null +++ b/tests/pos/t11788b/Bar.scala @@ -0,0 +1,5 @@ +package p + +object Bar extends App { + println(new Foo().test()) +} diff --git a/tests/pos/t11788b/Foo.java b/tests/pos/t11788b/Foo.java new file mode 100644 index 000000000000..960085bd453f --- /dev/null +++ b/tests/pos/t11788b/Foo.java @@ -0,0 +1,10 @@ +package p; + +public class Foo { + private String java; + + public java.lang.Integer test() { + //return Integer.valueOf(42); + throw null; + } +} diff --git a/tests/pos/t11788b/java.java b/tests/pos/t11788b/java.java new file mode 100644 index 000000000000..713e265b5e72 --- /dev/null +++ b/tests/pos/t11788b/java.java @@ -0,0 +1,8 @@ +package p; + +public class java { + public static class lang { + public static class Integer { + } + } +} diff --git a/tests/pos/t11788c/Bar.scala b/tests/pos/t11788c/Bar.scala new file mode 100644 index 000000000000..f3d61e4f468f --- /dev/null +++ b/tests/pos/t11788c/Bar.scala @@ -0,0 +1,5 @@ +package p + +object Bar extends App { + println(new Foo().test()) +} diff --git a/tests/pos/t11788c/Foo.java b/tests/pos/t11788c/Foo.java new file mode 100644 index 000000000000..c244f1f13554 --- /dev/null +++ b/tests/pos/t11788c/Foo.java @@ -0,0 +1,10 @@ +package p; + +public class Foo { + private String java; + + // java is class in scope, not the term member or package + public java.lang.Integer.Inner test() { + throw null; + } +} diff --git a/tests/pos/t11788c/java.java b/tests/pos/t11788c/java.java new file mode 100644 index 000000000000..3b5ff8e11cd2 --- /dev/null +++ b/tests/pos/t11788c/java.java @@ -0,0 +1,10 @@ +package p; + +public class java { + public static class lang { + public static class Integer { + public static class Inner { + } + } + } +} diff --git a/tests/pos/tuple-exaustivity.scala b/tests/pos/tuple-exaustivity.scala deleted file mode 100644 index a27267fc89e5..000000000000 --- a/tests/pos/tuple-exaustivity.scala +++ /dev/null @@ -1,6 +0,0 @@ -//> using options -Xfatal-warnings -deprecation -feature - -def test(t: Tuple) = - t match - case Tuple() => - case head *: tail => diff --git a/tests/pos/tuple-ops.scala b/tests/pos/tuple-ops.scala index 739b1ebeeb02..e89c0e8e51aa 100644 --- a/tests/pos/tuple-ops.scala +++ b/tests/pos/tuple-ops.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples import Tuple.* def test = diff --git a/tests/printing/dependent-annot-default-args.check b/tests/printing/dependent-annot-default-args.check index f457d5d62edb..ccb988e83663 100644 --- a/tests/printing/dependent-annot-default-args.check +++ b/tests/printing/dependent-annot-default-args.check @@ -23,15 +23,16 @@ package { new dependent-annot-default-args$package() final module class dependent-annot-default-args$package() extends Object() { this: dependent-annot-default-args$package.type => - def f(x: Int): Int @annot(x) = x + def f(x: Any): Any @annot(x) = x def f2(x: Int): Int @annot2( y = Array.apply[Any](["Hello",x : Any]*)(scala.reflect.ClassTag.Any)) = x + def f3(x: Any, y: Any): Any @annot(x = x, y = y) = x def test: Unit = { val y: Int = ??? - val z: Int @annot(y) = f(y) + val z: Any @annot(y) = f(y) val z2: Int @annot2( y = Array.apply[Any](["Hello",y : Any]*)(scala.reflect.ClassTag.Any) @@ -41,6 +42,78 @@ package { @annot2( y = Array.apply[Any](["Hello",y : Any]*)(scala.reflect.ClassTag.Any)) val z4: Int = 45 + val z5: annot = + { + val y$1: Array[String] = + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])) + new annot(x = 1, y = y$1) + } + val z6: annot2 = + { + val y$2: Array[Any] = + Array.apply[Any](["World" : Any]*)(scala.reflect.ClassTag.Any) + new annot2(x = 1, y = y$2) + } + @annot(x = 2, + y = + { + val y$3: Array[String] = + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])) + new annot(x = 1, y = y$3) + } + ) val z7: Int = 45 + @annot(x = 4, + y = + 3: + Int @annot(x = 1, + y = + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])) + ) + ) val z8: Int = 45 + val z9: + Int @annot(x = 2, + y = + { + val y$4: Array[String] = + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])) + new annot(x = 1, y = y$4) + } + ) + = 46 + @annot(x = 4, + y = + 3: + Int @annot(x = 1, + y = + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])) + ) + ) val z10: Int = 45 + val z11: Any @annot(annot) = + f( + { + val y$5: Array[String] = + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])) + new annot(x = 1, y = y$5) + } + ) + val z12: Any @annot(x = x, y = y) = + f3( + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])), + 1) + val z13: Any @annot(x = x, y = y) = + { + val y$6: Array[String] = + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])) + f3(x = 1, y = y$6) + } () } } diff --git a/tests/printing/dependent-annot-default-args.scala b/tests/printing/dependent-annot-default-args.scala index 11fc9ef52cc9..f3cb2a82c910 100644 --- a/tests/printing/dependent-annot-default-args.scala +++ b/tests/printing/dependent-annot-default-args.scala @@ -1,8 +1,9 @@ class annot(x: Any, y: Any = 42) extends annotation.Annotation class annot2(x: Any = -1, y: Array[Any] = Array("Hello")) extends annotation.Annotation -def f(x: Int): Int @annot(x) = x +def f(x: Any): Any @annot(x) = x def f2(x: Int): Int @annot2(y = Array("Hello", x)) = x +def f3(x: Any, y: Any): Any @annot(y=y, x=x) = x def test = val y: Int = ??? @@ -13,3 +14,14 @@ def test = @annot(44) val z3 = 45 @annot2(y = Array("Hello", y)) val z4 = 45 + // Arguments are still lifted if the annotation class is instantiated + // explicitly. See #22526. + val z5 = new annot(y = Array("World"), x = 1) + val z6 = new annot2(y = Array("World"), x = 1) + @annot(y = new annot(y = Array("World"), x = 1), x = 2) val z7 = 45 + @annot(y = 3: Int @annot(y = Array("World"), x = 1), x = 4) val z8 = 45 + val z9: Int @annot(y = new annot(y = Array("World"), x = 1), x = 2) = 46 + @annot(y = 3: Int @annot(y = Array("World"), x = 1), x = 4) val z10 = 45 + val z11 = f(new annot(y = Array("World"), x = 1)) + val z12 = f3(Array("World"), 1) + val z13 = f3(y=Array("World"), x=1) diff --git a/tests/printing/posttyper/i22533.check b/tests/printing/posttyper/i22533.check new file mode 100644 index 000000000000..33c023d94a74 --- /dev/null +++ b/tests/printing/posttyper/i22533.check @@ -0,0 +1,25 @@ +[[syntax trees at end of posttyper]] // tests/printing/posttyper/i22533.scala +package { + @SourceFile("tests/printing/posttyper/i22533.scala") trait A() extends Any { + override def equals(x: Any): Boolean = ??? + override def hashCode(): Int = ??? + } + @SourceFile("tests/printing/posttyper/i22533.scala") final class Foo(u: Int) + extends AnyVal(), A { + override def hashCode(): Int = Foo.this.u.hashCode() + override def equals(x$0: Any): Boolean = + x$0 match + { + case x$0 @ _:Foo @unchecked => this.u.==(x$0.u) + case _ => false + } + private[this] val u: Int + } + final lazy module val Foo: Foo = new Foo() + @SourceFile("tests/printing/posttyper/i22533.scala") final module class Foo() + extends AnyRef() { this: Foo.type => + private def writeReplace(): AnyRef = + new scala.runtime.ModuleSerializationProxy(classOf[Foo.type]) + } +} + diff --git a/tests/printing/posttyper/i22533.flags b/tests/printing/posttyper/i22533.flags new file mode 100644 index 000000000000..21379f85d52a --- /dev/null +++ b/tests/printing/posttyper/i22533.flags @@ -0,0 +1 @@ +-Ycompile-scala2-library diff --git a/tests/printing/posttyper/i22533.scala b/tests/printing/posttyper/i22533.scala new file mode 100644 index 000000000000..07e9e1c4c011 --- /dev/null +++ b/tests/printing/posttyper/i22533.scala @@ -0,0 +1,7 @@ +//> using options -Ycompile-scala2-library + +trait A extends Any: + override def equals(x: Any): Boolean = ??? + override def hashCode(): Int = ??? + +class Foo(u: Int) extends AnyVal, A \ No newline at end of file diff --git a/tests/rewrites/ambigious-named-tuple-assignment.check b/tests/rewrites/ambiguous-named-tuple-assignment.check similarity index 82% rename from tests/rewrites/ambigious-named-tuple-assignment.check rename to tests/rewrites/ambiguous-named-tuple-assignment.check index 00e6cc4112f1..2f2dd7721239 100644 --- a/tests/rewrites/ambigious-named-tuple-assignment.check +++ b/tests/rewrites/ambiguous-named-tuple-assignment.check @@ -1,17 +1,15 @@ -import scala.language.experimental.namedTuples - object i21770: def f(g: Int => Unit) = g(0) var cache: Option[Int] = None f(i => {cache = Some(i)}) - + object i21861: var age: Int = 28 { age = 29 } - - + + object i21861c: def age: Int = ??? def age_=(x: Int): Unit = () diff --git a/tests/rewrites/ambigious-named-tuple-assignment.scala b/tests/rewrites/ambiguous-named-tuple-assignment.scala similarity index 82% rename from tests/rewrites/ambigious-named-tuple-assignment.scala rename to tests/rewrites/ambiguous-named-tuple-assignment.scala index e9685b7b58cf..189c804fbe01 100644 --- a/tests/rewrites/ambigious-named-tuple-assignment.scala +++ b/tests/rewrites/ambiguous-named-tuple-assignment.scala @@ -1,17 +1,15 @@ -import scala.language.experimental.namedTuples - object i21770: def f(g: Int => Unit) = g(0) var cache: Option[Int] = None f(i => (cache = Some(i))) - + object i21861: var age: Int = 28 ( age = 29 ) - - + + object i21861c: def age: Int = ??? def age_=(x: Int): Unit = () diff --git a/tests/rewrites/i22440.check b/tests/rewrites/i22440.check new file mode 100644 index 000000000000..417fd442f9f2 --- /dev/null +++ b/tests/rewrites/i22440.check @@ -0,0 +1,5 @@ +//> using options -source 3.7-migration + +def foo(implicit x: Int) = () +val _ = foo(using 1) +val _ = foo (using 1) diff --git a/tests/rewrites/i22440.scala b/tests/rewrites/i22440.scala new file mode 100644 index 000000000000..7bbe8e44b5f7 --- /dev/null +++ b/tests/rewrites/i22440.scala @@ -0,0 +1,5 @@ +//> using options -source 3.7-migration + +def foo(implicit x: Int) = () +val _ = foo(1) +val _ = foo (1) \ No newline at end of file diff --git a/tests/rewrites/infix-named-args.check b/tests/rewrites/infix-named-args.check index 5f59cf272ba1..7f85ab490ca4 100644 --- a/tests/rewrites/infix-named-args.check +++ b/tests/rewrites/infix-named-args.check @@ -1,5 +1,3 @@ -import scala.language.experimental.namedTuples - class C: def multi(x: Int, y: Int): Int = x + y def **(x: Int, y: Int): Int = x + y diff --git a/tests/rewrites/infix-named-args.scala b/tests/rewrites/infix-named-args.scala index a954776a9104..bcdf4a21a9d2 100644 --- a/tests/rewrites/infix-named-args.scala +++ b/tests/rewrites/infix-named-args.scala @@ -1,5 +1,3 @@ -import scala.language.experimental.namedTuples - class C: def multi(x: Int, y: Int): Int = x + y def **(x: Int, y: Int): Int = x + y diff --git a/tests/rewrites/unused.check b/tests/rewrites/unused.check new file mode 100644 index 000000000000..1ff93bfb6ef2 --- /dev/null +++ b/tests/rewrites/unused.check @@ -0,0 +1,55 @@ + +//> using options -Wunused:all + +package p1: + import java.lang.Runnable + class C extends Runnable { def run() = () } + +package p2: + import java.lang.Runnable + class C extends Runnable { def run() = () } + +package p3: + import java.lang.Runnable + class C extends Runnable { def run() = () } + +package p4: + import java.lang.{Runnable, System}, System.out + class C extends Runnable { def run() = out.println() } + +package p5: + import java.lang.{Runnable, System}, System.out + class C extends Runnable { def run() = out.println() } + +package p6: + import java.lang.{Runnable, System}, System.out + class C extends Runnable { def run() = out.println() } + +package p7: + import java.lang.{Runnable, + System}, System.out + class C extends Runnable { def run() = out.println() } + +package p8: + import java.lang.{Runnable as R, System}, System.out + class C extends R { def run() = out.println() } + +package p9: + import java.lang.{Runnable as R, System, + Thread} + class C extends R { def run() = Thread(() => System.out.println()).start() } + +package p10: + object X: + def x = 42 + class C: + import X.* // preserve text, don't rewrite to p10.X.* + def c = x + +package p11: + import collection.mutable, mutable.ListBuffer // warn // warn // not checked :( + def buf = ListBuffer.empty[String] + +package p12: + import java.lang.System, java.lang.Runnable + class C extends Runnable { def run() = System.out.println() } diff --git a/tests/rewrites/unused.scala b/tests/rewrites/unused.scala new file mode 100644 index 000000000000..85a83c7c0015 --- /dev/null +++ b/tests/rewrites/unused.scala @@ -0,0 +1,61 @@ + +//> using options -Wunused:all + +package p1: + import java.lang.Runnable + import java.lang.{Thread, String}, java.lang.Integer + class C extends Runnable { def run() = () } + +package p2: + import java.lang.Thread + import java.lang.String + import java.lang.Runnable + import java.lang.Integer + class C extends Runnable { def run() = () } + +package p3: + import java.lang.{Runnable, Thread, String} + class C extends Runnable { def run() = () } + +package p4: + import java.lang.{Runnable, System, Thread}, System.out + class C extends Runnable { def run() = out.println() } + +package p5: + import java.lang.{Thread, Runnable, System}, System.out + class C extends Runnable { def run() = out.println() } + +package p6: + import java.lang.{Runnable, System}, java.lang.Thread, System.out + class C extends Runnable { def run() = out.println() } + +package p7: + import java.lang.{Runnable, + Thread, + System}, System.out + class C extends Runnable { def run() = out.println() } + +package p8: + import java.lang.{Runnable as R, System, + Thread}, System.out + class C extends R { def run() = out.println() } + +package p9: + import java.lang.{Runnable as R, System, + Thread}, System.out + class C extends R { def run() = Thread(() => System.out.println()).start() } + +package p10: + object X: + def x = 42 + class C: + import X.*, java.util.HashMap // preserve text, don't rewrite to p10.X.* + def c = x + +package p11: + import collection.mutable, mutable.ListBuffer, java.lang.{Runnable, Thread} // warn // warn // not checked :( + def buf = ListBuffer.empty[String] + +package p12: + import collection.mutable, java.lang.System, java.lang.Runnable + class C extends Runnable { def run() = System.out.println() } diff --git a/tests/run-macros/annot-add-global-object/Macro_1.scala b/tests/run-macros/annot-add-global-object/Macro_1.scala index 031d6e33fefe..b928cf23c2e8 100644 --- a/tests/run-macros/annot-add-global-object/Macro_1.scala +++ b/tests/run-macros/annot-add-global-object/Macro_1.scala @@ -14,7 +14,7 @@ class addClass extends MacroAnnotation: def decls(cls: Symbol): List[Symbol] = List(Symbol.newMethod(cls, "run", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]), Flags.EmptyFlags, Symbol.noSymbol)) - val mod = Symbol.newModule(Symbol.spliceOwner, Symbol.freshName("Bar"), Flags.EmptyFlags, Flags.EmptyFlags, parents.map(_.tpe), decls, Symbol.noSymbol) + val mod = Symbol.newModule(Symbol.spliceOwner, Symbol.freshName("Bar"), Flags.EmptyFlags, Flags.EmptyFlags, _ => parents.map(_.tpe), decls, Symbol.noSymbol) val cls = mod.moduleClass val runSym = cls.declaredMethod("run").head diff --git a/tests/run-macros/annot-add-local-object/Macro_1.scala b/tests/run-macros/annot-add-local-object/Macro_1.scala index 3d47fafd599a..bbdfbfd287d4 100644 --- a/tests/run-macros/annot-add-local-object/Macro_1.scala +++ b/tests/run-macros/annot-add-local-object/Macro_1.scala @@ -14,7 +14,7 @@ class addClass extends MacroAnnotation: def decls(cls: Symbol): List[Symbol] = List(Symbol.newMethod(cls, "run", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]), Flags.EmptyFlags, Symbol.noSymbol)) - val mod = Symbol.newModule(Symbol.spliceOwner, "Baz", Flags.EmptyFlags, Flags.EmptyFlags, parents.map(_.tpe), decls, Symbol.noSymbol) + val mod = Symbol.newModule(Symbol.spliceOwner, "Baz", Flags.EmptyFlags, Flags.EmptyFlags, _ => parents.map(_.tpe), decls, Symbol.noSymbol) val cls = mod.moduleClass val runSym = cls.declaredMethod("run").head diff --git a/tests/run-macros/annot-add-nested-object/Macro_1.scala b/tests/run-macros/annot-add-nested-object/Macro_1.scala index ce6cbaa67a57..cfc4691e95c9 100644 --- a/tests/run-macros/annot-add-nested-object/Macro_1.scala +++ b/tests/run-macros/annot-add-nested-object/Macro_1.scala @@ -14,7 +14,7 @@ class addClass extends MacroAnnotation: def decls(cls: Symbol): List[Symbol] = List(Symbol.newMethod(cls, "run", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]), Flags.EmptyFlags, Symbol.noSymbol)) - val mod = Symbol.newModule(Symbol.spliceOwner, Symbol.freshName("Bar"), Flags.EmptyFlags, Flags.EmptyFlags, parents.map(_.tpe), decls, Symbol.noSymbol) + val mod = Symbol.newModule(Symbol.spliceOwner, Symbol.freshName("Bar"), Flags.EmptyFlags, Flags.EmptyFlags, _ => parents.map(_.tpe), decls, Symbol.noSymbol) val cls = mod.moduleClass val runSym = cls.declaredMethod("run").head diff --git a/tests/run-macros/i20449.check b/tests/run-macros/i20449.check new file mode 100644 index 000000000000..289b3f95c488 --- /dev/null +++ b/tests/run-macros/i20449.check @@ -0,0 +1,50 @@ +------ UserName.T - Directly ------- +Original: Main_2$package.UserName.T +Dealias: Main_2$package.UserName.T +Dealias dealias: Main_2$package.UserName.T + +------ UserName.T - Directly ------- +Original: Main_2$package.UserName +Dealias: Main_2$package.UserName.T +Dealias dealias: Main_2$package.UserName.T + +------ ForeignWrapper1[UserName.T] ------- +Original: Main_2$package.UserName.T +Dealias: Main_2$package.UserName.T +Dealias dealias: Main_2$package.UserName.T + +------ ForeignWrapper2[UserName.T] ------- +Original: Main_2$package.UserName.T +Dealias: Main_2$package.UserName.T +Dealias dealias: Main_2$package.UserName.T + +------ ForeignWrapper1[UserName] ------- +Original: Main_2$package.UserName +Dealias: Main_2$package.UserName.T +Dealias dealias: Main_2$package.UserName.T + +------ ForeignWrapper2[UserName] ------- +Original: Main_2$package.UserName +Dealias: Main_2$package.UserName.T +Dealias dealias: Main_2$package.UserName.T + +------ Wrapper1[UserName.T] ------- +Original: Main_2$package.UserName.T +Dealias: Main_2$package.UserName.T +Dealias dealias: Main_2$package.UserName.T + +------ Wrapper2[UserName.T] ------- +Original: Main_2$package.UserName.T +Dealias: Main_2$package.UserName.T +Dealias dealias: Main_2$package.UserName.T + +------ Wrapper1[UserName] ------- +Original: Main_2$package.UserName +Dealias: Main_2$package.UserName.T +Dealias dealias: Main_2$package.UserName.T + +------ Wrapper2[UserName] ------- +Original: Main_2$package.UserName +Dealias: Main_2$package.UserName.T +Dealias dealias: Main_2$package.UserName.T + diff --git a/tests/run-macros/i20449/Macro_1.scala b/tests/run-macros/i20449/Macro_1.scala new file mode 100644 index 000000000000..74b1dce3c4d6 --- /dev/null +++ b/tests/run-macros/i20449/Macro_1.scala @@ -0,0 +1,29 @@ +import scala.quoted.* + +class ForeignWrapper1[-A] { + inline def getTypeInfo(inline source: String): String = + ${ getTypeInfoImpl[A]('source) } + def createWrapper2 = ForeignWrapper2(this) +} + +class ForeignWrapper2[-A](val self: ForeignWrapper1[A]) { + inline def getTypeInfo(inline source: String): String = + ${getTypeInfoImpl[A]('source)} +} + +transparent inline def getTypeInfo[T](inline source: String) = + ${ getTypeInfoImpl[T]('source) } + +def getTypeInfoImpl[T: Type](source: Expr[String])(using ctx: Quotes) : Expr[String] = { + import ctx.reflect.* + + val tpe = TypeRepr.of[T] + val str = + s"""|------ ${source.valueOrAbort} ------- + |Original: ${tpe.show} + |Dealias: ${tpe.dealias.show} + |Dealias dealias: ${tpe.dealias.dealias.show} + """.stripMargin + + Expr(str) +} diff --git a/tests/run-macros/i20449/Main_2.scala b/tests/run-macros/i20449/Main_2.scala new file mode 100644 index 000000000000..3d2991489c1b --- /dev/null +++ b/tests/run-macros/i20449/Main_2.scala @@ -0,0 +1,41 @@ +object UserName { + opaque type T = String + + def apply(s: String): T = s +} + +type UserName = UserName.T + +class Wrapper1[-A] { + inline def getTypeInfo(inline source: String): String = + ${ getTypeInfoImpl[A]('source) } + def createWrapper2 = Wrapper2(this) +} + +class Wrapper2[-A](val self: Wrapper1[A]) { + inline def getTypeInfo(inline source: String): String = + ${getTypeInfoImpl[A]('source)} +} + + +@main def Test() = { + println(getTypeInfo[UserName.T]("UserName.T - Directly")) + println(getTypeInfo[UserName]("UserName.T - Directly")) + + val foreignWrapper = ForeignWrapper1[UserName.T]() + println(foreignWrapper.getTypeInfo("ForeignWrapper1[UserName.T]")) + println(foreignWrapper.createWrapper2.getTypeInfo("ForeignWrapper2[UserName.T]")) + + val foreignWrapper2 = ForeignWrapper1[UserName]() + println(foreignWrapper2.getTypeInfo("ForeignWrapper1[UserName]")) + println(foreignWrapper2.createWrapper2.getTypeInfo("ForeignWrapper2[UserName]")) + + val wrapper = Wrapper1[UserName.T]() + println(wrapper.getTypeInfo("Wrapper1[UserName.T]")) + println(wrapper.createWrapper2.getTypeInfo("Wrapper2[UserName.T]")) + + val wrapper2 = Wrapper1[UserName]() + println(wrapper2.getTypeInfo("Wrapper1[UserName]")) + println(wrapper2.createWrapper2.getTypeInfo("Wrapper2[UserName]")) + +} diff --git a/tests/run-macros/i21225.check b/tests/run-macros/i21225.check new file mode 100644 index 000000000000..51779b7b89af --- /dev/null +++ b/tests/run-macros/i21225.check @@ -0,0 +1,2 @@ +bar codec +foo codec diff --git a/tests/run-macros/i21225/Macro_1.scala b/tests/run-macros/i21225/Macro_1.scala new file mode 100644 index 000000000000..2b7bfd3b969f --- /dev/null +++ b/tests/run-macros/i21225/Macro_1.scala @@ -0,0 +1,33 @@ +//> using options -experimental + +import scala.quoted.* + +trait Codec[-T] { def print(): Unit } +object Codec { + inline def derivedWithDeps[T](deps: Any): Codec[T] = ${derivedWithDepsImpl[T]('deps)} + + private def derivedWithDepsImpl[T](deps: Expr[Any])(using q: Quotes)(using Type[T]): Expr[Codec[T]] = { + import q.reflect.* + + val givenSelector: Selector = GivenSelector(None) + val theImport = Import(deps.asTerm, List(givenSelector)) + Block(List(theImport), '{scala.compiletime.summonInline[Codec[T]]}.asTerm).asExprOf[Codec[T]] + /* import deps.given + * summonInline[Codec[T]] + */ + } + + inline def derivedWithDepsWithNamedOmitted[T](deps: Any): Codec[T] = ${derivedWithDepsWithNamedOmittedImpl[T]('deps)} + + private def derivedWithDepsWithNamedOmittedImpl[T](deps: Expr[Any])(using q: Quotes)(using Type[T]): Expr[Codec[T]] = { + import q.reflect.* + + val givenSelector: Selector = GivenSelector(None) + val omitSelector: Selector = OmitSelector("named") + val theImport = Import(deps.asTerm, List(givenSelector, omitSelector)) + Block(List(theImport), '{scala.compiletime.summonInline[Codec[T]]}.asTerm).asExprOf[Codec[T]] + /* import deps.{given, named => _} + * summonInline[Codec[T]] + */ + } +} diff --git a/tests/run-macros/i21225/Test_2.scala b/tests/run-macros/i21225/Test_2.scala new file mode 100644 index 000000000000..bd9081588754 --- /dev/null +++ b/tests/run-macros/i21225/Test_2.scala @@ -0,0 +1,14 @@ +//> using options -experimental + +import scala.quoted.* + +sealed trait Foo +class Bar extends Foo +object CustomCodecs { + given named: Codec[Bar] = new Codec[Bar] { def print(): Unit = println("bar codec")} + given Codec[Foo] = new Codec[Foo] { def print(): Unit = println("foo codec") } +} + +@main def Test = + Codec.derivedWithDeps[Bar](CustomCodecs).print() + Codec.derivedWithDepsWithNamedOmitted[Bar](CustomCodecs).print() diff --git a/tests/run-macros/newClassAnnotation.check b/tests/run-macros/newClassAnnotation.check new file mode 100644 index 000000000000..433c40f72180 --- /dev/null +++ b/tests/run-macros/newClassAnnotation.check @@ -0,0 +1,7 @@ +{ + @JavaAnnot("string in a java annotation") @ScalaAnnotation("string in a scala annotation") class name() extends java.lang.Object + + (new name(): scala.Any) +} +class Test_2$package$name$1 +string in a java annotation diff --git a/tests/run-macros/newClassAnnotation/JavaAnnot.java b/tests/run-macros/newClassAnnotation/JavaAnnot.java new file mode 100644 index 000000000000..2c815478c198 --- /dev/null +++ b/tests/run-macros/newClassAnnotation/JavaAnnot.java @@ -0,0 +1,5 @@ +import java.lang.annotation.*; +public @Retention(RetentionPolicy.RUNTIME) +@interface JavaAnnot { + public String value(); +} \ No newline at end of file diff --git a/tests/run-macros/newClassAnnotation/Macro_1.scala b/tests/run-macros/newClassAnnotation/Macro_1.scala new file mode 100644 index 000000000000..c7631317675b --- /dev/null +++ b/tests/run-macros/newClassAnnotation/Macro_1.scala @@ -0,0 +1,56 @@ +//> using options -experimental + +import scala.quoted.* +import scala.annotation.StaticAnnotation + +class ScalaAnnotation(value: String) extends StaticAnnotation + +inline def makeClass(inline name: String): Any = ${ makeClassExpr('name) } +private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Any] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + def decls(cls: Symbol): List[Symbol] = Nil + val conMethodType = (classType: TypeRepr) => MethodType(Nil)((_: MethodType) => Nil, (_: MethodType) => classType) + + val javaAnnotSym = TypeRepr.of[JavaAnnot].typeSymbol + val scalaAnnotSym = TypeRepr.of[ScalaAnnotation].typeSymbol + + val javaAnnotationDef = Apply(Select(New(TypeIdent(javaAnnotSym)), javaAnnotSym.primaryConstructor), List(Literal(StringConstant("string in a java annotation")))) + val scalaAnnotationDef = Apply(Select(New(TypeIdent(scalaAnnotSym)), scalaAnnotSym.primaryConstructor), List(Literal(StringConstant("string in a scala annotation")))) + + val cls = Symbol.newClass( + Symbol.spliceOwner, + name, + parents = _ => List(TypeRepr.of[Object]), + decls, + selfType = None, + clsFlags = Flags.EmptyFlags, + clsPrivateWithin = Symbol.noSymbol, + clsAnnotations = List(javaAnnotationDef, scalaAnnotationDef), + conMethodType, + conFlags = Flags.EmptyFlags, + conPrivateWithin = Symbol.noSymbol, + conParamFlags = List(List()), + conParamPrivateWithins = List(List()) + ) + + val clsDef = ClassDef(cls, List(TypeTree.of[Object]), body = Nil) + val newCls = + Typed( + Apply( + Select(New(TypeIdent(cls)), cls.primaryConstructor), + Nil + ), + TypeTree.of[Any] + ) + + val res = Block(List(clsDef), newCls).asExpr + + Expr.ofTuple(res, Expr(res.show)) + + // { + // @JavaAnnot("string in a java annotation") @ScalaAnnotation("string in a scala annotation") class name() extends java.lang.Object + // (new name(): scala.Any) + // } +} diff --git a/tests/run-macros/newClassAnnotation/Test_2.scala b/tests/run-macros/newClassAnnotation/Test_2.scala new file mode 100644 index 000000000000..a824259eba09 --- /dev/null +++ b/tests/run-macros/newClassAnnotation/Test_2.scala @@ -0,0 +1,7 @@ +//> using options -experimental + +@main def Test = + val (cls, str) = makeClass("name") + println(str) + println(cls.getClass) + println(cls.getClass.getAnnotation(classOf[JavaAnnot]).value) diff --git a/tests/run-macros/newClassExtendsJavaClass.check b/tests/run-macros/newClassExtendsJavaClass.check new file mode 100644 index 000000000000..bd07b5536ebc --- /dev/null +++ b/tests/run-macros/newClassExtendsJavaClass.check @@ -0,0 +1,2 @@ +class Main_2$package$foo$1 +22 diff --git a/tests/run-macros/newClassExtendsJavaClass/JavaClass.java b/tests/run-macros/newClassExtendsJavaClass/JavaClass.java new file mode 100644 index 000000000000..8a02cf294a01 --- /dev/null +++ b/tests/run-macros/newClassExtendsJavaClass/JavaClass.java @@ -0,0 +1,9 @@ +class JavaClass { + T value; + public JavaClass(T value) { + this.value = value; + } + public T getT() { + return value; + } +} \ No newline at end of file diff --git a/tests/run-macros/newClassExtendsJavaClass/Macro_1.scala b/tests/run-macros/newClassExtendsJavaClass/Macro_1.scala new file mode 100644 index 000000000000..c342c78da796 --- /dev/null +++ b/tests/run-macros/newClassExtendsJavaClass/Macro_1.scala @@ -0,0 +1,24 @@ +//> using options -experimental + +import scala.quoted.* + +transparent inline def makeClass(inline name: String): JavaClass[Int] = ${ makeClassExpr('name) } +private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[JavaClass[Int]] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + val parents = List(TypeTree.of[JavaClass[Int]]) + def decls(cls: Symbol): List[Symbol] = Nil + + val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = _ => parents.map(_.tpe), decls, selfType = None, Flags.EmptyFlags, Symbol.noSymbol, List(("idx", TypeRepr.of[Int]))) + + val parentsWithSym = List(Apply(TypeApply(Select(New(TypeTree.of[JavaClass[Int]]), TypeRepr.of[JavaClass].typeSymbol.primaryConstructor), List(TypeTree.of[Int])), List(Ref(cls.fieldMember("idx"))))) + val clsDef = ClassDef(cls, parentsWithSym, body = Nil) + val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(Literal(IntConstant(22)))), TypeTree.of[JavaClass[Int]]) + + Block(List(clsDef), newCls).asExprOf[JavaClass[Int]] + // '{ + // class `name`(idx: Int) extends JavaClass[Int](idx) + // new `name`(22) + // } +} \ No newline at end of file diff --git a/tests/run-macros/newClassExtendsJavaClass/Main_2.scala b/tests/run-macros/newClassExtendsJavaClass/Main_2.scala new file mode 100644 index 000000000000..b41c22427c8e --- /dev/null +++ b/tests/run-macros/newClassExtendsJavaClass/Main_2.scala @@ -0,0 +1,7 @@ +//> using options -experimental + +@main def Test: Unit = { + val cls = makeClass("foo") + println(cls.getClass) + println(cls.getT()) +} diff --git a/tests/run-macros/newClassParams.check b/tests/run-macros/newClassParams.check new file mode 100644 index 000000000000..314cd012f3e5 --- /dev/null +++ b/tests/run-macros/newClassParams.check @@ -0,0 +1 @@ +Foo method call with (10, test) diff --git a/tests/run-macros/newClassParams/Macro_1.scala b/tests/run-macros/newClassParams/Macro_1.scala new file mode 100644 index 000000000000..6ec48e24ebe3 --- /dev/null +++ b/tests/run-macros/newClassParams/Macro_1.scala @@ -0,0 +1,42 @@ +//> using options -experimental + +import scala.quoted._ + +inline def makeClassAndCall(inline name: String, idx: Int, str: String): Unit = ${ makeClassAndCallExpr('name, 'idx, 'str) } +private def makeClassAndCallExpr(nameExpr: Expr[String], idxExpr: Expr[Int], strExpr: Expr[String])(using Quotes): Expr[Unit] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + + def decls(cls: Symbol): List[Symbol] = + List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]))) + val parents = List(TypeTree.of[Object]) + val cls = Symbol.newClass( + Symbol.spliceOwner, + name, + parents = _ => parents.map(_.tpe), + decls, + selfType = None, + clsFlags = Flags.EmptyFlags, + Symbol.noSymbol, + List(("idx", TypeRepr.of[Int]), ("str", TypeRepr.of[String])) + ) + + val fooSym = cls.declaredMethod("foo").head + val idxSym = cls.fieldMember("idx") + val strSym = cls.fieldMember("str") + val fooDef = DefDef(fooSym, argss => + Some('{println(s"Foo method call with (${${Ref(idxSym).asExpr}}, ${${Ref(strSym).asExpr}})")}.asTerm) + ) + val clsDef = ClassDef(cls, parents, body = List(fooDef)) + val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(idxExpr.asTerm, strExpr.asTerm)) + + Block(List(clsDef), Apply(Select(newCls, cls.methodMember("foo")(0)), Nil)).asExprOf[Unit] + + // '{ + // class `name`(idx: Int, str: String) { + // def foo() = println("Foo method call with ($idx, $str)") + // } + // new `name`(`idx`, `str`) + // } +} diff --git a/tests/run-macros/newClassParams/Test_2.scala b/tests/run-macros/newClassParams/Test_2.scala new file mode 100644 index 000000000000..64762f17b92f --- /dev/null +++ b/tests/run-macros/newClassParams/Test_2.scala @@ -0,0 +1,5 @@ +//> using options -experimental + +@main def Test: Unit = { + makeClassAndCall("bar", 10, "test") +} diff --git a/tests/run-macros/newClassParamsExtendsClassParams.check b/tests/run-macros/newClassParamsExtendsClassParams.check new file mode 100644 index 000000000000..86d844c7d00f --- /dev/null +++ b/tests/run-macros/newClassParamsExtendsClassParams.check @@ -0,0 +1,4 @@ +Calling Foo.foo with i = 22 +class Test_2$package$foo$1 +Calling Foo.foo with i = 22 +class Test_2$package$bar$1 diff --git a/tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala b/tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala new file mode 100644 index 000000000000..de35a4dfa206 --- /dev/null +++ b/tests/run-macros/newClassParamsExtendsClassParams/Macro_1.scala @@ -0,0 +1,29 @@ +//> using options -experimental + +import scala.quoted._ + +inline def makeClass(inline name: String): Foo = ${ makeClassExpr('name) } +private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Foo] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + val parents = List('{ new Foo(1) }.asTerm) + def decls(cls: Symbol): List[Symbol] = Nil + + val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = _ => parents.map(_.tpe), decls, selfType = None, Flags.EmptyFlags, Symbol.noSymbol, List(("idx", TypeRepr.of[Int]))) + + val parentsWithSym = List(Apply(Select(New(TypeTree.of[Foo]), TypeRepr.of[Foo].typeSymbol.primaryConstructor), List(Ref(cls.fieldMember("idx"))))) + val clsDef = ClassDef(cls, parentsWithSym, body = Nil) + val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(Literal(IntConstant(22)))), TypeTree.of[Foo]) + + Block(List(clsDef), newCls).asExprOf[Foo] + + // '{ + // class `name`(idx: Int) extends Foo(idx) + // new `name`(22) + // } +} + +class Foo(i: Int) { + def foo(): Unit = println(s"Calling Foo.foo with i = $i") +} diff --git a/tests/run-macros/newClassParamsExtendsClassParams/Test_2.scala b/tests/run-macros/newClassParamsExtendsClassParams/Test_2.scala new file mode 100644 index 000000000000..6e902825fdc6 --- /dev/null +++ b/tests/run-macros/newClassParamsExtendsClassParams/Test_2.scala @@ -0,0 +1,10 @@ +//> using options -experimental + +@main def Test: Unit = { + val foo: Foo = makeClass("foo") + foo.foo() + println(foo.getClass) + val bar: Foo = makeClass("bar") + bar.foo() + println(bar.getClass) +} diff --git a/tests/run-macros/newClassTraitAndAbstract.check b/tests/run-macros/newClassTraitAndAbstract.check new file mode 100644 index 000000000000..65e8c6435722 --- /dev/null +++ b/tests/run-macros/newClassTraitAndAbstract.check @@ -0,0 +1,14 @@ +class Test_2$package$anon$1 +{ + trait foo[A, B <: scala.Int](val param1: A, val param2: B) extends java.lang.Object + class anon() extends java.lang.Object with foo[java.lang.String, scala.Int]("a", 1) + + (new anon(): scala.Any) +} +class Test_2$package$anon$2 +{ + abstract class bar[A, B <: scala.Int](val param1: A, val param2: B) extends java.lang.Object + class anon() extends bar[java.lang.String, scala.Int]("a", 1) + + (new anon(): scala.Any) +} diff --git a/tests/run-macros/newClassTraitAndAbstract/Macro_1.scala b/tests/run-macros/newClassTraitAndAbstract/Macro_1.scala new file mode 100644 index 000000000000..56cd320085db --- /dev/null +++ b/tests/run-macros/newClassTraitAndAbstract/Macro_1.scala @@ -0,0 +1,99 @@ +//> using options -experimental + +import scala.quoted.* + +transparent inline def makeTrait(inline name: String): Any = ${ makeTraitExpr('name) } +transparent inline def makeAbstractClass(inline name: String): Any = ${ makeAbstractClassExpr('name) } + +private def makeTraitExpr(name: Expr[String])(using Quotes): Expr[Any] = { + makeClassExpr(name, quotes.reflect.Flags.Trait, List(quotes.reflect.TypeTree.of[Object])) + // '{ + // trait `name`[A, B <: Int](param1: A, param2: B) + // class anon() extends `name`[String, Int]("a", 1) + // new $anon() + // } +} + +private def makeAbstractClassExpr(name: Expr[String])(using Quotes): Expr[Any] = { + makeClassExpr(name, quotes.reflect.Flags.Abstract, Nil) + // '{ + // abstract class `name`[A, B <: Int](param1: A, param2: B) + // class anon() extends `name`[String, Int]("a", 1) + // new $anon() + // } +} + +private def makeClassExpr(using Quotes)( + nameExpr: Expr[String], clsFlags: quotes.reflect.Flags, childExtraParents: List[quotes.reflect.TypeTree] + ): Expr[Any] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + def decls(cls: Symbol): List[Symbol] = Nil + val conMethodType = + (classType: TypeRepr) => PolyType(List("A", "B"))( + _ => List(TypeBounds.empty, TypeBounds.upper(TypeRepr.of[Int])), + polyType => MethodType(List("param1", "param2"))((_: MethodType) => List(polyType.param(0), polyType.param(1)), (_: MethodType) => + AppliedType(classType, List(polyType.param(0), polyType.param(1))) + ) + ) + + val traitSymbol = Symbol.newClass( + Symbol.spliceOwner, + name, + parents = _ => List(TypeRepr.of[Object]), + decls, + selfType = None, + clsFlags, + clsPrivateWithin = Symbol.noSymbol, + clsAnnotations = Nil, + conMethodType, + conFlags = Flags.EmptyFlags, + conPrivateWithin = Symbol.noSymbol, + conParamFlags = List(List(Flags.EmptyFlags, Flags.EmptyFlags), List(Flags.EmptyFlags, Flags.EmptyFlags)), + conParamPrivateWithins = List(List(Symbol.noSymbol, Symbol.noSymbol), List(Symbol.noSymbol, Symbol.noSymbol)) + ) + val traitDef = ClassDef(traitSymbol, List(TypeTree.of[Object]), body = Nil) + + val traitTypeTree = Applied(TypeIdent(traitSymbol), List(TypeTree.of[String], TypeTree.of[Int])) + val clsSymbol = Symbol.newClass( + Symbol.spliceOwner, + "anon", + parents = _ => childExtraParents.map(_.tpe) ++ List(traitTypeTree.tpe), + decls = _ => Nil, + selfType = None, + clsFlags = Flags.EmptyFlags, + clsPrivateWithin = Symbol.noSymbol, + clsAnnotations = Nil, + conMethodType = (classType: TypeRepr) => MethodType(Nil)(_ => Nil, _ => classType), + conFlags = Flags.EmptyFlags, + conPrivateWithin = Symbol.noSymbol, + conParamFlags = List(List()), + conParamPrivateWithins = List(List(Symbol.noSymbol, Symbol.noSymbol), List(Symbol.noSymbol, Symbol.noSymbol)) + ) + val obj = '{new java.lang.Object()}.asTerm match + case Inlined(_, _, term) => term + + val parentsWithSym = childExtraParents ++ List( + Apply( + TypeApply( + Select(New(traitTypeTree), traitSymbol.primaryConstructor), + List(TypeTree.of[String], TypeTree.of[Int]) + ), + List(Expr("a").asTerm, Expr(1).asTerm) + ) + ) + val clsDef = ClassDef(clsSymbol, parentsWithSym, body = Nil) + + val newCls = + Typed( + Apply( + Select(New(TypeIdent(clsSymbol)), clsSymbol.primaryConstructor), + Nil + ), + TypeTree.of[Any] + ) + val res = Block(List(traitDef), Block(List(clsDef), newCls)).asExpr + + Expr.ofTuple(res, Expr(res.show)) +} diff --git a/tests/run-macros/newClassTraitAndAbstract/Test_2.scala b/tests/run-macros/newClassTraitAndAbstract/Test_2.scala new file mode 100644 index 000000000000..5b6512a49010 --- /dev/null +++ b/tests/run-macros/newClassTraitAndAbstract/Test_2.scala @@ -0,0 +1,11 @@ +//> using options -experimental + +@main def Test: Unit = { + val (cls1, show1) = makeTrait("foo") + println(cls1.getClass) + println(show1) + + val (cls2, show2) = makeAbstractClass("bar") + println(cls2.getClass) + println(show2) +} diff --git a/tests/run-macros/newClassTypeParams.check b/tests/run-macros/newClassTypeParams.check new file mode 100644 index 000000000000..8c400039681c --- /dev/null +++ b/tests/run-macros/newClassTypeParams.check @@ -0,0 +1,6 @@ +class Test_2$package$foo$1 +{ + class foo[A, B <: scala.Int](val param1: A, val param2: B) extends java.lang.Object + + (new foo[java.lang.String, scala.Int]("test", 1): scala.Any) +} diff --git a/tests/run-macros/newClassTypeParams/Macro_1.scala b/tests/run-macros/newClassTypeParams/Macro_1.scala new file mode 100644 index 000000000000..2efb31610c73 --- /dev/null +++ b/tests/run-macros/newClassTypeParams/Macro_1.scala @@ -0,0 +1,53 @@ +//> using options -experimental + +import scala.quoted.* + +transparent inline def makeClass(inline name: String): Any = ${ makeClassExpr('name) } +private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Any] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + def decls(cls: Symbol): List[Symbol] = Nil + val conMethodType = + (classType: TypeRepr) => PolyType(List("A", "B"))( + _ => List(TypeBounds.empty, TypeBounds.upper(TypeRepr.of[Int])), + polyType => MethodType(List("param1", "param2"))((_: MethodType) => List(polyType.param(0), polyType.param(1)), (_: MethodType) => + AppliedType(classType, List(polyType.param(0), polyType.param(1))) + ) + ) + + val cls = Symbol.newClass( + Symbol.spliceOwner, + name, + parents = _ => List(TypeRepr.of[Object]), + decls, + selfType = None, + clsFlags = Flags.EmptyFlags, + clsPrivateWithin = Symbol.noSymbol, + clsAnnotations = Nil, + conMethodType, + conFlags = Flags.EmptyFlags, + conPrivateWithin = Symbol.noSymbol, + conParamFlags = List(List(Flags.EmptyFlags, Flags.EmptyFlags), List(Flags.EmptyFlags, Flags.EmptyFlags)), + conParamPrivateWithins = List(List(Symbol.noSymbol, Symbol.noSymbol), List(Symbol.noSymbol, Symbol.noSymbol)) + ) + + val clsDef = ClassDef(cls, List(TypeTree.of[Object]), body = Nil) + val newCls = + Typed( + Apply( + TypeApply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(TypeTree.of[String], TypeTree.of[Int])), + List(Expr("test").asTerm, Expr(1).asTerm) + ), + TypeTree.of[Any] + ) + + val res = Block(List(clsDef), newCls).asExpr + + Expr.ofTuple(res, Expr(res.show)) + + // '{ + // class `name`[A, B <: Int](param1: A, param2: B) + // new `name`[String, Int]("a", 1) + // } +} diff --git a/tests/run-macros/newClassTypeParams/Test_2.scala b/tests/run-macros/newClassTypeParams/Test_2.scala new file mode 100644 index 000000000000..742c0f480a77 --- /dev/null +++ b/tests/run-macros/newClassTypeParams/Test_2.scala @@ -0,0 +1,7 @@ +//> using options -experimental + +@main def Test: Unit = { + val (cls, show) = makeClass("foo") + println(cls.getClass) + println(show) +} diff --git a/tests/run-macros/newClassTypeParamsDoc.check b/tests/run-macros/newClassTypeParamsDoc.check new file mode 100644 index 000000000000..5d3b08bf509c --- /dev/null +++ b/tests/run-macros/newClassTypeParamsDoc.check @@ -0,0 +1,2 @@ +Calling getParam +test diff --git a/tests/run-macros/newClassTypeParamsDoc/Macro_1.scala b/tests/run-macros/newClassTypeParamsDoc/Macro_1.scala new file mode 100644 index 000000000000..9fb81e6f7348 --- /dev/null +++ b/tests/run-macros/newClassTypeParamsDoc/Macro_1.scala @@ -0,0 +1,67 @@ +//> using options -experimental + +import scala.quoted.* + +transparent inline def makeClass(): Any = ${ makeClassExpr } +private def makeClassExpr(using Quotes): Expr[Any] = { + import quotes.reflect.* + + val name = "myClass" + def decls(cls: Symbol): List[Symbol] = + List(Symbol.newMethod(cls, "getParam", MethodType(Nil)(_ => Nil, _ => cls.typeMember("T").typeRef))) + val conMethodType = + (classType: TypeRepr) => PolyType(List("T"))(_ => List(TypeBounds.empty), polyType => + MethodType(List("param"))((_: MethodType) => List(polyType.param(0)), (_: MethodType) => + AppliedType(classType, List(polyType.param(0))) + ) + ) + val cls = Symbol.newClass( + Symbol.spliceOwner, + name, + parents = _ => List(TypeRepr.of[Object]), + decls, + selfType = None, + clsFlags = Flags.EmptyFlags, + clsPrivateWithin = Symbol.noSymbol, + clsAnnotations = Nil, + conMethodType, + conFlags = Flags.EmptyFlags, + conPrivateWithin = Symbol.noSymbol, + conParamFlags = List(List(Flags.EmptyFlags), List(Flags.EmptyFlags)), + conParamPrivateWithins = List(List(Symbol.noSymbol), List(Symbol.noSymbol)) + ) + + val getParamSym = cls.declaredMethod("getParam").head + def getParamRhs(): Option[Term] = + val paramValue = This(cls).select(cls.fieldMember("param")).asExpr + Some('{ println("Calling getParam"); $paramValue }.asTerm) + val getParamDef = DefDef(getParamSym, _ => getParamRhs()) + + val clsDef = ClassDef(cls, List(TypeTree.of[Object]), body = List(getParamDef)) + val appliedTypeTree = Applied(TypeIdent(cls), List(TypeTree.of[String])) + val newCls = + Typed( + Apply( + Select( + Apply( + TypeApply(Select(New(appliedTypeTree), cls.primaryConstructor), List(TypeTree.of[String])), + List(Expr("test").asTerm) + ), + cls.methodMember("getParam").head + ), + Nil + ), + TypeTree.of[String] + ) + + Block(List(clsDef), newCls).asExpr + + // '{ + // class myClass[T](val param: T) { + // def getParam(): T = + // println("Calling getParam") + // param + // } + // new myClass[String]("test").getParam() + // } +} diff --git a/tests/run-macros/newClassTypeParamsDoc/Test_2.scala b/tests/run-macros/newClassTypeParamsDoc/Test_2.scala new file mode 100644 index 000000000000..318db32242fc --- /dev/null +++ b/tests/run-macros/newClassTypeParamsDoc/Test_2.scala @@ -0,0 +1,5 @@ +//> using options -experimental + +@main def Test: Unit = { + println(makeClass()) +} diff --git a/tests/run-macros/summonIgnoring-nonrecursive.check b/tests/run-macros/summonIgnoring-nonrecursive.check new file mode 100644 index 000000000000..643b49550f62 --- /dev/null +++ b/tests/run-macros/summonIgnoring-nonrecursive.check @@ -0,0 +1,3 @@ +TC[C2] generated in macro using: +TC2[_] generated in macro using: +TC[C1] generated in macro diff --git a/tests/run-macros/summonIgnoring-nonrecursive/Macro_1.scala b/tests/run-macros/summonIgnoring-nonrecursive/Macro_1.scala new file mode 100644 index 000000000000..b34a9a878483 --- /dev/null +++ b/tests/run-macros/summonIgnoring-nonrecursive/Macro_1.scala @@ -0,0 +1,50 @@ +//> using options -experimental +import scala.quoted._ +class C1 +trait TC[T] { + def print(): Unit +} + +object TC { + implicit transparent inline def auto[T]: TC[T] = ${autoImpl[T]} + def autoImpl[T: Type](using Quotes): Expr[TC[T]] = + import quotes.reflect._ + if (TypeRepr.of[T].typeSymbol == Symbol.classSymbol("C1")){ + '{ + new TC[T] { + def print() = { + println("TC[C1] generated in macro") + } + } + } + } else { + Expr.summonIgnoring[TC2[C1]](Symbol.classSymbol("TC").companionModule.methodMember("auto")*) match + case Some(a) => + '{ + new TC[T] { + def print(): Unit = + println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro using:") + $a.print() + } + } + case None => + '{ + new TC[T]{ + def print(): Unit = + println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro without TC2[_]") + } + } + } +} + +trait TC2[T] { + def print(): Unit +} + +object TC2 { + implicit def auto2[T](using tc: TC[T]): TC2[T] = new TC2[T] { + def print(): Unit = + println(s"TC2[_] generated in macro using:") + tc.print() + } +} diff --git a/tests/run-macros/summonIgnoring-nonrecursive/Test_2.scala b/tests/run-macros/summonIgnoring-nonrecursive/Test_2.scala new file mode 100644 index 000000000000..abd947d3e7fb --- /dev/null +++ b/tests/run-macros/summonIgnoring-nonrecursive/Test_2.scala @@ -0,0 +1,6 @@ +//> using options -experimental + +@main def Test(): Unit = { + class C2 + summon[TC[C2]].print() +} diff --git a/tests/run-macros/summonIgnoring.check b/tests/run-macros/summonIgnoring.check new file mode 100644 index 000000000000..5369c42c4888 --- /dev/null +++ b/tests/run-macros/summonIgnoring.check @@ -0,0 +1,5 @@ +No given in scope: +TC[C2] generated in macro without TC[C1] +Given in scope: +TC[C2] generated in macro using: +TC[C1] defined by a user diff --git a/tests/run-macros/summonIgnoring/Macro_1.scala b/tests/run-macros/summonIgnoring/Macro_1.scala new file mode 100644 index 000000000000..e4771588ce4e --- /dev/null +++ b/tests/run-macros/summonIgnoring/Macro_1.scala @@ -0,0 +1,38 @@ +//> using options -experimental +import scala.quoted._ +class C1 +trait TC[T] { + def print(): Unit +} +object TC { + implicit transparent inline def auto[T]: TC[T] = ${autoImpl[T]} + def autoImpl[T: Type](using Quotes): Expr[TC[T]] = + import quotes.reflect._ + if(TypeRepr.of[T].typeSymbol == Symbol.classSymbol("C1")){ + '{ + new TC[T] { + def print() = { + println("TC[C1] generated in macro") + } + } + } + } else { + Expr.summonIgnoring[TC[C1]](Symbol.classSymbol("TC").companionModule.methodMember("auto")*) match + case Some(a) => + '{ + new TC[T] { + def print(): Unit = + println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro using:") + $a.print() + } + } + case None => + '{ + new TC[T]{ + def print(): Unit = + println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro without TC[C1]") + } + } + } + +} diff --git a/tests/run-macros/summonIgnoring/Test_2.scala b/tests/run-macros/summonIgnoring/Test_2.scala new file mode 100644 index 000000000000..ca9007f269e2 --- /dev/null +++ b/tests/run-macros/summonIgnoring/Test_2.scala @@ -0,0 +1,15 @@ +//> using options -experimental + +@main def Test(): Unit = { + class C2 + println("No given in scope:") + summon[TC[C2]].print() + + { + println("Given in scope:") + given TC[C1] = new TC[C1] { + def print() = println("TC[C1] defined by a user") + } + summon[TC[C2]].print() + } +} diff --git a/tests/run-macros/tasty-extractors-2.check b/tests/run-macros/tasty-extractors-2.check index 5dd6af8d8b04..15d844670b7a 100644 --- a/tests/run-macros/tasty-extractors-2.check +++ b/tests/run-macros/tasty-extractors-2.check @@ -49,7 +49,7 @@ TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit") Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil)), None, List(DefDef("a", Nil, Inferred(), Some(Literal(IntConstant(0))))))), Literal(UnitConstant()))) TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit") -Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil), TypeSelect(Select(Ident("_root_"), "scala"), "Product"), TypeSelect(Select(Ident("_root_"), "scala"), "Serializable")), None, List(DefDef("hashCode", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_hashCode"), List(This(Some("Foo")))))), DefDef("equals", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Apply(Select(Apply(Select(This(Some("Foo")), "eq"), List(TypeApply(Select(Ident("x$0"), "$asInstanceOf$"), List(Inferred())))), "||"), List(Match(Ident("x$0"), List(CaseDef(Bind("x$0", Typed(Wildcard(), Inferred())), None, Apply(Select(Literal(BooleanConstant(true)), "&&"), List(Apply(Select(Ident("x$0"), "canEqual"), List(This(Some("Foo"))))))), CaseDef(Wildcard(), None, Literal(BooleanConstant(false))))))))), DefDef("toString", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_toString"), List(This(Some("Foo")))))), DefDef("canEqual", List(TermParamClause(List(ValDef("that", Inferred(), None)))), Inferred(), Some(TypeApply(Select(Ident("that"), "isInstanceOf"), List(Inferred())))), DefDef("productArity", Nil, Inferred(), Some(Literal(IntConstant(0)))), DefDef("productPrefix", Nil, Inferred(), Some(Literal(StringConstant("Foo")))), DefDef("productElement", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Wildcard(), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), ""), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("productElementName", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Wildcard(), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), ""), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("copy", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil))))), ValDef("Foo", TypeIdent("Foo$"), Some(Apply(Select(New(TypeIdent("Foo$")), ""), Nil))), ClassDef("Foo$", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil), Inferred()), Some(ValDef("_", Singleton(Ident("Foo")), None)), List(DefDef("apply", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil))), DefDef("unapply", List(TermParamClause(List(ValDef("x$1", Inferred(), None)))), Singleton(Literal(BooleanConstant(true))), Some(Literal(BooleanConstant(true)))), DefDef("toString", Nil, Inferred(), Some(Literal(StringConstant("Foo")))), TypeDef("MirroredMonoType", TypeBoundsTree(Inferred(), Inferred())), DefDef("fromProduct", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil)))))), Literal(UnitConstant()))) +Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil), TypeSelect(Select(Ident("_root_"), "scala"), "Product"), TypeSelect(Select(Ident("_root_"), "scala"), "Serializable")), None, List(DefDef("hashCode", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_hashCode"), List(This(Some("Foo")))))), DefDef("equals", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Apply(Select(Apply(Select(This(Some("Foo")), "eq"), List(TypeApply(Select(Ident("x$0"), "$asInstanceOf$"), List(Inferred())))), "||"), List(Match(Ident("x$0"), List(CaseDef(Bind("x$0", Typed(Wildcard(), Inferred())), None, Apply(Select(Literal(BooleanConstant(true)), "&&"), List(Apply(Select(Ident("x$0"), "canEqual"), List(This(Some("Foo"))))))), CaseDef(Wildcard(), None, Literal(BooleanConstant(false))))))))), DefDef("toString", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_toString"), List(This(Some("Foo")))))), DefDef("canEqual", List(TermParamClause(List(ValDef("that", Inferred(), None)))), Inferred(), Some(TypeApply(Select(Ident("that"), "isInstanceOf"), List(Inferred())))), DefDef("productArity", Nil, Inferred(), Some(Literal(IntConstant(0)))), DefDef("productPrefix", Nil, Inferred(), Some(Literal(StringConstant("Foo")))), DefDef("productElement", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Wildcard(), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), ""), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("productElementName", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Wildcard(), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), ""), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("copy", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil))))), ValDef("Foo", TypeIdent("Foo$"), Some(Apply(Select(New(TypeIdent("Foo$")), ""), Nil))), ClassDef("Foo$", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil), Inferred()), Some(ValDef("_", Singleton(Ident("Foo")), None)), List(DefDef("apply", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil))), DefDef("unapply", List(TermParamClause(List(ValDef("x$1", Inferred(), None)))), Singleton(Literal(BooleanConstant(true))), Some(Literal(BooleanConstant(true)))), DefDef("toString", Nil, Inferred(), Some(Literal(StringConstant("Foo")))), TypeDef("MirroredMonoType", TypeBoundsTree(Inferred(), Inferred())), DefDef("fromProduct", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Block(Nil, Apply(Select(New(Inferred()), ""), Nil))))))), Literal(UnitConstant()))) TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit") Inlined(None, Nil, Block(List(ClassDef("Foo1", DefDef("", List(TermParamClause(List(ValDef("a", TypeIdent("Int"), None)))), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil)), None, List(ValDef("a", Inferred(), None)))), Literal(UnitConstant()))) diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index 65e3a730ee7e..3e7a4868b0cb 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -32,16 +32,24 @@ val experimentalDefinitionInLibrary = Set( "scala.annotation.retainsCap", "scala.annotation.retainsArg", "scala.Pure", - "scala.caps", - "scala.caps$", + "scala.caps.CapSet", + "scala.caps.Capability", + "scala.caps.Contains", + "scala.caps.Contains$", + "scala.caps.Contains$.containsImpl", + "scala.caps.Exists", + "scala.caps.internal", + "scala.caps.internal$", + "scala.caps.cap", + "scala.caps.cap$", + "scala.caps.unsafe", + "scala.caps.unsafe$", + "scala.caps.use", //// New feature: into "scala.annotation.into", "scala.annotation.internal.$into", - //// New feature: @publicInBinary - "scala.annotation.publicInBinary", - //// New feature: Macro annotations "scala.annotation.MacroAnnotation", @@ -73,16 +81,14 @@ val experimentalDefinitionInLibrary = Set( "scala.quoted.Quotes.reflectModule.MethodTypeMethods.hasErasedParams", "scala.quoted.Quotes.reflectModule.TermParamClauseMethods.erasedArgs", "scala.quoted.Quotes.reflectModule.TermParamClauseMethods.hasErasedArgs", + "scala.quoted.Quotes.reflectModule.GivenSelectorModule.apply", + "scala.quoted.Quotes.reflectModule.OmitSelectorModule.apply", + "scala.quoted.Quotes.reflectModule.RenameSelectorModule.apply", + "scala.quoted.Quotes.reflectModule.SimpleSelectorModule.apply", // New feature: fromNullable for explicit nulls "scala.Predef$.fromNullable", - // New feature: named tuples - "scala.NamedTuple", - "scala.NamedTuple$", - "scala.NamedTupleDecomposition", - "scala.NamedTupleDecomposition$", - // New feature: modularity "scala.Precise", "scala.annotation.internal.WitnessNames", @@ -93,7 +99,10 @@ val experimentalDefinitionInLibrary = Set( "scala.quoted.runtime.Patterns$.higherOrderHoleWithTypes", // New feature: SIP 57 - runtimeChecked replacement of @unchecked - "scala.Predef$.runtimeChecked", "scala.annotation.internal.RuntimeChecked" + "scala.Predef$.runtimeChecked", "scala.annotation.internal.RuntimeChecked", + + // New feature: SIP 61 - @unroll annotation + "scala.annotation.unroll" ) diff --git a/tests/run/better-fors-map-elim.check b/tests/run/better-fors-map-elim.check new file mode 100644 index 000000000000..0ef3447a47c4 --- /dev/null +++ b/tests/run/better-fors-map-elim.check @@ -0,0 +1,4 @@ +MySome(()) +MySome(2) +MySome((2,3)) +MySome((2,(3,4))) diff --git a/tests/run/better-fors-map-elim.scala b/tests/run/better-fors-map-elim.scala new file mode 100644 index 000000000000..6f4db6573dec --- /dev/null +++ b/tests/run/better-fors-map-elim.scala @@ -0,0 +1,65 @@ +//> using options -preview +// import scala.language.experimental.betterFors + +class myOptionModule(doOnMap: => Unit) { + sealed trait MyOption[+A] { + def map[B](f: A => B): MyOption[B] = this match { + case MySome(x) => { + doOnMap + MySome(f(x)) + } + case MyNone => MyNone + } + def flatMap[B](f: A => MyOption[B]): MyOption[B] = this match { + case MySome(x) => f(x) + case MyNone => MyNone + } + } + case class MySome[A](x: A) extends MyOption[A] + case object MyNone extends MyOption[Nothing] + object MyOption { + def apply[A](x: A): MyOption[A] = MySome(x) + } +} + +object Test extends App { + + val myOption = new myOptionModule(println("map called")) + + import myOption.* + + def portablePrintMyOption(opt: MyOption[Any]): Unit = + if opt == MySome(()) then + println("MySome(())") + else + println(opt) + + val z = for { + a <- MyOption(1) + b <- MyOption(()) + } yield () + + portablePrintMyOption(z) + + val z2 = for { + a <- MyOption(1) + b <- MyOption(2) + } yield b + + portablePrintMyOption(z2) + + val z3 = for { + a <- MyOption(1) + (b, c) <- MyOption((2, 3)) + } yield (b, c) + + portablePrintMyOption(z3) + + val z4 = for { + a <- MyOption(1) + (b, (c, d)) <- MyOption((2, (3, 4))) + } yield (b, (c, d)) + + portablePrintMyOption(z4) + +} diff --git a/tests/run/better-fors.scala b/tests/run/better-fors.scala index 8c0bff230632..b0912aacd4dc 100644 --- a/tests/run/better-fors.scala +++ b/tests/run/better-fors.scala @@ -1,4 +1,5 @@ -import scala.language.experimental.betterFors +//> using options -preview +// import scala.language.experimental.betterFors def for1 = for { diff --git a/tests/run/exceptions-2.scala b/tests/run/exceptions-2.scala index ddb4c9c51ee1..73ca70eb5add 100644 --- a/tests/run/exceptions-2.scala +++ b/tests/run/exceptions-2.scala @@ -1,4 +1,4 @@ -// scalajs: --skip +// scalajs: --compliant-semantics /* * Try exception handling and finally blocks. diff --git a/tests/run/exceptions-nest.scala b/tests/run/exceptions-nest.scala index 2e58459fb9f7..cfaf09929a42 100644 --- a/tests/run/exceptions-nest.scala +++ b/tests/run/exceptions-nest.scala @@ -1,4 +1,4 @@ -// scalajs: --skip +// scalajs: --compliant-semantics object Test extends App { @@ -12,9 +12,12 @@ object Test extends App { try { println(test8) } catch { case _: Throwable => println("OK") } println(test9) println(test10) - println(test11) + portablePrintln(test11) println(test12) + def portablePrintln(x: Any): Unit = + println(if (x == ()) "()" else x) + def test1 = { var x = 1 try { diff --git a/tests/run/extra-implicits.scala b/tests/run/extra-implicits.scala index 62ff862c709f..45978341556c 100644 --- a/tests/run/extra-implicits.scala +++ b/tests/run/extra-implicits.scala @@ -1,18 +1,18 @@ case class A(x: String) case class B(x: String) -given a1: A("default") -given b1: B("default") -val a2 = A("explicit") -val b2 = B("explicit") +given A("default") +given B("default") +val a = A("explicit") +val b = B("explicit") def f(using a: A, b: B): Unit = println(a) println(b) @main def Test = - f(using a2) - f(using a = a2) - f(using b = b2) - f(using b = b2, a = a2) + f(using a) + f(using a = a) + f(using b = b) + f(using b = b, a = a) diff --git a/tests/run/fors.scala b/tests/run/fors.scala index a12d0e977157..4e802af4c53d 100644 --- a/tests/run/fors.scala +++ b/tests/run/fors.scala @@ -1,3 +1,4 @@ +//> using options -preview //############################################################################ // for-comprehensions (old and new syntax) //############################################################################ @@ -113,8 +114,6 @@ object Test extends App { /////////////////// elimination of map /////////////////// - import scala.language.experimental.betterFors - @tailrec def pair[B](xs: List[Int], ys: List[B], n: Int): List[(Int, B)] = if n == 0 then xs.zip(ys) diff --git a/tests/run/i13215.scala b/tests/run/i13215.scala index f43e9aa1e38a..738eb25d598a 100644 --- a/tests/run/i13215.scala +++ b/tests/run/i13215.scala @@ -1,4 +1,4 @@ -//> using options -experimental -Werror -WunstableInlineAccessors +//> using options -Werror -WunstableInlineAccessors import scala.annotation.publicInBinary diff --git a/tests/run/i22150.scala b/tests/run/i22150.scala index 80c2222a98e7..0a4be2047cd8 100644 --- a/tests/run/i22150.scala +++ b/tests/run/i22150.scala @@ -1,6 +1,3 @@ -//> using options -experimental -language:experimental.namedTuples -import language.experimental.namedTuples - val directionsNT = IArray( (dx = 0, dy = 1), // up (dx = 1, dy = 0), // right diff --git a/tests/run/i22497.check b/tests/run/i22497.check new file mode 100644 index 000000000000..7ef6ca89b9f4 --- /dev/null +++ b/tests/run/i22497.check @@ -0,0 +1,3 @@ +public Foo() +public Foo(int) +public Foo(java.lang.String) diff --git a/tests/run/i22497.scala b/tests/run/i22497.scala new file mode 100644 index 000000000000..723668db9750 --- /dev/null +++ b/tests/run/i22497.scala @@ -0,0 +1,11 @@ +// scalajs: --skip + +import scala.annotation.publicInBinary +import scala.annotation.experimental + +class Foo: + @publicInBinary private def this(i: Int) = this() + @publicInBinary protected def this(i: String) = this() + +@main def Test = + println(classOf[Foo].getConstructors().mkString("\n")) diff --git a/tests/run/i22498.scala b/tests/run/i22498.scala new file mode 100644 index 000000000000..839a73ecff88 --- /dev/null +++ b/tests/run/i22498.scala @@ -0,0 +1,8 @@ +import scala.annotation.publicInBinary + +class Foo: + @publicInBinary private def this(x: Int) = this() + inline def proxy: Foo = new Foo(0) + +@main def Test = + val x = (new Foo).proxy diff --git a/tests/run/i22900.check b/tests/run/i22900.check new file mode 100644 index 000000000000..f6636139a701 --- /dev/null +++ b/tests/run/i22900.check @@ -0,0 +1,8 @@ +6 +6 +6 +6 +7 +6 +7 +(6) diff --git a/tests/run/i22900.scala b/tests/run/i22900.scala new file mode 100644 index 000000000000..f7786d32d717 --- /dev/null +++ b/tests/run/i22900.scala @@ -0,0 +1,26 @@ +object NameBaseExtractor { + def unapply(x: Int): Some[(someName: Int)] = Some((someName = x + 3)) +} +object NameBaseExtractor2 { + def unapply(x: Int): Some[(someName: Int, age: Int)] = Some((someName = x + 3, age = x + 4)) +} +@main +def Test = + val x1 = 3 match + case NameBaseExtractor(someName = x) => x + println(x1) + val NameBaseExtractor(someName = x2) = 3 + println(x2) + val NameBaseExtractor((someName = x3)) = 3 + println(x3) + + val NameBaseExtractor2(someName = x4, age = x5) = 3 + println(x4) + println(x5) + + val NameBaseExtractor2((someName = x6, age = x7)) = 3 + println(x6) + println(x7) + + val NameBaseExtractor(y1) = 3 + println(y1) diff --git a/tests/run/i22900a.check b/tests/run/i22900a.check new file mode 100644 index 000000000000..a94217f352f9 --- /dev/null +++ b/tests/run/i22900a.check @@ -0,0 +1,3 @@ +3 +6 +3 diff --git a/tests/run/i22900a.scala b/tests/run/i22900a.scala new file mode 100644 index 000000000000..301deeecdf13 --- /dev/null +++ b/tests/run/i22900a.scala @@ -0,0 +1,15 @@ +case class C(someName: Int) + +object NameBaseExtractor3 { + def unapply(x: Int): Some[C] = Some(C(someName = x + 3)) +} + +@main +def Test = { + val C(someName = xx) = C(3) + println(xx) + val NameBaseExtractor3(C(someName = x)) = 3 + println(x) + C(3) match + case C(someName = xx) => println(xx) +} \ No newline at end of file diff --git a/tests/run/i8073.scala b/tests/run/i8073.scala new file mode 100644 index 000000000000..6b5bfc3b9832 --- /dev/null +++ b/tests/run/i8073.scala @@ -0,0 +1,86 @@ +import scala.deriving.Mirror + +trait A: + type B + +object Test: + case class CC(a: A, b: a.B) + + def test1(): Unit = + val generic = summon[Mirror.Of[CC]] + // No language syntax for type projection of a singleton type + // summon[generic.MirroredElemTypes =:= (A, CC#a.B)] + + val aa: A { type B = Int } = new A { type B = Int } + val x: CC { val a: aa.type } = CC(aa, 1).asInstanceOf[CC { val a: aa.type }] // manual `tracked` + + val dependent = summon[Mirror.Of[x.type]] + summon[dependent.MirroredElemTypes =:= (A, x.a.B)] + + assert(CC(aa, 1) == generic.fromProduct((aa, 1))) + assert(CC(aa, 1) == dependent.fromProduct((aa, 1))) + + x match + case CC(a, b) => + val a1: A = a + // Dependent pattern matching is not currently supported + // val b1: a1.B = b + val b1 = b // Type is CC#a.B + + end test1 + + case class CCPoly[T <: A](a: T, b: a.B) + + def test2(): Unit = + val generic = summon[Mirror.Of[CCPoly[A]]] + // No language syntax for type projection of a singleton type + // summon[generic.MirroredElemTypes =:= (A, CCPoly[A]#a.B)] + + val aa: A { type B = Int } = new A { type B = Int } + val x: CCPoly[aa.type] = CCPoly(aa, 1) + + val dependent = summon[Mirror.Of[x.type]] + summon[dependent.MirroredElemTypes =:= (aa.type, x.a.B)] + + assert(CCPoly[A](aa, 1) == generic.fromProduct((aa, 1))) + assert(CCPoly[A](aa, 1) == dependent.fromProduct((aa, 1))) + + x match + case CCPoly(a, b) => + val a1: A = a + // Dependent pattern matching is not currently supported + // val b1: a1.B = b + val b1 = b // Type is CC#a.B + + end test2 + + enum Enum: + case EC(a: A, b: a.B) + + def test3(): Unit = + val generic = summon[Mirror.Of[Enum.EC]] + // No language syntax for type projection of a singleton type + // summon[generic.MirroredElemTypes =:= (A, Enum.EC#a.B)] + + val aa: A { type B = Int } = new A { type B = Int } + val x: Enum.EC { val a: aa.type } = Enum.EC(aa, 1).asInstanceOf[Enum.EC { val a: aa.type }] // manual `tracked` + + val dependent = summon[Mirror.Of[x.type]] + summon[dependent.MirroredElemTypes =:= (A, x.a.B)] + + assert(Enum.EC(aa, 1) == generic.fromProduct((aa, 1))) + assert(Enum.EC(aa, 1) == dependent.fromProduct((aa, 1))) + + x match + case Enum.EC(a, b) => + val a1: A = a + // Dependent pattern matching is not currently supported + // val b1: a1.B = b + val b1 = b // Type is Enum.EC#a.B + + end test3 + + def main(args: Array[String]): Unit = + test1() + test2() + test3() diff --git a/tests/run/i8073b.scala b/tests/run/i8073b.scala new file mode 100644 index 000000000000..cc85731d01df --- /dev/null +++ b/tests/run/i8073b.scala @@ -0,0 +1,86 @@ +import scala.deriving.Mirror + +trait A: + type B + +// Test local mirrors +@main def Test = + case class CC(a: A, b: a.B) + + def test1(): Unit = + val generic = summon[Mirror.Of[CC]] + // No language syntax for type projection of a singleton type + // summon[generic.MirroredElemTypes =:= (A, CC#a.B)] + + val aa: A { type B = Int } = new A { type B = Int } + val x: CC { val a: aa.type } = CC(aa, 1).asInstanceOf[CC { val a: aa.type }] // manual `tracked` + + val dependent = summon[Mirror.Of[x.type]] + summon[dependent.MirroredElemTypes =:= (A, x.a.B)] + + assert(CC(aa, 1) == generic.fromProduct((aa, 1))) + assert(CC(aa, 1) == dependent.fromProduct((aa, 1))) + + x match + case CC(a, b) => + val a1: A = a + // Dependent pattern matching is not currently supported + // val b1: a1.B = b + val b1 = b // Type is CC#a.B + + end test1 + + case class CCPoly[T <: A](a: T, b: a.B) + + def test2(): Unit = + val generic = summon[Mirror.Of[CCPoly[A]]] + // No language syntax for type projection of a singleton type + // summon[generic.MirroredElemTypes =:= (A, CCPoly[A]#a.B)] + + val aa: A { type B = Int } = new A { type B = Int } + val x: CCPoly[aa.type] = CCPoly(aa, 1) + + val dependent = summon[Mirror.Of[x.type]] + summon[dependent.MirroredElemTypes =:= (aa.type, x.a.B)] + + assert(CCPoly[A](aa, 1) == generic.fromProduct((aa, 1))) + assert(CCPoly[A](aa, 1) == dependent.fromProduct((aa, 1))) + + x match + case CCPoly(a, b) => + val a1: A = a + // Dependent pattern matching is not currently supported + // val b1: a1.B = b + val b1 = b // Type is CC#a.B + + end test2 + + enum Enum: + case EC(a: A, b: a.B) + + def test3(): Unit = + val generic = summon[Mirror.Of[Enum.EC]] + // No language syntax for type projection of a singleton type + // summon[generic.MirroredElemTypes =:= (A, Enum.EC#a.B)] + + val aa: A { type B = Int } = new A { type B = Int } + val x: Enum.EC { val a: aa.type } = Enum.EC(aa, 1).asInstanceOf[Enum.EC { val a: aa.type }] // manual `tracked` + + val dependent = summon[Mirror.Of[x.type]] + summon[dependent.MirroredElemTypes =:= (A, x.a.B)] + + assert(Enum.EC(aa, 1) == generic.fromProduct((aa, 1))) + assert(Enum.EC(aa, 1) == dependent.fromProduct((aa, 1))) + + x match + case Enum.EC(a, b) => + val a1: A = a + // Dependent pattern matching is not currently supported + // val b1: a1.B = b + val b1 = b // Type is Enum.EC#a.B + + end test3 + + test1() + test2() + test3() diff --git a/tests/run/named-patmatch.scala b/tests/run/named-patmatch.scala index e62497e4aa8f..6fe1934f008e 100644 --- a/tests/run/named-patmatch.scala +++ b/tests/run/named-patmatch.scala @@ -1,5 +1,4 @@ import annotation.experimental -import language.experimental.namedTuples @main def Test = locally: diff --git a/tests/run/named-patterns.scala b/tests/run/named-patterns.scala index 7c24dc8d683a..e92bbf751c22 100644 --- a/tests/run/named-patterns.scala +++ b/tests/run/named-patterns.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples object Test1: class Person(val name: String, val age: Int) diff --git a/tests/run/named-tuple-ops.scala b/tests/run/named-tuple-ops.scala index 076ab5028c6c..8c6db6f2fa1c 100644 --- a/tests/run/named-tuple-ops.scala +++ b/tests/run/named-tuple-ops.scala @@ -1,5 +1,4 @@ //> using options -source future -import language.experimental.namedTuples import scala.compiletime.asMatchable type City = (name: String, zip: Int, pop: Int) diff --git a/tests/run/named-tuples-mirror.check b/tests/run/named-tuples-mirror.check new file mode 100644 index 000000000000..e6656d280fe6 --- /dev/null +++ b/tests/run/named-tuples-mirror.check @@ -0,0 +1,4 @@ +NamedTuple +List(foo: Int, bla: String) +15 +test diff --git a/tests/run/named-tuples-mirror.scala b/tests/run/named-tuples-mirror.scala new file mode 100644 index 000000000000..02706e942225 --- /dev/null +++ b/tests/run/named-tuples-mirror.scala @@ -0,0 +1,28 @@ +import scala.deriving.* +import scala.compiletime.* + +type ToString[T] = T match + case Int => "Int" + case String => "String" + +inline def showLabelsAndTypes[Types <: Tuple, Labels <: Tuple]: List[String] = + inline erasedValue[Types] match { + case _: (tpe *: types) => + inline erasedValue[Labels] match { + case _: (label *: labels) => + val labelStr = constValue[label] + val tpeStr = constValue[ToString[tpe]] + s"$labelStr: $tpeStr" :: showLabelsAndTypes[types, labels] + } + case _: EmptyTuple => + Nil +} + +@main def Test = + val mirror = summon[Mirror.Of[(foo: Int, bla: String)]] + println(constValue[mirror.MirroredLabel]) + println(showLabelsAndTypes[mirror.MirroredElemTypes, mirror.MirroredElemLabels]) + + val namedTuple = summon[Mirror.Of[(foo: Int, bla: String)]].fromProduct((15, "test")) + println(namedTuple.foo) + println(namedTuple.bla) diff --git a/tests/run/named-tuples-xxl.scala b/tests/run/named-tuples-xxl.scala index 3a0a1e5e1294..8c831fb1d223 100644 --- a/tests/run/named-tuples-xxl.scala +++ b/tests/run/named-tuples-xxl.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples import NamedTuple.toTuple type Person = ( diff --git a/tests/run/named-tuples.scala b/tests/run/named-tuples.scala index 406c6195cf0f..c99393a403b3 100644 --- a/tests/run/named-tuples.scala +++ b/tests/run/named-tuples.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples import NamedTuple.* type Person = (name: String, age: Int) diff --git a/tests/run/noProtectedSuper.scala b/tests/run/noProtectedSuper.scala index d05c13d90c9f..41b0615d12ab 100644 --- a/tests/run/noProtectedSuper.scala +++ b/tests/run/noProtectedSuper.scala @@ -1,5 +1,3 @@ -//> using options -experimental - import scala.annotation.publicInBinary package p { diff --git a/tests/run/pkgobjvals.check b/tests/run/pkgobjvals.check new file mode 100644 index 000000000000..3e327fcc0c3e --- /dev/null +++ b/tests/run/pkgobjvals.check @@ -0,0 +1,4 @@ +Foo was created +Foo was created +Foo was created +Foo was created diff --git a/tests/run/pkgobjvals.scala b/tests/run/pkgobjvals.scala new file mode 100644 index 000000000000..8df1a984642c --- /dev/null +++ b/tests/run/pkgobjvals.scala @@ -0,0 +1,22 @@ +import language.experimental.packageObjectValues + +package a: + package object b: + class Foo: + println("Foo was created") + + def foo() = Foo() + end b + + def test = + val bb = b + bb.foo() + new bb.Foo() +end a + +@main def Test = + a.test + val ab: a.b.type = a.b + ab.foo() + new ab.Foo() + diff --git a/tests/run/publicInBinary/Lib_1.scala b/tests/run/publicInBinary/Lib_1.scala index e7b5a0780d1c..b6db126b7d82 100644 --- a/tests/run/publicInBinary/Lib_1.scala +++ b/tests/run/publicInBinary/Lib_1.scala @@ -1,4 +1,4 @@ -//> using options -experimental -Werror -WunstableInlineAccessors +//> using options -Werror -WunstableInlineAccessors package foo diff --git a/tests/run/t8601d.scala b/tests/run/t8601d.scala index a6962847cb83..6e022de90ab8 100644 --- a/tests/run/t8601d.scala +++ b/tests/run/t8601d.scala @@ -1,3 +1,5 @@ +// scalajs: --compliant-semantics + object Test { def monitor(x: AnyRef): Unit = {x.synchronized(()); ()} def check(x: => Any) = try { x; sys.error("failed to throw NPE") } catch { case _: NullPointerException => } diff --git a/tests/run/tyql.scala b/tests/run/tyql.scala index 8fe253b559ac..ee3fd1138265 100644 --- a/tests/run/tyql.scala +++ b/tests/run/tyql.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples import NamedTuple.{NamedTuple, AnyNamedTuple} /* This is a demonstrator that shows how to map regular for expressions to diff --git a/tests/run/unroll-caseclass-integration.jvm.check b/tests/run/unroll-caseclass-integration.jvm.check new file mode 100644 index 000000000000..0418867c80c6 --- /dev/null +++ b/tests/run/unroll-caseclass-integration.jvm.check @@ -0,0 +1,34 @@ +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "hello31337" + "true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "hello31337false" + "0" +Assertion passed: found "hello31337false12345" + +public unroll.Unrolled(java.lang.String,int) +public unroll.Unrolled(java.lang.String,int,boolean) +public unroll.Unrolled(java.lang.String,int,boolean,long) +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false9" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false9" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false9" diff --git a/tests/run/unroll-caseclass-integration.scala-js.check b/tests/run/unroll-caseclass-integration.scala-js.check new file mode 100644 index 000000000000..dbd2d93b08dd --- /dev/null +++ b/tests/run/unroll-caseclass-integration.scala-js.check @@ -0,0 +1,30 @@ +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "hello31337" + "true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "hello31337false" + "0" +Assertion passed: found "hello31337false12345" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false9" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false9" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false9" diff --git a/tests/run/unroll-caseclass-integration/TestUtils_1.scala b/tests/run/unroll-caseclass-integration/TestUtils_1.scala new file mode 100644 index 000000000000..e639a54d924b --- /dev/null +++ b/tests/run/unroll-caseclass-integration/TestUtils_1.scala @@ -0,0 +1,13 @@ +//> using options -experimental +package unroll + +object TestUtils { + def logAssertStartsWith(actual: String, expected: String): Unit = { + assert(actual.startsWith(expected)) + val suffix = { + val suffix0 = actual.stripPrefix(expected) + if (suffix0.isEmpty) "" else s""" + "$suffix0"""" + } + println(s"""Assertion passed: found "$expected"$suffix""") + } +} diff --git a/tests/run/unroll-caseclass-integration/Test_4.scala b/tests/run/unroll-caseclass-integration/Test_4.scala new file mode 100644 index 000000000000..dc60b5bf2059 --- /dev/null +++ b/tests/run/unroll-caseclass-integration/Test_4.scala @@ -0,0 +1,7 @@ +//> using options -experimental +import unroll.* + +@main def Test: Unit = + UnrollTestMainV1.main(Array.empty[String]) + UnrollTestMainV2.main(Array.empty[String]) + UnrollTestMainV3.main(Array.empty[String]) diff --git a/tests/run/unroll-caseclass-integration/UnrollTestMain_1.scala b/tests/run/unroll-caseclass-integration/UnrollTestMain_1.scala new file mode 100644 index 000000000000..45ce6a768f2a --- /dev/null +++ b/tests/run/unroll-caseclass-integration/UnrollTestMain_1.scala @@ -0,0 +1,25 @@ +//> using options -experimental +package unroll +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV1{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2") + + logAssertStartsWith(Unrolled("cow").foo, "cow1") + logAssertStartsWith(Unrolled("cow", 2).foo, "cow2") + + val unrolled = Unrolled("cow") + + logAssertStartsWith(unrolled.copy(s = "cow").foo, "cow1") + logAssertStartsWith(unrolled.copy(s = "cow", n = 2).foo, "cow2") + + val Unrolled(s, n) = unrolled + + assert(s == "cow") + assert(n == 1) + + UnrollTestScalaSpecificV1.test() + } +} diff --git a/tests/run/unroll-caseclass-integration/UnrollTestMain_2.scala b/tests/run/unroll-caseclass-integration/UnrollTestMain_2.scala new file mode 100644 index 000000000000..f6cfbfc54a2c --- /dev/null +++ b/tests/run/unroll-caseclass-integration/UnrollTestMain_2.scala @@ -0,0 +1,30 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV2{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1true") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2true") + logAssertStartsWith(new Unrolled("cow", 2, false).foo, "cow2false") + + logAssertStartsWith(Unrolled("cow").foo, "cow1true") + logAssertStartsWith(Unrolled("cow", 2).foo, "cow2true") + logAssertStartsWith(Unrolled("cow", 2, false).foo, "cow2false") + + val unrolled = Unrolled("cow") + + logAssertStartsWith(unrolled.copy(s = "cow").foo, "cow1true") + logAssertStartsWith(unrolled.copy(s = "cow", n = 2).foo, "cow2true") + logAssertStartsWith(unrolled.copy(s = "cow", n = 2, b = false).foo, "cow2false") + + val Unrolled(s, n, b) = unrolled + + assert(s == "cow") + assert(n == 1) + assert(b == true) + + UnrollTestScalaSpecificV2.test() + } +} diff --git a/tests/run/unroll-caseclass-integration/UnrollTestMain_3.scala b/tests/run/unroll-caseclass-integration/UnrollTestMain_3.scala new file mode 100644 index 000000000000..b0e4d7a5d25a --- /dev/null +++ b/tests/run/unroll-caseclass-integration/UnrollTestMain_3.scala @@ -0,0 +1,36 @@ +//> using options -experimental +package unroll +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV3{ + def main(args: Array[String]): Unit = { + UnrollTestScalaSpecificV3() + UnrollTestPlatformSpecificV3() + + logAssertStartsWith(new Unrolled("cow").foo, "cow1true0") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2true0") + logAssertStartsWith(new Unrolled("cow", 2, false).foo, "cow2false0") + logAssertStartsWith(new Unrolled("cow", 2, false, 9L).foo, "cow2false9") + + logAssertStartsWith(Unrolled("cow").foo, "cow1true0") + logAssertStartsWith(Unrolled("cow", 2).foo, "cow2true0") + logAssertStartsWith(Unrolled("cow", 2, false).foo, "cow2false0") + logAssertStartsWith(Unrolled("cow", 2, false, 9L).foo, "cow2false9") + + val unrolled = Unrolled("cow") + + logAssertStartsWith(unrolled.copy(s = "cow").foo, "cow1true0") + logAssertStartsWith(unrolled.copy(s = "cow", n = 2).foo, "cow2true0") + logAssertStartsWith(unrolled.copy(s = "cow", n = 2, b = false).foo, "cow2false0") + logAssertStartsWith(unrolled.copy(s = "cow", n = 2, b = false, l = 9L).foo, "cow2false9") + + val Unrolled(s, n, b, l) = unrolled + + assert(s == "cow") + assert(n == 1) + assert(b == true) + assert(l == 0L) + + + } +} diff --git a/tests/run/unroll-caseclass-integration/UnrollTestPlatformSpecificJS_3.scala b/tests/run/unroll-caseclass-integration/UnrollTestPlatformSpecificJS_3.scala new file mode 100644 index 000000000000..190890788014 --- /dev/null +++ b/tests/run/unroll-caseclass-integration/UnrollTestPlatformSpecificJS_3.scala @@ -0,0 +1,9 @@ +//> using options -experimental +//> using target.platform scala-js +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + // do nothing for scala-js + } +} diff --git a/tests/run/unroll-caseclass-integration/UnrollTestPlatformSpecific_3.scala b/tests/run/unroll-caseclass-integration/UnrollTestPlatformSpecific_3.scala new file mode 100644 index 000000000000..0ec252f740f4 --- /dev/null +++ b/tests/run/unroll-caseclass-integration/UnrollTestPlatformSpecific_3.scala @@ -0,0 +1,35 @@ +//> using options -experimental +//> using target.platform jvm +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + val cls = classOf[Unrolled] + + assert(scala.util.Try(cls.getConstructor(classOf[String])).isFailure) + println() + assert( + cls.getConstructor(classOf[String], classOf[Int]) + .newInstance("hello", 2: Integer) + .asInstanceOf[Unrolled] + .foo == + "hello2true0" + ) + assert( + cls.getConstructor(classOf[String], classOf[Int], classOf[Boolean]) + .newInstance("hello", 2: Integer, java.lang.Boolean.FALSE) + .asInstanceOf[Unrolled] + .foo == + "hello2false0" + ) + assert( + cls.getConstructor(classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .newInstance("hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) + .asInstanceOf[Unrolled] + .foo == + "hello2false3" + ) + + cls.getConstructors.map(_.toString).sorted.foreach(println) + } +} diff --git a/tests/run/unroll-caseclass-integration/UnrollTestScalaSpecific_1.scala b/tests/run/unroll-caseclass-integration/UnrollTestScalaSpecific_1.scala new file mode 100644 index 000000000000..28eb2815e979 --- /dev/null +++ b/tests/run/unroll-caseclass-integration/UnrollTestScalaSpecific_1.scala @@ -0,0 +1,21 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestScalaSpecificV1{ + def test() = { + val unrolled = summon[scala.deriving.Mirror.Of[Unrolled]].fromProduct( + new Product{ + def canEqual(that: Any) = true + def productArity = 2 + def productElement(n: Int) = n match{ + case 0 => "hello" + case 1 => 31337 + } + } + ) + + logAssertStartsWith(unrolled.foo, "hello31337") + } +} diff --git a/tests/run/unroll-caseclass-integration/UnrollTestScalaSpecific_2.scala b/tests/run/unroll-caseclass-integration/UnrollTestScalaSpecific_2.scala new file mode 100644 index 000000000000..5d4079a093ce --- /dev/null +++ b/tests/run/unroll-caseclass-integration/UnrollTestScalaSpecific_2.scala @@ -0,0 +1,22 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestScalaSpecificV2{ + def test() = { + val unrolled = summon[scala.deriving.Mirror.Of[Unrolled]].fromProduct( + new Product { + def canEqual(that: Any) = true + def productArity = 3 + def productElement(n: Int) = n match { + case 0 => "hello" + case 1 => 31337 + case 2 => false + } + } + + ) + logAssertStartsWith(unrolled.foo, "hello31337false") + } +} \ No newline at end of file diff --git a/tests/run/unroll-caseclass-integration/UnrollTestScalaSpecific_3.scala b/tests/run/unroll-caseclass-integration/UnrollTestScalaSpecific_3.scala new file mode 100644 index 000000000000..46f84998baa0 --- /dev/null +++ b/tests/run/unroll-caseclass-integration/UnrollTestScalaSpecific_3.scala @@ -0,0 +1,21 @@ +//> using options -experimental +package unroll +import unroll.TestUtils.logAssertStartsWith +object UnrollTestScalaSpecificV3{ + def apply() = { + val unrolled = summon[scala.deriving.Mirror.Of[Unrolled]].fromProduct( + new Product { + def canEqual(that: Any) = true + def productArity = 4 + def productElement(n: Int) = n match { + case 0 => "hello" + case 1 => 31337 + case 2 => false + case 3 => 12345L + } + } + ) + + logAssertStartsWith(unrolled.foo, "hello31337false12345") + } +} \ No newline at end of file diff --git a/tests/run/unroll-caseclass-integration/Unrolled_1.scala b/tests/run/unroll-caseclass-integration/Unrolled_1.scala new file mode 100644 index 000000000000..e3f57bddd325 --- /dev/null +++ b/tests/run/unroll-caseclass-integration/Unrolled_1.scala @@ -0,0 +1,6 @@ +//> using options -experimental +package unroll + +case class Unrolled(s: String, n: Int = 1){ + def foo = s + n +} diff --git a/tests/run/unroll-caseclass-integration/Unrolled_2.scala b/tests/run/unroll-caseclass-integration/Unrolled_2.scala new file mode 100644 index 000000000000..cb2232a57726 --- /dev/null +++ b/tests/run/unroll-caseclass-integration/Unrolled_2.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +case class Unrolled(s: String, n: Int = 1, @unroll b: Boolean = true){ + def foo = s + n + b +} diff --git a/tests/run/unroll-caseclass-integration/Unrolled_3.scala b/tests/run/unroll-caseclass-integration/Unrolled_3.scala new file mode 100644 index 000000000000..66b4981660df --- /dev/null +++ b/tests/run/unroll-caseclass-integration/Unrolled_3.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +case class Unrolled(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0){ + def foo = s + n + b + l +} diff --git a/tests/run/unroll-classMethod-integration.jvm.check b/tests/run/unroll-classMethod-integration.jvm.check new file mode 100644 index 000000000000..db6f0ee5732d --- /dev/null +++ b/tests/run/unroll-classMethod-integration.jvm.check @@ -0,0 +1,14 @@ +Assertion passed: found "cow" + "1true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +public boolean unroll.Unrolled.foo$default$3() +public final java.lang.String unroll.Unrolled.foo(java.lang.String) +public final java.lang.String unroll.Unrolled.foo(java.lang.String,int,boolean) +public final java.lang.String unroll.Unrolled.foo(java.lang.String,int,boolean,long) +public int unroll.Unrolled.foo$default$2() +public long unroll.Unrolled.foo$default$4() +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-classMethod-integration.scala-js.check b/tests/run/unroll-classMethod-integration.scala-js.check new file mode 100644 index 000000000000..001534a32889 --- /dev/null +++ b/tests/run/unroll-classMethod-integration.scala-js.check @@ -0,0 +1,8 @@ +Assertion passed: found "cow" + "1true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-classMethod-integration/TestUtils_1.scala b/tests/run/unroll-classMethod-integration/TestUtils_1.scala new file mode 100644 index 000000000000..e639a54d924b --- /dev/null +++ b/tests/run/unroll-classMethod-integration/TestUtils_1.scala @@ -0,0 +1,13 @@ +//> using options -experimental +package unroll + +object TestUtils { + def logAssertStartsWith(actual: String, expected: String): Unit = { + assert(actual.startsWith(expected)) + val suffix = { + val suffix0 = actual.stripPrefix(expected) + if (suffix0.isEmpty) "" else s""" + "$suffix0"""" + } + println(s"""Assertion passed: found "$expected"$suffix""") + } +} diff --git a/tests/run/unroll-classMethod-integration/Test_4.scala b/tests/run/unroll-classMethod-integration/Test_4.scala new file mode 100644 index 000000000000..dc60b5bf2059 --- /dev/null +++ b/tests/run/unroll-classMethod-integration/Test_4.scala @@ -0,0 +1,7 @@ +//> using options -experimental +import unroll.* + +@main def Test: Unit = + UnrollTestMainV1.main(Array.empty[String]) + UnrollTestMainV2.main(Array.empty[String]) + UnrollTestMainV3.main(Array.empty[String]) diff --git a/tests/run/unroll-classMethod-integration/UnrollTestMain_1.scala b/tests/run/unroll-classMethod-integration/UnrollTestMain_1.scala new file mode 100644 index 000000000000..62d61c81d21d --- /dev/null +++ b/tests/run/unroll-classMethod-integration/UnrollTestMain_1.scala @@ -0,0 +1,10 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV1{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow"), "cow") + } +} diff --git a/tests/run/unroll-classMethod-integration/UnrollTestMain_2.scala b/tests/run/unroll-classMethod-integration/UnrollTestMain_2.scala new file mode 100644 index 000000000000..921ee48ff0db --- /dev/null +++ b/tests/run/unroll-classMethod-integration/UnrollTestMain_2.scala @@ -0,0 +1,12 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV2{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow"), "cow1true") + logAssertStartsWith(new Unrolled().foo("cow", 2), "cow2true") + logAssertStartsWith(new Unrolled().foo("cow", 2, false), "cow2false") + } +} diff --git a/tests/run/unroll-classMethod-integration/UnrollTestMain_3.scala b/tests/run/unroll-classMethod-integration/UnrollTestMain_3.scala new file mode 100644 index 000000000000..bc985b58c359 --- /dev/null +++ b/tests/run/unroll-classMethod-integration/UnrollTestMain_3.scala @@ -0,0 +1,15 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV3{ + def main(args: Array[String]): Unit = { + UnrollTestPlatformSpecificV3() + + logAssertStartsWith(new Unrolled().foo("cow"), "cow1true0") + logAssertStartsWith(new Unrolled().foo("cow", 2), "cow2true0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false), "cow2false0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false, 3), "cow2false3") + } +} diff --git a/tests/run/unroll-classMethod-integration/UnrollTestPlatformSpecificJS_3.scala b/tests/run/unroll-classMethod-integration/UnrollTestPlatformSpecificJS_3.scala new file mode 100644 index 000000000000..190890788014 --- /dev/null +++ b/tests/run/unroll-classMethod-integration/UnrollTestPlatformSpecificJS_3.scala @@ -0,0 +1,9 @@ +//> using options -experimental +//> using target.platform scala-js +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + // do nothing for scala-js + } +} diff --git a/tests/run/unroll-classMethod-integration/UnrollTestPlatformSpecific_3.scala b/tests/run/unroll-classMethod-integration/UnrollTestPlatformSpecific_3.scala new file mode 100644 index 000000000000..decffbaf3c74 --- /dev/null +++ b/tests/run/unroll-classMethod-integration/UnrollTestPlatformSpecific_3.scala @@ -0,0 +1,32 @@ +//> using options -experimental +//> using target.platform jvm +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + val instance = new Unrolled() + val cls = classOf[Unrolled] + + assert( + cls.getMethod("foo", classOf[String]).invoke(instance, "hello") == + "hello1true0" + ) + + // Only generate unrolled methods for annotated params + // (b: Boolean) is not annotated so this method should not exist + assert(scala.util.Try(cls.getMethod("foo", classOf[String], classOf[Int])).isFailure) + + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE) == + "hello2false0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) == + "hello2false3" + ) + + cls.getMethods.filter(_.getName.contains("foo")).map(_.toString).sorted.foreach(println) + } +} diff --git a/tests/run/unroll-classMethod-integration/Unrolled_1.scala b/tests/run/unroll-classMethod-integration/Unrolled_1.scala new file mode 100644 index 000000000000..aa6ca65fe6a3 --- /dev/null +++ b/tests/run/unroll-classMethod-integration/Unrolled_1.scala @@ -0,0 +1,6 @@ +//> using options -experimental +package unroll + +class Unrolled{ + def foo(s: String) = s +} diff --git a/tests/run/unroll-classMethod-integration/Unrolled_2.scala b/tests/run/unroll-classMethod-integration/Unrolled_2.scala new file mode 100644 index 000000000000..2091bb4c5a9e --- /dev/null +++ b/tests/run/unroll-classMethod-integration/Unrolled_2.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +class Unrolled{ + final def foo(s: String, @unroll n: Int = 1, b: Boolean = true) = s + n + b +} diff --git a/tests/run/unroll-classMethod-integration/Unrolled_3.scala b/tests/run/unroll-classMethod-integration/Unrolled_3.scala new file mode 100644 index 000000000000..8991bda3aeb7 --- /dev/null +++ b/tests/run/unroll-classMethod-integration/Unrolled_3.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +class Unrolled{ + final def foo(s: String, @unroll n: Int = 1, b: Boolean = true, @unroll l: Long = 0) = s + n + b + l +} diff --git a/tests/run/unroll-clause-interleaving/Test_4.scala b/tests/run/unroll-clause-interleaving/Test_4.scala new file mode 100644 index 000000000000..4090469bb716 --- /dev/null +++ b/tests/run/unroll-clause-interleaving/Test_4.scala @@ -0,0 +1,7 @@ +//> using options -experimental + +@main def Test: Unit = + val u = Unrolled() + TestV1().test(u) + TestV2().test(u) + TestV3().test(u) diff --git a/tests/run/unroll-clause-interleaving/Unrolled_1.scala b/tests/run/unroll-clause-interleaving/Unrolled_1.scala new file mode 100644 index 000000000000..7954acc15680 --- /dev/null +++ b/tests/run/unroll-clause-interleaving/Unrolled_1.scala @@ -0,0 +1,14 @@ +//> using options -experimental + +import scala.annotation.unroll + +class Unrolled { + final def foo(x: Int)[T]( + s: T, + ): String = "" + x + s +} + +class TestV1 { + def test(u: Unrolled): Unit = + assert(u.foo(0)("foo").startsWith("0foo")) +} diff --git a/tests/run/unroll-clause-interleaving/Unrolled_2.scala b/tests/run/unroll-clause-interleaving/Unrolled_2.scala new file mode 100644 index 000000000000..5adc2bc924ec --- /dev/null +++ b/tests/run/unroll-clause-interleaving/Unrolled_2.scala @@ -0,0 +1,16 @@ +//> using options -experimental + +import scala.annotation.unroll + +class Unrolled { + final def foo(x: Int)[T]( + s: T, + @unroll y: Boolean = true, + ): String = "" + x + s + y +} + +class TestV2 { + def test(u: Unrolled): Unit = + assert(u.foo(0)("foo").startsWith("0footrue")) + assert(u.foo(0)("foo", false).startsWith("0foofalse")) +} diff --git a/tests/run/unroll-clause-interleaving/Unrolled_3.scala b/tests/run/unroll-clause-interleaving/Unrolled_3.scala new file mode 100644 index 000000000000..e23b9d12843a --- /dev/null +++ b/tests/run/unroll-clause-interleaving/Unrolled_3.scala @@ -0,0 +1,18 @@ +//> using options -experimental + +import scala.annotation.unroll + +class Unrolled { + final def foo(x: Int)[T]( + s: T, + @unroll y: Boolean = true, + @unroll i: Int = 0, + ): String = "" + x + s + y + i +} + +class TestV3 { + def test(u: Unrolled): Unit = + assert(u.foo(0)("foo").startsWith("0footrue0")) + assert(u.foo(0)("foo", false).startsWith("0foofalse0")) + assert(u.foo(0)("foo", false, 1).startsWith("0foofalse1")) +} diff --git a/tests/run/unroll-curriedMethod-integration.jvm.check b/tests/run/unroll-curriedMethod-integration.jvm.check new file mode 100644 index 000000000000..bd696cc21ed5 --- /dev/null +++ b/tests/run/unroll-curriedMethod-integration.jvm.check @@ -0,0 +1,14 @@ +Assertion passed: found "cow" + "1true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +public boolean unroll.Unrolled.foo$default$3() +public final java.lang.String unroll.Unrolled.foo(java.lang.String,int,boolean,long,scala.Function1) +public final java.lang.String unroll.Unrolled.foo(java.lang.String,int,boolean,scala.Function1) +public final java.lang.String unroll.Unrolled.foo(java.lang.String,scala.Function1) +public int unroll.Unrolled.foo$default$2() +public long unroll.Unrolled.foo$default$4() +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-curriedMethod-integration.scala-js.check b/tests/run/unroll-curriedMethod-integration.scala-js.check new file mode 100644 index 000000000000..001534a32889 --- /dev/null +++ b/tests/run/unroll-curriedMethod-integration.scala-js.check @@ -0,0 +1,8 @@ +Assertion passed: found "cow" + "1true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-curriedMethod-integration/TestUtils_1.scala b/tests/run/unroll-curriedMethod-integration/TestUtils_1.scala new file mode 100644 index 000000000000..e639a54d924b --- /dev/null +++ b/tests/run/unroll-curriedMethod-integration/TestUtils_1.scala @@ -0,0 +1,13 @@ +//> using options -experimental +package unroll + +object TestUtils { + def logAssertStartsWith(actual: String, expected: String): Unit = { + assert(actual.startsWith(expected)) + val suffix = { + val suffix0 = actual.stripPrefix(expected) + if (suffix0.isEmpty) "" else s""" + "$suffix0"""" + } + println(s"""Assertion passed: found "$expected"$suffix""") + } +} diff --git a/tests/run/unroll-curriedMethod-integration/Test_4.scala b/tests/run/unroll-curriedMethod-integration/Test_4.scala new file mode 100644 index 000000000000..dc60b5bf2059 --- /dev/null +++ b/tests/run/unroll-curriedMethod-integration/Test_4.scala @@ -0,0 +1,7 @@ +//> using options -experimental +import unroll.* + +@main def Test: Unit = + UnrollTestMainV1.main(Array.empty[String]) + UnrollTestMainV2.main(Array.empty[String]) + UnrollTestMainV3.main(Array.empty[String]) diff --git a/tests/run/unroll-curriedMethod-integration/UnrollTestMain_1.scala b/tests/run/unroll-curriedMethod-integration/UnrollTestMain_1.scala new file mode 100644 index 000000000000..411aae125e20 --- /dev/null +++ b/tests/run/unroll-curriedMethod-integration/UnrollTestMain_1.scala @@ -0,0 +1,10 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV1{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow")(identity), "cow") + } +} diff --git a/tests/run/unroll-curriedMethod-integration/UnrollTestMain_2.scala b/tests/run/unroll-curriedMethod-integration/UnrollTestMain_2.scala new file mode 100644 index 000000000000..9be52201ed8b --- /dev/null +++ b/tests/run/unroll-curriedMethod-integration/UnrollTestMain_2.scala @@ -0,0 +1,12 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV2{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow")(identity), "cow1true") + logAssertStartsWith(new Unrolled().foo("cow", 2)(identity), "cow2true") + logAssertStartsWith(new Unrolled().foo("cow", 2, false)(identity), "cow2false") + } +} diff --git a/tests/run/unroll-curriedMethod-integration/UnrollTestMain_3.scala b/tests/run/unroll-curriedMethod-integration/UnrollTestMain_3.scala new file mode 100644 index 000000000000..1f281db4f497 --- /dev/null +++ b/tests/run/unroll-curriedMethod-integration/UnrollTestMain_3.scala @@ -0,0 +1,15 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV3{ + def main(args: Array[String]): Unit = { + UnrollTestPlatformSpecificV3() + + logAssertStartsWith(new Unrolled().foo("cow")(identity), "cow1true0") + logAssertStartsWith(new Unrolled().foo("cow", 2)(identity), "cow2true0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false)(identity), "cow2false0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false, 3)(identity), "cow2false3") + } +} diff --git a/tests/run/unroll-curriedMethod-integration/UnrollTestPlatformSpecificJS_3.scala b/tests/run/unroll-curriedMethod-integration/UnrollTestPlatformSpecificJS_3.scala new file mode 100644 index 000000000000..190890788014 --- /dev/null +++ b/tests/run/unroll-curriedMethod-integration/UnrollTestPlatformSpecificJS_3.scala @@ -0,0 +1,9 @@ +//> using options -experimental +//> using target.platform scala-js +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + // do nothing for scala-js + } +} diff --git a/tests/run/unroll-curriedMethod-integration/UnrollTestPlatformSpecific_3.scala b/tests/run/unroll-curriedMethod-integration/UnrollTestPlatformSpecific_3.scala new file mode 100644 index 000000000000..24ca27b644a1 --- /dev/null +++ b/tests/run/unroll-curriedMethod-integration/UnrollTestPlatformSpecific_3.scala @@ -0,0 +1,31 @@ +//> using options -experimental +//> using target.platform jvm +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + val instance = new Unrolled() + val cls = classOf[Unrolled] + + assert( + cls.getMethod("foo", classOf[String], classOf[String => String]).invoke(instance, "hello", identity[String](_)) == + "hello1true0" + ) + + assert( + scala.util.Try(cls.getMethod("foo", classOf[String], classOf[Int], classOf[String => String])).isFailure + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[String => String]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, identity[String](_)) == + "hello2false0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[Long], classOf[String => String]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer, identity[String](_)) == + "hello2false3" + ) + + cls.getMethods.filter(_.getName.contains("foo")).map(_.toString).sorted.foreach(println) + } +} diff --git a/tests/run/unroll-curriedMethod-integration/Unrolled_1.scala b/tests/run/unroll-curriedMethod-integration/Unrolled_1.scala new file mode 100644 index 000000000000..d6b8b06da582 --- /dev/null +++ b/tests/run/unroll-curriedMethod-integration/Unrolled_1.scala @@ -0,0 +1,6 @@ +//> using options -experimental +package unroll + +class Unrolled{ + def foo(s: String)(f: String => String) = f(s) +} diff --git a/tests/run/unroll-curriedMethod-integration/Unrolled_2.scala b/tests/run/unroll-curriedMethod-integration/Unrolled_2.scala new file mode 100644 index 000000000000..5a526bf6eeb2 --- /dev/null +++ b/tests/run/unroll-curriedMethod-integration/Unrolled_2.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +class Unrolled{ + final def foo(s: String, @unroll n: Int = 1, b: Boolean = true)(f: String => String) = f(s + n + b) +} diff --git a/tests/run/unroll-curriedMethod-integration/Unrolled_3.scala b/tests/run/unroll-curriedMethod-integration/Unrolled_3.scala new file mode 100644 index 000000000000..008576a9a5c3 --- /dev/null +++ b/tests/run/unroll-curriedMethod-integration/Unrolled_3.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +class Unrolled{ + final def foo(s: String, @unroll n: Int = 1, b: Boolean = true, @unroll l: Long = 0)(f: String => String) = f(s + n + b + l) +} diff --git a/tests/run/unroll-genericMethod-integration.jvm.check b/tests/run/unroll-genericMethod-integration.jvm.check new file mode 100644 index 000000000000..e1880e773f8e --- /dev/null +++ b/tests/run/unroll-genericMethod-integration.jvm.check @@ -0,0 +1,14 @@ +Assertion passed: found "cow" + "1true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +public boolean unroll.Unrolled.foo$default$3() +public final java.lang.String unroll.Unrolled.foo(java.lang.Object) +public final java.lang.String unroll.Unrolled.foo(java.lang.Object,int,boolean) +public final java.lang.String unroll.Unrolled.foo(java.lang.Object,int,boolean,long) +public int unroll.Unrolled.foo$default$2() +public long unroll.Unrolled.foo$default$4() +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-genericMethod-integration.scala-js.check b/tests/run/unroll-genericMethod-integration.scala-js.check new file mode 100644 index 000000000000..001534a32889 --- /dev/null +++ b/tests/run/unroll-genericMethod-integration.scala-js.check @@ -0,0 +1,8 @@ +Assertion passed: found "cow" + "1true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-genericMethod-integration/TestUtils_1.scala b/tests/run/unroll-genericMethod-integration/TestUtils_1.scala new file mode 100644 index 000000000000..e639a54d924b --- /dev/null +++ b/tests/run/unroll-genericMethod-integration/TestUtils_1.scala @@ -0,0 +1,13 @@ +//> using options -experimental +package unroll + +object TestUtils { + def logAssertStartsWith(actual: String, expected: String): Unit = { + assert(actual.startsWith(expected)) + val suffix = { + val suffix0 = actual.stripPrefix(expected) + if (suffix0.isEmpty) "" else s""" + "$suffix0"""" + } + println(s"""Assertion passed: found "$expected"$suffix""") + } +} diff --git a/tests/run/unroll-genericMethod-integration/Test_4.scala b/tests/run/unroll-genericMethod-integration/Test_4.scala new file mode 100644 index 000000000000..dc60b5bf2059 --- /dev/null +++ b/tests/run/unroll-genericMethod-integration/Test_4.scala @@ -0,0 +1,7 @@ +//> using options -experimental +import unroll.* + +@main def Test: Unit = + UnrollTestMainV1.main(Array.empty[String]) + UnrollTestMainV2.main(Array.empty[String]) + UnrollTestMainV3.main(Array.empty[String]) diff --git a/tests/run/unroll-genericMethod-integration/UnrollTestMain_1.scala b/tests/run/unroll-genericMethod-integration/UnrollTestMain_1.scala new file mode 100644 index 000000000000..62d61c81d21d --- /dev/null +++ b/tests/run/unroll-genericMethod-integration/UnrollTestMain_1.scala @@ -0,0 +1,10 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV1{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow"), "cow") + } +} diff --git a/tests/run/unroll-genericMethod-integration/UnrollTestMain_2.scala b/tests/run/unroll-genericMethod-integration/UnrollTestMain_2.scala new file mode 100644 index 000000000000..921ee48ff0db --- /dev/null +++ b/tests/run/unroll-genericMethod-integration/UnrollTestMain_2.scala @@ -0,0 +1,12 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV2{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo("cow"), "cow1true") + logAssertStartsWith(new Unrolled().foo("cow", 2), "cow2true") + logAssertStartsWith(new Unrolled().foo("cow", 2, false), "cow2false") + } +} diff --git a/tests/run/unroll-genericMethod-integration/UnrollTestMain_3.scala b/tests/run/unroll-genericMethod-integration/UnrollTestMain_3.scala new file mode 100644 index 000000000000..bc985b58c359 --- /dev/null +++ b/tests/run/unroll-genericMethod-integration/UnrollTestMain_3.scala @@ -0,0 +1,15 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV3{ + def main(args: Array[String]): Unit = { + UnrollTestPlatformSpecificV3() + + logAssertStartsWith(new Unrolled().foo("cow"), "cow1true0") + logAssertStartsWith(new Unrolled().foo("cow", 2), "cow2true0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false), "cow2false0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false, 3), "cow2false3") + } +} diff --git a/tests/run/unroll-genericMethod-integration/UnrollTestPlatformSpecificJS_3.scala b/tests/run/unroll-genericMethod-integration/UnrollTestPlatformSpecificJS_3.scala new file mode 100644 index 000000000000..190890788014 --- /dev/null +++ b/tests/run/unroll-genericMethod-integration/UnrollTestPlatformSpecificJS_3.scala @@ -0,0 +1,9 @@ +//> using options -experimental +//> using target.platform scala-js +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + // do nothing for scala-js + } +} diff --git a/tests/run/unroll-genericMethod-integration/UnrollTestPlatformSpecific_3.scala b/tests/run/unroll-genericMethod-integration/UnrollTestPlatformSpecific_3.scala new file mode 100644 index 000000000000..2ef19e0f378e --- /dev/null +++ b/tests/run/unroll-genericMethod-integration/UnrollTestPlatformSpecific_3.scala @@ -0,0 +1,29 @@ +//> using options -experimental +//> using target.platform jvm +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + val instance = new Unrolled() + val cls = classOf[Unrolled] + + assert( + cls.getMethod("foo", classOf[Object]).invoke(instance, "hello") == + "hello1true0" + ) + + assert(scala.util.Try(cls.getMethod("foo", classOf[Object], classOf[Int])).isFailure) + assert( + cls.getMethod("foo", classOf[Object], classOf[Int], classOf[Boolean]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE) == + "hello2false0" + ) + assert( + cls.getMethod("foo", classOf[Object], classOf[Int], classOf[Boolean], classOf[Long]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) == + "hello2false3" + ) + cls.getMethods.filter(_.getName.contains("foo")).map(_.toString).sorted.foreach(println) + + } +} diff --git a/tests/run/unroll-genericMethod-integration/Unrolled_1.scala b/tests/run/unroll-genericMethod-integration/Unrolled_1.scala new file mode 100644 index 000000000000..a1d69945bc7e --- /dev/null +++ b/tests/run/unroll-genericMethod-integration/Unrolled_1.scala @@ -0,0 +1,6 @@ +//> using options -experimental +package unroll + +class Unrolled{ + def foo[T](s: T) = s.toString +} diff --git a/tests/run/unroll-genericMethod-integration/Unrolled_2.scala b/tests/run/unroll-genericMethod-integration/Unrolled_2.scala new file mode 100644 index 000000000000..e5970388fff8 --- /dev/null +++ b/tests/run/unroll-genericMethod-integration/Unrolled_2.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +class Unrolled{ + final def foo[T](s: T, @unroll n: Int = 1, b: Boolean = true) = s.toString + n + b +} diff --git a/tests/run/unroll-genericMethod-integration/Unrolled_3.scala b/tests/run/unroll-genericMethod-integration/Unrolled_3.scala new file mode 100644 index 000000000000..2ababa300ed1 --- /dev/null +++ b/tests/run/unroll-genericMethod-integration/Unrolled_3.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +class Unrolled{ + final def foo[T](s: T, @unroll n: Int = 1, b: Boolean = true, @unroll l: Long = 0) = s.toString + n + b + l +} diff --git a/tests/run/unroll-inferredFinal.scala b/tests/run/unroll-inferredFinal.scala new file mode 100644 index 000000000000..b4e1ccd9f011 --- /dev/null +++ b/tests/run/unroll-inferredFinal.scala @@ -0,0 +1,17 @@ +//> using options -experimental + +import scala.annotation.unroll + +object UnrolledObj { + // final is not needed because objects can't be extended + def foo(s: String, @unroll y: Boolean = true): String = s + y +} + +// final inferred for constructor +class UnrolledClass(s: String, @unroll y: Boolean = true): + override def toString = s"UnrolledClass($s,$y)" + + +@main def Test: Unit = + assert(UnrolledObj.foo("foo") == "footrue") + assert(new UnrolledClass("foo").toString == "UnrolledClass(foo,true)") diff --git a/tests/run/unroll-methodWithImplicit-integration.jvm.check b/tests/run/unroll-methodWithImplicit-integration.jvm.check new file mode 100644 index 000000000000..bd696cc21ed5 --- /dev/null +++ b/tests/run/unroll-methodWithImplicit-integration.jvm.check @@ -0,0 +1,14 @@ +Assertion passed: found "cow" + "1true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +public boolean unroll.Unrolled.foo$default$3() +public final java.lang.String unroll.Unrolled.foo(java.lang.String,int,boolean,long,scala.Function1) +public final java.lang.String unroll.Unrolled.foo(java.lang.String,int,boolean,scala.Function1) +public final java.lang.String unroll.Unrolled.foo(java.lang.String,scala.Function1) +public int unroll.Unrolled.foo$default$2() +public long unroll.Unrolled.foo$default$4() +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-methodWithImplicit-integration.scala-js.check b/tests/run/unroll-methodWithImplicit-integration.scala-js.check new file mode 100644 index 000000000000..001534a32889 --- /dev/null +++ b/tests/run/unroll-methodWithImplicit-integration.scala-js.check @@ -0,0 +1,8 @@ +Assertion passed: found "cow" + "1true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-methodWithImplicit-integration/TestUtils_1.scala b/tests/run/unroll-methodWithImplicit-integration/TestUtils_1.scala new file mode 100644 index 000000000000..e639a54d924b --- /dev/null +++ b/tests/run/unroll-methodWithImplicit-integration/TestUtils_1.scala @@ -0,0 +1,13 @@ +//> using options -experimental +package unroll + +object TestUtils { + def logAssertStartsWith(actual: String, expected: String): Unit = { + assert(actual.startsWith(expected)) + val suffix = { + val suffix0 = actual.stripPrefix(expected) + if (suffix0.isEmpty) "" else s""" + "$suffix0"""" + } + println(s"""Assertion passed: found "$expected"$suffix""") + } +} diff --git a/tests/run/unroll-methodWithImplicit-integration/Test_4.scala b/tests/run/unroll-methodWithImplicit-integration/Test_4.scala new file mode 100644 index 000000000000..dc60b5bf2059 --- /dev/null +++ b/tests/run/unroll-methodWithImplicit-integration/Test_4.scala @@ -0,0 +1,7 @@ +//> using options -experimental +import unroll.* + +@main def Test: Unit = + UnrollTestMainV1.main(Array.empty[String]) + UnrollTestMainV2.main(Array.empty[String]) + UnrollTestMainV3.main(Array.empty[String]) diff --git a/tests/run/unroll-methodWithImplicit-integration/UnrollTestMain_1.scala b/tests/run/unroll-methodWithImplicit-integration/UnrollTestMain_1.scala new file mode 100644 index 000000000000..e4e91e6709f3 --- /dev/null +++ b/tests/run/unroll-methodWithImplicit-integration/UnrollTestMain_1.scala @@ -0,0 +1,11 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV1{ + def main(args: Array[String]): Unit = { + implicit def f(s: String): String = s + logAssertStartsWith(new Unrolled().foo("cow"), "cow") + } +} diff --git a/tests/run/unroll-methodWithImplicit-integration/UnrollTestMain_2.scala b/tests/run/unroll-methodWithImplicit-integration/UnrollTestMain_2.scala new file mode 100644 index 000000000000..69a5a0b39ab6 --- /dev/null +++ b/tests/run/unroll-methodWithImplicit-integration/UnrollTestMain_2.scala @@ -0,0 +1,13 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV2{ + def main(args: Array[String]): Unit = { + implicit def f(s: String): String = s + logAssertStartsWith(new Unrolled().foo("cow"), "cow1true") + logAssertStartsWith(new Unrolled().foo("cow", 2), "cow2true") + logAssertStartsWith(new Unrolled().foo("cow", 2, false), "cow2false") + } +} diff --git a/tests/run/unroll-methodWithImplicit-integration/UnrollTestMain_3.scala b/tests/run/unroll-methodWithImplicit-integration/UnrollTestMain_3.scala new file mode 100644 index 000000000000..ffd528e15f5e --- /dev/null +++ b/tests/run/unroll-methodWithImplicit-integration/UnrollTestMain_3.scala @@ -0,0 +1,16 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV3{ + def main(args: Array[String]): Unit = { + UnrollTestPlatformSpecificV3() + + implicit def f(s: String): String = s + logAssertStartsWith(new Unrolled().foo("cow"), "cow1true0") + logAssertStartsWith(new Unrolled().foo("cow", 2), "cow2true0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false), "cow2false0") + logAssertStartsWith(new Unrolled().foo("cow", 2, false, 3), "cow2false3") + } +} diff --git a/tests/run/unroll-methodWithImplicit-integration/UnrollTestPlatformSpecificJS_3.scala b/tests/run/unroll-methodWithImplicit-integration/UnrollTestPlatformSpecificJS_3.scala new file mode 100644 index 000000000000..190890788014 --- /dev/null +++ b/tests/run/unroll-methodWithImplicit-integration/UnrollTestPlatformSpecificJS_3.scala @@ -0,0 +1,9 @@ +//> using options -experimental +//> using target.platform scala-js +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + // do nothing for scala-js + } +} diff --git a/tests/run/unroll-methodWithImplicit-integration/UnrollTestPlatformSpecific_3.scala b/tests/run/unroll-methodWithImplicit-integration/UnrollTestPlatformSpecific_3.scala new file mode 100644 index 000000000000..86cd5514cfd5 --- /dev/null +++ b/tests/run/unroll-methodWithImplicit-integration/UnrollTestPlatformSpecific_3.scala @@ -0,0 +1,31 @@ +//> using options -experimental +//> using target.platform jvm +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + val instance = new Unrolled() + val cls = classOf[Unrolled] + + assert( + cls.getMethod("foo", classOf[String], classOf[String => String]).invoke(instance, "hello", identity[String](_)) == + "hello1true0" + ) + + assert(scala.util.Try(cls.getMethod("foo", classOf[String], classOf[Int], classOf[String => String])).isFailure) + + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[String => String]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, identity[String](_)) == + "hello2false0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[Long], classOf[String => String]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer, identity[String](_)) == + "hello2false3" + ) + + cls.getMethods.filter(_.getName.contains("foo")).map(_.toString).sorted.foreach(println) + + } +} diff --git a/tests/run/unroll-methodWithImplicit-integration/Unrolled_1.scala b/tests/run/unroll-methodWithImplicit-integration/Unrolled_1.scala new file mode 100644 index 000000000000..1de415b26952 --- /dev/null +++ b/tests/run/unroll-methodWithImplicit-integration/Unrolled_1.scala @@ -0,0 +1,6 @@ +//> using options -experimental +package unroll + +class Unrolled{ + def foo(s: String)(implicit f: String => String) = f(s) +} diff --git a/tests/run/unroll-methodWithImplicit-integration/Unrolled_2.scala b/tests/run/unroll-methodWithImplicit-integration/Unrolled_2.scala new file mode 100644 index 000000000000..01a5d2fb037a --- /dev/null +++ b/tests/run/unroll-methodWithImplicit-integration/Unrolled_2.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +class Unrolled{ + final def foo(s: String, @unroll n: Int = 1, b: Boolean = true)(implicit f: String => String) = f(s + n + b) +} diff --git a/tests/run/unroll-methodWithImplicit-integration/Unrolled_3.scala b/tests/run/unroll-methodWithImplicit-integration/Unrolled_3.scala new file mode 100644 index 000000000000..07627f604d76 --- /dev/null +++ b/tests/run/unroll-methodWithImplicit-integration/Unrolled_3.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +class Unrolled{ + final def foo(s: String, @unroll n: Int = 1, b: Boolean = true, @unroll l: Long = 0)(implicit f: String => String) = f(s + n + b + l) +} diff --git a/tests/run/unroll-multiple.scala b/tests/run/unroll-multiple.scala new file mode 100644 index 000000000000..e1790be26395 --- /dev/null +++ b/tests/run/unroll-multiple.scala @@ -0,0 +1,72 @@ +//> using options -experimental + +import scala.annotation.unroll +import scala.deriving.Mirror + +class Unrolled { + final def foo( + s: String, + @unroll y: Boolean = true, + @unroll i: Int = 0, + @unroll c: Char = '?'): String = s + y + i + c +} + +class Outer { + class Unrolled { + final def bar( + s: String, + @unroll y: Boolean = true, + @unroll i: Int = 0, + @unroll c: Char = '?'): String = s + y + i + c + } + + case class UnrolledCase( + s: String, + @unroll y: Boolean = true, + @unroll i: Int = 0, + @unroll c: Char = '?') { + def baz: String = s + y + i + c + } + + class UnrolledSecondary { + private var msg = "" + + def qux: String = msg + + def this( + s: String, + @unroll y: Boolean = true, + @unroll i: Int = 0, + @unroll c: Char = '?') = { + this() + msg = s + y + i + c + } + } +} + +@main def Test: Unit = + assert(Unrolled().foo("foo") == "footrue0?") + assert(Unrolled().foo("foo", false) == "foofalse0?") + assert(Unrolled().foo("foo", false, 1) == "foofalse1?") + assert(Unrolled().foo("foo", false, 1, '@') == "foofalse1@") + val outer = new Outer() + assert(new outer.Unrolled().bar("bar") == "bartrue0?") + assert(new outer.Unrolled().bar("bar", false) == "barfalse0?") + assert(new outer.Unrolled().bar("bar", false, 1) == "barfalse1?") + assert(new outer.Unrolled().bar("bar", false, 1, '@') == "barfalse1@") + assert(outer.UnrolledCase.apply("baz").baz == "baztrue0?") + assert(outer.UnrolledCase.apply("baz", false).baz == "bazfalse0?") + assert(outer.UnrolledCase.apply("baz", false, 1).baz == "bazfalse1?") + assert(outer.UnrolledCase.apply("baz", false, 1, '@').baz == "bazfalse1@") + assert((new outer.UnrolledCase("baz")).baz == "baztrue0?") + assert((new outer.UnrolledCase("baz", false)).baz == "bazfalse0?") + assert((new outer.UnrolledCase("baz", false, 1)).baz == "bazfalse1?") + assert((new outer.UnrolledCase("baz", false, 1, '@')).baz == "bazfalse1@") + assert(summon[Mirror.Of[outer.UnrolledCase]].fromProduct(Tuple("baz")).baz == "baztrue0?") + assert(summon[Mirror.Of[outer.UnrolledCase]].fromProduct(("baz", false)).baz == "bazfalse0?") + assert(summon[Mirror.Of[outer.UnrolledCase]].fromProduct(("baz", false, 1)).baz == "bazfalse1?") + assert(summon[Mirror.Of[outer.UnrolledCase]].fromProduct(("baz", false, 1, '@')).baz == "bazfalse1@") + assert(new outer.UnrolledSecondary("qux").qux == "quxtrue0?") + assert(new outer.UnrolledSecondary("qux", false).qux == "quxfalse0?") + assert(new outer.UnrolledSecondary("qux", false, 1).qux == "quxfalse1?") + assert(new outer.UnrolledSecondary("qux", false, 1, '@').qux == "quxfalse1@") diff --git a/tests/run/unroll-objectMethod-integration.jvm.check b/tests/run/unroll-objectMethod-integration.jvm.check new file mode 100644 index 000000000000..b29f42ca6d8e --- /dev/null +++ b/tests/run/unroll-objectMethod-integration.jvm.check @@ -0,0 +1,21 @@ +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +public boolean unroll.Unrolled$.foo$default$3() +public final java.lang.String unroll.Unrolled$.foo(java.lang.String,int) +public final java.lang.String unroll.Unrolled$.foo(java.lang.String,int,boolean) +public final java.lang.String unroll.Unrolled$.foo(java.lang.String,int,boolean,long) +public int unroll.Unrolled$.foo$default$2() +public long unroll.Unrolled$.foo$default$4() +public static boolean unroll.Unrolled.foo$default$3() +public static int unroll.Unrolled.foo$default$2() +public static java.lang.String unroll.Unrolled.foo(java.lang.String,int) +public static java.lang.String unroll.Unrolled.foo(java.lang.String,int,boolean) +public static java.lang.String unroll.Unrolled.foo(java.lang.String,int,boolean,long) +public static long unroll.Unrolled.foo$default$4() +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-objectMethod-integration.scala-js.check b/tests/run/unroll-objectMethod-integration.scala-js.check new file mode 100644 index 000000000000..248ef79ed88a --- /dev/null +++ b/tests/run/unroll-objectMethod-integration.scala-js.check @@ -0,0 +1,9 @@ +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-objectMethod-integration/TestUtils_1.scala b/tests/run/unroll-objectMethod-integration/TestUtils_1.scala new file mode 100644 index 000000000000..e639a54d924b --- /dev/null +++ b/tests/run/unroll-objectMethod-integration/TestUtils_1.scala @@ -0,0 +1,13 @@ +//> using options -experimental +package unroll + +object TestUtils { + def logAssertStartsWith(actual: String, expected: String): Unit = { + assert(actual.startsWith(expected)) + val suffix = { + val suffix0 = actual.stripPrefix(expected) + if (suffix0.isEmpty) "" else s""" + "$suffix0"""" + } + println(s"""Assertion passed: found "$expected"$suffix""") + } +} diff --git a/tests/run/unroll-objectMethod-integration/Test_4.scala b/tests/run/unroll-objectMethod-integration/Test_4.scala new file mode 100644 index 000000000000..dc60b5bf2059 --- /dev/null +++ b/tests/run/unroll-objectMethod-integration/Test_4.scala @@ -0,0 +1,7 @@ +//> using options -experimental +import unroll.* + +@main def Test: Unit = + UnrollTestMainV1.main(Array.empty[String]) + UnrollTestMainV2.main(Array.empty[String]) + UnrollTestMainV3.main(Array.empty[String]) diff --git a/tests/run/unroll-objectMethod-integration/UnrollTestMain_1.scala b/tests/run/unroll-objectMethod-integration/UnrollTestMain_1.scala new file mode 100644 index 000000000000..9cba53c444ce --- /dev/null +++ b/tests/run/unroll-objectMethod-integration/UnrollTestMain_1.scala @@ -0,0 +1,11 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV1{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(Unrolled.foo("cow"), "cow1") + logAssertStartsWith(Unrolled.foo("cow", 2), "cow2") + } +} diff --git a/tests/run/unroll-objectMethod-integration/UnrollTestMain_2.scala b/tests/run/unroll-objectMethod-integration/UnrollTestMain_2.scala new file mode 100644 index 000000000000..2fe609543d51 --- /dev/null +++ b/tests/run/unroll-objectMethod-integration/UnrollTestMain_2.scala @@ -0,0 +1,12 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV2{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(Unrolled.foo("cow"), "cow1true") + logAssertStartsWith(Unrolled.foo("cow", 2), "cow2true") + logAssertStartsWith(Unrolled.foo("cow", 2, false), "cow2false") + } +} diff --git a/tests/run/unroll-objectMethod-integration/UnrollTestMain_3.scala b/tests/run/unroll-objectMethod-integration/UnrollTestMain_3.scala new file mode 100644 index 000000000000..e6d442d3bf7c --- /dev/null +++ b/tests/run/unroll-objectMethod-integration/UnrollTestMain_3.scala @@ -0,0 +1,15 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV3{ + def main(args: Array[String]): Unit = { + UnrollTestPlatformSpecificV3() + + logAssertStartsWith(Unrolled.foo("cow"), "cow1true0") + logAssertStartsWith(Unrolled.foo("cow", 2), "cow2true0") + logAssertStartsWith(Unrolled.foo("cow", 2, false), "cow2false0") + logAssertStartsWith(Unrolled.foo("cow", 2, false, 3), "cow2false3") + } +} diff --git a/tests/run/unroll-objectMethod-integration/UnrollTestPlatformSpecificJS_3.scala b/tests/run/unroll-objectMethod-integration/UnrollTestPlatformSpecificJS_3.scala new file mode 100644 index 000000000000..190890788014 --- /dev/null +++ b/tests/run/unroll-objectMethod-integration/UnrollTestPlatformSpecificJS_3.scala @@ -0,0 +1,9 @@ +//> using options -experimental +//> using target.platform scala-js +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + // do nothing for scala-js + } +} diff --git a/tests/run/unroll-objectMethod-integration/UnrollTestPlatformSpecific_3.scala b/tests/run/unroll-objectMethod-integration/UnrollTestPlatformSpecific_3.scala new file mode 100644 index 000000000000..61450765623a --- /dev/null +++ b/tests/run/unroll-objectMethod-integration/UnrollTestPlatformSpecific_3.scala @@ -0,0 +1,50 @@ +//> using options -experimental +//> using target.platform jvm +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + val instance = Unrolled + val instanceCls = Class.forName("unroll.Unrolled$") + + instanceCls.getMethods.filter(_.getName.contains("foo")).map(_.toString).sorted.foreach(println) + + // Make sure singleton instance forwarder methods are generated + assert(scala.util.Try(instanceCls.getMethod("foo", classOf[String])).isFailure) + assert( + instanceCls.getMethod("foo", classOf[String], classOf[Int]).invoke(instance, "hello", 2: Integer) == + "hello2true0" + ) + assert( + instanceCls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE) == + "hello2false0" + ) + assert( + instanceCls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) == + "hello2false3" + ) + + // Make sure static forwarder methods are generated + val staticCls = Class.forName("unroll.Unrolled") + staticCls.getMethods.filter(_.getName.contains("foo")).map(_.toString).sorted.foreach(println) + + assert(scala.util.Try(staticCls.getMethod("foo", classOf[String])).isFailure) + assert( + staticCls.getMethod("foo", classOf[String], classOf[Int]).invoke(null, "hello", 2: Integer) == + "hello2true0" + ) + assert( + staticCls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean]) + .invoke(null, "hello", 2: Integer, java.lang.Boolean.FALSE) == + "hello2false0" + ) + assert( + staticCls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .invoke(null, "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) == + "hello2false3" + ) + + } +} diff --git a/tests/run/unroll-objectMethod-integration/Unrolled_1.scala b/tests/run/unroll-objectMethod-integration/Unrolled_1.scala new file mode 100644 index 000000000000..042ab3180cdc --- /dev/null +++ b/tests/run/unroll-objectMethod-integration/Unrolled_1.scala @@ -0,0 +1,7 @@ +//> using options -experimental +package unroll + +object Unrolled{ + + def foo(s: String, n: Int = 1) = s + n +} diff --git a/tests/run/unroll-objectMethod-integration/Unrolled_2.scala b/tests/run/unroll-objectMethod-integration/Unrolled_2.scala new file mode 100644 index 000000000000..bfef86beb6b2 --- /dev/null +++ b/tests/run/unroll-objectMethod-integration/Unrolled_2.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +object Unrolled{ + final def foo(s: String, n: Int = 1, @unroll b: Boolean = true) = s + n + b +} diff --git a/tests/run/unroll-objectMethod-integration/Unrolled_3.scala b/tests/run/unroll-objectMethod-integration/Unrolled_3.scala new file mode 100644 index 000000000000..c76521e731d8 --- /dev/null +++ b/tests/run/unroll-objectMethod-integration/Unrolled_3.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +object Unrolled{ + final def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = s + n + b + l +} diff --git a/tests/run/unroll-primaryConstructor-integration.jvm.check b/tests/run/unroll-primaryConstructor-integration.jvm.check new file mode 100644 index 000000000000..f5f6e28d5635 --- /dev/null +++ b/tests/run/unroll-primaryConstructor-integration.jvm.check @@ -0,0 +1,13 @@ +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" + +public unroll.Unrolled(java.lang.String,int) +public unroll.Unrolled(java.lang.String,int,boolean) +public unroll.Unrolled(java.lang.String,int,boolean,long) +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-primaryConstructor-integration.scala-js.check b/tests/run/unroll-primaryConstructor-integration.scala-js.check new file mode 100644 index 000000000000..248ef79ed88a --- /dev/null +++ b/tests/run/unroll-primaryConstructor-integration.scala-js.check @@ -0,0 +1,9 @@ +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-primaryConstructor-integration/TestUtils_1.scala b/tests/run/unroll-primaryConstructor-integration/TestUtils_1.scala new file mode 100644 index 000000000000..e639a54d924b --- /dev/null +++ b/tests/run/unroll-primaryConstructor-integration/TestUtils_1.scala @@ -0,0 +1,13 @@ +//> using options -experimental +package unroll + +object TestUtils { + def logAssertStartsWith(actual: String, expected: String): Unit = { + assert(actual.startsWith(expected)) + val suffix = { + val suffix0 = actual.stripPrefix(expected) + if (suffix0.isEmpty) "" else s""" + "$suffix0"""" + } + println(s"""Assertion passed: found "$expected"$suffix""") + } +} diff --git a/tests/run/unroll-primaryConstructor-integration/Test_4.scala b/tests/run/unroll-primaryConstructor-integration/Test_4.scala new file mode 100644 index 000000000000..dc60b5bf2059 --- /dev/null +++ b/tests/run/unroll-primaryConstructor-integration/Test_4.scala @@ -0,0 +1,7 @@ +//> using options -experimental +import unroll.* + +@main def Test: Unit = + UnrollTestMainV1.main(Array.empty[String]) + UnrollTestMainV2.main(Array.empty[String]) + UnrollTestMainV3.main(Array.empty[String]) diff --git a/tests/run/unroll-primaryConstructor-integration/UnrollTestMain_1.scala b/tests/run/unroll-primaryConstructor-integration/UnrollTestMain_1.scala new file mode 100644 index 000000000000..ac80b2a5734b --- /dev/null +++ b/tests/run/unroll-primaryConstructor-integration/UnrollTestMain_1.scala @@ -0,0 +1,11 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV1{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2") + } +} diff --git a/tests/run/unroll-primaryConstructor-integration/UnrollTestMain_2.scala b/tests/run/unroll-primaryConstructor-integration/UnrollTestMain_2.scala new file mode 100644 index 000000000000..5140d999ee9b --- /dev/null +++ b/tests/run/unroll-primaryConstructor-integration/UnrollTestMain_2.scala @@ -0,0 +1,12 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV2{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1true") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2true") + logAssertStartsWith(new Unrolled("cow", 2, false).foo, "cow2false") + } +} diff --git a/tests/run/unroll-primaryConstructor-integration/UnrollTestMain_3.scala b/tests/run/unroll-primaryConstructor-integration/UnrollTestMain_3.scala new file mode 100644 index 000000000000..c80c33672d3e --- /dev/null +++ b/tests/run/unroll-primaryConstructor-integration/UnrollTestMain_3.scala @@ -0,0 +1,15 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV3{ + def main(args: Array[String]): Unit = { + UnrollTestPlatformSpecificV3() + + logAssertStartsWith(new Unrolled("cow").foo, "cow1true0") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2true0") + logAssertStartsWith(new Unrolled("cow", 2, false).foo, "cow2false0") + logAssertStartsWith(new Unrolled("cow", 2, false, 3).foo, "cow2false3") + } +} diff --git a/tests/run/unroll-primaryConstructor-integration/UnrollTestPlatformSpecificJS_3.scala b/tests/run/unroll-primaryConstructor-integration/UnrollTestPlatformSpecificJS_3.scala new file mode 100644 index 000000000000..190890788014 --- /dev/null +++ b/tests/run/unroll-primaryConstructor-integration/UnrollTestPlatformSpecificJS_3.scala @@ -0,0 +1,9 @@ +//> using options -experimental +//> using target.platform scala-js +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + // do nothing for scala-js + } +} diff --git a/tests/run/unroll-primaryConstructor-integration/UnrollTestPlatformSpecific_3.scala b/tests/run/unroll-primaryConstructor-integration/UnrollTestPlatformSpecific_3.scala new file mode 100644 index 000000000000..0ec252f740f4 --- /dev/null +++ b/tests/run/unroll-primaryConstructor-integration/UnrollTestPlatformSpecific_3.scala @@ -0,0 +1,35 @@ +//> using options -experimental +//> using target.platform jvm +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + val cls = classOf[Unrolled] + + assert(scala.util.Try(cls.getConstructor(classOf[String])).isFailure) + println() + assert( + cls.getConstructor(classOf[String], classOf[Int]) + .newInstance("hello", 2: Integer) + .asInstanceOf[Unrolled] + .foo == + "hello2true0" + ) + assert( + cls.getConstructor(classOf[String], classOf[Int], classOf[Boolean]) + .newInstance("hello", 2: Integer, java.lang.Boolean.FALSE) + .asInstanceOf[Unrolled] + .foo == + "hello2false0" + ) + assert( + cls.getConstructor(classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .newInstance("hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) + .asInstanceOf[Unrolled] + .foo == + "hello2false3" + ) + + cls.getConstructors.map(_.toString).sorted.foreach(println) + } +} diff --git a/tests/run/unroll-primaryConstructor-integration/Unrolled_1.scala b/tests/run/unroll-primaryConstructor-integration/Unrolled_1.scala new file mode 100644 index 000000000000..0ddd25a70127 --- /dev/null +++ b/tests/run/unroll-primaryConstructor-integration/Unrolled_1.scala @@ -0,0 +1,6 @@ +//> using options -experimental +package unroll + +class Unrolled(s: String, n: Int = 1){ + def foo = s + n +} diff --git a/tests/run/unroll-primaryConstructor-integration/Unrolled_2.scala b/tests/run/unroll-primaryConstructor-integration/Unrolled_2.scala new file mode 100644 index 000000000000..c8558df1af55 --- /dev/null +++ b/tests/run/unroll-primaryConstructor-integration/Unrolled_2.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +class Unrolled(s: String, n: Int = 1, @unroll b: Boolean = true){ + final def foo = s + n + b +} diff --git a/tests/run/unroll-primaryConstructor-integration/Unrolled_3.scala b/tests/run/unroll-primaryConstructor-integration/Unrolled_3.scala new file mode 100644 index 000000000000..c6be439e1dec --- /dev/null +++ b/tests/run/unroll-primaryConstructor-integration/Unrolled_3.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +class Unrolled(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0){ + def foo = s + n + b + l +} diff --git a/tests/run/unroll-secondParameterList-integration.jvm.check b/tests/run/unroll-secondParameterList-integration.jvm.check new file mode 100644 index 000000000000..d9ba05c5fc84 --- /dev/null +++ b/tests/run/unroll-secondParameterList-integration.jvm.check @@ -0,0 +1,14 @@ +Assertion passed: found "cow" + "1true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +public boolean unroll.Unrolled.foo$default$4(scala.Function1) +public final java.lang.String unroll.Unrolled.foo(scala.Function1,java.lang.String) +public final java.lang.String unroll.Unrolled.foo(scala.Function1,java.lang.String,int,boolean) +public final java.lang.String unroll.Unrolled.foo(scala.Function1,java.lang.String,int,boolean,long) +public int unroll.Unrolled.foo$default$3(scala.Function1) +public long unroll.Unrolled.foo$default$5(scala.Function1) +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-secondParameterList-integration.scala-js.check b/tests/run/unroll-secondParameterList-integration.scala-js.check new file mode 100644 index 000000000000..001534a32889 --- /dev/null +++ b/tests/run/unroll-secondParameterList-integration.scala-js.check @@ -0,0 +1,8 @@ +Assertion passed: found "cow" + "1true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-secondParameterList-integration/TestUtils_1.scala b/tests/run/unroll-secondParameterList-integration/TestUtils_1.scala new file mode 100644 index 000000000000..e639a54d924b --- /dev/null +++ b/tests/run/unroll-secondParameterList-integration/TestUtils_1.scala @@ -0,0 +1,13 @@ +//> using options -experimental +package unroll + +object TestUtils { + def logAssertStartsWith(actual: String, expected: String): Unit = { + assert(actual.startsWith(expected)) + val suffix = { + val suffix0 = actual.stripPrefix(expected) + if (suffix0.isEmpty) "" else s""" + "$suffix0"""" + } + println(s"""Assertion passed: found "$expected"$suffix""") + } +} diff --git a/tests/run/unroll-secondParameterList-integration/Test_4.scala b/tests/run/unroll-secondParameterList-integration/Test_4.scala new file mode 100644 index 000000000000..dc60b5bf2059 --- /dev/null +++ b/tests/run/unroll-secondParameterList-integration/Test_4.scala @@ -0,0 +1,7 @@ +//> using options -experimental +import unroll.* + +@main def Test: Unit = + UnrollTestMainV1.main(Array.empty[String]) + UnrollTestMainV2.main(Array.empty[String]) + UnrollTestMainV3.main(Array.empty[String]) diff --git a/tests/run/unroll-secondParameterList-integration/UnrollTestMain_1.scala b/tests/run/unroll-secondParameterList-integration/UnrollTestMain_1.scala new file mode 100644 index 000000000000..163225fb93bf --- /dev/null +++ b/tests/run/unroll-secondParameterList-integration/UnrollTestMain_1.scala @@ -0,0 +1,10 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV1{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo(identity)("cow"), "cow") + } +} diff --git a/tests/run/unroll-secondParameterList-integration/UnrollTestMain_2.scala b/tests/run/unroll-secondParameterList-integration/UnrollTestMain_2.scala new file mode 100644 index 000000000000..de0776c569db --- /dev/null +++ b/tests/run/unroll-secondParameterList-integration/UnrollTestMain_2.scala @@ -0,0 +1,12 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV2{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled().foo(identity)("cow"), "cow1true") + logAssertStartsWith(new Unrolled().foo(identity)("cow", 2), "cow2true") + logAssertStartsWith(new Unrolled().foo(identity)("cow", 2, false), "cow2false") + } +} diff --git a/tests/run/unroll-secondParameterList-integration/UnrollTestMain_3.scala b/tests/run/unroll-secondParameterList-integration/UnrollTestMain_3.scala new file mode 100644 index 000000000000..b18a7f18b4d0 --- /dev/null +++ b/tests/run/unroll-secondParameterList-integration/UnrollTestMain_3.scala @@ -0,0 +1,15 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV3{ + def main(args: Array[String]): Unit = { + UnrollTestPlatformSpecificV3() + + logAssertStartsWith(new Unrolled().foo(identity)("cow"), "cow1true0") + logAssertStartsWith(new Unrolled().foo(identity)("cow", 2), "cow2true0") + logAssertStartsWith(new Unrolled().foo(identity)("cow", 2, false), "cow2false0") + logAssertStartsWith(new Unrolled().foo(identity)("cow", 2, false, 3), "cow2false3") + } +} diff --git a/tests/run/unroll-secondParameterList-integration/UnrollTestPlatformSpecificJS_3.scala b/tests/run/unroll-secondParameterList-integration/UnrollTestPlatformSpecificJS_3.scala new file mode 100644 index 000000000000..190890788014 --- /dev/null +++ b/tests/run/unroll-secondParameterList-integration/UnrollTestPlatformSpecificJS_3.scala @@ -0,0 +1,9 @@ +//> using options -experimental +//> using target.platform scala-js +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + // do nothing for scala-js + } +} diff --git a/tests/run/unroll-secondParameterList-integration/UnrollTestPlatformSpecific_3.scala b/tests/run/unroll-secondParameterList-integration/UnrollTestPlatformSpecific_3.scala new file mode 100644 index 000000000000..a3cb5b7a8f0c --- /dev/null +++ b/tests/run/unroll-secondParameterList-integration/UnrollTestPlatformSpecific_3.scala @@ -0,0 +1,32 @@ +//> using options -experimental +//> using target.platform jvm +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + val instance = new Unrolled() + val cls = classOf[Unrolled] + + assert( + cls.getMethod("foo", classOf[String => String], classOf[String]) + .invoke(instance, identity[String](_), "hello") == + "hello1true0" + ) + + assert( + scala.util.Try(cls.getMethod("foo", classOf[String => String], classOf[String], classOf[Int])).isFailure + ) + assert( + cls.getMethod("foo", classOf[String => String], classOf[String], classOf[Int], classOf[Boolean]) + .invoke(instance, identity[String](_), "hello", 2: Integer, java.lang.Boolean.FALSE) == + "hello2false0" + ) + assert( + cls.getMethod("foo", classOf[String => String], classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .invoke(instance, identity[String](_), "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) == + "hello2false3" + ) + + cls.getMethods.filter(_.getName.contains("foo")).map(_.toString).sorted.foreach(println) + } +} diff --git a/tests/run/unroll-secondParameterList-integration/Unrolled_1.scala b/tests/run/unroll-secondParameterList-integration/Unrolled_1.scala new file mode 100644 index 000000000000..fbe0a58dca24 --- /dev/null +++ b/tests/run/unroll-secondParameterList-integration/Unrolled_1.scala @@ -0,0 +1,6 @@ +//> using options -experimental +package unroll + +class Unrolled{ + def foo(f: String => String)(s: String) = f(s) +} diff --git a/tests/run/unroll-secondParameterList-integration/Unrolled_2.scala b/tests/run/unroll-secondParameterList-integration/Unrolled_2.scala new file mode 100644 index 000000000000..68c4170f6f6e --- /dev/null +++ b/tests/run/unroll-secondParameterList-integration/Unrolled_2.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +class Unrolled{ + final def foo(f: String => String)(s: String, @unroll n: Int = 1, b: Boolean = true) = f(s + n + b) +} diff --git a/tests/run/unroll-secondParameterList-integration/Unrolled_3.scala b/tests/run/unroll-secondParameterList-integration/Unrolled_3.scala new file mode 100644 index 000000000000..ddbe8c4cfaf4 --- /dev/null +++ b/tests/run/unroll-secondParameterList-integration/Unrolled_3.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +class Unrolled{ + final def foo(f: String => String)(s: String, @unroll n: Int = 1, b: Boolean = true, @unroll l: Long = 0) = f(s + n + b + l) +} diff --git a/tests/run/unroll-secondaryConstructor-integration.jvm.check b/tests/run/unroll-secondaryConstructor-integration.jvm.check new file mode 100644 index 000000000000..016acefc31a1 --- /dev/null +++ b/tests/run/unroll-secondaryConstructor-integration.jvm.check @@ -0,0 +1,14 @@ +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" + +public unroll.Unrolled() +public unroll.Unrolled(java.lang.String,int) +public unroll.Unrolled(java.lang.String,int,boolean) +public unroll.Unrolled(java.lang.String,int,boolean,long) +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-secondaryConstructor-integration.scala-js.check b/tests/run/unroll-secondaryConstructor-integration.scala-js.check new file mode 100644 index 000000000000..248ef79ed88a --- /dev/null +++ b/tests/run/unroll-secondaryConstructor-integration.scala-js.check @@ -0,0 +1,9 @@ +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-secondaryConstructor-integration/TestUtils_1.scala b/tests/run/unroll-secondaryConstructor-integration/TestUtils_1.scala new file mode 100644 index 000000000000..e639a54d924b --- /dev/null +++ b/tests/run/unroll-secondaryConstructor-integration/TestUtils_1.scala @@ -0,0 +1,13 @@ +//> using options -experimental +package unroll + +object TestUtils { + def logAssertStartsWith(actual: String, expected: String): Unit = { + assert(actual.startsWith(expected)) + val suffix = { + val suffix0 = actual.stripPrefix(expected) + if (suffix0.isEmpty) "" else s""" + "$suffix0"""" + } + println(s"""Assertion passed: found "$expected"$suffix""") + } +} diff --git a/tests/run/unroll-secondaryConstructor-integration/Test_4.scala b/tests/run/unroll-secondaryConstructor-integration/Test_4.scala new file mode 100644 index 000000000000..dc60b5bf2059 --- /dev/null +++ b/tests/run/unroll-secondaryConstructor-integration/Test_4.scala @@ -0,0 +1,7 @@ +//> using options -experimental +import unroll.* + +@main def Test: Unit = + UnrollTestMainV1.main(Array.empty[String]) + UnrollTestMainV2.main(Array.empty[String]) + UnrollTestMainV3.main(Array.empty[String]) diff --git a/tests/run/unroll-secondaryConstructor-integration/UnrollTestMain_1.scala b/tests/run/unroll-secondaryConstructor-integration/UnrollTestMain_1.scala new file mode 100644 index 000000000000..ac80b2a5734b --- /dev/null +++ b/tests/run/unroll-secondaryConstructor-integration/UnrollTestMain_1.scala @@ -0,0 +1,11 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV1{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2") + } +} diff --git a/tests/run/unroll-secondaryConstructor-integration/UnrollTestMain_2.scala b/tests/run/unroll-secondaryConstructor-integration/UnrollTestMain_2.scala new file mode 100644 index 000000000000..5140d999ee9b --- /dev/null +++ b/tests/run/unroll-secondaryConstructor-integration/UnrollTestMain_2.scala @@ -0,0 +1,12 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV2{ + def main(args: Array[String]): Unit = { + logAssertStartsWith(new Unrolled("cow").foo, "cow1true") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2true") + logAssertStartsWith(new Unrolled("cow", 2, false).foo, "cow2false") + } +} diff --git a/tests/run/unroll-secondaryConstructor-integration/UnrollTestMain_3.scala b/tests/run/unroll-secondaryConstructor-integration/UnrollTestMain_3.scala new file mode 100644 index 000000000000..c80c33672d3e --- /dev/null +++ b/tests/run/unroll-secondaryConstructor-integration/UnrollTestMain_3.scala @@ -0,0 +1,15 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV3{ + def main(args: Array[String]): Unit = { + UnrollTestPlatformSpecificV3() + + logAssertStartsWith(new Unrolled("cow").foo, "cow1true0") + logAssertStartsWith(new Unrolled("cow", 2).foo, "cow2true0") + logAssertStartsWith(new Unrolled("cow", 2, false).foo, "cow2false0") + logAssertStartsWith(new Unrolled("cow", 2, false, 3).foo, "cow2false3") + } +} diff --git a/tests/run/unroll-secondaryConstructor-integration/UnrollTestPlatformSpecificJS_3.scala b/tests/run/unroll-secondaryConstructor-integration/UnrollTestPlatformSpecificJS_3.scala new file mode 100644 index 000000000000..190890788014 --- /dev/null +++ b/tests/run/unroll-secondaryConstructor-integration/UnrollTestPlatformSpecificJS_3.scala @@ -0,0 +1,9 @@ +//> using options -experimental +//> using target.platform scala-js +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + // do nothing for scala-js + } +} diff --git a/tests/run/unroll-secondaryConstructor-integration/UnrollTestPlatformSpecific_3.scala b/tests/run/unroll-secondaryConstructor-integration/UnrollTestPlatformSpecific_3.scala new file mode 100644 index 000000000000..0ec252f740f4 --- /dev/null +++ b/tests/run/unroll-secondaryConstructor-integration/UnrollTestPlatformSpecific_3.scala @@ -0,0 +1,35 @@ +//> using options -experimental +//> using target.platform jvm +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + val cls = classOf[Unrolled] + + assert(scala.util.Try(cls.getConstructor(classOf[String])).isFailure) + println() + assert( + cls.getConstructor(classOf[String], classOf[Int]) + .newInstance("hello", 2: Integer) + .asInstanceOf[Unrolled] + .foo == + "hello2true0" + ) + assert( + cls.getConstructor(classOf[String], classOf[Int], classOf[Boolean]) + .newInstance("hello", 2: Integer, java.lang.Boolean.FALSE) + .asInstanceOf[Unrolled] + .foo == + "hello2false0" + ) + assert( + cls.getConstructor(classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .newInstance("hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) + .asInstanceOf[Unrolled] + .foo == + "hello2false3" + ) + + cls.getConstructors.map(_.toString).sorted.foreach(println) + } +} diff --git a/tests/run/unroll-secondaryConstructor-integration/Unrolled_1.scala b/tests/run/unroll-secondaryConstructor-integration/Unrolled_1.scala new file mode 100644 index 000000000000..855b20efa8f6 --- /dev/null +++ b/tests/run/unroll-secondaryConstructor-integration/Unrolled_1.scala @@ -0,0 +1,10 @@ +//> using options -experimental +package unroll + +class Unrolled(){ + var foo = "" + def this(s: String, n: Int = 1) = { + this() + foo = s + n + } +} diff --git a/tests/run/unroll-secondaryConstructor-integration/Unrolled_2.scala b/tests/run/unroll-secondaryConstructor-integration/Unrolled_2.scala new file mode 100644 index 000000000000..b8f1f4f28328 --- /dev/null +++ b/tests/run/unroll-secondaryConstructor-integration/Unrolled_2.scala @@ -0,0 +1,13 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +class Unrolled() { + var foo = "" + + def this(s: String, n: Int = 1, @unroll b: Boolean = true) = { + this() + foo = s + n + b + } +} diff --git a/tests/run/unroll-secondaryConstructor-integration/Unrolled_3.scala b/tests/run/unroll-secondaryConstructor-integration/Unrolled_3.scala new file mode 100644 index 000000000000..1da3e0d69ec6 --- /dev/null +++ b/tests/run/unroll-secondaryConstructor-integration/Unrolled_3.scala @@ -0,0 +1,13 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +class Unrolled() { + var foo = "" + + def this(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = { + this() + foo = s + n + b + l + } +} diff --git a/tests/run/unroll-traitMethod-integration.jvm.check b/tests/run/unroll-traitMethod-integration.jvm.check new file mode 100644 index 000000000000..1f83c55f588f --- /dev/null +++ b/tests/run/unroll-traitMethod-integration.jvm.check @@ -0,0 +1,31 @@ +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" + +public default boolean unroll.Unrolled.foo$default$3() +public default int unroll.Unrolled.foo$default$2() +public default java.lang.String unroll.Unrolled.foo(java.lang.String,int) +public default java.lang.String unroll.Unrolled.foo(java.lang.String,int,boolean) +public default java.lang.String unroll.Unrolled.foo(java.lang.String,int,boolean,long) +public default long unroll.Unrolled.foo$default$4() +public static boolean unroll.Unrolled.foo$default$3$(unroll.Unrolled) +public static int unroll.Unrolled.foo$default$2$(unroll.Unrolled) +public static java.lang.String unroll.Unrolled.foo$(unroll.Unrolled,java.lang.String,int) +public static java.lang.String unroll.Unrolled.foo$(unroll.Unrolled,java.lang.String,int,boolean) +public static java.lang.String unroll.Unrolled.foo$(unroll.Unrolled,java.lang.String,int,boolean,long) +public static long unroll.Unrolled.foo$default$4$(unroll.Unrolled) +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-traitMethod-integration.scala-js.check b/tests/run/unroll-traitMethod-integration.scala-js.check new file mode 100644 index 000000000000..1ac3ffa822f0 --- /dev/null +++ b/tests/run/unroll-traitMethod-integration.scala-js.check @@ -0,0 +1,18 @@ +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "cow1" + "true0" +Assertion passed: found "cow2" + "true0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true" + "0" +Assertion passed: found "cow2true" + "0" +Assertion passed: found "cow2false" + "0" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" +Assertion passed: found "cow1true0" +Assertion passed: found "cow2true0" +Assertion passed: found "cow2false0" +Assertion passed: found "cow2false3" diff --git a/tests/run/unroll-traitMethod-integration/TestUtils_1.scala b/tests/run/unroll-traitMethod-integration/TestUtils_1.scala new file mode 100644 index 000000000000..e639a54d924b --- /dev/null +++ b/tests/run/unroll-traitMethod-integration/TestUtils_1.scala @@ -0,0 +1,13 @@ +//> using options -experimental +package unroll + +object TestUtils { + def logAssertStartsWith(actual: String, expected: String): Unit = { + assert(actual.startsWith(expected)) + val suffix = { + val suffix0 = actual.stripPrefix(expected) + if (suffix0.isEmpty) "" else s""" + "$suffix0"""" + } + println(s"""Assertion passed: found "$expected"$suffix""") + } +} diff --git a/tests/run/unroll-traitMethod-integration/Test_4.scala b/tests/run/unroll-traitMethod-integration/Test_4.scala new file mode 100644 index 000000000000..dc60b5bf2059 --- /dev/null +++ b/tests/run/unroll-traitMethod-integration/Test_4.scala @@ -0,0 +1,7 @@ +//> using options -experimental +import unroll.* + +@main def Test: Unit = + UnrollTestMainV1.main(Array.empty[String]) + UnrollTestMainV2.main(Array.empty[String]) + UnrollTestMainV3.main(Array.empty[String]) diff --git a/tests/run/unroll-traitMethod-integration/UnrollTestMain_1.scala b/tests/run/unroll-traitMethod-integration/UnrollTestMain_1.scala new file mode 100644 index 000000000000..b290c95e1f9c --- /dev/null +++ b/tests/run/unroll-traitMethod-integration/UnrollTestMain_1.scala @@ -0,0 +1,15 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV1{ + def main(args: Array[String]): Unit = { + val unrolled = new Unrolled{} + logAssertStartsWith(unrolled.foo("cow"), "cow1") + logAssertStartsWith(unrolled.foo("cow", 2), "cow2") + + logAssertStartsWith(Unrolled.foo("cow"), "cow1") + logAssertStartsWith(Unrolled.foo("cow", 2), "cow2") + } +} diff --git a/tests/run/unroll-traitMethod-integration/UnrollTestMain_2.scala b/tests/run/unroll-traitMethod-integration/UnrollTestMain_2.scala new file mode 100644 index 000000000000..6721b302e3f1 --- /dev/null +++ b/tests/run/unroll-traitMethod-integration/UnrollTestMain_2.scala @@ -0,0 +1,17 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV2{ + def main(args: Array[String]): Unit = { + val unrolled = new Unrolled{} + logAssertStartsWith(unrolled.foo("cow"), "cow1true") + logAssertStartsWith(unrolled.foo("cow", 2), "cow2true") + logAssertStartsWith(unrolled.foo("cow", 2, false), "cow2false") + + logAssertStartsWith(Unrolled.foo("cow"), "cow1true") + logAssertStartsWith(Unrolled.foo("cow", 2), "cow2true") + logAssertStartsWith(Unrolled.foo("cow", 2, false), "cow2false") + } +} diff --git a/tests/run/unroll-traitMethod-integration/UnrollTestMain_3.scala b/tests/run/unroll-traitMethod-integration/UnrollTestMain_3.scala new file mode 100644 index 000000000000..d4091763a86f --- /dev/null +++ b/tests/run/unroll-traitMethod-integration/UnrollTestMain_3.scala @@ -0,0 +1,21 @@ +//> using options -experimental +package unroll + +import unroll.TestUtils.logAssertStartsWith + +object UnrollTestMainV3{ + def main(args: Array[String]): Unit = { + UnrollTestPlatformSpecificV3() + + val unrolled = new Unrolled{} + logAssertStartsWith(unrolled.foo("cow"), "cow1true0") + logAssertStartsWith(unrolled.foo("cow", 2), "cow2true0") + logAssertStartsWith(unrolled.foo("cow", 2, false), "cow2false0") + logAssertStartsWith(unrolled.foo("cow", 2, false, 3), "cow2false3") + + logAssertStartsWith(Unrolled.foo("cow"), "cow1true0") + logAssertStartsWith(Unrolled.foo("cow", 2), "cow2true0") + logAssertStartsWith(Unrolled.foo("cow", 2, false), "cow2false0") + logAssertStartsWith(Unrolled.foo("cow", 2, false, 3), "cow2false3") + } +} diff --git a/tests/run/unroll-traitMethod-integration/UnrollTestPlatformSpecificJS_3.scala b/tests/run/unroll-traitMethod-integration/UnrollTestPlatformSpecificJS_3.scala new file mode 100644 index 000000000000..190890788014 --- /dev/null +++ b/tests/run/unroll-traitMethod-integration/UnrollTestPlatformSpecificJS_3.scala @@ -0,0 +1,9 @@ +//> using options -experimental +//> using target.platform scala-js +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + // do nothing for scala-js + } +} diff --git a/tests/run/unroll-traitMethod-integration/UnrollTestPlatformSpecific_3.scala b/tests/run/unroll-traitMethod-integration/UnrollTestPlatformSpecific_3.scala new file mode 100644 index 000000000000..bf004b8f656d --- /dev/null +++ b/tests/run/unroll-traitMethod-integration/UnrollTestPlatformSpecific_3.scala @@ -0,0 +1,30 @@ +//> using options -experimental +//> using target.platform jvm +package unroll + +object UnrollTestPlatformSpecificV3{ + def apply() = { + val instance = new Unrolled {} + val cls = classOf[Unrolled] + + assert(scala.util.Try(cls.getMethod("foo", classOf[String])).isFailure) + println() + assert( + cls.getMethod("foo", classOf[String], classOf[Int]).invoke(instance, "hello", 2: Integer) == + "hello2true0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE) == + "hello2false0" + ) + assert( + cls.getMethod("foo", classOf[String], classOf[Int], classOf[Boolean], classOf[Long]) + .invoke(instance, "hello", 2: Integer, java.lang.Boolean.FALSE, 3: Integer) == + "hello2false3" + ) + + cls.getMethods.filter(_.getName.contains("foo")).map(_.toString).sorted.foreach(println) + + } +} diff --git a/tests/run/unroll-traitMethod-integration/Unrolled_1.scala b/tests/run/unroll-traitMethod-integration/Unrolled_1.scala new file mode 100644 index 000000000000..aa9375698103 --- /dev/null +++ b/tests/run/unroll-traitMethod-integration/Unrolled_1.scala @@ -0,0 +1,8 @@ +//> using options -experimental +package unroll + +trait Unrolled{ + def foo(s: String, n: Int = 1) = s + n +} + +object Unrolled extends Unrolled \ No newline at end of file diff --git a/tests/run/unroll-traitMethod-integration/Unrolled_2.scala b/tests/run/unroll-traitMethod-integration/Unrolled_2.scala new file mode 100644 index 000000000000..242054d41be4 --- /dev/null +++ b/tests/run/unroll-traitMethod-integration/Unrolled_2.scala @@ -0,0 +1,10 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +trait Unrolled{ + final def foo(s: String, n: Int = 1, @unroll b: Boolean = true) = s + n + b +} + +object Unrolled extends Unrolled diff --git a/tests/run/unroll-traitMethod-integration/Unrolled_3.scala b/tests/run/unroll-traitMethod-integration/Unrolled_3.scala new file mode 100644 index 000000000000..398ddbb4da22 --- /dev/null +++ b/tests/run/unroll-traitMethod-integration/Unrolled_3.scala @@ -0,0 +1,10 @@ +//> using options -experimental +package unroll + +import scala.annotation.unroll + +trait Unrolled{ + final def foo(s: String, n: Int = 1, @unroll b: Boolean = true, @unroll l: Long = 0) = s + n + b + l +} + +object Unrolled extends Unrolled diff --git a/tests/run/unroll-value-class/Test_4.scala b/tests/run/unroll-value-class/Test_4.scala new file mode 100644 index 000000000000..98d7ff7e3f64 --- /dev/null +++ b/tests/run/unroll-value-class/Test_4.scala @@ -0,0 +1,7 @@ +//> using options -experimental + +@main def Test: Unit = + val u = Unrolled(0) + TestV1().test(u) + TestV2().test(u) + TestV3().test(u) diff --git a/tests/run/unroll-value-class/Unrolled_1.scala b/tests/run/unroll-value-class/Unrolled_1.scala new file mode 100644 index 000000000000..86d12831b78f --- /dev/null +++ b/tests/run/unroll-value-class/Unrolled_1.scala @@ -0,0 +1,13 @@ +//> using options -experimental + +import scala.annotation.unroll + +class Unrolled(val x: Int) extends AnyVal { + final def foo( + s: String, + ): String = "" + x + s +} + +class TestV1: + def test(u: Unrolled): Unit = + assert(u.foo("foo").startsWith("0foo")) diff --git a/tests/run/unroll-value-class/Unrolled_2.scala b/tests/run/unroll-value-class/Unrolled_2.scala new file mode 100644 index 000000000000..1be3f4ee38b6 --- /dev/null +++ b/tests/run/unroll-value-class/Unrolled_2.scala @@ -0,0 +1,15 @@ +//> using options -experimental + +import scala.annotation.unroll + +class Unrolled(val x: Int) extends AnyVal { + final def foo( + s: String, + @unroll y: Boolean = true, + ): String = "" + x + s + y +} + +class TestV2: + def test(u: Unrolled): Unit = + assert(u.foo("foo").startsWith("0footrue")) + assert(u.foo("foo", false).startsWith("0foofalse")) diff --git a/tests/run/unroll-value-class/Unrolled_3.scala b/tests/run/unroll-value-class/Unrolled_3.scala new file mode 100644 index 000000000000..05e5399bbe53 --- /dev/null +++ b/tests/run/unroll-value-class/Unrolled_3.scala @@ -0,0 +1,17 @@ +//> using options -experimental + +import scala.annotation.unroll + +class Unrolled(val x: Int) extends AnyVal { + final def foo( + s: String, + @unroll y: Boolean = true, + @unroll i: Int = 0 + ): String = "" + x + s + y + i +} + +class TestV3: + def test(u: Unrolled): Unit = + assert(u.foo("foo").startsWith("0footrue0")) + assert(u.foo("foo", false).startsWith("0foofalse0")) + assert(u.foo("foo", false, 1).startsWith("0foofalse1")) diff --git a/tests/semanticdb/expect/JavaStaticVar.expect.scala b/tests/semanticdb/expect/JavaStaticVar.expect.scala new file mode 100644 index 000000000000..171596bc9519 --- /dev/null +++ b/tests/semanticdb/expect/JavaStaticVar.expect.scala @@ -0,0 +1,7 @@ +package example + +import com.javacp.JavaStaticVar/*->com::javacp::JavaStaticVar#*/ + +class ScalaFoo/*<-example::ScalaFoo#*/ { + val javaStaticVarFoo/*<-example::ScalaFoo#javaStaticVarFoo.*/ = JavaStaticVar/*->com::javacp::JavaStaticVar#*/.foo/*->com::javacp::JavaStaticVar#foo.*/ +} diff --git a/tests/semanticdb/expect/JavaStaticVar.scala b/tests/semanticdb/expect/JavaStaticVar.scala new file mode 100644 index 000000000000..7a7efeaa670a --- /dev/null +++ b/tests/semanticdb/expect/JavaStaticVar.scala @@ -0,0 +1,7 @@ +package example + +import com.javacp.JavaStaticVar + +class ScalaFoo { + val javaStaticVarFoo = JavaStaticVar.foo +} diff --git a/tests/semanticdb/expect/Synthetic.expect.scala b/tests/semanticdb/expect/Synthetic.expect.scala index c8ccb2281cbb..5286745100a7 100644 --- a/tests/semanticdb/expect/Synthetic.expect.scala +++ b/tests/semanticdb/expect/Synthetic.expect.scala @@ -30,7 +30,7 @@ class Synthetic/*<-example::Synthetic#*/ { null.asInstanceOf/*->scala::Any#asInstanceOf().*/[Int/*->scala::Int#*/ => Int/*->scala::Int#*/](2) } - class J/*<-example::Synthetic#J#*/[T/*<-example::Synthetic#J#[T]*/: /*<-example::Synthetic#J#evidence$1.*/Manifest/*->scala::Predef.Manifest#*//*->example::Synthetic#J#[T]*/] { val arr/*<-example::Synthetic#J#arr.*/ = Array/*->scala::Array.*/.empty/*->scala::Array.empty().*/[T/*->example::Synthetic#J#[T]*/] } + class J/*<-example::Synthetic#J#*/[T/*<-example::Synthetic#J#[T]*/: Manifest/*->scala::Predef.Manifest#*//*->example::Synthetic#J#[T]*//*<-example::Synthetic#J#evidence$1.*/] { val arr/*<-example::Synthetic#J#arr.*/ = Array/*->scala::Array.*/.empty/*->scala::Array.empty().*/[T/*->example::Synthetic#J#[T]*/] } class F/*<-example::Synthetic#F#*/ implicit val ordering/*<-example::Synthetic#ordering.*/: Ordering/*->scala::package.Ordering#*/[F/*->example::Synthetic#F#*/] = ???/*->scala::Predef.`???`().*/ diff --git a/tests/semanticdb/javacp/com/javacp/JavaStaticVar.java b/tests/semanticdb/javacp/com/javacp/JavaStaticVar.java new file mode 100644 index 000000000000..1124c8768421 --- /dev/null +++ b/tests/semanticdb/javacp/com/javacp/JavaStaticVar.java @@ -0,0 +1,5 @@ +package com.javacp; + +public class JavaStaticVar { + public static int foo = 0; +} diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 26221899035b..f674c6fb4159 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -8,7 +8,7 @@ Text => empty Language => Scala Symbols => 9 entries Occurrences => 19 entries -Diagnostics => 4 entries +Diagnostics => 2 entries Symbols: example/Access# => class Access extends Object { self: Access => +8 decls } @@ -43,12 +43,10 @@ Occurrences: [9:11..9:14): ??? -> scala/Predef.`???`(). Diagnostics: -[3:14..3:16): [warning] unused private member [4:16..4:16): [warning] Ignoring [this] qualifier. This syntax will be deprecated in the future; it should be dropped. See: https://docs.scala-lang.org/scala3/reference/dropped-features/this-qualifier.html This construct can be rewritten automatically under -rewrite -source 3.4-migration. -[4:20..4:22): [warning] unused private member [7:18..7:18): [warning] Ignoring [this] qualifier. This syntax will be deprecated in the future; it should be dropped. See: https://docs.scala-lang.org/scala3/reference/dropped-features/this-qualifier.html @@ -575,7 +573,7 @@ Text => empty Language => Scala Symbols => 108 entries Occurrences => 127 entries -Diagnostics => 6 entries +Diagnostics => 11 entries Synthetics => 2 entries Symbols: @@ -818,6 +816,7 @@ Occurrences: [53:10..53:11): + -> scala/Int#`+`(+4). Diagnostics: +[13:20..13:21): [warning] unused explicit parameter [18:9..18:10): [warning] unused explicit parameter [20:23..20:23): [warning] Ignoring [this] qualifier. This syntax will be deprecated in the future; it should be dropped. @@ -830,6 +829,10 @@ See: https://docs.scala-lang.org/scala3/reference/dropped-features/this-qualifie This construct can be rewritten automatically under -rewrite -source 3.4-migration. [22:27..22:28): [warning] unused explicit parameter [24:10..24:11): [warning] unused explicit parameter +[36:11..36:12): [warning] unused explicit parameter +[39:11..39:12): [warning] unused explicit parameter +[39:19..39:20): [warning] unused explicit parameter +[51:30..51:31): [warning] unused explicit parameter Synthetics: [51:16..51:27):List(1).map => *[Int] @@ -2095,6 +2098,7 @@ Text => empty Language => Scala Symbols => 45 entries Occurrences => 66 entries +Diagnostics => 1 entries Synthetics => 3 entries Symbols: @@ -2212,6 +2216,9 @@ Occurrences: [41:8..41:17): given_Z_T -> givens/InventedNames$package.given_Z_T(). [41:18..41:24): String -> scala/Predef.String# +Diagnostics: +[24:13..24:13): [warning] unused implicit parameter + Synthetics: [24:0..24:0): => *(x$1) [34:8..34:20):given_Double => *(intValue) @@ -2269,6 +2276,33 @@ Synthetics: [8:2..8:10):(x1, x1) => *(Tuple2(Int, Int)) [8:10..8:10): => *(Int, Int) +expect/JavaStaticVar.scala +-------------------------- + +Summary: +Schema => SemanticDB v4 +Uri => JavaStaticVar.scala +Text => empty +Language => Scala +Symbols => 3 entries +Occurrences => 9 entries + +Symbols: +example/ScalaFoo# => class ScalaFoo extends Object { self: ScalaFoo => +2 decls } +example/ScalaFoo#``(). => primary ctor (): ScalaFoo +example/ScalaFoo#javaStaticVarFoo. => val method javaStaticVarFoo Int + +Occurrences: +[0:8..0:15): example <- example/ +[2:7..2:10): com -> com/ +[2:11..2:17): javacp -> com/javacp/ +[2:18..2:31): JavaStaticVar -> com/javacp/JavaStaticVar# +[4:6..4:14): ScalaFoo <- example/ScalaFoo# +[5:2..5:2): <- example/ScalaFoo#``(). +[5:6..5:22): javaStaticVarFoo <- example/ScalaFoo#javaStaticVarFoo. +[5:25..5:38): JavaStaticVar -> com/javacp/JavaStaticVar# +[5:39..5:42): foo -> com/javacp/JavaStaticVar#foo. + expect/Local.scala ------------------ @@ -2734,7 +2768,7 @@ Occurrences: [17:9..17:10): U <- example/Methods#m7().[U] [17:12..17:20): Ordering -> scala/math/Ordering# [17:12..17:20): Ordering -> example/Methods#m7().[U] -[17:12..17:12): <- example/Methods#m7().(evidence$1) +[17:20..17:20): <- example/Methods#m7().(evidence$1) [17:22..17:23): c <- example/Methods#m7().(c) [17:25..17:32): Methods -> example/Methods# [17:33..17:34): T -> example/Methods#[T] @@ -3533,6 +3567,7 @@ Text => empty Language => Scala Symbols => 62 entries Occurrences => 165 entries +Diagnostics => 3 entries Synthetics => 39 entries Symbols: @@ -3668,9 +3703,9 @@ Occurrences: [32:8..32:9): J <- example/Synthetic#J# [32:9..32:9): <- example/Synthetic#J#``(). [32:10..32:11): T <- example/Synthetic#J#[T] -[32:13..32:13): <- example/Synthetic#J#evidence$1. [32:13..32:21): Manifest -> scala/Predef.Manifest# [32:13..32:21): Manifest -> example/Synthetic#J#[T] +[32:21..32:21): <- example/Synthetic#J#evidence$1. [32:29..32:32): arr <- example/Synthetic#J#arr. [32:35..32:40): Array -> scala/Array. [32:41..32:46): empty -> scala/Array.empty(). @@ -3766,6 +3801,11 @@ Occurrences: [68:18..68:24): impure -> local20 [68:30..68:31): s -> local16 +Diagnostics: +[19:21..19:22): [warning] unused pattern variable +[41:4..41:5): [warning] unused pattern variable +[63:10..63:11): [warning] unused explicit parameter + Synthetics: [5:2..5:13):List(1).map => *[Int] [5:2..5:6):List => *.apply[Int] @@ -4267,7 +4307,6 @@ Text => empty Language => Scala Symbols => 8 entries Occurrences => 18 entries -Diagnostics => 1 entries Symbols: _empty_/Test_depmatch. => final object Test_depmatch extends Object { self: Test_depmatch.type => +4 decls } @@ -4299,9 +4338,6 @@ Occurrences: [6:19..6:20): U -> local0 [6:24..6:27): ??? -> scala/Predef.`???`(). -Diagnostics: -[6:8..6:9): [warning] unused local definition - expect/example-dir/FileInDir.scala ---------------------------------- @@ -4620,6 +4656,7 @@ Text => empty Language => Scala Symbols => 24 entries Occurrences => 63 entries +Diagnostics => 2 entries Symbols: _empty_/Copy# => trait Copy [typeparam In <: Txn[In], typeparam Out <: Txn[Out]] extends Object { self: Copy[In, Out] => +5 decls } @@ -4712,6 +4749,10 @@ Occurrences: [14:8..14:15): println -> scala/Predef.println(+1). [17:4..17:7): out -> local0 +Diagnostics: +[13:12..13:17): [warning] unused pattern variable +[13:28..13:34): [warning] unused pattern variable + expect/inlineconsume.scala -------------------------- @@ -5006,7 +5047,7 @@ Text => empty Language => Scala Symbols => 50 entries Occurrences => 78 entries -Diagnostics => 4 entries +Diagnostics => 6 entries Synthetics => 2 entries Symbols: @@ -5146,6 +5187,8 @@ Diagnostics: [9:36..9:37): [warning] unused explicit parameter [9:42..9:43): [warning] unused explicit parameter [21:11..21:12): [warning] unused explicit parameter +[24:24..24:27): [warning] unused pattern variable +[25:27..25:28): [warning] unused pattern variable Synthetics: [23:6..23:10):List => *.unapplySeq[Nothing] @@ -5558,13 +5601,13 @@ Occurrences: [119:39..119:42): Int -> scala/Int# Diagnostics: -[5:13..5:14): [warning] unused explicit parameter [62:25..62:29): [warning] with as a type operator has been deprecated; use & instead This construct can be rewritten automatically under -rewrite -source 3.4-migration. [63:25..63:29): [warning] with as a type operator has been deprecated; use & instead This construct can be rewritten automatically under -rewrite -source 3.4-migration. [71:31..71:31): [warning] `_` is deprecated for wildcard arguments of types: use `?` instead This construct can be rewritten automatically under -rewrite -source 3.4-migration. +[96:13..96:14): [warning] unused explicit parameter Synthetics: [68:20..68:24):@ann => *[Int] diff --git a/tests/untried/neg/class-of-double-targs.check b/tests/untried/neg/class-of-double-targs.check deleted file mode 100644 index f7e2094f9778..000000000000 --- a/tests/untried/neg/class-of-double-targs.check +++ /dev/null @@ -1,4 +0,0 @@ -class-of-double-targs.scala:2: error: expression of type Class[Int](classOf[scala.Int]) does not take type parameters. - classOf[Int][Int] - ^ -one error found diff --git a/tests/untried/neg/class-of-double-targs.scala b/tests/untried/neg/class-of-double-targs.scala deleted file mode 100644 index 26a2fa838136..000000000000 --- a/tests/untried/neg/class-of-double-targs.scala +++ /dev/null @@ -1,3 +0,0 @@ -object Test { - classOf[Int][Int] -} diff --git a/tests/untried/neg/t0842.check b/tests/untried/neg/t0842.check deleted file mode 100644 index 3351aa1174ce..000000000000 --- a/tests/untried/neg/t0842.check +++ /dev/null @@ -1,4 +0,0 @@ -t0842.scala:1: error: A.this.type does not take type parameters -trait A[T] { def m: this.type[T] = this } - ^ -one error found diff --git a/tests/untried/neg/t0842.scala b/tests/untried/neg/t0842.scala deleted file mode 100644 index f32c2ba26d71..000000000000 --- a/tests/untried/neg/t0842.scala +++ /dev/null @@ -1 +0,0 @@ -trait A[T] { def m: this.type[T] = this } diff --git a/tests/untried/neg/t1701.check b/tests/untried/neg/t1701.check deleted file mode 100644 index d603e62e5a6a..000000000000 --- a/tests/untried/neg/t1701.check +++ /dev/null @@ -1,4 +0,0 @@ -t1701.scala:1: error: Cloneable does not take type parameters -class A extends java.lang.Cloneable[String, Option, Int] - ^ -one error found diff --git a/tests/untried/neg/t1701.scala b/tests/untried/neg/t1701.scala deleted file mode 100644 index 7cd6ff9953be..000000000000 --- a/tests/untried/neg/t1701.scala +++ /dev/null @@ -1 +0,0 @@ -class A extends java.lang.Cloneable[String, Option, Int] diff --git a/tests/untried/neg/t278.check b/tests/untried/neg/t278.check deleted file mode 100644 index 405f7d225c17..000000000000 --- a/tests/untried/neg/t278.check +++ /dev/null @@ -1,11 +0,0 @@ -t278.scala:5: error: overloaded method value a with alternatives: - => C.this.A => Unit - => () => Unit - does not take type parameters - println(a[A]) - ^ -t278.scala:4: error: method a is defined twice - conflicting symbols both originated in file 't278.scala' - def a = (p:A) => () - ^ -two errors found diff --git a/tests/untried/neg/t278.scala b/tests/untried/neg/t278.scala deleted file mode 100644 index 39a711bb0985..000000000000 --- a/tests/untried/neg/t278.scala +++ /dev/null @@ -1,6 +0,0 @@ -class C { - class A - def a = () => () - def a = (p:A) => () - println(a[A]) -} diff --git a/tests/untried/neg/t2918.check b/tests/untried/neg/t2918.check deleted file mode 100644 index aae3045e8af7..000000000000 --- a/tests/untried/neg/t2918.check +++ /dev/null @@ -1,10 +0,0 @@ -t2918.scala:2: error: illegal cyclic reference involving type A - def g[X, A[X] <: A[X]](x: A[X]) = x - ^ -t2918.scala:2: error: cyclic aliasing or subtyping involving type A - def g[X, A[X] <: A[X]](x: A[X]) = x - ^ -t2918.scala:2: error: A does not take type parameters - def g[X, A[X] <: A[X]](x: A[X]) = x - ^ -three errors found diff --git a/tests/untried/neg/t2918.scala b/tests/untried/neg/t2918.scala deleted file mode 100644 index ff2be39ae0c4..000000000000 --- a/tests/untried/neg/t2918.scala +++ /dev/null @@ -1,3 +0,0 @@ -object Test { - def g[X, A[X] <: A[X]](x: A[X]) = x -} diff --git a/tests/untried/neg/t5093.check b/tests/untried/neg/t5093.check deleted file mode 100644 index daba46001153..000000000000 --- a/tests/untried/neg/t5093.check +++ /dev/null @@ -1,10 +0,0 @@ -t5093.scala:2: error: illegal cyclic reference involving type C - def f[C[X] <: C[X]](l: C[_]) = l.x - ^ -t5093.scala:2: error: cyclic aliasing or subtyping involving type C - def f[C[X] <: C[X]](l: C[_]) = l.x - ^ -t5093.scala:2: error: C does not take type parameters - def f[C[X] <: C[X]](l: C[_]) = l.x - ^ -three errors found diff --git a/tests/untried/neg/t5093.scala b/tests/untried/neg/t5093.scala deleted file mode 100644 index 9cde364dee4c..000000000000 --- a/tests/untried/neg/t5093.scala +++ /dev/null @@ -1,3 +0,0 @@ -class T { - def f[C[X] <: C[X]](l: C[_]) = l.x -} diff --git a/tests/untried/neg/t8219-any-any-ref-equals.check b/tests/untried/neg/t8219-any-any-ref-equals.check deleted file mode 100644 index 95d2536fba06..000000000000 --- a/tests/untried/neg/t8219-any-any-ref-equals.check +++ /dev/null @@ -1,10 +0,0 @@ -t8219-any-any-ref-equals.scala:5: error: method ==: (x$1: Any)Boolean does not take type parameters. - "".==[Int] - ^ -t8219-any-any-ref-equals.scala:6: error: method ==: (x$1: Any)Boolean does not take type parameters. - ("": AnyRef).==[Int] - ^ -t8219-any-any-ref-equals.scala:7: error: method ==: (x$1: Any)Boolean does not take type parameters. - ("": Object).==[Int] - ^ -three errors found diff --git a/tests/untried/neg/t8219-any-any-ref-equals.scala b/tests/untried/neg/t8219-any-any-ref-equals.scala deleted file mode 100644 index f1b81fa734c1..000000000000 --- a/tests/untried/neg/t8219-any-any-ref-equals.scala +++ /dev/null @@ -1,8 +0,0 @@ -object Test { - // The error message tells us that AnyRef#== and Any#== are overloaded. - // A real class couldn't define such an overload, why do we allow AnyRef - // to do so? - "".==[Int] - ("": AnyRef).==[Int] - ("": Object).==[Int] -} diff --git a/tests/untried/neg/warn-unused-imports.check b/tests/untried/neg/warn-unused-imports.check deleted file mode 100644 index 36c6dd03c38d..000000000000 --- a/tests/untried/neg/warn-unused-imports.check +++ /dev/null @@ -1,33 +0,0 @@ -warn-unused-imports.scala:57: warning: Unused import - import p1.A // warn - ^ -warn-unused-imports.scala:62: warning: Unused import - import p1.{ A, B } // warn on A - ^ -warn-unused-imports.scala:67: warning: Unused import - import p1.{ A, B } // warn on both - ^ -warn-unused-imports.scala:67: warning: Unused import - import p1.{ A, B } // warn on both - ^ -warn-unused-imports.scala:73: warning: Unused import - import c._ // warn - ^ -warn-unused-imports.scala:78: warning: Unused import - import p1._ // warn - ^ -warn-unused-imports.scala:85: warning: Unused import - import c._ // warn - ^ -warn-unused-imports.scala:91: warning: Unused import - import p1.c._ // warn - ^ -warn-unused-imports.scala:98: warning: Unused import - import p1._ // warn - ^ -warn-unused-imports.scala:118: warning: Unused import - import p1.A // warn - ^ -error: No warnings can be incurred under -Xfatal-warnings. -10 warnings found -one error found diff --git a/tests/untried/neg/warn-unused-imports.flags b/tests/untried/neg/warn-unused-imports.flags deleted file mode 100644 index 24db705df164..000000000000 --- a/tests/untried/neg/warn-unused-imports.flags +++ /dev/null @@ -1 +0,0 @@ --Xfatal-warnings -Ywarn-unused-import diff --git a/tests/untried/neg/warn-unused-imports.scala b/tests/untried/neg/warn-unused-imports.scala deleted file mode 100644 index 4b1c1183dcba..000000000000 --- a/tests/untried/neg/warn-unused-imports.scala +++ /dev/null @@ -1,125 +0,0 @@ -class Bippo { - def length: Int = 123 - class Tree -} - -package object p1 { - class A - implicit class B(val s: String) { def bippy = s } - val c: Bippo = new Bippo - type D = String -} -package object p2 { - class A - implicit class B(val s: String) { def bippy = s } - val c: Bippo = new Bippo - type D = Int -} - -trait NoWarn { - { - import p1._ // no warn - println("abc".bippy) - } - - { - import p1._ // no warn - println(new A) - } - - { - import p1.B // no warn - println("abc".bippy) - } - - { - import p1._ // no warn - import c._ // no warn - println(length) - } - - { - import p1._ // no warn - import c._ // no warn - val x: Tree = null - println(x) - } - - { - import p1.D // no warn - val x: D = null - println(x) - } -} - -trait Warn { - { - import p1.A // warn - println(123) - } - - { - import p1.{ A, B } // warn on A - println("abc".bippy) - } - - { - import p1.{ A, B } // warn on both - println(123) - } - - { - import p1._ // no warn (technically this could warn, but not worth the effort to unroll unusedness transitively) - import c._ // warn - println(123) - } - - { - import p1._ // warn - println(123) - } - - { - class Tree - import p1._ // no warn - import c._ // warn - val x: Tree = null - println(x) - } - - { - import p1.c._ // warn - println(123) - } -} - -trait Nested { - { - import p1._ // warn - trait Warn { // warn about unused local trait for good measure - import p2.* - println(new A) - println("abc".bippy) - } - println("") - } - - { - import p1._ // no warn - trait NoWarn { - import p2.B // no warn - println("abc".bippy) - println(new A) - } - println(new NoWarn { }) - } - - { - import p1.A // warn - trait Warn { - import p2.A - println(new A) - } - println(new Warn { }) - } -} diff --git a/tests/untried/neg/warn-unused-privates.check b/tests/untried/neg/warn-unused-privates.check deleted file mode 100644 index d012869c934a..000000000000 --- a/tests/untried/neg/warn-unused-privates.check +++ /dev/null @@ -1,66 +0,0 @@ -warn-unused-privates.scala:2: warning: private constructor in class Bippy is never used - private def this(c: Int) = this(c, c) // warn - ^ -warn-unused-privates.scala:4: warning: private method in class Bippy is never used - private def boop(x: Int) = x+a+b // warn - ^ -warn-unused-privates.scala:6: warning: private val in class Bippy is never used - final private val MILLIS2: Int = 1000 // warn - ^ -warn-unused-privates.scala:13: warning: private val in object Bippy is never used - private val HEY_INSTANCE: Int = 1000 // warn - ^ -warn-unused-privates.scala:35: warning: private val in class Boppy is never used - private val hummer = "def" // warn - ^ -warn-unused-privates.scala:42: warning: private var in trait Accessors is never used - private var v1: Int = 0 // warn - ^ -warn-unused-privates.scala:42: warning: private setter in trait Accessors is never used - private var v1: Int = 0 // warn - ^ -warn-unused-privates.scala:43: warning: private setter in trait Accessors is never used - private var v2: Int = 0 // warn, never set - ^ -warn-unused-privates.scala:44: warning: private var in trait Accessors is never used - private var v3: Int = 0 // warn, never got - ^ -warn-unused-privates.scala:56: warning: private default argument in trait DefaultArgs is never used - private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 - ^ -warn-unused-privates.scala:56: warning: private default argument in trait DefaultArgs is never used - private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 - ^ -warn-unused-privates.scala:67: warning: local var in method f0 is never used - var x = 1 // warn - ^ -warn-unused-privates.scala:74: warning: local val in method f1 is never used - val b = new Outer // warn - ^ -warn-unused-privates.scala:84: warning: private object in object Types is never used - private object Dongo { def f = this } // warn - ^ -warn-unused-privates.scala:94: warning: local object in method l1 is never used - object HiObject { def f = this } // warn - ^ -warn-unused-privates.scala:78: warning: local var x in method f2 is never set - it could be a val - var x = 100 // warn about it being a var - ^ -warn-unused-privates.scala:85: warning: private class Bar1 in object Types is never used - private class Bar1 // warn - ^ -warn-unused-privates.scala:87: warning: private type Alias1 in object Types is never used - private type Alias1 = String // warn - ^ -warn-unused-privates.scala:95: warning: local class Hi is never used - class Hi { // warn - ^ -warn-unused-privates.scala:99: warning: local class DingDongDoobie is never used - class DingDongDoobie // warn - ^ -warn-unused-privates.scala:102: warning: local type OtherThing is never used - type OtherThing = String // warn - ^ -error: No warnings can be incurred under -Xfatal-warnings. -21 warnings found -one error found diff --git a/tests/untried/neg/warn-unused-privates.flags b/tests/untried/neg/warn-unused-privates.flags deleted file mode 100644 index 25474aefb362..000000000000 --- a/tests/untried/neg/warn-unused-privates.flags +++ /dev/null @@ -1 +0,0 @@ --Ywarn-unused -Xfatal-warnings diff --git a/tests/untried/neg/warn-unused-privates.scala b/tests/untried/neg/warn-unused-privates.scala deleted file mode 100644 index 64e7679f37ac..000000000000 --- a/tests/untried/neg/warn-unused-privates.scala +++ /dev/null @@ -1,105 +0,0 @@ -class Bippy(a: Int, b: Int) { - private def this(c: Int) = this(c, c) // warn - private def bippy(x: Int): Int = bippy(x) // TODO: could warn - private def boop(x: Int) = x + a + b // warn - final private val MILLIS1 = 2000 // no warn, might have been inlined - final private val MILLIS2: Int = 1000 // warn - final private val HI_COMPANION: Int = 500 // no warn, accessed from companion - def hi() = Bippy.HI_INSTANCE -} -object Bippy { - def hi(x: Bippy) = x.HI_COMPANION - private val HI_INSTANCE: Int = 500 // no warn, accessed from instance - private val HEY_INSTANCE: Int = 1000 // warn -} - -class A(val msg: String) -class B1(msg: String) extends A(msg) -class B2(msg0: String) extends A(msg0) -class B3(msg0: String) extends A("msg") - -/*** Early defs warnings disabled primarily due to SI-6595. - * The test case is here to assure we aren't issuing false positives; - * the ones labeled "warn" don't warn. - ***/ -class Boppy extends { - private val hmm: String = "abc" // no warn, used in early defs - private val hom: String = "def" // no warn, used in body - private final val him = "ghi" // no warn, might have been (was) inlined - final val him2 = "ghi" // no warn, same - final val himinline = him - private val hum: String = "jkl" // warn - final val ding = hmm.length -} with Mutable { - val dinger = hom - private val hummer = "def" // warn - - private final val bum = "ghi" // no warn, might have been (was) inlined - final val bum2 = "ghi" // no warn, same -} - -trait Accessors { - private var v1: Int = 0 // warn - private var v2: Int = 0 // warn, never set - private var v3: Int = 0 // warn, never got - private var v4: Int = 0 // no warn - - def bippy(): Int = { - v3 = 5 - v4 = 6 - v2 + v4 - } -} - -trait DefaultArgs { - // warn about default getters for x2 and x3 - private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 - - def boppy() = bippy(5, 100, 200) -} - -class Outer { - class Inner -} - -trait Locals { - def f0 = { - var x = 1 // warn - var y = 2 - y = 3 - y + y - } - def f1 = { - val a = new Outer // no warn - val b = new Outer // warn - new a.Inner - } - def f2 = { - var x = 100 // warn about it being a var - x - } -} - -object Types { - private object Dongo { def f = this } // warn - private class Bar1 // warn - private class Bar2 // no warn - private type Alias1 = String // warn - private type Alias2 = String // no warn - def bippo = (new Bar2).toString - - def f(x: Alias2) = x.length - - def l1() = { - object HiObject { def f = this } // warn - class Hi { // warn - def f1: Hi = new Hi - def f2(x: Hi) = x - } - class DingDongDoobie // warn - class Bippy // no warn - type Something = Bippy // no warn - type OtherThing = String // warn - (new Bippy): Something - } -} diff --git a/tests/untried/neg/wellkinded_app.check b/tests/untried/neg/wellkinded_app.check deleted file mode 100644 index d57a0e4b5648..000000000000 --- a/tests/untried/neg/wellkinded_app.check +++ /dev/null @@ -1,4 +0,0 @@ -wellkinded_app.scala:3: error: x does not take type parameters - type t = x[x] - ^ -one error found diff --git a/tests/untried/neg/wellkinded_app.scala b/tests/untried/neg/wellkinded_app.scala deleted file mode 100644 index 7fa3f95a9848..000000000000 --- a/tests/untried/neg/wellkinded_app.scala +++ /dev/null @@ -1,4 +0,0 @@ -// test well-kindedness checks -class WellKinded[x] { - type t = x[x] -} diff --git a/tests/untried/neg/wellkinded_app2.check b/tests/untried/neg/wellkinded_app2.check deleted file mode 100644 index 20a177ea590e..000000000000 --- a/tests/untried/neg/wellkinded_app2.check +++ /dev/null @@ -1,4 +0,0 @@ -wellkinded_app2.scala:3: error: s does not take type parameters - val foo: s[Int] - ^ -one error found diff --git a/tests/untried/neg/wellkinded_app2.scala b/tests/untried/neg/wellkinded_app2.scala deleted file mode 100644 index 7bb4c87f6b17..000000000000 --- a/tests/untried/neg/wellkinded_app2.scala +++ /dev/null @@ -1,4 +0,0 @@ -// test well-kindedness checks -class WellKinded[s <: Throwable] { - val foo: s[Int] -} diff --git a/tests/untried/neg/wellkinded_bounds.check b/tests/untried/neg/wellkinded_bounds.check deleted file mode 100644 index 806eb09a76d7..000000000000 --- a/tests/untried/neg/wellkinded_bounds.check +++ /dev/null @@ -1,4 +0,0 @@ -wellkinded_bounds.scala:2: error: type List takes type parameters -class WellKindedWrongSyntax[s <: List] { // must be s[x] <: List[x] - ^ -one error found diff --git a/tests/untried/neg/wellkinded_bounds.scala b/tests/untried/neg/wellkinded_bounds.scala deleted file mode 100644 index cfa5e74c115f..000000000000 --- a/tests/untried/neg/wellkinded_bounds.scala +++ /dev/null @@ -1,3 +0,0 @@ -// test well-kindedness checks -- syntax error -class WellKindedWrongSyntax[s <: List] { // must be s[x] <: List[x] -} diff --git a/tests/untried/neg/wellkinded_wrongarity.check b/tests/untried/neg/wellkinded_wrongarity.check deleted file mode 100644 index b9f033b4536b..000000000000 --- a/tests/untried/neg/wellkinded_wrongarity.check +++ /dev/null @@ -1,4 +0,0 @@ -wellkinded_wrongarity.scala:5: error: Tuple2 takes two type parameters, expected: one -object mp extends Monad[Tuple2] - ^ -one error found diff --git a/tests/untried/neg/wellkinded_wrongarity.scala b/tests/untried/neg/wellkinded_wrongarity.scala deleted file mode 100644 index 39c7601d53a3..000000000000 --- a/tests/untried/neg/wellkinded_wrongarity.scala +++ /dev/null @@ -1,5 +0,0 @@ -// test well-kindedness checks -- arity error - -class Monad[m[x]] - -object mp extends Monad[Tuple2] diff --git a/tests/untried/neg/wellkinded_wrongarity2.check b/tests/untried/neg/wellkinded_wrongarity2.check deleted file mode 100644 index 922f73381e29..000000000000 --- a/tests/untried/neg/wellkinded_wrongarity2.check +++ /dev/null @@ -1,13 +0,0 @@ -wellkinded_wrongarity2.scala:5: error: String takes no type parameters, expected: one -trait ms1 extends Monad[String] // wrong - ^ -wellkinded_wrongarity2.scala:6: error: t takes no type parameters, expected: one -trait ms2[t] extends Monad[t] // wrong - ^ -wellkinded_wrongarity2.scala:7: error: m[t] takes no type parameters, expected: one -trait ms3[m[_], t] extends Monad[m[t]] // wrong -- added to check regression on bug - ^ -wellkinded_wrongarity2.scala:12: error: type m takes type parameters -trait Bar2[m[_]] extends Foo[m] // check that m is properly recognized as kind *->*, while * is expected - ^ -four errors found diff --git a/tests/untried/neg/wellkinded_wrongarity2.scala b/tests/untried/neg/wellkinded_wrongarity2.scala deleted file mode 100644 index aac617bd8770..000000000000 --- a/tests/untried/neg/wellkinded_wrongarity2.scala +++ /dev/null @@ -1,12 +0,0 @@ -// test well-kindedness checks - -// expecting types of kind *->* -class Monad[m[x]] -trait ms1 extends Monad[String] // wrong -trait ms2[t] extends Monad[t] // wrong -trait ms3[m[_], t] extends Monad[m[t]] // wrong -- added to check regression on bug - -// expecting types of kind * -trait Foo[x] -trait Bar1[m[_]] extends Foo[m[Int]] // check that m[Int] is properly recognized as kind-* -trait Bar2[m[_]] extends Foo[m] // check that m is properly recognized as kind *->*, while * is expected diff --git a/tests/warn/21681.check b/tests/warn/21681.check index adf3586e6e0b..70c8e6e2170c 100644 --- a/tests/warn/21681.check +++ b/tests/warn/21681.check @@ -1,7 +1,7 @@ --- [E203] Syntax Migration Warning: tests/warn/21681.scala:5:2 --------------------------------------------------------- -5 | (age = 29) // warn +-- [E203] Syntax Migration Warning: tests/warn/21681.scala:3:2 --------------------------------------------------------- +3 | (age = 29) // warn | ^^^^^^^^^^ - | Deprecated syntax: in the future it would be interpreted as a named tuple with one element, + | Deprecated syntax: since 3.7 this is interpreted as a named tuple with one element, | not as an assignment. | | To assign a value, use curly braces: `{age = 29}`. diff --git a/tests/warn/21681.scala b/tests/warn/21681.scala index 67f45571ecf6..76a19c96e1cb 100644 --- a/tests/warn/21681.scala +++ b/tests/warn/21681.scala @@ -1,5 +1,3 @@ -import scala.language.experimental.namedTuples - def main() = var age: Int = 28 (age = 29) // warn diff --git a/tests/warn/21681b.check b/tests/warn/21681b.check index 09c007f351b4..b00dcae7cf04 100644 --- a/tests/warn/21681b.check +++ b/tests/warn/21681b.check @@ -1,7 +1,7 @@ --- [E203] Syntax Migration Warning: tests/warn/21681b.scala:5:2 -------------------------------------------------------- -5 | (age = 29) // warn +-- [E203] Syntax Migration Warning: tests/warn/21681b.scala:3:2 -------------------------------------------------------- +3 | (age = 29) // warn | ^^^^^^^^^^ - | Deprecated syntax: in the future it would be interpreted as a named tuple with one element, + | Deprecated syntax: since 3.7 this is interpreted as a named tuple with one element, | not as an assignment. | | To assign a value, use curly braces: `{age = 29}`. diff --git a/tests/warn/21681b.scala b/tests/warn/21681b.scala index 44d04fc98aad..710d69b0dd23 100644 --- a/tests/warn/21681b.scala +++ b/tests/warn/21681b.scala @@ -1,5 +1,3 @@ -import scala.language.experimental.namedTuples - object Test: var age: Int = 28 (age = 29) // warn diff --git a/tests/warn/21681c.check b/tests/warn/21681c.check index 20273f723384..1c42cc17243a 100644 --- a/tests/warn/21681c.check +++ b/tests/warn/21681c.check @@ -1,7 +1,7 @@ --- [E203] Syntax Migration Warning: tests/warn/21681c.scala:7:2 -------------------------------------------------------- -7 | (age = 29) // warn +-- [E203] Syntax Migration Warning: tests/warn/21681c.scala:5:2 -------------------------------------------------------- +5 | (age = 29) // warn | ^^^^^^^^^^ - | Deprecated syntax: in the future it would be interpreted as a named tuple with one element, + | Deprecated syntax: since 3.7 this is interpreted as a named tuple with one element, | not as an assignment. | | To assign a value, use curly braces: `{age = 29}`. diff --git a/tests/warn/21681c.scala b/tests/warn/21681c.scala index a0c361382a54..5e2eae11708c 100644 --- a/tests/warn/21681c.scala +++ b/tests/warn/21681c.scala @@ -1,5 +1,3 @@ -import scala.language.experimental.namedTuples - object Test: def age: Int = ??? def age_=(x: Int): Unit = () diff --git a/tests/warn/21770.check b/tests/warn/21770.check index 7853d77a423c..65d3cdcbbfff 100644 --- a/tests/warn/21770.check +++ b/tests/warn/21770.check @@ -1,7 +1,7 @@ --- [E203] Syntax Migration Warning: tests/warn/21770.scala:7:9 --------------------------------------------------------- -7 | f(i => (cache = Some(i))) // warn +-- [E203] Syntax Migration Warning: tests/warn/21770.scala:5:9 --------------------------------------------------------- +5 | f(i => (cache = Some(i))) // warn | ^^^^^^^^^^^^^^^^^ - | Deprecated syntax: in the future it would be interpreted as a named tuple with one element, + | Deprecated syntax: since 3.7 this is interpreted as a named tuple with one element, | not as an assignment. | | To assign a value, use curly braces: `{cache = Some(i)}`. diff --git a/tests/warn/21770.scala b/tests/warn/21770.scala index 8ee5b52e7b3f..9696a31d6ba8 100644 --- a/tests/warn/21770.scala +++ b/tests/warn/21770.scala @@ -1,7 +1,5 @@ -import scala.language.experimental.namedTuples - def f(g: Int => Unit) = g(0) -def test = +def test = var cache: Option[Int] = None f(i => (cache = Some(i))) // warn diff --git a/tests/warn/deprecated-origin-verbose.check b/tests/warn/deprecated-origin-verbose.check index e67efaf8668d..db0ee26fd779 100644 --- a/tests/warn/deprecated-origin-verbose.check +++ b/tests/warn/deprecated-origin-verbose.check @@ -2,13 +2,13 @@ 12 | class D extends C // warn | ^ | class C in package p is deprecated since 1.0: Old style -Matching filters for @nowarn or -Wconf: - - cat=deprecation - - origin=p.C + |Matching filters for @nowarn or -Wconf: + | - cat=deprecation + | - origin=p.C -- Deprecation Warning: tests/warn/deprecated-origin-verbose.scala:13:20 ----------------------------------------------- 13 | class Oil extends Crude // warn | ^^^^^ | class Crude in package p is deprecated since 1.0: Bad style -Matching filters for @nowarn or -Wconf: - - cat=deprecation - - origin=p.Crude + |Matching filters for @nowarn or -Wconf: + | - cat=deprecation + | - origin=p.Crude diff --git a/tests/pos/ext-override.scala b/tests/warn/ext-override.scala similarity index 82% rename from tests/pos/ext-override.scala rename to tests/warn/ext-override.scala index 7c082695cbaa..e5933350fdb9 100644 --- a/tests/pos/ext-override.scala +++ b/tests/warn/ext-override.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings +//> using options -Werror trait Foo[T]: extension (x: T) diff --git a/tests/warn/i12460.check b/tests/warn/i12460.check new file mode 100644 index 000000000000..ba44bed1a18f --- /dev/null +++ b/tests/warn/i12460.check @@ -0,0 +1,24 @@ +-- [E208] Potential Issue Warning: tests/warn/i12460.scala:3:23 -------------------------------------------------------- +3 |extension (s: String = "hello, world") def invert = s.reverse.toUpperCase // warn + | ^ + | Extension method invert should not have a default argument for its receiver. + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | The receiver cannot be omitted when an extension method is invoked as a selection. + | A default argument for that parameter would never be used in that case. + | An extension method can be invoked as a regular method, but if that is the intended usage, + | it should not be defined as an extension. + --------------------------------------------------------------------------------------------------------------------- +-- [E208] Potential Issue Warning: tests/warn/i12460.scala:5:37 -------------------------------------------------------- +5 |extension (using String)(s: String = "hello, world") def revert = s.reverse.toUpperCase // warn + | ^ + | Extension method revert should not have a default argument for its receiver. + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | The receiver cannot be omitted when an extension method is invoked as a selection. + | A default argument for that parameter would never be used in that case. + | An extension method can be invoked as a regular method, but if that is the intended usage, + | it should not be defined as an extension. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/warn/i12460.scala b/tests/warn/i12460.scala new file mode 100644 index 000000000000..0e37c6aa7de7 --- /dev/null +++ b/tests/warn/i12460.scala @@ -0,0 +1,9 @@ +//> using options -explain -Ystop-after:refchecks + +extension (s: String = "hello, world") def invert = s.reverse.toUpperCase // warn + +extension (using String)(s: String = "hello, world") def revert = s.reverse.toUpperCase // warn + +extension (s: String) + def divert(m: String = "hello, world") = (s+m).reverse.toUpperCase // ok + def divertimento(using String)(m: String = "hello, world") = (s+m).reverse.toUpperCase // ok diff --git a/tests/pos/i15226.scala b/tests/warn/i15226.scala similarity index 100% rename from tests/pos/i15226.scala rename to tests/warn/i15226.scala diff --git a/tests/warn/i15503a.scala b/tests/warn/i15503a.scala index df8691c21a13..884261e140a7 100644 --- a/tests/warn/i15503a.scala +++ b/tests/warn/i15503a.scala @@ -1,5 +1,4 @@ -//> using options -Wunused:imports - +//> using options -Wunused:imports -Wconf:origin=Suppressed.*:s object FooUnused: import collection.mutable.Set // warn @@ -68,7 +67,7 @@ object InlineChecks: inline def getSet = Set(1) object InlinedBar: - import collection.mutable.Set // ok + import collection.mutable.Set // warn (don't be fooled by inline expansion) import collection.mutable.Map // warn val a = InlineFoo.getSet @@ -100,20 +99,28 @@ object SomeGivenImports: given String = "foo" /* BEGIN : Check on packages*/ -package testsamepackageimport: - package p { +package nestedpackageimport: + package p: class C - } - - package p { - import p._ // warn - package q { - class U { + package p: + package q: + import p.* // warn + class U: def f = new C - } - } - } -// ----------------------- +package unnestedpackageimport: + package p: + class C + package p.q: + import p.* // nowarn + class U: + def f = new C + +package redundancy: + object redundant: + def f = 42 + import redundancy.* // warn superseded by def in scope + class R: + def g = redundant.f package testpackageimport: package a: @@ -213,7 +220,8 @@ package testImportsInImports: package c: import a.b // OK import b.x // OK - val y = x + import b.x as z // OK + val y = x + z //------------------------------------- package testOnOverloadedMethodsImports: @@ -265,4 +273,190 @@ package foo.test.typeapply.hklamdba.i16680: import foo.IO // OK def f[F[_]]: String = "hello" - def go = f[IO] \ No newline at end of file + def go = f[IO] + +object Selections: + def f(list: List[Int]): Int = + import list.{head => first} // OK + first + + def f2(list: List[Int]): Int = + import list.head // OK + head + + def f3(list: List[Int]): Int = + import list.head // warn + list.head + + object N: + val ns: List[Int] = Nil + + def g(): Int = + import N.ns // OK + ns.head +end Selections + +object `more nestings`: + object Outer: + object Inner: + val thing = 42 + def j() = + import Inner.thing // warn + thing + def k() = + import Inner.thing // warn + Inner.thing + + object Thing: + object Inner: + val thing = 42 + import Inner.thing // warn + def j() = + thing + def k() = + Inner.thing + +object Suppressed: + val suppressed = 42 +object Suppressing: + import Suppressed.* // no warn, see options + def f = 42 + +package i22692: + import javax.swing.* + import javax.swing.event as swingEvent // no warn, regression test for warning in 3.6 + + type b = AbstractButton + type t = swingEvent.AncestorListener + +package ancient: + package p { + class Bippo { + def length: Int = 123 + class Tree + } + } + + package object p1 { + import p._ + class A + implicit class B(val s: String) { def bippy = s } + val c: Bippo = new Bippo + type D = String + } + package object p2 { + import p._ + class A + implicit class B(val s: String) { def bippy = s } + val c: Bippo = new Bippo + type D = Int + } + + trait NoWarn { + { + import p1._ // no warn + println("abc".bippy) + } + + { + import p1._ // no warn + println(new A) + } + + { + import p1.B // no warn + println("abc".bippy) + } + + { + import p1._ // no warn + import c._ // no warn + println(length) + } + + { + import p1._ // no warn + import c._ // no warn + val x: Tree = null + println(x) + } + + { + import p1.D // no warn + val x: D = null + println(x) + } + } + + trait Warn { + { + import p1.A // warn + println(123) + } + + { + import p1.{ A, B } // warn on A + println("abc".bippy) + } + + { + import p1.{ A, B } //warn // warn on both + println(123) + } + + { + import p1._ // no warn (technically this could warn, but not worth the effort to unroll unusedness transitively) + import c._ // warn + println(123) + } + + { + import p1._ // warn + println(123) + } + + { + class Tree + import p1._ // no warn + import c._ // warn + val x: Tree = null + println(x) + } + + { + import p1.c._ // warn + println(123) + } + } + + trait Nested { + { + import p1._ // warn + trait Warn { + import p2.* + println(new A) + println("abc".bippy) + } + println("") + } + + { + import p1._ // no warn + trait NoWarn { + import p2.B // no warn + println("abc".bippy) + println(new A) + } + println(new NoWarn { }) + } + + { + import p1.A // warn + trait Warn { + import p2.A + println(new A) + } + println(new Warn { }) + } + } +end ancient diff --git a/tests/warn/i15503b.scala b/tests/warn/i15503b.scala index 7ab86026ff00..0ea110f833f1 100644 --- a/tests/warn/i15503b.scala +++ b/tests/warn/i15503b.scala @@ -117,7 +117,7 @@ package foo.scala2.tests: object Types { def l1() = { - object HiObject { def f = this } // OK + object HiObject { def f = this } // warn class Hi { // warn def f1: Hi = new Hi def f2(x: Hi) = x @@ -141,4 +141,4 @@ package test.foo.twisted.i16682: } isInt - def f = myPackage("42") \ No newline at end of file + def f = myPackage("42") diff --git a/tests/warn/i15503c.scala b/tests/warn/i15503c.scala index a813329da89b..86b972487e17 100644 --- a/tests/warn/i15503c.scala +++ b/tests/warn/i15503c.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:privates -source:3.3 +//> using options -Wunused:privates -source:3.3 trait C class A: @@ -33,17 +33,22 @@ class A: var w = 2 // OK package foo.test.constructors: - case class A private (x:Int) // OK + case class A private (x: Int) // OK class B private (val x: Int) // OK - class C private (private val x: Int) // warn + object B { def default = B(42) } + class C private (private val xy: Int) // warn + object C { def default = C(42) } class D private (private val x: Int): // OK def y = x + object D { def default = D(42) } class E private (private var x: Int): // warn not set def y = x + object E { def default = E(42) } class F private (private var x: Int): // OK def y = x = 3 x + object F { def default = F(42) } package test.foo.i16682: object myPackage: @@ -55,4 +60,13 @@ package test.foo.i16682: case _ => println("NaN") } - def f = myPackage.isInt("42") \ No newline at end of file + def f = myPackage.isInt("42") + +object LazyVals: + import java.util.concurrent.CountDownLatch + + // This trait extends Serializable to fix #16806 that caused a race condition + sealed trait LazyValControlState extends Serializable + + final class Waiting extends CountDownLatch(1), LazyValControlState: + private def writeReplace(): Any = null diff --git a/tests/warn/i15503d.scala b/tests/warn/i15503d.scala index 9a0ba9b2f8dc..073b340637fa 100644 --- a/tests/warn/i15503d.scala +++ b/tests/warn/i15503d.scala @@ -1,5 +1,4 @@ -//> using options -Wunused:unsafe-warn-patvars -// todo : change to :patvars +//> using options -Wunused:patvars sealed trait Calc sealed trait Const extends Calc @@ -8,23 +7,117 @@ case class S(pred: Const) extends Const case object Z extends Const val a = Sum(S(S(Z)),Z) match { - case Sum(a,Z) => Z // warn + case Sum(x,Z) => Z // warn // case Sum(a @ _,Z) => Z // todo : this should pass in the future - case Sum(a@S(_),Z) => Z // warn - case Sum(a@S(_),Z) => a // warn unreachable - case Sum(a@S(b@S(_)), Z) => a // warn - case Sum(a@S(b@S(_)), Z) => a // warn - case Sum(a@S(b@(S(_))), Z) => Sum(a,b) // warn unreachable + case Sum(x@S(_),Z) => Z // warn + case Sum(x@S(_),Z) => x // warn unreachable + case Sum(x@S(y@S(_)), Z) => x // warn + case Sum(x@S(y@S(_)), Z) => x // warn + case Sum(x@S(y@(S(_))), Z) => Sum(x,y) // warn unreachable case Sum(_,_) => Z // OK case _ => Z // warn unreachable } -// todo : This should pass in the future -// val b = for { -// case Some(x) <- Option(Option(1)) -// } println(s"$x") +case class K(i: Int, j: Int) -// todo : This should *NOT* pass in the future -// val c = for { -// case Some(x) <- Option(Option(1)) -// } println(s"hello world") \ No newline at end of file +class C(c0: Option[Int], k0: K): + private val Some(c) = c0: @unchecked // warn valdef from pattern + private val K(i, j) = k0 // nowarn (name of case class element is nowarn) + val K(v, w) = k0 // nowarn nonprivate + private val K(r, s) = k0 // warn // warn valdefs from pattern + def f(x: Option[Int]) = x match + case Some(y) => true // warn Bind in pattern + case _ => false + def g(ns: List[Int]) = + for x <- ns do println() // warn valdef function param from for + def g1(ns: List[Int]) = + for x <- ns do println(x) // x => println(x) + def h(ns: List[Int]) = + for x <- ns; y = x + 1 // warn tupling from for; x is used, y is unused + do println() + def k(x: Option[K]) = + x match + case Some(K(i, j)) => // nowarn canonical names + case _ => + + val m = Map( + "first" -> Map((true, 1), (false, 2), (true, 3)), + "second" -> Map((true, 1), (false, 2), (true, 3)), + ) + def guardedUse = + m.map: (a, m1) => + for (status, lag) <- m1 if status + yield (a, status, lag) + def guardedUseOnly = + m.map: (a, m1) => + for (status, lag) <- m1 if status + yield (a, lag) + def guardedUseMissing = + m.map: (a, m1) => + for (status, lag) <- m1 // warn + yield (a, lag) + def flatGuardedUse = + for (a, m1) <- m; (status, lag) <- m1 if status + yield (a, status, lag) + def leading = + for _ <- List("42"); i = 1; _ <- List("0", "27")(i) + yield () + def optional = + for case Some(x) <- List(Option(42)) + yield x + def nonoptional = + for case Some(x) <- List(Option(42)) // warn + yield 27 + def optionalName = + for case Some(value) <- List(Option(42)) + yield 27 + +class Wild: + def f(x: Any) = + x match + case _: Option[?] => true + case _ => false + +def untuple(t: Tuple) = + t match + case Tuple() => + case h *: t => // no warn canonical names taken from tuple element types, (Head, Tail) -> (head, tail) + //case head *: tail => // no warn canonical names taken from tuple element types, (Head, Tail) -> (head, tail) + +// empty case class: +// def equals(other) = other match { case other => true } // exonerated name +object i15967: + sealed trait A[-Z] + final case class B[Y]() extends A[Y] + +object `patvar is assignable`: + var (i, j) = (42, 27) // no warn nonprivate + j += 1 + println((i, j)) + +object `privy patvar is assignable`: + private var (i, j) = (42, 27) // warn + j += 1 + println((i, j)) + +object `local patvar is assignable`: + def f() = + var (i, j) = (42, 27) // warn + j += 1 + println((i, j)) + +object `mutable patvar in for`: + def f(xs: List[Int]) = + for x <- xs; y = x + 1 if y > 10 yield + var z :: Nil = y :: Nil: @unchecked // warn + z + 10 + +class `unset var requires -Wunused`: + private var i = 0 // no warn as we didn't ask for it + def f = println(i) + +class `i22743 lazy vals are defs`: + def f: (Int, String) = (42, "hello, world") + lazy val (i, s) = f // no warn because def is neither local nor private + val (j, t) = f // existing no warn for val with attachment + private lazy val (k, u) = f // warn // warn a warning so nice, they warn it twice diff --git a/tests/warn/i15503e.scala b/tests/warn/i15503e.scala index 46d73a4945cd..63a9dea163d4 100644 --- a/tests/warn/i15503e.scala +++ b/tests/warn/i15503e.scala @@ -1,4 +1,6 @@ -//> using options -Wunused:explicits +//> using options -Wunused:explicits + +import annotation.* object Foo { /* This goes around the "trivial method" detection */ @@ -31,16 +33,17 @@ package scala3main: package foo.test.lambda.param: val default_val = 1 val a = (i: Int) => i // OK - val b = (i: Int) => default_val // OK + val b = (i: Int) => default_val // warn val c = (_: Int) => default_val // OK package foo.test.trivial: /* A twisted test from Scala 2 */ - class C { + class C(val value: Int) { def answer: 42 = 42 object X private def g0(x: Int) = ??? // OK private def f0(x: Int) = () // OK + private def f00(x: Int) = {} // OK private def f1(x: Int) = throw new RuntimeException // OK private def f2(x: Int) = 42 // OK private def f3(x: Int): Option[Int] = None // OK @@ -50,13 +53,16 @@ package foo.test.trivial: private def f7(x: Int) = Y // OK private def f8(x: Int): List[C] = Nil // OK private def f9(x: Int): List[Int] = List(1,2,3,4) // warn - private def foo:Int = 32 // OK + private def foo: Int = 32 // OK private def f77(x: Int) = foo // warn + private def self(x: Int): C = this // no warn + private def unwrap(x: Int): Int = value // no warn } object Y package foo.test.i16955: - class S(var r: String) // OK + class S(var rrr: String) // OK + class T(rrr: String) // warn package foo.test.i16865: trait Foo: @@ -64,7 +70,26 @@ package foo.test.i16865: trait Bar extends Foo object Ex extends Bar: - def fn(a: Int, b: Int): Int = b + 3 // OK + def fn(a: Int, b: Int): Int = b + 3 // no warn (override) object Ex2 extends Bar: - override def fn(a: Int, b: Int): Int = b + 3 // OK \ No newline at end of file + override def fn(a: Int, b: Int): Int = b + 3 // no warn (override) + +final class alpha(externalName: String) extends StaticAnnotation // no warn annotation arg + +object Unimplemented: + import compiletime.* + inline def f(inline x: Int | Double): Unit = error("unimplemented") // no warn param of trivial method + +def `trivially wrapped`(x: String): String ?=> String = "hello, world" // no warn param of trivial method + +object UnwrapTyped: + import compiletime.error + inline def requireConst(inline x: Boolean | Byte | Short | Int | Long | Float | Double | Char | String): Unit = + error("Compiler bug: `requireConst` was not evaluated by the compiler") + + transparent inline def codeOf(arg: Any): String = + error("Compiler bug: `codeOf` was not evaluated by the compiler") + +object `default usage`: + def f(i: Int)(j: Int = i * 2) = j // warn I guess diff --git a/tests/warn/i15503f.scala b/tests/warn/i15503f.scala index ccf0b7e74065..e134c2ded422 100644 --- a/tests/warn/i15503f.scala +++ b/tests/warn/i15503f.scala @@ -6,9 +6,81 @@ val default_int = 1 object Xd { private def f1(a: Int) = a // OK private def f2(a: Int) = 1 // OK - private def f3(a: Int)(using Int) = a // OK - private def f4(a: Int)(using Int) = default_int // OK + private def f3(a: Int)(using Int) = a // warn + private def f4(a: Int)(using Int) = default_int // warn private def f6(a: Int)(using Int) = summon[Int] // OK private def f7(a: Int)(using Int) = summon[Int] + a // OK private def f8(a: Int)(using foo: Int) = a // warn + private def f9(a: Int)(using Int) = ??? // OK trivial + private def g1(a: Int)(implicit foo: Int) = a // warn } + +trait T +object T: + def hole(using T) = () + +class C(using T) // warn + +class D(using T): + def t = T.hole // nowarn + +object Example: + import scala.quoted.* + given OptionFromExpr[T](using Type[T], FromExpr[T]): FromExpr[Option[T]] with + def unapply(x: Expr[Option[T]])(using Quotes) = x match + case '{ Option[T](${Expr(y)}) } => Some(Option(y)) + case '{ None } => Some(None) + case '{ ${Expr(opt)} : Some[T] } => Some(opt) + case _ => None + +object ExampleWithoutWith: + import scala.quoted.* + given [T] => (Type[T], FromExpr[T]) => FromExpr[Option[T]]: + def unapply(x: Expr[Option[T]])(using Quotes) = x match + case '{ Option[T](${Expr(y)}) } => Some(Option(y)) + case '{ None } => Some(None) + case '{ ${Expr(opt)} : Some[T] } => Some(opt) + case _ => None + +//absolving names on matches of quote trees requires consulting non-abstract types in QuotesImpl +object Unmatched: + import scala.quoted.* + def transform[T](e: Expr[T])(using Quotes): Expr[T] = + import quotes.reflect.* + def f(tree: Tree) = + tree match + case Ident(name) => + case _ => + e + +trait Ctx +case class K(i: Int)(using val ctx: Ctx) // nowarn +class L(val i: Int)(using val ctx: Ctx) // nowarn +class M(val i: Int)(using ctx: Ctx) // warn + +package givens: + + trait X: + def doX: Int + + trait Y: + def doY: String + + given X: + def doX = 7 + + given X => Y: // warn protected param to given class + def doY = "7" + /* desugared. It is protected so that its type can be used in member defs without leaking. + * possibly it should be protected only for named parameters. + given class given_Y(using x$1: givens.X) extends Object(), givens.Y { + protected given val x$1: givens.X + def doY: String = "7" + } + final given def given_Y(using x$1: givens.X): givens.given_Y = + new givens.given_Y(using x$1)() + */ + + given namely: (x: X) => Y: // warn protected param to given class + def doY = "8" +end givens diff --git a/tests/warn/i15503g.scala b/tests/warn/i15503g.scala index fbd9f3c1352c..cfbfcdb04d1e 100644 --- a/tests/warn/i15503g.scala +++ b/tests/warn/i15503g.scala @@ -6,8 +6,8 @@ object Foo { private def f1(a: Int) = a // OK private def f2(a: Int) = default_int // warn - private def f3(a: Int)(using Int) = a // OK - private def f4(a: Int)(using Int) = default_int // warn + private def f3(a: Int)(using Int) = a // warn + private def f4(a: Int)(using Int) = default_int // warn // warn private def f6(a: Int)(using Int) = summon[Int] // warn private def f7(a: Int)(using Int) = summon[Int] + a // OK /* --- Trivial method check --- */ @@ -20,4 +20,5 @@ package foo.test.i17101: extension[A] (x: Test[A]) { // OK def value: A = x def causesIssue: Unit = println("oh no") - } \ No newline at end of file + def isAnIssue(y: A): Boolean = x == x // warn + } diff --git a/tests/warn/i15503h.scala b/tests/warn/i15503h.scala index 854693981488..a2bf47c843dd 100644 --- a/tests/warn/i15503h.scala +++ b/tests/warn/i15503h.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:linted +//> using options -Wunused:linted import collection.mutable.Set // warn @@ -7,7 +7,7 @@ class A { val b = 2 // OK private def c = 2 // warn - def d(using x:Int): Int = b // ok + def d(using x: Int): Int = b // warn def e(x: Int) = 1 // OK def f = val x = 1 // warn @@ -15,6 +15,6 @@ class A { 3 def g(x: Int): Int = x match - case x:1 => 0 // OK + case x: 1 => 0 // OK case _ => 1 -} \ No newline at end of file +} diff --git a/tests/warn/i15503i.scala b/tests/warn/i15503i.scala index b7981e0e4206..89ff382eb68c 100644 --- a/tests/warn/i15503i.scala +++ b/tests/warn/i15503i.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:all +//> using options -Wunused:all -Ystop-after:checkUnusedPostInlining import collection.mutable.{Map => MutMap} // warn import collection.mutable.Set // warn @@ -17,10 +17,12 @@ class A { private def c2 = 2 // OK def c3 = c2 - def d1(using x:Int): Int = default_int // ok - def d2(using x:Int): Int = x // OK + def d1(using x: Int): Int = default_int // warn param + def d2(using x: Int): Int = x // OK + def d3(using Int): Int = summon[Int] // OK + def d4(using Int): Int = default_int // warn - def e1(x: Int) = default_int // ok + def e1(x: Int) = default_int // warn param def e2(x: Int) = x // OK def f = val x = 1 // warn @@ -29,11 +31,15 @@ class A { def g = 4 // OK y + g - // todo : uncomment once patvars is fixed - // def g(x: Int): Int = x match - // case x:1 => 0 // ?error - // case x:2 => x // ?OK - // case _ => 1 // ?OK + def g(x: Int): Int = x match + case x: 1 => 0 // no warn same name as selector (for shadowing or unused) + case x: 2 => x // OK + case _ => 1 // OK + + def h(x: Int): Int = x match + case y: 1 => 0 // warn unused despite trivial type and RHS + case y: 2 => y // OK + case _ => 1 // OK } /* ---- CHECK scala.annotation.unused ---- */ @@ -44,7 +50,7 @@ package foo.test.scala.annotation: val default_int = 12 def a1(a: Int) = a // OK - def a2(a: Int) = default_int // ok + def a2(a: Int) = default_int // warn def a3(@unused a: Int) = default_int //OK @@ -82,8 +88,8 @@ package foo.test.i16678: def run = println(foo(number => number.toString, value = 5)) // OK println(foo(number => "", value = 5)) // warn - println(foo(func = number => "", value = 5)) // warn println(foo(func = number => number.toString, value = 5)) // OK + println(foo(func = number => "", value = 5)) // warn println(foo(func = _.toString, value = 5)) // OK package foo.test.possibleclasses: @@ -91,7 +97,7 @@ package foo.test.possibleclasses: k: Int, // OK private val y: Int // OK /* Kept as it can be taken from pattern */ )( - s: Int, + s: Int, // warn val t: Int, // OK private val z: Int // warn ) @@ -130,22 +136,22 @@ package foo.test.possibleclasses: package foo.test.possibleclasses.withvar: case class AllCaseClass( k: Int, // OK - private var y: Int // OK /* Kept as it can be taken from pattern */ + private var y: Int // warn unset )( - s: Int, - var t: Int, // OK - private var z: Int // warn + s: Int, // warn + var ttt: Int, // OK + private var zzz: Int // warn ) case class AllCaseUsed( k: Int, // OK - private var y: Int // OK + private var y: Int // warn unset )( s: Int, // OK - var t: Int, // OK global scope can be set somewhere else - private var z: Int // warn not set + var tt: Int, // OK global scope can be set somewhere else + private var zz: Int // warn not set ) { - def a = k + y + s + t + z + def a = k + y + s + tt + zz } class AllClass( @@ -199,14 +205,14 @@ package foo.test.i16877: package foo.test.i16926: def hello(): Unit = for { - i <- (0 to 10).toList + i <- (0 to 10).toList // warn patvar (a, b) = "hello" -> "world" // OK } yield println(s"$a $b") package foo.test.i16925: def hello = for { - i <- 1 to 2 if true + i <- 1 to 2 if true // OK _ = println(i) // OK } yield () @@ -247,7 +253,7 @@ package foo.test.i16679a: import scala.deriving.Mirror object CaseClassByStringName: inline final def derived[A](using inline A: Mirror.Of[A]): CaseClassByStringName[A] = - new CaseClassByStringName[A]: // warn + new CaseClassByStringName[A]: def name: String = A.toString object secondPackage: @@ -263,7 +269,7 @@ package foo.test.i16679b: object CaseClassName: import scala.deriving.Mirror inline final def derived[A](using inline A: Mirror.Of[A]): CaseClassName[A] = - new CaseClassName[A]: // warn + new CaseClassName[A]: def name: String = A.toString object Foo: @@ -279,7 +285,7 @@ package foo.test.i17156: package a: trait Foo[A] object Foo: - inline def derived[T]: Foo[T] = new Foo{} // warn + inline def derived[T]: Foo[T] = new Foo {} package b: import a.Foo @@ -313,3 +319,52 @@ package foo.test.i17117: val test = t1.test } } + +// manual testing of cached look-ups +package deeply: + object Deep: + def f(): Unit = + def g(): Unit = + def h(): Unit = + println(Deep) + println(Deep) + println(Deep) + h() + g() + override def toString = "man, that is deep!" +/* result cache saves before context traversal and import/member look-up +CHK object Deep +recur * 10 = context depth is 10 between reference and definition +CHK method println +recur = was 19 at predef where max root is 21 +CHK object Deep +recur = cached result +CHK method println +recur +CHK object Deep +recur +*/ + +package constructors: + class C private (i: Int): // warn param + def this() = this(27) + private def this(s: String) = this(s.toInt) // warn ctor + def c = new C(42) + private def f(i: Int) = i // warn overloaded member + private def f(s: String) = s + def g = f("hello") // use one of overloaded member + + class D private (i: Int): + private def this() = this(42) // no warn used by companion + def d = i + object D: + def apply(): D = new D() + +package reversed: // reverse-engineered + class C: + def c: scala.Int = 42 // Int marked used; lint does not look up .scala + class D: + def d: Int = 27 // Int is found in root import scala.* + class E: + import scala.* // no warn because root import (which is cached! by previous lookup) is lower precedence + def e: Int = 27 diff --git a/tests/warn/i15503k.scala b/tests/warn/i15503k.scala new file mode 100644 index 000000000000..8148de44c588 --- /dev/null +++ b/tests/warn/i15503k.scala @@ -0,0 +1,43 @@ + +//> using options -Wunused:imports + +import scala.compiletime.ops.int.* // no warn + +object TupleOps: + /** Type of the element at position N in the tuple X */ + type Elem[X <: Tuple, N <: Int] = X match { + case x *: xs => + N match { + case 0 => x + case S[n1] => Elem[xs, n1] + } + } + + /** Literal constant Int size of a tuple */ + type Size[X <: Tuple] <: Int = X match { + case EmptyTuple => 0 + case x *: xs => S[Size[xs]] + } + +object Summoner: + transparent inline def summoner[T](using x: T): x.type = x + +object `Summoner's Tale`: + import compiletime.summonFrom // no warn + inline def valueOf[T]: T = summonFrom: // implicit match + case ev: ValueOf[T] => ev.value + import Summoner.* // no warn + def f[T](using T): T = summoner[T] // Inlined + +class C: + private def m: Int = 42 // no warn +object C: + class D: + private val c: C = C() // no warn + export c.m // no work to do, expanded member is non-private and uses the select expr + +object UsefulTypes: + trait T +object TypeUser: + import UsefulTypes.* + def f(x: => T) = x diff --git a/tests/warn/i15503kb/power.scala b/tests/warn/i15503kb/power.scala new file mode 100644 index 000000000000..f7e5ccce6d58 --- /dev/null +++ b/tests/warn/i15503kb/power.scala @@ -0,0 +1,14 @@ + +object Power: + import scala.math.pow as power + import scala.quoted.* + inline def powerMacro(x: Double, inline n: Int): Double = ${ powerCode('x, 'n) } + def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = + n match + case Expr(m) => unrolledPowerCode(x, m) + case _ => '{ power($x, $n.toDouble) } + def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = + n match + case 0 => '{ 1.0 } + case 1 => x + case _ => '{ $x * ${ unrolledPowerCode(x, n - 1) } } diff --git a/tests/warn/i15503kb/square.scala b/tests/warn/i15503kb/square.scala new file mode 100644 index 000000000000..2a5f76e9be83 --- /dev/null +++ b/tests/warn/i15503kb/square.scala @@ -0,0 +1,5 @@ +//> using options -Werror -Wunused:all + +object PowerUser: + import Power.* + def square(x: Double): Double = powerMacro(x, 2) diff --git a/tests/warn/i15662.scala b/tests/warn/i15662.scala index 0cf0e57ed0c3..f17a040b4980 100644 --- a/tests/warn/i15662.scala +++ b/tests/warn/i15662.scala @@ -5,6 +5,7 @@ case class Composite[T](v: T) def m(composite: Composite[?]): Unit = composite match { case Composite[Int](v) => println(v) // warn: cannot be checked at runtime + case _ => println("OTHER") } def m2(composite: Composite[?]): Unit = diff --git a/tests/pos/i15967.scala b/tests/warn/i15967.scala similarity index 100% rename from tests/pos/i15967.scala rename to tests/warn/i15967.scala diff --git a/tests/warn/i16639a.scala b/tests/warn/i16639a.scala index 9fe4efe57d7b..ca7798297aea 100644 --- a/tests/warn/i16639a.scala +++ b/tests/warn/i16639a.scala @@ -1,7 +1,7 @@ //> using options -Wunused:all -source:3.3 class Bippy(a: Int, b: Int) { - private def this(c: Int) = this(c, c) + private def this(c: Int) = this(c, c) // warn private def boop(x: Int) = x+a+b // warn private def bippy(x: Int): Int = bippy(x) // warn TODO: could warn final private val MILLIS1 = 2000 // warn no warn, /Dotty:Warn @@ -24,13 +24,13 @@ class B3(msg0: String) extends A("msg") // warn /Dotty: unused explicit paramete trait Bing trait Accessors { - private var v1: Int = 0 // warn warn - private var v2: Int = 0 // warn warn, never set - private var v3: Int = 0 + private var v1: Int = 0 // warn + private var v2: Int = 0 // warn, never set + private var v3: Int = 0 // warn, never got private var v4: Int = 0 // no warn - private[this] var v5 = 0 // warn warn, never set - private[this] var v6 = 0 + private[this] var v5 = 0 // warn, never set + private[this] var v6 = 0 // warn, never got private[this] var v7 = 0 // no warn def bippy(): Int = { @@ -43,13 +43,13 @@ trait Accessors { } class StableAccessors { - private var s1: Int = 0 // warn warn - private var s2: Int = 0 // warn warn, never set - private var s3: Int = 0 + private var s1: Int = 0 // warn + private var s2: Int = 0 // warn, never set + private var s3: Int = 0 // warn, never got private var s4: Int = 0 // no warn - private[this] var s5 = 0 // warn warn, never set - private[this] var s6 = 0 // no warn, limitation /Dotty: Why limitation ? + private[this] var s5 = 0 // warn, never set + private[this] var s6 = 0 // warn, never got private[this] var s7 = 0 // no warn def bippy(): Int = { @@ -62,7 +62,7 @@ class StableAccessors { } trait DefaultArgs { - private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 // no more warn warn since #17061 + private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 // warn // warn def boppy() = bippy(5, 100, 200) } @@ -91,7 +91,7 @@ trait Locals { } object Types { - private object Dongo { def f = this } // no more warn since #17061 + private object Dongo { def f = this } // warn private class Bar1 // warn warn private class Bar2 // no warn private type Alias1 = String // warn warn @@ -101,7 +101,7 @@ object Types { def f(x: Alias2) = x.length def l1() = { - object HiObject { def f = this } // no more warn since #17061 + object HiObject { def f = this } // warn class Hi { // warn warn def f1: Hi = new Hi def f2(x: Hi) = x @@ -124,9 +124,9 @@ trait Underwarn { } class OtherNames { - private def x_=(i: Int): Unit = () // no more warn since #17061 - private def x: Int = 42 // warn Dotty triggers unused private member : To investigate - private def y_=(i: Int): Unit = () // // no more warn since #17061 + private def x_=(i: Int): Unit = () // warn + private def x: Int = 42 // warn + private def y_=(i: Int): Unit = () // warn private def y: Int = 42 def f = y @@ -145,7 +145,7 @@ trait Forever { val t = Option((17, 42)) for { ns <- t - (i, j) = ns // no warn + (i, j) = ns // warn // warn -Wunused:patvars is in -Wunused:all } yield 42 // val emitted only if needed, hence nothing unused } } @@ -158,14 +158,14 @@ trait CaseyKasem { def f = 42 match { case x if x < 25 => "no warn" case y if toString.nonEmpty => "no warn" + y - case z => "warn" + case z => "warn" // warn patvar } } trait CaseyAtTheBat { def f = Option(42) match { case Some(x) if x < 25 => "no warn" - case Some(y @ _) if toString.nonEmpty => "no warn" - case Some(z) => "warn" + case Some(y @ _) if toString.nonEmpty => "no warn" // warn todo whether to use name @ _ to suppress + case Some(z) => "warn" // warn patvar case None => "no warn" } } @@ -173,7 +173,7 @@ trait CaseyAtTheBat { class `not even using companion privates` object `not even using companion privates` { - private implicit class `for your eyes only`(i: Int) { // no more warn since #17061 + private implicit class `for your eyes only`(i: Int) { // warn def f = i } } @@ -202,4 +202,4 @@ trait `short comings` { val x = 42 // warn /Dotty only triggers in dotty 17 } -} \ No newline at end of file +} diff --git a/tests/warn/i16743.check b/tests/warn/i16743.check index 6fa1f2c83357..9fdf80e71f2b 100644 --- a/tests/warn/i16743.check +++ b/tests/warn/i16743.check @@ -1,3 +1,10 @@ +-- [E194] Potential Issue Warning: tests/warn/i16743.scala:90:8 -------------------------------------------------------- +90 | def length() = 42 // warn This extension method will be shadowed by .length() on String. + | ^ + | Extension method length will never be selected from type String + | because String already has a member with the same name and compatible parameter types. + | + | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:30:6 -------------------------------------------------------- 30 | def t = 27 // warn | ^ diff --git a/tests/warn/i16743.scala b/tests/warn/i16743.scala index 4c9c99cf30d0..213e22ff4cb4 100644 --- a/tests/warn/i16743.scala +++ b/tests/warn/i16743.scala @@ -66,7 +66,7 @@ trait DungeonDweller: trait SadDungeonDweller: def f[A](x: Dungeon.IArray[A]) = 27 // x.length // just to confirm, length is not a member -trait Quote: +trait Quote: // see tests/warn/ext-override.scala type Tree <: AnyRef given TreeMethods: TreeMethods trait TreeMethods: @@ -87,7 +87,7 @@ class Depends: object Depending: extension (using depends: Depends)(x: depends.Thing) def y = 42 - def length() = 42 // nowarn see Quote above + def length() = 42 // warn This extension method will be shadowed by .length() on String. def f(using d: Depends) = d.thing.y def g(using d: Depends) = d.thing.length() diff --git a/tests/pos/i17230.min1.scala b/tests/warn/i17230.min1.scala similarity index 100% rename from tests/pos/i17230.min1.scala rename to tests/warn/i17230.min1.scala diff --git a/tests/pos/i17230.orig.scala b/tests/warn/i17230.orig.scala similarity index 100% rename from tests/pos/i17230.orig.scala rename to tests/warn/i17230.orig.scala diff --git a/tests/pos/i17314.scala b/tests/warn/i17314.scala similarity index 84% rename from tests/pos/i17314.scala rename to tests/warn/i17314.scala index 8ece4a3bd7ac..cff90d843c38 100644 --- a/tests/pos/i17314.scala +++ b/tests/warn/i17314.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings -Wunused:all -deprecation -feature +//> using options -Wunused:all -deprecation -feature import java.net.URI @@ -10,9 +10,7 @@ object circelike { type Configuration trait ConfiguredCodec[T] object ConfiguredCodec: - inline final def derived[A](using conf: Configuration)(using - inline mirror: Mirror.Of[A] - ): ConfiguredCodec[A] = + inline final def derived[A](using conf: Configuration)(using inline mirror: Mirror.Of[A]): ConfiguredCodec[A] = // warn // warn class InlinedConfiguredCodec extends ConfiguredCodec[A]: val codec = summonInline[Codec[URI]] // simplification new InlinedConfiguredCodec diff --git a/tests/pos/i17314a.scala b/tests/warn/i17314a.scala similarity index 70% rename from tests/pos/i17314a.scala rename to tests/warn/i17314a.scala index 4bce56d8bbed..14ae96848d63 100644 --- a/tests/pos/i17314a.scala +++ b/tests/warn/i17314a.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings -Wunused:all -deprecation -feature +//> using options -Werror -Wunused:all -deprecation -feature package foo: class Foo[T] diff --git a/tests/warn/i17314b.scala b/tests/warn/i17314b.scala index e1500028ca93..ad4c8f1e4a31 100644 --- a/tests/warn/i17314b.scala +++ b/tests/warn/i17314b.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:all +//> using options -Wunused:all package foo: class Foo[T] diff --git a/tests/warn/i17318.scala b/tests/warn/i17318.scala new file mode 100644 index 000000000000..d242c96484a7 --- /dev/null +++ b/tests/warn/i17318.scala @@ -0,0 +1,33 @@ + +//> using options -Werror -Wunused:all + +object events { + final val PollOut = 0x002 + transparent inline def POLLIN = 0x001 +} + +def withShort(v: Short): Unit = ??? +def withInt(v: Int): Unit = ??? + +def usage() = + import events.POLLIN // reports unused + def v: Short = POLLIN + println(v) + +def usage2() = + import events.POLLIN // reports unused + withShort(POLLIN) + +def usage3() = + import events.POLLIN // does not report unused + withInt(POLLIN) + +def usage4() = + import events.POLLIN // reports unused + withShort(POLLIN) + +def value = 42 +def withDouble(v: Double): Unit = ??? +def usage5() = withDouble(value) +def usage6() = withShort(events.POLLIN) +def usage7() = withShort(events.PollOut) diff --git a/tests/warn/i17371.scala b/tests/warn/i17371.scala new file mode 100644 index 000000000000..f9be76bfed07 --- /dev/null +++ b/tests/warn/i17371.scala @@ -0,0 +1,39 @@ +//> using options -Wunused:all + +class A +class B + +def Test() = + val ordA: Ordering[A] = ??? + val ordB: Ordering[B] = ??? + val a: A = ??? + val b: B = ??? + + import ordA.given + val _ = a > a + + import ordB.given + val _ = b < b + +// unminimized OP +trait Circular[T] extends Ordering[T] +trait Turns[C: Circular, T] extends Ordering[T]: // warn Circular is not a marker interface + extension (turns: T) def extract: C + +def f[K, T](start: T, end: T)(using circular: Circular[K], turns: Turns[K, T]): Boolean = + import turns.given + if start > end then throw new IllegalArgumentException("start must be <= end") + + import circular.given + start.extract < end.extract + +// -Wunused:implicits warns for unused implicit evidence unless it is an empty interface (only universal members). +// scala 2 also offers -Wunused:synthetics for whether to warn for synthetic implicit params. +object ContextBounds: + class C[A: Ordered](a: A): // warn + def f = a + + trait T[A] + + class D[A: T](a: A): // no warn + def f = a diff --git a/tests/warn/i17667.scala b/tests/warn/i17667.scala new file mode 100644 index 000000000000..cc3f6bfc8472 --- /dev/null +++ b/tests/warn/i17667.scala @@ -0,0 +1,10 @@ + +//> using options -Wunused:imports + +object MyImplicits: + extension (a: Int) def print: Unit = println(s"Hello, I am $a") + +import MyImplicits.print //Global import of extension +object Foo: + def printInt(a: Int): Unit = a.print + import MyImplicits.* // warn //Local import of extension diff --git a/tests/warn/i17667b.scala b/tests/warn/i17667b.scala new file mode 100644 index 000000000000..ba8fc7219945 --- /dev/null +++ b/tests/warn/i17667b.scala @@ -0,0 +1,22 @@ + +//> using options -Wunused:all + +import scala.util.Try +import scala.concurrent.* // warn +import scala.collection.Set +class C { + def ss[A](using Set[A]) = println() // warn + private def f = Try(42).get + private def z: Int = // warn + Try(27 + z).get + def g = f + f + def k = + val i = g + g + val j = i + 2 // warn + i + 1 + def c = C() + import scala.util.Try // warn +} +class D { + def d = C().g +} diff --git a/tests/warn/i17753.scala b/tests/warn/i17753.scala new file mode 100644 index 000000000000..66e4fdb8727b --- /dev/null +++ b/tests/warn/i17753.scala @@ -0,0 +1,10 @@ +//> using options -Wunused:all + +class PartiallyApplied[A] { + transparent inline def func[B](): Nothing = ??? +} + +def call[A] = new PartiallyApplied[A] + +def good = call[Int].func[String]() // no warn inline proxy +def bad = { call[Int].func[String]() } // no warn inline proxy diff --git a/tests/warn/i18313.scala b/tests/warn/i18313.scala new file mode 100644 index 000000000000..44b005b313e4 --- /dev/null +++ b/tests/warn/i18313.scala @@ -0,0 +1,14 @@ +//> using options -Werror -Wunused:imports + +import scala.deriving.Mirror + +case class Test(i: Int, d: Double) +case class Decoder(d: Product => Test) + +// OK, no warning returned +//val ok = Decoder(summon[Mirror.Of[Test]].fromProduct) +// +// returns warning: +// [warn] unused import +// [warn] import scala.deriving.Mirror +val d = Decoder(d = summon[Mirror.Of[Test]].fromProduct) // no warn diff --git a/tests/warn/i18341.scala b/tests/warn/i18341.scala new file mode 100644 index 000000000000..63b725df000f --- /dev/null +++ b/tests/warn/i18341.scala @@ -0,0 +1,21 @@ + +//> using options -Wunused:params,nowarn + +import annotation.* + +class B(@nowarn useless: Int) + +class C(@nowarn("msg=unused") useless: Int) + +class D(useless: Int) // warn + +class E(@nowarn useful: Int): // warn + def e = useful * 10 // 10x useful + +class X: + def extensionInCompanion: String = ??? +@nowarn // extensionInCompanion +object X: + implicit def companionConversion(x: X): B = ??? + + extension (x: X) def extensionInCompanion: String = ??? diff --git a/tests/warn/i18366.scala b/tests/warn/i18366.scala new file mode 100644 index 000000000000..b6385b5bbb59 --- /dev/null +++ b/tests/warn/i18366.scala @@ -0,0 +1,19 @@ +//> using options -Werror -Wunused:all + +trait Builder { + def foo(): Unit +} + +def `i18366` = + val builder: Builder = ??? + import builder.{foo => bar} + bar() + +import java.io.DataOutputStream + +val buffer: DataOutputStream = ??? + +import buffer.{write => put} + +def `i17315` = + put(0: Byte) diff --git a/tests/warn/i18564.scala b/tests/warn/i18564.scala new file mode 100644 index 000000000000..19682b7955f9 --- /dev/null +++ b/tests/warn/i18564.scala @@ -0,0 +1,39 @@ + +//> using options -Wunused:imports + +import scala.compiletime.* +import scala.deriving.* + +trait Foo + +trait HasFoo[A]: + /** true if A contains a Foo */ + val hasFoo: Boolean + +// given instances that need to be imported to be in scope +object HasFooInstances: + given defaultHasFoo[A]: HasFoo[A] with + val hasFoo: Boolean = false + given HasFoo[Foo] with + val hasFoo: Boolean = true + +object HasFooDeriving: + + inline private def tupleHasFoo[T <: Tuple]: Boolean = + inline erasedValue[T] match + case _: EmptyTuple => false + case _: (t *: ts) => summonInline[HasFoo[t]].hasFoo || tupleHasFoo[ts] + + inline def deriveHasFoo[T](using p: Mirror.ProductOf[T]): HasFoo[T] = + // falsely reported as unused even though it has influence on this code + import HasFooInstances.given // no warn at inline method + val pHasFoo = tupleHasFoo[p.MirroredElemTypes] + new HasFoo[T]: // warn New anonymous class definition will be duplicated at each inline site + val hasFoo: Boolean = pHasFoo + +/* the import is used upon inline elaboration +object Test: + import HasFooDeriving.* + case class C(x: Foo, y: Int) + def f: HasFoo[C] = deriveHasFoo[C] +*/ diff --git a/tests/warn/i19252.scala b/tests/warn/i19252.scala new file mode 100644 index 000000000000..42ca99208299 --- /dev/null +++ b/tests/warn/i19252.scala @@ -0,0 +1,13 @@ +//> using options -Werror -Wunused:all +object Deps: + trait D1 + object D2 +end Deps + +object Bug: + import Deps.D1 // no warn + + class Cl(d1: D1): + import Deps.* + def f = (d1, D2) +end Bug diff --git a/tests/warn/i19657-mega.scala b/tests/warn/i19657-mega.scala new file mode 100644 index 000000000000..07411ee73fb1 --- /dev/null +++ b/tests/warn/i19657-mega.scala @@ -0,0 +1,4 @@ +//> using options -Wshadow:type-parameter-shadow -Wunused:all + +class F[X, M[N[X]]]: // warn + private def x[X] = toString // warn // warn diff --git a/tests/warn/i19657.scala b/tests/warn/i19657.scala new file mode 100644 index 000000000000..2caa1c832abe --- /dev/null +++ b/tests/warn/i19657.scala @@ -0,0 +1,117 @@ +//> using options -Wunused:imports -Ystop-after:checkUnusedPostInlining + +trait Schema[A] + +case class Foo() +case class Bar() + +trait SchemaGenerator[A] { + given Schema[A] = new Schema[A]{} +} + +object FooCodec extends SchemaGenerator[Foo] +object BarCodec extends SchemaGenerator[Bar] + +def summonSchemas(using Schema[Foo], Schema[Bar]) = () + +def summonSchema(using Schema[Foo]) = () + +def `i19657 check prefix to pick selector`: Unit = + import FooCodec.given + import BarCodec.given + summonSchemas + +def `i19657 regression test`: Unit = + import FooCodec.given + import BarCodec.given // warn + summonSchema + +def `i19657 check prefix to pick specific selector`: Unit = + import FooCodec.given_Schema_A + import BarCodec.given_Schema_A + summonSchemas + +def `same symbol different names`: Unit = + import FooCodec.given_Schema_A + import FooCodec.given_Schema_A as AThing + summonSchema(using given_Schema_A) + summonSchema(using AThing) + +package i17156: + package a: + trait Foo[A] + object Foo: + class Food[A] extends Foo[A] + inline def derived[T]: Foo[T] = Food() + + package b: + import a.Foo + type Xd[A] = Foo[A] + + package c: + import b.Xd + trait Z derives Xd // checks if dealiased import is prefix a.Foo + class Bar extends Xd[Int] // checks if import qual b is prefix of b.Xd + +object Coll: + class C: + type HM[K, V] = scala.collection.mutable.HashMap[K, V] +object CC extends Coll.C +import CC.* + +def `param type is imported`(map: HM[String, String]): Unit = println(map("hello, world")) + +object Constants: + final val i = 42 + def extra = 3 +def `old-style constants are usages`: Unit = + object Local: + final val j = 27 + import Constants.i + println(i + Local.j) + +object Constantinople: + val k = 42 +class `scope of super`: + import Constants.i // was bad warn + class C(x: Int): + def y = x + class D(j: Int) extends C(i + j): + import Constants.* // does not resolve i in C(i) and does not shadow named import + def m = i // actually picks the higher-precedence import + def f = + import Constantinople.* + class E(e: Int) extends C(i + k): + def g = e + y + k + 1 + E(0).g + def consume = extra // use the wildcard import from Constants + +import scala.annotation.meta.* +object Alias { + type A = Deprecated @param +} + +// avoid reporting on runtime (nothing to do with transparent inline) +import scala.runtime.EnumValue + +trait Lime + +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) with EnumValue + case Green extends Color(0x00FF00) with Lime + case Blue extends Color(0x0000FF) + +object prefixes: + class C: + object N: + type U + object Test: + val c: C = ??? + def k2: c.N.U = ??? + import c.N.* + def k3: U = ??? // TypeTree if not a select + object Alt: + val c: C = ??? + import c.N + def k4: N.U = ??? +end prefixes diff --git a/tests/warn/i20520.scala b/tests/warn/i20520.scala new file mode 100644 index 000000000000..e09d16c27af2 --- /dev/null +++ b/tests/warn/i20520.scala @@ -0,0 +1,11 @@ + +//> using options -Wunused:all + +@main def run = + val veryUnusedVariable: Int = value // warn local + +package i20520: + private def veryUnusedMethod(x: Int): Unit = println() // warn param + private val veryUnusedVariableToplevel: Unit = println() // package members are accessible under separate compilation + +def value = 42 diff --git a/tests/pos/i20860.scala b/tests/warn/i20860.scala similarity index 74% rename from tests/pos/i20860.scala rename to tests/warn/i20860.scala index 1e1ddea11b75..b318d861fce0 100644 --- a/tests/pos/i20860.scala +++ b/tests/warn/i20860.scala @@ -1,3 +1,5 @@ +//> using options -Werror -Wunused:imports + def `i20860 use result to check selector bound`: Unit = import Ordering.Implicits.given Ordering[?] summon[Ordering[Seq[Int]]] diff --git a/tests/warn/i20951.scala b/tests/warn/i20951.scala new file mode 100644 index 000000000000..0ca82e1b8d66 --- /dev/null +++ b/tests/warn/i20951.scala @@ -0,0 +1,7 @@ +//> using options -Wunused:all +object Foo { + val dummy = 42 + def f(): Unit = Option(1).map((x: Int) => dummy) // warn + def g(): Unit = Option(1).map((x: Int) => ???) // warn + def main(args: Array[String]): Unit = {} +} diff --git a/tests/warn/i21190.scala b/tests/warn/i21190.scala new file mode 100644 index 000000000000..652a4ad36853 --- /dev/null +++ b/tests/warn/i21190.scala @@ -0,0 +1,23 @@ + +//> using options -Werror + +opaque type FromEnd = Int +object FromEnd: + inline def apply(i: Int): FromEnd = i + extension (fe: FromEnd) + inline def value: Int = fe + +// Warning appears when extension is in same namespace as opaque type +extension [A](a: Array[A]) + inline def apply(fe: FromEnd): A = + a(a.length - 1 - FromEnd.value(fe)) + +class R: + def run(): String = + val xs = Array(1, 2, 3) + + s"""First element = ${xs(0)} + |Last element = ${xs(FromEnd(0))}""".stripMargin + +@main def test = println: + R().run() diff --git a/tests/warn/i21420.scala b/tests/warn/i21420.scala index 0ee4aa3f28f6..65a288c7ae5b 100644 --- a/tests/warn/i21420.scala +++ b/tests/warn/i21420.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:imports +//> using options -Werror -Wunused:imports object decisions4s{ trait HKD @@ -7,7 +7,7 @@ object decisions4s{ object DiagnosticsExample { import decisions4s.HKD - val _ = new HKD {} + case class Input[F[_]]() extends HKD import decisions4s.* - val _ = new DecisionTable {} + val decisionTable: DecisionTable = ??? } diff --git a/tests/warn/i21525.scala b/tests/warn/i21525.scala new file mode 100644 index 000000000000..721486c4bd64 --- /dev/null +++ b/tests/warn/i21525.scala @@ -0,0 +1,36 @@ +//> using options -Werror -Wunused:imports + +import scala.reflect.{Typeable, TypeTest} +import compiletime.* + +trait A { + type B + type C <: B + + given instance: TypeTest[B, C] +} + +def f(a: A, b: a.B): Boolean = { + import a.C + b match { + case _: C => + true + case _ => + false + } +} + +trait T: + type X + given Typeable[X] = deferred + +def g(t: T, x: Any) = + import t.X + x match + case _: X => true + case _ => false + +def typer[T: Typeable](x: Any) = + x match + case _: T => 1 + case _ => 0 diff --git a/tests/warn/i21809.scala b/tests/warn/i21809.scala new file mode 100644 index 000000000000..91e7a334b13b --- /dev/null +++ b/tests/warn/i21809.scala @@ -0,0 +1,17 @@ +//> using options -Wunused:imports + +package p { + package q { + import q.* // warn so long as we pass typer + class Test { + //override def toString = new C().toString + " for Test" + def d = D() + } + class D + } +} +package q { + class C { + override def toString = "q.C" + } +} diff --git a/tests/warn/i21860.unenum.scala b/tests/warn/i21860.unenum.scala index 7335e1b6851d..e4b282e35e76 100644 --- a/tests/warn/i21860.unenum.scala +++ b/tests/warn/i21860.unenum.scala @@ -3,7 +3,7 @@ sealed trait Corners { self: Figure => } sealed abstract class Shape extends Figure object Shape: - case object Triange extends Shape with Corners + case object Triangle extends Shape with Corners case object Square extends Shape with Corners case object Circle extends Shape case object Ellipsis extends Shape diff --git a/tests/warn/i21917.scala b/tests/warn/i21917.scala new file mode 100644 index 000000000000..cade4e90db3d --- /dev/null +++ b/tests/warn/i21917.scala @@ -0,0 +1,27 @@ +//> using options -Wunused:imports + +import Pet.Owner + +class Dog(owner: Owner) extends Pet(owner) { + import Pet.* // warn although unambiguous (i.e., it was disambiguated) + //import Car.* // ambiguous + + def bark(): String = "bite" + + def this(owner: Owner, goodDog: Boolean) = { + this(owner) + if (goodDog) println(s"$owner's dog is a good boy") + } + + val getOwner: Owner = owner +} + +class Pet(val owner: Owner) + +object Pet { + class Owner +} + +object Car { + class Owner +} diff --git a/tests/warn/i22212.check b/tests/warn/i22212.check new file mode 100644 index 000000000000..d81ec1ffeff8 --- /dev/null +++ b/tests/warn/i22212.check @@ -0,0 +1,21 @@ + +-- [E029] Pattern Match Exhaustivity Warning: tests/warn/i22212/Test_2.scala:3:19 -------------------------------------- +3 | Macro.makeMatch() // warn: match may not be exhaustive. + | ^^^^^^^^^^^^^^^^^ + | match may not be exhaustive. + | + | It would fail on pattern case: Baz + |--------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from Macro_1.scala:7 +7 | (_: Foo) match + | ^^^^^^ + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from Macro_1.scala:7 +7 | (_: Foo) match + | ^ +8 | case Bar => () + --------------------------------------------------------------------------------------------------------------------- + | + | longer explanation available when compiling with `-explain` diff --git a/tests/warn/i22212/Data_1.scala b/tests/warn/i22212/Data_1.scala new file mode 100644 index 000000000000..1f14d9e2d3eb --- /dev/null +++ b/tests/warn/i22212/Data_1.scala @@ -0,0 +1,3 @@ +sealed trait Foo +case object Bar extends Foo +case object Baz extends Foo diff --git a/tests/warn/i22212/Macro_1.scala b/tests/warn/i22212/Macro_1.scala new file mode 100644 index 000000000000..9fcd9a2273ac --- /dev/null +++ b/tests/warn/i22212/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +object Macro { + inline def makeMatch() = ${makeMatchImpl} + def makeMatchImpl(using Quotes) = { + '{ + (_: Foo) match + case Bar => () + } + } +} diff --git a/tests/warn/i22212/Test_2.scala b/tests/warn/i22212/Test_2.scala new file mode 100644 index 000000000000..8990a85ff066 --- /dev/null +++ b/tests/warn/i22212/Test_2.scala @@ -0,0 +1,3 @@ +object Test: + def main(args: Array[String]): Unit = + Macro.makeMatch() // warn: match may not be exhaustive. diff --git a/tests/warn/i22232.check b/tests/warn/i22232.check new file mode 100644 index 000000000000..cf3d6d4e004e --- /dev/null +++ b/tests/warn/i22232.check @@ -0,0 +1,28 @@ +-- [E194] Potential Issue Warning: tests/warn/i22232.scala:3:23 -------------------------------------------------------- +3 | extension (c: C) def equals(that: Any): Boolean = false // warn + | ^ + | Extension method equals will never be selected from type C + | because C already has a member with the same name and compatible parameter types. + | + | longer explanation available when compiling with `-explain` +-- [E194] Potential Issue Warning: tests/warn/i22232.scala:9:25 -------------------------------------------------------- +9 | extension (d: D) def equals(that: Any): Boolean = false // warn + | ^ + | Extension method equals will never be selected from type C + | because C already has a member with the same name and compatible parameter types. + | + | longer explanation available when compiling with `-explain` +-- [E194] Potential Issue Warning: tests/warn/i22232.scala:13:38 ------------------------------------------------------- +13 | extension (arr: MyString[Byte]) def length: Int = 0 // warn + | ^ + | Extension method length will never be selected from type String + | because String already has a member with the same name and compatible parameter types. + | + | longer explanation available when compiling with `-explain` +-- [E194] Potential Issue Warning: tests/warn/i22232.scala:17:46 ------------------------------------------------------- +17 | extension [T <: MyString[Byte]](arr: T) def length: Int = 0 // warn + | ^ + | Extension method length will never be selected from type String + | because String already has a member with the same name and compatible parameter types. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/warn/i22232.scala b/tests/warn/i22232.scala new file mode 100644 index 000000000000..f94e413920a2 --- /dev/null +++ b/tests/warn/i22232.scala @@ -0,0 +1,37 @@ +class C +object C: + extension (c: C) def equals(that: Any): Boolean = false // warn + +object X: + class C + opaque type D <: C = C + object D: + extension (d: D) def equals(that: Any): Boolean = false // warn + +object Upperbound1: + opaque type MyString[+T] <: String = String + extension (arr: MyString[Byte]) def length: Int = 0 // warn + +object Upperbound2: + opaque type MyString[+T] <: String = String + extension [T <: MyString[Byte]](arr: T) def length: Int = 0 // warn + +object Upperbound3: + opaque type MyString[+T] <: String = String + extension [T](arr: T) def length: Int = 0 // nowarn + +object NonUpperbound1: + opaque type MyString[+T] = String + extension (arr: MyString[Byte]) def length: Int = 0 // nowarn + +object NonUpperbound2: + opaque type MyString[+T] = String + extension [T <: MyString[Byte]](arr: T) def length2: Int = 0 // nowarn + +object NonUpperbound3: + opaque type MyString[+T] = String + extension [T](arr: T) def length: Int = 0 // nowarn + +object NonUpperbound4: + opaque type MyString = String + extension (arr: MyString) def length: Int = 0 // nowarn diff --git a/tests/warn/i22233.scala b/tests/warn/i22233.scala new file mode 100644 index 000000000000..08caea1c25fb --- /dev/null +++ b/tests/warn/i22233.scala @@ -0,0 +1 @@ +extension (s: String) def length = 42 // warn diff --git a/tests/warn/i22371.scala b/tests/warn/i22371.scala new file mode 100644 index 000000000000..00f5b66695e0 --- /dev/null +++ b/tests/warn/i22371.scala @@ -0,0 +1,8 @@ +//> using options -Werror -Wunused:all +import scala.compiletime.deferred + +class Context + +trait Foo: + given context: Context = deferred + given () => Context = deferred diff --git a/tests/warn/i22412.check b/tests/warn/i22412.check new file mode 100644 index 000000000000..adbe698db058 --- /dev/null +++ b/tests/warn/i22412.check @@ -0,0 +1,10 @@ +-- [E002] Syntax Warning: tests/warn/i22412.scala:3:34 ----------------------------------------------------------------- +3 | @annotation.nowarn("v") def f = try 1 // warn + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. + |Matching filters for @nowarn or -Wconf: + | - id=E2 + | - name=EmptyCatchAndFinallyBlock + | + | longer explanation available when compiling with `-explain` diff --git a/tests/warn/i22412.scala b/tests/warn/i22412.scala new file mode 100644 index 000000000000..870a264c1df1 --- /dev/null +++ b/tests/warn/i22412.scala @@ -0,0 +1,3 @@ + +class C: + @annotation.nowarn("v") def f = try 1 // warn diff --git a/tests/warn/i22440.check b/tests/warn/i22440.check new file mode 100644 index 000000000000..eaa357661a59 --- /dev/null +++ b/tests/warn/i22440.check @@ -0,0 +1,7 @@ +-- Warning: tests/warn/i22440.scala:4:12 ------------------------------------------------------------------------------- +4 |val _ = foo(1) // warn + | ^ + | Implicit parameters should be provided with a `using` clause. + | This code can be rewritten automatically under -rewrite -source 3.7-migration. + | To disable the warning, please use the following option: + | "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s" diff --git a/tests/warn/i22440.scala b/tests/warn/i22440.scala new file mode 100644 index 000000000000..dfe88b5e5494 --- /dev/null +++ b/tests/warn/i22440.scala @@ -0,0 +1,4 @@ +//> using options -source 3.7 + +def foo(implicit x: Int) = x +val _ = foo(1) // warn diff --git a/tests/warn/i22590.arity2.scala b/tests/warn/i22590.arity2.scala new file mode 100644 index 000000000000..8ce84ab299f1 --- /dev/null +++ b/tests/warn/i22590.arity2.scala @@ -0,0 +1,15 @@ +sealed trait T_B +case class CC_A() extends T_B +case class CC_C() extends T_B + +sealed trait T_A +case class CC_B[B](a: B,b:T_B) extends T_A + + +@main def test() = { + val v_a: CC_B[Int] = null + val v_b: Int = v_a match { // warn: match may not be exhaustive. + case CC_B(12, CC_A()) => 0 + case CC_B(_, CC_C()) => 0 + } +} diff --git a/tests/warn/i22590.scala b/tests/warn/i22590.scala new file mode 100644 index 000000000000..1520a07b86b8 --- /dev/null +++ b/tests/warn/i22590.scala @@ -0,0 +1,9 @@ +sealed trait T_A +case class CC_B[T](a: T) extends T_A + +@main def test() = { + val v_a: CC_B[Int] = CC_B(10) + val v_b: Int = v_a match{ // warn: match may not be exhaustive. + case CC_B(12) => 0 + } +} diff --git a/tests/warn/i22629.scala b/tests/warn/i22629.scala new file mode 100644 index 000000000000..53d17ec4da3b --- /dev/null +++ b/tests/warn/i22629.scala @@ -0,0 +1,42 @@ +//> using options -Wunused:all -Yno-deep-subtypes "-Wconf:msg=set repeatedly:s" + +//import either.* + +trait ResultMapper[A] { + final def map[B](f: A => B): ResultMapper[B] = ??? + + infix final def and[B](other: ResultMapper[B]): ResultMapper[(A, B)] = ??? +} + +trait BoilerplateResultMappers { + + def and[B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W]( + b: ResultMapper[B], c: ResultMapper[C], d: ResultMapper[D], e: ResultMapper[E], f: ResultMapper[F], g: ResultMapper[G], h: ResultMapper[H], i: ResultMapper[I], j: ResultMapper[J], k: ResultMapper[K], l: ResultMapper[L], m: ResultMapper[M], n: ResultMapper[N], o: ResultMapper[O], p: ResultMapper[P], q: ResultMapper[Q], r: ResultMapper[R], s: ResultMapper[S], t: ResultMapper[T], u: ResultMapper[U], v: ResultMapper[V], w: ResultMapper[W] + ): ResultMapper[(B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W)] = + (b and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w).map { + case ((((((((((((((((((((((b), c), d), e), f), g), h), i), j), k), l), m), n), o), p), q), r), s), t), u), v), w) => + (b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w) + } +} + +/* +object either { + type ResultMapperException = RuntimeException + implicit class EitherOps[A](private val ea: Either[ResultMapperException, A]) extends AnyVal { + def and[B](eb: Either[ResultMapperException, B]): Either[ResultMapperException, (A, B)] = + (ea, eb) match { + case (Right(a), Right(b)) => + Right((a, b)) + + case (Right(_), Left(ex)) => + Left(ex) + + case (Left(ex), Right(_)) => + Left(ex) + + case (Left(_), Left(_)) => + Left(RuntimeException()) + } + } +} +*/ diff --git a/tests/warn/i22672/lib_1.scala b/tests/warn/i22672/lib_1.scala new file mode 100644 index 000000000000..722f2ae03899 --- /dev/null +++ b/tests/warn/i22672/lib_1.scala @@ -0,0 +1,16 @@ + +package p + +import annotation.{unchecked as _, *} + +@deprecated("old api", since="1.0") +def g = 42 + +//@deprecated("new api", since="1.0") +@nowarn("cat=deprecation") +inline def f = + g + +transparent inline def body = + g: @nowarn @unchecked + g: @unchecked @nowarn diff --git a/tests/warn/i22672/usage_2.scala b/tests/warn/i22672/usage_2.scala new file mode 100644 index 000000000000..39c0550b8fd4 --- /dev/null +++ b/tests/warn/i22672/usage_2.scala @@ -0,0 +1,13 @@ + +//> using options -deprecation + +package q + +def test = p.f // inline f is nowarn + +def bodily = p.body // transparent inline with annotated body + +@deprecated("do I even know how it works", since="0.1") +def huh = "hello" + +def failing = huh // warn diff --git a/tests/warn/i22681.scala b/tests/warn/i22681.scala new file mode 100644 index 000000000000..e15d186479c3 --- /dev/null +++ b/tests/warn/i22681.scala @@ -0,0 +1,17 @@ + +//> using options -Wunused:all + +trait T: + def t: Int + +class C: + def f: Runnable { def u: Int } = new Runnable with T: + private def v = 42 // avoid g judged too trivial to warn + def run() = () + def g = v // warn effectively private member is unused + def t = v // nowarn + def u = v // nowarn because leaked by refinement + val v: Runnable { def u: Int } = new Runnable: + private def v = 42 // avoid g judged too trivial to warn + def run() = () + def u = v // nowarn because leaked by refinement diff --git a/tests/warn/i22705.scala b/tests/warn/i22705.scala new file mode 100644 index 000000000000..d30c1b310201 --- /dev/null +++ b/tests/warn/i22705.scala @@ -0,0 +1,28 @@ +//> using options -Werror + +object Native { + class Obj: + def f: String = "F" +} + +object Types { + + opaque type Node = Native.Obj + + type S = Node + + object S: + def apply(): S = new Node + + extension (s: S) + def f: String = "S" +} + +import Types.* + +object Main { + def main(args: Array[String]): Unit = { + val v: S = S() + println(v.f) + } +} diff --git a/tests/warn/i22706.scala b/tests/warn/i22706.scala new file mode 100644 index 000000000000..5bd642020e1c --- /dev/null +++ b/tests/warn/i22706.scala @@ -0,0 +1,30 @@ +//> using options -Werror + +object Native { + class O { + def f: String = "F" + } + class M extends O +} + +object Types { + opaque type N = Native.O + opaque type GS = Native.M + + type S = N | GS + + object S: + def apply(): S = new N + + extension (s: S) + def f: String = "S" +} + +import Types.* + +object Main { + def main(args: Array[String]): Unit = { + val v: S = S() + println(v.f) + } +} diff --git a/tests/warn/i22727.scala b/tests/warn/i22727.scala new file mode 100644 index 000000000000..c7b1240c7e6b --- /dev/null +++ b/tests/warn/i22727.scala @@ -0,0 +1,14 @@ +//> using options -Werror + +object Main { + type IXY = (Int, Int) + + extension (xy: IXY) { + def map(f: Int => Int): (Int, Int) = (f(xy._1), f(xy._2)) + } + + def main(args: Array[String]): Unit = { + val a = (0, 1) + println(a) + } +} diff --git a/tests/warn/i22742.scala b/tests/warn/i22742.scala new file mode 100644 index 000000000000..0927d29e1a05 --- /dev/null +++ b/tests/warn/i22742.scala @@ -0,0 +1,10 @@ +//> using options -Wunused:all -Werror + +trait Foldable[F[_]]: + def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B + +type Id[A] = A + +given foldableId: Foldable[Id] = + new Foldable[Id]: + def foldLeft[A, B](fa: Id[A], b: B)(f: (B, A) => B): B = b diff --git a/tests/warn/i22744.scala b/tests/warn/i22744.scala new file mode 100644 index 000000000000..655a14b162ce --- /dev/null +++ b/tests/warn/i22744.scala @@ -0,0 +1,35 @@ + +//> using options -Wunused:privates -Werror + +object test { + private trait Foo[A] { val value: A } + + private object Foo { // no warn prefix of implicit value + given int: Foo[Int] = new Foo[Int] { val value = 1 } + } + + val i = summon[Foo[Int]].value +} + +object supplement { + private trait Foo[A] { val value: A } + + private object Foo { // no warn prefix of implicit value + given int: Foo[Int] = new Foo[Int] { val value = 1 } + } + + private def fooValue[A](using f: Foo[A]): A = f.value + + val i = fooValue[Int] +} + +package p: + private trait Foo[A] { val value: A } + + private object Foo { // no warn prefix of implicit value + given int: Foo[Int] = new Foo[Int] { val value = 1 } + } + + private def fooValue[A](using f: Foo[A]): A = f.value + + val i = fooValue[Int] diff --git a/tests/warn/i22746.scala b/tests/warn/i22746.scala new file mode 100644 index 000000000000..79576f5924be --- /dev/null +++ b/tests/warn/i22746.scala @@ -0,0 +1,21 @@ + +//> using options -Wunused:all -Werror + +import java.time.ZonedDateTime + +trait Foo[A] { + def apply(a: A, t: ZonedDateTime): A +} + +extension [A](a: A)(using f: Foo[A]) { + def foo(t: ZonedDateTime = ZonedDateTime.now): A = f(a, t) +} + +def test[I, A](in: I)( + run: I => Either[Throwable, A], + onErr: Throwable => Throwable = identity[Throwable] +): Either[Throwable, A] = + run(in) match { + case Left(t) => Left(onErr(t)) + case r @ Right(_) => r + } diff --git a/tests/warn/i22899.scala b/tests/warn/i22899.scala new file mode 100644 index 000000000000..ae6544e29286 --- /dev/null +++ b/tests/warn/i22899.scala @@ -0,0 +1,27 @@ +case class CaseClass(a: Int) + +object ProductMatch_CaseClass { + def unapply(int: Int): CaseClass = CaseClass(int) +} + +object ProductMatch_NamedTuple { + def unapply(int: Int): (a: Int) = (a = int) +} + +object NameBasedMatch_CaseClass { + def unapply(int: Int): Some[CaseClass] = Some(CaseClass(int)) +} + +object NameBasedMatch_NamedTuple { + def unapply(int: Int): Some[(a: Int)] = Some((a = int)) +} + +object Test { + val ProductMatch_CaseClass(a = x1) = 1 // ok, was pattern's type (x1 : Int) is more specialized than the right hand side expression's type Int + val ProductMatch_NamedTuple(a = x2) = 2 // ok, was pattern binding uses refutable extractor `org.test.ProductMatch_NamedTuple` + val NameBasedMatch_CaseClass(a = x3) = 3 // ok, was pattern's type (x3 : Int) is more specialized than the right hand side expression's type Int + val NameBasedMatch_NamedTuple(a = x4) = 4 // ok, was pattern's type (x4 : Int) is more specialized than the right hand side expression's type Int + + val CaseClass(a = x5) = CaseClass(5) // ok, was pattern's type (x5 : Int) is more specialized than the right hand side expression's type Int + val (a = x6) = (a = 6) // ok +} \ No newline at end of file diff --git a/tests/warn/i3323.scala b/tests/warn/i3323.scala new file mode 100644 index 000000000000..e409134ff0ba --- /dev/null +++ b/tests/warn/i3323.scala @@ -0,0 +1,8 @@ +//> using options -Werror +class Foo { + def foo[A](lss: List[List[A]]): Unit = { + lss match { + case xss: List[List[A]] => // no warn erasure + } + } +} diff --git a/tests/warn/infix-named-args-migration.scala b/tests/warn/infix-named-args-migration.scala index 361004f08f13..c6da1923ee94 100644 --- a/tests/warn/infix-named-args-migration.scala +++ b/tests/warn/infix-named-args-migration.scala @@ -1,5 +1,4 @@ -//> using options -source:3.6-migration -import scala.language.experimental.namedTuples +//> using options -source:3.7-migration class C: def f = 42 + (x = 1) // warn // interpreted as 42.+(x = 1) under migration, x is a valid synthetic parameter name diff --git a/tests/warn/opaque-match.scala b/tests/warn/opaque-match.scala index 06cca835ab91..f66370def9b2 100644 --- a/tests/warn/opaque-match.scala +++ b/tests/warn/opaque-match.scala @@ -13,8 +13,10 @@ def Test[T] = case _: C => ??? // ok C() match case _: O.T => ??? // warn + case _ => ??? C() match case _: T => ??? // warn + case _ => ??? (??? : Any) match case _: List[O.T] => ??? // warn diff --git a/tests/pos/patmat-exhaustive.scala b/tests/warn/patmat-exhaustive.scala similarity index 56% rename from tests/pos/patmat-exhaustive.scala rename to tests/warn/patmat-exhaustive.scala index 9e3cb7d8f615..a8f057664829 100644 --- a/tests/pos/patmat-exhaustive.scala +++ b/tests/warn/patmat-exhaustive.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings -deprecation -feature +//> using options -Werror -deprecation -feature def foo: Unit = object O: @@ -8,5 +8,5 @@ def foo: Unit = val x: O.A = ??? x match - case x: B => ??? - case x: C => ??? + case _: B => ??? + case _: C => ??? diff --git a/tests/warn/scala2-t11681.scala b/tests/warn/scala2-t11681.scala index ae2187181ceb..507b58bf2277 100644 --- a/tests/warn/scala2-t11681.scala +++ b/tests/warn/scala2-t11681.scala @@ -23,7 +23,7 @@ trait BadAPI extends InterFace { a } override def call(a: Int, - b: String, // OK + b: String, // no warn (override) c: Double): Int = { println(c) a @@ -33,7 +33,7 @@ trait BadAPI extends InterFace { override def equals(other: Any): Boolean = true // OK - def i(implicit s: String) = answer // ok + def i(implicit s: String) = answer // warn now /* def future(x: Int): Int = { @@ -59,10 +59,10 @@ class Revaluing(u: Int) { def f = u } // OK case class CaseyKasem(k: Int) // OK -case class CaseyAtTheBat(k: Int)(s: String) // ok +case class CaseyAtTheBat(k: Int)(s: String) // warn unused s trait Ignorance { - def f(readResolve: Int) = answer // ok + def f(readResolve: Int) = answer // warn now } class Reusing(u: Int) extends Unusing(u) // OK @@ -78,30 +78,30 @@ trait Unimplementation { trait DumbStuff { def f(implicit dummy: DummyImplicit) = answer // ok - def g(dummy: DummyImplicit) = answer // ok + def g(dummy: DummyImplicit) = answer // warn now } trait Proofs { def f[A, B](implicit ev: A =:= B) = answer // ok def g[A, B](implicit ev: A <:< B) = answer // ok - def f2[A, B](ev: A =:= B) = answer // ok - def g2[A, B](ev: A <:< B) = answer // ok + def f2[A, B](ev: A =:= B) = answer // warn now + def g2[A, B](ev: A <:< B) = answer // warn now } trait Anonymous { - def f = (i: Int) => answer // ok + def f = (i: Int) => answer // warn now def f1 = (_: Int) => answer // OK def f2: Int => Int = _ + 1 // OK - def g = for (i <- List(1)) yield answer // ok + def g = for (i <- List(1)) yield answer // no warn (that is a patvar) } trait Context[A] trait Implicits { - def f[A](implicit ctx: Context[A]) = answer // ok - def g[A: Context] = answer // OK + def f[A](implicit ctx: Context[A]) = answer // warn implicit param even though only marker + def g[A: Context] = answer // no warn bound that is marker only } -class Bound[A: Context] // OK +class Bound[A: Context] // no warn bound that is marker only object Answers { def answer: Int = 42 } diff --git a/tests/pos/t10373.scala b/tests/warn/t10373.scala similarity index 74% rename from tests/pos/t10373.scala rename to tests/warn/t10373.scala index 0d91313f694d..ca49e7b1ce16 100644 --- a/tests/pos/t10373.scala +++ b/tests/warn/t10373.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings -deprecation -feature +//> using options -deprecation -feature abstract class Foo { def bar(): Unit = this match { @@ -7,7 +7,7 @@ abstract class Foo { // Works fine } - def baz(that: Foo): Unit = (this, that) match { + def baz(that: Foo): Unit = (this, that) match { // warn: match may not be exhaustive. case (Foo_1(), _) => //do something case (Foo_2(), _) => //do something // match may not be exhaustive diff --git a/tests/warn/t13095.scala b/tests/warn/t13095.scala new file mode 100644 index 000000000000..0634f4ed903d --- /dev/null +++ b/tests/warn/t13095.scala @@ -0,0 +1,10 @@ +//> using options -Wunused:patvars -Werror + +case class A(x: Int, y: Int) + +object Main { + for { + a <- List.empty[A] + A(x, y) = a + } yield x + y +} diff --git a/tests/warn/t22507.scala b/tests/warn/t22507.scala new file mode 100644 index 000000000000..76ae9a2b9a8b --- /dev/null +++ b/tests/warn/t22507.scala @@ -0,0 +1,14 @@ + +//> using options -Werror -Wunused:privates + +case class BinaryFen(value: Array[Byte]) extends AnyVal: + + def read: Unit = + val reader = new Iterator[Byte]: + val inner = value.iterator + override inline def hasNext: Boolean = inner.hasNext // nowarn + override inline def next: Byte = if hasNext then inner.next else 0.toByte // nowarn + + if reader.hasNext then + val b: Byte = reader.next + println(b) diff --git a/tests/warn/tuple-exhaustivity.scala b/tests/warn/tuple-exhaustivity.scala new file mode 100644 index 000000000000..9060d112b197 --- /dev/null +++ b/tests/warn/tuple-exhaustivity.scala @@ -0,0 +1,6 @@ +//> using options -Werror -deprecation -feature + +def test(t: Tuple) = + t match + case Tuple() => + case h *: t => diff --git a/tests/warn/unused-can-equal.scala b/tests/warn/unused-can-equal.scala new file mode 100644 index 000000000000..6e38591ccef1 --- /dev/null +++ b/tests/warn/unused-can-equal.scala @@ -0,0 +1,16 @@ + +//> using options -Werror -Wunused:all + +import scala.language.strictEquality + +class Box[T](x: T) derives CanEqual: + def y = x + +def f[A, B](a: A, b: B)(using CanEqual[A, B]) = a == b // no warn + +def g = + import Box.given // no warn + "42".length + +@main def test() = println: + Box(1) == Box(1L) diff --git a/tests/warn/unused-locals.scala b/tests/warn/unused-locals.scala new file mode 100644 index 000000000000..10bf160fb717 --- /dev/null +++ b/tests/warn/unused-locals.scala @@ -0,0 +1,43 @@ +//> using options -Wunused:locals + +class Outer { + class Inner +} + +trait Locals { + def f0 = { + var x = 1 // warn + var y = 2 // no warn + y = 3 + y + y + } + def f1 = { + val a = new Outer // no warn + val b = new Outer // warn + new a.Inner + } + def f2 = { + var x = 100 // warn about it being a var + x + } +} + +object Types { + def l1() = { + object HiObject { def f = this } // warn + class Hi { // warn + def f1: Hi = new Hi + def f2(x: Hi) = x + } + class DingDongDoobie // warn + class Bippy // no warn + type Something = Bippy // no warn + type OtherThing = String // warn + (new Bippy): Something + } +} + +// breakage: local val x$1 in method skolemize is never used +case class SymbolKind(accurate: String, sanitized: String, abbreviation: String) { + def skolemize: SymbolKind = copy(accurate = s"$accurate skolem", abbreviation = s"$abbreviation#SKO") +} diff --git a/tests/warn/unused-params.scala b/tests/warn/unused-params.scala new file mode 100644 index 000000000000..3266f3957247 --- /dev/null +++ b/tests/warn/unused-params.scala @@ -0,0 +1,159 @@ +//> using options -Wunused:params +// + +import Answers._ + +trait InterFace { + /** Call something. */ + def call(a: Int, b: String, c: Double): Int +} + +trait BadAPI extends InterFace { + def f(a: Int, + b: String, // warn + c: Double): Int = { + println(c) + a + } + @deprecated("no warn in deprecated API", since="yesterday") + def g(a: Int, + b: String, // no warn + c: Double): Int = { + println(c) + a + } + override def call(a: Int, + b: String, // no warn (override) + c: Double): Int = { + println(c) + a + } + + def meth(x: Int) = x + + override def equals(other: Any): Boolean = true // no warn + + def i(implicit s: String) = answer // warn + + /* + def future(x: Int): Int = { + val y = 42 + val x = y // maybe option to warn only if shadowed + x + } + */ +} + +// mustn't alter warnings in super +trait PoorClient extends BadAPI { + override def meth(x: Int) = ??? // no warn + override def f(a: Int, b: String, c: Double): Int = a + b.toInt + c.toInt +} + +class Unusing(u: Int) { // warn + def f = ??? +} + +class Valuing(val u: Int) // no warn + +class Revaluing(u: Int) { def f = u } // no warn + +case class CaseyKasem(k: Int) // no warn + +case class CaseyAtTheBat(k: Int)(s: String) // warn + +trait Ignorance { + def f(readResolve: Int) = answer // warn +} + +class Reusing(u: Int) extends Unusing(u) // no warn + +class Main { + def main(args: Array[String]): Unit = println("hello, args") // no warn +} + +trait Unimplementation { + def f(u: Int): Int = ??? // no warn for param in unimplementation +} + +trait DumbStuff { + def f(implicit dummy: DummyImplicit) = answer + def g(dummy: DummyImplicit) = answer // warn +} +trait Proofs { + def f[A, B](implicit ev: A =:= B) = answer + def g[A, B](implicit ev: A <:< B) = answer + def f2[A, B](ev: A =:= B) = answer // warn + def g2[A, B](ev: A <:< B) = answer // warn +} + +trait Anonymous { + def f = (i: Int) => answer // warn + + def f1 = (_: Int) => answer // no warn underscore parameter (a fresh name) + + def f2: Int => Int = _ + 1 // no warn placeholder syntax (a fresh name and synthetic parameter) + + def g = for (i <- List(1)) yield answer // no warn patvar elaborated as map.(i => 42) +} +trait Context[A] { def m(a: A): A = a } +trait Implicits { + def f[A](implicit ctx: Context[A]) = answer // warn + def g[A: Context] = answer // warn + def h[A](using Context[A]) = answer // warn +} +class Bound[A: Context] // warn +object Answers { + def answer: Int = 42 +} + +trait BadMix { self: InterFace => + def f(a: Int, + b: String, // warn + c: Double): Int = { + println(c) + a + } + @deprecated("no warn in deprecated API", since="yesterday") + def g(a: Int, + b: String, // no warn + c: Double): Int = { + println(c) + a + } + override def call(a: Int, + XXXX: String, // warn no longer excused because required by superclass + c: Double): Int = { + println(c) + a + } + + def meth(x: Int) = x + + override def equals(other: Any): Boolean = true // no warn + + def i(implicit s: String) = answer // warn +} + +class Unequal { + override def equals(other: Any) = toString.nonEmpty // no warn (override) +} + +class Seriously { + def f(s: Serializable) = toString.nonEmpty // warn explicit param of marker trait +} + +class TryStart(start: String) { + def FINALLY(end: END.type) = start // no warn for DSL taking a singleton +} + +object END + +object Optional: + extension (opt: Option.type) // no warn for extension of module + @annotation.experimental + inline def fromNullable[T](t: T | Null): Option[T] = Option(t).asInstanceOf[Option[T]] + +class Nested { + @annotation.unused private def actuallyNotUsed(fresh: Int, stale: Int) = fresh // no warn if owner is unused +} diff --git a/tests/warn/unused-privates.scala b/tests/warn/unused-privates.scala new file mode 100644 index 000000000000..8864bc16de2b --- /dev/null +++ b/tests/warn/unused-privates.scala @@ -0,0 +1,310 @@ +// +//> using options -deprecation -Wunused:privates,locals +// +class Bippy(a: Int, b: Int) { + private def this(c: Int) = this(c, c) // warn (DO warn, was NO) + private def bippy(x: Int): Int = bippy(x) // warn + private def boop(x: Int) = x+a+b // warn + final private val MILLIS1 = 2000 // warn, scala2: no warn, might have been inlined + final private val MILLIS2: Int = 1000 // warn + final private val HI_COMPANION: Int = 500 // no warn, accessed from companion + def hi() = Bippy.HI_INSTANCE +} +object Bippy { + def hi(x: Bippy) = x.HI_COMPANION + private val HI_INSTANCE: Int = 500 // no warn, accessed from instance + private val HEY_INSTANCE: Int = 1000 // warn + private lazy val BOOL: Boolean = true // warn +} + +class A(val msg: String) +class B1(msg: String) extends A(msg) +class B2(msg0: String) extends A(msg0) +class B3(msg0: String) extends A("msg") + +trait Accessors { + private var v1: Int = 0 // warn + private var v2: Int = 0 // warn, never set + private var v3: Int = 0 // warn, never got + private var v4: Int = 0 // no warn + + private var v5 = 0 // warn, never set + private var v6 = 0 // warn, never got + private var v7 = 0 // no warn + + def bippy(): Int = { + v3 = 3 + v4 = 4 + v6 = 6 + v7 = 7 + v2 + v4 + v5 + v7 + } +} + +class StableAccessors { + private var s1: Int = 0 // warn + private var s2: Int = 0 // warn, never set + private var s3: Int = 0 // warn, never got + private var s4: Int = 0 // no warn + + private var s5 = 0 // warn, never set + private var s6 = 0 // warn, never got + private var s7 = 0 // no warn + + def bippy(): Int = { + s3 = 3 + s4 = 4 + s6 = 6 + s7 = 7 + s2 + s4 + s5 + s7 + } +} + +trait DefaultArgs { + // DO warn about default getters for x2 and x3 + private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 // warn // warn + + def boppy() = bippy(5, 100, 200) +} + +/* scala/bug#7707 Both usages warn default arg because using PrivateRyan.apply, not new. +case class PrivateRyan private (ryan: Int = 42) { def f = PrivateRyan() } +object PrivateRyan { def f = PrivateRyan() } +*/ + +class Outer { + class Inner +} + +trait Locals { + def f0 = { + var x = 1 // warn + var y = 2 + y = 3 + y + y + } + def f1 = { + val a = new Outer // no warn + val b = new Outer // warn + new a.Inner + } + def f2 = { + var x = 100 // warn about it being a var + x + } +} + +object Types { + private object Dongo { def f = this } // warn + private class Bar1 // warn + private class Bar2 // no warn + private type Alias1 = String // warn + private type Alias2 = String // no warn + def bippo = (new Bar2).toString + + def f(x: Alias2) = x.length + + def l1() = { + object HiObject { def f = this } // warn + class Hi { // warn + def f1: Hi = new Hi + def f2(x: Hi) = x + } + class DingDongDoobie // warn + class Bippy // no warn + type Something = Bippy // no warn + type OtherThing = String // warn + (new Bippy): Something + } +} + +trait Underwarn { + def f(): Seq[Int] + + def g() = { + val Seq(_, _) = f() // no warn + true + } +} + +class OtherNames { + private def x_=(i: Int): Unit = () // warn + private def x: Int = 42 // warn + private def y_=(i: Int): Unit = () // warn + private def y: Int = 42 + + def f = y +} + +case class C(a: Int, b: String, c: Option[String]) +case class D(a: Int) + +// patvars which used to warn as vals in older scala 2 +trait Boundings { + + def c = C(42, "hello", Some("world")) + def d = D(42) + + def f() = { + val C(x, y, Some(z)) = c: @unchecked // no warn + 17 + } + def g() = { + val C(x @ _, y @ _, Some(z @ _)) = c: @unchecked // no warn + 17 + } + def h() = { + val C(x @ _, y @ _, z @ Some(_)) = c: @unchecked // no warn for z? + 17 + } + + def v() = { + val D(x) = d // no warn + 17 + } + def w() = { + val D(x @ _) = d // no warn + 17 + } + +} + +trait Forever { + def f = { + val t = Option((17, 42)) + for { + ns <- t + (i, j) = ns // no warn + } yield (i + j) + } + def g = { + val t = Option((17, 42)) + for { + ns <- t + (i, j) = ns // no warn + } yield 42 // val emitted only if needed, hence nothing unused + } +} + +trait Ignorance { + private val readResolve = 42 // warn wrong signatured for special members +} + +trait CaseyKasem { + def f = 42 match { + case x if x < 25 => "no warn" + case y if toString.nonEmpty => "no warn" + y + case z => "warn" + } +} +trait CaseyAtTheBat { + def f = Option(42) match { + case Some(x) if x < 25 => "no warn" + case Some(y @ _) if toString.nonEmpty => "no warn" + case Some(z) => "warn" + case None => "no warn" + } +} + +class `not even using companion privates` + +object `not even using companion privates` { + private implicit class `for your eyes only`(i: Int) { // warn + def f = i + } +} + +class `no warn in patmat anonfun isDefinedAt` { + def f(pf: PartialFunction[String, Int]) = pf("42") + def g = f { + case s => s.length // no warn (used to warn case s => true in isDefinedAt) + } +} + +// this is the ordinary case, as AnyRef is an alias of Object +class `nonprivate alias is enclosing` { + class C + type C2 = C + private class D extends C2 // warn +} + +object `classof something` { + private class intrinsically + def f = classOf[intrinsically].toString() +} + +trait `scala 2 short comings` { + def f: Int = { + val x = 42 // warn + 17 + } +} + +class `issue 12600 ignore abstract types` { + type Abs +} + +class `t12992 enclosing def is unused` { + private val n = 42 + @annotation.unused def f() = n + 2 // unused code uses n +} + +class `recursive reference is not a usage` { + private def f(i: Int): Int = // warn + if (i <= 0) i + else f(i-1) + private class P { // warn + def f() = new P() + } +} + +class `absolve serial framework` extends Serializable: + import java.io.{IOException, ObjectInputStream, ObjectOutputStream, ObjectStreamException} + @throws(classOf[IOException]) + private def writeObject(stream: ObjectOutputStream): Unit = () + @throws(classOf[ObjectStreamException]) + private def writeReplace(): Object = ??? + @throws(classOf[ClassNotFoundException]) + @throws(classOf[IOException]) + private def readObject(stream: ObjectInputStream): Unit = () + @throws(classOf[ObjectStreamException]) + private def readObjectNoData(): Unit = () + @throws(classOf[ObjectStreamException]) + private def readResolve(): Object = ??? + +class `absolve ONLY serial framework`: + import java.io.{IOException, ObjectInputStream, ObjectOutputStream, ObjectStreamException} + @throws(classOf[IOException]) + private def writeObject(stream: ObjectOutputStream): Unit = () // warn + @throws(classOf[ObjectStreamException]) + private def writeReplace(): Object = new Object // warn + @throws(classOf[ClassNotFoundException]) + @throws(classOf[IOException]) + private def readObject(stream: ObjectInputStream): Unit = () // warn + @throws(classOf[ObjectStreamException]) + private def readObjectNoData(): Unit = () // warn + @throws(classOf[ObjectStreamException]) + private def readResolve(): Object = new Object // warn + +@throws(classOf[java.io.ObjectStreamException]) +private def readResolve(): Object = ??? // TODO warn +private def print() = println() // TODO warn +private val printed = false // TODO warn + +package locked: + private[locked] def locker(): Unit = () // TODO warn as we cannot distinguish unqualified private at top level + package basement: + private[locked] def shackle(): Unit = () // no warn as it is not top level at boundary + +object `i19998 refinement`: + trait Foo { + type X[a] + } + trait Bar[X[_]] { + private final type SelfX[a] = X[a] // was false positive + val foo: Foo { type X[a] = SelfX[a] } + } + +object `patvar is assignable`: + private var (i, j) = (42, 27) // no warn patvars under -Wunused:privates + println((i, j))