diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000000..40ec37515a2e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +# Any changes to the Scala 3 Standard Library must be approve +# by one of the officers responsible of maintaining it +/library/ @scala/stdlib-officers +/library-aux/ @scala/stdlib-officers +/library-js/ @scala/stdlib-officers diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 6e53805900c4..8feba58c44d7 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -9,19 +9,33 @@ assignees: '' ## Compiler version -If you're not sure what version you're using, run `print scalaVersion` from sbt -(if you're running scalac manually, use `scalac -version` instead). +If you're not sure which version you're using, run `print scalaVersion` from sbt. +(If you're running scalac manually, use `scalac -version` instead.) + +If possible, check if your issue appears in the nightly version of the compiler! For example, in Scala CLI (the `scala`/`scala-cli` runner script), you can use `//> using scala 3.nightly` (or `-S 3.nightly` from the command line) to grab the latest one. ## Minimized code -```Scala -println("hello, world") +```scala +//> using scala 3.7.0 +//> using options -Wall -Werror +//> using dep com.outr::scribe:3.16.1 + +@main def test = println("hello, world") ``` ## Output diff --git a/.github/workflows/build-msi.yml b/.github/workflows/build-msi.yml index 14838c589d6a..ca6100179776 100644 --- a/.github/workflows/build-msi.yml +++ b/.github/workflows/build-msi.yml @@ -21,8 +21,8 @@ jobs: build: runs-on: windows-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-java@v5 with: distribution: 'adopt' java-version: '8' diff --git a/.github/workflows/build-sdk.yml b/.github/workflows/build-sdk.yml index cd111df1a083..8248f10ae824 100644 --- a/.github/workflows/build-sdk.yml +++ b/.github/workflows/build-sdk.yml @@ -55,8 +55,8 @@ jobs: env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-java@v5 with: distribution: temurin java-version: ${{ inputs.java-version }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index db2a66b8b234..c6d6fe539e62 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,10 +26,10 @@ on: - cron: '0 3 * * *' # Every day at 3 AM workflow_dispatch: -# Cancels any in-progress runs within the same group identified by workflow name and GH reference (branch or tag) +# Cancels any in-progress runs within the same group identified by workflow name and GH reference (branch or tag) # For example it would: # - terminate previous PR CI execution after pushing more changes to the same PR branch -# - terminate previous on-push CI run after merging new PR to main +# - terminate previous on-push CI run after merging new PR to main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} @@ -692,7 +692,7 @@ jobs: - name: Publish Nightly if: "steps.not_yet_published.outcome == 'success'" run: | - ./project/scripts/sbtPublish ";project scala3-bootstrapped ;publishSigned ;sonatypeBundleRelease" + ./project/scripts/sbtPublish ";project scala3-bootstrapped ;publishSigned ;sonaRelease" nightly_documentation: runs-on: [self-hosted, Linux] @@ -824,7 +824,7 @@ jobs: prepareSDK "-x86_64-pc-win32" "dist-win-x86_64" "./dist/win-x86_64/" - name: Download MSI package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: scala.msi path: . @@ -862,7 +862,7 @@ jobs: scala3-${{ env.RELEASE_TAG }}.msi - name: Publish Release - run: ./project/scripts/sbtPublish ";project scala3-bootstrapped ;publishSigned ;sonatypeBundleUpload" + run: ./project/scripts/sbtPublish ";project scala3-bootstrapped ;publishSigned ;sonaUpload" open_issue_on_failure: diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index d052dc5eb6fc..50b75751f49a 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -6,6 +6,7 @@ on: jobs: check: runs-on: ubuntu-latest + if: github.event.pull_request.user.login != 'dependabot' steps: - name: Verify CLA uses: scala/cla-checker@v1 diff --git a/.github/workflows/language-reference.yaml b/.github/workflows/language-reference.yaml index d79f4d029a77..6d6a5fd904db 100644 --- a/.github/workflows/language-reference.yaml +++ b/.github/workflows/language-reference.yaml @@ -31,7 +31,7 @@ jobs: ssh-key: ${{ secrets.DOCS_KEY }} - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -70,22 +70,3 @@ jobs: fi cd .. - backport-to-main: - name: Create pull request with backport to main - permissions: - pull-requests: write # for repo-sync/pull-request to create a PR - runs-on: ubuntu-latest - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' - steps: - - uses: actions/checkout@v4 - - uses: repo-sync/pull-request@v2 - with: - destination_branch: main - pr_label: area:documentation - pr_title: Sync with the stable documentation branch - pr_body: | - This pull request is syncing the main with changes from language-reference-stable. - - It was created automatically after ${{ github.event.head_commit.id }} by @${{ github.event.head_commit.author.username }} - pr_assignee: ${{ github.event.head_commit.author.username }} - diff --git a/.github/workflows/lts-backport.yaml b/.github/workflows/lts-backport.yaml index bf602cd075df..9f5bb992440b 100644 --- a/.github/workflows/lts-backport.yaml +++ b/.github/workflows/lts-backport.yaml @@ -16,7 +16,7 @@ jobs: with: fetch-depth: 0 - uses: coursier/cache-action@v6 - - uses: VirtusLab/scala-cli-setup@v1.7.1 + - uses: VirtusLab/scala-cli-setup@v1.9.1 - run: scala-cli ./project/scripts/addToBackportingProject.scala -- ${{ github.sha }} env: GRAPHQL_API_TOKEN: ${{ secrets.GRAPHQL_API_TOKEN }} diff --git a/.github/workflows/publish-chocolatey.yml b/.github/workflows/publish-chocolatey.yml index 88a8a7913188..62f0fb864c21 100644 --- a/.github/workflows/publish-chocolatey.yml +++ b/.github/workflows/publish-chocolatey.yml @@ -31,7 +31,7 @@ jobs: runs-on: windows-latest steps: - name: Fetch the Chocolatey package from GitHub - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: scala.nupkg - name: Publish the package to Chocolatey diff --git a/.github/workflows/publish-sdkman.yml b/.github/workflows/publish-sdkman.yml index fbbada2a1a70..a3643aa59331 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@2800d4359ae097a99afea7e0370f0c6e726182a4 + - uses: sdkman/sdkman-release-action@c70225d437d17182d19476702b671513dc8bf048 with: CONSUMER-KEY : ${{ secrets.CONSUMER-KEY }} CONSUMER-TOKEN : ${{ secrets.CONSUMER-TOKEN }} diff --git a/.github/workflows/scaladoc.yaml b/.github/workflows/scaladoc.yaml index d2e3071e765b..6b79e1037e47 100644 --- a/.github/workflows/scaladoc.yaml +++ b/.github/workflows/scaladoc.yaml @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 @@ -80,7 +80,7 @@ jobs: uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: 17 diff --git a/.github/workflows/stdlib.yaml b/.github/workflows/stdlib.yaml new file mode 100644 index 000000000000..8a6c4fba2383 --- /dev/null +++ b/.github/workflows/stdlib.yaml @@ -0,0 +1,292 @@ +name: Compile Full Standard Library + +on: + push: + branches: + - 'main' + pull_request: + +permissions: + contents: read + +jobs: + scala-library-nonbootstrapped: + runs-on: ubuntu-latest + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + + - uses: sbt/setup-sbt@v1 + - name: Compile `scala-library-nonbootstrapped` + run: ./project/scripts/sbt scala-library-nonbootstrapped/compile + + scala3-library-nonbootstrapped: + runs-on: ubuntu-latest + ##needs: [scala-library-nonbootstrapped] Add when we add support for caching here + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + + - uses: sbt/setup-sbt@v1 + - name: Compile `scala3-library-nonbootstrapped` + run: ./project/scripts/sbt scala3-library-nonbootstrapped/compile + + scala-library-bootstrapped: + runs-on: ubuntu-latest + needs : [scala3-compiler-nonbootstrapped, scala3-sbt-bridge-nonbootstrapped, scala-library-nonbootstrapped, scala3-library-nonbootstrapped] + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + + - uses: sbt/setup-sbt@v1 + - name: Compile `scala-library-bootstrapped` + run: ./project/scripts/sbt scala-library-bootstrapped/compile + + scala3-library-bootstrapped: + runs-on: ubuntu-latest + ##needs: [scala-library-bootstrapped] Add when we add support for caching here + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + + - uses: sbt/setup-sbt@v1 + - name: Compile `scala3-library-bootstrapped` + run: ./project/scripts/sbt scala3-library-bootstrapped-new/compile + + tasty-core-nonbootstrapped: + runs-on: ubuntu-latest + ##needs: [scala3-library-nonbootstrapped] Add when we add support for caching here + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + - uses: sbt/setup-sbt@v1 + - name: Compile `tasty-core-nonbootstrapped` + run: ./project/scripts/sbt tasty-core-nonbootstrapped/compile + + scala3-compiler-nonbootstrapped: + runs-on: ubuntu-latest + ##needs: [tasty-core-nonbootstrapped, scala3-library-nonbootstrapped] Add when we add support for caching here + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + - uses: sbt/setup-sbt@v1 + - name: Compile `scala3-compiler-nonbootstrapped` + run: ./project/scripts/sbt scala3-compiler-nonbootstrapped/compile + + scala3-sbt-bridge-nonbootstrapped: + runs-on: ubuntu-latest + ##needs: [scala3-compiler-nonbootstrapped] Add when we add support for caching here + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + - uses: sbt/setup-sbt@v1 + - name: Compile `scala3-sbt-bridge-nonbootstrapped` + run: ./project/scripts/sbt scala3-sbt-bridge-nonbootstrapped/compile + + tasty-core-bootstrapped: + runs-on: ubuntu-latest + ##needs: [scala3-library-bootstrapped] Add when we add support for caching here + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + - uses: sbt/setup-sbt@v1 + - name: Compile `tasty-core-bootstrapped` + run: ./project/scripts/sbt tasty-core-bootstrapped-new/compile + + scala3-compiler-bootstrapped: + runs-on: ubuntu-latest + ##needs: [tasty-core-bootstrapped, scala3-library-bootstrapped] Add when we add support for caching here + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + - uses: sbt/setup-sbt@v1 + - name: Compile `scala3-compiler-bootstrapped` + run: ./project/scripts/sbt scala3-compiler-bootstrapped-new/compile + + scala3-sbt-bridge-bootstrapped: + runs-on: ubuntu-latest + ##needs: [scala3-compiler-bootstrapped] Add when we add support for caching here + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + - uses: sbt/setup-sbt@v1 + - name: Compile `scala3-sbt-bridge-bootstrapped` + run: ./project/scripts/sbt scala3-sbt-bridge-bootstrapped/compile + + scala3-staging: + runs-on: ubuntu-latest + ##needs: [scala3-compiler-bootstrapped] Add when we add support for caching here + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + - uses: sbt/setup-sbt@v1 + - name: Compile `scala3-staging` + run: ./project/scripts/sbt scala3-staging-new/compile + + scala3-tasty-inspector: + runs-on: ubuntu-latest + ##needs: [scala3-compiler-bootstrapped] Add when we add support for caching here + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + - uses: sbt/setup-sbt@v1 + - name: Compile `scala3-staging` + run: ./project/scripts/sbt scala3-staging-new/compile + - name: Compile `scala3-tasty-inspector` + run: ./project/scripts/sbt scala3-tasty-inspector-new/compile + + ################################################################################################# + ########################################### TEST JOBS ########################################### + ################################################################################################# + + test-scala3-sbt-bridge-nonbootstrapped: + runs-on: ubuntu-latest + ##needs: [scala3-sbt-bridge-nonbootstrapped] Add when we add support for caching here + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + - uses: sbt/setup-sbt@v1 + - name: Test `scala3-sbt-bridge-nonbootstrapped` + run: ./project/scripts/sbt scala3-sbt-bridge-nonbootstrapped/test + + test-scala3-sbt-bridge-bootstrapped: + runs-on: ubuntu-latest + ##needs: [scala3-sbt-bridge-bootstrapped] Add when we add support for caching here + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + - uses: sbt/setup-sbt@v1 + - name: Test `scala3-sbt-bridge-bootstrapped` + run: ./project/scripts/sbt scala3-sbt-bridge-bootstrapped/test + + test-tasty-core-nonbootstrapped: + runs-on: ubuntu-latest + ##needs: [tasty-core-nonbootstrapped] Add when we add support for caching here + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + - uses: sbt/setup-sbt@v1 + - name: Test `tasty-core-nonbootstrapped` + run: ./project/scripts/sbt tasty-core-nonbootstrapped/test + + test-tasty-core-bootstrapped: + runs-on: ubuntu-latest + ##needs: [tasty-core-bootstrapped] Add when we add support for caching here + steps: + - name: Git Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + - uses: sbt/setup-sbt@v1 + - name: Test `tasty-core-bootstrapped` + run: ./project/scripts/sbt tasty-core-bootstrapped-new/test diff --git a/.github/workflows/test-chocolatey.yml b/.github/workflows/test-chocolatey.yml index e302968b9129..212af8dae50d 100644 --- a/.github/workflows/test-chocolatey.yml +++ b/.github/workflows/test-chocolatey.yml @@ -30,12 +30,12 @@ jobs: test: runs-on: windows-latest steps: - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: distribution: temurin java-version: ${{ inputs.java-version }} - name: Download the 'nupkg' from GitHub Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: scala.nupkg path: ${{ env.CHOCOLATEY-REPOSITORY }} diff --git a/.github/workflows/test-launchers.yml b/.github/workflows/test-launchers.yml index 25bd5a4bf42f..e15f4da3bdfc 100644 --- a/.github/workflows/test-launchers.yml +++ b/.github/workflows/test-launchers.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' @@ -32,7 +32,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' @@ -51,7 +51,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' @@ -70,7 +70,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' @@ -89,7 +89,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' diff --git a/.github/workflows/test-msi.yml b/.github/workflows/test-msi.yml index 1299c3d55061..4ced29a61cf0 100644 --- a/.github/workflows/test-msi.yml +++ b/.github/workflows/test-msi.yml @@ -24,12 +24,12 @@ jobs: test: runs-on: windows-latest steps: - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: distribution: temurin java-version: ${{ inputs.java-version }} - name: Download MSI artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: scala.msi path: . diff --git a/.jvmopts b/.jvmopts index 4df4f826d1db..d8606eba733e 100644 --- a/.jvmopts +++ b/.jvmopts @@ -1,4 +1,4 @@ --Xss1m +-Xss2m -Xms1024m -Xmx8192m -XX:MaxInlineLevel=35 diff --git a/build.sbt b/build.sbt index 9425c4eed1e9..2257fdb9f677 100644 --- a/build.sbt +++ b/build.sbt @@ -1,15 +1,27 @@ val scala3 = Build.scala3 +val `scala3-nonbootstrapped` = Build.`scala3-nonbootstrapped` val `scala3-bootstrapped` = Build.`scala3-bootstrapped` +val `scala3-bootstrapped-new` = Build.`scala3-bootstrapped-new` val `scala3-interfaces` = Build.`scala3-interfaces` val `scala3-compiler` = Build.`scala3-compiler` +val `scala3-compiler-nonbootstrapped` = Build.`scala3-compiler-nonbootstrapped` +val `scala3-compiler-bootstrapped-new` = Build.`scala3-compiler-bootstrapped-new` val `scala3-compiler-bootstrapped` = Build.`scala3-compiler-bootstrapped` +val `scala-library-nonbootstrapped` = Build.`scala-library-nonbootstrapped` +val `scala3-library-nonbootstrapped` = Build.`scala3-library-nonbootstrapped` +val `scala-library-bootstrapped` = Build.`scala-library-bootstrapped` +val `scala3-library-bootstrapped-new` = Build.`scala3-library-bootstrapped-new` val `scala3-library` = Build.`scala3-library` val `scala3-library-bootstrapped` = Build.`scala3-library-bootstrapped` val `scala3-library-bootstrappedJS` = Build.`scala3-library-bootstrappedJS` val `scala3-sbt-bridge` = Build.`scala3-sbt-bridge` +val `scala3-sbt-bridge-bootstrapped` = Build.`scala3-sbt-bridge-bootstrapped` +val `scala3-sbt-bridge-nonbootstrapped` = Build.`scala3-sbt-bridge-nonbootstrapped` val `scala3-sbt-bridge-tests` = Build.`scala3-sbt-bridge-tests` val `scala3-staging` = Build.`scala3-staging` +val `scala3-staging-new` = Build.`scala3-staging-new` val `scala3-tasty-inspector` = Build.`scala3-tasty-inspector` +val `scala3-tasty-inspector-new` = Build.`scala3-tasty-inspector-new` val `scala3-language-server` = Build.`scala3-language-server` val `scala3-bench` = Build.`scala3-bench` val `scala3-bench-bootstrapped` = Build.`scala3-bench-bootstrapped` @@ -19,6 +31,8 @@ val `scala2-library-tasty` = Build.`scala2-library-tasty` val `scala2-library-cc` = Build.`scala2-library-cc` val `scala2-library-cc-tasty` = Build.`scala2-library-cc-tasty` val `tasty-core` = Build.`tasty-core` +val `tasty-core-nonbootstrapped` = Build.`tasty-core-nonbootstrapped` +val `tasty-core-bootstrapped-new` = Build.`tasty-core-bootstrapped-new` val `tasty-core-bootstrapped` = Build.`tasty-core-bootstrapped` val `tasty-core-scala2` = Build.`tasty-core-scala2` val scaladoc = Build.scaladoc 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/changelogs/3.7.1-RC1.md b/changelogs/3.7.1-RC1.md new file mode 100644 index 000000000000..8f61c0d2bf32 --- /dev/null +++ b/changelogs/3.7.1-RC1.md @@ -0,0 +1,157 @@ +# Highlights of the release + +- Support for JDK 25 [#23004](https://github.com/scala/scala3/pull/23004) +- Warn if interpolator uses toString [#20578](https://github.com/scala/scala3/pull/20578) +- Warn if match in block is not used for PartialFunction [#23002](https://github.com/scala/scala3/pull/23002) + +# Other changes and fixes + +## Annotations + +- Approximate annotated types in `wildApprox` [#22893](https://github.com/scala/scala3/pull/22893) +- Keep unused annot on params [#23037](https://github.com/scala/scala3/pull/23037) + +## Erasure + +- Disallow context function types as value-class parameters [#23015](https://github.com/scala/scala3/pull/23015) + +## Experimental: Capture Checking + +- Two fixes to handling of abstract types with cap bounds [#22838](https://github.com/scala/scala3/pull/22838) +- Drop idempotent type maps [#22910](https://github.com/scala/scala3/pull/22910) +- Fix setup of class constructors [#22980](https://github.com/scala/scala3/pull/22980) + +## Named Tuples + +- Call dealias after stripping type variables for tupleElementTypesUpTo [#23005](https://github.com/scala/scala3/pull/23005) +- Avoid loosing denotations of named types during `integrate` [#22839](https://github.com/scala/scala3/pull/22839) + +## Experimental: Unroll + +- Fix #22833: allow unroll annotation in methods of final class [#22926](https://github.com/scala/scala3/pull/22926) + +## Experimental: Referencable Package Objects + +- Add experimental.packageObjectValues language setting [#23001](https://github.com/scala/scala3/pull/23001) + +## Exports + +- Respect export alias for default arg forwarder [#21109](https://github.com/scala/scala3/pull/21109) + +## Extension Methods + +- Extension check checks for no parens not empty parens [#22825](https://github.com/scala/scala3/pull/22825) + +## GADTs + +- Fix: Prevent GADT reasoning in pattern alternatives [#22853](https://github.com/scala/scala3/pull/22853) + +## Linting + +- Dealias when looking into imports [#22889](https://github.com/scala/scala3/pull/22889) +- Process Export for unused check [#22984](https://github.com/scala/scala3/pull/22984) +- Drill into QuotePattern bindings symbol info [#22987](https://github.com/scala/scala3/pull/22987) +- No warn implicit param of overriding method [#22901](https://github.com/scala/scala3/pull/22901) +- No warn for evidence params of marker traits such as NotGiven [#22985](https://github.com/scala/scala3/pull/22985) + +## Initialization + +- Check for tasty error in template trees. [#22867](https://github.com/scala/scala3/pull/22867) + +## Metaprogramming: Compile-time + +- Fix issue with certain synthetics missing in compiletime.typechecks [#22978](https://github.com/scala/scala3/pull/22978) + +## Pattern Matching + +- Fix existing GADT constraints with introduced pattern-bound symbols [#22928](https://github.com/scala/scala3/pull/22928) + +## Pickling + +- Fix fromProduct synthesized code for parameter-dependent case classes [#22961](https://github.com/scala/scala3/pull/22961) + +## Presentation Compiler + +- Completions for requests just before string [#22894](https://github.com/scala/scala3/pull/22894) +- Fix: go to def should lead to all: apply, object and class [#22771](https://github.com/scala/scala3/pull/22771) +- Ignore ending `$` when looking at end marker names [#22798](https://github.com/scala/scala3/pull/22798) +- Feature: Skip auto importing symbols we know are wrong in current context [#22813](https://github.com/scala/scala3/pull/22813) +- Show the Autofill completion case as what would be auto-filled [#22819](https://github.com/scala/scala3/pull/22819) +- Bugfix: Fix issues with annotations not detected [#22878](https://github.com/scala/scala3/pull/22878) +- Improvement: Rework IndexedContext to reuse the previously calculated scopes [#22898](https://github.com/scala/scala3/pull/22898) +- Pc: Properly adjust indentation when inlining blocks [#22915](https://github.com/scala/scala3/pull/22915) +- Improvement: Support using directives in worksheets [#22957](https://github.com/scala/scala3/pull/22957) +- Fix: show hover for synthetics if explicitly used [#22973](https://github.com/scala/scala3/pull/22973) +- Pc: fix: inline value when def indentation equals 2 [#22990](https://github.com/scala/scala3/pull/22990) + +## Rewrites + +- Fix insertion of `using` in applications with trailing lambda syntax [#22937](https://github.com/scala/scala3/pull/22937) +- Test chars safely when highlighting [#22918](https://github.com/scala/scala3/pull/22918) + +## Reporting + +- Print infix operations in infix form [#22854](https://github.com/scala/scala3/pull/22854) + +## Scaladoc + +- Chore: add support for 'abstract override' modifier [#22802](https://github.com/scala/scala3/pull/22802) +- Scaladoc: fix generation of unique header ids [#22779](https://github.com/scala/scala3/pull/22779) + +## Typer + +- Disallow context bounds in type lambdas [#22659](https://github.com/scala/scala3/pull/22659) +- Refuse trailing type parameters in extractors [#22699](https://github.com/scala/scala3/pull/22699) +- Fix #22724: Revert the PolyType case in #21744 [#22820](https://github.com/scala/scala3/pull/22820) +- Fix isGenericArrayElement for higher-kinded types [#22938](https://github.com/scala/scala3/pull/22938) +- Tighten condition to preserve denotation in IntegrateMap [#23060](https://github.com/scala/scala3/pull/23060) + +## Transform + +- Mix in the `productPrefix` hash statically in case class `hashCode` [#22865](https://github.com/scala/scala3/pull/22865) + +## Value Classes + +- Fix #21918: Disallow value classes extending type aliases of AnyVal [#23021](https://github.com/scala/scala3/pull/23021) + + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.0..3.7.1-RC1` these are: + +``` + 135 Martin Odersky + 27 Som Snytt + 13 Matt Bovel + 10 Wojciech Mazur + 9 Hamza Remmal + 5 Quentin Bernet + 5 Tomasz Godzik + 4 aherlihy + 3 HarrisL2 + 3 Jan Chyb + 3 Natsu Kagami + 3 Ondrej Lhotak + 3 Sébastien Doeraene + 2 Piotr Chabelski + 2 Yichen Xu + 2 Yoonjae Jeon + 2 kasiaMarek + 1 Aleksey Troitskiy + 1 Daisy Li + 1 Dale Wijnand + 1 Jan-Pieter van den Heuvel + 1 Jędrzej Rochala + 1 Kacper Korban + 1 Katarzyna Marek + 1 Lukas Rytz + 1 Mikołaj Fornal + 1 Nikita Glushchenko + 1 Oliver Bračevac + 1 Ondřej Lhoták + 1 dependabot[bot] + 1 noti0na1 + 1 philippus +``` diff --git a/changelogs/3.7.1-RC2.md b/changelogs/3.7.1-RC2.md new file mode 100644 index 000000000000..6a9b9d88bb79 --- /dev/null +++ b/changelogs/3.7.1-RC2.md @@ -0,0 +1,25 @@ +# Backported chnages + +- Backport "chore: filter allowed source versions by import and by settings" to 3.7.1 (#23231) +- Backport "Bump Scala CLI to v1.8.0 (was v1.7.1)" to 3.7.1 (#23230) +- Backport "Mention extension in unused param warning" to 3.7.1 (#23229) +- Backport "Revert recent changes to opaque type proxy generation" to 3.7.1 (#23228) +- Backport "Remove premature caching of lookups for unused lint" to 3.7.1 (#23227) + +# Reverted changes + +- Revert "Make overload pruning based on result types less aggressive (#21744)" in 3.7.1-RC2 (#23239) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.1-RC1..3.7.1-RC2` these are: + +``` + 4 Hamza Remmal + 4 Som Snytt + 3 Jan Chyb + 3 Wojciech Mazur + 1 Piotr Chabelski +``` diff --git a/changelogs/3.7.1.md b/changelogs/3.7.1.md new file mode 100644 index 000000000000..4ffb178aea89 --- /dev/null +++ b/changelogs/3.7.1.md @@ -0,0 +1,171 @@ +# Highlights of the release + +- Support for JDK 25 [#23004](https://github.com/scala/scala3/pull/23004) +- Warn if interpolator uses toString [#20578](https://github.com/scala/scala3/pull/20578) +- Warn if match in block is not used for PartialFunction [#23002](https://github.com/scala/scala3/pull/23002) + +# Other changes and fixes + +## Annotations + +- Approximate annotated types in `wildApprox` [#22893](https://github.com/scala/scala3/pull/22893) +- Keep unused annot on params [#23037](https://github.com/scala/scala3/pull/23037) + +## Erasure + +- Disallow context function types as value-class parameters [#23015](https://github.com/scala/scala3/pull/23015) + +## Experimental: Capture Checking + +- Two fixes to handling of abstract types with cap bounds [#22838](https://github.com/scala/scala3/pull/22838) +- Drop idempotent type maps [#22910](https://github.com/scala/scala3/pull/22910) +- Fix setup of class constructors [#22980](https://github.com/scala/scala3/pull/22980) + +## Experimental: Unroll + +- Fix #22833: allow unroll annotation in methods of final class [#22926](https://github.com/scala/scala3/pull/22926) + +## Experimental: Referencable Package Objects + +- Add experimental.packageObjectValues language setting [#23001](https://github.com/scala/scala3/pull/23001) + +## Exports + +- Respect export alias for default arg forwarder [#21109](https://github.com/scala/scala3/pull/21109) + +## Extension Methods + +- Extension check checks for no parens not empty parens [#22825](https://github.com/scala/scala3/pull/22825) + +## GADTs + +- Fix: Prevent GADT reasoning in pattern alternatives [#22853](https://github.com/scala/scala3/pull/22853) + +## Linting + +- Dealias when looking into imports [#22889](https://github.com/scala/scala3/pull/22889) +- Process Export for unused check [#22984](https://github.com/scala/scala3/pull/22984) +- Drill into QuotePattern bindings symbol info [#22987](https://github.com/scala/scala3/pull/22987) +- No warn implicit param of overriding method [#22901](https://github.com/scala/scala3/pull/22901) +- No warn for evidence params of marker traits such as NotGiven [#22985](https://github.com/scala/scala3/pull/22985) +- Mention extension in unused param warning [#23132](https://github.com/scala/scala3/pull/23132) +- Remove premature caching of lookups for unused lint [#22982](https://github.com/scala/scala3/pull/22982) +- Enclosing package p.q not visible as q [#23069](https://github.com/scala/scala3/pull/23069) + +## Inline + +- Revert recent changes to opaque type proxy generation [#23059](https://github.com/scala/scala3/pull/23059) + +## Initialization + +- Check for tasty error in template trees. [#22867](https://github.com/scala/scala3/pull/22867) + +## Metaprogramming: Compile-time + +- Fix issue with certain synthetics missing in compiletime.typechecks [#22978](https://github.com/scala/scala3/pull/22978) + +## Named Tuples + +- Call dealias after stripping type variables for tupleElementTypesUpTo [#23005](https://github.com/scala/scala3/pull/23005) +- Avoid loosing denotations of named types during `integrate` [#22839](https://github.com/scala/scala3/pull/22839) + +## Pattern Matching + +- Fix existing GADT constraints with introduced pattern-bound symbols [#22928](https://github.com/scala/scala3/pull/22928) + +## Pickling + +- Fix fromProduct synthesized code for parameter-dependent case classes [#22961](https://github.com/scala/scala3/pull/22961) + +## Presentation Compiler + +- Completions for requests just before string [#22894](https://github.com/scala/scala3/pull/22894) +- Fix: go to def should lead to all: apply, object and class [#22771](https://github.com/scala/scala3/pull/22771) +- Ignore ending `$` when looking at end marker names [#22798](https://github.com/scala/scala3/pull/22798) +- Feature: Skip auto importing symbols we know are wrong in current context [#22813](https://github.com/scala/scala3/pull/22813) +- Show the Autofill completion case as what would be auto-filled [#22819](https://github.com/scala/scala3/pull/22819) +- Bugfix: Fix issues with annotations not detected [#22878](https://github.com/scala/scala3/pull/22878) +- Improvement: Rework IndexedContext to reuse the previously calculated scopes [#22898](https://github.com/scala/scala3/pull/22898) +- Pc: Properly adjust indentation when inlining blocks [#22915](https://github.com/scala/scala3/pull/22915) +- Improvement: Support using directives in worksheets [#22957](https://github.com/scala/scala3/pull/22957) +- Fix: show hover for synthetics if explicitly used [#22973](https://github.com/scala/scala3/pull/22973) +- Pc: fix: inline value when def indentation equals 2 [#22990](https://github.com/scala/scala3/pull/22990) + +## Rewrites + +- Fix insertion of `using` in applications with trailing lambda syntax [#22937](https://github.com/scala/scala3/pull/22937) +- Test chars safely when highlighting [#22918](https://github.com/scala/scala3/pull/22918) + +## Reporting + +- Print infix operations in infix form [#22854](https://github.com/scala/scala3/pull/22854) + +## Runner + +- Bump Scala CLI to v1.8.0 (was v1.7.1) [#23168](https://github.com/scala/scala3/pull/23168) + +## Scaladoc + +- Chore: add support for 'abstract override' modifier [#22802](https://github.com/scala/scala3/pull/22802) +- Scaladoc: fix generation of unique header ids [#22779](https://github.com/scala/scala3/pull/22779) + +## Settings + +- Filter allowed source versions by import and by settings [#23215](https://github.com/scala/scala3/pull/23215) + +## Typer + +- Disallow context bounds in type lambdas [#22659](https://github.com/scala/scala3/pull/22659) +- Refuse trailing type parameters in extractors [#22699](https://github.com/scala/scala3/pull/22699) +- Fix #22724: Revert the PolyType case in #21744 [#22820](https://github.com/scala/scala3/pull/22820) +- Fix isGenericArrayElement for higher-kinded types [#22938](https://github.com/scala/scala3/pull/22938) +- Tighten condition to preserve denotation in IntegrateMap [#23060](https://github.com/scala/scala3/pull/23060) + +## Transform + +- Mix in the `productPrefix` hash statically in case class `hashCode` [#22865](https://github.com/scala/scala3/pull/22865) + +## Value Classes + +- Fix #21918: Disallow value classes extending type aliases of AnyVal [#23021](https://github.com/scala/scala3/pull/23021) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.0..3.7.1` these are: + +``` + 135 Martin Odersky + 31 Som Snytt + 14 Wojciech Mazur + 13 Hamza Remmal + 13 Matt Bovel + 6 Jan Chyb + 5 Quentin Bernet + 5 Tomasz Godzik + 4 aherlihy + 3 HarrisL2 + 3 Natsu Kagami + 3 Ondrej Lhotak + 3 Piotr Chabelski + 3 Sébastien Doeraene + 2 Yichen Xu + 2 Yoonjae Jeon + 2 kasiaMarek + 1 Aleksey Troitskiy + 1 Daisy Li + 1 Dale Wijnand + 1 Jan-Pieter van den Heuvel + 1 Jędrzej Rochala + 1 Kacper Korban + 1 Katarzyna Marek + 1 Lukas Rytz + 1 Mikołaj Fornal + 1 Nikita Glushchenko + 1 Oliver Bračevac + 1 Ondřej Lhoták + 1 dependabot[bot] + 1 noti0na1 + 1 philippus +``` diff --git a/changelogs/3.7.3-RC1.md b/changelogs/3.7.3-RC1.md new file mode 100644 index 000000000000..25a2526d8e50 --- /dev/null +++ b/changelogs/3.7.3-RC1.md @@ -0,0 +1,224 @@ +# Release highlights + +- Standardize on `-Vprint:...` (still support `-Xprint:...` as alias) [#22828](https://github.com/scala/scala3/pull/22828) + +# Other changes and fixes + +## Desugaring + +- Fix #23224: Optimize simple tuple extraction [#23373](https://github.com/scala/scala3/pull/23373) + +## Enums + +- Make hashcode of enum items stable [#23218](https://github.com/scala/scala3/pull/23218) + +## Erasure + +- Replace erased class modifiers with Erased base traits [#23447](https://github.com/scala/scala3/pull/23447) +- Bring back part of PruneErasedDefs [#23466](https://github.com/scala/scala3/pull/23466) + +## Experimental: Capture Checking + +- Fix parsing crash for update in later phases [#23390](https://github.com/scala/scala3/pull/23390) +- Implement boxing for singleton type arguments [#23418](https://github.com/scala/scala3/pull/23418) +- Expand Capability types also in arguments of Capability classes [#23427](https://github.com/scala/scala3/pull/23427) +- Adjustments to the capability trilogy [#23428](https://github.com/scala/scala3/pull/23428) +- Set context owner to the method for `paramsToCap` [#23436](https://github.com/scala/scala3/pull/23436) +- Flatten nested capture sets in retainedElementsRaw [#23571](https://github.com/scala/scala3/pull/23571) +- Fix well-formed test for capabilities [#23393](https://github.com/scala/scala3/pull/23393) +- Add restricted capabilities `x.only[C]` [#23485](https://github.com/scala/scala3/pull/23485) +- Rely on hidden sets for use checking [#23580](https://github.com/scala/scala3/pull/23580) + +## Experimental: Seperation Checking + +- Make separation checking controlled by language import [#23560](https://github.com/scala/scala3/pull/23560) + +## Experimental: Erased Definitions + +- Refactorings and fixes to erased definition handling [#23404](https://github.com/scala/scala3/pull/23404) + +## Experimental: Explicit Nulls + +- Add quick fix to remove unnecessary .nn [#23461](https://github.com/scala/scala3/pull/23461) +- Add `stableNull` annotation to force tracking mutable fields [#23528](https://github.com/scala/scala3/pull/23528) + +## Experimental: Global Initialization + +- Rewrite resolveThis in global init checker [#23282](https://github.com/scala/scala3/pull/23282) +- Fix errors in the global initialization checker when compiling bootstrapped dotty [#23429](https://github.com/scala/scala3/pull/23429) +- Fix error in product-sequence match in global init checker [#23480](https://github.com/scala/scala3/pull/23480) + +## Experimental: Into + +- Fix isConversionTargetType test [#23401](https://github.com/scala/scala3/pull/23401) + +## Experimental: Modularity + +- Refinements to skolemizaton [#23513](https://github.com/scala/scala3/pull/23513) + +## Experimental: Unroll + +- Enable UnrollDefinitions phase in REPL frontend phases [#23433](https://github.com/scala/scala3/pull/23433) + +## Extension Methods + +- Avoid forcing extension on check of local select [#23439](https://github.com/scala/scala3/pull/23439) + +## Implicits + +- Refine implicit search fallbacks for better ClassTag handling [#23532](https://github.com/scala/scala3/pull/23532) + +## Inline + +- Fix Symbol.info remapping in TreeTypeMap [#23432](https://github.com/scala/scala3/pull/23432) +- Fail not inlined inline method calls early [#22925](https://github.com/scala/scala3/pull/22925) +- Fix inline export forwarder generation regression [#23126](https://github.com/scala/scala3/pull/23126) + +## Linting + +- Consider setter of effectively private var [#23211](https://github.com/scala/scala3/pull/23211) +- Add accessible check for import usage [#23348](https://github.com/scala/scala3/pull/23348) +- Check OrType in interpolated toString lint [#23365](https://github.com/scala/scala3/pull/23365) +- Use result of lambda type of implicit in CheckUnused [#23497](https://github.com/scala/scala3/pull/23497) + +## Match Types + +- Fix: #23261 Distinguish 0.0 and -0.0 in ConstantType match types [#23265](https://github.com/scala/scala3/pull/23265) + +## Named Tuples + +- Skip bypassing unapply for scala 2 case classes to allow for single-element named tuple in unapply [#23603](https://github.com/scala/scala3/pull/23603) + +## Parser + +- Enforce `-new-syntax` under `-language:future` [#23443](https://github.com/scala/scala3/pull/23443) +- Disallow Scala 2 implicits under `-source:future` [#23472](https://github.com/scala/scala3/pull/23472) + +## Pattern Matching + +- Fix problems in checking that a constructor is uninhabited for exhaustive match checking [#23403](https://github.com/scala/scala3/pull/23403) + +## Pickling + +- Don't force annotation unpickling when testing for SilentIntoAnnot [#23506](https://github.com/scala/scala3/pull/23506) +- Drop invalid assumption from TastyUnpickler [#23353](https://github.com/scala/scala3/pull/23353) + +## Printer + +- Print update modifier when printing update method definitions [#23392](https://github.com/scala/scala3/pull/23392) + +## Positions + +- Compare span points in pathTo to determine best span [#23581](https://github.com/scala/scala3/pull/23581) +- Add line number magic comment support [#23549](https://github.com/scala/scala3/pull/23549) + +## Presentation Compiler + +- Port Inlay hints for name parameters [#23375](https://github.com/scala/scala3/pull/23375) +- Fix: Simplify infer type for apply [#23434](https://github.com/scala/scala3/pull/23434) +- Fix: Inconsistent annotation tooltips [#23454](https://github.com/scala/scala3/pull/23454) +- Fix adjust type when already exists [#23455](https://github.com/scala/scala3/pull/23455) +- Exclude named parameters inlay hints for java defined [#23462](https://github.com/scala/scala3/pull/23462) +- Fix: StringIndexOutOfBoundsException in presentation compiler's hasColon method [#23498](https://github.com/scala/scala3/pull/23498) +- Add InferredMethodProvider for automatic method signature generation [#23563](https://github.com/scala/scala3/pull/23563) +- Fix completions for Quotes [#23619](https://github.com/scala/scala3/pull/23619) +- Handle default arguments in named parameters for inlay hints [#23641](https://github.com/scala/scala3/pull/23641) + +## Quotes + +- Skip splice level checking for `` symbols [#22782](https://github.com/scala/scala3/pull/22782) +- Fix stale top level synthetic package object being used in later runs [#23464](https://github.com/scala/scala3/pull/23464) +- Emit an error for quoted pattern type variable after `new` [#23618](https://github.com/scala/scala3/pull/23618) +- Fix issue with certain polyfunctions not properly matching in macros [#23614](https://github.com/scala/scala3/pull/23614) +- Check PCP of constructor calls on the type [#7531](https://github.com/scala/scala3/pull/7531) + +## Reflection + +- Quotes reflect: sort the typeMembers output list and filter out non-members [#22876](https://github.com/scala/scala3/pull/22876) + +## Reporting + +- Add an explainer to the DoubleDefinition error [#23470](https://github.com/scala/scala3/pull/23470) +- Suppress warnings in comprehensions with 22+ binds [#23590](https://github.com/scala/scala3/pull/23590) +- Unhelpful error message when trying to use named extraction, when not matching case class or named tuple [#23354](https://github.com/scala/scala3/pull/23354) +- Improve error message for conflicting definitions [#23453](https://github.com/scala/scala3/pull/23453) +- `-Yprofile-trace` properly report macro splicing source [#23488](https://github.com/scala/scala3/pull/23488) +- `-Yprofile-trace` profiles all inline calls [#23490](https://github.com/scala/scala3/pull/23490) + +## Rewrites + +- Patch empty implicit parens on error recovery [#22835](https://github.com/scala/scala3/pull/22835) +- Rewrite underscore with optional space [#23525](https://github.com/scala/scala3/pull/23525) + +## Scaladoc + +- Scaladoc: fixes and improvements to context bounds and extension methods [#22156](https://github.com/scala/scala3/pull/22156) +- Encode path of class [#23503](https://github.com/scala/scala3/pull/23503) + +## SemanticDB + +- Bugfix: Also save infos in semanticdb [#23587](https://github.com/scala/scala3/pull/23587) + +## Transform + +- Handle multiple type parameter lists in value class methods [#23516](https://github.com/scala/scala3/pull/23516) +- Check path of module prefix for tailrec [#23491](https://github.com/scala/scala3/pull/23491) + +## Tuples + +- Normalize tuple types in var args seq literals and classOf instances [#23465](https://github.com/scala/scala3/pull/23465) + +## Typer + +- Fix #22922: Add TypeParamRef handling in isSingletonBounded [#23501](https://github.com/scala/scala3/pull/23501) +- Fix this references everywhere in dependent function types [#23514](https://github.com/scala/scala3/pull/23514) +- Don't approximate a type using `Nothing` as prefix [#23531](https://github.com/scala/scala3/pull/23531) +- Support cleanup actions in class completers [#23515](https://github.com/scala/scala3/pull/23515) +- Fix regressions in asSeenFrom introduced in 3.7 [#23438](https://github.com/scala/scala3/pull/23438) +- Use correct owner in eta expansion [#7564](https://github.com/scala/scala3/pull/7564) +- Fix irrefutability checking in `for` with untupling [#23273](https://github.com/scala/scala3/pull/23273) +- Fix missing members reporting for var setters [#23476](https://github.com/scala/scala3/pull/23476) +- Guard against invalid prefixes in argForParam [#23508](https://github.com/scala/scala3/pull/23508) +- Add missing case to TypeComparer [#23550](https://github.com/scala/scala3/pull/23550) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.2..3.7.3-RC1` these are: + +``` + 80 Martin Odersky + 56 Hamza Remmal + 22 Wojciech Mazur + 20 noti0na1 + 18 Yichen Xu + 16 Som Snytt + 14 Jan Chyb + 9 Matt Bovel + 7 EnzeXing + 6 Guillaume Martres + 5 Sébastien Doeraene + 5 aherlihy + 4 Zieliński Patryk + 3 Oliver Bračevac + 3 Tomasz Godzik + 2 Alexander + 2 Mikołaj Fornal + 2 Piotr Chabelski + 2 Seyon Sivatharan + 1 Alex1005a + 1 HarrisL2 + 1 Jan + 1 Jentsch + 1 Jędrzej Rochala + 1 Katarzyna Marek + 1 Marc GRIS + 1 Martin Duhem + 1 Patryk Zieliński + 1 Przemysław Sajnóg + 1 Seth Tisue + 1 Wessel W. Bakker + 1 bingchen-li + 1 kijuky +``` diff --git a/changelogs/3.7.3-RC2.md b/changelogs/3.7.3-RC2.md new file mode 100644 index 000000000000..243d06b5348d --- /dev/null +++ b/changelogs/3.7.3-RC2.md @@ -0,0 +1,29 @@ +# Backported chnages + +- Warn if implicit default shadows given [#23559](https://github.com/scala/scala3/pull/23559) +- Bump Scala CLI to v1.8.5 (was v1.8.4) [#23702](https://github.com/scala/scala3/pull/23702) +- Fix issue with pc breaking in requiredMethod on newly overloaded valueOf [#23708](https://github.com/scala/scala3/pull/23708) +- Handle default arguments in named parameters for inlay hints [#23641](https://github.com/scala/scala3/pull/23641) +- Add suppression if nowarn differs [#23652](https://github.com/scala/scala3/pull/23652) +- Fix match type bounds checking problem [#23695](https://github.com/scala/scala3/pull/23695) +- Generalize "Don't approximate a type using Nothing as prefix" [#23628](https://github.com/scala/scala3/pull/23628) +- More careful ClassTag instantiation [#23659](https://github.com/scala/scala3/pull/23659) +- Use more context for implicit search only if no default argument [#23664](https://github.com/scala/scala3/pull/23664) +- Fix extracting refinements from intersection types in dynamic select hovers [#23640](https://github.com/scala/scala3/pull/23640) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.3-RC1..3.7.3-RC2` these are: + +``` + 5 Martin Odersky + 4 Som Snytt + 2 Wojciech Mazur + 1 Guillaume Martres + 1 Jan Chyb + 1 Kacper Korban + 1 Piotr Chabelski + 1 aherlihy +``` diff --git a/changelogs/3.7.3-RC3.md b/changelogs/3.7.3-RC3.md new file mode 100644 index 000000000000..59e385fa26a8 --- /dev/null +++ b/changelogs/3.7.3-RC3.md @@ -0,0 +1,14 @@ +# Backported chnages + +- Update scala-cli to 1.9.0 (was 1.8.5) [#23861](https://github.com/scala/scala3/pull/23861) + + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.3-RC2..3.7.3-RC3` these are: + +``` + 3 Wojciech Mazur +``` diff --git a/changelogs/3.7.3.md b/changelogs/3.7.3.md new file mode 100644 index 000000000000..c8c31a4d5743 --- /dev/null +++ b/changelogs/3.7.3.md @@ -0,0 +1,238 @@ +# Release highlights + +- Warn if implicit default shadows given [#23559](https://github.com/scala/scala3/pull/23559) +- Standardize on `-Vprint:...` (still support `-Xprint:...` as alias) [#22828](https://github.com/scala/scala3/pull/22828) + +# Other changes and fixes + +## Desugaring + +- Optimize simple tuple extraction [#23373](https://github.com/scala/scala3/pull/23373) + +## Enums + +- Make hashcode of enum items stable [#23218](https://github.com/scala/scala3/pull/23218) + +## Erasure + +- Replace erased class modifiers with Erased base traits [#23447](https://github.com/scala/scala3/pull/23447) +- Bring back part of PruneErasedDefs [#23466](https://github.com/scala/scala3/pull/23466) + +## Experimental: Capture Checking + +- Fix parsing crash for update in later phases [#23390](https://github.com/scala/scala3/pull/23390) +- Implement boxing for singleton type arguments [#23418](https://github.com/scala/scala3/pull/23418) +- Expand Capability types also in arguments of Capability classes [#23427](https://github.com/scala/scala3/pull/23427) +- Adjustments to the capability trilogy [#23428](https://github.com/scala/scala3/pull/23428) +- Set context owner to the method for `paramsToCap` [#23436](https://github.com/scala/scala3/pull/23436) +- Flatten nested capture sets in retainedElementsRaw [#23571](https://github.com/scala/scala3/pull/23571) +- Fix well-formed test for capabilities [#23393](https://github.com/scala/scala3/pull/23393) +- Add restricted capabilities `x.only[C]` [#23485](https://github.com/scala/scala3/pull/23485) +- Rely on hidden sets for use checking [#23580](https://github.com/scala/scala3/pull/23580) + +## Experimental: Seperation Checking + +- Make separation checking controlled by language import [#23560](https://github.com/scala/scala3/pull/23560) + +## Experimental: Erased Definitions + +- Refactorings and fixes to erased definition handling [#23404](https://github.com/scala/scala3/pull/23404) + +## Experimental: Explicit Nulls + +- Add quick fix to remove unnecessary .nn [#23461](https://github.com/scala/scala3/pull/23461) +- Add `stableNull` annotation to force tracking mutable fields [#23528](https://github.com/scala/scala3/pull/23528) + +## Experimental: Global Initialization + +- Rewrite resolveThis in global init checker [#23282](https://github.com/scala/scala3/pull/23282) +- Fix errors in the global initialization checker when compiling bootstrapped dotty [#23429](https://github.com/scala/scala3/pull/23429) +- Fix error in product-sequence match in global init checker [#23480](https://github.com/scala/scala3/pull/23480) + +## Experimental: Into + +- Fix isConversionTargetType test [#23401](https://github.com/scala/scala3/pull/23401) + +## Experimental: Modularity + +- Refinements to skolemizaton [#23513](https://github.com/scala/scala3/pull/23513) + +## Experimental: Unroll + +- Enable UnrollDefinitions phase in REPL frontend phases [#23433](https://github.com/scala/scala3/pull/23433) + +## Extension Methods + +- Avoid forcing extension on check of local select [#23439](https://github.com/scala/scala3/pull/23439) + +## Implicits + +- Refine implicit search fallbacks for better ClassTag handling [#23532](https://github.com/scala/scala3/pull/23532) + +## Inline + +- Fix Symbol.info remapping in TreeTypeMap [#23432](https://github.com/scala/scala3/pull/23432) +- Fail not inlined inline method calls early [#22925](https://github.com/scala/scala3/pull/22925) +- Fix inline export forwarder generation regression [#23126](https://github.com/scala/scala3/pull/23126) + +## Linting + +- Consider setter of effectively private var [#23211](https://github.com/scala/scala3/pull/23211) +- Add accessible check for import usage [#23348](https://github.com/scala/scala3/pull/23348) +- Check OrType in interpolated toString lint [#23365](https://github.com/scala/scala3/pull/23365) +- Use result of lambda type of implicit in CheckUnused [#23497](https://github.com/scala/scala3/pull/23497) +- Add suppression if nowarn differs [#23652](https://github.com/scala/scala3/pull/23652) + +## Match Types + +- Fix: #23261 Distinguish 0.0 and -0.0 in ConstantType match types [#23265](https://github.com/scala/scala3/pull/23265) + +## Named Tuples + +- Skip bypassing unapply for scala 2 case classes to allow for single-element named tuple in unapply [#23603](https://github.com/scala/scala3/pull/23603) + +## Parser + +- Enforce `-new-syntax` under `-language:future` [#23443](https://github.com/scala/scala3/pull/23443) +- Disallow Scala 2 implicits under `-source:future` [#23472](https://github.com/scala/scala3/pull/23472) + +## Pattern Matching + +- Fix problems in checking that a constructor is uninhabited for exhaustive match checking [#23403](https://github.com/scala/scala3/pull/23403) + +## Pickling + +- Don't force annotation unpickling when testing for SilentIntoAnnot [#23506](https://github.com/scala/scala3/pull/23506) +- Drop invalid assumption from TastyUnpickler [#23353](https://github.com/scala/scala3/pull/23353) + +## Printer + +- Print update modifier when printing update method definitions [#23392](https://github.com/scala/scala3/pull/23392) + +## Positions + +- Compare span points in pathTo to determine best span [#23581](https://github.com/scala/scala3/pull/23581) +- Add line number magic comment support [#23549](https://github.com/scala/scala3/pull/23549) + +## Presentation Compiler + +- Port Inlay hints for name parameters [#23375](https://github.com/scala/scala3/pull/23375) +- Fix: Simplify infer type for apply [#23434](https://github.com/scala/scala3/pull/23434) +- Fix: Inconsistent annotation tooltips [#23454](https://github.com/scala/scala3/pull/23454) +- Fix adjust type when already exists [#23455](https://github.com/scala/scala3/pull/23455) +- Exclude named parameters inlay hints for java defined [#23462](https://github.com/scala/scala3/pull/23462) +- Fix: StringIndexOutOfBoundsException in presentation compiler's hasColon method [#23498](https://github.com/scala/scala3/pull/23498) +- Add InferredMethodProvider for automatic method signature generation [#23563](https://github.com/scala/scala3/pull/23563) +- Fix completions for Quotes [#23619](https://github.com/scala/scala3/pull/23619) +- Handle default arguments in named parameters for inlay hints [#23641](https://github.com/scala/scala3/pull/23641) +- Fix issue with pc breaking in requiredMethod on newly overloaded valueOf [#23708](https://github.com/scala/scala3/pull/23708) +- Handle default arguments in named parameters for inlay hints [#23641](https://github.com/scala/scala3/pull/23641) +- Fix extracting refinements from intersection types in dynamic select hovers [#23640](https://github.com/scala/scala3/pull/23640) + +## Quotes + +- Skip splice level checking for `` symbols [#22782](https://github.com/scala/scala3/pull/22782) +- Fix stale top level synthetic package object being used in later runs [#23464](https://github.com/scala/scala3/pull/23464) +- Emit an error for quoted pattern type variable after `new` [#23618](https://github.com/scala/scala3/pull/23618) +- Fix issue with certain polyfunctions not properly matching in macros [#23614](https://github.com/scala/scala3/pull/23614) +- Check PCP of constructor calls on the type [#7531](https://github.com/scala/scala3/pull/7531) + +## Reflection + +- Quotes reflect: sort the typeMembers output list and filter out non-members [#22876](https://github.com/scala/scala3/pull/22876) + +## Reporting + +- Add an explainer to the DoubleDefinition error [#23470](https://github.com/scala/scala3/pull/23470) +- Suppress warnings in comprehensions with 22+ binds [#23590](https://github.com/scala/scala3/pull/23590) +- Unhelpful error message when trying to use named extraction, when not matching case class or named tuple [#23354](https://github.com/scala/scala3/pull/23354) +- Improve error message for conflicting definitions [#23453](https://github.com/scala/scala3/pull/23453) +- `-Yprofile-trace` properly report macro splicing source [#23488](https://github.com/scala/scala3/pull/23488) +- `-Yprofile-trace` profiles all inline calls [#23490](https://github.com/scala/scala3/pull/23490) + +## Rewrites + +- Patch empty implicit parens on error recovery [#22835](https://github.com/scala/scala3/pull/22835) +- Rewrite underscore with optional space [#23525](https://github.com/scala/scala3/pull/23525) + +## Runner + +- Bump Scala CLI to v1.9.0 (was v1.8.4) [#23702](https://github.com/scala/scala3/pull/23856) + +## Scaladoc + +- Scaladoc: fixes and improvements to context bounds and extension methods [#22156](https://github.com/scala/scala3/pull/22156) +- Encode path of class [#23503](https://github.com/scala/scala3/pull/23503) + +## SemanticDB + +- Bugfix: Also save infos in semanticdb [#23587](https://github.com/scala/scala3/pull/23587) + +## Transform + +- Handle multiple type parameter lists in value class methods [#23516](https://github.com/scala/scala3/pull/23516) +- Check path of module prefix for tailrec [#23491](https://github.com/scala/scala3/pull/23491) + +## Tuples + +- Normalize tuple types in var args seq literals and classOf instances [#23465](https://github.com/scala/scala3/pull/23465) + +## Typer + +- Fix #22922: Add TypeParamRef handling in isSingletonBounded [#23501](https://github.com/scala/scala3/pull/23501) +- Fix this references everywhere in dependent function types [#23514](https://github.com/scala/scala3/pull/23514) +- Don't approximate a type using `Nothing` as prefix [#23531](https://github.com/scala/scala3/pull/23531) +- Support cleanup actions in class completers [#23515](https://github.com/scala/scala3/pull/23515) +- Fix regressions in asSeenFrom introduced in 3.7 [#23438](https://github.com/scala/scala3/pull/23438) +- Use correct owner in eta expansion [#7564](https://github.com/scala/scala3/pull/7564) +- Fix irrefutability checking in `for` with untupling [#23273](https://github.com/scala/scala3/pull/23273) +- Fix missing members reporting for var setters [#23476](https://github.com/scala/scala3/pull/23476) +- Guard against invalid prefixes in argForParam [#23508](https://github.com/scala/scala3/pull/23508) +- Add missing case to TypeComparer [#23550](https://github.com/scala/scala3/pull/23550) +- Fix match type bounds checking problem [#23695](https://github.com/scala/scala3/pull/23695) +- Generalize "Don't approximate a type using Nothing as prefix" [#23628](https://github.com/scala/scala3/pull/23628) +- More careful ClassTag instantiation [#23659](https://github.com/scala/scala3/pull/23659) +- Use more context for implicit search only if no default argument [#23664](https://github.com/scala/scala3/pull/23664) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.2..3.7.3` these are: + +``` + 85 Martin Odersky + 56 Hamza Remmal + 29 Wojciech Mazur + 20 Som Snytt + 20 noti0na1 + 18 Yichen Xu + 15 Jan Chyb + 9 Matt Bovel + 7 EnzeXing + 7 Guillaume Martres + 6 aherlihy + 5 Sébastien Doeraene + 4 Zieliński Patryk + 3 Oliver Bračevac + 3 Piotr Chabelski + 3 Tomasz Godzik + 2 Alexander + 2 Mikołaj Fornal + 2 Seyon Sivatharan + 1 Alex1005a + 1 HarrisL2 + 1 Jan + 1 Jentsch + 1 Jędrzej Rochala + 1 Kacper Korban + 1 Katarzyna Marek + 1 Marc GRIS + 1 Martin Duhem + 1 Patryk Zieliński + 1 Przemysław Sajnóg + 1 Seth Tisue + 1 Wessel W. Bakker + 1 bingchen-li + 1 kijuky +``` diff --git a/changelogs/3.7.4-RC1.md b/changelogs/3.7.4-RC1.md new file mode 100644 index 000000000000..518addfa3400 --- /dev/null +++ b/changelogs/3.7.4-RC1.md @@ -0,0 +1,128 @@ +# Highlights of the release + +- Bump Scala CLI to v1.9.1 (was v1.9.0) [#23962](https://github.com/scala/scala3/pull/23962) +- Make coverage more similar to the one in Scala 2 [#23722](https://github.com/scala/scala3/pull/23722) + +# Other changes and fixes + +## Context Functions + +- Explain no expansion of ContextFunction0 [#23844](https://github.com/scala/scala3/pull/23844) + +## Experimental: Capture Checking + +- Fix #23737: Update superCallContext to include dummy capture parameters in scope [#23740](https://github.com/scala/scala3/pull/23740) +- Fix separation checking for function results [#23927](https://github.com/scala/scala3/pull/23927) +- Simple enhancement for pattern matching with capturing types [#23524](https://github.com/scala/scala3/pull/23524) +- Don't check bounds in match type cases at CC [#23738](https://github.com/scala/scala3/pull/23738) + +## Experimental: Explicit Nulls + +- Add warnings for inferred flexible types in public methods and fields [#23880](https://github.com/scala/scala3/pull/23880) + +## Exports + +- Refine isEffectivelyFinal to avoid no-owner crash [#23675](https://github.com/scala/scala3/pull/23675) + +## Implicits + +- Fix LiftToAnchors for higher-kinded type applications [#23672](https://github.com/scala/scala3/pull/23672) +- Fix implicit scope liftToAnchors for parameter lower bounds [#23679](https://github.com/scala/scala3/pull/23679) + +## Linting + +- Invent given pattern name in for comprehension [#23121](https://github.com/scala/scala3/pull/23121) +- Unused var message mentions unread or unset [#23719](https://github.com/scala/scala3/pull/23719) +- Lint function arrow intended context function [#23847](https://github.com/scala/scala3/pull/23847) + +## Match Types + +- Fix `derivesFrom` false negative in `provablyDisjointClasses` [#23834](https://github.com/scala/scala3/pull/23834) + + +## Parser + +- Improve message for nested package missing braces [#23816](https://github.com/scala/scala3/pull/23816) +- Fix: allow postfix setters under language.postfixOps [#23775](https://github.com/scala/scala3/pull/23775) + +## Pattern Matching + +- Fix: do not transform `Ident` to `This` in PostTyper anymore [#23899](https://github.com/scala/scala3/pull/23899) +- Call inhabited for AppliedType recursively [#23964](https://github.com/scala/scala3/pull/23964) +- Fix false unreachable case warning [#23800](https://github.com/scala/scala3/pull/23800) +- Add subtype-based fallback in inferPrefixMap and recalculate constraints after application [#23771](https://github.com/scala/scala3/pull/23771) + +## Presentation Compiler + +- Additional completions for using clause [#23647](https://github.com/scala/scala3/pull/23647) +- Completions - do not add `[]` for `... derives TC@@` [#23811](https://github.com/scala/scala3/pull/23811) +- Improve symbol order in completions provided by the presentation compiler [#23888](https://github.com/scala/scala3/pull/23888) +- Porting XRayModeHints [#23891](https://github.com/scala/scala3/pull/23891) +- Go to definition and hover for named args in pattern match [#23956](https://github.com/scala/scala3/pull/23956) + +## Reporting + +- Do not discard amended format when f-interpolator warns [#23697](https://github.com/scala/scala3/pull/23697) +- Mention named givens in double def explainer [#23833](https://github.com/scala/scala3/pull/23833) +- Compute the right span for abstract error messages [#23853](https://github.com/scala/scala3/pull/23853) +- Add quick fix to add .nn [#23598](https://github.com/scala/scala3/pull/23598) +- Add addendum to `private val` parameter variance error message [#23876](https://github.com/scala/scala3/pull/23876) + +## Scaladoc + +- Indicate optional parameters with `= ...` [#23676](https://github.com/scala/scala3/pull/23676) +- Scaladoc Support for Capture & Separation Checking [#23607](https://github.com/scala/scala3/pull/23607) +- Capture Calcuclus: don't eagerly drop caps on parameters [#23759](https://github.com/scala/scala3/pull/23759) + +## SemanticDB + +- Add context parameters to SemanticDB synthetics [#23381](https://github.com/scala/scala3/pull/23381) +- Include synthetic apply in semanticdb [#23629](https://github.com/scala/scala3/pull/23629) + +## Tuples + +- Fix: make vals created in desugaring of n-ary lambdas non-synthetic [#23896](https://github.com/scala/scala3/pull/23896) + +## Typer + +- Prevent crash in SAM conversion with mismatched arity [#23877](https://github.com/scala/scala3/pull/23877) +- Handle assertion error in TyperState [#23665](https://github.com/scala/scala3/pull/23665) +- Correctly require a `ClassTag` when building a multidimensional `Array` [#23902](https://github.com/scala/scala3/pull/23902) +- Make isExactlyNothing and isExactlyAny work for And/OrTypes [#24016](https://github.com/scala/scala3/pull/24016) + + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.3..3.7.4-RC1` these are: + +``` + 12 Som Snytt + 11 noti0na1 + 11 Wojciech Mazur + 6 Martin Odersky + 5 Eugene Flesselle + 4 Hamza Remmal + 4 Natsu Kagami + 4 Seyon Sivatharan + 3 Oliver Bračevac + 3 Yoonjae Jeon + 3 dependabot[bot] + 2 Jan Chyb + 2 Katarzyna Marek + 2 Matt Bovel + 1 HarrisL2 + 1 Kacper Korban + 1 Martin Duhem + 1 Paweł Perłakowski + 1 Piotr Chabelski + 1 Tomasz Godzik + 1 Vadim Chelyshov + 1 Yichen Xu + 1 Zieliński Patryk + 1 aherlihy + 1 katrinafyi + 1 vder + 1 zielinsky +``` \ No newline at end of file diff --git a/changelogs/3.7.4-RC2.md b/changelogs/3.7.4-RC2.md new file mode 100644 index 000000000000..9627af49dd99 --- /dev/null +++ b/changelogs/3.7.4-RC2.md @@ -0,0 +1,28 @@ +# Backported chnages + +- Always traverse Inlined.call in linter [#24043](https://github.com/scala/scala3/pull/24043) +- Deduplicate patches before applying them to sources [#24215](https://github.com/scala/scala3/pull/24215) +- Fix compiler crash with `-Ymagic-offset-header` [#24124](https://github.com/scala/scala3/pull/24124) +- Fix completions for named tuples [#24169](https://github.com/scala/scala3/pull/24169) +- Fix java record varargs field accessor [#24172](https://github.com/scala/scala3/pull/24172) +- Fix parameter untupling for named tuples (#23440) [#24152](https://github.com/scala/scala3/pull/24152) +- Fix possible SuspendException thrown when using macros [#24174](https://github.com/scala/scala3/pull/24174) +- Fix rendering of function-type aliases [#24042](https://github.com/scala/scala3/pull/24042) +- Ignore warnings when compiletime.testing is imported [#24036](https://github.com/scala/scala3/pull/24036) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.4-RC1..3.7.4-RC2` these are: + +``` + 5 Wojciech Mazur + 2 Li Haoyi + 2 Som Snytt + 2 Tomasz Godzik + 1 Florian3k + 1 Kacper Korban + 1 Oliver Bračevac + 1 aherlihy +``` diff --git a/changelogs/3.7.4-RC3.md b/changelogs/3.7.4-RC3.md new file mode 100644 index 000000000000..c7f0c1654d51 --- /dev/null +++ b/changelogs/3.7.4-RC3.md @@ -0,0 +1,19 @@ +# Backported chnages + +- Lint avoids revisiting Inlined.call [#24277](https://github.com/scala/scala3/pull/24277) +- Register no elements for lint after inlining [#24279](https://github.com/scala/scala3/pull/24279) +- Use enclosing enclosingInlineds for empty call [#24281](https://github.com/scala/scala3/pull/24281) +- Exclude synthetic opaque proxy from lint [#24264](https://github.com/scala/scala3/pull/24264) +- Deprecate `scala_legacy`/`MainGenericRunner`/`scalac -run`/`scalac -repl` for removal [#24267](https://github.com/scala/scala3/pull/24267) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.4-RC2..3.7.4-RC4` these are: + +``` + 4 Wojciech Mazur + 2 Piotr Chabelski + 2 Som Snytt +``` diff --git a/changelogs/3.7.4.md b/changelogs/3.7.4.md new file mode 100644 index 000000000000..1a72af451008 --- /dev/null +++ b/changelogs/3.7.4.md @@ -0,0 +1,149 @@ +# Highlights of the release + +- Bump Scala CLI to v1.9.1 (was v1.9.0) [#23962](https://github.com/scala/scala3/pull/23962) +- Make coverage more similar to the one in Scala 2 [#23722](https://github.com/scala/scala3/pull/23722) + +# Deprecations for removal +- Deprecate `scala_legacy`/`MainGenericRunner`/`scalac -run`/`scalac -repl` for removal [#24267](https://github.com/scala/scala3/pull/24267) + +# Other changes and fixes + +## Context Functions + +- Explain no expansion of ContextFunction0 [#23844](https://github.com/scala/scala3/pull/23844) + +## Experimental: Capture Checking + +- Fix #23737: Update superCallContext to include dummy capture parameters in scope [#23740](https://github.com/scala/scala3/pull/23740) +- Fix separation checking for function results [#23927](https://github.com/scala/scala3/pull/23927) +- Simple enhancement for pattern matching with capturing types [#23524](https://github.com/scala/scala3/pull/23524) +- Don't check bounds in match type cases at CC [#23738](https://github.com/scala/scala3/pull/23738) + +## Experimental: Explicit Nulls + +- Add warnings for inferred flexible types in public methods and fields [#23880](https://github.com/scala/scala3/pull/23880) + +## Exports + +- Refine isEffectivelyFinal to avoid no-owner crash [#23675](https://github.com/scala/scala3/pull/23675) + +## Implicits + +- Fix LiftToAnchors for higher-kinded type applications [#23672](https://github.com/scala/scala3/pull/23672) +- Fix implicit scope liftToAnchors for parameter lower bounds [#23679](https://github.com/scala/scala3/pull/23679) + +## Linting + +- Invent given pattern name in for comprehension [#23121](https://github.com/scala/scala3/pull/23121) +- Unused var message mentions unread or unset [#23719](https://github.com/scala/scala3/pull/23719) +- Lint function arrow intended context function [#23847](https://github.com/scala/scala3/pull/23847) +- Always traverse Inlined.call in linter [#24043](https://github.com/scala/scala3/pull/24043) +- Ignore warnings when compiletime.testing is imported [#24036](https://github.com/scala/scala3/pull/24036) +- Lint avoids revisiting Inlined.call [#24277](https://github.com/scala/scala3/pull/24277) +- Register no elements for lint after inlining [#24279](https://github.com/scala/scala3/pull/24279) +- Use enclosing enclosingInlineds for empty call [#24281](https://github.com/scala/scala3/pull/24281) +- Exclude synthetic opaque proxy from lint [#24264](https://github.com/scala/scala3/pull/24264) + +## Match Types + +- Fix `derivesFrom` false negative in `provablyDisjointClasses` [#23834](https://github.com/scala/scala3/pull/23834) + + +## Parser + +- Improve message for nested package missing braces [#23816](https://github.com/scala/scala3/pull/23816) +- Fix: Allow postfix setters under language.postfixOps [#23775](https://github.com/scala/scala3/pull/23775) +- Fix Java record varargs field accessor [#24172](https://github.com/scala/scala3/pull/24172) + +## Pattern Matching + +- Fix: do not transform `Ident` to `This` in PostTyper anymore [#23899](https://github.com/scala/scala3/pull/23899) +- Call inhabited for AppliedType recursively [#23964](https://github.com/scala/scala3/pull/23964) +- Fix false unreachable case warning [#23800](https://github.com/scala/scala3/pull/23800) +- Add subtype-based fallback in inferPrefixMap and recalculate constraints after application [#23771](https://github.com/scala/scala3/pull/23771) + +## Presentation Compiler + +- Additional completions for using clause [#23647](https://github.com/scala/scala3/pull/23647) +- Completions - do not add `[]` for `... derives TC@@` [#23811](https://github.com/scala/scala3/pull/23811) +- Improve symbol order in completions provided by the presentation compiler [#23888](https://github.com/scala/scala3/pull/23888) +- Porting XRayModeHints [#23891](https://github.com/scala/scala3/pull/23891) +- Go to definition and hover for named args in pattern match [#23956](https://github.com/scala/scala3/pull/23956) +- Fix parameter untupling for named tuples (#23440) [#24152](https://github.com/scala/scala3/pull/24152) +- Fix possible SuspendException thrown when using macros [#24174](https://github.com/scala/scala3/pull/24174) +- Fix completions for named tuples [#24169](https://github.com/scala/scala3/pull/24169) + +## Reporting + +- Do not discard amended format when f-interpolator warns [#23697](https://github.com/scala/scala3/pull/23697) +- Mention named givens in double def explainer [#23833](https://github.com/scala/scala3/pull/23833) +- Compute the right span for abstract error messages [#23853](https://github.com/scala/scala3/pull/23853) +- Add quick fix to add .nn [#23598](https://github.com/scala/scala3/pull/23598) +- Add addendum to `private val` parameter variance error message [#23876](https://github.com/scala/scala3/pull/23876) +- Fix compiler crash with `-Ymagic-offset-header` [#24124](https://github.com/scala/scala3/pull/24124) + +## Rewrites + +- Deduplicate patches before applying them to sources [#24215](https://github.com/scala/scala3/pull/24215) + +## Scaladoc + +- Indicate optional parameters with `= ...` [#23676](https://github.com/scala/scala3/pull/23676) +- Scaladoc Support for Capture & Separation Checking [#23607](https://github.com/scala/scala3/pull/23607) +- Capture Calcuclus: don't eagerly drop caps on parameters [#23759](https://github.com/scala/scala3/pull/23759) +- Fix rendering of function-type aliases [#24042](https://github.com/scala/scala3/pull/24042) + +## SemanticDB + +- Add context parameters to SemanticDB synthetics [#23381](https://github.com/scala/scala3/pull/23381) +- Include synthetic apply in semanticdb [#23629](https://github.com/scala/scala3/pull/23629) + +## Tuples + +- Fix: make vals created in desugaring of n-ary lambdas non-synthetic [#23896](https://github.com/scala/scala3/pull/23896) + +## Typer + +- Prevent crash in SAM conversion with mismatched arity [#23877](https://github.com/scala/scala3/pull/23877) +- Handle assertion error in TyperState [#23665](https://github.com/scala/scala3/pull/23665) +- Correctly require a `ClassTag` when building a multidimensional `Array` [#23902](https://github.com/scala/scala3/pull/23902) +- Make isExactlyNothing and isExactlyAny work for And/OrTypes [#24016](https://github.com/scala/scala3/pull/24016) + + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.7.3..3.7.4` these are: + +``` + 23 Wojciech Mazur + 16 Som Snytt + 11 noti0na1 + 6 Martin Odersky + 5 Eugene Flesselle + 4 Hamza Remmal + 4 Natsu Kagami + 4 Oliver Bračevac + 4 Seyon Sivatharan + 3 Piotr Chabelski + 3 Tomasz Godzik + 3 Yoonjae Jeon + 3 dependabot[bot] + 2 Jan Chyb + 2 Kacper Korban + 2 Katarzyna Marek + 2 Li Haoyi + 2 Matt Bovel + 2 aherlihy + 1 Florian3k + 1 HarrisL2 + 1 Martin Duhem + 1 Paweł Perłakowski + 1 Vadim Chelyshov + 1 Yichen Xu + 1 Zieliński Patryk + 1 katrinafyi + 1 vder + 1 zielinsky +``` \ No newline at end of file diff --git a/community-build/community-projects/scodec b/community-build/community-projects/scodec index 9b0423b90de9..21e68cf7a2dd 160000 --- a/community-build/community-projects/scodec +++ b/community-build/community-projects/scodec @@ -1 +1 @@ -Subproject commit 9b0423b90de95fc968fafe4543e6b16ef9f81d08 +Subproject commit 21e68cf7a2ddcd0aece5ff005cad3c53db406068 diff --git a/community-build/community-projects/specs2 b/community-build/community-projects/specs2 index 005c5847ecf9..06c992d17048 160000 --- a/community-build/community-projects/specs2 +++ b/community-build/community-projects/specs2 @@ -1 +1 @@ -Subproject commit 005c5847ecf9439691505f0628d318b0fed9d341 +Subproject commit 06c992d1704846840a0ea3cab4780ea7305e2980 diff --git a/community-build/src/scala/dotty/communitybuild/projects.scala b/community-build/src/scala/dotty/communitybuild/projects.scala index b575bd2eadf8..df0793b6fb2a 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.7", + "-sbt-version", "1.11.5", "-Dsbt.supershell=false", s"-Ddotty.communitybuild.dir=$communitybuildDir", s"--addPluginSbtFile=$sbtPluginFilePath" diff --git a/compiler/src/dotty/tools/MainGenericCompiler.scala b/compiler/src/dotty/tools/MainGenericCompiler.scala index aa924a237f73..c84052df75de 100644 --- a/compiler/src/dotty/tools/MainGenericCompiler.scala +++ b/compiler/src/dotty/tools/MainGenericCompiler.scala @@ -1,7 +1,5 @@ package dotty.tools -import scala.language.unsafeNulls - import scala.annotation.tailrec import scala.io.Source import scala.util.Try @@ -87,7 +85,7 @@ case class CompileSettings( object MainGenericCompiler { - val classpathSeparator = File.pathSeparator + val classpathSeparator: String = File.pathSeparator @sharable val javaOption = raw"""-J(.*)""".r @sharable val javaPropOption = raw"""-D(.+?)=(.?)""".r @@ -102,6 +100,8 @@ object MainGenericCompiler { case ("-q" | "-quiet") :: tail => process(tail, settings.withQuiet) case "-repl" :: tail => + Console.err.println(s"[warning] The -repl command line option has been deprecated removal in Scala 3.8.0.") + Console.err.println(s"[warning] Please use the 'scala repl' command (Scala CLI) to run the REPL.") process(tail, settings.withCompileMode(CompileMode.Repl)) case "-script" :: targetScript :: tail => process(Nil, settings @@ -116,6 +116,8 @@ object MainGenericCompiler { case "-print-tasty" :: tail => process(tail, settings.withCompileMode(CompileMode.PrintTasty)) case "-run" :: tail => + Console.err.println(s"[warning] The -run command line option has been deprecated removal in Scala 3.8.0.") + Console.err.println(s"[warning] Please use the 'scala' command (Scala CLI) instead.") process(tail, settings.withCompileMode(CompileMode.Run)) case "-colors" :: tail => process(tail, settings.withColors) diff --git a/compiler/src/dotty/tools/MainGenericRunner.scala b/compiler/src/dotty/tools/MainGenericRunner.scala index bf477f019cba..fcb69c59d840 100644 --- a/compiler/src/dotty/tools/MainGenericRunner.scala +++ b/compiler/src/dotty/tools/MainGenericRunner.scala @@ -1,7 +1,5 @@ package dotty.tools -import scala.language.unsafeNulls - import scala.annotation.tailrec import scala.io.Source import scala.util.Try @@ -98,7 +96,7 @@ case class Settings( object MainGenericRunner { - val classpathSeparator = File.pathSeparator + val classpathSeparator: String = File.pathSeparator def processClasspath(cp: String, tail: List[String]): (List[String], List[String]) = val cpEntries = cp.split(classpathSeparator).toList @@ -160,7 +158,7 @@ object MainGenericRunner { .withScriptArgs(tail*) .noSave // -save not useful here case arg :: tail => - val line = Try(Source.fromFile(arg).getLines.toList).toOption.flatMap(_.headOption) + val line = Try(Source.fromFile(arg).getLines().toList).toOption.flatMap(_.headOption) lazy val hasScalaHashbang = { val s = line.getOrElse("") ; s.startsWith("#!") && s.contains("scala") } if arg.endsWith(".scala") || arg.endsWith(".sc") || hasScalaHashbang then settings @@ -275,12 +273,11 @@ object MainGenericRunner { val silenced = sys.props.get("scala.use_legacy_launcher") == Some("true") if !silenced then - Console.err.println(s"[warning] MainGenericRunner class is deprecated since Scala 3.5.0, and Scala CLI features will not work.") - Console.err.println(s"[warning] Please be sure to update to the Scala CLI launcher to use the new features.") + Console.err.println(s"[warning] The MainGenericRunner class and 'legacy_scala' command have been deprecated for removal in Scala 3.8.0.") + Console.err.println(s"[warning] Please be sure to migrate to the scala command (Scala CLI).") if ranByCoursierBootstrap then Console.err.println(s"[warning] It appears that your Coursier-based Scala installation is misconfigured.") Console.err.println(s"[warning] To update to the new Scala CLI runner, please update (coursier, cs) commands first before re-installing scala.") - Console.err.println(s"[warning] Check the Scala 3.5.0 release notes to troubleshoot your installation.") run(settings) match diff --git a/compiler/src/dotty/tools/backend/jvm/AsmUtils.scala b/compiler/src/dotty/tools/backend/jvm/AsmUtils.scala index e6393ce82054..8a71a09aa7ab 100644 --- a/compiler/src/dotty/tools/backend/jvm/AsmUtils.scala +++ b/compiler/src/dotty/tools/backend/jvm/AsmUtils.scala @@ -2,8 +2,6 @@ package dotty.tools package backend package jvm -import scala.language.unsafeNulls - import scala.tools.asm.tree.{AbstractInsnNode} import java.io.PrintWriter import scala.tools.asm.util.{TraceClassVisitor, TraceMethodVisitor, Textifier} diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala b/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala index e1ff94be6362..5b1a8e1683f0 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala @@ -2,8 +2,6 @@ package dotty.tools package backend package jvm -import scala.language.unsafeNulls - import dotty.tools.dotc.core.Flags.* import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.report @@ -79,7 +77,7 @@ final class BCodeAsmCommon[I <: DottyBackendInterface](val interface: I) { enclosingClass(classSym.originalOwner.originalLexicallyEnclosingClass) } - /*final*/ case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String) + /*final*/ case class EnclosingMethodEntry(owner: String, name: String | Null, methodDescriptor: String | Null) /** * Data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not diff --git a/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala b/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala index 601837b37c96..dac019e985ef 100644 --- a/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala +++ b/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala @@ -8,7 +8,6 @@ import scala.collection.mutable import scala.jdk.CollectionConverters.* import dotty.tools.dotc.report -import scala.language.unsafeNulls /** * This component hosts tools and utilities used in the backend that require access to a `BTypes` diff --git a/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala b/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala index 81929c11fdcf..26531fbfe569 100644 --- a/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala +++ b/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala @@ -18,8 +18,6 @@ import dotty.tools.io.PlainFile.toPlainFile import BTypes.InternalName import dotty.tools.io.JarArchive -import scala.language.unsafeNulls - /** !!! This file is now copied in `dotty.tools.io.FileWriters` in a more general way that does not rely upon * `PostProcessorFrontendAccess`, this should probably be changed to wrap that class instead. * @@ -54,11 +52,11 @@ class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) { def close(): Unit protected def classRelativePath(className: InternalName, suffix: String = ".class"): String = - className.replace('.', '/').nn + suffix + className.replace('.', '/') + suffix } object ClassfileWriter { - private def getDirectory(dir: String): Path = Paths.get(dir).nn + private def getDirectory(dir: String): Path = Paths.get(dir) def apply(): ClassfileWriter = { val jarManifestMainClass: Option[String] = compilerSettings.mainClass.orElse { @@ -137,7 +135,7 @@ class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) { new JarEntryWriter(jarFile, jarManifestMainClass, jarCompressionLevel) } else if (file.isVirtual) new VirtualFileWriter(file) - else if (file.isDirectory) new DirEntryWriter(file.file.toPath.nn) + else if (file.isDirectory) new DirEntryWriter(file.file.nn.toPath) else throw new IllegalStateException(s"don't know how to handle an output of $file [${file.getClass}]") } @@ -151,7 +149,7 @@ class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) { val jarWriter: JarOutputStream = { import scala.util.Properties.* val manifest = new Manifest - val attrs = manifest.getMainAttributes.nn + val attrs = manifest.getMainAttributes attrs.put(MANIFEST_VERSION, "1.0") attrs.put(ScalaCompilerVersion, versionNumberString) mainClass.foreach(c => attrs.put(MAIN_CLASS, c)) @@ -184,7 +182,7 @@ class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) { // important detail here, even on Windows, Zinc expects the separator within the jar // to be the system default, (even if in the actual jar file the entry always uses '/'). // see https://github.com/sbt/zinc/blob/dcddc1f9cfe542d738582c43f4840e17c053ce81/internal/compiler-bridge/src/main/scala/xsbt/JarUtils.scala#L47 - val pathInJar = + val pathInJar = if File.separatorChar == '/' then relativePath else relativePath.replace('/', File.separatorChar) PlainFile.toPlainFile(Paths.get(s"${file.absolutePath}!$pathInJar")) @@ -293,5 +291,5 @@ class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) { } /** Can't output a file due to the state of the file system. */ - class FileConflictException(msg: String, cause: Throwable = null) extends IOException(msg, cause) + class FileConflictException(msg: String, cause: Throwable | Null = null) extends IOException(msg, cause) } diff --git a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala index d4843cd56639..093d4f997aa7 100644 --- a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala +++ b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala @@ -1,6 +1,5 @@ package dotty.tools.backend.jvm -import scala.language.unsafeNulls import dotty.tools.dotc.CompilationUnit import dotty.tools.dotc.ast.Trees.{PackageDef, ValDef} @@ -71,7 +70,7 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( val tastyAttrNode = if (mirrorClassNode ne null) mirrorClassNode else mainClassNode genTastyAndSetAttributes(sym, tastyAttrNode) - def registerGeneratedClass(classNode: ClassNode, isArtifact: Boolean): Unit = + def registerGeneratedClass(classNode: ClassNode | Null, isArtifact: Boolean): Unit = if classNode ne null then generatedClasses += GeneratedClass(classNode, sourceClassName = sym.javaClassName, @@ -86,8 +85,8 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( case ex: InterruptedException => throw ex case ex: CompilationUnit.SuspendException => throw ex case ex: Throwable => - ex.printStackTrace() - report.error(s"Error while emitting ${unit.source}\n${ex.getMessage}", NoSourcePosition) + if !ex.isInstanceOf[TypeError] then ex.printStackTrace() + report.error(s"Error while emitting ${unit.source}\n${ex.getMessage}", cd.sourcePos) def genTastyAndSetAttributes(claszSymbol: Symbol, store: ClassNode): Unit = @@ -131,7 +130,7 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( } clsFile => { val className = cls.name.replace('/', '.') - if (ctx.compilerCallback != null) + if (ctx.compilerCallback ne null) ctx.compilerCallback.onClassGenerated(sourceFile, convertAbstractFile(clsFile), className) ctx.withIncCallback: cb => @@ -153,7 +152,7 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( new interfaces.AbstractFile { override def name = absfile.name override def path = absfile.path - override def jfile = Optional.ofNullable(absfile.file) + override def jfile: Optional[java.io.File] = Optional.ofNullable(absfile.file) } private def genClass(cd: TypeDef, unit: CompilationUnit): ClassNode = { diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index cab17b31c3f3..1cd83dba707a 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -1,6 +1,5 @@ package dotty.tools.backend.jvm -import scala.language.unsafeNulls import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Flags.* @@ -44,14 +43,14 @@ class DottyBackendInterface(val superCallsMap: ReadOnlyMap[Symbol, List[ClassSym object DesugaredSelect extends DeconstructorCommon[tpd.Tree] { - var desugared: tpd.Select = null + var desugared: tpd.Select | Null = null override def isEmpty: Boolean = desugared eq null - def _1: Tree = desugared.qualifier + def _1: Tree = desugared.nn.qualifier - def _2: Name = desugared.name + def _2: Name = desugared.nn.name override def unapply(s: tpd.Tree): this.type = { s match { @@ -69,17 +68,17 @@ class DottyBackendInterface(val superCallsMap: ReadOnlyMap[Symbol, List[ClassSym } object ArrayValue extends DeconstructorCommon[tpd.JavaSeqLiteral] { - def _1: Type = field.tpe match { + def _1: Type = field.nn.tpe match { case JavaArrayType(elem) => elem case _ => - report.error(em"JavaSeqArray with type ${field.tpe} reached backend: $field", ctx.source.atSpan(field.span)) + report.error(em"JavaSeqArray with type ${field.nn.tpe} reached backend: $field", ctx.source.atSpan(field.nn.span)) UnspecifiedErrorType } - def _2: List[Tree] = field.elems + def _2: List[Tree] = field.nn.elems } - abstract class DeconstructorCommon[T >: Null <: AnyRef] { - var field: T = null + abstract class DeconstructorCommon[T <: AnyRef] { + var field: T | Null = null def get: this.type = this def isEmpty: Boolean = field eq null def isDefined = !isEmpty diff --git a/compiler/src/dotty/tools/backend/jvm/GeneratedClassHandler.scala b/compiler/src/dotty/tools/backend/jvm/GeneratedClassHandler.scala index bf2ae9a131aa..70db11fc7029 100644 --- a/compiler/src/dotty/tools/backend/jvm/GeneratedClassHandler.scala +++ b/compiler/src/dotty/tools/backend/jvm/GeneratedClassHandler.scala @@ -14,7 +14,6 @@ import scala.util.control.NonFatal import dotty.tools.dotc.core.Phases import dotty.tools.dotc.core.Decorators.em -import scala.language.unsafeNulls import scala.compiletime.uninitialized /** @@ -189,4 +188,4 @@ final private class CompilationUnitInPostProcess(private var classes: List[Gener var task: Future[Unit] = uninitialized val bufferedReporting = new PostProcessorFrontendAccess.BufferingBackendReporting() -} \ No newline at end of file +} diff --git a/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala b/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala index 9f172806a3b5..e0910460ab0e 100644 --- a/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala +++ b/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala @@ -36,7 +36,7 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, val bTypes: setInnerClasses(classNode) serializeClass(classNode) catch - case e: java.lang.RuntimeException if e.getMessage != null && e.getMessage.nn.contains("too large!") => + case e: java.lang.RuntimeException if e.getMessage != null && e.getMessage.contains("too large!") => backendReporting.error(em"Could not write class $internalName because it exceeds JVM code size limits. ${e.getMessage}") null case ex: Throwable => @@ -58,8 +58,8 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, val bTypes: } private def warnCaseInsensitiveOverwrite(clazz: GeneratedClass) = { - val name = clazz.classNode.name.nn - val lowerCaseJavaName = name.nn.toLowerCase + val name = clazz.classNode.name + val lowerCaseJavaName = name.toLowerCase val clsPos = clazz.position caseInsensitively.putIfAbsent(lowerCaseJavaName, (name, clsPos)) match { case null => () @@ -71,7 +71,7 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, val bTypes: val locationAddendum = if pos1.source.path == pos2.source.path then "" else s" (defined in ${pos2.source.file.name})" - def nicify(name: String): String = name.replace('/', '.').nn + def nicify(name: String): String = name.replace('/', '.') if name1 == name2 then backendReporting.error( em"${nicify(name1)} and ${nicify(name2)} produce classes that overwrite one another", pos1) diff --git a/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala b/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala index e1b2120fa848..3e28d2a949cb 100644 --- a/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala +++ b/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala @@ -146,7 +146,7 @@ object PostProcessorFrontendAccess { override def backendReporting: BackendReporting = { val local = localReporter.get() if local eq null then directBackendReporting - else local.nn + else local } override object directBackendReporting extends BackendReporting { diff --git a/compiler/src/dotty/tools/backend/jvm/Primitives.scala b/compiler/src/dotty/tools/backend/jvm/Primitives.scala index c9ddfeab24e1..b173736b946c 100644 --- a/compiler/src/dotty/tools/backend/jvm/Primitives.scala +++ b/compiler/src/dotty/tools/backend/jvm/Primitives.scala @@ -150,13 +150,12 @@ object Primitives { case object XOR extends LogicalOp /** Signals the beginning of a series of concatenations. - * On the JVM platform, it should create a new StringBuffer - */ + * On the JVM platform, it should create a new StringBuilder. + */ case object StartConcat extends Primitive - /** - * type: (buf) => STR - * jvm : It should turn the StringBuffer into a String. + /** type: (buf) => STR + * jvm : It should turn the StringBuilder into a String. */ case object EndConcat extends Primitive diff --git a/compiler/src/dotty/tools/dotc/Bench.scala b/compiler/src/dotty/tools/dotc/Bench.scala index cbc490919cfe..26259dcaa28b 100644 --- a/compiler/src/dotty/tools/dotc/Bench.scala +++ b/compiler/src/dotty/tools/dotc/Bench.scala @@ -30,7 +30,7 @@ object Bench extends Driver: println(s"time elapsed: ${times(curRun)}ms") if ctx.settings.Xprompt.value || waitAfter == curRun + 1 then print("hit to continue >") - System.in.nn.read() + System.in.read() reporter def extractNumArg(args: Array[String], name: String, default: Int = 1): (Int, Array[String]) = { diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 6aab7d54d59e..66044dd9462d 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -92,7 +92,7 @@ class Compiler { new ExplicitSelf, // Make references to non-trivial self types explicit as casts new StringInterpolatorOpt, // Optimizes raw and s and f string interpolators by rewriting them to string concatenations or formats new DropBreaks) :: // Optimize local Break throws by rewriting them - List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions + List(new PruneErasedDefs, // Make erased symbols private new UninitializedDefs, // Replaces `compiletime.uninitialized` by `_` new InlinePatterns, // Remove placeholders of inlined patterns new VCInlineMethods, // Inlines calls to value class methods @@ -171,7 +171,7 @@ class Compiler { val rctx = if ctx.settings.Xsemanticdb.value then ctx.addMode(Mode.ReadPositions) - else if ctx.settings.YcheckInitGlobal.value then + else if ctx.settings.YsafeInitGlobal.value then ctx.addMode(Mode.ReadPositions) else ctx diff --git a/compiler/src/dotty/tools/dotc/Driver.scala b/compiler/src/dotty/tools/dotc/Driver.scala index 98abe2ac6c38..a401bb689db0 100644 --- a/compiler/src/dotty/tools/dotc/Driver.scala +++ b/compiler/src/dotty/tools/dotc/Driver.scala @@ -38,7 +38,7 @@ class Driver { finish(compiler, run) catch case ex: FatalError => - report.error(ex.getMessage.nn) // signals that we should fail compilation. + report.error(ex.getMessage) // signals that we should fail compilation. case ex: Throwable if ctx.usedBestEffortTasty => report.bestEffortError(ex, "Some best-effort tasty files were not able to be read.") throw ex @@ -117,7 +117,7 @@ class Driver { .distinct val ctx1 = ctx.fresh val fullClassPath = - (newEntries :+ ctx.settings.classpath.value).mkString(java.io.File.pathSeparator.nn) + (newEntries :+ ctx.settings.classpath.value).mkString(java.io.File.pathSeparator) ctx1.setSetting(ctx1.settings.classpath, fullClassPath) else ctx diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index b4dbb76dc464..58ea3e03edba 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -92,14 +92,21 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { mySuspendedMessages.getOrElseUpdate(warning.pos.source, mutable.LinkedHashSet.empty) += warning def nowarnAction(dia: Diagnostic): Action.Warning.type | Action.Verbose.type | Action.Silent.type = - mySuppressions.getOrElse(dia.pos.source, Nil).find(_.matches(dia)) match { - case Some(s) => + mySuppressions.get(dia.pos.source) match + case Some(suppressions) => + val matching = suppressions.iterator.filter(_.matches(dia)) + if matching.hasNext then + val s = matching.next() + for other <- matching do + if !other.used then + other.markSuperseded() // superseded unless marked used later s.markUsed() - if (s.verbose) Action.Verbose + if s.verbose then Action.Verbose else Action.Silent - case _ => + else Action.Warning - } + case none => + Action.Warning def registerNowarn(annotPos: SourcePosition, range: Span)(conf: String, pos: SrcPos)(using Context): Unit = var verbose = false @@ -118,12 +125,10 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { .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 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 + if sup.start != sup.end then suppressions += sup def reportSuspendedMessages(source: SourceFile)(using Context): Unit = { @@ -134,7 +139,8 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { mySuspendedMessages.remove(source).foreach(_.foreach(ctx.reporter.issueIfNotSuppressed)) } - def runFinished(hasErrors: Boolean): Unit = + def runFinished()(using Context): Unit = + val hasErrors = ctx.reporter.hasErrors // report suspended messages (in case the run finished before typer) mySuspendedMessages.keysIterator.toList.foreach(reportSuspendedMessages) // report unused nowarns only if all all phases are done @@ -142,10 +148,16 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { for source <- mySuppressions.keysIterator.toList sups <- mySuppressions.remove(source) - sup <- sups.reverse - if !sup.used do - report.warning("@nowarn annotation does not suppress any warnings", sup.annotPos) + val suppressions = sups.reverse.toList + for sup <- suppressions do + if !sup.used + && !suppressions.exists(s => s.ne(sup) && s.used && s.annotPos == sup.annotPos) // duplicate + && sup.filters != List(MessageFilter.None) // invalid suppression, don't report as unused + then + val more = if sup.superseded then " but matches a diagnostic" else "" + report.warning("@nowarn annotation does not suppress any warnings"+more, sup.annotPos) + end suppressions /** The compilation units currently being compiled, this may return different * results over time. @@ -236,7 +248,7 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { try trackProgress(_.cancel()) finally - Thread.currentThread().nn.interrupt() + Thread.currentThread().interrupt() private def doAdvancePhase(currentPhase: Phase, wasRan: Boolean)(using Context): Unit = trackProgress: progress => @@ -367,7 +379,7 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { profiler.onPhase(phase): try units = phase.runOn(units) catch case _: InterruptedException => cancelInterrupted() - if (ctx.settings.Xprint.value.containsPhase(phase)) + if (ctx.settings.Vprint.value.containsPhase(phase)) for (unit <- units) def printCtx(unit: CompilationUnit) = phase.printingContext( ctx.fresh.setPhase(phase.next).setCompilationUnit(unit)) @@ -411,7 +423,7 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { ctx.reporter.finalizeReporting() if (!ctx.reporter.hasErrors) Rewrites.writeBack() - suppressions.runFinished(hasErrors = ctx.reporter.hasErrors) + suppressions.runFinished() while (finalizeActions.nonEmpty && canProgress()) { val action = finalizeActions.remove(0) action() diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 471a9953c4f0..2ed3415ebe70 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -67,6 +67,8 @@ object desugar { */ val TrailingForMap: Property.Key[Unit] = Property.StickyKey() + val WasTypedInfix: Property.Key[Unit] = Property.StickyKey() + /** What static check should be applied to a Match? */ enum MatchCheck { case None, Exhaustive, IrrefutablePatDef, IrrefutableGenFrom @@ -642,7 +644,7 @@ object desugar { .withMods(mods & (GivenOrImplicit | Erased | hasDefault | Tracked) | Param) } - /** Desugar type def (not param): Under x.moduliity this can expand + /** Desugar type def (not param): Under x.modularity this can expand * context bounds, which are expanded to evidence ValDefs. These will * ultimately map to deferred givens. */ @@ -652,6 +654,8 @@ object desugar { tdef, evidenceBuf, (tdef.mods.flags.toTermFlags & AccessFlags) | Lazy | DeferredGivenFlags, inventGivenName, Nil) + if tdef.mods.flags.is(Into, butNot = Opaque) then + report.error(ModifierNotAllowedForDefinition(Into), flagSourcePos(tdef, Into)) if evidenceBuf.isEmpty then result else Thicket(result :: evidenceBuf.toList) /** The expansion of a class definition. See inline comments for what is involved */ @@ -703,7 +707,7 @@ object desugar { def isNonEnumCase = !isEnumCase && (isCaseClass || isCaseObject) val isValueClass = parents.nonEmpty && isAnyVal(parents.head) // This is not watertight, but `extends AnyVal` will be replaced by `inline` later. - val caseClassInScala2Library = isCaseClass && ctx.settings.YcompileScala2Library.value + val caseClassInScala2Library = isCaseClass && Feature.shouldBehaveAsScala2 val originalTparams = constr1.leadingTypeParams val originalVparamss = asTermOnly(constr1.trailingParamss) @@ -922,7 +926,7 @@ object desugar { val copyRestParamss = derivedVparamss.tail.nestedMap(vparam => cpy.ValDef(vparam)(rhs = EmptyTree)) var flags = Synthetic | constr1.mods.flags & copiedAccessFlags - if ctx.settings.YcompileScala2Library.value then flags &~= Private + if Feature.shouldBehaveAsScala2 then flags &~= Private DefDef( nme.copy, joinParams(derivedTparams, copyFirstParams :: copyRestParamss), @@ -983,7 +987,7 @@ object desugar { else { val appMods = var flags = Synthetic | constr1.mods.flags & copiedAccessFlags - if ctx.settings.YcompileScala2Library.value then flags &~= Private + if Feature.shouldBehaveAsScala2 then flags &~= Private Modifiers(flags).withPrivateWithin(constr1.mods.privateWithin) val appParamss = derivedVparamss.nestedZipWithConserve(constrVparamss)((ap, cp) => @@ -1066,7 +1070,7 @@ object desugar { paramss // drop leading () that got inserted by class // TODO: drop this once we do not silently insert empty class parameters anymore case paramss => paramss - val finalFlag = if ctx.settings.YcompileScala2Library.value then EmptyFlags else Final + val finalFlag = if Feature.shouldBehaveAsScala2 then EmptyFlags else Final // implicit wrapper is typechecked in same scope as constructor, so // we can reuse the constructor parameters; no derived params are needed. DefDef( @@ -1343,7 +1347,7 @@ object desugar { )).withSpan(tree.span) end makePolyFunctionType - /** Invent a name for an anonympus given of type or template `impl`. */ + /** Invent a name for an anonymous given of type or template `impl`. */ def inventGivenName(impl: Tree)(using Context): SimpleName = val str = impl match case impl: Template => @@ -1448,6 +1452,27 @@ object desugar { sel end match + case class TuplePatternInfo(arity: Int, varNum: Int, wildcardNum: Int) + object TuplePatternInfo: + def apply(pat: Tree)(using Context): TuplePatternInfo = pat match + case Tuple(pats) => + var arity = 0 + var varNum = 0 + var wildcardNum = 0 + pats.foreach: p => + arity += 1 + p match + case id: Ident if !isBackquoted(id) => + if id.name.isVarPattern then + varNum += 1 + if id.name == nme.WILDCARD then + wildcardNum += 1 + case _ => + TuplePatternInfo(arity, varNum, wildcardNum) + case _ => + TuplePatternInfo(-1, -1, -1) + end TuplePatternInfo + /** If `pat` is a variable pattern, * * val/var/lazy val p = e @@ -1481,30 +1506,47 @@ object desugar { |please bind to an identifier and use an alias given.""", bind) false - def isTuplePattern(arity: Int): Boolean = pat match { - case Tuple(pats) if pats.size == arity => - pats.forall(isVarPattern) - case _ => false - } - - val isMatchingTuple: Tree => Boolean = { - case Tuple(es) => isTuplePattern(es.length) && !hasNamedArg(es) - case _ => false - } + val tuplePatternInfo = TuplePatternInfo(pat) + + // When desugaring a PatDef in general, we use pattern matching on the rhs + // and collect the variable values in a tuple, then outside the match, + // we destructure the tuple to get the individual variables. + // We can achieve two kinds of tuple optimizations if the pattern is a tuple + // of simple variables or wildcards: + // 1. Full optimization: + // If the rhs is known to produce a literal tuple of the same arity, + // we can directly fetch the values from the tuple. + // For example: `val (x, y) = if ... then (1, "a") else (2, "b")` becomes + // `val $1$ = if ...; val x = $1$._1; val y = $1$._2`. + // 2. Partial optimization: + // If the rhs can be typed as a tuple and matched with correct arity, we can + // return the tuple itself in the case if there are no more than one variable + // in the pattern, or return the the value if there is only one variable. + + val fullTupleOptimizable = + val isMatchingTuple: Tree => Boolean = { + case Tuple(es) => tuplePatternInfo.varNum == es.length && !hasNamedArg(es) + case _ => false + } + tuplePatternInfo.arity > 0 + && tuplePatternInfo.arity == tuplePatternInfo.varNum + && forallResults(rhs, isMatchingTuple) - // We can only optimize `val pat = if (...) e1 else e2` if: - // - `e1` and `e2` are both tuples of arity N - // - `pat` is a tuple of N variables or wildcard patterns like `(x1, x2, ..., xN)` - val tupleOptimizable = forallResults(rhs, isMatchingTuple) + val partialTupleOptimizable = + tuplePatternInfo.arity > 0 + && tuplePatternInfo.arity == tuplePatternInfo.varNum + // We exclude the case where there is only one variable, + // because it should be handled by `makeTuple` directly. + && tuplePatternInfo.wildcardNum < tuplePatternInfo.arity - 1 val inAliasGenerator = original match case _: GenAlias => true case _ => false - val vars = - if (tupleOptimizable) // include `_` + val vars: List[VarInfo] = + if fullTupleOptimizable || partialTupleOptimizable then // include `_` pat match - case Tuple(pats) => pats.map { case id: Ident => id -> TypeTree() } + case Tuple(pats) => pats.map { case id: Ident => (id, TypeTree()) } else getVariables( tree = pat, @@ -1515,12 +1557,27 @@ object desugar { errorOnGivenBinding ) // no `_` - val ids = for ((named, _) <- vars) yield Ident(named.name) + val ids = for ((named, tpt) <- vars) yield Ident(named.name) + val matchExpr = - if (tupleOptimizable) rhs + if fullTupleOptimizable then rhs else - val caseDef = CaseDef(pat, EmptyTree, makeTuple(ids).withAttachment(ForArtifact, ())) + val caseDef = + if partialTupleOptimizable then + val tmpTuple = UniqueName.fresh() + // Replace all variables with wildcards in the pattern + val pat1 = pat match + case Tuple(pats) => + val wildcardPats = pats.map(p => Ident(nme.WILDCARD).withSpan(p.span)) + Tuple(wildcardPats).withSpan(pat.span) + CaseDef( + Bind(tmpTuple, pat1), + EmptyTree, + Ident(tmpTuple).withAttachment(ForArtifact, ()) + ) + else CaseDef(pat, EmptyTree, makeTuple(ids).withAttachment(ForArtifact, ())) Match(makeSelector(rhs, MatchCheck.IrrefutablePatDef), caseDef :: Nil) + vars match { case Nil if !mods.is(Lazy) => matchExpr @@ -1665,10 +1722,12 @@ object desugar { case _ => Apply(sel, arg :: Nil) - if op.name.isRightAssocOperatorName then + val apply = if op.name.isRightAssocOperatorName then makeOp(right, left, Span(op.span.start, right.span.end)) else makeOp(left, right, Span(left.span.start, op.span.end, op.span.start)) + apply.pushAttachment(WasTypedInfix, ()) + return apply } /** Translate throws type `A throws E1 | ... | En` to @@ -1717,7 +1776,7 @@ object desugar { */ def tuple(tree: Tuple, pt: Type)(using Context): Tree = var elems = checkWellFormedTupleElems(tree.trees) - if ctx.mode.is(Mode.Pattern) then elems = adaptPatternArgs(elems, pt) + if ctx.mode.is(Mode.Pattern) then elems = adaptPatternArgs(elems, pt, tree.srcPos) val elemValues = elems.mapConserve(stripNamedArg) val tup = val arity = elems.length @@ -1757,25 +1816,31 @@ object desugar { * - If `elems` are named pattern elements, rearrange them to match `pt`. * This requires all names in `elems` to be also present in `pt`. */ - def adaptPatternArgs(elems: List[Tree], pt: Type)(using Context): List[Tree] = + def adaptPatternArgs(elems: List[Tree], pt: Type, pos: SrcPos)(using Context): List[Tree] = def reorderedNamedArgs(wildcardSpan: Span): List[untpd.Tree] = - var selNames = pt.namedTupleElementTypes(false).map(_(0)) - if selNames.isEmpty && pt.classSymbol.is(CaseClass) then - selNames = pt.classSymbol.caseAccessors.map(_.name.asTermName) - val nameToIdx = selNames.zipWithIndex.toMap - val reordered = Array.fill[untpd.Tree](selNames.length): - untpd.Ident(nme.WILDCARD).withSpan(wildcardSpan) - for case arg @ NamedArg(name: TermName, _) <- elems do - nameToIdx.get(name) match - case Some(idx) => - if reordered(idx).isInstanceOf[Ident] then - reordered(idx) = arg - else - report.error(em"Duplicate named pattern", arg.srcPos) - case _ => - report.error(em"No element named `$name` is defined in selector type $pt", arg.srcPos) - reordered.toList + inline def isCaseClass = pt.classSymbol.is(CaseClass) && !defn.isTupleClass(pt.classSymbol) + if !isCaseClass && !pt.isNamedTupleType then + report.error(NamedPatternNotApplicable(pt), pos) + Nil + else + var selNames = pt.namedTupleElementTypes(false).map(_(0)) + if isCaseClass && selNames.isEmpty then + selNames = pt.classSymbol.caseAccessors.map(_.name.asTermName) + val nameToIdx = selNames.zipWithIndex.toMap + val reordered = Array.fill[untpd.Tree](selNames.length): + untpd.Ident(nme.WILDCARD).withSpan(wildcardSpan) + for case arg @ NamedArg(name: TermName, _) <- elems do + nameToIdx.get(name) match + case Some(idx) => + if reordered(idx).isInstanceOf[Ident] then + reordered(idx) = arg + else + report.error(em"Duplicate named pattern", arg.srcPos) + case _ => + report.error(em"No element named `$name` is defined in selector type $pt", arg.srcPos) + reordered.toList + end if elems match case (first @ NamedArg(_, _)) :: _ => reorderedNamedArgs(first.span.startPos) @@ -1868,18 +1933,18 @@ object desugar { /** Map n-ary function `(x1: T1, ..., xn: Tn) => body` where n != 1 to unary function as follows: * * (x$1: (T1, ..., Tn)) => { - * def x1: T1 = x$1._1 + * val x1: T1 = x$1._1 * ... - * def xn: Tn = x$1._n + * val xn: Tn = x$1._n * body * } * * or if `isGenericTuple` * * (x$1: (T1, ... Tn) => { - * def x1: T1 = x$1.apply(0) + * val x1: T1 = x$1.apply(0) * ... - * def xn: Tn = x$1.apply(n-1) + * val xn: Tn = x$1.apply(n-1) * body * } * @@ -1903,7 +1968,6 @@ object desugar { ValDef(param.name, param.tpt, selector(idx)) .withSpan(param.span) .withAttachment(UntupledParam, ()) - .withFlags(Synthetic) } Function(param :: Nil, Block(vdefs, body)) } @@ -2041,7 +2105,19 @@ object desugar { val matchCheckMode = if (gen.checkMode == GenCheckMode.Check || gen.checkMode == GenCheckMode.CheckAndFilter) MatchCheck.IrrefutableGenFrom else MatchCheck.None - makeCaseLambda(CaseDef(gen.pat, EmptyTree, body) :: Nil, matchCheckMode) + val pat = gen.pat.match + case Tuple(pats) if pats.length > Definitions.MaxImplementedFunctionArity => + /* The pattern case is a tupleXXL, because we have bound > 21 variables in the comprehension. + * In this case, we need to mark all the typed patterns as @unchecked, or get loads of warnings. + * Cf. warn test i23164.scala */ + Tuple: + pats.map: + case t @ Bind(name, tp @ Typed(id, tpt)) => + val annotated = Annotated(tpt, New(ref(defn.UncheckedAnnot.typeRef))) + cpy.Bind(t)(name, cpy.Typed(tp)(id, annotated)).withMods(t.mods) + case t => t + case _ => gen.pat + makeCaseLambda(CaseDef(pat, EmptyTree, body) :: Nil, matchCheckMode) } def hasGivenBind(pat: Tree): Boolean = pat.existsSubTree { @@ -2059,18 +2135,19 @@ object desugar { * that refers to the bound variable for the pattern. Wildcard Binds are * also replaced by Binds with fresh names. */ - def makeIdPat(pat: Tree): (Tree, Ident) = pat match { - case bind @ Bind(name, pat1) => - if name == nme.WILDCARD then - val name = UniqueName.fresh() - (cpy.Bind(pat)(name, pat1).withMods(bind.mods), Ident(name)) - else (pat, Ident(name)) + def makeIdPat(pat: Tree): (Tree, Ident) = pat match + case pat @ Bind(nme.WILDCARD, body) => + val name = + body match + case Typed(Ident(nme.WILDCARD), tpt) if pat.mods.is(Given) => inventGivenName(tpt) + case _ => UniqueName.fresh() + (cpy.Bind(pat)(name, body).withMods(pat.mods), Ident(name)) + case Bind(name, _) => (pat, Ident(name)) case id: Ident if isVarPattern(id) && id.name != nme.WILDCARD => (id, id) case Typed(id: Ident, _) if isVarPattern(id) && id.name != nme.WILDCARD => (pat, id) case _ => val name = UniqueName.fresh() (Bind(name, pat), Ident(name)) - } /** Make a pattern filter: * rhs.withFilter { case pat => true case _ => false } @@ -2260,19 +2337,12 @@ object desugar { Annotated( AppliedTypeTree(ref(defn.SeqType), t), New(ref(defn.RepeatedAnnot.typeRef), Nil :: Nil)) - else if op.name == nme.CC_REACH then - Apply(ref(defn.Caps_reachCapability), t :: Nil) - else if op.name == nme.CC_READONLY then - Apply(ref(defn.Caps_readOnlyCapability), t :: Nil) else assert(ctx.mode.isExpr || ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive), ctx.mode) Select(t, op.name) case PrefixOp(op, t) => - if op.name == tpnme.into then - Annotated(t, New(ref(defn.IntoAnnot.typeRef), Nil :: Nil)) - else - val nspace = if (ctx.mode.is(Mode.Type)) tpnme else nme - Select(t, nspace.UNARY_PREFIX ++ op.name) + val nspace = if (ctx.mode.is(Mode.Type)) tpnme else nme + Select(t, nspace.UNARY_PREFIX ++ op.name) case ForDo(enums, body) => makeFor(nme.foreach, nme.foreach, enums, body) orElse tree case ForYield(enums, body) => diff --git a/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala b/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala index e77642a8e2b9..861db55b1903 100644 --- a/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala +++ b/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala @@ -94,7 +94,8 @@ object NavigateAST { * When choosing better fit we compare spans. If candidate span has starting or ending point inside (exclusive) * current best fit it is selected as new best fit. This means that same spans are failing the first predicate. * - * In case when spans start and end at same offsets we prefer non synthethic one. + * In case when spans start and end at same offsets we prefer non synthethic one, + * and then one with better point (see isBetterPoint below). */ def isBetterFit(currentBest: List[Positioned], candidate: List[Positioned]): Boolean = if currentBest.isEmpty && candidate.nonEmpty then true @@ -102,9 +103,20 @@ object NavigateAST { val bestSpan = currentBest.head.span val candidateSpan = candidate.head.span - bestSpan != candidateSpan && - envelops(bestSpan, candidateSpan) || - bestSpan.contains(candidateSpan) && bestSpan.isSynthetic && !candidateSpan.isSynthetic + def isBetterPoint = + // Given two spans with same end points, + // we compare their points in relation to the point we are looking for (span.point) + // The candidate (candidateSpan.point) is better than what we have so far (bestSpan.point), when: + // 1) candidate is closer to target from the right + span.point <= candidateSpan.point && candidateSpan.point < bestSpan.point + // 2) candidate is closer to target from the left + || bestSpan.point < candidateSpan.point && candidateSpan.point <= span.point + // 3) candidate is to on the left side of target, and best so far is on the right + || candidateSpan.point <= span.point && span.point < bestSpan.point + + bestSpan != candidateSpan && envelops(bestSpan, candidateSpan) + || bestSpan.contains(candidateSpan) && bestSpan.isSynthetic && !candidateSpan.isSynthetic + || candidateSpan.start == bestSpan.start && candidateSpan.end == bestSpan.end && isBetterPoint else false def isRecoveryTree(sel: untpd.Select): Boolean = @@ -141,7 +153,9 @@ object NavigateAST { case _ => val iterator = p match case defdef: DefTree[?] => - p.productIterator ++ defdef.mods.productIterator + val mods = defdef.mods + val annotations = defdef.symbol.annotations.filter(_.tree.span.contains(span)).map(_.tree) + p.productIterator ++ annotations ++ mods.productIterator case _ => p.productIterator childPath(iterator, p :: path) diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index d8017783f47f..1396ee928e7f 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -3,7 +3,8 @@ package dotc package ast import util.Spans.* -import util.{SourceFile, SourcePosition, SrcPos} +import util.{SourceFile, SourcePosition, SrcPos, WrappedSourceFile} +import WrappedSourceFile.MagicHeaderInfo, MagicHeaderInfo.* import core.Contexts.* import core.Decorators.* import core.NameOps.* @@ -51,7 +52,15 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src def source: SourceFile = mySource - def sourcePos(using Context): SourcePosition = source.atSpan(span) + def sourcePos(using Context): SourcePosition = + val info = WrappedSourceFile.locateMagicHeader(source) + info match + // This span is in user code + case HasHeader(offset, originalFile) + if span.exists && span.start >= offset => + originalFile.atSpan(span.shift(-offset)) + // Otherwise, return the source position in the wrapper code + case _ => source.atSpan(span) /** This positioned item, widened to `SrcPos`. Used to make clear we only need the * position, typically for error reporting. diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 45e17794ec96..5415a6e10609 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -9,6 +9,8 @@ import Annotations.Annotation import NameKinds.ContextBoundParamName import typer.ConstFold import reporting.trace +import config.Feature +import util.SrcPos import Decorators.* import Constants.Constant @@ -264,6 +266,19 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] => case _ => false } + /** Expression was written `e: Unit` to quell warnings. Looks into adapted tree. */ + def isAscribedToUnit(tree: Tree): Boolean = + import typer.Typer.AscribedToUnit + tree.hasAttachment(AscribedToUnit) + || { + def loop(tree: Tree): Boolean = tree match + case Apply(fn, _) => fn.hasAttachment(AscribedToUnit) || loop(fn) + case TypeApply(fn, _) => fn.hasAttachment(AscribedToUnit) || loop(fn) + case Block(_, expr) => expr.hasAttachment(AscribedToUnit) || loop(expr) + case _ => false + loop(tree) + } + /** Does this CaseDef catch Throwable? */ def catchesThrowable(cdef: CaseDef)(using Context): Boolean = catchesAllOf(cdef, defn.ThrowableType) @@ -335,14 +350,16 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] => } /** Checks whether predicate `p` is true for all result parts of this expression, - * where we zoom into Ifs, Matches, and Blocks. + * where we zoom into Ifs, Matches, Tries, and Blocks. */ - def forallResults(tree: Tree, p: Tree => Boolean): Boolean = tree match { + def forallResults(tree: Tree, p: Tree => Boolean): Boolean = tree match case If(_, thenp, elsep) => forallResults(thenp, p) && forallResults(elsep, p) - case Match(_, cases) => cases forall (c => forallResults(c.body, p)) + case Match(_, cases) => cases.forall(c => forallResults(c.body, p)) + case Try(_, cases, finalizer) => + cases.forall(c => forallResults(c.body, p)) + && (finalizer.isEmpty || forallResults(finalizer, p)) case Block(_, expr) => forallResults(expr, p) case _ => p(tree) - } /** The tree stripped of the possibly nested applications (term and type). * The original tree if it's not an application. @@ -466,7 +483,7 @@ 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 => + case tree: TypeDef if Feature.shouldBehaveAsScala2 => if (tree.isClassDef) EmptyFlags else NoInitsInterface case tree: TypeDef => if (tree.isClassDef) NoInits else NoInitsInterface case tree: DefDef => @@ -479,7 +496,7 @@ 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 + else if Feature.shouldBehaveAsScala2 then EmptyFlags else NoInits @@ -522,6 +539,10 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] if id.span == result.span.startPos => Some(result) case _ => None end ImpureByNameTypeTree + + /** The position of the modifier associated with given flag in this definition. */ + def flagSourcePos(mdef: DefTree, flag: FlagSet): SrcPos = + mdef.mods.mods.find(_.flags == flag).getOrElse(mdef).srcPos } trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => @@ -569,14 +590,22 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case New(_) | Closure(_, _, _) => Pure case TypeApply(fn, _) => - if (fn.symbol.is(Erased) || fn.symbol == defn.QuotedTypeModule_of || fn.symbol == defn.Predef_classOf) Pure else exprPurity(fn) + val sym = fn.symbol + if tree.tpe.isInstanceOf[MethodOrPoly] then exprPurity(fn) + else if sym == defn.QuotedTypeModule_of + || sym == defn.Predef_classOf + || sym == defn.Compiletime_erasedValue && tree.tpe.dealias.isInstanceOf[ConstantType] + || defn.capsErasedValueMethods.contains(sym) + then Pure + else Impure case Apply(fn, args) => - if isPureApply(tree, fn) then - minOf(exprPurity(fn), args.map(exprPurity)) `min` Pure - else if fn.symbol.is(Erased) then - Pure + val factorPurity = minOf(exprPurity(fn), args.map(exprPurity)) + if tree.tpe.isInstanceOf[MethodOrPoly] then // no evaluation + factorPurity `min` Pure + else if isPureApply(tree, fn) then + factorPurity `min` Pure else if fn.symbol.isStableMember /* && fn.symbol.is(Lazy) */ then - minOf(exprPurity(fn), args.map(exprPurity)) `min` Idempotent + factorPurity `min` Idempotent else Impure case Typed(expr, _) => @@ -611,6 +640,15 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => def isPureBinding(tree: Tree)(using Context): Boolean = statPurity(tree) >= Pure + def isPureSyntheticCaseApply(sym: Symbol)(using Context): Boolean = + sym.isAllOf(SyntheticMethod) + && sym.name == nme.apply + && sym.owner.is(Module) + && { + val cls = sym.owner.companionClass + cls.is(Case) && cls.isNoInitsRealClass + } + /** Is the application `tree` with function part `fn` known to be pure? * Function value and arguments can still be impure. */ @@ -622,6 +660,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => tree.tpe.isInstanceOf[ConstantType] && tree.symbol != NoSymbol && isKnownPureOp(tree.symbol) // A constant expression with pure arguments is pure. || fn.symbol.isStableMember && fn.symbol.isConstructor // constructors of no-inits classes are stable + || isPureSyntheticCaseApply(fn.symbol) /** The purity level of this reference. * @return @@ -630,8 +669,6 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * or its type is a constant type * IdempotentPath if reference is lazy and stable * Impure otherwise - * @DarkDimius: need to make sure that lazy accessor methods have Lazy and Stable - * flags set. */ def refPurity(tree: Tree)(using Context): PurityLevel = { val sym = tree.symbol @@ -846,7 +883,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => /** An extractor for def of a closure contained the block of the closure. */ object closureDef { def unapply(tree: Tree)(using Context): Option[DefDef] = tree match { - case Block((meth : DefDef) :: Nil, closure: Closure) if meth.symbol == closure.meth.symbol => + case Block((meth: DefDef) :: Nil, closure: Closure) if meth.symbol == closure.meth.symbol => Some(meth) case Block(Nil, expr) => unapply(expr) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index 414b27101b7d..6dd85d730da8 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -225,6 +225,11 @@ class TreeTypeMap( val tmap1 = tmap.withMappedSyms( origCls(cls).typeParams ::: origDcls, cls.typeParams ::: mappedDcls) + mapped.foreach { sym => + // outer Symbols can reference nested ones in info, + // so we remap that once again with the updated TreeTypeMap + sym.info = tmap1.mapType(sym.info) + } origDcls.lazyZip(mappedDcls).foreach(cls.asClass.replace) tmap1 } diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index fcc257da27e4..8749f7ddc10c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -34,6 +34,9 @@ object Trees { val SyntheticUnit: Property.StickyKey[Unit] = Property.StickyKey() + /** Property key for marking capture-set variables and members */ + val CaptureVar: Property.StickyKey[Unit] = Property.StickyKey() + /** Trees take a parameter indicating what the type of their `tpe` field * is. Two choices: `Type` or `Untyped`. * Untyped trees have type `Tree[Untyped]`. @@ -509,9 +512,9 @@ object Trees { /** The kind of application */ enum ApplyKind: - case Regular // r.f(x) - case Using // r.f(using x) - case InfixTuple // r f (x1, ..., xN) where N != 1; needs to be treated specially for an error message in typedApply + case Regular // r.f(x) + case Using // r.f(using x) + case InfixTuple // r f (x1, ..., xN) where N != 1; needs to be treated specially for an error message in typedApply /** fun(args) */ case class Apply[+T <: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 3432e349fdf6..92c20afe7a73 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -301,7 +301,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { assert(vparams.hasSameLengthAs(tp.paramNames) && vparams.head.isTerm) (vparams.asInstanceOf[List[TermSymbol]], remaining1) case nil => - (tp.paramNames.lazyZip(tp.paramInfos).lazyZip(tp.erasedParams).map(valueParam), Nil) + (tp.paramNames.lazyZip(tp.paramInfos).lazyZip(tp.paramErasureStatuses).map(valueParam), Nil) val (rtp, paramss) = recur(tp.instantiate(vparams.map(_.termRef)), remaining1) (rtp, vparams :: paramss) case _ => @@ -375,7 +375,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { * new parents { termForwarders; typeAliases } * * @param parents a non-empty list of class types - * @param termForwarders a non-empty list of forwarding definitions specified by their name and the definition they forward to. + * @param termForwarders a non-empty list of forwarding definitions specified by their name + * and the definition they forward to. * @param typeMembers a possibly-empty list of type members specified by their name and their right hand side. * @param adaptVarargs if true, allow matching a vararg superclass constructor * with a missing argument in superArgs, and synthesize an @@ -1543,7 +1544,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { * @param selectorPredicate A test to find the selector to use. * @return The symbols imported. */ - def importedSymbols(imp: Import, + def importedSymbols(imp: ImportOrExport, selectorPredicate: untpd.ImportSelector => Boolean = util.common.alwaysTrue) (using Context): List[Symbol] = imp.selectors.find(selectorPredicate) match diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 145c61584fcc..96c8c4c4f845 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -206,7 +206,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Var()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Mutable) - case class Mut()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Mutable) + case class Update()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Mutable) case class Implicit()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Implicit) @@ -236,6 +236,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Tracked()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Tracked) + case class Into()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Into) + /** Used under pureFunctions to mark impure function types `A => B` in `FunctionWithMods` */ case class Impure()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Impure) } @@ -386,6 +388,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { /** Property key for contextual Apply trees of the form `fn given arg` */ val KindOfApply: Property.StickyKey[ApplyKind] = Property.StickyKey() + val RetainsAnnot: Property.StickyKey[Unit] = Property.StickyKey() + // ------ Creation methods for untyped only ----------------- def Ident(name: Name)(implicit src: SourceFile): Ident = new Ident(name) @@ -528,18 +532,32 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { Select(scalaDot(nme.caps), nme.CAPTURE_ROOT) def makeRetaining(parent: Tree, refs: List[Tree], annotName: TypeName)(using Context): Annotated = - Annotated(parent, New(scalaAnnotationDot(annotName), List(refs))) - - def makeCapsOf(tp: RefTree)(using Context): Tree = - TypeApply(capsInternalDot(nme.capsOf), tp :: Nil) - - // Capture set variable `[C^]` becomes: `[C >: CapSet <: CapSet^{cap}]` - def makeCapsBound()(using Context): TypeBoundsTree = - TypeBoundsTree( - Select(scalaDot(nme.caps), tpnme.CapSet), - makeRetaining( - Select(scalaDot(nme.caps), tpnme.CapSet), - Nil, tpnme.retainsCap)) + var annot: Tree = scalaAnnotationDot(annotName) + if annotName == tpnme.retainsCap then + annot = New(annot, Nil) + else + val trefs = + if refs.isEmpty then + // The NothingType is used to represent the empty capture set. + ref(defn.NothingType) + else + // Treat all references as term references before typing. + // A dummy term symbol will be created for each capture variable, + // and references to them will be replaced with the corresponding + // type references during typing. + refs.map(SingletonTypeTree).reduce[Tree]((a, b) => makeOrType(a, b)) + annot = New(AppliedTypeTree(annot, trefs :: Nil), Nil) + annot.putAttachment(RetainsAnnot, ()) + Annotated(parent, annot) + + def makeReachAnnot()(using Context): Tree = + New(ref(defn.ReachCapabilityAnnot.typeRef), Nil :: Nil) + + def makeReadOnlyAnnot()(using Context): Tree = + New(ref(defn.ReadOnlyCapabilityAnnot.typeRef), Nil :: Nil) + + def makeOnlyAnnot(qid: Tree)(using Context) = + New(AppliedTypeTree(ref(defn.OnlyCapabilityAnnot.typeRef), qid :: Nil), Nil :: Nil) def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(using Context): DefDef = DefDef(nme.CONSTRUCTOR, joinParams(tparams, vparamss), TypeTree(), rhs) @@ -563,6 +581,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def makeAndType(left: Tree, right: Tree)(using Context): AppliedTypeTree = AppliedTypeTree(ref(defn.andType.typeRef), left :: right :: Nil) + def makeOrType(left: Tree, right: Tree)(using Context): AppliedTypeTree = + AppliedTypeTree(ref(defn.orType.typeRef), left :: right :: Nil) + def makeParameter(pname: TermName, tpe: Tree, mods: Modifiers, isBackquoted: Boolean = false)(using Context): ValDef = { val vdef = ValDef(pname, tpe, EmptyTree) if (isBackquoted) vdef.pushAttachment(Backquoted, ()) @@ -573,12 +594,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { ValDef(nme.syntheticParamName(n), if (tpt == null) TypeTree() else tpt, EmptyTree) .withFlags(flags) - def isInto(t: Tree)(using Context): Boolean = t match - case PrefixOp(Ident(tpnme.into), _) => true - case Function(_, res) => isInto(res) - case Parens(t) => isInto(t) - case _ => false - def lambdaAbstract(params: List[ValDef] | List[TypeDef], tpt: Tree)(using Context): Tree = params match case Nil => tpt diff --git a/compiler/src/dotty/tools/dotc/cc/CCState.scala b/compiler/src/dotty/tools/dotc/cc/CCState.scala index c92c9faa6fe6..26977691339a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CCState.scala +++ b/compiler/src/dotty/tools/dotc/cc/CCState.scala @@ -3,7 +3,7 @@ package dotc package cc import core.* -import CaptureSet.{CompareResult, CompareFailure, VarState} +import CaptureSet.VarState import collection.mutable import reporting.Message import Contexts.Context @@ -16,24 +16,6 @@ class CCState: // ------ Error diagnostics ----------------------------- - /** Error reprting notes produces since the last call to `test` */ - var notes: List[ErrorNote] = Nil - - def addNote(note: ErrorNote): Unit = - if !notes.exists(_.getClass == note.getClass) then - notes = note :: notes - - def test(op: => CompareResult): CompareResult = - val saved = notes - notes = Nil - try op match - case res: CompareFailure => res.withNotes(notes) - case res => res - finally notes = saved - - def testOK(op: => Boolean): CompareResult = - test(if op then CompareResult.OK else CompareResult.Fail(Nil)) - /** Warnings relating to upper approximations of capture sets with * existentially bound variables. */ @@ -88,7 +70,7 @@ class CCState: // ------ Iteration count of capture checking run - private var iterCount = 1 + private var iterCount = 0 def iterationId = iterCount @@ -96,6 +78,19 @@ class CCState: iterCount += 1 try op finally iterCount -= 1 + def start(): Unit = + iterCount = 1 + + private var mySepCheck = false + + /** Are we currently running separation checks? */ + def isSepCheck = mySepCheck + + def inSepCheck(op: => Unit): Unit = + val saved = mySepCheck + mySepCheck = true + try op finally mySepCheck = saved + // ------ Global counters ----------------------- /** Next CaptureSet.Var id */ @@ -118,6 +113,8 @@ class CCState: private var capIsRoot: Boolean = false + private var collapseFresh: Boolean = false + object CCState: opaque type Level = Int @@ -162,4 +159,21 @@ object CCState: /** Is `caps.cap` a root capability that is allowed to subsume other capabilities? */ def capIsRoot(using Context): Boolean = ccState.capIsRoot + /** Run `op` under the assumption that all FreshCap instances are equal + * to each other and to GlobalCap. + * Needed to make override checking of types containing fresh work. + * Asserted in override checking, tested in maxSubsumes. + * Is this sound? Test case is neg-custom-args/captures/leaked-curried.scala. + */ + inline def withCollapsedFresh[T](op: => T)(using Context): T = + if isCaptureCheckingOrSetup then + val ccs = ccState + val saved = ccs.collapseFresh + ccs.collapseFresh = true + try op finally ccs.collapseFresh = saved + else op + + /** Should all FreshCap instances be treated as equal to GlobalCap? */ + def collapseFresh(using Context): Boolean = ccState.collapseFresh + end CCState diff --git a/compiler/src/dotty/tools/dotc/cc/Capability.scala b/compiler/src/dotty/tools/dotc/cc/Capability.scala new file mode 100644 index 000000000000..c3c743a7c8f5 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/cc/Capability.scala @@ -0,0 +1,1061 @@ +package dotty.tools +package dotc +package cc + +import core.* +import Types.*, Symbols.*, Contexts.*, Decorators.* +import util.{SimpleIdentitySet, EqHashMap} +import typer.ErrorReporting.Addenda +import util.common.alwaysTrue +import scala.collection.mutable +import CCState.* +import Periods.{NoRunId, RunWidth} +import compiletime.uninitialized +import StdNames.nme +import CaptureSet.VarState +import Annotations.Annotation +import Flags.* +import config.Printers.capt +import CCState.{Level, undefinedLevel} +import annotation.constructorOnly +import ast.tpd +import printing.{Printer, Showable} +import printing.Texts.Text +import reporting.{Message, trace} +import NameOps.isImpureFunction +import annotation.internal.sharable + +/** Capabilities are members of capture sets. They partially overlap with types + * as shown in the trait hierarchy below. + * + * Capability --+-- RootCapabilty -----+-- GlobalCap + * | +-- FreshCap + * | +-- ResultCap + * | + * +-- CoreCapability ----+-- ObjectCapability --+-- TermRef + * | | +-- ThisType + * | | +-- TermParamRef + * | | + * | +-- SetCapability -----+-- TypeRef + * | +-- TypeParamRef + * | + * +-- DerivedCapability -+-- Reach + * +-- Only + * +-- ReadOnly + * +-- Maybe + * + * All CoreCapabilities are Types, or, more specifically instances of TypeProxy. + */ +object Capabilities: + opaque type Validity = Int + def validId(runId: Int, iterId: Int): Validity = + runId + (iterId << RunWidth) + def currentId(using Context): Validity = validId(ctx.runId, ccState.iterationId) + val invalid: Validity = validId(NoRunId, 0) + + @sharable private var nextRootId = 0 + + /** The base trait of all root capabilities */ + trait RootCapability extends Capability: + val rootId = nextRootId + nextRootId += 1 + def descr(using Context): String + + /** The base trait of all capabilties represented as types */ + trait CoreCapability extends TypeProxy, Capability: + override def toText(printer: Printer): Text = printer.toText(this) + + trait ObjectCapability extends CoreCapability + + trait SetCapability extends CoreCapability + + trait DerivedCapability extends Capability: + def underlying: Capability + + /** If `x` is a capability, its maybe capability `x?`. `x?` stands for a capability + * `x` that might or might not be part of a capture set. We have `{} <: {x?} <: {x}`. + * Maybe capabilities cannot be propagated between sets. If `a <: b` and `a` + * acquires `x?` then `x` is propagated to `b` as a conservative approximation. + * + * Maybe capabilities should only arise for capture sets that appear in invariant + * position in their surrounding type. They are similar to TypeBounds types, but + * restricted to capture sets. For instance, + * + * Array[C^{x?}] + * + * should be morally equivalent to + * + * Array[_ >: C^{} <: C^{x}] + * + * but it has fewer issues with type inference. + */ + case class Maybe(underlying: Capability) extends DerivedCapability + + /** The readonly capability `x.rd`. We have {x.rd} <: {x}. + * + * Read-only capabilities cannot wrap maybe capabilities + * but they can wrap reach capabilities. We have + * (x?).readOnly = (x.rd)? + */ + case class ReadOnly(underlying: ObjectCapability | RootCapability | Reach | Restricted) + extends DerivedCapability + + /** The restricted capability `x.only[C]`. We have {x.only[C]} <: {x}. + * + * Restricted capabilities cannot wrap maybe capabilities or read-only capabilities + * but they can wrap reach capabilities. We have + * (x?).restrict[T] = (x.restrict[T])? + * (x.rd).restrict[T] = (x.restrict[T]).rd + */ + case class Restricted(underlying: ObjectCapability | RootCapability | Reach, cls: ClassSymbol) + extends DerivedCapability + + /** If `x` is a capability, its reach capability `x*`. `x*` stands for all + * capabilities reachable through `x`. + * We have `{x} <: {x*} <: dcs(x)}` where the deep capture set `dcs(x)` of `x` + * is the union of all capture sets that appear in covariant position in the + * type of `x`. If `x` and `y` are different variables then `{x*}` and `{y*}` + * are unrelated. + * + * Reach capabilities cannot wrap read-only capabilities or maybe capabilities. + * We have + * (x?).reach = (x.reach)? + * (x.rd).reach = (x.reach).rd + * (x.only[T]).reach = (x*).only[T] + */ + case class Reach(underlying: ObjectCapability) extends DerivedCapability + + /** The global root capability referenced as `caps.cap` + * `cap` does not subsume other capabilities, except in arguments of + * `withCapAsRoot` calls. + */ + @sharable // We override below all operations that access internal capability state + object GlobalCap extends RootCapability: + def descr(using Context) = "the universal root capability" + override val maybe = Maybe(this) + override val readOnly = ReadOnly(this) + override def restrict(cls: ClassSymbol)(using Context) = Restricted(this, cls) + override def reach = unsupported("cap.reach") + override def singletonCaptureSet(using Context) = CaptureSet.universal + override def captureSetOfInfo(using Context) = singletonCaptureSet + override def cached[C <: DerivedCapability](newRef: C): C = unsupported("cached") + override def invalidateCaches() = () + + /** The class of "fresh" roots. These do subsume other capabilties in scope. + * They track with hidden sets which other capabilities were subsumed. + * Hidden sets are inspected by separation checking. + * @param owner the owner of the context in which the FreshCap was created + * @param origin an indication where and why the FreshCap was created, used + * for diagnostics + */ + case class FreshCap private (owner: Symbol, origin: Origin)(using @constructorOnly ctx: Context) extends RootCapability: + val hiddenSet = CaptureSet.HiddenSet(owner, this: @unchecked) + // fails initialization check without the @unchecked + + override def equals(that: Any) = that match + case that: FreshCap => this eq that + case _ => false + + def descr(using Context) = + val originStr = origin match + case Origin.InDecl(sym) if sym.exists => + origin.explanation + case _ => + i" created in ${hiddenSet.owner.sanitizedDescription}${origin.explanation}" + val classifierStr = + if hiddenSet.classifier != defn.AnyClass + then i" classified as ${hiddenSet.classifier.name}" + else "" + i"a fresh root capability$classifierStr$originStr" + + object FreshCap: + def apply(origin: Origin)(using Context): FreshCap | GlobalCap.type = + FreshCap(ctx.owner, origin) + + /** A root capability associated with a function type. These are conceptually + * existentially quantified over the function's result type. + * @param binder The function type with which the capability is associated. + * It is a MethodicType since we also have ResultCaps that are + * associated with the ExprTypes of parameterless functions. + * Currently we never create results over PolyTypes. TODO change this? + * Setup: + * + * In the setup phase, `cap` instances in the result of a dependent function type + * or method type such as `(x: T): C^{cap}` are converted to `ResultCap(binder)` instances, + * where `binder` refers to the method type. Most other cap instances are mapped to + * Fresh instances instead. For example the `cap` in the result of `T => C^{cap}` + * is mapped to a Fresh instance. + * + * If one needs to use a dependent function type yet one still want to map `cap` to + * a fresh instance instead an existential root, one can achieve that by the use + * of a type alias. For instance, the following type creates an existential for `^`: + * + * (x: A) => (C^{x}, D^) + * + * By contrast, this variant creates a fresh instance instead: + * + * type F[X] = (x: A) => (C^{x}, X) + * F[D^] + * + * The trick is that the argument D^ is mapped to D^{fresh} before the `F` alias + * is expanded. + */ + case class ResultCap(binder: MethodicType) extends RootCapability: + + private var myOrigin: RootCapability = GlobalCap + private var variants: SimpleIdentitySet[ResultCap] = SimpleIdentitySet.empty + + /** Every ResultCap capability has an origin. This is + * - A FreshCap capability `f`, if the current capability was created as a mirror + * of `f` in the ToResult map. + * - Another ResultCap capability `r`, if the current capability was created + * via a chain of `derivedResult` calls from an original ResultCap `r` + * (which was not created using `derivedResult`). + * - GlobalCap otherwise + */ + def origin: RootCapability = myOrigin + + /** Initialize origin of this capability to a FreshCap instance (or to GlobalCap + * if separation checks are turned off). + * @pre The capability's origin was not yet set. + */ + def setOrigin(freshOrigin: FreshCap | GlobalCap.type): Unit = + assert(myOrigin eq GlobalCap) + myOrigin = freshOrigin + + /** If the current capability was created via a chain of `derivedResult` calls + * from an original ResultCap `r`, that `r`. Otherwise `this`. + */ + def primaryResultCap: ResultCap = origin match + case origin: ResultCap => origin + case _ => this + + def originalBinder: MethodicType = primaryResultCap.binder + + /** A ResultCap with given `binder1` derived from this capability. + * This is typically done as a result of a SubstBinding map. + * ResultCaps so created are cached, so that for every pair + * of a ResultCap `r` and a binder `b`, there exists at most one ResultCap + * instance that is derived transitively from `r` and has binder `b`. + */ + def derivedResult(binder1: MethodicType): ResultCap = + if binder1 eq binder then this + else + val primary = primaryResultCap + primary.variants.iterator.find(_.binder eq binder1) match + case Some(rcap) => rcap + case None => + val rcap = ResultCap(binder1) + rcap.myOrigin = primary + primary.variants += rcap + rcap + + def descr(using Context) = + i"a root capability associated with the result type of $binder" + end ResultCap + + /** A trait for references in CaptureSets. These can be NamedTypes, ThisTypes or ParamRefs, + * as well as three kinds of AnnotatedTypes representing readOnly, reach, and maybe capabilities. + * If there are several annotations they come with an order: + * `*` first, `.only` next, `.rd` next, `?` last. + */ + trait Capability extends Showable: + + private var myCaptureSet: CaptureSet | Null = uninitialized + private var captureSetValid: Validity = invalid + private var mySingletonCaptureSet: CaptureSet.Const | Null = null + private var myDerived: List[DerivedCapability] = Nil + private var myClassifiers: Classifiers = UnknownClassifier + private var classifiersValid: Validity = invalid + + protected def cached[C <: DerivedCapability](newRef: C): C = + def recur(refs: List[DerivedCapability]): C = refs match + case ref :: refs1 => + val exists = ref match + case Restricted(_, cls) => + newRef match + case Restricted(_, newCls) => cls == newCls + case _ => false + case _ => + ref.getClass == newRef.getClass + if exists then ref.asInstanceOf[C] + else recur(refs1) + case Nil => + myDerived = newRef :: myDerived + newRef + recur(myDerived) + + def maybe: Maybe = this match + case self: Maybe => self + case _ => cached(Maybe(this)) + + def readOnly: ReadOnly | Maybe = this match + case Maybe(ref1) => Maybe(ref1.readOnly) + case self: ReadOnly => self + case self: (ObjectCapability | RootCapability | Reach | Restricted) => cached(ReadOnly(self)) + + def restrict(cls: ClassSymbol)(using Context): Restricted | ReadOnly | Maybe = this match + case Maybe(ref1) => Maybe(ref1.restrict(cls)) + case ReadOnly(ref1) => ReadOnly(ref1.restrict(cls).asInstanceOf[Restricted]) + case self @ Restricted(ref1, prevCls) => + val combinedCls = leastClassifier(prevCls, cls) + if combinedCls == prevCls then self + else cached(Restricted(ref1, combinedCls)) + case self: (ObjectCapability | RootCapability | Reach) => cached(Restricted(self, cls)) + + def reach: Reach | Restricted | ReadOnly | Maybe = this match + case Maybe(ref1) => Maybe(ref1.reach) + case ReadOnly(ref1) => ReadOnly(ref1.reach.asInstanceOf[Reach | Restricted]) + case Restricted(ref1, cls) => Restricted(ref1.reach.asInstanceOf[Reach], cls) + case self: Reach => self + case self: ObjectCapability => cached(Reach(self)) + + /** Is this a maybe reference of the form `x?`? */ + final def isMaybe(using Context): Boolean = this ne stripMaybe + + /** Is this a read-only reference of the form `x.rd` or `x.rd?` or a + * capture set variable with only read-ony references in its upper bound? + */ + final def isReadOnly(using Context): Boolean = this match + case tp: SetCapability => tp.captureSetOfInfo.isReadOnly + case _ => this ne stripReadOnly + + final def restriction(using Context): Symbol = this match + case Restricted(_, cls) => cls + case ReadOnly(ref1) => ref1.restriction + case Maybe(ref1) => ref1.restriction + case _ => NoSymbol + + /** Is this a reach reference of the form `x*` or a readOnly or maybe variant + * of a reach reference? + */ + final def isReach(using Context): Boolean = this ne stripReach + + final def stripMaybe(using Context): Capability = this match + case Maybe(ref1) => ref1 + case _ => this + + final def stripReadOnly(using Context): Capability = this match + case ReadOnly(ref1) => ref1 + case Maybe(ref1) => ref1.stripReadOnly.maybe + case _ => this + + /** Drop restrictions with clss `cls` or a superclass of `cls` */ + final def stripRestricted(cls: ClassSymbol)(using Context): Capability = this match + case Restricted(ref1, cls1) if cls.isSubClass(cls1) => ref1 + case ReadOnly(ref1) => ref1.stripRestricted(cls).readOnly + case Maybe(ref1) => ref1.stripRestricted(cls).maybe + case _ => this + + final def stripRestricted(using Context): Capability = + stripRestricted(defn.NothingClass) + + final def stripReach(using Context): Capability = this match + case Reach(ref1) => ref1 + case ReadOnly(ref1) => ref1.stripReach.readOnly + case Restricted(ref1, cls) => ref1.stripReach.restrict(cls) + case Maybe(ref1) => ref1.stripReach.maybe + case _ => this + + /** Is this reference the generic root capability `cap` or a Fresh instance? */ + final def isCapOrFresh(using Context): Boolean = this match + case GlobalCap | _: FreshCap => true + case _ => false + + /** Is this reference a root capability or a derived version of one? + * These capabilities have themselves as their captureSetOfInfo. + */ + final def isTerminalCapability(using Context): Boolean = + core.isInstanceOf[RootCapability] + + /** Is the reference tracked? This is true if it can be tracked and the capture + * set of the underlying type is not always empty. + */ + final def isTracked(using Context): Boolean = this.core match + case _: RootCapability => true + case tp: CoreCapability => tp.isTrackableRef && !captureSetOfInfo.isAlwaysEmpty + + /** An exclusive capability is a capability that derives + * indirectly from a maximal capability without going through + * a read-only capability first. + */ + final def isExclusive(using Context): Boolean = + !isReadOnly && (isTerminalCapability || captureSetOfInfo.isExclusive) + + /** Similar to isExlusive, but also includes capabilties with capture + * set variables in their info whose status is still open. + */ + final def maybeExclusive(using Context): Boolean = + !isReadOnly && (isTerminalCapability || captureSetOfInfo.maybeExclusive) + + final def isWellformed(using Context): Boolean = this match + case self: CoreCapability => self.isTrackableRef + case _ => true + + /** The non-derived capability underlying this capability */ + final def core: CoreCapability | RootCapability = this match + case self: (CoreCapability | RootCapability) => self + case self: DerivedCapability => self.underlying.core + + /** The type underlying this capability, NoType for root capabilities */ + final def coreType: CoreCapability | NoType.type = core match + case c: CoreCapability => c + case _ => NoType + + /** The first element of this path type, skipping selections + * and qualifiers. Note that class parameter references are of + * the form this.C but their pathroot is still this.C, not this. + */ + final def pathRoot(using Context): Capability = this match + case _: RootCapability => this + case self: DerivedCapability => self.underlying.pathRoot + case self: CoreCapability => self.dealias match + case tp1: (TermRef | TypeRef) => // can't use NamedType here since it is not a capability + if tp1.symbol.maybeOwner.isClass && !tp1.symbol.is(TypeParam) then + tp1.prefix match + case pre: Capability => pre.pathRoot + case _ => tp1 + else tp1 + case tp1: CoreCapability => tp1 + case _ => self + + /** The logical owner of the root of this class: + * - If this path starts with `C.this`, the class `C`. + * - If it starts with a reference `r`, `r`'s owner. + * - If it starts with cap, the `scala.caps` package class. + * - If it starts with a fresh instance, its owner. + * - If it starts with a ParamRef or a ResultCap, NoSymbol. + */ + final def pathOwner(using Context): Symbol = pathRoot match + case tp1: ThisType => tp1.cls + case tp1: NamedType => tp1.symbol.owner + case GlobalCap => defn.CapsModule.moduleClass + case tp1: FreshCap => tp1.ccOwner + case _ => NoSymbol + + final def paramPathRoot(using Context): Type = core match + case tp1: NamedType => + tp1.prefix match + case _: ThisType | NoPrefix => + if tp1.symbol.is(Param) || tp1.symbol.is(ParamAccessor) then tp1 + else NoType + case prefix: CoreCapability => prefix.paramPathRoot + case _ => NoType + case tp1: ParamRef => tp1 + case _ => NoType + + final def isParamPath(using Context): Boolean = paramPathRoot.exists + + final def ccOwner(using Context): Symbol = this match + case self: ThisType => self.cls + case TermRef(prefix: Capability, _) => prefix.ccOwner + case self: NamedType => self.symbol + case self: DerivedCapability => self.underlying.ccOwner + case self: FreshCap => self.hiddenSet.owner + case _ /* : GlobalCap | ResultCap | ParamRef */ => NoSymbol + + /** The symbol that represents the level closest-enclosing ccOwner. + * Symbols representing levels are + * - class symbols, but not inner (non-static) module classes + * - method symbols, but not accessors or constructors + */ + final def levelOwner(using Context): Symbol = + def adjust(owner: Symbol): Symbol = + if !owner.exists + || owner.isClass && (!owner.is(Flags.Module) || owner.isStatic) + || owner.is(Flags.Method, butNot = Flags.Accessor) && !owner.isConstructor + then owner + else adjust(owner.owner) + adjust(ccOwner) + + /** Tests whether the capability derives from capability class `cls`. */ + def derivesFromCapTrait(cls: ClassSymbol)(using Context): Boolean = this match + case Reach(ref1) => ref1.widen.derivesFromCapTraitDeeply(cls) + case self: DerivedCapability => self.underlying.derivesFromCapTrait(cls) + case self: CoreCapability => self.superType.derivesFromCapTrait(cls) + case _ => false + + def derivesFromCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_Capability) + def derivesFromMutable(using Context): Boolean = derivesFromCapTrait(defn.Caps_Mutable) + def derivesFromSharable(using Context): Boolean = derivesFromCapTrait(defn.Caps_Sharable) + + /** The capture set consisting of exactly this reference */ + def singletonCaptureSet(using Context): CaptureSet.Const = + if mySingletonCaptureSet == null then + mySingletonCaptureSet = CaptureSet(this) + mySingletonCaptureSet.uncheckedNN + + /** The capture set of the type underlying this reference */ + def captureSetOfInfo(using Context): CaptureSet = + if captureSetValid == currentId then myCaptureSet.nn + else if myCaptureSet.asInstanceOf[AnyRef] eq CaptureSet.Pending then CaptureSet.empty + else + myCaptureSet = CaptureSet.Pending + val computed = CaptureSet.ofInfo(this) + def isProvisional = this.core match + case core: TypeProxy => !core.underlying.exists || core.underlying.isProvisional + case _ => false + if !isCaptureChecking || ctx.mode.is(Mode.IgnoreCaptures) || isProvisional then + myCaptureSet = null + else + myCaptureSet = computed + captureSetValid = currentId + computed + + /** The transitive classifiers of this capability. */ + def transClassifiers(using Context): Classifiers = + def toClassifiers(cls: ClassSymbol): Classifiers = + if cls == defn.AnyClass then Unclassified + else ClassifiedAs(cls :: Nil) + if classifiersValid != currentId then + myClassifiers = this match + case self: FreshCap => + toClassifiers(self.hiddenSet.classifier) + case self: RootCapability => + Unclassified + case Restricted(_, cls) => + assert(cls != defn.AnyClass) + if cls == defn.NothingClass then ClassifiedAs(Nil) + else ClassifiedAs(cls :: Nil) + case ReadOnly(ref1) => + ref1.transClassifiers + case Maybe(ref1) => + ref1.transClassifiers + case Reach(_) => + captureSetOfInfo.transClassifiers + case self: CoreCapability => + joinClassifiers(toClassifiers(self.classifier), captureSetOfInfo.transClassifiers) + if myClassifiers != UnknownClassifier then + classifiersValid == currentId + myClassifiers + end transClassifiers + + def tryClassifyAs(cls: ClassSymbol)(using Context): Boolean = + cls == defn.AnyClass + || this.match + case self: FreshCap => + self.hiddenSet.tryClassifyAs(cls) + case self: RootCapability => + true + case Restricted(_, cls1) => + assert(cls != defn.AnyClass) + cls1.isSubClass(cls) + case ReadOnly(ref1) => + ref1.tryClassifyAs(cls) + case Maybe(ref1) => + ref1.tryClassifyAs(cls) + case Reach(_) => + captureSetOfInfo.tryClassifyAs(cls) + case self: CoreCapability => + self.classifier.isSubClass(cls) + && captureSetOfInfo.tryClassifyAs(cls) + + def isKnownClassifiedAs(cls: ClassSymbol)(using Context): Boolean = + transClassifiers match + case ClassifiedAs(cs) => cs.forall(_.isSubClass(cls)) + case _ => false + + def isKnownEmpty(using Context): Boolean = this match + case Restricted(ref1, cls) => + val isEmpty = ref1.transClassifiers match + case ClassifiedAs(cs) => + cs.forall(c => leastClassifier(c, cls) == defn.NothingClass) + case _ => false + isEmpty || ref1.isKnownEmpty + case ReadOnly(ref1) => ref1.isKnownEmpty + case Maybe(ref1) => ref1.isKnownEmpty + case _ => false + + def invalidateCaches() = + captureSetValid = invalid + classifiersValid = invalid + + /** x subsumes x + * x =:= y ==> x subsumes y + * x subsumes y ==> x subsumes y.f + * x subsumes y ==> x* subsumes y, x subsumes y? + * x subsumes y ==> x* subsumes y*, x? subsumes y? + * x: x1.type /\ x1 subsumes y ==> x subsumes y + * X = CapSet^cx, exists rx in cx, rx subsumes y ==> X subsumes y + * Y = CapSet^cy, forall ry in cy, x subsumes ry ==> x subsumes Y + * X: CapSet^c1...CapSet^c2, (CapSet^c1) subsumes y ==> X subsumes y + * Y: CapSet^c1...CapSet^c2, x subsumes (CapSet^c2) ==> x subsumes Y + * Contains[X, y] ==> X subsumes y + */ + final def subsumes(y: Capability)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = + + /** Are `x` and `y` capabilities such that x subsumes y? */ + def subsumingRefs(x: Type | Capability, y: Type | Capability): Boolean = x match + case x: Capability => y match + case y: Capability => x.subsumes(y) + case _ => false + case _ => false + + /** Perform `test` on all object capabilities in `info` */ + def viaInfo(info: Type)(test: Type => Boolean): Boolean = info.dealias match + case info: ObjectCapability => test(info) + case CapturingType(parent, _) => viaInfo(parent)(test) + case info: AndType => viaInfo(info.tp1)(test) || viaInfo(info.tp2)(test) + case info: OrType => viaInfo(info.tp1)(test) && viaInfo(info.tp2)(test) + case _ => false + + try (this eq y) + || maxSubsumes(y, canAddHidden = !vs.isOpen) + || y.match + case y: TermRef => + y.prefix.match + case ypre: Capability => + this.subsumes(ypre) + || this.match + case x @ TermRef(xpre: Capability, _) if x.symbol == y.symbol => + // To show `{x.f} <:< {y.f}`, it is important to prove `x` and `y` + // are equvalent, which means `x =:= y` in terms of subtyping, + // not just `{x} =:= {y}` in terms of subcapturing. + // It is possible to construct two singleton types `x` and `y`, + // which subsume each other, but are not equal references. + // See `tests/neg-custom-args/captures/path-prefix.scala` for example. + withMode(Mode.IgnoreCaptures): + TypeComparer.isSameRef(xpre, ypre) + case _ => + false + case _ => false + || viaInfo(y.info)(subsumingRefs(this, _)) + case Maybe(y1) => this.stripMaybe.subsumes(y1) + case ReadOnly(y1) => this.stripReadOnly.subsumes(y1) + case Restricted(y1, cls) => this.stripRestricted(cls).subsumes(y1) + case y: TypeRef if y.derivesFrom(defn.Caps_CapSet) => + // The upper and lower bounds don't have to be in the form of `CapSet^{...}`. + // They can be other capture set variables, which are bounded by `CapSet`, + // like `def test[X^, Y^, Z >: X <: Y]`. + y.info match + case TypeBounds(_, hi @ CapturingType(parent, refs)) => + refs.elems.forall(this.subsumes) + case TypeBounds(_, hi: Capability) => + this.subsumes(hi) + case _ => + y.captureSetOfInfo.elems.forall(this.subsumes) + case _ => false + || this.match + case Reach(x1) => x1.subsumes(y.stripReach) + case Restricted(x1, cls) => y.isKnownClassifiedAs(cls) && x1.subsumes(y) + case x: TermRef => viaInfo(x.info)(subsumingRefs(_, y)) + case x: TypeRef if assumedContainsOf(x).contains(y) => true + case x: TypeRef if x.derivesFrom(defn.Caps_CapSet) => + x.info match + case TypeBounds(CapturingType(_, lorefs), _) => + lorefs.elems.exists(_.subsumes(y)) + case TypeBounds(lo: Capability, _) => + lo.subsumes(y) + case _ => + x.captureSetOfInfo.elems.exists(_.subsumes(y)) + case _ => false + catch case ex: AssertionError => + println(i"error while subsumes $this >> $y") + throw ex + end subsumes + + /** This is a maximal capability that subsumes `y` in given context and VarState. + * @param canAddHidden If true we allow maximal capabilities to subsume all other capabilities. + * We add those capabilities to the hidden set if this is a Fresh instance. + * If false we only accept `y` elements that are already in the + * hidden set of this Fresh instance. The idea is that in a VarState that + * accepts additions we first run `maxSubsumes` with `canAddHidden = false` + * so that new variables get added to the sets. If that fails, we run + * the test again with canAddHidden = true as a last effort before we + * fail a comparison. + */ + def maxSubsumes(y: Capability, canAddHidden: Boolean)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = + (this eq y) + || this.match + case x: FreshCap => + def levelOK = + if ccConfig.useFreshLevels && !CCState.collapseFresh then + val yOwner = y.levelOwner + yOwner.isStaticOwner || x.ccOwner.isContainedIn(yOwner) + else y.core match + case ResultCap(_) | _: ParamRef => false + case _ => true + + vs.ifNotSeen(this)(x.hiddenSet.elems.exists(_.subsumes(y))) + || levelOK + && ( y.tryClassifyAs(x.hiddenSet.classifier) + || { capt.println(i"$y cannot be classified as $x"); false } + ) + && canAddHidden + && vs.addHidden(x.hiddenSet, y) + case x: ResultCap => + val result = y match + case y: ResultCap => vs.unify(x, y) + case _ => y.derivesFromSharable + if !result then + TypeComparer.addErrorNote(CaptureSet.ExistentialSubsumesFailure(x, y)) + result + case GlobalCap => + y match + case GlobalCap => true + case _: ResultCap => false + case _: FreshCap if CCState.collapseFresh => true + case _ => + y.derivesFromSharable + || canAddHidden && vs != VarState.HardSeparate && CCState.capIsRoot + case Restricted(x1, cls) => + y.isKnownClassifiedAs(cls) && x1.maxSubsumes(y, canAddHidden) + case _ => + y match + case ReadOnly(y1) => this.stripReadOnly.maxSubsumes(y1, canAddHidden) + case Restricted(y1, cls) => this.stripRestricted(cls).maxSubsumes(y1, canAddHidden) + case _ => false + + /** `x covers y` if we should retain `y` when computing the overlap of + * two footprints which have `x` respectively `y` as elements. + * We assume that .rd have already been stripped on both sides. + * We have: + * + * x covers x + * x covers y ==> x covers y.f + * x covers y ==> x* covers y*, x? covers y? + * TODO what other clauses from subsumes do we need to port here? + */ + final def covers(y: Capability)(using Context): Boolean = + (this eq y) + || y.match + case y @ TermRef(ypre: Capability, _) => + this.covers(ypre) + case Reach(y1) => + this match + case Reach(x1) => x1.covers(y1) + case _ => false + case Maybe(y1) => + this match + case Maybe(x1) => x1.covers(y1) + case _ => false + case y: FreshCap => + y.hiddenSet.superCaps.exists(this covers _) + case _ => + false + + def assumedContainsOf(x: TypeRef)(using Context): SimpleIdentitySet[Capability] = + CaptureSet.assumedContains.getOrElse(x, SimpleIdentitySet.empty) + + /** The type representing this capability. + * Note this method does not distinguish different `RootCapability` instances, + * and should only be used for printing or phases not related to CC. + */ + def toType(using Context): Type = this match + case c: RootCapability => defn.captureRoot.termRef + case c: CoreCapability => c + case c: DerivedCapability => + val c1 = c.underlying.toType + c match + case _: ReadOnly => ReadOnlyCapability(c1) + case Restricted(_, cls) => OnlyCapability(c1, cls) + case _: Reach => ReachCapability(c1) + case _: Maybe => MaybeCapability(c1) + case _ => c1 + + def toText(printer: Printer): Text = printer.toTextCapability(this) + end Capability + + /** Result type of `transClassifiers`. Interprete as follows: + * UnknownClassifier: No list could be computed since some capture sets + * are still unsolved variables + * Unclassified : No set exists since some parts of tcs are not classified + * ClassifiedAs(clss: All parts of tcss are classified with classes in clss + */ + enum Classifiers: + case UnknownClassifier + case Unclassified + case ClassifiedAs(clss: List[ClassSymbol]) + + export Classifiers.{UnknownClassifier, Unclassified, ClassifiedAs} + + /** The least classifier between `cls1` and `cls2`, which are either + * AnyClass, NothingClass, or a class directly extending caps.Classifier. + * @return if one of cls1, cls2 is a subclass of the other, the subclass + * otherwise NothingClass (which is a subclass of all classes) + */ + def leastClassifier(cls1: ClassSymbol, cls2: ClassSymbol)(using Context): ClassSymbol = + if cls1.isSubClass(cls2) then cls1 + else if cls2.isSubClass(cls1) then cls2 + else defn.NothingClass + + def joinClassifiers(cs1: Classifiers, cs2: Classifiers)(using Context): Classifiers = + // Drop classes that subclass classes of the other set + // @param proper If true, only drop proper subclasses of a class of the other set + def filterSub(cs1: List[ClassSymbol], cs2: List[ClassSymbol], proper: Boolean) = + cs1.filter: cls1 => + !cs2.exists: cls2 => + cls1.isSubClass(cls2) && (!proper || cls1 != cls2) + (cs1, cs2) match + case (Unclassified, _) | (_, Unclassified) => Unclassified + case (UnknownClassifier, _) | (_, UnknownClassifier) => UnknownClassifier + case (ClassifiedAs(cs1), ClassifiedAs(cs2)) => + ClassifiedAs(filterSub(cs1, cs2, proper = true) ++ filterSub(cs2, cs1, proper = false)) + + /** The place of - and cause for - creating a fresh capability. Used for + * error diagnostics + */ + enum Origin: + case InDecl(sym: Symbol) + case TypeArg(tp: Type) + case UnsafeAssumePure + case Formal(pref: ParamRef, app: tpd.Apply) + case ResultInstance(methType: Type, meth: Symbol) + case UnapplyInstance(info: MethodType) + case NewMutable(tp: Type) + case NewCapability(tp: Type) + case LambdaExpected(respt: Type) + case LambdaActual(restp: Type) + case OverriddenType(member: Symbol) + case DeepCS(ref: TypeRef) + case Unknown + + def explanation(using Context): String = this match + case InDecl(sym: Symbol) => + if sym.is(Method) then i" in the result type of $sym" + else if sym.exists then i" in the type of $sym" + else "" + case TypeArg(tp: Type) => + i" of type argument $tp" + case UnsafeAssumePure => + " when instantiating argument of unsafeAssumePure" + case Formal(pref, app) => + val meth = app.symbol + if meth.exists + then i" when checking argument to parameter ${pref.paramName} of $meth" + else "" + case ResultInstance(mt, meth) => + val methDescr = if meth.exists then i"$meth's type " else "" + i" when instantiating $methDescr$mt" + case UnapplyInstance(info) => + i" when instantiating argument of unapply with type $info" + case NewMutable(tp) => + i" when constructing mutable $tp" + case NewCapability(tp) => + i" when constructing Capability instance $tp" + case LambdaExpected(respt) => + i" when instantiating expected result type $respt of lambda" + case LambdaActual(restp: Type) => + i" when instantiating result type $restp of lambda" + case OverriddenType(member: Symbol) => + i" when instantiating upper bound of member overridden by $member" + case DeepCS(ref: TypeRef) => + i" when computing deep capture set of $ref" + case Unknown => + "" + end Origin + + // ---------- Maps between different kinds of root capabilities ----------------- + + + /** Map each occurrence of cap to a different Fresh instance + * Exception: CapSet^ stays as it is. + */ + class CapToFresh(origin: Origin)(using Context) extends BiTypeMap, FollowAliasesMap: + thisMap => + + override def apply(t: Type) = + if variance < 0 then t + else t match + case t @ CapturingType(_, _) => + mapOver(t) + case t @ AnnotatedType(parent, ann) => + val parent1 = this(parent) + if ann.symbol.isRetains && ann.tree.toCaptureSet.containsCap then + this(CapturingType(parent1, ann.tree.toCaptureSet)) + else + t.derivedAnnotatedType(parent1, ann) + case defn.RefinedFunctionOf(_) => + t // stop at dependent function types + case _ => + mapFollowingAliases(t) + + override def mapCapability(c: Capability, deep: Boolean): Capability = c match + case GlobalCap => FreshCap(origin) + case _ => super.mapCapability(c, deep) + + override def fuse(next: BiTypeMap)(using Context) = next match + case next: Inverse => assert(false); Some(IdentityTypeMap) + case _ => None + + override def toString = "CapToFresh" + + class Inverse extends BiTypeMap, FollowAliasesMap: + def apply(t: Type): Type = t match + case t @ CapturingType(_, refs) => mapOver(t) + case _ => mapFollowingAliases(t) + + override def mapCapability(c: Capability, deep: Boolean): Capability = c match + case _: FreshCap => GlobalCap + case _ => super.mapCapability(c, deep) + + def inverse = thisMap + override def toString = thisMap.toString + ".inverse" + + lazy val inverse = Inverse() + + end CapToFresh + + /** Maps cap to fresh. CapToFresh is a BiTypeMap since we don't want to + * freeze a set when it is mapped. On the other hand, we do not want Fresh + * values to flow back to cap since that would fail disallowRootCapability + * tests elsewhere. We therefore use `withoutMappedFutureElems` to prevent + * the map being installed for future use. + */ + def capToFresh(tp: Type, origin: Origin)(using Context): Type = + ccState.withoutMappedFutureElems: + CapToFresh(origin)(tp) + + /** Maps fresh to cap */ + def freshToCap(tp: Type)(using Context): Type = + CapToFresh(Origin.Unknown).inverse(tp) + + /** Map top-level free existential variables one-to-one to Fresh instances */ + def resultToFresh(tp: Type, origin: Origin)(using Context): Type = + val subst = new TypeMap: + val seen = EqHashMap[ResultCap, FreshCap | GlobalCap.type]() + var localBinders: SimpleIdentitySet[MethodType] = SimpleIdentitySet.empty + + def apply(t: Type): Type = t match + case t: MethodType => + // skip parameters + val saved = localBinders + if t.marksExistentialScope then localBinders = localBinders + t + try t.derivedLambdaType(resType = this(t.resType)) + finally localBinders = saved + case t: PolyType => + // skip parameters + t.derivedLambdaType(resType = this(t.resType)) + case _ => + mapOver(t) + + override def mapCapability(c: Capability, deep: Boolean) = c match + case c @ ResultCap(binder) => + if localBinders.contains(binder) then c // keep bound references + else seen.getOrElseUpdate(c, FreshCap(origin)) // map free references to FreshCap + case _ => super.mapCapability(c, deep) + end subst + + subst(tp) + end resultToFresh + + /** Replace all occurrences of `cap` (or fresh) in parts of this type by an existentially bound + * variable bound by `mt`. + * Stop at function or method types since these have been mapped before. + */ + def toResult(tp: Type, mt: MethodicType, fail: Message => Unit)(using Context): Type = + + abstract class CapMap extends BiTypeMap: + override def mapOver(t: Type): Type = t match + case t @ FunctionOrMethod(args, res) if variance > 0 && !t.isAliasFun => + t // `t` should be mapped in this case by a different call to `toResult`. See [[toResultInResults]]. + case t: (LazyRef | TypeVar) => + mapConserveSuper(t) + case _ => + super.mapOver(t) + + object toVar extends CapMap: + + def apply(t: Type) = t match + case defn.FunctionNOf(args, res, contextual) if t.typeSymbol.name.isImpureFunction => + if variance > 0 then + super.mapOver: + defn.FunctionNOf(args, res, contextual) + .capturing(ResultCap(mt).singletonCaptureSet) + else mapOver(t) + case _ => + mapOver(t) + + override def mapCapability(c: Capability, deep: Boolean) = c match + case c: (FreshCap | GlobalCap.type) => + if variance > 0 then + val res = ResultCap(mt) + c match + case c: FreshCap => res.setOrigin(c) + case _ => + res + else + if variance == 0 then + fail(em"""$tp captures the root capability `cap` in invariant position. + |This capability cannot be converted to an existential in the result type of a function.""") + // we accept variance < 0, and leave the cap as it is + c + case _ => + super.mapCapability(c, deep) + + //.showing(i"mapcap $t = $result") + override def toString = "toVar" + + object inverse extends BiTypeMap: + def apply(t: Type) = mapOver(t) + + override def mapCapability(c: Capability, deep: Boolean) = c match + case c @ ResultCap(`mt`) => + // do a reverse getOrElseUpdate on `seen` to produce the + // `Fresh` assosicated with `t` + val primary = c.primaryResultCap + primary.origin match + case GlobalCap => + val fresh = FreshCap(Origin.Unknown) + primary.setOrigin(fresh) + fresh + case origin: FreshCap => + origin + case _ => + super.mapCapability(c, deep) + + def inverse = toVar.this + override def toString = "toVar.inverse" + end inverse + end toVar + + toVar(tp) + end toResult + + /** Map global roots in function results to result roots. Also, + * map roots in the types of def methods that are parameterless + * or have only type parameters. + */ + def toResultInResults(sym: Symbol, fail: Message => Unit, keepAliases: Boolean = false)(tp: Type)(using Context): Type = + val m = new TypeMap with FollowAliasesMap: + def apply(t: Type): Type = t match + case AnnotatedType(parent @ defn.RefinedFunctionOf(mt), ann) if ann.symbol == defn.InferredDepFunAnnot => + val mt1 = mapOver(mt).asInstanceOf[MethodType] + if mt1 ne mt then mt1.toFunctionType(alwaysDependent = true) + else parent + case defn.RefinedFunctionOf(mt) => + val mt1 = apply(mt) + if mt1 ne mt then mt1.toFunctionType(alwaysDependent = true) + else t + case t: MethodType if variance > 0 && t.marksExistentialScope => + val t1 = mapOver(t).asInstanceOf[MethodType] + t1.derivedLambdaType(resType = toResult(t1.resType, t1, fail)) + case CapturingType(parent, refs) => + t.derivedCapturingType(this(parent), refs) + case t: (LazyRef | TypeVar) => + mapConserveSuper(t) + case _ => + try + if keepAliases then mapOver(t) + else mapFollowingAliases(t) + catch case ex: AssertionError => + println(i"error while mapping $t") + throw ex + m(tp) match + case tp1: ExprType if sym.is(Method, butNot = Accessor) => + // Map the result of parameterless `def` methods. + tp1.derivedExprType(toResult(tp1.resType, tp1, fail)) + case tp1: PolyType if !tp1.resType.isInstanceOf[MethodicType] => + // Map also the result type of method with only type parameters. + // This way, the `^` in the following method will be mapped to a `ResultCap`: + // ``` + // object Buffer: + // def empty[T]: Buffer[T]^ + // ``` + // This is more desirable than interpreting `^` as a `Fresh` at the level of `Buffer.empty` + // in most cases. + tp1.derivedLambdaType(resType = toResult(tp1.resType, tp1, fail)) + case tp1 => tp1 + end toResultInResults + +end Capabilities diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala index 2be492ed6189..60183126bca2 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala @@ -10,6 +10,7 @@ import Decorators.* import config.Printers.capt import printing.Printer import printing.Texts.Text +import cc.Capabilities.{Capability, RootCapability} /** An annotation representing a capture set and whether it is boxed. * It simulates a normal @retains annotation except that it is more efficient, @@ -38,15 +39,14 @@ case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) exte /** Reconstitute annotation tree from capture set */ override def tree(using Context) = - val elems = refs.elems.toList.map { - case cr: TermRef => ref(cr) - case cr: TermParamRef => untpd.Ident(cr.paramName).withType(cr) - case cr: ThisType => This(cr.cls) - case root(_) => ref(root.cap) - // TODO: Will crash if the type is an annotated type, for example `cap.rd` - } - val arg = repeated(elems, TypeTree(defn.AnyType)) - New(symbol.typeRef, arg :: Nil) + if symbol == defn.RetainsCapAnnot then + New(symbol.typeRef, Nil) + else + val elems = refs.elems.toList.map(_.toType) + val trefs = + if elems.isEmpty then defn.NothingType + else elems.reduce((a, b) => OrType(a, b, soft = false)) + New(AppliedType(symbol.typeRef, trefs :: Nil), Nil) override def symbol(using Context) = cls @@ -63,10 +63,12 @@ case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) exte override def mapWith(tm: TypeMap)(using Context) = val elems = refs.elems.toList - val elems1 = elems.mapConserve(tm) + val elems1 = elems.mapConserve(tm.mapCapability(_)) if elems1 eq elems then this - else if elems1.forall(_.isTrackableRef) - then derivedAnnotation(CaptureSet(elems1.asInstanceOf[List[CaptureRef]]*), boxed) + else if elems1.forall: + case elem1: Capability => elem1.isWellformed + case _ => false + then derivedAnnotation(CaptureSet(elems1.asInstanceOf[List[Capability]]*), boxed) else EmptyAnnotation override def refersToParamOf(tl: TermLambda)(using Context): Boolean = diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index afe0cfb6a8ff..cd8615f0f8d5 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -4,29 +4,36 @@ package cc import core.* import Types.*, Symbols.*, Contexts.*, Annotations.*, Flags.* -import Names.TermName +import Names.{Name, TermName} import ast.{tpd, untpd} import Decorators.*, NameOps.* import config.Printers.capt import util.Property.Key import tpd.* +import Annotations.Annotation import CaptureSet.VarState +import Capabilities.* +import StdNames.nme /** Attachment key for capturing type trees */ private val Captures: Key[CaptureSet] = Key() -/** Context property to print root.Fresh(...) as "fresh" instead of "cap" */ -val PrintFresh: Key[Unit] = Key() - /** Are we at checkCaptures phase? */ def isCaptureChecking(using Context): Boolean = ctx.phaseId == Phases.checkCapturesPhase.id -/** Are we at checkCaptures or Setup phase? */ +/** Are we in the CheckCaptures or Setup phase? */ def isCaptureCheckingOrSetup(using Context): Boolean = - val ccId = Phases.checkCapturesPhase.id - val ctxId = ctx.phaseId - ctxId == ccId || ctxId == ccId - 1 + val ccPhase = Phases.checkCapturesPhase + ccPhase.exists + && { + val ccId = ccPhase.id + val ctxId = ctx.phaseId + ctxId == ccId + || ctxId == ccId - 1 && ccState.iterationId > 0 + // Note: just checking phase id is not enough since Setup would + // also be the phase after pattern matcher. + } /** A dependent function type with given arguments and result type * TODO Move somewhere else where we treat all function type related ops together. @@ -38,38 +45,15 @@ def depFun(args: List[Type], resultType: Type, isContextual: Boolean, paramNames else make(args, resultType) mt.toFunctionType(alwaysDependent = true) -/** An exception thrown if a @retains argument is not syntactically a CaptureRef */ +/** An exception thrown if a @retains argument is not syntactically a Capability */ class IllegalCaptureRef(tpe: Type)(using Context) extends Exception(tpe.show) -/** A base trait for data producing addenda to error messages */ -trait ErrorNote - /** The currently valid CCState */ def ccState(using Context): CCState = Phases.checkCapturesPhase.asInstanceOf[CheckCaptures].ccState1 extension (tree: Tree) - /** Map tree with CaptureRef type to its type, - * map CapSet^{refs} to the `refs` references, - * throw IllegalCaptureRef otherwise - */ - def toCaptureRefs(using Context): List[CaptureRef] = tree match - case ReachCapabilityApply(arg) => - arg.toCaptureRefs.map(_.reach) - case ReadOnlyCapabilityApply(arg) => - arg.toCaptureRefs.map(_.readOnly) - case CapsOfApply(arg) => - arg.toCaptureRefs - case _ => tree.tpe.dealiasKeepAnnots match - case ref: CaptureRef if ref.isTrackableRef => - ref :: Nil - case AnnotatedType(parent, ann) - if ann.symbol.isRetains && parent.derivesFrom(defn.Caps_CapSet) => - ann.tree.toCaptureSet.elems.toList - case tpe => - throw IllegalCaptureRef(tpe) // if this was compiled from cc syntax, problem should have been reported at Typer - /** Convert a @retains or @retainsByName annotation tree to the capture set it represents. * For efficience, the result is cached as an Attachment on the tree. */ @@ -77,28 +61,56 @@ extension (tree: Tree) tree.getAttachment(Captures) match case Some(refs) => refs case None => - val refs = CaptureSet(tree.retainedElems.flatMap(_.toCaptureRefs)*) - //.showing(i"toCaptureSet $tree --> $result", capt) + val refs = CaptureSet(tree.retainedSet.retainedElements*) tree.putAttachment(Captures, refs) refs - /** The arguments of a @retains, @retainsCap or @retainsByName annotation */ - def retainedElems(using Context): List[Tree] = tree match - case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) => - elems - case _ => - if tree.symbol.maybeOwner == defn.RetainsCapAnnot - then ref(root.cap) :: Nil - else Nil + /** The type representing the capture set of @retains, @retainsCap or @retainsByName annotation. */ + def retainedSet(using Context): Type = + tree match + case Apply(TypeApply(_, refs :: Nil), _) => refs.tpe + case _ => + if tree.symbol.maybeOwner == defn.RetainsCapAnnot + then defn.captureRoot.termRef else NoType extension (tp: Type) - /** Is this type a CaptureRef that can be tracked? + def toCapability(using Context): Capability = tp match + case ReachCapability(tp1) => + tp1.toCapability.reach + case ReadOnlyCapability(tp1) => + tp1.toCapability.readOnly + case OnlyCapability(tp1, cls) => + tp1.toCapability.restrict(cls) + case ref: TermRef if ref.isCapRef => + GlobalCap + case ref: Capability if ref.isTrackableRef => + ref + case _ => + // if this was compiled from cc syntax, problem should have been reported at Typer + throw IllegalCaptureRef(tp) + + /** A list of raw elements of a retained set. + * This will not crash even if it contains a non-wellformed Capability. + */ + def retainedElementsRaw(using Context): List[Type] = tp match + case OrType(tp1, tp2) => + tp1.retainedElementsRaw ++ tp2.retainedElementsRaw + case AnnotatedType(tp1, ann) if tp1.derivesFrom(defn.Caps_CapSet) && ann.symbol.isRetains => + ann.tree.retainedSet.retainedElementsRaw + case tp => + // Nothing is a special type to represent the empty set + if tp.isNothingType then Nil + else tp :: Nil // should be checked by wellformedness + + /** A list of capabilities of a retained set. */ + def retainedElements(using Context): List[Capability] = + retainedElementsRaw.map(_.toCapability) + + /** Is this type a Capability that can be tracked? * This is true for * - all ThisTypes and all TermParamRef, * - stable TermRefs with NoPrefix or ThisTypes as prefixes, - * - the root capability `caps.cap` - * - abstract or parameter TypeRefs that derive from caps.CapSet * - annotated types that represent reach or maybe capabilities */ final def isTrackableRef(using Context): Boolean = tp match @@ -108,30 +120,26 @@ extension (tp: Type) || ((tp.prefix eq NoPrefix) || tp.symbol.isField && !tp.symbol.isStatic && tp.prefix.isTrackableRef - || tp.isCap ) && !tp.symbol.isOneOf(UnstableValueFlags) case tp: TypeRef => tp.symbol.isType && tp.derivesFrom(defn.Caps_CapSet) case tp: TypeParamRef => !tp.underlying.exists // might happen during construction of lambdas || tp.derivesFrom(defn.Caps_CapSet) - case root.Result(_) => true - case AnnotatedType(parent, annot) => - defn.capabilityWrapperAnnots.contains(annot.symbol) && parent.isTrackableRef case _ => false /** The capture set of a type. This is: - * - For trackable capture references: The singleton capture set consisting of + * - For object capabilities: The singleton capture set consisting of * just the reference, provided the underlying capture set of their info is not empty. - * - For other capture references: The capture set of their info + * - For other capabilities: The capture set of their info * - For all other types: The result of CaptureSet.ofType */ final def captureSet(using Context): CaptureSet = tp match - case tp: CaptureRef if tp.isTrackableRef => + case tp: CoreCapability if tp.isTrackableRef => val cs = tp.captureSetOfInfo if cs.isAlwaysEmpty then cs else tp.singletonCaptureSet - case tp: SingletonCaptureRef => tp.captureSetOfInfo + case tp: ObjectCapability => tp.captureSetOfInfo case _ => CaptureSet.ofType(tp, followResult = false) /** The deep capture set of a type. This is by default the union of all @@ -144,20 +152,14 @@ extension (tp: Type) val dcs = CaptureSet.ofTypeDeeply(tp.widen.stripCapturing, includeTypevars) if dcs.isAlwaysEmpty then tp.captureSet else tp match - case tp @ ReachCapability(_) => - tp.singletonCaptureSet - case ReadOnlyCapability(ref) => - ref.deepCaptureSet(includeTypevars).readOnly - case tp: SingletonCaptureRef if tp.isTrackableRef => - tp.reach.singletonCaptureSet - case _ => - tp.captureSet ++ dcs + case tp: ObjectCapability if tp.isTrackableRef => tp.reach.singletonCaptureSet + case _ => tp.captureSet ++ dcs def deepCaptureSet(using Context): CaptureSet = deepCaptureSet(includeTypevars = false) /** A type capturing `ref` */ - def capturing(ref: CaptureRef)(using Context): Type = + def capturing(ref: Capability)(using Context): Type = if tp.captureSet.accountsFor(ref) then tp else CapturingType(tp, ref.singletonCaptureSet) @@ -165,7 +167,7 @@ extension (tp: Type) * the two capture sets are combined. */ def capturing(cs: CaptureSet)(using Context): Type = - if (cs.isAlwaysEmpty || cs.isConst && cs.subCaptures(tp.captureSet, VarState.Separate).isOK) + if (cs.isAlwaysEmpty || cs.isConst && cs.subCaptures(tp.captureSet, VarState.Separate)) && !cs.keepAlways then tp else tp match @@ -193,33 +195,6 @@ extension (tp: Type) case _ => tp - /** The first element of this path type. Note that class parameter references - * are of the form this.C but their pathroot is still this.C, not this. - */ - final def pathRoot(using Context): Type = tp.dealias match - case tp1: NamedType - if tp1.symbol.maybeOwner.isClass && tp1.symbol != defn.captureRoot && !tp1.symbol.is(TypeParam) => - tp1.prefix.pathRoot - case tp1 => tp1 - - /** If this part starts with `C.this`, the class `C`. - * Otherwise, if it starts with a reference `r`, `r`'s owner. - * Otherwise NoSymbol. - */ - final def pathOwner(using Context): Symbol = pathRoot match - case tp1: NamedType => tp1.symbol.owner - case tp1: ThisType => tp1.cls - case _ => NoSymbol - - final def isParamPath(using Context): Boolean = tp.dealias match - case tp1: NamedType => - tp1.prefix match - case _: ThisType | NoPrefix => - tp1.symbol.is(Param) || tp1.symbol.is(ParamAccessor) - case prefix => prefix.isParamPath - case _: ParamRef => true - case _ => false - /** If this is a unboxed capturing type with nonempty capture set, its boxed version. * Or, if type is a TypeBounds of capturing types, the version where the bounds are boxed. * The identity for all other types. @@ -232,10 +207,43 @@ extension (tp: Type) case _ => tp + /** If `tp` is an unboxed capturing type or a function returning an unboxed capturing type, + * convert it to be boxed. + */ + def boxDeeply(using Context): Type = + def recur(tp: Type): Type = tp.dealiasKeepAnnotsAndOpaques match + case tp @ CapturingType(parent, refs) => + if tp.isBoxed || parent.derivesFrom(defn.Caps_CapSet) then tp + else tp.boxed + case tp @ AnnotatedType(parent, ann) => + if ann.symbol.isRetains && !parent.derivesFrom(defn.Caps_CapSet) + then CapturingType(parent, ann.tree.toCaptureSet, boxed = true) + else tp.derivedAnnotatedType(parent.boxDeeply, ann) + case tp: (Capability & SingletonType) if tp.isTrackableRef && !tp.isAlwaysPure => + recur(CapturingType(tp, CaptureSet(tp))) + case tp1 @ AppliedType(tycon, args) if defn.isNonRefinedFunction(tp1) => + val res = args.last + val boxedRes = recur(res) + if boxedRes eq res then tp + else tp1.derivedAppliedType(tycon, args.init :+ boxedRes) + case tp1 @ defn.RefinedFunctionOf(rinfo: MethodType) => + val boxedRinfo = recur(rinfo) + if boxedRinfo eq rinfo then tp + else boxedRinfo.toFunctionType(alwaysDependent = true) + case tp1: MethodOrPoly => + val res = tp1.resType + val boxedRes = recur(res) + if boxedRes eq res then tp + else tp1.derivedLambdaType(resType = boxedRes) + case _ => tp + tp match + case tp: MethodOrPoly => tp // don't box results of methods outside refinements + case _ => recur(tp) + /** The capture set consisting of all top-level captures of `tp` that appear under a box. * Unlike for `boxed` this also considers parents of capture types, unions and * intersections, and type proxies other than abstract types. - * Furthermore, if the original type is a capture ref `x`, it replaces boxed universal sets + * Furthermore, if the original type is a capability `x`, it replaces boxed universal sets * on the fly with x*. */ def boxedCaptureSet(using Context): CaptureSet = @@ -244,12 +252,13 @@ extension (tp: Type) val pcs = getBoxed(parent, pre) if !tp.isBoxed then pcs - else if pre.exists && refs.containsRootCapability then - val reachRef = if refs.isReadOnly then pre.reach.readOnly else pre.reach - pcs ++ reachRef.singletonCaptureSet - else - pcs ++ refs - case ref: CaptureRef if ref.isTracked && !pre.exists => getBoxed(ref, ref) + else pre match + case pre: ObjectCapability if refs.containsTerminalCapability => + val reachRef = if refs.isReadOnly then pre.reach.readOnly else pre.reach + pcs ++ reachRef.singletonCaptureSet + case _ => + pcs ++ refs + case ref: Capability if ref.isTracked && !pre.exists => getBoxed(ref, ref) case tp: TypeRef if tp.symbol.isAbstractOrParamType => CaptureSet.empty case tp: TypeProxy => getBoxed(tp.superType, pre) case tp: AndType => getBoxed(tp.tp1, pre) ** getBoxed(tp.tp2, pre) @@ -283,7 +292,7 @@ extension (tp: Type) def forceBoxStatus(boxed: Boolean)(using Context): Type = tp.widenDealias match case tp @ CapturingType(parent, refs) if tp.isBoxed != boxed => val refs1 = tp match - case ref: CaptureRef if ref.isTracked || ref.isReach || ref.isReadOnly => + case ref: Capability if ref.isTracked || ref.isInstanceOf[DerivedCapability] => ref.singletonCaptureSet case _ => refs CapturingType(parent, refs1, boxed) @@ -308,9 +317,9 @@ extension (tp: Type) case tp: (TypeRef | AppliedType) => val sym = tp.typeSymbol if sym.isClass then sym.isPureClass - else tp.superType.isAlwaysPure + else !tp.superType.isAny && tp.superType.isAlwaysPure case tp: TypeProxy => - tp.superType.isAlwaysPure + !tp.superType.isAny && tp.superType.isAlwaysPure case tp: AndType => tp.tp1.isAlwaysPure || tp.tp2.isAlwaysPure case tp: OrType => @@ -324,6 +333,11 @@ extension (tp: Type) && tp.membersBasedOnFlags(Mutable | Method, EmptyFlags) .exists(_.hasAltWith(_.symbol.isUpdateMethod)) + /** Is this a reference to caps.cap? Note this is _not_ the GlobalCap capability. */ + def isCapRef(using Context): Boolean = tp match + case tp: TermRef => tp.name == nme.CAPTURE_ROOT && tp.symbol == defn.captureRoot + case _ => false + /** Knowing that `tp` is a function type, is it an alias to a function other * than `=>`? */ @@ -353,10 +367,6 @@ extension (tp: Type) val sym = tp.typeSymbol if sym.isClass then sym.derivesFrom(cls) else tp.superType.derivesFromCapTrait(cls) - case ReachCapability(tp1) => - tp1.widen.derivesFromCapTraitDeeply(cls) - case ReadOnlyCapability(tp1) => - tp1.derivesFromCapTrait(cls) case tp: (TypeProxy & ValueType) => tp.superType.derivesFromCapTrait(cls) case tp: AndType => @@ -368,7 +378,7 @@ extension (tp: Type) def derivesFromCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_Capability) def derivesFromMutable(using Context): Boolean = derivesFromCapTrait(defn.Caps_Mutable) - def derivesFromSharedCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_SharedCapability) + def derivesFromSharedCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_Sharable) /** Drop @retains annotations everywhere */ def dropAllRetains(using Context): Type = // TODO we should drop retains from inferred types before unpickling @@ -380,108 +390,36 @@ extension (tp: Type) mapOver(t) tm(tp) - /** If `x` is a capture ref, its maybe capability `x?`, represented internally - * as `x @maybeCapability`. `x?` stands for a capability `x` that might or might - * not be part of a capture set. We have `{} <: {x?} <: {x}`. Maybe capabilities - * cannot be propagated between sets. If `a <: b` and `a` acquires `x?` then - * `x` is propagated to `b` as a conservative approximation. - * - * Maybe capabilities should only arise for capture sets that appear in invariant - * position in their surrounding type. They are similar to TypeBunds types, but - * restricted to capture sets. For instance, - * - * Array[C^{x?}] - * - * should be morally equivalent to - * - * Array[_ >: C^{} <: C^{x}] - * - * but it has fewer issues with type inference. - */ - def maybe(using Context): CaptureRef = tp match - case tp @ AnnotatedType(_, annot) if annot.symbol == defn.MaybeCapabilityAnnot => tp - case _ => MaybeCapability(tp) - - /** If `x` is a capture ref, its reach capability `x*`, represented internally - * as `x @reachCapability`. `x*` stands for all capabilities reachable through `x`". - * We have `{x} <: {x*} <: dcs(x)}` where the deep capture set `dcs(x)` of `x` - * is the union of all capture sets that appear in covariant position in the - * type of `x`. If `x` and `y` are different variables then `{x*}` and `{y*}` - * are unrelated. - * - * Reach capabilities cannot wrap read-only capabilities or maybe capabilities. - * We have - * (x.rd).reach = x*.rd - * (x.rd)? = (x*)? - */ - def reach(using Context): CaptureRef = tp match - case tp @ AnnotatedType(tp1: CaptureRef, annot) - if annot.symbol == defn.MaybeCapabilityAnnot => - tp1.reach.maybe - case tp @ AnnotatedType(tp1: CaptureRef, annot) - if annot.symbol == defn.ReadOnlyCapabilityAnnot => - tp1.reach.readOnly - case tp @ AnnotatedType(tp1: CaptureRef, annot) - if annot.symbol == defn.ReachCapabilityAnnot => - tp - case _ => - ReachCapability(tp) - - /** If `x` is a capture ref, its read-only capability `x.rd`, represented internally - * as `x @readOnlyCapability`. We have {x.rd} <: {x}. If `x` is a reach capability `y*`, - * then its read-only version is `x.rd*`. - * - * Read-only capabilities cannot wrap maybe capabilities - * but they can wrap reach capabilities. We have - * (x?).readOnly = (x.rd)? - */ - def readOnly(using Context): CaptureRef = tp match - case tp @ AnnotatedType(tp1: CaptureRef, annot) - if annot.symbol == defn.MaybeCapabilityAnnot => - tp1.readOnly.maybe - case tp @ AnnotatedType(tp1: CaptureRef, annot) - if annot.symbol == defn.ReadOnlyCapabilityAnnot => - tp - case _ => - ReadOnlyCapability(tp) - - /** If `x` is a capture ref, replace all no-flip covariant occurrences of `cap` + /** If `x` is a capability, replace all no-flip covariant occurrences of `cap` * in type `tp` with `x*`. */ - def withReachCaptures(ref: Type)(using Context): Type = - object narrowCaps extends TypeMap: - var change = false - def apply(t: Type) = - if variance <= 0 then t - else t.dealias match - case t @ CapturingType(p, cs) if cs.containsRootCapability => - change = true - val reachRef = if cs.isReadOnly then ref.reach.readOnly else ref.reach - t.derivedCapturingType(apply(p), reachRef.singletonCaptureSet) - case t @ AnnotatedType(parent, ann) => - // Don't map annotations, which includes capture sets - t.derivedAnnotatedType(this(parent), ann) - case t @ FunctionOrMethod(args, res) => - if args.forall(_.isAlwaysPure) then - // Also map existentials in results to reach capabilities if all - // preceding arguments are known to be always pure - t.derivedFunctionOrMethod(args, apply(root.resultToFresh(res))) - else - t - case _ => - mapOver(t) - end narrowCaps - - ref match - case ref: CaptureRef if ref.isTrackableRef => - val tp1 = narrowCaps(tp) - if narrowCaps.change then - capt.println(i"narrow $tp of $ref to $tp1") - tp1 - else - tp - case _ => + def withReachCaptures(ref: Type)(using Context): Type = ref match + case ref: ObjectCapability if ref.isTrackableRef => + object narrowCaps extends TypeMap: + var change = false + def apply(t: Type) = + if variance <= 0 then t + else t.dealias match + case t @ CapturingType(p, cs) if cs.containsCapOrFresh => + change = true + val reachRef = if cs.isReadOnly then ref.reach.readOnly else ref.reach + t.derivedCapturingType(apply(p), reachRef.singletonCaptureSet) + case t @ AnnotatedType(parent, ann) => + // Don't map annotations, which includes capture sets + t.derivedAnnotatedType(this(parent), ann) + case t @ FunctionOrMethod(args, res) => + t.derivedFunctionOrMethod(args, apply(res)) + case _ => + mapOver(t) + end narrowCaps + val tp1 = narrowCaps(tp) + if narrowCaps.change then + capt.println(i"narrow $tp of $ref to $tp1") + tp1 + else tp + case _ => + tp end withReachCaptures /** Does this type contain no-flip covariant occurrences of `cap`? */ @@ -499,11 +437,36 @@ extension (tp: Type) foldOver(x, t) acc(false, tp) - def level(using Context): CCState.Level = - tp match - case tp: TermRef => ccState.symLevel(tp.symbol) - case tp: ThisType => ccState.symLevel(tp.cls).nextInner - case _ => CCState.undefinedLevel + def refinedOverride(name: Name, rinfo: Type)(using Context): Type = + RefinedType(tp, name, + AnnotatedType(rinfo, Annotation(defn.RefineOverrideAnnot, util.Spans.NoSpan))) + + def dropUseAndConsumeAnnots(using Context): Type = + tp.dropAnnot(defn.UseAnnot).dropAnnot(defn.ConsumeAnnot) + + /** If `tp` is a function or method, a type of the same kind with the given + * argument and result types. + */ + def derivedFunctionOrMethod(argTypes: List[Type], resType: Type)(using Context): Type = tp match + case tp @ AppliedType(tycon, args) if defn.isNonRefinedFunction(tp) => + val args1 = argTypes :+ resType + if args.corresponds(args1)(_ eq _) then tp + else tp.derivedAppliedType(tycon, args1) + case tp @ defn.RefinedFunctionOf(rinfo) => + val rinfo1 = rinfo.derivedFunctionOrMethod(argTypes, resType) + if rinfo1 eq rinfo then tp + else if rinfo1.isInstanceOf[PolyType] then tp.derivedRefinedType(refinedInfo = rinfo1) + else rinfo1.toFunctionType(alwaysDependent = true) + case tp: MethodType => + tp.derivedLambdaType(paramInfos = argTypes, resType = resType) + case tp: PolyType => + assert(argTypes.isEmpty) + tp.derivedLambdaType(resType = resType) + case _ => + tp + + def classifier(using Context): ClassSymbol = + tp.classSymbols.map(_.classifier).foldLeft(defn.AnyClass)(leastClassifier) extension (tp: MethodType) /** A method marks an existential scope unless it is the prefix of a curried method */ @@ -518,7 +481,7 @@ extension (cls: ClassSymbol) || bc.is(CaptureChecked) && bc.givenSelfType.dealiasKeepAnnots.match case CapturingType(_, refs) => refs.isAlwaysEmpty - case RetainingType(_, refs) => refs.isEmpty + case RetainingType(_, refs) => refs.retainedElements.isEmpty case selfType => isCaptureChecking // At Setup we have not processed self types yet, so // unless a self type is explicitly given, we can't tell @@ -536,6 +499,16 @@ extension (cls: ClassSymbol) val selfType = bc.givenSelfType bc.is(CaptureChecked) && selfType.exists && selfType.captureSet.elems == refs.elems + def isClassifiedCapabilityClass(using Context): Boolean = + cls.derivesFrom(defn.Caps_Capability) && cls.parentSyms.contains(defn.Caps_Classifier) + + def classifier(using Context): ClassSymbol = + if cls.derivesFrom(defn.Caps_Capability) then + cls.baseClasses + .filter(_.parentSyms.contains(defn.Caps_Classifier)) + .foldLeft(defn.AnyClass)(leastClassifier) + else defn.AnyClass + extension (sym: Symbol) /** This symbol is one of `retains` or `retainsCap` */ @@ -560,15 +533,13 @@ extension (sym: Symbol) /** Does this symbol allow results carrying the universal capability? * Currently this is true only for function type applies (since their - * results are unboxed) and `erasedValue` since this function is magic in - * that is allows to conjure global capabilies from nothing (aside: can we find a - * more controlled way to achieve this?). + * results are unboxed) and `caps.{$internal,unsafe}.erasedValue` since + * these function are magic in that they allow to conjure global capabilies from nothing. * But it could be generalized to other functions that so that they can take capability * classes as arguments. */ def allowsRootCapture(using Context): Boolean = - sym == defn.Compiletime_erasedValue - || defn.isFunctionClass(sym.maybeOwner) + defn.capsErasedValueMethods.contains(sym) || defn.isFunctionClass(sym.maybeOwner) /** When applying `sym`, would the result type be unboxed? * This is the case if the result type contains a top-level reference to an enclosing @@ -600,11 +571,12 @@ extension (sym: Symbol) def hasTrackedParts(using Context): Boolean = !CaptureSet.ofTypeDeeply(sym.info).isAlwaysEmpty - /** `sym` is annotated @use or it is a type parameter with a matching + /** `sym` itself or its info is annotated @use or it is a type parameter with a matching * @use-annotated term parameter that contains `sym` in its deep capture set. */ def isUseParam(using Context): Boolean = sym.hasAnnotation(defn.UseAnnot) + || sym.info.hasAnnotation(defn.UseAnnot) || sym.is(TypeParam) && sym.owner.rawParamss.nestedExists: param => param.is(TermParam) && param.hasAnnotation(defn.UseAnnot) @@ -612,6 +584,11 @@ extension (sym: Symbol) case c: TypeRef => c.symbol == sym case _ => false + /** `sym` or its info is annotated with `@consume`. */ + def isConsumeParam(using Context): Boolean = + sym.hasAnnotation(defn.ConsumeAnnot) + || sym.info.hasAnnotation(defn.ConsumeAnnot) + def isUpdateMethod(using Context): Boolean = sym.isAllOf(Mutable | Method, butNot = Accessor) @@ -628,89 +605,51 @@ extension (tp: AnnotatedType) case ann: CaptureAnnotation => ann.boxed case _ => false - def rootAnnot: root.Annot = (tp.annot: @unchecked) match - case ann: root.Annot => ann - /** Drop retains annotations in the type. */ class CleanupRetains(using Context) extends TypeMap: def apply(tp: Type): Type = tp match case AnnotatedType(tp, annot) if annot.symbol == defn.RetainsAnnot || annot.symbol == defn.RetainsByNameAnnot => - RetainingType(tp, Nil, byName = annot.symbol == defn.RetainsByNameAnnot) + RetainingType(tp, defn.NothingType, byName = annot.symbol == defn.RetainsByNameAnnot) case _ => mapOver(tp) -/** A typemap that follows aliases and keeps their transformed results if - * there is a change. +/** A base class for extractors that match annotated types with a specific + * Capability annotation. */ -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. - */ -object ReachCapabilityApply: - def unapply(tree: Apply)(using Context): Option[Tree] = tree match - case Apply(reach, arg :: Nil) if reach.symbol == defn.Caps_reachCapability => Some(arg) - case _ => None - -/** An extractor for `caps.readOnlyCapability(ref)`, which is used to express a read-only - * capability as a tree in a @retains annotation. - */ -object ReadOnlyCapabilityApply: - def unapply(tree: Apply)(using Context): Option[Tree] = tree match - case Apply(ro, arg :: Nil) if ro.symbol == defn.Caps_readOnlyCapability => Some(arg) - case _ => None - -/** An extractor for `caps.capsOf[X]`, which is used to express a generic capture set - * as a tree in a @retains annotation. - */ -object CapsOfApply: - def unapply(tree: TypeApply)(using Context): Option[Tree] = tree match - case TypeApply(capsOf, arg :: Nil) if capsOf.symbol == defn.Caps_capsOf => Some(arg) - case _ => None - abstract class AnnotatedCapability(annotCls: Context ?=> ClassSymbol): def apply(tp: Type)(using Context): AnnotatedType = - assert(tp.isTrackableRef, i"not a trackable ref: $tp") - tp match - case AnnotatedType(_, annot) => - assert(!unwrappable.contains(annot.symbol), i"illegal combination of derived capabilities: $annotCls over ${annot.symbol}") - case _ => - tp match - case tp: CaptureRef => tp.derivedRef(annotCls) - case _ => AnnotatedType(tp, Annotation(annotCls, util.Spans.NoSpan)) + AnnotatedType(tp, Annotation(annotCls, util.Spans.NoSpan)) - def unapply(tree: AnnotatedType)(using Context): Option[CaptureRef] = tree match - case AnnotatedType(parent: CaptureRef, ann) if ann.hasSymbol(annotCls) => Some(parent) + def unapply(tree: AnnotatedType)(using Context): Option[Type] = tree match + case AnnotatedType(parent: Type, ann) if ann.hasSymbol(annotCls) => Some(parent) case _ => None - - protected def unwrappable(using Context): Set[Symbol] end AnnotatedCapability -/** An extractor for `ref @maybeCapability`, which is used to express - * the maybe capability `ref?` as a type. - */ -object MaybeCapability extends AnnotatedCapability(defn.MaybeCapabilityAnnot): - protected def unwrappable(using Context) = Set() - /** An extractor for `ref @readOnlyCapability`, which is used to express * the read-only capability `ref.rd` as a type. */ -object ReadOnlyCapability extends AnnotatedCapability(defn.ReadOnlyCapabilityAnnot): - protected def unwrappable(using Context) = Set(defn.MaybeCapabilityAnnot) +object ReadOnlyCapability extends AnnotatedCapability(defn.ReadOnlyCapabilityAnnot) -/** An extractor for `ref @annotation.internal.reachCapability`, which is used to express +/** An extractor for `ref @reachCapability`, which is used to express * the reach capability `ref*` as a type. */ -object ReachCapability extends AnnotatedCapability(defn.ReachCapabilityAnnot): - protected def unwrappable(using Context) = Set(defn.MaybeCapabilityAnnot, defn.ReadOnlyCapabilityAnnot) +object ReachCapability extends AnnotatedCapability(defn.ReachCapabilityAnnot) + +/** An extractor for `ref @amaybeCapability`, which is used to express + * the maybe capability `ref?` as a type. + */ +object MaybeCapability extends AnnotatedCapability(defn.MaybeCapabilityAnnot) + +object OnlyCapability: + def apply(tp: Type, cls: ClassSymbol)(using Context): AnnotatedType = + AnnotatedType(tp, + Annotation(defn.OnlyCapabilityAnnot.typeRef.appliedTo(cls.typeRef), Nil, util.Spans.NoSpan)) + + def unapply(tree: AnnotatedType)(using Context): Option[(Type, ClassSymbol)] = tree match + case AnnotatedType(parent: Type, ann) if ann.hasSymbol(defn.OnlyCapabilityAnnot) => + Some((parent, ann.tree.tpe.argTypes.head.classSymbol.asClass)) + case _ => None +end OnlyCapability /** An extractor for all kinds of function types as well as method and poly types. * It includes aliases of function types such as `=>`. TODO: Can we do without? @@ -725,28 +664,6 @@ object FunctionOrMethod: case defn.RefinedFunctionOf(rinfo) => unapply(rinfo) case _ => None -/** If `tp` is a function or method, a type of the same kind with the given - * argument and result types. - */ -extension (self: Type) - def derivedFunctionOrMethod(argTypes: List[Type], resType: Type)(using Context): Type = self match - case self @ AppliedType(tycon, args) if defn.isNonRefinedFunction(self) => - val args1 = argTypes :+ resType - if args.corresponds(args1)(_ eq _) then self - else self.derivedAppliedType(tycon, args1) - case self @ defn.RefinedFunctionOf(rinfo) => - val rinfo1 = rinfo.derivedFunctionOrMethod(argTypes, resType) - if rinfo1 eq rinfo then self - else if rinfo1.isInstanceOf[PolyType] then self.derivedRefinedType(refinedInfo = rinfo1) - else rinfo1.toFunctionType(alwaysDependent = true) - case self: MethodType => - self.derivedLambdaType(paramInfos = argTypes, resType = resType) - case self: PolyType => - assert(argTypes.isEmpty) - self.derivedLambdaType(resType = resType) - case _ => - self - /** An extractor for a contains argument */ object ContainsImpl: def unapply(tree: TypeApply)(using Context): Option[(Tree, Tree)] = @@ -759,11 +676,14 @@ object ContainsImpl: /** An extractor for a contains parameter */ object ContainsParam: - def unapply(sym: Symbol)(using Context): Option[(TypeRef, CaptureRef)] = + def unapply(sym: Symbol)(using Context): Option[(TypeRef, Capability)] = sym.info.dealias match - case AppliedType(tycon, (cs: TypeRef) :: (ref: CaptureRef) :: Nil) + case AppliedType(tycon, (cs: TypeRef) :: arg2 :: Nil) if tycon.typeSymbol == defn.Caps_ContainsTrait - && cs.typeSymbol.isAbstractOrParamType => Some((cs, ref)) + && cs.typeSymbol.isAbstractOrParamType => + arg2.stripCapturing match // ref.type was converted to box ref.type^{ref} by boxing + case ref: Capability => Some((cs, ref)) + case _ => None case _ => None /** A class encapsulating the assumulator logic needed for `CaptureSet.ofTypeDeeply` @@ -784,16 +704,15 @@ abstract class DeepTypeAccumulator[T](using Context) extends TypeAccumulator[T]: def apply(acc: T, t: Type) = if variance < 0 then acc else t.dealias match - case t @ CapturingType(p, cs1) => - capturingCase(acc, p, cs1) + case t @ CapturingType(parent, cs) => + capturingCase(acc, parent, cs) case t: TypeRef if t.symbol.isAbstractOrParamType && !seen.contains(t.symbol) => seen += t.symbol abstractTypeCase(acc, t, t.info.bounds.hi) case AnnotatedType(parent, _) => this(acc, parent) case t @ FunctionOrMethod(args, res) => - if args.forall(_.isAlwaysPure) then this(acc, root.resultToFresh(res)) - else acc + this(acc, res) case _ => foldOver(acc, t) end DeepTypeAccumulator diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala deleted file mode 100644 index 98f1502a0c1c..000000000000 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ /dev/null @@ -1,330 +0,0 @@ -package dotty.tools -package dotc -package cc - -import core.* -import Types.*, Symbols.*, Contexts.*, Decorators.* -import util.{SimpleIdentitySet, Property} -import typer.ErrorReporting.Addenda -import util.common.alwaysTrue -import scala.collection.mutable -import CCState.* -import Periods.{NoRunId, RunWidth} -import compiletime.uninitialized -import StdNames.nme -import CaptureSet.VarState -import Annotations.Annotation -import config.Printers.capt - -object CaptureRef: - opaque type Validity = Int - def validId(runId: Int, iterId: Int): Validity = - runId + (iterId << RunWidth) - def currentId(using Context): Validity = validId(ctx.runId, ccState.iterationId) - val invalid: Validity = validId(NoRunId, 0) - -/** A trait for references in CaptureSets. These can be NamedTypes, ThisTypes or ParamRefs, - * as well as three kinds of AnnotatedTypes representing readOnly, reach, and maybe capabilities. - * If there are several annotations they come with an order: - * `*` first, `.rd` next, `?` last. - */ -trait CaptureRef extends TypeProxy, ValueType: - import CaptureRef.* - - private var myCaptureSet: CaptureSet | Null = uninitialized - private var myCaptureSetValid: Validity = invalid - private var mySingletonCaptureSet: CaptureSet.Const | Null = null - private var myDerivedRefs: List[AnnotatedType] = Nil - - /** A derived reach, readOnly or maybe reference. Derived references are cached. */ - def derivedRef(annotCls: ClassSymbol)(using Context): AnnotatedType = - def recur(refs: List[AnnotatedType]): AnnotatedType = refs match - case ref :: refs1 => - if ref.annot.symbol == annotCls then ref else recur(refs1) - case Nil => - val derived = AnnotatedType(this, Annotation(annotCls, util.Spans.NoSpan)) - myDerivedRefs = derived :: myDerivedRefs - derived - recur(myDerivedRefs) - - /** Is the reference tracked? This is true if it can be tracked and the capture - * set of the underlying type is not always empty. - */ - final def isTracked(using Context): Boolean = - this.isTrackableRef && (isRootCapability || !captureSetOfInfo.isAlwaysEmpty) - - /** Is this a maybe reference of the form `x?`? */ - final def isMaybe(using Context): Boolean = this ne stripMaybe - - /** Is this a read-only reference of the form `x.rd` or a capture set variable - * with only read-ony references in its upper bound? - */ - final def isReadOnly(using Context): Boolean = this match - case tp: TypeRef => tp.captureSetOfInfo.isReadOnly - case _ => this ne stripReadOnly - - /** Is this a reach reference of the form `x*`? */ - final def isReach(using Context): Boolean = this ne stripReach - - final def stripMaybe(using Context): CaptureRef = this match - case AnnotatedType(tp1: CaptureRef, annot) if annot.symbol == defn.MaybeCapabilityAnnot => - tp1 - case _ => - this - - final def stripReadOnly(using Context): CaptureRef = this match - case tp @ AnnotatedType(tp1: CaptureRef, annot) => - val sym = annot.symbol - if sym == defn.ReadOnlyCapabilityAnnot then - tp1 - else if sym == defn.MaybeCapabilityAnnot then - tp.derivedAnnotatedType(tp1.stripReadOnly, annot) - else - this - case _ => - this - - final def stripReach(using Context): CaptureRef = this match - case tp @ AnnotatedType(tp1: CaptureRef, annot) => - val sym = annot.symbol - if sym == defn.ReachCapabilityAnnot then - tp1 - else if sym == defn.ReadOnlyCapabilityAnnot || sym == defn.MaybeCapabilityAnnot then - tp.derivedAnnotatedType(tp1.stripReach, annot) - else - this - case _ => - this - - /** Is this reference the generic root capability `cap` ? */ - final def isCap(using Context): Boolean = this match - case tp: TermRef => tp.name == nme.CAPTURE_ROOT && tp.symbol == defn.captureRoot - case _ => false - - /** Is this reference a Fresh instance? */ - final def isFresh(using Context): Boolean = this match - case root.Fresh(_) => true - case _ => false - - /** Is this reference the generic root capability `cap` or a Fresh instance? */ - final def isCapOrFresh(using Context): Boolean = isCap || isFresh - - /** Is this reference one of the generic root capabilities `cap` or `cap.rd` ? */ - final def isRootCapability(using Context): Boolean = this match - case ReadOnlyCapability(tp1) => tp1.isRootCapability - case root(_) => true - case _ => isCap - - /** An exclusive capability is a capability that derives - * indirectly from a maximal capability without going through - * a read-only capability first. - */ - final def isExclusive(using Context): Boolean = - !isReadOnly && (isRootCapability || captureSetOfInfo.isExclusive) - - // With the support of paths, we don't need to normalize the `TermRef`s anymore. - // /** Normalize reference so that it can be compared with `eq` for equality */ - // final def normalizedRef(using Context): CaptureRef = this match - // case tp @ AnnotatedType(parent: CaptureRef, annot) if tp.isTrackableRef => - // tp.derivedAnnotatedType(parent.normalizedRef, annot) - // case tp: TermRef if tp.isTrackableRef => - // tp.symbol.termRef - // case _ => this - - /** The capture set consisting of exactly this reference */ - final def singletonCaptureSet(using Context): CaptureSet.Const = - if mySingletonCaptureSet == null then - mySingletonCaptureSet = CaptureSet(this) - mySingletonCaptureSet.uncheckedNN - - /** The capture set of the type underlying this reference */ - final def captureSetOfInfo(using Context): CaptureSet = - if myCaptureSetValid == currentId then myCaptureSet.nn - else if myCaptureSet.asInstanceOf[AnyRef] eq CaptureSet.Pending then CaptureSet.empty - else - myCaptureSet = CaptureSet.Pending - val computed = CaptureSet.ofInfo(this) - if !isCaptureChecking - || ctx.mode.is(Mode.IgnoreCaptures) - || !underlying.exists - || underlying.isProvisional - then - myCaptureSet = null - else - myCaptureSet = computed - myCaptureSetValid = currentId - computed - - final def invalidateCaches() = - myCaptureSetValid = invalid - - /** x subsumes x - * x =:= y ==> x subsumes y - * x subsumes y ==> x subsumes y.f - * x subsumes y ==> x* subsumes y, x subsumes y? - * x subsumes y ==> x* subsumes y*, x? subsumes y? - * x: x1.type /\ x1 subsumes y ==> x subsumes y - * X = CapSet^cx, exists rx in cx, rx subsumes y ==> X subsumes y - * Y = CapSet^cy, forall ry in cy, x subsumes ry ==> x subsumes Y - * X: CapSet^c1...CapSet^c2, (CapSet^c1) subsumes y ==> X subsumes y - * Y: CapSet^c1...CapSet^c2, x subsumes (CapSet^c2) ==> x subsumes Y - * Contains[X, y] ==> X subsumes y - */ - final def subsumes(y: CaptureRef)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = - - def subsumingRefs(x: Type, y: Type): Boolean = x match - case x: CaptureRef => y match - case y: CaptureRef => x.subsumes(y) - case _ => false - case _ => false - - def viaInfo(info: Type)(test: Type => Boolean): Boolean = info.dealias match - case info: SingletonCaptureRef => test(info) - case CapturingType(parent, _) => - if this.derivesFrom(defn.Caps_CapSet) then test(info) - /* - 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). - */ - else viaInfo(parent)(test) - case info: AndType => viaInfo(info.tp1)(test) || viaInfo(info.tp2)(test) - case info: OrType => viaInfo(info.tp1)(test) && viaInfo(info.tp2)(test) - case _ => false - - (this eq y) - || maxSubsumes(y, canAddHidden = !vs.isOpen) - || y.match - case y: TermRef if !y.isCap => - y.prefix.match - case ypre: CaptureRef => - this.subsumes(ypre) - || this.match - case x @ TermRef(xpre: CaptureRef, _) if x.symbol == y.symbol => - // To show `{x.f} <:< {y.f}`, it is important to prove `x` and `y` - // are equvalent, which means `x =:= y` in terms of subtyping, - // not just `{x} =:= {y}` in terms of subcapturing. - // It is possible to construct two singleton types `x` and `y`, - // which subsume each other, but are not equal references. - // See `tests/neg-custom-args/captures/path-prefix.scala` for example. - withMode(Mode.IgnoreCaptures) {TypeComparer.isSameRef(xpre, ypre)} - case _ => - false - case _ => false - || viaInfo(y.info)(subsumingRefs(this, _)) - case MaybeCapability(y1) => this.stripMaybe.subsumes(y1) - case ReadOnlyCapability(y1) => this.stripReadOnly.subsumes(y1) - case y: TypeRef if y.derivesFrom(defn.Caps_CapSet) => - // The upper and lower bounds don't have to be in the form of `CapSet^{...}`. - // They can be other capture set variables, which are bounded by `CapSet`, - // like `def test[X^, Y^, Z >: X <: Y]`. - 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) || 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 - case ReachCapability(x1) => x1.subsumes(y.stripReach) - case x: TermRef => viaInfo(x.info)(subsumingRefs(_, y)) - case x: TypeRef if assumedContainsOf(x).contains(y) => true - case x: TypeRef if x.derivesFrom(defn.Caps_CapSet) => - x.info match - case TypeBounds(lo: CaptureRef, _) => - lo.subsumes(y) - case _ => - x.captureSetOfInfo.elems.exists(_.subsumes(y)) - case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) => - refs.elems.exists(_.subsumes(y)) - case _ => false - end subsumes - - /** This is a maximal capability that subsumes `y` in given context and VarState. - * @param canAddHidden If true we allow maximal capabilities to subsume all other capabilities. - * We add those capabilities to the hidden set if this is a Fresh instance. - * If false we only accept `y` elements that are already in the - * hidden set of this Fresh instance. The idea is that in a VarState that - * accepts additions we first run `maxSubsumes` with `canAddHidden = false` - * so that new variables get added to the sets. If that fails, we run - * the test again with canAddHidden = true as a last effort before we - * fail a comparison. - */ - def maxSubsumes(y: CaptureRef, canAddHidden: Boolean)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = - def yIsExistential = y.stripReadOnly match - case root.Result(_) => - capt.println(i"failed existential $this >: $y") - true - case _ => false - (this eq y) - || this.match - case root.Fresh(hidden) => - vs.ifNotSeen(this)(hidden.elems.exists(_.subsumes(y))) - || !y.stripReadOnly.isCap - && !yIsExistential - && !y.isInstanceOf[TermParamRef] - && canAddHidden - && vs.addHidden(hidden, y) - case x @ root.Result(binder) => - val result = y match - case y @ root.Result(_) => vs.unify(x, y) - case _ => y.derivesFromSharedCapability - if !result then - ccState.addNote(CaptureSet.ExistentialSubsumesFailure(x, y)) - result - case _ => - y match - case ReadOnlyCapability(y1) => this.stripReadOnly.maxSubsumes(y1, canAddHidden) - case _ if this.isCap => - y.isCap - || y.derivesFromSharedCapability - || !yIsExistential - && canAddHidden - && vs != VarState.HardSeparate - && (CCState.capIsRoot - // || { println(i"no longer $this maxSubsumes $y, ${y.isCap}"); false } // debug - ) - || false - case _ => - false - - /** `x covers y` if we should retain `y` when computing the overlap of - * two footprints which have `x` respectively `y` as elements. - * We assume that .rd have already been stripped on both sides. - * We have: - * - * x covers x - * x covers y ==> x covers y.f - * x covers y ==> x* covers y*, x? covers y? - * TODO what other clauses from subsumes do we need to port here? - */ - final def covers(y: CaptureRef)(using Context): Boolean = - (this eq y) - || y.match - case y @ TermRef(ypre: CaptureRef, _) if !y.isCap => - this.covers(ypre) - case ReachCapability(y1) => - this match - case ReachCapability(x1) => x1.covers(y1) - case _ => false - case MaybeCapability(y1) => - this match - case MaybeCapability(x1) => x1.covers(y1) - case _ => false - case root.Fresh(hidden) => - hidden.superCaps.exists(this covers _) - case _ => - false - - def assumedContainsOf(x: TypeRef)(using Context): SimpleIdentitySet[CaptureRef] = - CaptureSet.assumedContains.getOrElse(x, SimpleIdentitySet.empty) - -end CaptureRef - -trait SingletonCaptureRef extends SingletonType, CaptureRef - diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 93ca0956baed..bdb7a774ca51 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -14,11 +14,12 @@ import printing.{Showable, Printer} import printing.Texts.* import util.{SimpleIdentitySet, Property} import typer.ErrorReporting.Addenda -import util.common.alwaysTrue import scala.collection.{mutable, immutable} +import TypeComparer.ErrorNote import CCState.* import TypeOps.AvoidMap import compiletime.uninitialized +import Capabilities.* /** A class for capture sets. Capture sets can be constants or variables. * Capture sets support inclusion constraints <:< where <:< is subcapturing. @@ -31,23 +32,35 @@ import compiletime.uninitialized * That is, constraints can be of the forms * * cs1 <:< cs2 - * cs1 = ∪ {f(x) | x ∈ cs2} where f is a function from capture references to capture sets. - * cs1 = ∪ {x | x ∈ cs2, p(x)} where p is a predicate on capture references + * cs1 = ∪ {f(x) | x ∈ cs2} where f is a function from capabilities to capture sets. + * cs1 = ∪ {x | x ∈ cs2, p(x)} where p is a predicate on capabilities * cs1 = cs2 ∩ cs2 * * We call the resulting constraint system "monadic set constraints". * To support capture propagation across maps, mappings are supported only * if the mapped function is either a bijection or if it is idempotent - * on capture references (c.f. doc comment on `map` below). + * on capabilities (c.f. doc comment on `map` below). */ sealed abstract class CaptureSet extends Showable: import CaptureSet.* + import Mutability.* /** The elements of this capture set. For capture variables, * the elements known so far. */ def elems: Refs + protected var myMut : Mutability = Ignored + + /** The access kind of this CaptureSet. */ + def mutability(using Context): Mutability = myMut + + def mutability_=(x: Mutability): Unit = + myMut = x + + /** Mark this capture set as belonging to a Mutable type. */ + def setMutable()(using Context): Unit + /** Is this capture set constant (i.e. not an unsolved capture variable)? * Solved capture variables count as constant. */ @@ -70,9 +83,20 @@ sealed abstract class CaptureSet extends Showable: */ def owner: Symbol + /** If this set is a variable: Drop capabilities that are known to be empty + * This is called during separation checking so that capabilities that turn + * out to be always empty because of conflicting clasisifiers don't contribute + * to peaks. We can't do it before that since classifiers are set during + * capture checking. + */ + def dropEmpties()(using Context): this.type + /** Is this capture set definitely non-empty? */ final def isNotEmpty: Boolean = !elems.isEmpty + /** If this is a Var, its `id`, otherwise -1 */ + def maybeId: Int = -1 + /** Convert to Const. @pre: isConst */ def asConst(using Context): Const = this match case c: Const => c @@ -92,23 +116,51 @@ sealed abstract class CaptureSet extends Showable: /** Does this capture set contain the root reference `cap` as element? */ final def isUniversal(using Context) = - elems.exists(_.isCap) + elems.contains(GlobalCap) /** Does this capture set contain a root reference `cap` or `cap.rd` as element? */ - final def containsRootCapability(using Context) = - elems.exists(_.isRootCapability) + final def containsTerminalCapability(using Context) = + elems.exists(_.isTerminalCapability) + + /** Does this capture set contain a ResultCap element? */ + final def containsResultCapability(using Context) = + elems.exists(_.core.isInstanceOf[ResultCap]) + + /** Does this capture set contain a GlobalCap or FreshCap, and at the same time + * does not contain a ResultCap? + */ + final def containsCapOrFresh(using Context) = + !containsResultCapability + && elems.exists: elem => + elem.core match + case GlobalCap => true + case _: FreshCap => true + case _ => false final def containsCap(using Context) = - elems.exists(_.stripReadOnly.isCap) + elems.exists(_.core eq GlobalCap) final def isReadOnly(using Context): Boolean = elems.forall(_.isReadOnly) + final def isAlwaysReadOnly(using Context): Boolean = isConst && isReadOnly + final def isExclusive(using Context): Boolean = elems.exists(_.isExclusive) + /** Similar to isExlusive, but also includes capture set variables + * with unknown status. + */ + final def maybeExclusive(using Context): Boolean = reporting.trace(i"mabe exclusive $this"): + if isConst then elems.exists(_.maybeExclusive) + else mutability != ReadOnly + final def keepAlways: Boolean = this.isInstanceOf[EmptyWithProvenance] + def failWith(fail: TypeComparer.ErrorNote)(using Context): false = + TypeComparer.addErrorNote(fail) + false + /** Try to include an element in this capture set. * @param elem The element to be added * @param origin The set that originated the request, or `empty` if the request came from outside. @@ -128,14 +180,15 @@ sealed abstract class CaptureSet extends Showable: * element is not the root capability, try instead to include its underlying * capture set. */ - protected def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = - if accountsFor(elem) then CompareResult.OK - else addNewElem(elem) + protected def tryInclude(elem: Capability, origin: CaptureSet)(using Context, VarState): Boolean = reporting.trace(i"try include $elem in $this # ${maybeId}"): + accountsFor(elem) || addNewElem(elem) /** Try to include all element in `refs` to this capture set. */ - protected final def tryInclude(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = - (CompareResult.OK /: newElems): (r, elem) => - r.andAlso(tryInclude(elem, origin)) + protected final def tryInclude(newElems: Refs, origin: CaptureSet)(using Context, VarState): Boolean = + newElems.forall(tryInclude(_, origin)) + + protected def mutableToReader(origin: CaptureSet)(using Context): Boolean = + if mutability == Mutable then toReader() else true /** Add an element to this capture set, assuming it is not already accounted for, * and omitting any mapping or filtering. @@ -144,34 +197,32 @@ sealed abstract class CaptureSet extends Showable: * element is not the root capability, try instead to include its underlying * capture set. */ - protected final def addNewElem(elem: CaptureRef)(using ctx: Context, vs: VarState): CompareResult = - if elem.isRootCapability || !vs.isOpen then - addThisElem(elem) - else - addThisElem(elem).orElse: - val underlying = elem.captureSetOfInfo - tryInclude(underlying.elems, this).andAlso: - underlying.addDependent(this) - CompareResult.OK - - /** Add new elements one by one using `addNewElem`, abort on first failure */ - protected final def addNewElems(newElems: Refs)(using Context, VarState): CompareResult = - (CompareResult.OK /: newElems): (r, elem) => - r.andAlso(addNewElem(elem)) + protected final def addNewElem(elem: Capability)(using ctx: Context, vs: VarState): Boolean = + addThisElem(elem) + || !elem.isTerminalCapability + && vs.isOpen + && { + val underlying = elem.captureSetOfInfo + val res = tryInclude(underlying.elems, this) + if res then underlying.addDependent(this) + res + } /** Add a specific element, assuming it is not already accounted for, * and omitting any mapping or filtering, without possibility to backtrack * to the underlying capture set. */ - protected def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult + protected def addThisElem(elem: Capability)(using Context, VarState): Boolean - protected def addIfHiddenOrFail(elem: CaptureRef)(using ctx: Context, vs: VarState): CompareResult = - if elems.exists(_.maxSubsumes(elem, canAddHidden = true)) - then CompareResult.OK - else CompareResult.Fail(this :: Nil) + protected def toReader()(using Context): Boolean + + protected def addIfHiddenOrFail(elem: Capability)(using ctx: Context, vs: VarState): Boolean = + elems.exists(_.maxSubsumes(elem, canAddHidden = true)) + || elem.isKnownEmpty + || failWith(IncludeFailure(this, elem)) /** If this is a variable, add `cs` as a dependent set */ - protected def addDependent(cs: CaptureSet)(using Context, VarState): CompareResult + protected def addDependent(cs: CaptureSet)(using Context, VarState): Boolean /** If `cs` is a variable, add this capture set as one of its dependent sets */ protected def addAsDependentTo(cs: CaptureSet)(using Context): this.type = @@ -181,19 +232,22 @@ sealed abstract class CaptureSet extends Showable: /** {x} <:< this where <:< is subcapturing, but treating all variables * as frozen. */ - def accountsFor(x: CaptureRef)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = + def accountsFor(x: Capability)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = - def debugInfo(using Context) = i"$this accountsFor $x, which has capture set ${x.captureSetOfInfo}" + def debugInfo(using Context) = + val suffix = if ctx.settings.YccVerbose.value then i" with ${x.captureSetOfInfo}" else "" + i"$this accountsFor $x$suffix" def test(using Context) = reporting.trace(debugInfo): - elems.exists(_.subsumes(x)) - || // Even though subsumes already follows captureSetOfInfo, this is not enough. - // For instance x: C^{y, z}. Then neither y nor z subsumes x but {y, z} accounts for x. - !x.isRootCapability - && !x.derivesFrom(defn.Caps_CapSet) - && !(vs.isSeparating && x.captureSetOfInfo.containsRootCapability) - // in VarState.Separate, don't try to widen to cap since that might succeed with {cap} <: {cap} - && x.captureSetOfInfo.subCaptures(this, VarState.Separate).isOK + TypeComparer.noNotes: // Any failures in accountsFor should not lead to error notes + elems.exists(_.subsumes(x)) + || // Even though subsumes already follows captureSetOfInfo, this is not enough. + // For instance x: C^{y, z}. Then neither y nor z subsumes x but {y, z} accounts for x. + !x.isTerminalCapability + && !x.coreType.derivesFrom(defn.Caps_CapSet) + && !(vs.isSeparating && x.captureSetOfInfo.containsTerminalCapability) + // in VarState.Separate, don't try to widen to cap since that might succeed with {cap} <: {cap} + && x.captureSetOfInfo.subCaptures(this, VarState.Separate) comparer match case comparer: ExplainingTypeComparer => comparer.traceIndented(debugInfo)(test) @@ -207,11 +261,12 @@ sealed abstract class CaptureSet extends Showable: * a set `cs` might account for `x` only if it subsumes `x` or it contains the * root capability `cap`. */ - def mightAccountFor(x: CaptureRef)(using Context): Boolean = + def mightAccountFor(x: Capability)(using Context): Boolean = reporting.trace(i"$this mightAccountFor $x, ${x.captureSetOfInfo}?", show = true): - CCState.withCapAsRoot: // OK here since we opportunistically choose an alternative which gets checked later - elems.exists(_.subsumes(x)(using ctx)(using VarState.ClosedUnrecorded)) - || !x.isRootCapability + CCState.withCollapsedFresh: // OK here since we opportunistically choose an alternative which gets checked later + TypeComparer.noNotes: + elems.exists(_.subsumes(x)(using ctx)(using VarState.ClosedUnrecorded)) + || !x.isTerminalCapability && { val elems = x.captureSetOfInfo.elems !elems.isEmpty && elems.forall(mightAccountFor) @@ -227,34 +282,42 @@ sealed abstract class CaptureSet extends Showable: && !that.elems.forall(this.mightAccountFor) /** The subcapturing test, taking an explicit VarState. */ - final def subCaptures(that: CaptureSet, vs: VarState)(using Context): CompareResult = + final def subCaptures(that: CaptureSet, vs: VarState)(using Context): Boolean = subCaptures(that)(using ctx, vs) /** The subcapturing test, using a given VarState */ - final def subCaptures(that: CaptureSet)(using ctx: Context, vs: VarState = VarState()): CompareResult = - val result = that.tryInclude(elems, this) - if result.isOK then - addDependent(that) - else - result.levelError.foreach(ccState.addNote) - varState.rollBack() - result - //.showing(i"subcaptures $this <:< $that = ${result.show}", capt) + final def subCaptures(that: CaptureSet)(using ctx: Context, vs: VarState = VarState()): Boolean = + TypeComparer.inNestedLevel: + val this1 = this.adaptMutability(that) + if this1 == null then false + else if this1 ne this then + capt.println(i"WIDEN ro $this with ${this.mutability} <:< $that with ${that.mutability} to $this1") + this1.subCaptures(that, vs) + else + that.tryInclude(elems, this) && addDependent(that) /** Two capture sets are considered =:= equal if they mutually subcapture each other * in a frozen state. */ def =:= (that: CaptureSet)(using Context): Boolean = - this.subCaptures(that, VarState.Separate).isOK - && that.subCaptures(this, VarState.Separate).isOK + this.subCaptures(that, VarState.Separate) + && that.subCaptures(this, VarState.Separate) + + def adaptMutability(that: CaptureSet)(using Context): CaptureSet | Null = + val m1 = this.mutability + val m2 = that.mutability + if m1 == Mutable && m2 == Reader then this.readOnly + else if m1 == Reader && m2 == Mutable then + if that.toReader() then this else null + else this /** The smallest capture set (via <:<) that is a superset of both * `this` and `that` */ def ++ (that: CaptureSet)(using Context): CaptureSet = - if this.subCaptures(that, VarState.HardSeparate).isOK then + if this.subCaptures(that, VarState.HardSeparate) then if that.isAlwaysEmpty && this.keepAlways then this else that - else if that.subCaptures(this, VarState.HardSeparate).isOK then this + else if that.subCaptures(this, VarState.HardSeparate) then this else if this.isConst && that.isConst then Const(this.elems ++ that.elems) else Union(this, that) @@ -263,14 +326,14 @@ sealed abstract class CaptureSet extends Showable: /** The smallest superset (via <:<) of this capture set that also contains `ref`. */ - def + (ref: CaptureRef)(using Context): CaptureSet = + def + (ref: Capability)(using Context): CaptureSet = this ++ ref.singletonCaptureSet /** The largest capture set (via <:<) that is a subset of both `this` and `that` */ def **(that: CaptureSet)(using Context): CaptureSet = - if this.subCaptures(that, VarState.Closed()).isOK then this - else if that.subCaptures(this, VarState.Closed()).isOK then that + if this.subCaptures(that, VarState.Closed()) then this + else if that.subCaptures(this, VarState.Closed()) then that else if this.isConst && that.isConst then Const(elemIntersection(this, that)) else Intersection(this, that) @@ -285,13 +348,13 @@ sealed abstract class CaptureSet extends Showable: if that.isAlwaysEmpty then this else Diff(asVar, that) /** The largest subset (via <:<) of this capture set that does not account for `ref` */ - def - (ref: CaptureRef)(using Context): CaptureSet = + def - (ref: Capability)(using Context): CaptureSet = this -- ref.singletonCaptureSet /** The largest subset (via <:<) of this capture set that only contains elements * for which `p` is true. */ - def filter(p: Context ?=> CaptureRef => Boolean)(using Context): CaptureSet = + def filter(p: Context ?=> Capability => Boolean)(using Context): CaptureSet = if this.isConst then val elems1 = elems.filter(p) if elems1 == elems then this @@ -312,13 +375,13 @@ sealed abstract class CaptureSet extends Showable: * will also map any elements added in the future to themselves. This assumption * can be tested to hold by setting the ccConfig.checkSkippedMaps setting to true. * - If the map is some other map that does not map all elements to themselves, - * freeze the current set (i.e. make it porvisionally solved) and return + * freeze the current set (i.e. make it provisionally solved) and return * the mapped elements as a constant set. */ def map(tm: TypeMap)(using Context): CaptureSet = tm match case tm: BiTypeMap => - val mappedElems = elems.map(tm.forward) + val mappedElems = elems.map(tm.mapCapability(_)) if isConst then if mappedElems == elems then this else Const(mappedElems) @@ -349,17 +412,59 @@ sealed abstract class CaptureSet extends Showable: def maybe(using Context): CaptureSet = map(MaybeMap()) - def readOnly(using Context): CaptureSet = map(ReadOnlyMap()) + def restrict(cls: ClassSymbol)(using Context): CaptureSet = map(RestrictMap(cls)) + + def readOnly(using Context): CaptureSet = + val res = map(ReadOnlyMap()) + if mutability != Ignored then res.mutability = Reader + res + + def transClassifiers(using Context): Classifiers = + def elemClassifiers = + (ClassifiedAs(Nil) /: elems.map(_.transClassifiers))(joinClassifiers) + if ccState.isSepCheck then + dropEmpties() + elemClassifiers + else if isConst then + elemClassifiers + else + UnknownClassifier + + def tryClassifyAs(cls: ClassSymbol)(using Context): Boolean = + elems.forall(_.tryClassifyAs(cls)) - /** Invoke handler if this set has (or later aquires) the root capability `cap` */ - def disallowRootCapability(handler: () => Context ?=> Unit)(using Context): this.type = - if containsRootCapability then handler() + def adoptClassifier(cls: ClassSymbol)(using Context): Unit = + for elem <- elems do + elem.stripReadOnly match + case fresh: FreshCap => fresh.hiddenSet.adoptClassifier(cls) + case _ => + + /** A bad root `elem` is inadmissible as a member of this set. What is a bad roots depends + * on the value of `rootLimit`. + * If the limit is null, all capture roots are good. + * If the limit is NoSymbol, all Fresh roots are good, but cap and Result roots are bad. + * If the limit is some other symbol, cap and Result roots are bad, as well as + * all Fresh roots that are contained (via ccOwner) in `rootLimit`. + */ + protected def isBadRoot(rootLimit: Symbol | Null, elem: Capability)(using Context): Boolean = + if rootLimit == null then false + else elem.core match + case GlobalCap | _: ResultCap => true + case elem: FreshCap => elem.ccOwner.isContainedIn(rootLimit) + case _ => false + + /** Invoke `handler` if this set has (or later aquires) a root capability. + * Excluded are Fresh instances unless their ccOwner is contained in `upto`. + * If `upto` is NoSymbol, all Fresh instances are admitted. + */ + def disallowRootCapability(upto: Symbol)(handler: () => Context ?=> Unit)(using Context): this.type = + if elems.exists(isBadRoot(upto, _)) then handler() this /** Invoke handler on the elements to ensure wellformedness of the capture set. * The handler might add additional elements to the capture set. */ - def ensureWellformed(handler: CaptureRef => Context ?=> Unit)(using Context): this.type = + def ensureWellformed(handler: Capability => Context ?=> Unit)(using Context): this.type = elems.foreach(handler(_)) this @@ -401,46 +506,60 @@ sealed abstract class CaptureSet extends Showable: def processElems[T](f: Refs => T): T = f(elems) object CaptureSet: - type Refs = SimpleIdentitySet[CaptureRef] + type Refs = SimpleIdentitySet[Capability] type Vars = SimpleIdentitySet[Var] type Deps = SimpleIdentitySet[CaptureSet] + enum Mutability: + case Mutable, Reader, Ignored + + def | (that: Mutability): Mutability = + if this == that then this + else if this == Ignored || that == Ignored then Ignored + else if this == Reader || that == Reader then Reader + else Mutable + + def & (that: Mutability): Mutability = + if this == that then this + else if this == Ignored then that + else if that == Ignored then this + else if this == Reader then that + else this + + end Mutability + import Mutability.* + /** If set to `true`, capture stack traces that tell us where sets are created */ private final val debugSets = false val emptyRefs: Refs = SimpleIdentitySet.empty /** The empty capture set `{}` */ + @sharable // sharable since the set is empty, so setMutable is a no-op val empty: CaptureSet.Const = Const(emptyRefs) /** The universal capture set `{cap}` */ - def universal(using Context): CaptureSet = - root.cap.singletonCaptureSet - - /** The same as CaptureSet.universal but generated implicitly for - * references of Capability subtypes - */ - def universalImpliedByCapability(using Context) = - defn.universalCSImpliedByCapability + def universal(using Context): Const = + Const(SimpleIdentitySet(GlobalCap)) - def fresh(owner: Symbol = NoSymbol)(using Context): CaptureSet = - root.Fresh.withOwner(owner).singletonCaptureSet + def fresh(origin: Origin)(using Context): Const = + FreshCap(origin).singletonCaptureSet /** The shared capture set `{cap.rd}` */ - def shared(using Context): CaptureSet = - root.cap.readOnly.singletonCaptureSet + def shared(using Context): Const = + GlobalCap.readOnly.singletonCaptureSet /** Used as a recursion brake */ @sharable private[dotc] val Pending = Const(SimpleIdentitySet.empty) - def apply(elems: CaptureRef*)(using Context): CaptureSet.Const = + def apply(elems: Capability*)(using Context): Const = if elems.isEmpty then empty else for elem <- elems do - assert(elem.isTrackableRef, i"not a trackable ref: $elem") + assert(elem.isWellformed, i"not a trackable ref: $elem") Const(SimpleIdentitySet(elems*)) - def apply(elems: Refs)(using Context): CaptureSet.Const = + def apply(elems: Refs)(using Context): Const = if elems.isEmpty then empty else Const(elems) /** The subclass of constant capture sets with given elements `elems` */ @@ -449,13 +568,17 @@ object CaptureSet: def isAlwaysEmpty(using Context) = elems.isEmpty def isProvisionallySolved(using Context) = false - def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult = - val res = addIfHiddenOrFail(elem) - if !res.isOK && this.isProvisionallySolved then - println(i"Cannot add $elem to provisionally solved $this") - res + def addThisElem(elem: Capability)(using Context, VarState): Boolean = + addIfHiddenOrFail(elem) + || { + if this.isProvisionallySolved then + capt.println(i"Cannot add $elem to provisionally solved $this") + false + } + + def toReader()(using Context) = failWith(MutAdaptFailure(this)) - def addDependent(cs: CaptureSet)(using Context, VarState) = CompareResult.OK + def addDependent(cs: CaptureSet)(using Context, VarState) = true def upperApprox(origin: CaptureSet)(using Context): CaptureSet = this @@ -465,26 +588,46 @@ object CaptureSet: def owner = NoSymbol + def dropEmpties()(using Context) = this + + private var isComplete = true + + def setMutable()(using Context): Unit = + if !elems.isEmpty then + isComplete = false // delay computation of Mutability status + + override def mutability(using Context): Mutability = + if !isComplete then + myMut = if maybeExclusive then Mutable else Reader + isComplete = true + myMut + override def toString = elems.toString end Const - case class EmptyWithProvenance(ref: CaptureRef, mapped: Type) extends Const(SimpleIdentitySet.empty): + case class EmptyWithProvenance(ref: Capability, mapped: CaptureSet) extends Const(SimpleIdentitySet.empty): override def optionalInfo(using Context): String = if ctx.settings.YccDebug.value then i" under-approximating the result of mapping $ref to $mapped" else "" + /* The same as {cap.rd} but generated implicitly for references of Capability subtypes. + */ + case class CSImpliedByCapability() extends Const(SimpleIdentitySet(GlobalCap.readOnly)) + /** A special capture set that gets added to the types of symbols that were not * themselves capture checked, in order to admit arbitrary corresponding capture * sets in subcapturing comparisons. Similar to platform types for explicit * nulls, this provides more lenient checking against compilation units that * were not yet compiled with capture checking on. */ + @sharable // sharable since the set is empty, so setMutable is a no-op object Fluid extends Const(emptyRefs): override def isAlwaysEmpty(using Context) = false - override def addThisElem(elem: CaptureRef)(using Context, VarState) = CompareResult.OK - override def accountsFor(x: CaptureRef)(using Context)(using VarState): Boolean = true - override def mightAccountFor(x: CaptureRef)(using Context): Boolean = true + override def addThisElem(elem: Capability)(using Context, VarState) = true + override def toReader()(using Context) = true + override def accountsFor(x: Capability)(using Context)(using VarState): Boolean = true + override def mightAccountFor(x: Capability)(using Context): Boolean = true override def toString = "" end Fluid @@ -499,7 +642,9 @@ object CaptureSet: ccs.varId += 1 ccs.varId - //assert(id != 40) + override def maybeId = id + + //assert(id != 8, this) /** A variable is solved if it is aproximated to a from-then-on constant set. * Interpretation: @@ -520,77 +665,110 @@ object CaptureSet: */ var deps: Deps = SimpleIdentitySet.empty + def setMutable()(using Context): Unit = + mutability = Mutable + def isConst(using Context) = solved >= ccState.iterationId def isAlwaysEmpty(using Context) = isConst && elems.isEmpty def isProvisionallySolved(using Context): Boolean = solved > 0 && solved != Int.MaxValue def isMaybeSet = false // overridden in BiMapped + private var emptiesDropped = false + + def dropEmpties()(using Context): this.type = + if !emptiesDropped then + emptiesDropped = true + for elem <- elems do + if elem.isKnownEmpty then + elems -= empty + this + /** A handler to be invoked if the root reference `cap` is added to this set */ var rootAddedHandler: () => Context ?=> Unit = () => () - private[CaptureSet] var noUniversal = false + /** The limit deciding which capture roots are bad (i.e. cannot be contained in this set). + * @see isBadRoot for details. + */ + private[CaptureSet] var rootLimit: Symbol | Null = null - /** A handler to be invoked when new elems are added to this set */ - var newElemAddedHandler: CaptureRef => Context ?=> Unit = _ => () + private var myClassifier: ClassSymbol = defn.AnyClass + def classifier: ClassSymbol = myClassifier - var description: String = "" + private def narrowClassifier(cls: ClassSymbol)(using Context): Unit = + val newClassifier = leastClassifier(classifier, cls) + if newClassifier == defn.NothingClass then + println(i"conflicting classifications for $this, was $classifier, now $cls") + myClassifier = newClassifier - /** Record current elements in given VarState provided it does not yet - * contain an entry for this variable. - */ - private def recordElemsState()(using VarState): Boolean = - varState.getElems(this) match - case None => varState.putElems(this, elems) - case _ => true + override def adoptClassifier(cls: ClassSymbol)(using Context): Unit = + if !classifier.isSubClass(cls) then // serves as recursion brake + narrowClassifier(cls) + super.adoptClassifier(cls) - /** Record current dependent sets in given VarState provided it does not yet - * contain an entry for this variable. - */ - private[CaptureSet] def recordDepsState()(using VarState): Boolean = - varState.getDeps(this) match - case None => varState.putDeps(this, deps) - case _ => true + override def tryClassifyAs(cls: ClassSymbol)(using Context): Boolean = + classifier.isSubClass(cls) + || super.tryClassifyAs(cls) + && { narrowClassifier(cls); true } - /** Reset elements to what was recorded in `state` */ - def resetElems()(using state: VarState): Unit = - elems = state.elems(this) + /** A handler to be invoked when new elems are added to this set */ + var newElemAddedHandler: Capability => Context ?=> Unit = _ => () - /** Reset dependent sets to what was recorded in `state` */ - def resetDeps()(using state: VarState): Unit = - deps = state.deps(this) + var description: String = "" /** Check that all maps recorded in skippedMaps map `elem` to itself * or something subsumed by it. */ - private def checkSkippedMaps(elem: CaptureRef)(using Context): Unit = + private def checkSkippedMaps(elem: Capability)(using Context): Unit = for tm <- skippedMaps do - val elem1 = tm(elem) - for elem1 <- tm(elem).captureSet.elems do + for elem1 <- mappedSet(elem, tm, variance = 1).elems do assert(elem.subsumes(elem1), i"Skipped map ${tm.getClass} maps newly added $elem to $elem1 in $this") - final def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult = - if isConst || !recordElemsState() then // Fail if variable is solved or given VarState is frozen + protected def includeElem(elem: Capability)(using Context): Unit = + if !elems.contains(elem) then + elems += elem + TypeComparer.logUndoAction: () => + elems -= elem + + def includeDep(cs: CaptureSet)(using Context): Unit = + if !deps.contains(cs) then + deps += cs + TypeComparer.logUndoAction: () => + deps -= cs + + final def addThisElem(elem: Capability)(using Context, VarState): Boolean = + if isConst || !varState.canRecord then // Fail if variable is solved or given VarState is frozen addIfHiddenOrFail(elem) else if !levelOK(elem) then - CompareResult.LevelError(this, elem) // or `elem` is not visible at the level of the set. + failWith(IncludeFailure(this, elem, levelError = true)) // or `elem` is not visible at the level of the set. + else if !elem.tryClassifyAs(classifier) then + failWith(IncludeFailure(this, elem)) else // id == 108 then assert(false, i"trying to add $elem to $this") - assert(elem.isTrackableRef, elem) + assert(elem.isWellformed, elem) assert(!this.isInstanceOf[HiddenSet] || summon[VarState].isSeparating, summon[VarState]) - elems += elem - if elem.isRootCapability then + includeElem(elem) + if isBadRoot(rootLimit, elem) then rootAddedHandler() - newElemAddedHandler(elem) val normElem = if isMaybeSet then elem else elem.stripMaybe // assert(id != 5 || elems.size != 3, this) - val res = (CompareResult.OK /: deps): (r, dep) => - r.andAlso(dep.tryInclude(normElem, this)) - if ccConfig.checkSkippedMaps && res.isOK then checkSkippedMaps(elem) - res.orElse: + val res = deps.forall: dep => + reporting.trace(i"forward $normElem from $this # $id to $dep # ${dep.maybeId} of class ${dep.getClass.toString}"): + dep.tryInclude(normElem, this) + if ccConfig.checkSkippedMaps && res then checkSkippedMaps(elem) + if !res then elems -= elem - res.addToTrace(this) + TypeComparer.updateErrorNotes: + case note: IncludeFailure => note.addToTrace(this) + res + + final def toReader()(using Context) = + if isConst then failWith(MutAdaptFailure(this)) + else + mutability = Reader + TypeComparer.logUndoAction(() => mutability = Mutable) + deps.forall(_.mutableToReader(this)) private def isPartOf(binder: Type)(using Context): Boolean = val find = new TypeAccumulator[Boolean]: @@ -600,53 +778,51 @@ object CaptureSet: case _ => foldOver(b, t) find(false, binder) - // TODO: Also track allowable TermParamRefs and root.Results in capture sets - private def levelOK(elem: CaptureRef)(using Context): Boolean = - if elem.isRootCapability then - !noUniversal - else elem match - case elem @ root.Result(mt) => - !noUniversal && isPartOf(mt.resType) - case elem: TermRef if level.isDefined => - elem.prefix match - case prefix: CaptureRef => - levelOK(prefix) - case _ => - ccState.symLevel(elem.symbol) <= level - case elem: ThisType if level.isDefined => - ccState.symLevel(elem.cls).nextInner <= level - case elem: ParamRef if !this.isInstanceOf[BiMapped] => - isPartOf(elem.binder.resType) - || { - capt.println( - i"""LEVEL ERROR $elem for $this - |elem binder = ${elem.binder}""") - false - } - case ReachCapability(elem1) => - levelOK(elem1) - case ReadOnlyCapability(elem1) => - levelOK(elem1) - case MaybeCapability(elem1) => - levelOK(elem1) - case _ => - true + private def levelOK(elem: Capability)(using Context): Boolean = elem match + case _: FreshCap => + !level.isDefined + || ccState.symLevel(elem.ccOwner) <= level + || { + capt.println(i"LEVEL ERROR $elem cannot be included in $this of $owner") + false + } + case elem @ ResultCap(binder) => + rootLimit == null && (this.isInstanceOf[BiMapped] || isPartOf(binder.resType)) + case GlobalCap => + rootLimit == null + case elem: TermRef if level.isDefined => + elem.prefix match + case prefix: Capability => + levelOK(prefix) + case _ => + ccState.symLevel(elem.symbol) <= level + case elem: ThisType if level.isDefined => + ccState.symLevel(elem.cls).nextInner <= level + case elem: ParamRef if !this.isInstanceOf[BiMapped] => + isPartOf(elem.binder.resType) + || { + capt.println( + i"""LEVEL ERROR $elem for $this + |elem binder = ${elem.binder}""") + false + } + case elem: DerivedCapability => + levelOK(elem.underlying) + case _ => + true - def addDependent(cs: CaptureSet)(using Context, VarState): CompareResult = - if (cs eq this) || cs.isUniversal || isConst then - CompareResult.OK - else if recordDepsState() then - deps += cs - CompareResult.OK - else - CompareResult.Fail(this :: Nil) + def addDependent(cs: CaptureSet)(using Context, VarState): Boolean = + (cs eq this) + || cs.isUniversal + || isConst + || varState.canRecord && { includeDep(cs); true } - override def disallowRootCapability(handler: () => Context ?=> Unit)(using Context): this.type = - noUniversal = true + override def disallowRootCapability(upto: Symbol)(handler: () => Context ?=> Unit)(using Context): this.type = + rootLimit = upto rootAddedHandler = handler - super.disallowRootCapability(handler) + super.disallowRootCapability(upto)(handler) - override def ensureWellformed(handler: CaptureRef => (Context) ?=> Unit)(using Context): this.type = + override def ensureWellformed(handler: Capability => (Context) ?=> Unit)(using Context): this.type = newElemAddedHandler = handler super.ensureWellformed(handler) @@ -667,10 +843,7 @@ object CaptureSet: computingApprox = true try val approx = computeApprox(origin).ensuring(_.isConst) - if approx.elems.exists: - case root.Result(_) => true - case _ => false - then + if approx.elems.exists(_.isInstanceOf[ResultCap]) then ccState.approxWarnings += em"""Capture set variable $this gets upper-approximated |to existential variable from $approx, using {cap} instead.""" @@ -680,7 +853,7 @@ object CaptureSet: /** The intersection of all upper approximations of dependent sets */ protected def computeApprox(origin: CaptureSet)(using Context): CaptureSet = - (universal /: deps) { (acc, sup) => acc ** sup.upperApprox(this) } + ((universal: CaptureSet) /: deps) { (acc, sup) => acc ** sup.upperApprox(this) } /** Widen the variable's elements to its upper approximation and * mark it as constant from now on. This is used for contra-variant type variables @@ -689,18 +862,20 @@ object CaptureSet: def solve()(using Context): Unit = CCState.withCapAsRoot: // // OK here since we infer parameter types that get checked later val approx = upperApprox(empty) - .map(root.CapToFresh(NoSymbol).inverse) // Fresh --> cap + .map(CapToFresh(Origin.Unknown).inverse) // Fresh --> cap .showing(i"solve $this = $result", capt) //println(i"solving var $this $approx ${approx.isConst} deps = ${deps.toList}") val newElems = approx.elems -- elems given VarState() - if tryInclude(newElems, empty).isOK then + if tryInclude(newElems, empty) then markSolved(provisional = false) /** Mark set as solved and propagate this info to all dependent sets */ def markSolved(provisional: Boolean)(using Context): Unit = solved = if provisional then ccState.iterationId else Int.MaxValue deps.foreach(_.propagateSolved(provisional)) + if mutability == Mutable && !maybeExclusive then mutability = Reader + var skippedMaps: Set[TypeMap] = Set.empty @@ -747,21 +922,27 @@ object CaptureSet: * Test case: Without that tweak, logger.scala would not compile. */ class RefiningVar(owner: Symbol)(using Context) extends Var(owner): - override def disallowRootCapability(handler: () => Context ?=> Unit)(using Context) = this + override def disallowRootCapability(upto: Symbol)(handler: () => Context ?=> Unit)(using Context) = this /** A variable that is derived from some other variable via a map or filter. */ abstract class DerivedVar(owner: Symbol, initialElems: Refs)(using @constructorOnly ctx: Context) extends Var(owner, initialElems): // For debugging: A trace where a set was created. Note that logically it would make more - // sense to place this variable in BiMapped, but that runs afoul of the initializatuon checker. - // val stack = if debugSets && this.isInstanceOf[BiMapped] then (new Throwable).getStackTrace().nn.take(20) else null + // sense to place this variable in Mapped, but that runs afoul of the initialization checker. + // val stack = if debugSets && this.isInstanceOf[Mapped] then (new Throwable).getStackTrace().take(20) else null /** The variable from which this variable is derived */ def source: Var + mutability = source.mutability + addAsDependentTo(source) + override def mutableToReader(origin: CaptureSet)(using Context): Boolean = + super.mutableToReader(origin) + && ((origin eq source) || source.mutableToReader(this)) + override def propagateSolved(provisional: Boolean)(using Context) = if source.isConst && !isConst then markSolved(provisional) @@ -792,20 +973,21 @@ object CaptureSet: (val source: Var, val bimap: BiTypeMap, initialElems: Refs)(using @constructorOnly ctx: Context) extends DerivedVar(source.owner, initialElems): - override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + override def tryInclude(elem: Capability, origin: CaptureSet)(using Context, VarState): Boolean = if origin eq source then - val mappedElem = bimap.forward(elem) - if accountsFor(mappedElem) then CompareResult.OK - else addNewElem(mappedElem) + val mappedElem = bimap.mapCapability(elem) + accountsFor(mappedElem) || addNewElem(mappedElem) else if accountsFor(elem) then - CompareResult.OK + true else + // Propagate backwards to source. The element will be added then by another + // forward propagation from source that hits the first branch `if origin eq source then`. try - source.tryInclude(bimap.backward(elem), this) - .showing(i"propagating new elem $elem backward from $this to $source = $result", captDebug) - .andAlso(addNewElem(elem)) + reporting.trace(i"prop backwards $elem from $this # $id to $source # ${source.id} via $summarize"): + source.tryInclude(bimap.inverse.mapCapability(elem), this) + .showing(i"propagating new elem $elem backward from $this/$id to $source = $result", captDebug) catch case ex: AssertionError => - println(i"fail while tryInclude $elem of ${elem.getClass} in $this / ${this.summarize}") + println(i"fail while prop backwards tryInclude $elem of ${elem.getClass} from $this # $id / ${this.summarize} to $source # ${source.id}") throw ex /** For a BiTypeMap, supertypes of the mapped type also constrain @@ -827,20 +1009,19 @@ object CaptureSet: /** A variable with elements given at any time as { x <- source.elems | p(x) } */ class Filtered private[CaptureSet] - (val source: Var, val p: Context ?=> CaptureRef => Boolean)(using @constructorOnly ctx: Context) + (val source: Var, val p: Context ?=> Capability => Boolean)(using @constructorOnly ctx: Context) extends DerivedVar(source.owner, source.elems.filter(p)): - override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + override def tryInclude(elem: Capability, origin: CaptureSet)(using Context, VarState): Boolean = if accountsFor(elem) then - CompareResult.OK + true else if origin eq source then - if p(elem) then addNewElem(elem) - else CompareResult.OK + !p(elem) || addNewElem(elem) else // Filtered elements have to be back-propagated to source. // Elements that don't satisfy `p` are not allowed. if p(elem) then source.tryInclude(elem, this) - else CompareResult.Fail(this :: Nil) + else failWith(IncludeFailure(this, elem)) override def computeApprox(origin: CaptureSet)(using Context): CaptureSet = if source eq origin then @@ -861,20 +1042,30 @@ object CaptureSet: extends Var(initialElems = cs1.elems ++ cs2.elems): addAsDependentTo(cs1) addAsDependentTo(cs2) + mutability = cs1.mutability | cs2.mutability - override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = - if accountsFor(elem) then CompareResult.OK + override def tryInclude(elem: Capability, origin: CaptureSet)(using Context, VarState): Boolean = + if accountsFor(elem) then true else val res = super.tryInclude(elem, origin) // If this is the union of a constant and a variable, // propagate `elem` to the variable part to avoid slack // between the operands and the union. - if res.isOK && (origin ne cs1) && (origin ne cs2) then + if res && (origin ne cs1) && (origin ne cs2) then if cs1.isConst then cs2.tryInclude(elem, origin) else if cs2.isConst then cs1.tryInclude(elem, origin) else res else res + override def mutableToReader(origin: CaptureSet)(using Context): Boolean = + super.mutableToReader(origin) + && { + if (origin eq cs1) || (origin eq cs2) then true + else if cs1.isConst && cs1.mutability == Mutable then cs2.mutableToReader(this) + else if cs2.isConst && cs2.mutability == Mutable then cs1.mutableToReader(this) + else true + } + override def propagateSolved(provisional: Boolean)(using Context) = if cs1.isConst && cs2.isConst && !isConst then markSolved(provisional) end Union @@ -885,14 +1076,23 @@ object CaptureSet: addAsDependentTo(cs2) deps += cs1 deps += cs2 + mutability = cs1.mutability & cs2.mutability - override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = - val present = + override def tryInclude(elem: Capability, origin: CaptureSet)(using Context, VarState): Boolean = + val inIntersection = if origin eq cs1 then cs2.accountsFor(elem) else if origin eq cs2 then cs1.accountsFor(elem) else true - if present && !accountsFor(elem) then addNewElem(elem) - else CompareResult.OK + !inIntersection + || accountsFor(elem) + || addNewElem(elem) + && ((origin eq cs1) || cs1.tryInclude(elem, this)) + && ((origin eq cs2) || cs2.tryInclude(elem, this)) + + override def mutableToReader(origin: CaptureSet)(using Context): Boolean = + super.mutableToReader(origin) + && ((origin eq cs1) || cs1.mutableToReader(this)) + && ((origin eq cs2) || cs2.mutableToReader(this)) override def computeApprox(origin: CaptureSet)(using Context): CaptureSet = if (origin eq cs1) || (origin eq cs2) then @@ -923,30 +1123,31 @@ object CaptureSet: * which are already subject through snapshotting and rollbacks in VarState. * It's advantageous if we don't need to deal with other pieces of state there. */ - class HiddenSet(initialOwner: Symbol)(using @constructorOnly ictx: Context) + class HiddenSet(initialOwner: Symbol, val owningCap: FreshCap)(using @constructorOnly ictx: Context) extends Var(initialOwner): - var owningCap: AnnotatedType = uninitialized var givenOwner: Symbol = initialOwner override def owner = givenOwner - // assert(id != 34, i"$initialHidden") + //assert(id != 3) - private def aliasRef: AnnotatedType | Null = + description = i"of elements subsumed by a fresh cap in $initialOwner" + + private def aliasRef: FreshCap | Null = if myElems.size == 1 then myElems.nth(0) match - case al @ root.Fresh(hidden) if deps.contains(hidden) => al + case alias: FreshCap if deps.contains(alias.hiddenSet) => alias case _ => null else null private def aliasSet: HiddenSet = if myElems.size == 1 then myElems.nth(0) match - case root.Fresh(hidden) if deps.contains(hidden) => hidden + case alias: FreshCap if deps.contains(alias.hiddenSet) => alias.hiddenSet case _ => this else this - def superCaps: List[AnnotatedType] = + def superCaps: List[FreshCap] = deps.toList.map(_.asInstanceOf[HiddenSet].owningCap) override def elems: Refs = @@ -963,28 +1164,28 @@ object CaptureSet: * hidden set H which is a superset of this set, then make this set an * alias of H. */ - def add(elem: CaptureRef)(using ctx: Context, vs: VarState): Unit = + def add(elem: Capability)(using ctx: Context, vs: VarState): Unit = val alias = aliasSet if alias ne this then alias.add(elem) else def addToElems() = - elems += elem + includeElem(elem) deps.foreach: dep => assert(dep != this) vs.addHidden(dep.asInstanceOf[HiddenSet], elem) elem match - case root.Fresh(hidden) => - if this ne hidden then - val alias = hidden.aliasRef + case elem: FreshCap => + if this ne elem.hiddenSet then + val alias = elem.hiddenSet.aliasRef if alias != null then add(alias) - else if deps.contains(hidden) then // make this an alias of elem - capt.println(i"Alias $this to $hidden") + else if deps.contains(elem.hiddenSet) then // make this an alias of elem + capt.println(i"Alias $this to ${elem.hiddenSet}") elems = SimpleIdentitySet(elem) - deps = SimpleIdentitySet(hidden) + deps = SimpleIdentitySet(elem.hiddenSet) else addToElems() - hidden.deps += this + elem.hiddenSet.includeDep(this) case _ => addToElems() @@ -1000,31 +1201,29 @@ object CaptureSet: end HiddenSet /** Extrapolate tm(r) according to `variance`. Let r1 be the result of tm(r). - * - If r1 is a tracked CaptureRef, return {r1} + * - If r1 is a tracked capability, return {r1} * - If r1 has an empty capture set, return {} * - Otherwise, * - if the variance is covariant, return r1's capture set * - if the variance is contravariant, return {} * - Otherwise assertion failure */ - def extrapolateCaptureRef(r: CaptureRef, tm: TypeMap, variance: Int)(using Context): CaptureSet = - val r1 = tm(r) - val upper = r1.captureSet - def isExact = - upper.isAlwaysEmpty - || upper.isConst && upper.elems.size == 1 && upper.elems.contains(r1) - || r.derivesFrom(defn.Caps_CapSet) - if variance > 0 || isExact then upper - else if variance < 0 then CaptureSet.EmptyWithProvenance(r, r1) - else upper.maybe + final def mappedSet(r: Capability, tm: TypeMap, variance: Int)(using Context): CaptureSet = + tm.mapCapability(r) match + case c: CoreCapability => c.captureSet + case c: Capability => c.singletonCaptureSet + case (cs: CaptureSet, exact) => + if cs.isAlwaysEmpty || exact || variance > 0 then cs + else if variance < 0 then CaptureSet.EmptyWithProvenance(r, cs) + else cs.maybe /** Apply `f` to each element in `xs`, and join result sets with `++` */ - def mapRefs(xs: Refs, f: CaptureRef => CaptureSet)(using Context): CaptureSet = + def mapRefs(xs: Refs, f: Capability => CaptureSet)(using Context): CaptureSet = ((empty: CaptureSet) /: xs)((cs, x) => cs ++ f(x)) /** Apply extrapolated `tm` to each element in `xs`, and join result sets with `++` */ def mapRefs(xs: Refs, tm: TypeMap, variance: Int)(using Context): CaptureSet = - mapRefs(xs, extrapolateCaptureRef(_, tm, variance)) + mapRefs(xs, mappedSet(_, tm, variance)) /** Return true iff * - arg1 is a TypeBounds >: CL T <: CH T of two capturing types with equal parents. @@ -1036,109 +1235,61 @@ object CaptureSet: case TypeBounds(CapturingType(lo, loRefs), CapturingType(hi, hiRefs)) if lo =:= hi => given VarState() val cs2 = arg2.captureSet - hiRefs.subCaptures(cs2).isOK && cs2.subCaptures(loRefs).isOK + hiRefs.subCaptures(cs2) && cs2.subCaptures(loRefs) case _ => false - /** A TypeMap that is the identity on capture references */ + /** A TypeMap that is the identity on capabilities */ trait IdentityCaptRefMap extends TypeMap /** A value of this class is produced and added as a note to ccState * when a subsumes check decides that an existential variable `ex` cannot be * instantiated to the other capability `other`. */ - case class ExistentialSubsumesFailure(val ex: root.Result, val other: CaptureRef) extends ErrorNote + case class ExistentialSubsumesFailure(val ex: ResultCap, val other: Capability) extends ErrorNote - trait CompareFailure: - private var myErrorNotes: List[ErrorNote] = Nil - def errorNotes: List[ErrorNote] = myErrorNotes - def withNotes(notes: List[ErrorNote]): this.type = - myErrorNotes = notes - this + /** Failure indicating that `elem` cannot be included in `cs` */ + case class IncludeFailure(cs: CaptureSet, elem: Capability, levelError: Boolean = false) extends ErrorNote, Showable: + private var myTrace: List[CaptureSet] = cs :: Nil - enum CompareResult extends Showable: - case OK - case Fail(trace: List[CaptureSet]) extends CompareResult, CompareFailure - case LevelError(cs: CaptureSet, elem: CaptureRef) extends CompareResult, CompareFailure, ErrorNote + def trace: List[CaptureSet] = myTrace + def addToTrace(cs1: CaptureSet) = + val res = IncludeFailure(cs, elem, levelError) + res.myTrace = cs1 :: this.myTrace + res override def toText(printer: Printer): Text = inContext(printer.printerContext): - this match - case OK => Str("OK") - case Fail(trace) => - if ctx.settings.YccDebug.value then printer.toText(trace, ", ") - else blocking.show - case LevelError(cs: CaptureSet, elem: CaptureRef) => - Str(i"($elem at wrong level for $cs at level ${cs.level.toString})") - - /** The result is OK */ - def isOK: Boolean = this == OK - - /** If not isOK, the blocking capture set */ - def blocking: CaptureSet = (this: @unchecked) match - case Fail(cs) => cs.last - case LevelError(cs, _) => cs - - /** Optionally, this result if it is a level error */ - def levelError: Option[LevelError] = this match - case result: LevelError => Some(result) - case _ => None - - inline def andAlso(op: Context ?=> CompareResult)(using Context): CompareResult = - if isOK then op else this - - inline def orElse(op: Context ?=> CompareResult)(using Context): CompareResult = - if isOK then this - else - val alt = op - if alt.isOK then alt - else this - - inline def addToTrace(cs: CaptureSet): CompareResult = this match - case Fail(trace) => Fail(cs :: trace) - case _ => this - end CompareResult + if levelError then + i"($elem at wrong level for $cs at level ${cs.level.toString})" + else + if ctx.settings.YccDebug.value + then i"$elem cannot be included in $trace" + else i"$elem cannot be included in $cs" + end IncludeFailure + + /** Failure indicating that a read-only capture set of a mutable type cannot be + * widened to an exclusive set. + * @param cs the exclusive set in question + * @param lo the lower type of the orginal type comparison, or NoType if not known + * @param hi the upper type of the orginal type comparison, or NoType if not known + */ + case class MutAdaptFailure(cs: CaptureSet, lo: Type = NoType, hi: Type = NoType) extends ErrorNote /** A VarState serves as a snapshot mechanism that can undo * additions of elements or super sets if an operation fails */ class VarState: - /** A map from captureset variables to their elements at the time of the snapshot. */ - private val elemsMap: util.EqHashMap[Var, Refs] = new util.EqHashMap - - /** A map from captureset variables to their dependent sets at the time of the snapshot. */ - private val depsMap: util.EqHashMap[Var, Deps] = new util.EqHashMap - - /** A map from root.Result values to other such values. If two result values + /** A map from ResultCap values to other ResultCap values. If two result values * `a` and `b` are unified, then `eqResultMap(a) = b` and `eqResultMap(b) = a`. */ - private var eqResultMap: util.SimpleIdentityMap[root.Result, root.Result] = util.SimpleIdentityMap.empty - - /** A snapshot of the `eqResultMap` value at the start of a VarState transaction */ - private var eqResultSnapshot: util.SimpleIdentityMap[root.Result, root.Result] | Null = null - - /** The recorded elements of `v` (it's required that a recording was made) */ - def elems(v: Var): Refs = elemsMap(v) - - /** Optionally the recorded elements of `v`, None if nothing was recorded for `v` */ - def getElems(v: Var): Option[Refs] = elemsMap.get(v) + private var eqResultMap: util.SimpleIdentityMap[ResultCap, ResultCap] = util.SimpleIdentityMap.empty /** Record elements, return whether this was allowed. * By default, recording is allowed in regular but not in frozen states. */ - def putElems(v: Var, elems: Refs): Boolean = { elemsMap(v) = elems; true } - - /** The recorded dependent sets of `v` (it's required that a recording was made) */ - def deps(v: Var): Deps = depsMap(v) - - /** Optionally the recorded dependent sets of `v`, None if nothing was recorded for `v` */ - def getDeps(v: Var): Option[Deps] = depsMap.get(v) - - /** Record dependent sets, return whether this was allowed. - * By default, recording is allowed in regular but not in frozen states. - */ - def putDeps(v: Var, deps: Deps): Boolean = { depsMap(v) = deps; true } + def canRecord: Boolean = true /** Does this state allow additions of elements to capture set variables? */ def isOpen = true @@ -1148,12 +1299,7 @@ object CaptureSet: * return whether this was allowed. By default, recording is allowed * but the special state VarState.Separate overrides this. */ - def addHidden(hidden: HiddenSet, elem: CaptureRef)(using Context): Boolean = - elemsMap.get(hidden) match - case None => - elemsMap(hidden) = hidden.elems - depsMap(hidden) = hidden.deps - case _ => + def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean = hidden.add(elem)(using ctx, this) true @@ -1162,31 +1308,32 @@ object CaptureSet: * substBinder when comparing two method types. In that case we can unify * the two roots1, provided none of the two roots have already been unified * themselves. So unification must be 1-1. + * + * Note, see (**) below: We also allow unifications of results that have different ExprType + * binders. This is necessary because ExprTypes don't get updated with SubstBindingMaps. + * It's sound since ExprTypes always appear alone and at the top-level, so there is + * no problem with confusing results at different levels. + * See pos-customargs/captures/overrides.scala for a test case. */ - def unify(root1: root.Result, root2: root.Result)(using Context): Boolean = - (root1, root2) match - case (root1 @ root.Result(binder1), root2 @ root.Result(binder2)) - if (binder1 eq binder2) - && (root1.rootAnnot.originalBinder ne root2.rootAnnot.originalBinder) - && eqResultMap(root1) == null - && eqResultMap(root2) == null - => - if eqResultSnapshot == null then eqResultSnapshot = eqResultMap - eqResultMap = eqResultMap.updated(root1, root2).updated(root2, root1) - true - case _ => - false - - /** Roll back global state to what was recorded in this VarState */ - def rollBack(): Unit = - elemsMap.keysIterator.foreach(_.resetElems()(using this)) - depsMap.keysIterator.foreach(_.resetDeps()(using this)) - if eqResultSnapshot != null then eqResultMap = eqResultSnapshot.nn + def unify(c1: ResultCap, c2: ResultCap)(using Context): Boolean = + ((c1.binder eq c2.binder) + || c1.binder.isInstanceOf[ExprType] && c2.binder.isInstanceOf[ExprType] // (**) + ) + && (c1.originalBinder ne c2.originalBinder) + && eqResultMap(c1) == null + && eqResultMap(c2) == null + && { + eqResultMap = eqResultMap.updated(c1, c2).updated(c2, c1) + TypeComparer.logUndoAction: () => + eqResultMap.remove(c1) + eqResultMap.remove(c2) + true + } - private var seen: util.EqHashSet[CaptureRef] = new util.EqHashSet + private var seen: util.EqHashSet[Capability] = new util.EqHashSet /** Run test `pred` unless `ref` was seen in an enclosing `ifNotSeen` operation */ - def ifNotSeen(ref: CaptureRef)(pred: => Boolean): Boolean = + def ifNotSeen(ref: Capability)(pred: => Boolean): Boolean = if seen.add(ref) then try pred finally seen -= ref else false @@ -1202,8 +1349,7 @@ object CaptureSet: * subsume arbitary types, which are then recorded in their hidden sets. */ class Closed extends VarState: - override def putElems(v: Var, refs: Refs) = false - override def putDeps(v: Var, deps: Deps) = false + override def canRecord = false override def isOpen = false override def toString = "closed varState" @@ -1212,7 +1358,7 @@ object CaptureSet: * No new references can be added. */ class Separating extends Closed: - override def addHidden(hidden: HiddenSet, elem: CaptureRef)(using Context): Boolean = false + override def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean = false override def toString = "separating varState" override def isSeparating = true @@ -1227,14 +1373,21 @@ object CaptureSet: */ def HardSeparate(using Context): Separating = ccState.HardSeparate - /** A special state that turns off recording of elements. Used only - * in `addSub` to prevent cycles in recordings. Instantiated in ccState.Unrecorded. + /** A mixin trait that overrides the addHidden and unify operations to + * not depend in state. */ + trait Stateless extends VarState: + + /** Allow adding hidden elements, but don't store them */ + override def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean = true + + /** Don't allow to unify result caps */ + override def unify(c1: ResultCap, c2: ResultCap)(using Context): Boolean = false + end Stateless + + /** An open state that turns off recording of hidden elements (but allows + * adding them). Used in `addAsDependentTo`. Instantiated in ccState.Unrecorded. */ - class Unrecorded extends VarState: - override def putElems(v: Var, refs: Refs) = true - override def putDeps(v: Var, deps: Deps) = true - override def rollBack(): Unit = () - override def addHidden(hidden: HiddenSet, elem: CaptureRef)(using Context): Boolean = true + class Unrecorded extends VarState, Stateless: override def toString = "unrecorded varState" def Unrecorded(using Context): Unrecorded = ccState.Unrecorded @@ -1242,8 +1395,7 @@ object CaptureSet: /** A closed state that turns off recording of hidden elements (but allows * adding them). Used in `mightAccountFor`. Instantiated in ccState.ClosedUnrecorded. */ - class ClosedUnrecorded extends Closed: - override def addHidden(hidden: HiddenSet, elem: CaptureRef)(using Context): Boolean = true + class ClosedUnrecorded extends Closed, Stateless: override def toString = "closed unrecorded varState" def ClosedUnrecorded(using Context): ClosedUnrecorded = ccState.ClosedUnrecorded @@ -1255,25 +1407,23 @@ object CaptureSet: /** A template for maps on capabilities where f(c) <: c and f(f(c)) = c */ private abstract class NarrowingCapabilityMap(using Context) extends BiTypeMap: + def apply(t: Type) = mapOver(t) - def mapRef(ref: CaptureRef): CaptureRef - - def apply(t: Type) = t match - case t: CaptureRef if t.isTrackableRef => mapRef(t) - case _ => mapOver(t) + protected def isSameMap(other: BiTypeMap) = other.getClass == getClass override def fuse(next: BiTypeMap)(using Context) = next match - case next: Inverse if next.inverse.getClass == getClass => assert(false); Some(IdentityTypeMap) - case next: NarrowingCapabilityMap if next.getClass == getClass => assert(false) + case next: Inverse if next.inverse.getClass == getClass => Some(IdentityTypeMap) + case next: NarrowingCapabilityMap if next.getClass == getClass => Some(this) case _ => None class Inverse extends BiTypeMap: def apply(t: Type) = t // since f(c) <: c, this is the best inverse + override def mapCapability(c: Capability, deep: Boolean): Capability = c def inverse = NarrowingCapabilityMap.this override def toString = NarrowingCapabilityMap.this.toString ++ ".inverse" override def fuse(next: BiTypeMap)(using Context) = next match - case next: NarrowingCapabilityMap if next.inverse.getClass == getClass => assert(false); Some(IdentityTypeMap) - case next: NarrowingCapabilityMap if next.getClass == getClass => assert(false) + case next: NarrowingCapabilityMap if isSameMap(next.inverse) => Some(IdentityTypeMap) + case next: NarrowingCapabilityMap if isSameMap(next) => Some(this) case _ => None lazy val inverse = Inverse() @@ -1281,14 +1431,21 @@ object CaptureSet: /** Maps `x` to `x?` */ private class MaybeMap(using Context) extends NarrowingCapabilityMap: - def mapRef(ref: CaptureRef): CaptureRef = ref.maybe + override def mapCapability(c: Capability, deep: Boolean) = c.maybe override def toString = "Maybe" /** Maps `x` to `x.rd` */ private class ReadOnlyMap(using Context) extends NarrowingCapabilityMap: - def mapRef(ref: CaptureRef): CaptureRef = ref.readOnly + override def mapCapability(c: Capability, deep: Boolean) = c.readOnly override def toString = "ReadOnly" + private class RestrictMap(val cls: ClassSymbol)(using Context) extends NarrowingCapabilityMap: + override def mapCapability(c: Capability, deep: Boolean) = c.restrict(cls) + override def toString = "Restrict" + override def isSameMap(other: BiTypeMap) = other match + case other: RestrictMap => cls == other.cls + case _ => false + /* Not needed: def ofClass(cinfo: ClassInfo, argTypes: List[Type])(using Context): CaptureSet = CaptureSet.empty @@ -1311,20 +1468,26 @@ object CaptureSet: css.foldLeft(empty)(_ ++ _) */ - /** The capture set of the type underlying CaptureRef */ - def ofInfo(ref: CaptureRef)(using Context): CaptureSet = ref match - case ReachCapability(ref1) => - ref1.widen.deepCaptureSet(includeTypevars = true) - .showing(i"Deep capture set of $ref: ${ref1.widen} = ${result}", capt) - case ReadOnlyCapability(ref1) => - ref1.captureSetOfInfo.map(ReadOnlyMap()) - case ref: ParamRef if !ref.underlying.exists => + /** The capture set of the type underlying the capability `c` */ + def ofInfo(c: Capability)(using Context): CaptureSet = c match + case Reach(c1) => + c1.widen.deepCaptureSet(includeTypevars = true) + .showing(i"Deep capture set of $c: ${c1.widen} = ${result}", capt) + case Restricted(c1, cls) => + if cls == defn.NothingClass then CaptureSet.empty + else c1.captureSetOfInfo.restrict(cls) // todo: should we simplify using subsumption here? + case ReadOnly(c1) => + c1.captureSetOfInfo.readOnly + case Maybe(c1) => + c1.captureSetOfInfo.maybe + case c: RootCapability => + c.singletonCaptureSet + case c: ParamRef if !c.underlying.exists => // might happen during construction of lambdas, assume `{cap}` in this case so that // `ref` will not seem subsumed by other capabilities in a `++`. universal - case _ => - if ref.isRootCapability then ref.singletonCaptureSet - else ofType(ref.underlying, followResult = false) + case c: CoreCapability => + ofType(c.underlying, followResult = false) /** Capture set of a type * @param followResult If true, also include capture sets of function results. @@ -1343,25 +1506,16 @@ object CaptureSet: case tp: (TypeRef | TypeParamRef) => if tp.derivesFrom(defn.Caps_CapSet) then tp.captureSet else empty - case tp @ root.Result(_) => - tp.captureSet case CapturingType(parent, refs) => recur(parent) ++ refs - case tp @ AnnotatedType(parent, ann) if ann.hasSymbol(defn.ReachCapabilityAnnot) => - // Note: we don't use the `ReachCapability(parent)` extractor here since that - // only works if `parent` is a CaptureRef, but in illegal programs it might not be. - // And then we do not want to fall back to empty. - parent match - case parent: SingletonCaptureRef if parent.isTrackableRef => - tp.singletonCaptureSet - case _ => - CaptureSet.ofTypeDeeply(parent.widen) + case tp @ AnnotatedType(parent, ann) if ann.symbol.isRetains => + recur(parent) ++ ann.tree.toCaptureSet case tpd @ defn.RefinedFunctionOf(rinfo: MethodType) if followResult => - ofType(tpd.parent, followResult = false) // pick up capture set from parent type + ofType(tpd.parent, followResult = false) // pick up capture set from parent type ++ recur(rinfo.resType) // add capture set of result .filter: case TermParamRef(binder, _) => binder ne rinfo - case root.Result(binder) => binder ne rinfo + case ResultCap(binder) => binder ne rinfo case _ => true case tpd @ AppliedType(tycon, args) => if followResult && defn.isNonRefinedFunction(tpd) then @@ -1392,11 +1546,11 @@ object CaptureSet: def capturingCase(acc: CaptureSet, parent: Type, refs: CaptureSet) = this(acc, parent) ++ refs def abstractTypeCase(acc: CaptureSet, t: TypeRef, upperBound: Type) = - if includeTypevars && upperBound.isExactlyAny then CaptureSet.fresh(t.symbol) + if includeTypevars && upperBound.isExactlyAny then fresh(Origin.DeepCS(t)) else this(acc, upperBound) collect(CaptureSet.empty, tp) - type AssumedContains = immutable.Map[TypeRef, SimpleIdentitySet[CaptureRef]] + type AssumedContains = immutable.Map[TypeRef, SimpleIdentitySet[Capability]] val AssumedContains: Property.Key[AssumedContains] = Property.Key() def assumedContains(using Context): AssumedContains = diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index 9f9b923b2c88..1cafe295f529 100644 --- a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala @@ -39,6 +39,9 @@ object CapturingType: case parent @ CapturingType(parent1, refs1) if boxed || !parent.isBoxed => apply(parent1, refs ++ refs1, boxed) case _ => + if parent.derivesFromMutable then refs.setMutable() + val classifier = parent.classifier + refs.adoptClassifier(parent.classifier) AnnotatedType(parent, CaptureAnnotation(refs, boxed)(defn.RetainsAnnot)) /** An extractor for CapturingTypes. Capturing types are recognized if @@ -52,11 +55,11 @@ object CapturingType: else decomposeCapturingType(tp) /** Decompose `tp` as a capturing type without taking IgnoreCaptures into account */ - def decomposeCapturingType(tp: Type)(using Context): Option[(Type, CaptureSet)] = tp match + def decomposeCapturingType(using Context)(tp: Type, alsoRetains: Boolean = isCaptureChecking): Option[(Type, CaptureSet)] = tp match case AnnotatedType(parent, ann: CaptureAnnotation) if isCaptureCheckingOrSetup => Some((parent, ann.refs)) - case AnnotatedType(parent, ann) if ann.symbol.isRetains && isCaptureChecking => + case AnnotatedType(parent, ann) if ann.symbol.isRetains && alsoRetains => // There are some circumstances where we cannot map annotated types // with retains annotations to capturing types, so this second recognizer // path still has to exist. One example is when checking capture sets @@ -68,6 +71,9 @@ object CapturingType: // type `C^{f}` which does not have a capture annotation yet. The transformed // type would be in a copy of the dependent function type, but it is useless // since we need to check the original reference. + // + // TODO In other situations we expect that the type is already transformed to a + // CapturingType and we should crash if this not the case. try Some((parent, ann.tree.toCaptureSet)) catch case ex: IllegalCaptureRef => None case _ => @@ -81,4 +87,9 @@ object CapturingType: end CapturingType +object CapturingOrRetainsType: + def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet)] = + if ctx.mode.is(Mode.IgnoreCaptures) then None + else CapturingType.decomposeCapturingType(tp, alsoRetains = true) + diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index a4551fa2b86c..c0c42bbdb32f 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -18,12 +18,14 @@ import util.{SimpleIdentitySet, EqHashMap, EqHashSet, SrcPos, Property} import transform.{Recheck, PreRecheck, CapturedVars} import Recheck.* import scala.collection.mutable -import CaptureSet.{withCaptureSetsExplained, CompareResult, CompareFailure, ExistentialSubsumesFailure} +import CaptureSet.{withCaptureSetsExplained, IncludeFailure, ExistentialSubsumesFailure, MutAdaptFailure} import CCState.* import StdNames.nme import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind} import reporting.{trace, Message, OverrideError} import Annotations.Annotation +import Capabilities.* +import dotty.tools.dotc.util.common.alwaysTrue /** The capture checker */ object CheckCaptures: @@ -58,9 +60,6 @@ object CheckCaptures: def isOutermost = outer0 == null - /** If an environment is open it tracks free references */ - def isOpen(using Context) = !captured.isAlwaysEmpty && kind != EnvKind.Boxed - def outersIterator: Iterator[Env] = new: private var cur = Env.this def hasNext = !cur.isOutermost @@ -99,31 +98,36 @@ object CheckCaptures: * This check is performed at Typer. */ def checkWellformed(parent: Tree, ann: Tree)(using Context): Unit = - def check(elem: Tree, pos: SrcPos): Unit = elem.tpe match - case ref: CaptureRef => - if !ref.isTrackableRef then - report.error(em"$elem cannot be tracked since it is not a parameter or local value", pos) + def check(elem: Type): Unit = elem match + case ref: TypeRef => + val refSym = ref.symbol + if refSym.isType && !refSym.info.derivesFrom(defn.Caps_CapSet) then + report.error(em"$elem is not a legal element of a capture set", ann.srcPos) + case ref: CoreCapability => + if !ref.isTrackableRef && !ref.isCapRef then + report.error(em"$elem cannot be tracked since it is not a parameter or local value", ann.srcPos) + case ReachCapability(ref) => + check(ref) + if ref.isCapRef then + report.error(em"Cannot form a reach capability from `cap`", ann.srcPos) + case ReadOnlyCapability(ref) => + check(ref) + case OnlyCapability(ref, cls) => + if !cls.isClassifiedCapabilityClass then + report.error( + em"""${ref.showRef}.only[${cls.name}] is not well-formed since $cls is not a classifier class. + |A classifier class is a class extending `caps.Capability` and directly extending `caps.Classifier`.""", + ann.srcPos) + check(ref) case tpe => - report.error(em"$elem: $tpe is not a legal element of a capture set", pos) - for elem <- ann.retainedElems do - elem match - case CapsOfApply(arg) => - def isLegalCapsOfArg = - arg.symbol.isType && arg.symbol.info.derivesFrom(defn.Caps_CapSet) - if !isLegalCapsOfArg then - report.error( - em"""$arg is not a legal prefix for `^` here, - |is must be a type parameter or abstract type with a caps.CapSet upper bound.""", - elem.srcPos) - case ReachCapabilityApply(arg) => check(arg, elem.srcPos) - case ReadOnlyCapabilityApply(arg) => check(arg, elem.srcPos) - case _ => check(elem, elem.srcPos) + report.error(em"$elem: $tpe is not a legal element of a capture set", ann.srcPos) + ann.retainedSet.retainedElementsRaw.foreach(check) /** Under the sealed policy, report an error if some part of `tp` contains the * root capability in its capture set or if it refers to a type parameter that * could possibly be instantiated with cap in a way that's visible at the type. */ - private def disallowRootCapabilitiesIn(tp: Type, carrier: Symbol, what: String, have: String, addendum: String, pos: SrcPos)(using Context) = + private def disallowRootCapabilitiesIn(tp: Type, upto: Symbol, what: String, have: String, addendum: String, pos: SrcPos)(using Context) = val check = new TypeTraverser: private val seen = new EqHashSet[TypeRef] @@ -150,7 +154,7 @@ object CheckCaptures: case CapturingType(parent, refs) => if variance >= 0 then val openScopes = openExistentialScopes - refs.disallowRootCapability: () => + refs.disallowRootCapability(upto): () => def part = if t eq tp then "" else @@ -158,7 +162,7 @@ object CheckCaptures: def showInOpenedFreshBinders(mts: List[MethodType]): String = mts match case Nil => i"the part $t of " case mt :: mts1 => - CCState.inNewExistentialScope(mt): + inNewExistentialScope(mt): showInOpenedFreshBinders(mts1) showInOpenedFreshBinders(openScopes.reverse) report.error( @@ -256,13 +260,20 @@ class CheckCaptures extends Recheck, SymTransformer: */ private val sepCheckFormals = util.EqHashMap[Tree, Type]() - /** The references used at identifier or application trees */ - private val usedSet = util.EqHashMap[Tree, CaptureSet]() + /** The references used at identifier or application trees, including the + * environment at the reference point. + */ + private val useInfos = mutable.ArrayBuffer[(Tree, CaptureSet, Env)]() + + private var usedSet = util.EqHashMap[Tree, CaptureSet]() /** The set of symbols that were rechecked via a completer */ private val completed = new mutable.HashSet[Symbol] - var needAnotherRun = false + /** Set on recheckClassDef since there we see all language imports */ + private var sepChecksEnabled = false + + private var needAnotherRun = false def resetIteration()(using Context): Unit = needAnotherRun = false @@ -273,7 +284,7 @@ class CheckCaptures extends Recheck, SymTransformer: extension [T <: Tree](tree: T) def needsSepCheck: Boolean = sepCheckFormals.contains(tree) def formalType: Type = sepCheckFormals.getOrElse(tree, NoType) - def markedFree = usedSet.getOrElse(tree, CaptureSet.empty) + def markedFree: CaptureSet = usedSet.getOrElse(tree, CaptureSet.empty) /** Instantiate capture set variables appearing contra-variantly to their * upper approximation. @@ -325,8 +336,8 @@ class CheckCaptures extends Recheck, SymTransformer: case t @ CapturingType(parent, refs) => for ref <- refs.elems do ref match - case root.Fresh(hidden) if !hidden.givenOwner.exists => - hidden.givenOwner = sym + case ref: FreshCap if !ref.hiddenSet.givenOwner.exists => + ref.hiddenSet.givenOwner = sym case _ => traverse(parent) case t @ defn.RefinedFunctionOf(rinfo) => @@ -348,39 +359,40 @@ class CheckCaptures extends Recheck, SymTransformer: /** Assert subcapturing `cs1 <: cs2` (available for debugging, otherwise unused) */ def assertSub(cs1: CaptureSet, cs2: CaptureSet)(using Context) = - assert(cs1.subCaptures(cs2).isOK, i"$cs1 is not a subset of $cs2") + assert(cs1.subCaptures(cs2), i"$cs1 is not a subset of $cs2") /** If `res` is not CompareResult.OK, report an error */ - def checkOK(res: CompareResult, prefix: => String, added: CaptureRef | CaptureSet, target: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context): Unit = + def checkOK(res: TypeComparer.CompareResult, prefix: => String, added: Capability | CaptureSet, target: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context): Unit = res match - case res: CompareFailure => + case TypeComparer.CompareResult.Fail(notes) => + val ((res: IncludeFailure) :: Nil, otherNotes) = + notes.partition(_.isInstanceOf[IncludeFailure]): @unchecked def msg(provisional: Boolean) = - def toAdd: String = errorNotes(res.errorNotes).toAdd.mkString + def toAdd: String = errorNotes(otherNotes).toAdd.mkString def descr: String = - val d = res.blocking.description + val d = res.cs.description if d.isEmpty then provenance else "" def kind = if provisional then "previously estimated\n" else "allowed " - em"$prefix included in the ${kind}capture set ${res.blocking}$descr$toAdd" + em"$prefix included in the ${kind}capture set ${res.cs}$descr$toAdd" target match case target: CaptureSet.Var - if res.blocking.isProvisionallySolved => + if res.cs.isProvisionallySolved => report.warning( msg(provisional = true) .prepend(i"Another capture checking run needs to be scheduled because\n"), pos) needAnotherRun = true added match - case added: CaptureRef => target.elems += added + case added: Capability => target.elems += added case added: CaptureSet => target.elems ++= added.elems case _ => - inContext(root.printContext(added, res.blocking)): - report.error(msg(provisional = false), pos) + report.error(msg(provisional = false), pos) case _ => /** Check subcapturing `{elem} <: cs`, report error on failure */ - def checkElem(elem: CaptureRef, cs: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context) = + def checkElem(elem: Capability, cs: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context) = checkOK( - ccState.test(elem.singletonCaptureSet.subCaptures(cs)), + TypeComparer.compareResult(elem.singletonCaptureSet.subCaptures(cs)), i"$elem cannot be referenced here; it is not", elem, cs, pos, provenance) @@ -388,8 +400,8 @@ class CheckCaptures extends Recheck, SymTransformer: def checkSubset(cs1: CaptureSet, cs2: CaptureSet, pos: SrcPos, provenance: => String = "", cs1description: String = "")(using Context) = checkOK( - ccState.test(cs1.subCaptures(cs2)), - if cs1.elems.size == 1 then i"reference ${cs1.elems.toList.head}$cs1description is not" + TypeComparer.compareResult(cs1.subCaptures(cs2)), + if cs1.elems.size == 1 then i"reference ${cs1.elems.nth(0)}$cs1description is not" else i"references $cs1$cs1description are not all", cs1, cs2, pos, provenance) @@ -426,11 +438,13 @@ class CheckCaptures extends Recheck, SymTransformer: else i"\nof the enclosing ${owner.showLocated}" - /** Does the given environment belong to a method that is (a) nested in a term + /** Under deferredReaches: + * Does the given environment belong to a method that is (a) nested in a term * and (b) not the method of an anonymous function? */ def isOfNestedMethod(env: Env | Null)(using Context) = - env != null + ccConfig.deferredReaches + && env != null && env.owner.is(Method) && env.owner.owner.isTerm && !env.owner.isAnonymousFunction @@ -441,8 +455,8 @@ class CheckCaptures extends Recheck, SymTransformer: def markFree(sym: Symbol, tree: Tree)(using Context): Unit = markFree(sym, sym.termRef, tree) - def markFree(sym: Symbol, ref: CaptureRef, tree: Tree)(using Context): Unit = - if sym.exists && ref.isTracked then markFree(ref.captureSet, tree) + def markFree(sym: Symbol, ref: Capability, tree: Tree)(using Context): Unit = + if sym.exists && ref.isTracked then markFree(ref.singletonCaptureSet, tree) /** Make sure the (projected) `cs` is a subset of the capture sets of all enclosing * environments. At each stage, only include references from `cs` that are outside @@ -459,74 +473,54 @@ class CheckCaptures extends Recheck, SymTransformer: !sym.isContainedIn(env.owner) } - /** If captureRef `c` refers to a parameter that is not @use declared, report an error. - * Exception under deferredReaches: If use comes from a nested closure, accept it. - */ - def checkUseDeclared(c: CaptureRef, env: Env, lastEnv: Env | Null) = - if lastEnv != null && env.nestedClosure.exists && env.nestedClosure == lastEnv.owner then - assert(ccConfig.deferredReaches) // access is from a nested closure under deferredReaches, so it's OK - else c.pathRoot match - case ref: NamedType if !ref.symbol.isUseParam => - val what = if ref.isType then "Capture set parameter" else "Local reach capability" - report.error( - em"""$what $c leaks into capture scope of ${env.ownerString}. - |To allow this, the ${ref.symbol} should be declared with a @use annotation""", tree.srcPos) - case _ => - /** Avoid locally defined capability by charging the underlying type * (which may not be cap). This scheme applies only under the deferredReaches setting. */ - def avoidLocalCapability(c: CaptureRef, env: Env, lastEnv: Env | Null): Unit = + def avoidLocalCapability(c: Capability, env: Env, lastEnv: Env | Null): Unit = if c.isParamPath then c match - case ReachCapability(_) | _: TypeRef => - checkUseDeclared(c, env, lastEnv) + case Reach(_) | _: TypeRef => + val accessFromNestedClosure = + lastEnv != null && env.nestedClosure.exists && env.nestedClosure == lastEnv.owner + if !accessFromNestedClosure then + checkUseDeclared(c, tree.srcPos) case _ => else val underlying = c match - case ReachCapability(c1) => - CaptureSet.ofTypeDeeply(c1.widen) - case _ => - CaptureSet.ofType(c.widen, followResult = false) - capt.println(i"Widen reach $c to $underlying in ${env.owner}") - underlying.disallowRootCapability: () => + case Reach(c1) => CaptureSet.ofTypeDeeply(c1.widen) + case _ => c.core match + case c1: RootCapability => c1.singletonCaptureSet + case c1: CoreCapability => + CaptureSet.ofType(c1.widen, followResult = false) + capt.println(i"Widen reach $c to $underlying in ${env.owner}") + underlying.disallowRootCapability(NoSymbol): () => report.error(em"Local capability $c in ${env.ownerString} cannot have `cap` as underlying capture set", tree.srcPos) recur(underlying, env, lastEnv) /** Avoid locally defined capability if it is a reach capability or capture set * parameter. This is the default. */ - def avoidLocalReachCapability(c: CaptureRef, env: Env): Unit = c match - case ReachCapability(c1) => - if c1.isParamPath then - checkUseDeclared(c, env, null) - else - // When a reach capabilty x* where `x` is not a parameter goes out - // of scope, we need to continue with `x`'s underlying deep capture set. - // It is an error if that set contains cap. - // The same is not an issue for normal capabilities since in a local - // definition `val x = e`, the capabilities of `e` have already been charged. - // Note: It's not true that the underlying capture set of a reach capability - // is always cap. Reach capabilities over paths depend on the prefix, which - // might turn a cap into something else. - // The path-use.scala neg test contains an example. - val underlying = CaptureSet.ofTypeDeeply(c1.widen) - capt.println(i"Widen reach $c to $underlying in ${env.owner}") - if ccConfig.useSepChecks then - recur(underlying.filter(!_.isRootCapability), env, null) - // we don't want to disallow underlying Fresh instances, since these are typically locally created - // fresh capabilities. We don't need to also follow the hidden set since separation - // checking makes ure that locally hidden references need to go to @consume parameters. - else - underlying.disallowRootCapability: () => - report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", tree.srcPos) - recur(underlying, env, null) - case c: TypeRef if c.isParamPath => - checkUseDeclared(c, env, null) + def avoidLocalReachCapability(c: Capability, env: Env): Unit = c match + case Reach(c1) if !c1.isParamPath => + // Parameter reaches are rejected in checkEscapingUses. + // When a reach capabilty x* where `x` is not a parameter goes out + // of scope, we need to continue with `x`'s underlying deep capture set. + // It is an error if that set contains cap. + // The same is not an issue for normal capabilities since in a local + // definition `val x = e`, the capabilities of `e` have already been charged. + // Note: It's not true that the underlying capture set of a reach capability + // is always cap. Reach capabilities over paths depend on the prefix, which + // might turn a cap into something else. + // The path-use.scala neg test contains an example. + val underlying = CaptureSet.ofTypeDeeply(c1.widen) + capt.println(i"Widen reach $c to $underlying in ${env.owner}") + recur(underlying.filter(!_.isTerminalCapability), env, null) + // we don't want to disallow underlying Fresh instances, since these are typically locally created + // fresh capabilities. We do check that they hide no parameter reach caps in checkEscapingUses case _ => def recur(cs: CaptureSet, env: Env, lastEnv: Env | Null): Unit = - if env.isOpen && !env.owner.isStaticOwner && !cs.isAlwaysEmpty then + if env.kind != EnvKind.Boxed && !env.owner.isStaticOwner && !cs.isAlwaysEmpty then // Only captured references that are visible from the environment // should be included. val included = cs.filter: c => @@ -540,23 +534,46 @@ class CheckCaptures extends Recheck, SymTransformer: capt.println(i"Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}") if !isOfNestedMethod(env) then recur(included, nextEnvToCharge(env, !_.owner.isStaticOwner), env) - // Don't propagate out of methods inside terms. The use set of these methods - // will be charged when that method is called. + // Under deferredReaches, don't propagate out of methods inside terms. + // The use set of these methods will be charged when that method is called. recur(cs, curEnv, null) - usedSet(tree) = tree.markedFree ++ cs + useInfos += ((tree, cs, curEnv)) end markFree + /** If capability `c` refers to a parameter that is not @use declared, report an error. + */ + def checkUseDeclared(c: Capability, pos: SrcPos)(using Context): Unit = + c.paramPathRoot match + case ref: NamedType if !ref.symbol.isUseParam => + val what = if ref.isType then "Capture set parameter" else "Local reach capability" + val owner = ref.symbol.owner + val ownerStr = if owner.isAnonymousFunction then "enclosing function" else owner.show + report.error( + em"""$what $c leaks into capture scope of $ownerStr. + |To allow this, the ${ref.symbol} should be declared with a @use annotation""", pos) + case _ => + /** Include references captured by the called method in the current environment stack */ def includeCallCaptures(sym: Symbol, resType: Type, tree: Tree)(using Context): Unit = resType match case _: MethodOrPoly => // wait until method is fully applied case _ => - def isRetained(ref: CaptureRef): Boolean = ref.pathRoot match + def isRetained(ref: Capability): Boolean = ref.pathRoot match case root: ThisType => ctx.owner.isContainedIn(root.cls) case _ => true - if sym.exists && curEnv.isOpen then + if sym.exists && curEnv.kind != EnvKind.Boxed then markFree(capturedVars(sym).filter(isRetained), tree) + /** If `tp` (possibly after widening singletons) is an ExprType + * of a parameterless method, map Result instances in it to Fresh instances + */ + def mapResultRoots(tp: Type, sym: Symbol)(using Context): Type = + tp.widenSingleton match + case tp: ExprType if sym.is(Method) => + resultToFresh(tp, Origin.ResultInstance(tp, sym)) + case _ => + tp + /** Under the sealed policy, disallow the root capability in type arguments. * Type arguments come either from a TypeApply node or from an AppliedType * which represents a trait parent in a template. @@ -567,7 +584,7 @@ class CheckCaptures extends Recheck, SymTransformer: * @param args the type arguments */ def disallowCapInTypeArgs(fn: Tree, sym: Symbol, args: List[Tree])(using Context): Unit = - def isExempt = sym.isTypeTestOrCast || sym == defn.Compiletime_erasedValue + def isExempt = sym.isTypeTestOrCast || defn.capsErasedValueMethods.contains(sym) if !isExempt then val paramNames = atPhase(thisPhase.prev): fn.tpe.widenDealias match @@ -581,7 +598,7 @@ class CheckCaptures extends Recheck, SymTransformer: def where = if sym.exists then i" in an argument of $sym" else "" val (addendum, errTree) = if arg.isInferred - then ("\nThis is often caused by a local capability$where\nleaking as part of its result.", fn) + then (i"\nThis is often caused by a local capability$where\nleaking as part of its result.", fn) else if arg.span.exists then ("", arg) else ("", fn) disallowRootCapabilitiesIn(arg.nuType, NoSymbol, @@ -606,7 +623,7 @@ class CheckCaptures extends Recheck, SymTransformer: // modifier implied by the expected type `pt`. // Example: If we have `x` and the expected type says we select that with `.a.b` // where `b` is a read-only method, we charge `x.a.b.rd` instead of `x`. - def addSelects(ref: TermRef, pt: Type): CaptureRef = pt match + def addSelects(ref: TermRef, pt: Type): Capability = pt match case pt: PathSelectionProto if ref.isTracked => if pt.sym.isReadOnlyMethod then ref.readOnly @@ -615,11 +632,11 @@ class CheckCaptures extends Recheck, SymTransformer: // class SerializationProxy in stdlib-cc/../LazyListIterable.scala has an example where this matters. addSelects(ref.select(pt.sym).asInstanceOf[TermRef], pt.pt) case _ => ref - var pathRef: CaptureRef = addSelects(sym.termRef, pt) - if pathRef.derivesFrom(defn.Caps_Mutable) && pt.isValueType && !pt.isMutableType then + var pathRef: Capability = addSelects(sym.termRef, pt) + if pathRef.derivesFromMutable && pt.isValueType && !pt.isMutableType then pathRef = pathRef.readOnly markFree(sym, pathRef, tree) - super.recheckIdent(tree, pt) + mapResultRoots(super.recheckIdent(tree, pt), tree.symbol) /** The expected type for the qualifier of a selection. If the selection * could be part of a capability path or is a a read-only method, we return @@ -666,15 +683,16 @@ class CheckCaptures extends Recheck, SymTransformer: |since its capture set ${qualType.captureSet} is read-only""", tree.srcPos) - val selType = recheckSelection(tree, qualType, name, disambiguate) + val selType = mapResultRoots(recheckSelection(tree, qualType, name, disambiguate), tree.symbol) val selWiden = selType.widen // Don't apply the rule // - on the LHS of assignments, or // - if the qualifier or selection type is boxed, or - // - the selection is either a trackable capture ref or a pure type + // - the selection is either a trackable capture reference or a pure type if noWiden(selType, pt) || qualType.isBoxedCapturing + || selType.isBoxedCapturing || selWiden.isBoxedCapturing || selType.isTrackableRef || selWiden.captureSet.isAlwaysEmpty @@ -695,19 +713,6 @@ class CheckCaptures extends Recheck, SymTransformer: selType }//.showing(i"recheck sel $tree, $qualType = $result") - /** Hook for massaging a function before it is applied. Copies all @use and @consume - * annotations on method parameter symbols to the corresponding paramInfo types. - */ - override def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = - val paramInfosWithUses = - funtpe.paramInfos.zipWithConserve(funtpe.paramNames): (formal, pname) => - val param = meth.paramNamed(pname) - def copyAnnot(tp: Type, cls: ClassSymbol) = param.getAnnotation(cls) match - case Some(ann) => AnnotatedType(tp, ann) - case _ => tp - copyAnnot(copyAnnot(formal, defn.UseAnnot), defn.ConsumeAnnot) - funtpe.derivedLambdaType(paramInfos = paramInfosWithUses) - /** Recheck applications, with special handling of unsafeAssumePure. * More work is done in `recheckApplication`, `recheckArg` and `instantiate` below. */ @@ -715,7 +720,7 @@ class CheckCaptures extends Recheck, SymTransformer: val meth = tree.fun.symbol if meth == defn.Caps_unsafeAssumePure then val arg :: Nil = tree.args: @unchecked - val argType0 = recheck(arg, pt.stripCapturing.capturing(root.Fresh())) + val argType0 = recheck(arg, pt.stripCapturing.capturing(FreshCap(Origin.UnsafeAssumePure))) val argType = if argType0.captureSet.isAlwaysEmpty then argType0 else argType0.widen.stripCapturing @@ -730,12 +735,13 @@ class CheckCaptures extends Recheck, SymTransformer: * occurrences are replaced by `Fresh` instances. Also, if formal parameter carries a `@use`, * charge the deep capture set of the actual argument to the environment. */ - protected override def recheckArg(arg: Tree, formal: Type)(using Context): Type = - val freshenedFormal = root.capToFresh(formal) + protected override def recheckArg(arg: Tree, formal: Type, pref: ParamRef, app: Apply)(using Context): Type = + val freshenedFormal = capToFresh(formal, Origin.Formal(pref, app)) val argType = recheck(arg, freshenedFormal) .showing(i"recheck arg $arg vs $freshenedFormal = $result", capt) if formal.hasAnnotation(defn.UseAnnot) || formal.hasAnnotation(defn.ConsumeAnnot) then - // The @use and/or @consume annotation is added to `formal` by `prepareFunction` + // The @use and/or @consume annotation is added to `formal` when creating methods types. + // See [[MethodTypeCompanion.adaptParamInfo]]. capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}") markFree(argType.deepCaptureSet, arg) if formal.containsCap then @@ -765,7 +771,9 @@ class CheckCaptures extends Recheck, SymTransformer: */ protected override def recheckApplication(tree: Apply, qualType: Type, funType: MethodType, argTypes: List[Type])(using Context): Type = - val appType = root.resultToFresh(super.recheckApplication(tree, qualType, funType, argTypes)) + val appType = resultToFresh( + super.recheckApplication(tree, qualType, funType, argTypes), + Origin.ResultInstance(funType, tree.symbol)) val qualCaptures = qualType.captureSet val argCaptures = for (argType, formal) <- argTypes.lazyZip(funType.paramInfos) yield @@ -818,20 +826,21 @@ class CheckCaptures extends Recheck, SymTransformer: * * Second half: union of initial capture set and all capture sets of arguments * to tracked parameters. The initial capture set `initCs` is augmented with - * - root.Fresh(...) if `core` extends Mutable - * - root.Fresh(...).rd if `core` extends Capability + * - FreshCap(...) if `core` extends Mutable + * - FreshCap(...).rd if `core` extends Capability */ def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) = var refined: Type = core var allCaptures: CaptureSet = - if core.derivesFromMutable then initCs ++ CaptureSet.fresh() - else if core.derivesFromCapability then initCs ++ root.Fresh.withOwner(core.classSymbol).readOnly.singletonCaptureSet + if core.derivesFromMutable then + initCs ++ FreshCap(Origin.NewMutable(core)).singletonCaptureSet + else if core.derivesFromCapability then + initCs ++ FreshCap(Origin.NewCapability(core)).readOnly.singletonCaptureSet else initCs for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol if !getter.is(Private) && getter.hasTrackedParts then - refined = RefinedType(refined, getterName, - AnnotatedType(argType.unboxed, Annotation(defn.RefineOverrideAnnot, util.Spans.NoSpan))) // Yichen you might want to check this + refined = refined.refinedOverride(getterName, argType.unboxed) // Yichen you might want to check this allCaptures ++= argType.captureSet (refined, allCaptures) @@ -865,8 +874,10 @@ class CheckCaptures extends Recheck, SymTransformer: val meth = tree.fun match case fun @ Select(qual, nme.apply) => qual.symbol.orElse(fun.symbol) case fun => fun.symbol + def methDescr = if meth.exists then i"$meth's type " else "" disallowCapInTypeArgs(tree.fun, meth, tree.args) - val res = root.resultToFresh(super.recheckTypeApply(tree, pt)) + val funType = super.recheckTypeApply(tree, pt) + val res = resultToFresh(funType, Origin.ResultInstance(funType, meth)) includeCallCaptures(tree.symbol, res, tree) checkContains(tree) res @@ -880,16 +891,13 @@ class CheckCaptures extends Recheck, SymTransformer: val cs = csArg.nuType.captureSet val ref = refArg.nuType capt.println(i"check contains $cs , $ref") - ref match - case ref: CaptureRef if ref.isTracked => + ref.stripCapturing match + case ref: Capability if ref.isTracked => checkElem(ref, cs, tree.srcPos) case _ => report.error(em"$refArg is not a tracked capability", refArg.srcPos) case _ => - override def recheckBlock(tree: Block, pt: Type)(using Context): Type = - ccState.inNestedLevel(super.recheckBlock(tree, pt)) - /** Recheck Closure node: add the captured vars of the anonymoys function * to the result type. See also `recheckClosureBlock` which rechecks the * block containing the anonymous function and the Closure node. @@ -916,7 +924,7 @@ class CheckCaptures extends Recheck, SymTransformer: assert(params.hasSameLengthAs(argTypes), i"$mdef vs $pt, ${params}") for (argType, param) <- argTypes.lazyZip(params) do val paramTpt = param.asInstanceOf[ValDef].tpt - val paramType = root.freshToCap(paramTpt.nuType) + val paramType = freshToCap(paramTpt.nuType) checkConformsExpr(argType, paramType, param) .showing(i"compared expected closure formal $argType against $param with ${paramTpt.nuType}", capt) if ccConfig.preTypeClosureResults && !(isEtaExpansion(mdef) && ccConfig.handleEtaExpansionsSpecially) then @@ -929,14 +937,14 @@ class CheckCaptures extends Recheck, SymTransformer: // neg-custom-args/captures/vars.scala. That's why this code is conditioned. // to apply only to closures that are not eta expansions. assert(paramss1.isEmpty) - val respt = root.resultToFresh: - pt match - case defn.RefinedFunctionOf(rinfo) => - val paramTypes = params.map(_.asInstanceOf[ValDef].tpt.nuType) - rinfo.instantiate(paramTypes) - case _ => - resType - val res = root.resultToFresh(mdef.tpt.nuType) + val respt0 = pt match + case defn.RefinedFunctionOf(rinfo) => + val paramTypes = params.map(_.asInstanceOf[ValDef].tpt.nuType) + rinfo.instantiate(paramTypes) + case _ => + resType + val respt = resultToFresh(respt0, Origin.LambdaExpected(respt0)) + val res = resultToFresh(mdef.tpt.nuType, Origin.LambdaActual(mdef.tpt.nuType)) // We need to open existentials here in order not to get vars mixed up in them // We do the proper check with existentials when we are finished with the closure block. capt.println(i"pre-check closure $expr of type $res against $respt") @@ -949,7 +957,12 @@ class CheckCaptures extends Recheck, SymTransformer: // TODO follow up on this try matchParams(mdef.paramss, pt) - recheckDef(mdef, mdef.symbol) + capt.println(i"recheck closure block $mdef: ${mdef.symbol.infoOrCompleter}") + if !mdef.symbol.isCompleted then + mdef.symbol.ensureCompleted() // this will recheck def + else + recheckDef(mdef, mdef.symbol) + recheckClosure(expr, pt, forceDependent = true) finally openClosures = openClosures.tail @@ -972,7 +985,7 @@ class CheckCaptures extends Recheck, SymTransformer: if sym.is(Module) then sym.info // Modules are checked by checking the module class else if sym.is(Mutable) && !sym.hasAnnotation(defn.UncheckedCapturesAnnot) then - val (carrier, addendum) = capturedBy.get(sym) match + val addendum = capturedBy.get(sym) match case Some(encl) => val enclStr = if encl.isAnonymousFunction then @@ -981,11 +994,11 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => "" s"an anonymous function$location" else encl.show - (NoSymbol, i"\n\nNote that $sym does not count as local since it is captured by $enclStr") + i"\n\nNote that $sym does not count as local since it is captured by $enclStr" case _ => - (sym, "") + "" disallowRootCapabilitiesIn( - tree.tpt.nuType, carrier, i"Mutable $sym", "have type", addendum, sym.srcPos) + tree.tpt.nuType, NoSymbol, i"Mutable $sym", "have type", addendum, sym.srcPos) checkInferredResult(super.recheckValDef(tree, sym), tree) finally if !sym.is(Param) then @@ -1054,14 +1067,24 @@ class CheckCaptures extends Recheck, SymTransformer: def canUseInferred = // If canUseInferred is false, all capturing types in the type of `sym` need to be given explicitly sym.isLocalToCompilationUnit // Symbols that can't be seen outside the compilation unit can always have inferred types - || sym.privateWithin == defn.EmptyPackageClass - // We make an exception for private symbols in a toplevel file in the empty package + || ctx.owner.enclosingPackageClass.isEmptyPackage + // We make an exception for symbols in the empty package. // these could theoretically be accessed from other files in the empty package, but - // it would be too annoying to require explicit types. + // usually it would be too annoying to require explicit types. || sym.name.is(DefaultGetterName) // Default getters are exempted since otherwise it would be // too annoying. This is a hole since a defualt getter's result type // might leak into a type variable. + def fail(tree: Tree, expected: Type, addenda: Addenda): Unit = + def maybeResult = if sym.is(Method) then " result" else "" + report.error( + em"""$sym needs an explicit$maybeResult type because the inferred type does not conform to + |the type that is externally visible in other compilation units. + | + | Inferred type : ${tree.tpe} + | Externally visible type: $expected""", + tree.srcPos) + def addenda(expected: Type) = new Addenda: override def toAdd(using Context) = def result = if tree.isInstanceOf[ValDef] then"" else " result" @@ -1076,9 +1099,12 @@ class CheckCaptures extends Recheck, SymTransformer: tree.tpt match case tpt: InferredTypeTree if !canUseInferred => val expected = tpt.tpe.dropAllRetains - todoAtPostCheck += (() => checkConformsExpr(tp, expected, tree.rhs, addenda(expected))) - // The check that inferred <: expected is done after recheck so that it - // does not interfere with normal rechecking by constraining capture set variables. + todoAtPostCheck += { () => + withCapAsRoot: + testAdapted(tp, expected, tree.rhs, addenda(expected))(fail) + // The check that inferred <: expected is done after recheck so that it + // does not interfere with normal rechecking by constraining capture set variables. + } case _ => tp end checkInferredResult @@ -1096,7 +1122,6 @@ class CheckCaptures extends Recheck, SymTransformer: try // Setup environment to reflect the new owner. val envForOwner: Map[Symbol, Env] = curEnv.outersIterator - .takeWhile(e => !capturedVars(e.owner).isAlwaysEmpty) // no refs can leak beyond this point .map(e => (e.owner, e)) .toMap def restoreEnvFor(sym: Symbol): Env = @@ -1112,6 +1137,10 @@ class CheckCaptures extends Recheck, SymTransformer: finally curEnv = saved + override def recheckTypeDef(tree: TypeDef, sym: Symbol)(using Context): Type = + try super.recheckTypeDef(tree, sym) + finally completed += sym + /** Recheck classDef by enforcing the following class-specific capture set relations: * 1. The capture set of a class includes the capture sets of its parents. * 2. The capture set of the self type of a class includes the capture set of the class. @@ -1123,20 +1152,20 @@ class CheckCaptures extends Recheck, SymTransformer: * is already done in the TypeApply. */ override def recheckClassDef(tree: TypeDef, impl: Template, cls: ClassSymbol)(using Context): Type = + if Feature.enabled(Feature.separationChecking) then sepChecksEnabled = true val localSet = capturedVars(cls) for parent <- impl.parents do // (1) checkSubset(capturedVars(parent.tpe.classSymbol), localSet, parent.srcPos, i"\nof the references allowed to be captured by $cls") val saved = curEnv - if localSet ne CaptureSet.empty then - curEnv = Env(cls, EnvKind.Regular, localSet, curEnv) + curEnv = Env(cls, EnvKind.Regular, localSet, curEnv) try val thisSet = cls.classInfo.selfType.captureSet.withDescription(i"of the self type of $cls") checkSubset(localSet, thisSet, tree.srcPos) // (2) for param <- cls.paramGetters do if !param.hasAnnotation(defn.ConstructorOnlyAnnot) - && !param.hasAnnotation(defn.UntrackedCapturesAnnot) then - CCState.withCapAsRoot: // OK? We need this here since self types use `cap` instead of `fresh` + && !param.hasAnnotation(defn.UntrackedCapturesAnnot) then + withCapAsRoot: // OK? We need this here since self types use `cap` instead of `fresh` checkSubset(param.termRef.captureSet, thisSet, param.srcPos) // (3) for pureBase <- cls.pureBaseClass do // (4) def selfTypeTree = impl.body @@ -1156,6 +1185,7 @@ class CheckCaptures extends Recheck, SymTransformer: ccState.inNestedLevelUnless(cls.is(Module)): super.recheckClassDef(tree, impl, cls) finally + completed += cls curEnv = saved /** If type is of the form `T @requiresCapability(x)`, @@ -1176,7 +1206,12 @@ class CheckCaptures extends Recheck, SymTransformer: * result type of a try */ override def recheckTry(tree: Try, pt: Type)(using Context): Type = - val tp = super.recheckTry(tree, pt) + val tryOwner = Setup.firstCanThrowEvidence(tree.expr) match + case Some(vd) => vd.symbol.owner + case None => ctx.owner + val bodyType = inContext(ctx.withOwner(tryOwner)): + recheck(tree.expr, pt) + val tp = recheckTryRest(bodyType, tree.cases, tree.finalizer, pt) if Feature.enabled(Feature.saferExceptions) then disallowRootCapabilitiesIn(tp, ctx.owner, "The result of `try`", "have type", @@ -1211,7 +1246,8 @@ class CheckCaptures extends Recheck, SymTransformer: val saved = curEnv tree match case _: RefTree | closureDef(_) if pt.isBoxedCapturing => - curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner, level = ccState.currentLevel), curEnv) + curEnv = Env(curEnv.owner, EnvKind.Boxed, + CaptureSet.Var(curEnv.owner, level = ccState.currentLevel), curEnv) case _ => val res = try @@ -1241,36 +1277,42 @@ class CheckCaptures extends Recheck, SymTransformer: type BoxErrors = mutable.ListBuffer[Message] | Null - private def errorNotes(notes: List[ErrorNote])(using Context): Addenda = - if notes.isEmpty then NothingToAdd + private def errorNotes(notes: List[TypeComparer.ErrorNote])(using Context): Addenda = + val printableNotes = notes.filter: + case IncludeFailure(_, _, true) => true + case _: ExistentialSubsumesFailure | _: MutAdaptFailure => true + case _ => false + if printableNotes.isEmpty then NothingToAdd else new Addenda: - override def toAdd(using Context) = notes.map: note => + override def toAdd(using Context) = printableNotes.map: note => val msg = note match - case CompareResult.LevelError(cs, ref) => - if ref.stripReadOnly.isCapOrFresh then - def capStr = if ref.isReadOnly then "cap.rd" else "cap" - i"""the universal capability `$capStr` + case IncludeFailure(cs, ref, _) => + if ref.core.isCapOrFresh then + i"""the universal capability $ref |cannot be included in capture set $cs""" else val levelStr = ref match case ref: TermRef => i", defined in ${ref.symbol.maybeOwner}" case _ => "" i"""reference ${ref}$levelStr - |cannot be included in outer capture set $cs""" + |cannot be included in outer capture set $cs""" case ExistentialSubsumesFailure(ex, other) => def since = - if other.isRootCapability then "" - else " since that capability is not a SharedCapability" - i"""the existential capture root in ${ex.rootAnnot.originalBinder.resType} + if other.isTerminalCapability then "" + else " since that capability is not a `Sharable` capability" + i"""the existential capture root in ${ex.originalBinder.resType} |cannot subsume the capability $other$since""" + case MutAdaptFailure(cs, lo, hi) => + def ofType(tp: Type) = if tp.exists then i"of the mutable type $tp" else "of a mutable type" + i"""$cs is an exclusive capture set ${ofType(hi)}, + |it cannot subsume a read-only capture set ${ofType(lo)}.""" i""" - | |Note that ${msg.toString}""" /** Addendas for error messages that show where we have under-approximated by - * mapping a a capture ref in contravariant position to the empty set because - * the original result type of the map was not itself a capture ref. + * mapping a a capability in contravariant position to the empty set because + * the original result type of the map was not itself a capability. */ private def addApproxAddenda(using Context) = new TypeAccumulator[Addenda]: @@ -1299,37 +1341,38 @@ class CheckCaptures extends Recheck, SymTransformer: * where local capture roots are instantiated to root variables. */ override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Type = + testAdapted(actual, expected, tree, addenda)(err.typeMismatch) + + inline def testAdapted(actual: Type, expected: Type, tree: Tree, addenda: Addenda) + (fail: (Tree, Type, Addenda) => Unit)(using Context): Type = var expected1 = alignDependentFunction(expected, actual.stripCapturing) + val falseDeps = expected1 ne expected val actualBoxed = adapt(actual, expected1, tree) //println(i"check conforms $actualBoxed <<< $expected1") if actualBoxed eq actual then // Only `addOuterRefs` when there is no box adaptation expected1 = addOuterRefs(expected1, actual, tree.srcPos) - ccState.testOK(isCompatible(actualBoxed, expected1)) match - case CompareResult.OK => - if debugSuccesses then tree match - case Ident(_) => - println(i"SUCCESS $tree for $actual <:< $expected:\n${TypeComparer.explained(_.isSubType(actualBoxed, expected1))}") - case _ => - actualBoxed - case fail: CompareFailure => + TypeComparer.compareResult(isCompatible(actualBoxed, expected1)) match + case TypeComparer.CompareResult.Fail(notes) => capt.println(i"conforms failed for ${tree}: $actual vs $expected") - inContext(root.printContext(actualBoxed, expected1)): - err.typeMismatch(tree.withType(actualBoxed), expected1, - addApproxAddenda( - addenda ++ errorNotes(fail.errorNotes), - expected1)) + if falseDeps then expected1 = unalignFunction(expected1) + fail(tree.withType(actualBoxed), expected1, + addApproxAddenda(addenda ++ errorNotes(notes), expected1)) actual - end checkConformsExpr + case /*OK*/ _ => + if debugSuccesses then tree match + case Ident(_) => + println(i"SUCCESS $tree for $actual <:< $expected:\n${TypeComparer.explained(_.isSubType(actualBoxed, expected1))}") + case _ => + actualBoxed + end testAdapted /** Turn `expected` into a dependent function when `actual` is dependent. */ private def alignDependentFunction(expected: Type, actual: Type)(using Context): Type = def recur(expected: Type): Type = expected.dealias match - case expected0 @ CapturingType(eparent, refs) => - val eparent1 = recur(eparent) - if eparent1 eq eparent then expected - else CapturingType(eparent1, refs, boxed = expected0.isBoxed) + case expected @ CapturingType(eparent, refs) => + expected.derivedCapturingType(recur(eparent), refs) case expected @ defn.FunctionOf(args, resultType, isContextual) if defn.isNonRefinedFunction(expected) => actual match @@ -1347,6 +1390,14 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => expected recur(expected) + private def unalignFunction(tp: Type)(using Context): Type = tp match + case tp @ CapturingType(parent, refs) => + tp.derivedCapturingType(unalignFunction(parent), refs) + case defn.RefinedFunctionOf(mt) => + mt.toFunctionType(alwaysDependent = false) + case _ => + tp + /** For the expected type, implement the rule outlined in #14390: * - when checking an expression `a: Ta^Ca` against an expected type `Te^Ce`, * - where the capture set `Ce` contains Cls.this, @@ -1377,7 +1428,7 @@ class CheckCaptures extends Recheck, SymTransformer: // set `arefs` that are outside some `C.this.type` reference in `erefs` for an enclosing // class `C`. If an added reference is not a ThisType itself, add it to the capture set // (i.e. use set) of the `C`. This makes sure that any outer reference implicitly subsumed - // by `C.this` becomes a capture reference of every instance of `C`. + // by `C.this` becomes a capability of every instance of `C`. def augment(erefs: CaptureSet, arefs: CaptureSet): CaptureSet = (erefs /: erefs.elems): (erefs, eref) => eref match @@ -1483,9 +1534,7 @@ class CheckCaptures extends Recheck, SymTransformer: val cs = actual.captureSet if covariant then cs ++ leaked else - if // CCState.withCapAsRoot: // Not sure withCapAsRoot is OK here, actually - !leaked.subCaptures(cs).isOK - then + if !leaked.subCaptures(cs) then report.error( em"""$expected cannot be box-converted to ${actual.capturing(leaked)} |since the additional capture set $leaked resulting from box conversion is not allowed in $actual""", tree.srcPos) @@ -1519,7 +1568,7 @@ class CheckCaptures extends Recheck, SymTransformer: recur(actual, expected, covariant) end adaptBoxed - /** If actual is a tracked CaptureRef `a` and widened is a capturing type T^C, + /** If actual is a tracked Capability `a` and widened is a capturing type T^C, * improve `T^C` to `T^{a}`, following the VAR rule of CC. * TODO: We probably should do this also for other top-level occurrences of captures * E.g. @@ -1529,7 +1578,7 @@ class CheckCaptures extends Recheck, SymTransformer: * foo: Foo { def a: C^{foo}; def b: C^{foo} }^{foo} */ private def improveCaptures(widened: Type, prefix: Type)(using Context): Type = prefix match - case ref: CaptureRef if ref.isTracked => + case ref: Capability if ref.isTracked => widened match case widened @ CapturingType(p, refs) if ref.singletonCaptureSet.mightSubcapture(refs) => val improvedCs = @@ -1548,16 +1597,81 @@ class CheckCaptures extends Recheck, SymTransformer: * to narrow to the read-only set, since that set can be propagated * by the type variable instantiation. */ - private def improveReadOnly(actual: Type, expected: Type)(using Context): Type = actual match - case actual @ CapturingType(parent, refs) - if parent.derivesFrom(defn.Caps_Mutable) - && expected.isValueType - && !expected.isMutableType - && !expected.isSingleton - && !expected.isBoxedCapturing => - actual.derivedCapturingType(parent, refs.readOnly) + private def improveReadOnly(actual: Type, expected: Type)(using Context): Type = reporting.trace(i"improv ro $actual vs $expected"): + actual.dealiasKeepAnnots match + case actual @ CapturingType(parent, refs) => + val parent1 = improveReadOnly(parent, expected) + val refs1 = + if parent1.derivesFrom(defn.Caps_Mutable) + && expected.isValueType + && (!expected.derivesFromMutable || expected.captureSet.isAlwaysReadOnly) + && !expected.isSingleton + && actual.isBoxedCapturing == expected.isBoxedCapturing + then refs.readOnly + else refs + actual.derivedCapturingType(parent1, refs1) + case actual @ FunctionOrMethod(aargs, ares) => + expected.dealias.stripCapturing match + case FunctionOrMethod(eargs, eres) => + actual.derivedFunctionOrMethod(aargs, improveReadOnly(ares, eres)) + case _ => + actual + case actual @ AppliedType(atycon, aargs) => + def improveArgs(aargs: List[Type], eargs: List[Type], formals: List[ParamInfo]): List[Type] = + aargs match + case aargs @ (aarg :: aargs1) => + val aarg1 = + if formals.head.paramVariance.is(Covariant) + then improveReadOnly(aarg, eargs.head) + else aarg + aargs.derivedCons(aarg1, improveArgs(aargs1, eargs.tail, formals.tail)) + case Nil => + aargs + val expected1 = expected.dealias.stripCapturing + val esym = expected1.typeSymbol + expected1 match + case AppliedType(etycon, eargs) => + if atycon.typeSymbol == esym then + actual.derivedAppliedType(atycon, + improveArgs(aargs, eargs, etycon.typeParams)) + else if esym.isClass then + // This case is tricky: Try to lift actual to the base type with class `esym`, + // improve the resulting arguments, and figure out if anything can be + // deduced from that for the original arguments. + actual.baseType(esym) match + case base @ AppliedType(_, bargs) => + // If any of the base type arguments can be improved, check + // whether they are the same as an original argument, and in this + // case improve the original argument. + val iargs = improveArgs(bargs, eargs, etycon.typeParams) + if iargs ne bargs then + val updates = + for + (barg, iarg) <- bargs.lazyZip(iargs) + if barg ne iarg + aarg <- aargs.find(_ eq barg) + yield (aarg, iarg) + if updates.nonEmpty then AppliedType(atycon, aargs.map(updates.toMap)) + else actual + else actual + case _ => actual + else actual + case _ => + actual + case actual @ RefinedType(aparent, aname, ainfo) => + expected.dealias.stripCapturing match + case RefinedType(eparent, ename, einfo) if aname == ename => + actual.derivedRefinedType( + improveReadOnly(aparent, eparent), + aname, + improveReadOnly(ainfo, einfo)) + case _ => + actual + case actual @ AnnotatedType(parent, ann) => + actual.derivedAnnotatedType(improveReadOnly(parent, expected), ann) case _ => actual + end improveReadOnly /* Currently not needed since it forms part of `adapt` private def improve(actual: Type, prefix: Type)(using Context): Type = @@ -1573,7 +1687,7 @@ class CheckCaptures extends Recheck, SymTransformer: actual.isSingleton && expected.match case expected: PathSelectionProto => !expected.sym.isOneOf(UnstableValueFlags) - case _ => expected.isSingleton || expected == LhsProto + case _ => expected.stripCapturing.isSingleton || expected == LhsProto /** Adapt `actual` type to `expected` type. This involves: * - narrow toplevel captures of `x`'s underlying type to `{x}` according to CC's VAR rule @@ -1582,9 +1696,19 @@ class CheckCaptures extends Recheck, SymTransformer: */ def adapt(actual: Type, expected: Type, tree: Tree)(using Context): Type = if noWiden(actual, expected) then - actual + expected match + case expected @ CapturingType(_, _) if expected.isBoxed => + // actual is a singleton type and expected is of the form box x.type^cs. + // Convert actual to the same form. + actual.boxDeeply + .showing(i"adapt single $actual / $result vs $expected", capt) + case _ => + actual else - val improvedVAR = improveCaptures(actual.widen.dealiasKeepAnnots, actual) + // Compute the widened type. Drop `@use` and `@consume` annotations from the type, + // since they obscures the capturing type. + val widened = actual.widen.dealiasKeepAnnots.dropUseAndConsumeAnnots + val improvedVAR = improveCaptures(widened, actual) val improved = improveReadOnly(improvedVAR, expected) val adapted = adaptBoxed( improved.withReachCaptures(actual), expected, tree, @@ -1624,7 +1748,8 @@ class CheckCaptures extends Recheck, SymTransformer: memberTp, otherTp.derivedTypeBounds( otherTp.lo, - hi.derivedCapturingType(parent, root.Fresh().singletonCaptureSet)))) + hi.derivedCapturingType(parent, + CaptureSet.fresh(Origin.OverriddenType(member)))))) case _ => None case _ => None case _ => None @@ -1683,7 +1808,8 @@ class CheckCaptures extends Recheck, SymTransformer: def traverse(t: Tree)(using Context) = t match case t: Template => - checkAllOverrides(ctx.owner.asClass, OverridingPairsCheckerCC(_, _, t)) + withCollapsedFresh: + checkAllOverrides(ctx.owner.asClass, OverridingPairsCheckerCC(_, _, t)) case _ => traverseChildren(t) end checkOverrides @@ -1727,6 +1853,7 @@ class CheckCaptures extends Recheck, SymTransformer: private val setup: SetupAPI = thisPhase.prev.asInstanceOf[Setup] override def checkUnit(unit: CompilationUnit)(using Context): Unit = + ccState.start() setup.setupUnit(unit.tpdTree, this) collectCapturedMutVars.traverse(unit.tpdTree) @@ -1868,6 +1995,48 @@ class CheckCaptures extends Recheck, SymTransformer: traverseChildren(t) check.traverse(tp) + /** Check that no uses refer to reach capabilities of parameters of enclosing + * methods or classes. + */ + def checkEscapingUses()(using Context) = + for (tree, uses, env) <- useInfos do + val seen = util.EqHashSet[Capability]() + + // The owner of the innermost environment of kind Boxed + def boxedOwner(env: Env): Symbol = + if env.kind == EnvKind.Boxed then env.owner + else if isOfNestedMethod(env) then env.owner.owner + else if env.owner.isStaticOwner then NoSymbol + else boxedOwner(nextEnvToCharge(env, alwaysTrue)) + + def checkUseUnlessBoxed(c: Capability, croot: NamedType) = + if !boxedOwner(env).isContainedIn(croot.symbol.owner) then + checkUseDeclared(c, tree.srcPos) + + def check(cs: CaptureSet): Unit = cs.elems.foreach(checkElem) + + def checkElem(c: Capability): Unit = + if !seen.contains(c) then + seen += c + c match + case Reach(c1) => + c1.paramPathRoot match + case croot: NamedType => checkUseUnlessBoxed(c, croot) + case _ => check(CaptureSet.ofTypeDeeply(c1.widen)) + case c: TypeRef => + c.paramPathRoot match + case croot: NamedType => checkUseUnlessBoxed(c, croot) + case _ => + case c: DerivedCapability => + checkElem(c.underlying) + case c: FreshCap => + check(c.hiddenSet) + case _ => + + check(uses) + end for + end checkEscapingUses + /** Check that arguments of TypeApplys and AppliedTypes conform to their bounds. */ def postCheck(unit: tpd.Tree)(using Context): Unit = @@ -1886,7 +2055,7 @@ class CheckCaptures extends Recheck, SymTransformer: val normArgs = args.lazyZip(tl.paramInfos).map: (arg, bounds) => arg.withType(arg.nuType.forceBoxStatus( bounds.hi.isBoxedCapturing | bounds.lo.isBoxedCapturing)) - CCState.withCapAsRoot: // OK? We need this since bounds use `cap` instead of `fresh` + withCollapsedFresh: // OK? We need this since bounds use `cap` instead of `fresh` checkBounds(normArgs, tl) if ccConfig.postCheckCapturesets then args.lazyZip(tl.paramNames).foreach(checkTypeParam(_, _, fun.symbol)) @@ -1896,7 +2065,12 @@ class CheckCaptures extends Recheck, SymTransformer: end checker checker.traverse(unit)(using ctx.withOwner(defn.RootClass)) - if ccConfig.useSepChecks then SepCheck(this).traverse(unit) + checkEscapingUses() + if sepChecksEnabled then + for (tree, cs, env) <- useInfos do + usedSet(tree) = tree.markedFree ++ cs + ccState.inSepCheck: + SepCheck(this).traverse(unit) if !ctx.reporter.errorsReported then // We dont report errors here if previous errors were reported, because other // errors often result in bad applied types, but flagging these bad types gives @@ -1906,7 +2080,7 @@ class CheckCaptures extends Recheck, SymTransformer: case tree: InferredTypeTree => case tree: New => case tree: TypeTree => - CCState.withCapAsRoot: + withCollapsedFresh: checkAppliedTypesIn(tree.withType(tree.nuType)) case _ => traverseChildren(t) checkApplied.traverse(unit) diff --git a/compiler/src/dotty/tools/dotc/cc/RetainingType.scala b/compiler/src/dotty/tools/dotc/cc/RetainingType.scala index efd0e96fd658..6fde63a0d3ac 100644 --- a/compiler/src/dotty/tools/dotc/cc/RetainingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/RetainingType.scala @@ -8,28 +8,22 @@ import ast.tpd.* import Annotations.Annotation import Decorators.i -/** A builder and extractor for annotated types with @retains or @retainsByName annotations. +/** A builder and extractor for annotated types with @retains or @retainsByName annotations + * excluding CapturingTypes. */ object RetainingType: - def apply(tp: Type, refs: List[Tree], byName: Boolean = false)(using Context): Type = + def apply(tp: Type, typeElems: Type, byName: Boolean = false)(using Context): Type = val annotCls = if byName then defn.RetainsByNameAnnot else defn.RetainsAnnot - val annotTree = - New(annotCls.typeRef, - Typed( - SeqLiteral(refs, TypeTree(defn.AnyType)), - TypeTree(defn.RepeatedParamClass.typeRef.appliedTo(defn.AnyType))) :: Nil) + val annotTree = New(AppliedType(annotCls.typeRef, typeElems :: Nil), Nil) AnnotatedType(tp, Annotation(annotTree)) - def unapply(tp: AnnotatedType)(using Context): Option[(Type, List[Tree])] = + def unapply(tp: AnnotatedType)(using Context): Option[(Type, Type)] = val sym = tp.annot.symbol if sym.isRetainsLike then tp.annot match - case _: CaptureAnnotation => - assert(ctx.mode.is(Mode.IgnoreCaptures), s"bad retains $tp at ${ctx.phase}") - None - case ann => - Some((tp.parent, ann.tree.retainedElems)) + case _: CaptureAnnotation => None + case ann => Some((tp.parent, ann.tree.retainedSet)) else None end RetainingType diff --git a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala index 6434db0638a3..6989ef21f081 100644 --- a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala +++ b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala @@ -14,6 +14,7 @@ import util.{SimpleIdentitySet, EqHashMap, SrcPos} import tpd.* import reflect.ClassTag import reporting.trace +import Capabilities.* /** The separation checker is a tree traverser that is run after capture checking. * It checks tree nodes for various separation conditions, explained in the @@ -72,7 +73,7 @@ object SepCheck: */ abstract class ConsumedSet: /** The references in the set. The array should be treated as immutable in client code */ - def refs: Array[CaptureRef] + def refs: Array[Capability] /** The associated source positoons. The array should be treated as immutable in client code */ def locs: Array[SrcPos] @@ -80,7 +81,7 @@ object SepCheck: /** The number of references in the set */ def size: Int - def toMap: Map[CaptureRef, SrcPos] = refs.take(size).zip(locs).toMap + def toMap: Map[Capability, SrcPos] = refs.take(size).zip(locs).toMap def show(using Context) = s"[${toMap.map((ref, loc) => i"$ref -> $loc").toList}]" @@ -89,12 +90,12 @@ object SepCheck: /** A fixed consumed set consisting of the given references `refs` and * associated source positions `locs` */ - class ConstConsumedSet(val refs: Array[CaptureRef], val locs: Array[SrcPos]) extends ConsumedSet: + class ConstConsumedSet(val refs: Array[Capability], val locs: Array[SrcPos]) extends ConsumedSet: def size = refs.size /** A mutable consumed set, which is initially empty */ class MutConsumedSet extends ConsumedSet: - var refs: Array[CaptureRef] = new Array(4) + var refs: Array[Capability] = new Array(4) var locs: Array[SrcPos] = new Array(4) var size = 0 var peaks: Refs = emptyRefs @@ -110,12 +111,12 @@ object SepCheck: locs = double(locs) /** If `ref` is in the set, its associated source position, otherwise `null` */ - def get(ref: CaptureRef): SrcPos | Null = + def get(ref: Capability): SrcPos | Null = var i = 0 while i < size && (refs(i) ne ref) do i += 1 if i < size then locs(i) else null - def clashing(ref: CaptureRef)(using Context): SrcPos | Null = + def clashing(ref: Capability)(using Context): SrcPos | Null = val refPeaks = ref.peaks if !peaks.sharedWith(refPeaks).isEmpty then var i = 0 @@ -126,7 +127,7 @@ object SepCheck: else null /** If `ref` is not yet in the set, add it with given source position */ - def put(ref: CaptureRef, loc: SrcPos)(using Context): Unit = + def put(ref: Capability, loc: SrcPos)(using Context): Unit = if get(ref) == null then ensureCapacity(1) refs(size) = ref @@ -164,13 +165,12 @@ object SepCheck: extension (refs: Refs) /** The footprint of a set of references `refs` the smallest set `F` such that - * 1. if includeMax is false then no maximal capability is in `F` - * 2. all capabilities in `refs` satisfying (1) are in `F` - * 3. if `f in F` then the footprint of `f`'s info is also in `F`. + * 1. all capabilities in `refs` satisfying (1) are in `F` + * 2. if `f in F` then the footprint of `f`'s info is also in `F`. */ - private def footprint(includeMax: Boolean = false)(using Context): Refs = - def retain(ref: CaptureRef) = includeMax || !ref.isRootCapability - def recur(elems: Refs, newElems: List[CaptureRef]): Refs = newElems match + private def footprint(using Context): Refs = + def retain(ref: Capability) = !ref.isTerminalCapability + def recur(elems: Refs, newElems: List[Capability]): Refs = newElems match case newElem :: newElems1 => val superElems = newElem.captureSetOfInfo.elems.filter: superElem => retain(superElem) && !elems.contains(superElem) @@ -180,24 +180,24 @@ object SepCheck: recur(elems, elems.toList) private def peaks(using Context): Refs = - def recur(seen: Refs, acc: Refs, newElems: List[CaptureRef]): Refs = trace(i"peaks $acc, $newElems = "): + def recur(seen: Refs, acc: Refs, newElems: List[Capability]): Refs = trace(i"peaks $acc, $newElems = "): newElems match case newElem :: newElems1 => if seen.contains(newElem) then recur(seen, acc, newElems1) - else newElem.stripReadOnly match - case root.Fresh(hidden) => - if hidden.deps.isEmpty then recur(seen + newElem, acc + newElem, newElems1) + else newElem.stripRestricted.stripReadOnly match + case elem: FreshCap => + if elem.hiddenSet.deps.isEmpty then recur(seen + newElem, acc + newElem, newElems1) else val superCaps = - if newElem.isReadOnly then hidden.superCaps.map(_.readOnly) - else hidden.superCaps + if newElem.isReadOnly then elem.hiddenSet.superCaps.map(_.readOnly) + else elem.hiddenSet.superCaps recur(seen + newElem, acc, superCaps ++ newElems) case _ => - if newElem.isRootCapability + if newElem.isTerminalCapability //|| newElem.isInstanceOf[TypeRef | TypeParamRef] then recur(seen + newElem, acc, newElems1) - else recur(seen + newElem, acc, newElem.captureSetOfInfo.elems.toList ++ newElems1) + else recur(seen + newElem, acc, newElem.captureSetOfInfo.dropEmpties().elems.toList ++ newElems1) case Nil => acc recur(emptyRefs, emptyRefs, refs.toList) @@ -235,11 +235,11 @@ object SepCheck: ++ refs1 .filter: - case ReadOnlyCapability(ref @ TermRef(prefix: CaptureRef, _)) => + case ReadOnly(ref @ TermRef(prefix: CoreCapability, _)) => // We can get away testing only references with at least one field selection // here since stripped readOnly references that equal a reference in refs2 // are added by the first clause of the symmetric call to common. - !ref.isCap && refs2.exists(_.covers(prefix)) + refs2.exists(_.covers(prefix)) case _ => false .map(_.stripReadOnly) @@ -252,11 +252,12 @@ object SepCheck: * its hidden set is `{y, z}`. */ private def hiddenSet(using Context): Refs = - val seen: util.EqHashSet[CaptureRef] = new util.EqHashSet + val seen: util.EqHashSet[Capability] = new util.EqHashSet - def hiddenByElem(elem: CaptureRef): Refs = elem match - case root.Fresh(hcs) => hcs.elems ++ recur(hcs.elems) - case ReadOnlyCapability(ref1) => hiddenByElem(ref1).map(_.readOnly) + def hiddenByElem(elem: Capability): Refs = elem match + case elem: FreshCap => elem.hiddenSet.elems ++ recur(elem.hiddenSet.elems) + case Restricted(elem1, cls) => hiddenByElem(elem1).map(_.restrict(cls)) + case ReadOnly(elem1) => hiddenByElem(elem1).map(_.readOnly) case _ => emptyRefs def recur(refs: Refs): Refs = @@ -279,7 +280,7 @@ object SepCheck: end extension - extension (ref: CaptureRef) + extension (ref: Capability) def peaks(using Context): Refs = SimpleIdentitySet(ref).peaks class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: @@ -319,21 +320,20 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: def sharedPeaksStr(shared: Refs)(using Context): String = shared.nth(0) match - case fresh @ root.Fresh(hidden) => - if hidden.owner.exists then i"$fresh of ${hidden.owner}" else i"$fresh" + case fresh: FreshCap => + if fresh.hiddenSet.owner.exists then i"$fresh of ${fresh.hiddenSet.owner}" else i"$fresh" case other => i"$other" def overlapStr(hiddenSet: Refs, clashSet: Refs)(using Context): String = - val hiddenFootprint = hiddenSet.footprint() - val clashFootprint = clashSet.footprint() + val hiddenFootprint = hiddenSet.footprint + val clashFootprint = clashSet.footprint // The overlap of footprints, or, of this empty the set of shared peaks. // We prefer footprint overlap since it tends to be more informative. val overlap = hiddenFootprint.overlapWith(clashFootprint) if !overlap.isEmpty then i"${CaptureSet(overlap)}" else - val sharedPeaks = hiddenSet.footprint(includeMax = true).sharedWith: - clashSet.footprint(includeMax = true) + val sharedPeaks = hiddenSet.peaks.sharedWith(clashSet.peaks) assert(!sharedPeaks.isEmpty, i"no overlap for $hiddenSet vs $clashSet") sharedPeaksStr(sharedPeaks) @@ -385,9 +385,9 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: |Some of these overlap with the captures of the ${clashArgStr.trim}$clashTypeStr. | | Hidden set of current argument : ${CaptureSet(hiddenSet)} - | Hidden footprint of current argument : ${CaptureSet(hiddenSet.footprint())} + | Hidden footprint of current argument : ${CaptureSet(hiddenSet.footprint)} | Capture set of $clashArgStr : ${CaptureSet(clashSet)} - | Footprint set of $clashArgStr : ${CaptureSet(clashSet.footprint())} + | Footprint set of $clashArgStr : ${CaptureSet(clashSet.footprint)} | The two sets overlap at : ${overlapStr(hiddenSet, clashSet)}""", polyArg.srcPos) @@ -419,7 +419,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: * @param loc the position where the capability was consumed * @param pos the position where the capability was used again */ - def consumeError(ref: CaptureRef, loc: SrcPos, pos: SrcPos)(using Context): Unit = + def consumeError(ref: Capability, loc: SrcPos, pos: SrcPos)(using Context): Unit = report.error( em"""Separation failure: Illegal access to $ref, which was passed to a |@consume parameter or was used as a prefix to a @consume method on line ${loc.line + 1} @@ -430,7 +430,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: * @param ref the capability * @param loc the position where the capability was consumed */ - def consumeInLoopError(ref: CaptureRef, pos: SrcPos)(using Context): Unit = + def consumeInLoopError(ref: Capability, pos: SrcPos)(using Context): Unit = report.error( em"""Separation failure: $ref appears in a loop, therefore it cannot |be passed to a @consume parameter or be used as a prefix of a @consume method call.""", @@ -457,14 +457,16 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: * Also check separation via checkType within individual arguments widened to their * formal paramater types. * - * @param fn the applied function - * @param args the flattened argument lists - * @param app the entire application tree - * @param deps cross argument dependencies: maps argument trees to - * those other arguments that where mentioned by coorresponding - * formal parameters. + * @param fn the applied function + * @param args the flattened argument lists + * @param app the entire application tree + * @param deps cross argument dependencies: maps argument trees to + * those other arguments that where mentioned by coorresponding + * formal parameters. + * @param resultPeaks peaks in the result type that could interfere with the + * hidden sets of formal parameters */ - private def checkApply(fn: Tree, args: List[Tree], app: Tree, deps: collection.Map[Tree, List[Tree]])(using Context): Unit = + private def checkApply(fn: Tree, args: List[Tree], app: Tree, deps: collection.Map[Tree, List[Tree]], resultPeaks: Refs)(using Context): Unit = val (qual, fnCaptures) = methPart(fn) match case Select(qual, _) => (qual, qual.nuType.captureSet) case _ => (fn, CaptureSet.empty) @@ -475,6 +477,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: i"""check separate $fn($args), fnCaptures = $fnCaptures, | formalCaptures = ${args.map(arg => CaptureSet(formalCaptures(arg)))}, | actualCaptures = ${args.map(arg => CaptureSet(captures(arg)))}, + | resultPeaks = ${resultPeaks}, | deps = ${deps.toList}""") val parts = qual :: args var reported: SimpleIdentitySet[Tree] = SimpleIdentitySet.empty @@ -519,26 +522,10 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: currentPeaks.hidden ++ argPeaks.hidden) end for - def collectRefs(args: List[Type], res: Type) = - args.foldLeft(argCaptures(res)): (refs, arg) => - refs ++ arg.deepCaptureSet.elems - - /** The deep capture sets of all parameters of this type (if it is a function type) */ - def argCaptures(tpe: Type): Refs = tpe match - case defn.FunctionOf(args, resultType, isContextual) => - collectRefs(args, resultType) - case defn.RefinedFunctionOf(mt) => - collectRefs(mt.paramInfos, mt.resType) - case CapturingType(parent, _) => - argCaptures(parent) - case _ => - emptyRefs - - if !deps(app).isEmpty then - lazy val appPeaks = argCaptures(app.nuType).peaks + if !resultPeaks.isEmpty then lazy val partPeaks = partsWithPeaks.toMap - for arg <- deps(app) do - if arg.needsSepCheck && !partPeaks(arg).hidden.sharedWith(appPeaks).isEmpty then + for arg <- args do + if arg.needsSepCheck && !partPeaks(arg).hidden.sharedWith(resultPeaks).isEmpty then sepApplyError(fn, parts, arg, app) end checkApply @@ -575,7 +562,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: if pos != null then consumeError(ref, pos, tree.srcPos) end checkUse - /** If `tp` denotes some version of a singleton capture ref `x.type` the set `{x, x*}` + /** If `tp` denotes some version of a singleton capability `x.type` the set `{x, x*}` * otherwise the empty set. */ def explicitRefs(tp: Type)(using Context): Refs = tp match @@ -598,7 +585,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: * - If the reference is to a this type of the enclosing class, the * access must be in a @consume method. * - * References that extend SharedCapability are excluded from checking. + * References that extend caps.Sharable are excluded from checking. * As a side effect, add all checked references with the given position `pos` * to the global `consumed` map. * @@ -612,14 +599,14 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: val badParams = mutable.ListBuffer[Symbol]() def currentOwner = role.dclSym.orElse(ctx.owner) for hiddenRef <- refsToCheck.deductSymRefs(role.dclSym).deduct(explicitRefs(tpe)) do - if !hiddenRef.derivesFromSharedCapability then + if !hiddenRef.derivesFromSharable then hiddenRef.pathRoot match case ref: TermRef => val refSym = ref.symbol if currentOwner.enclosingMethodOrClass.isProperlyContainedIn(refSym.maybeOwner.enclosingMethodOrClass) then report.error(em"""Separation failure: $descr non-local $refSym""", pos) else if refSym.is(TermParam) - && !refSym.hasAnnotation(defn.ConsumeAnnot) + && !refSym.isConsumeParam && currentOwner.isContainedIn(refSym.owner) then badParams += refSym @@ -649,7 +636,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: role match case _: TypeRole.Argument | _: TypeRole.Qualifier => for ref <- refsToCheck do - if !ref.derivesFromSharedCapability then + if !ref.derivesFromSharable then consumed.put(ref, pos) case _ => end checkConsumedRefs @@ -678,7 +665,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: val captured = genPart.deepCaptureSet.elems val hiddenSet = captured.hiddenSet.pruned val clashSet = otherPart.deepCaptureSet.elems - val deepClashSet = (clashSet.footprint() ++ clashSet.hiddenSet).pruned + val deepClashSet = (clashSet.footprint ++ clashSet.hiddenSet).pruned report.error( em"""Separation failure in ${role.description} $tpe. |One part, $genPart, hides capabilities ${CaptureSet(hiddenSet)}. @@ -756,7 +743,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: c.add(c1) case t @ CapturingType(parent, cs) => val c1 = this(c, parent) - if cs.elems.exists(_.stripReadOnly.isFresh) then c1.add(Captures.Hidden) + if cs.elems.exists(_.core.isInstanceOf[FreshCap]) then c1.add(Captures.Hidden) else if !cs.elems.isEmpty then c1.add(Captures.Explicit) else c1 case t: TypeRef if t.symbol.isAbstractOrParamType => @@ -781,11 +768,11 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: // "see through them" when we look at hidden sets. then val refs = tpe.deepCaptureSet.elems - val toCheck = refs.hiddenSet.footprint().deduct(refs.footprint()) + val toCheck = refs.hiddenSet.footprint.deduct(refs.footprint) checkConsumedRefs(toCheck, tpe, role, i"${role.description} $tpe hides", pos) case TypeRole.Argument(arg) => if tpe.hasAnnotation(defn.ConsumeAnnot) then - val capts = captures(arg).footprint() + val capts = captures(arg).footprint checkConsumedRefs(capts, tpe, role, i"argument to @consume parameter with type ${arg.nuType} refers to", pos) case _ => @@ -816,10 +803,15 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: * then the dependencies of an application `f(a, b, c)` of type C^{y} is the map * * [ b -> [a] - * , c -> [a, b] - * , f(a, b, c) -> [b]] + * , c -> [a, b] ] + * + * It also returns the interfering peaks of the result of the application. They are the + * peaks of argument captures and deep captures of the result function type, minus the + * those dependent on parameters. For instance, + * if `f` has the type (x: A, y: B, c: C) -> (op: () ->{b} Unit) -> List[() ->{x, y, a} Unit], its interfering + * peaks will be the peaks of `a` and `b`. */ - private def dependencies(fn: Tree, argss: List[List[Tree]], app: Tree)(using Context): collection.Map[Tree, List[Tree]] = + private def dependencies(fn: Tree, argss: List[List[Tree]], app: Tree)(using Context): (collection.Map[Tree, List[Tree]], Refs) = def isFunApply(sym: Symbol) = sym.name == nme.apply && defn.isFunctionClass(sym.owner) val mtpe = @@ -831,23 +823,47 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: val argMap = mtpsWithArgs.toMap val deps = mutable.HashMap[Tree, List[Tree]]().withDefaultValue(Nil) + def argOfDep(dep: Capability): Option[Tree] = + dep.stripReach match + case dep: TermParamRef => + Some(argMap(dep.binder)(dep.paramNum)) + case dep: ThisType if dep.cls == fn.symbol.owner => + val Select(qual, _) = fn: @unchecked // TODO can we use fn instead? + Some(qual) + case _ => + None + def recordDeps(formal: Type, actual: Tree) = - for dep <- formal.captureSet.elems.toList do - val referred = dep.stripReach match - case dep: TermParamRef => - argMap(dep.binder)(dep.paramNum) :: Nil - case dep: ThisType if dep.cls == fn.symbol.owner => - val Select(qual, _) = fn: @unchecked // TODO can we use fn instead? - qual :: Nil - case _ => - Nil + def captures = formal.captureSet + for dep <- captures.elems.toList do + val referred = argOfDep(dep) deps(actual) ++= referred + inline def isLocalRef(x: Capability): Boolean = x.isInstanceOf[TermParamRef] + + def resultArgCaptures(tpe: Type): Refs = + def collectRefs(args: List[Type], res: Type) = + args.foldLeft(resultArgCaptures(res)): (refs, arg) => + refs ++ arg.captureSet.elems + tpe match + case defn.FunctionOf(args, resultType, isContextual) => + collectRefs(args, resultType) + case defn.RefinedFunctionOf(mt) => + collectRefs(mt.paramInfos, mt.resType) + case CapturingType(parent, refs) => + resultArgCaptures(parent) ++ tpe.boxedCaptureSet.elems + case _ => + emptyRefs + for (mt, args) <- mtpsWithArgs; (formal, arg) <- mt.paramInfos.zip(args) do recordDeps(formal, arg) - recordDeps(mtpe.finalResultType, app) + + val resultType = mtpe.finalResultType + val resultCaptures = + (resultArgCaptures(resultType) ++ resultType.deepCaptureSet.elems).filter(!isLocalRef(_)) + val resultPeaks = resultCaptures.peaks capt.println(i"deps for $app = ${deps.toList}") - deps + (deps, resultPeaks) /** Decompose an application into a function prefix and a list of argument lists. @@ -860,7 +876,8 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: case TypeApply(fn, args) => recur(fn, argss) // skip type arguments case _ => if argss.nestedExists(_.needsSepCheck) then - checkApply(tree, argss.flatten, app, dependencies(tree, argss, app)) + val (deps, resultPeaks) = dependencies(tree, argss, app) + checkApply(tree, argss.flatten, app, deps, resultPeaks) recur(app, Nil) /** Is `tree` an application of `caps.unsafe.unsafeAssumeSeparate`? */ @@ -880,7 +897,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: def checkValOrDefDef(tree: ValOrDefDef)(using Context): Unit = if !tree.symbol.isOneOf(TermParamOrAccessor) && !isUnsafeAssumeSeparate(tree.rhs) then checkType(tree.tpt, tree.symbol) - capt.println(i"sep check def ${tree.symbol}: ${tree.tpt} with ${captures(tree.tpt).hiddenSet.footprint()}") + capt.println(i"sep check def ${tree.symbol}: ${tree.tpt} with ${captures(tree.tpt).hiddenSet.footprint}") pushDef(tree, captures(tree.tpt).hiddenSet.deductSymRefs(tree.symbol)) def inSection[T](op: => T)(using Context): T = @@ -893,15 +910,22 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: def traverseSection[T](tree: Tree)(using Context) = inSection(traverseChildren(tree)) + /** Should separatiion checking be disabled for the body of this method? + */ + def skippable(sym: Symbol)(using Context): Boolean = + sym.isInlineMethod + // We currently skip inline method bodies since these seem to generan + // spurious recheck completions. Test case is i20237.scala + /** Traverse `tree` and perform separation checks everywhere */ def traverse(tree: Tree)(using Context): Unit = if !isUnsafeAssumeSeparate(tree) then trace(i"checking separate $tree"): checkUse(tree) tree match - case tree @ Select(qual, _) if tree.symbol.is(Method) && tree.symbol.hasAnnotation(defn.ConsumeAnnot) => + case tree @ Select(qual, _) if tree.symbol.is(Method) && tree.symbol.isConsumeParam => traverseChildren(tree) checkConsumedRefs( - captures(qual).footprint(), qual.nuType, + captures(qual).footprint, qual.nuType, TypeRole.Qualifier(qual, tree.symbol), i"call prefix of @consume ${tree.symbol} refers to", qual.srcPos) case tree: GenericApply => @@ -915,11 +939,14 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: traverseChildren(tree) checkValOrDefDef(tree) case tree: DefDef => - inSection: - consumed.segment: - for params <- tree.paramss; case param: ValDef <- params do - pushDef(param, emptyRefs) - traverseChildren(tree) + if skippable(tree.symbol) then + capt.println(i"skipping sep check of ${tree.symbol}") + else + inSection: + consumed.segment: + for params <- tree.paramss; case param: ValDef <- params do + pushDef(param, emptyRefs) + traverseChildren(tree) checkValOrDefDef(tree) case If(cond, thenp, elsep) => traverse(cond) @@ -957,4 +984,4 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: consumeInLoopError(ref, pos) case _ => traverseChildren(tree) -end SepCheck \ No newline at end of file +end SepCheck diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 21c58385b58a..1a9d86c7d645 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -18,9 +18,10 @@ import reporting.Message import printing.{Printer, Texts}, Texts.{Text, Str} import collection.mutable import CCState.* -import dotty.tools.dotc.util.NoSourcePosition import CheckCaptures.CheckerAPI import NamerOps.methodType +import NameKinds.{CanThrowEvidenceName, TryOwnerName} +import Capabilities.* /** Operations accessed from CheckCaptures */ trait SetupAPI: @@ -51,6 +52,15 @@ object Setup: Some((res, exc)) case _ => None + + def firstCanThrowEvidence(body: Tree)(using Context): Option[Tree] = body match + case Block(stats, expr) => + if stats.isEmpty then firstCanThrowEvidence(expr) + else stats.find: + case vd: ValDef => vd.symbol.name.is(CanThrowEvidenceName) + case _ => false + case _ => None + end Setup import Setup.* @@ -151,37 +161,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else symd end transformSym - /** If `tp` is an unboxed capturing type or a function returning an unboxed capturing type, - * convert it to be boxed. - */ - private def box(tp: Type)(using Context): Type = - def recur(tp: Type): Type = tp.dealiasKeepAnnotsAndOpaques match - case tp @ CapturingType(parent, refs) => - if tp.isBoxed || parent.derivesFrom(defn.Caps_CapSet) then tp - else tp.boxed - case tp @ AnnotatedType(parent, ann) => - if ann.symbol.isRetains && !parent.derivesFrom(defn.Caps_CapSet) - then CapturingType(parent, ann.tree.toCaptureSet, boxed = true) - else tp.derivedAnnotatedType(box(parent), ann) - case tp1 @ AppliedType(tycon, args) if defn.isNonRefinedFunction(tp1) => - val res = args.last - val boxedRes = recur(res) - if boxedRes eq res then tp - else tp1.derivedAppliedType(tycon, args.init :+ boxedRes) - case tp1 @ defn.RefinedFunctionOf(rinfo: MethodType) => - val boxedRinfo = recur(rinfo) - if boxedRinfo eq rinfo then tp - else boxedRinfo.toFunctionType(alwaysDependent = true) - case tp1: MethodOrPoly => - val res = tp1.resType - val boxedRes = recur(res) - if boxedRes eq res then tp - else tp1.derivedLambdaType(resType = boxedRes) - case _ => tp - tp match - case tp: MethodOrPoly => tp // don't box results of methods outside refinements - case _ => recur(tp) - private trait SetupTypeMap extends FollowAliasesMap: private var isTopLevel = true @@ -199,6 +178,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: innerApply(tp) finally isTopLevel = saved + override def mapArg(arg: Type, tparam: ParamInfo): Type = + super.mapArg(Recheck.mapExprType(arg), tparam) + /** Map parametric functions with results that have a capture set somewhere * to dependent functions. */ @@ -211,9 +193,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case AppliedType(`tycon`, args0) => args0.last ne args.last case _ => false if expand then - depFun(args.init, args.last, + val fn = depFun( + args.init, args.last, isContextual = defn.isContextFunctionClass(tycon.classSymbol)) .showing(i"add function refinement $tp ($tycon, ${args.init}, ${args.last}) --> $result", capt) + AnnotatedType(fn, Annotation(defn.InferredDepFunAnnot, util.Spans.NoSpan)) else tp case _ => tp @@ -241,9 +225,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: CapturingType(OrType(tp1, parent2, tp.isSoft), refs2, tp2.isBoxed) case tp @ AppliedType(tycon, args) if !defn.isFunctionClass(tp.dealias.typeSymbol) && (tp.dealias eq tp) => - tp.derivedAppliedType(tycon, args.mapConserve(box)) + tp.derivedAppliedType(tycon, args.mapConserve(_.boxDeeply)) case tp: RealTypeBounds => - tp.derivedTypeBounds(tp.lo, box(tp.hi)) + tp.derivedTypeBounds(tp.lo, tp.hi.boxDeeply) case tp: LazyRef => normalizeCaptures(tp.ref) case _ => @@ -323,7 +307,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: try val tp1 = mapInferred(refine = true)(tp) - val tp2 = root.toResultInResults(_ => assert(false))(tp1) + val tp2 = toResultInResults(NoSymbol, _ => assert(false))(tp1) if tp2 ne tp then capt.println(i"expanded inferred in ${ctx.owner}: $tp --> $tp1 --> $tp2") tp2 catch case ex: AssertionError => @@ -351,7 +335,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: */ def stripImpliedCaptureSet(tp: Type): Type = tp match case tp @ CapturingType(parent, refs) - if (refs eq CaptureSet.universalImpliedByCapability) && !tp.isBoxedCapturing => + if refs.isInstanceOf[CaptureSet.CSImpliedByCapability] && !tp.isBoxedCapturing => parent case tp: AliasingBounds => tp.derivedAlias(stripImpliedCaptureSet(tp.alias)) @@ -390,37 +374,49 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: CapturingType(fntpe, cs, boxed = false) else fntpe - /** Check that types extending SharedCapability don't have a `cap` in their capture set. - * TODO This is not enough. - * We need to also track that we cannot get exclusive capabilities in paths - * where some prefix derives from SharedCapability. Also, can we just - * exclude `cap`, or do we have to extend this to all exclusive capabilties? - * The problem is that we know what is exclusive in general only after capture - * checking, not before. + /** 1. Check that parents of capturing types are not pure. + * 2. Check that types extending caps.Sharable don't have a `cap` in their capture set. + * TODO: Is this enough? + * We need to also track that we cannot get exclusive capabilities in paths + * where some prefix derives from Sharable. Also, can we just + * exclude `cap`, or do we have to extend this to all exclusive capabilties? + * The problem is that we know what is exclusive in general only after capture + * checking, not before. + * But maybe the rules for classification already cover these cases. */ - def checkSharedOK(tp: Type): tp.type = + def checkRetainsOK(tp: Type): tp.type = tp match - case CapturingType(parent, refs) - if refs.isUniversal && parent.derivesFromSharedCapability => - fail(em"$tp extends SharedCapability, so it cannot capture `cap`") + case CapturingType(parent, refs) => + if parent.isAlwaysPure && !tptToCheck.span.isZeroExtent then + // If tptToCheck is zero-extent it could be copied from an overridden + // method's result type. In that case, there's no point requiring + // an explicit result type in the override, the inherited capture set + // will be ignored anyway. + fail(em"$parent is a pure type, it makes no sense to add a capture set to it") + else if refs.isUniversal && parent.derivesFromSharedCapability then + fail(em"$tp extends Sharable, so it cannot capture `cap`") case _ => tp - /** Map references to capability classes C to C^, + /** Map references to capability classes C to C^{cap.rd}, * normalize captures and map to dependent functions. */ def defaultApply(t: Type) = if t.derivesFromCapability + && t.typeParams.isEmpty && !t.isSingleton && (!sym.isConstructor || (t ne tp.finalResultType)) // Don't add ^ to result types of class constructors deriving from Capability - then CapturingType(t, CaptureSet.universalImpliedByCapability, boxed = false) + then + normalizeCaptures(mapOver(t)) match + case t1 @ CapturingType(_, _) => t1 + case t1 => CapturingType(t1, CaptureSet.CSImpliedByCapability(), boxed = false) else normalizeCaptures(mapFollowingAliases(t)) def innerApply(t: Type) = t match case t @ CapturingType(parent, refs) => - checkSharedOK: + checkRetainsOK: t.derivedCapturingType(stripImpliedCaptureSet(this(parent)), refs) case t @ AnnotatedType(parent, ann) => val parent1 = this(parent) @@ -429,11 +425,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if !tptToCheck.isEmpty then checkWellformedLater(parent2, ann.tree, tptToCheck) try - checkSharedOK: + checkRetainsOK: CapturingType(parent2, ann.tree.toCaptureSet) catch case ex: IllegalCaptureRef => if !tptToCheck.isEmpty then - report.error(em"Illegal capture reference: ${ex.getMessage.nn}", tptToCheck.srcPos) + report.error(em"Illegal capture reference: ${ex.getMessage}", tptToCheck.srcPos) parent2 else if ann.symbol == defn.UncheckedCapturesAnnot then makeUnchecked(apply(parent)) @@ -458,7 +454,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def transform(tp: Type): Type = val tp1 = toCapturing(tp) - val tp2 = root.toResultInResults(fail, toCapturing.keepFunAliases)(tp1) + val tp2 = toResultInResults(sym, fail, toCapturing.keepFunAliases)(tp1) val snd = if toCapturing.keepFunAliases then "" else " 2nd time" if tp2 ne tp then capt.println(i"expanded explicit$snd in ${ctx.owner}: $tp --> $tp1 --> $tp2") tp2 @@ -472,14 +468,15 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val tp3 = if sym.isType then stripImpliedCaptureSet(tp2) else tp2 - if freshen then root.capToFresh(tp3).tap(addOwnerAsHidden(_, sym)) + if freshen then + capToFresh(tp3, Origin.InDecl(sym)).tap(addOwnerAsHidden(_, sym)) else tp3 end transformExplicitType /** Update info of `sym` for CheckCaptures phase only */ - private def updateInfo(sym: Symbol, info: Type)(using Context) = + private def updateInfo(sym: Symbol, info: Type, owner: Symbol)(using Context) = toBeUpdated += sym - sym.updateInfo(thisPhase, info, newFlagsFor(sym)) + sym.updateInfo(thisPhase, info, newFlagsFor(sym), owner) toBeUpdated -= sym /** The info of `sym` at the CheckCaptures phase */ @@ -491,12 +488,15 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def add = new TypeTraverser: var reach = false def traverse(t: Type): Unit = t match - case root.Fresh(hidden) => - if reach then hidden.elems += ref.reach - else if ref.isTracked then hidden.elems += ref - case t @ CapturingType(_, _) if t.isBoxed && !reach => - reach = true - try traverseChildren(t) finally reach = false + case t @ CapturingType(parent, refs) => + val saved = reach + reach |= t.isBoxed + try + traverse(parent) + for case fresh: FreshCap <- refs.elems.iterator do // TODO: what about fresh.rd elems? + if reach then fresh.hiddenSet.elems += ref.reach + else if ref.isTracked then fresh.hiddenSet.elems += ref + finally reach = saved case _ => traverseChildren(t) if ref.isTrackableRef then add.traverse(tp) @@ -515,7 +515,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if tree.isInferred then transformInferredType(tree.tpe) else transformExplicitType(tree.tpe, sym, freshen = !boxed, tptToCheck = tree) - if boxed then transformed = box(transformed) + if boxed then transformed = transformed.boxDeeply tree.setNuType( if sym.hasAnnotation(defn.UncheckedCapturesAnnot) then makeUnchecked(transformed) else transformed) @@ -568,7 +568,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: traverse(fn) for case arg: TypeTree <- args do if defn.isTypeTestOrCast(fn.symbol) then - arg.setNuType(root.capToFresh(arg.tpe)) + arg.setNuType( + capToFresh(arg.tpe, Origin.TypeArg(arg.tpe))) else transformTT(arg, NoSymbol, boxed = true) // type arguments in type applications are boxed @@ -584,11 +585,20 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tree @ SeqLiteral(elems, tpt: TypeTree) => traverse(elems) - tpt.setNuType(box(transformInferredType(tpt.tpe))) - - case tree: Block => - ccState.inNestedLevel(traverseChildren(tree)) + tpt.setNuType(transformInferredType(tpt.tpe).boxDeeply) + case tree @ Try(body, catches, finalizer) => + val tryOwner = firstCanThrowEvidence(body) match + case Some(vd) => + newSymbol(ctx.owner, TryOwnerName.fresh(), + Method | Synthetic, ExprType(defn.NothingType), coord = tree.span) + case _ => + ctx.owner + inContext(ctx.withOwner(tryOwner)): + traverse(body) + catches.foreach(traverse) + traverse(finalizer) + case tree: New => case _ => traverseChildren(tree) postProcess(tree) @@ -633,51 +643,61 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // have a new type installed here (meaning hasRememberedType is true) def signatureChanges = tree.tpt.hasNuType || paramSignatureChanges + def ownerChanges = + ctx.owner.name.is(TryOwnerName) def paramsToCap(mt: Type)(using Context): Type = mt match case mt: MethodType => - mt.derivedLambdaType( - paramInfos = mt.paramInfos.map(root.freshToCap), - resType = paramsToCap(mt.resType)) + try + mt.derivedLambdaType( + paramInfos = mt.paramInfos.map(freshToCap), + resType = paramsToCap(mt.resType)) + catch case ex: AssertionError => + println(i"error while mapping params ${mt.paramInfos} of $sym") + throw ex case mt: PolyType => mt.derivedLambdaType(resType = paramsToCap(mt.resType)) case _ => mt - // If there's a change in the signature, update the info of `sym` - if sym.exists && signatureChanges then + // If there's a change in the signature or owner, update the info of `sym` + if sym.exists && (signatureChanges || ownerChanges) then val updatedInfo = - - val paramSymss = sym.paramSymss - def newInfo(using Context) = // will be run in this or next phase - root.toResultInResults(report.error(_, tree.srcPos)): - if sym.is(Method) then - paramsToCap(methodType(paramSymss, localReturnType)) - else tree.tpt.nuType - if tree.tpt.isInstanceOf[InferredTypeTree] - && !sym.is(Param) && !sym.is(ParamAccessor) - then - val prevInfo = sym.info - new LazyType: - def complete(denot: SymDenotation)(using Context) = - assert(ctx.phase == thisPhase.next, i"$sym") - sym.info = prevInfo // set info provisionally so we can analyze the symbol in recheck - completeDef(tree, sym, this) - sym.info = newInfo - .showing(i"new info of $sym = $result", capt) - else if sym.is(Method) then - new LazyType: - def complete(denot: SymDenotation)(using Context) = - sym.info = newInfo - .showing(i"new info of $sym = $result", capt) - else newInfo - updateInfo(sym, updatedInfo) + if signatureChanges then + val paramSymss = sym.paramSymss + def newInfo(using Context) = // will be run in this or next phase + toResultInResults(sym, report.error(_, tree.srcPos)): + if sym.is(Method) then + inContext(ctx.withOwner(sym)): + paramsToCap(methodType(paramSymss, localReturnType)) + else tree.tpt.nuType + if tree.tpt.isInstanceOf[InferredTypeTree] + && !sym.is(Param) && !sym.is(ParamAccessor) + then + val prevInfo = sym.info + new LazyType: + def complete(denot: SymDenotation)(using Context) = + assert(ctx.phase == thisPhase.next, i"$sym") + sym.info = prevInfo // set info provisionally so we can analyze the symbol in recheck + completeDef(tree, sym, this) + sym.info = newInfo + .showing(i"new info of $sym = $result", capt) + else if sym.is(Method) then + new LazyType: + def complete(denot: SymDenotation)(using Context) = + sym.info = newInfo + .showing(i"new info of $sym = $result", capt) + else newInfo + else sym.info + val updatedOwner = if ownerChanges then ctx.owner else sym.owner + updateInfo(sym, updatedInfo, updatedOwner) case tree: Bind => val sym = tree.symbol - updateInfo(sym, transformInferredType(sym.info)) + updateInfo(sym, transformInferredType(sym.info), sym.owner) case tree: TypeDef => tree.symbol match case cls: ClassSymbol => + checkClassifiedInheritance(cls) ccState.inNestedLevelUnless(cls.is(Module)): val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo @@ -698,7 +718,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // Infer the self type for the rest, which is all classes without explicit // self types (to which we also add nested module classes), provided they are // neither pure, nor are publicily extensible with an unconstrained self type. - CapturingType(cinfo.selfType, CaptureSet.Var(cls, level = ccState.currentLevel)) + val cs = CaptureSet.Var(cls, level = ccState.currentLevel) + if cls.derivesFrom(defn.Caps_Capability) then + // If cls is a capability class, we need to add a fresh readonly capability to + // ensure we cannot treat the class as pure. + CaptureSet.fresh(Origin.InDecl(cls)).readOnly.subCaptures(cs) + CapturingType(cinfo.selfType, cs) // Compute new parent types val ps1 = inContext(ctx.withOwner(cls)): @@ -707,7 +732,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // Install new types and if it is a module class also update module object if (selfInfo1 ne selfInfo) || (ps1 ne ps) then val newInfo = ClassInfo(prefix, cls, ps1, decls, selfInfo1) - updateInfo(cls, newInfo) + updateInfo(cls, newInfo, cls.owner) capt.println(i"update class info of $cls with parents $ps selfinfo $selfInfo to $newInfo") cls.thisType.asInstanceOf[ThisType].invalidateCaches() if cls.is(ModuleClass) then @@ -720,7 +745,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // This would potentially give stackoverflows when setup is run repeatedly. // One test case is pos-custom-args/captures/checkbounds.scala under // ccConfig.alwaysRepeatRun = true. - updateInfo(modul, CapturingType(modul.info, selfCaptures)) + updateInfo(modul, CapturingType(modul.info, selfCaptures), modul.owner) modul.termRef.invalidateCaches() case _ => case _ => @@ -764,7 +789,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case CapturingType(_, refs) => !refs.isAlwaysEmpty case RetainingType(parent, refs) => - !refs.isEmpty + !refs.retainedElements.isEmpty case tp: (TypeRef | AppliedType) => val sym = tp.typeSymbol if sym.isClass @@ -810,8 +835,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: && !refs.isUniversal // if refs is {cap}, an added variable would not change anything case RetainingType(parent, refs) => needsVariable(parent) - && !refs.tpes.exists: - case ref: TermRef => ref.isCap + && !refs.retainedElements.exists: + case ref: TermRef => ref.isCapRef case _ => false case AnnotatedType(parent, _) => needsVariable(parent) @@ -879,7 +904,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def apply(t: Type) = t match case t @ CapturingType(parent, refs) => val parent1 = this(parent) - if refs.containsRootCapability then t.derivedCapturingType(parent1, CaptureSet.Fluid) + if refs.containsTerminalCapability then t.derivedCapturingType(parent1, CaptureSet.Fluid) else t case _ => mapFollowingAliases(t) @@ -889,6 +914,18 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def setupUnit(tree: Tree, checker: CheckerAPI)(using Context): Unit = setupTraverser(checker).traverse(tree)(using ctx.withPhase(thisPhase)) + // ------ Checks to run at Setup ---------------------------------------- + + private def checkClassifiedInheritance(cls: ClassSymbol)(using Context): Unit = + def recur(cs: List[ClassSymbol]): Unit = cs match + case c :: cs1 => + for c1 <- cs1 do + if !c.derivesFrom(c1) && !c1.derivesFrom(c) then + report.error(i"$cls inherits two unrelated classifier traits: $c and $c1", cls.srcPos) + recur(cs1) + case Nil => + recur(cls.baseClasses.filter(_.isClassifiedCapabilityClass).distinct) + // ------ Checks to run after main capture checking -------------------------- /** A list of actions to perform at postCheck */ @@ -905,19 +942,13 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * @param tpt the tree for which an error or warning should be reported */ private def checkWellformed(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = - capt.println(i"checkWF post $parent ${ann.retainedElems} in $tpt") - var retained = ann.retainedElems.toArray - for i <- 0 until retained.length do - val refTree = retained(i) - val refs = - try refTree.toCaptureRefs - catch case ex: IllegalCaptureRef => - report.error(em"Illegal capture reference: ${ex.getMessage.nn}", refTree.srcPos) - Nil - for ref <- refs do + capt.println(i"checkWF post $parent ${ann.retainedSet} in $tpt") + try + var retained = ann.retainedSet.retainedElements.toArray + for i <- 0 until retained.length do + val ref = retained(i) def pos = - if refTree.span.exists then refTree.srcPos - else if ann.span.exists then ann.srcPos + if ann.span.exists then ann.srcPos else tpt.srcPos def check(others: CaptureSet, dom: Type | CaptureSet): Unit = @@ -925,8 +956,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: report.warning(em"redundant capture: $dom already accounts for $ref", pos) if ref.captureSetOfInfo.elems.isEmpty - && !ref.derivesFrom(defn.Caps_Capability) - && !ref.derivesFrom(defn.Caps_CapSet) then + && !ref.coreType.derivesFrom(defn.Caps_Capability) + && !ref.coreType.derivesFrom(defn.Caps_CapSet) then val deepStr = if ref.isReach then " deep" else "" report.error(em"$ref cannot be tracked since its$deepStr capture set is empty", pos) check(parent.captureSet, parent) @@ -934,18 +965,19 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val others = for j <- 0 until retained.length if j != i - r <- retained(j).toCaptureRefs - if !r.isRootCapability + r = retained(j) + if !r.isTerminalCapability yield r val remaining = CaptureSet(others*) check(remaining, remaining) end for - end for + catch case ex: IllegalCaptureRef => + report.error(em"Illegal capture reference: ${ex.getMessage}", tpt.srcPos) end checkWellformed /** Check well formed at post check time. We need to wait until after * recheck because we find out only then whether capture sets are empty or - * capture references are redundant. + * capabilities are redundant. */ private def checkWellformedLater(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = if !tpt.span.isZeroExtent && enclosingInlineds.isEmpty then diff --git a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala index cdc3afff8a2d..bb2228932cb8 100644 --- a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala +++ b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala @@ -8,6 +8,7 @@ import StdNames.nme import Names.Name import NameKinds.DefaultGetterName import config.Printers.capt +import Capabilities.* /** Classification and transformation methods for function methods and * synthetic case class methods that need to be treated specially. @@ -77,12 +78,11 @@ object Synthetics: case tp: MethodOrPoly => tp.derivedLambdaType(resType = augmentResult(tp.resType)) case _ => - val refined = trackedParams.foldLeft(tp) { (parent, pref) => - RefinedType(parent, pref.paramName, + val refined = trackedParams.foldLeft(tp): (parent, pref) => + parent.refinedOverride(pref.paramName, CapturingType( atPhase(ctx.phase.next)(pref.underlying.stripCapturing), CaptureSet(pref))) - } CapturingType(refined, CaptureSet(trackedParams*)) if trackedParams.isEmpty then info else augmentResult(info).showing(i"augment apply/copy type $info to $result", capt) @@ -116,7 +116,7 @@ object Synthetics: def transformUnapplyCaptures(info: Type)(using Context): Type = info match case info: MethodType => val paramInfo :: Nil = info.paramInfos: @unchecked - val newParamInfo = CapturingType(paramInfo, CaptureSet.fresh()) + val newParamInfo = CapturingType(paramInfo, CaptureSet.universal) val trackedParam = info.paramRefs.head def newResult(tp: Type): Type = tp match case tp: MethodOrPoly => @@ -132,7 +132,7 @@ object Synthetics: val (pt: PolyType) = info: @unchecked val (mt: MethodType) = pt.resType: @unchecked val (enclThis: ThisType) = owner.thisType: @unchecked - val paramCaptures = CaptureSet(enclThis, root.cap) + val paramCaptures = CaptureSet(enclThis, GlobalCap) pt.derivedLambdaType(resType = MethodType(mt.paramNames)( mt1 => mt.paramInfos.map(_.capturing(paramCaptures)), mt1 => CapturingType(mt.resType, CaptureSet(enclThis, mt1.paramRefs.head)))) @@ -150,7 +150,7 @@ object Synthetics: def transformCompareCaptures = val (enclThis: ThisType) = symd.owner.thisType: @unchecked MethodType( - defn.ObjectType.capturing(CaptureSet(root.cap, enclThis)) :: Nil, + defn.ObjectType.capturing(CaptureSet(GlobalCap, enclThis)) :: Nil, defn.BooleanType) symd.copySymDenotation(info = symd.name match diff --git a/compiler/src/dotty/tools/dotc/cc/ccConfig.scala b/compiler/src/dotty/tools/dotc/cc/ccConfig.scala index 4c06f4a0843d..4cc2264c12cb 100644 --- a/compiler/src/dotty/tools/dotc/cc/ccConfig.scala +++ b/compiler/src/dotty/tools/dotc/cc/ccConfig.scala @@ -46,12 +46,16 @@ object ccConfig: */ inline val postCheckCapturesets = false + /** If true, do level checking for FreshCap instances */ + def useFreshLevels(using Context): Boolean = + Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`) + /** If true, turn on separation checking */ def useSepChecks(using Context): Boolean = - Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`) + Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.8`) /** Not used currently. Handy for trying out new features */ - def newScheme(using Context): Boolean = - Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.8`) + def newScheme(using ctx: Context): Boolean = + Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`) -end ccConfig \ No newline at end of file +end ccConfig diff --git a/compiler/src/dotty/tools/dotc/cc/root.scala b/compiler/src/dotty/tools/dotc/cc/root.scala deleted file mode 100644 index ee668efa8378..000000000000 --- a/compiler/src/dotty/tools/dotc/cc/root.scala +++ /dev/null @@ -1,355 +0,0 @@ -package dotty.tools -package dotc -package cc - -import core.* -import Types.*, Symbols.*, Contexts.*, Annotations.*, Flags.* -import StdNames.nme -import ast.tpd.* -import Decorators.* -import typer.ErrorReporting.errorType -import Names.TermName -import NameKinds.ExistentialBinderName -import NameOps.isImpureFunction -import reporting.Message -import util.{SimpleIdentitySet, EqHashMap} -import util.Spans.NoSpan -import annotation.constructorOnly - -/** A module defining three kinds of root capabilities - * - `cap` of kind `Global`: This is the global root capability. Among others it is - * used in the types of formal parameters, in type bounds, and in self types. - * `cap` does not subsume other capabilities, except in arguments of - * `withCapAsRoot` calls. - * - Instances of Fresh(hidden), of kind Fresh. These do subsume other capabilties in scope. - * They track with hidden sets which other capabilities were subsumed. - * Hidden sets are inspected by separation checking. - * - Instances of Result(binder), of kind Result. These are existentials associated with - * the result types of dependent methods. They don't subsume other capabilties. - * - * Representation: - * - * - `cap` is just the TermRef `scala.caps.cap` defined in the `caps` module - * - `Fresh` and `Result` instances are annotated types of `scala.caps.cap` - * with a special `root.Annot` annotation. The symbol of the annotation is - * `annotation.internal.rootCapability`. The annotation carries a kind, which provides - * a hidden set for Fresh instances and a binder method type for Result instances. - * - * Setup: - * - * In the setup phase, `cap` instances in the result of a dependent function type - * or method type such as `(x: T): C^{cap}` are converted to `Result(binder)` instances, - * where `binder` refers to the method type. Most other cap instances are mapped to - * Fresh instances instead. For example the `cap` in the result of `T => C^{cap}` - * is mapped to a Fresh instance. - * - * If one needs to use a dependent function type yet one still want to map `cap` to - * a fresh instance instead an existential root, one can achieve that by the use - * of a type alias. For instance, the following type creates an existential for `^`: - * - * (x: A) => (C^{x}, D^) - * - * By contrast, this variant creates a fresh instance instead: - * - * type F[X] = (x: A) => (C^{x}, X) - * F[D^] - * - * The trick is that the argument D^ is mapped to D^{fresh} before the `F` alias - * is expanded. - */ -object root: - - enum Kind: - case Result(binder: MethodType) - case Fresh(hidden: CaptureSet.HiddenSet) - case Global - - override def equals(other: Any): Boolean = - (this eq other.asInstanceOf[AnyRef]) || this.match - case Kind.Result(b1) => other match - case Kind.Result(b2) => b1 eq b2 - case _ => false - case Kind.Fresh(h1) => other match - case Kind.Fresh(h2) => h1 eq h2 - case _ => false - case Kind.Global => false - end Kind - - /** The annotation of a root instance */ - case class Annot(kind: Kind)(using @constructorOnly ictx: Context) extends Annotation: - - /** id printed under -uniqid, for debugging */ - val id = - val ccs = ccState - ccs.rootId += 1 - ccs.rootId - - override def symbol(using Context) = defn.RootCapabilityAnnot - override def tree(using Context) = New(symbol.typeRef, Nil) - override def derivedAnnotation(tree: Tree)(using Context): Annotation = this - - private var myOriginalKind = kind - def originalBinder: MethodType = myOriginalKind.asInstanceOf[Kind.Result].binder - - def derivedAnnotation(binder: MethodType)(using Context): Annotation = kind match - case Kind.Result(b) if b ne binder => - val ann = Annot(Kind.Result(binder)) - ann.myOriginalKind = myOriginalKind - ann - case _ => - this - - override def hash: Int = kind.hashCode - override def eql(that: Annotation) = that match - case Annot(kind) => this.kind eq kind - case _ => false - - /** Special treatment of `SubstBindingMaps` which can change the binder of a - * Result instances - */ - override def mapWith(tm: TypeMap)(using Context) = kind match - case Kind.Result(binder) => tm match - case tm: Substituters.SubstBindingMap[MethodType] @unchecked if tm.from eq binder => - derivedAnnotation(tm.to) - case tm: Substituters.SubstBindingsMap => - var i = 0 - while i < tm.from.length && (tm.from(i) ne binder) do i += 1 - if i < tm.from.length then derivedAnnotation(tm.to(i).asInstanceOf[MethodType]) - else this - case _ => this - case _ => this - end Annot - - def cap(using Context): TermRef = defn.captureRoot.termRef - - /** The type of fresh references */ - type Fresh = AnnotatedType - - object Fresh: - /** Constructor and extractor methods for "fresh" capabilities */ - private def make(owner: Symbol)(using Context): CaptureRef = - if ccConfig.useSepChecks then - val hiddenSet = CaptureSet.HiddenSet(owner) - val res = AnnotatedType(cap, Annot(Kind.Fresh(hiddenSet))) - hiddenSet.owningCap = res - //assert(hiddenSet.id != 3) - res - else - cap - - def withOwner(owner: Symbol)(using Context): CaptureRef = make(owner) - def apply()(using Context): CaptureRef = make(NoSymbol) - - def unapply(tp: AnnotatedType): Option[CaptureSet.HiddenSet] = tp.annot match - case Annot(Kind.Fresh(hidden)) => Some(hidden) - case _ => None - end Fresh - - /** The type of existentially bound references */ - type Result = AnnotatedType - - object Result: - def apply(binder: MethodType)(using Context): Result = - val hiddenSet = CaptureSet.HiddenSet(NoSymbol) - val res = AnnotatedType(cap, Annot(Kind.Result(binder))) - hiddenSet.owningCap = res - res - - def unapply(tp: Result)(using Context): Option[MethodType] = tp.annot match - case Annot(Kind.Result(binder)) => Some(binder) - case _ => None - end Result - - def unapply(root: CaptureRef)(using Context): Option[Kind] = root match - case root @ AnnotatedType(_, ann: Annot) => Some(ann.kind) - case _ if root.isCap => Some(Kind.Global) - case _ => None - - /** Map each occurrence of cap to a different Fresh instance - * Exception: CapSet^ stays as it is. - */ - class CapToFresh(owner: Symbol)(using Context) extends BiTypeMap, FollowAliasesMap: - thisMap => - - override def apply(t: Type) = - if variance <= 0 then t - else t match - case t: CaptureRef if t.isCap => - Fresh.withOwner(owner) - case t @ CapturingType(parent: TypeRef, _) if parent.symbol == defn.Caps_CapSet => - t - case t @ CapturingType(_, _) => - mapOver(t) - case t @ AnnotatedType(parent, ann) => - val parent1 = this(parent) - if ann.symbol.isRetains && ann.tree.toCaptureSet.containsCap then - this(CapturingType(parent1, ann.tree.toCaptureSet)) - else - t.derivedAnnotatedType(parent1, ann) - case _ => - mapFollowingAliases(t) - - override def fuse(next: BiTypeMap)(using Context) = next match - case next: Inverse => assert(false); Some(IdentityTypeMap) - case _ => None - - override def toString = "CapToFresh" - - class Inverse extends BiTypeMap, FollowAliasesMap: - def apply(t: Type): Type = t match - case t @ Fresh(_) => cap - case t @ CapturingType(_, refs) => mapOver(t) - case _ => mapFollowingAliases(t) - - override def fuse(next: BiTypeMap)(using Context) = next match - case next: CapToFresh => assert(false); Some(IdentityTypeMap) - case _ => None - - def inverse = thisMap - override def toString = thisMap.toString + ".inverse" - - lazy val inverse = Inverse() - - end CapToFresh - - /** Maps cap to fresh */ - def capToFresh(tp: Type, owner: Symbol = NoSymbol)(using Context): Type = - if ccConfig.useSepChecks then CapToFresh(owner)(tp) else tp - - /** Maps fresh to cap */ - def freshToCap(tp: Type)(using Context): Type = - if ccConfig.useSepChecks then CapToFresh(NoSymbol).inverse(tp) else tp - - /** Map top-level free existential variables one-to-one to Fresh instances */ - def resultToFresh(tp: Type)(using Context): Type = - val subst = new TypeMap: - val seen = EqHashMap[Annotation, CaptureRef]() - var localBinders: SimpleIdentitySet[MethodType] = SimpleIdentitySet.empty - - def apply(t: Type): Type = t match - case t @ Result(binder) => - if localBinders.contains(binder) then t // keep bound references - else seen.getOrElseUpdate(t.annot, Fresh()) // map free references to Fresh() - case t: MethodType => - // skip parameters - val saved = localBinders - if t.marksExistentialScope then localBinders = localBinders + t - try t.derivedLambdaType(resType = this(t.resType)) - finally localBinders = saved - case t: PolyType => - // skip parameters - t.derivedLambdaType(resType = this(t.resType)) - case _ => - mapOver(t) - - subst(tp) - end resultToFresh - - /** Replace all occurrences of `cap` (or fresh) in parts of this type by an existentially bound - * variable bound by `mt`. - * Stop at function or method types since these have been mapped before. - */ - def toResult(tp: Type, mt: MethodType, fail: Message => Unit)(using Context): Type = - - abstract class CapMap extends BiTypeMap: - override def mapOver(t: Type): Type = t match - case t @ FunctionOrMethod(args, res) if variance > 0 && !t.isAliasFun => - t // `t` should be mapped in this case by a different call to `mapCap`. - case t: (LazyRef | TypeVar) => - mapConserveSuper(t) - case _ => - super.mapOver(t) - - object toVar extends CapMap: - private val seen = EqHashMap[CaptureRef, Result]() - - def apply(t: Type) = t match - case t: CaptureRef if t.isCapOrFresh => - if variance > 0 then - seen.getOrElseUpdate(t, Result(mt)) - else - if variance == 0 then - fail(em"""$tp captures the root capability `cap` in invariant position. - |This capability cannot be converted to an existential in the result type of a function.""") - // we accept variance < 0, and leave the cap as it is - super.mapOver(t) - case defn.FunctionNOf(args, res, contextual) if t.typeSymbol.name.isImpureFunction => - if variance > 0 then - super.mapOver: - defn.FunctionNOf(args, res, contextual) - .capturing(Result(mt).singletonCaptureSet) - else mapOver(t) - case _ => - mapOver(t) - //.showing(i"mapcap $t = $result") - override def toString = "toVar" - - object inverse extends BiTypeMap: - def apply(t: Type) = t match - case t @ Result(`mt`) => - // do a reverse getOrElseUpdate on `seen` to produce the - // `Fresh` assosicated with `t` - val it = seen.iterator - var ref: CaptureRef | Null = null - while it.hasNext && ref == null do - val (k, v) = it.next - if v.annot eq t.annot then ref = k - if ref == null then - ref = Fresh() - seen(ref) = t - ref - case _ => mapOver(t) - def inverse = toVar.this - override def toString = "toVar.inverse" - end toVar - - toVar(tp) - end toResult - - /** Map global roots in function results to result roots */ - def toResultInResults(fail: Message => Unit, keepAliases: Boolean = false)(using Context): TypeMap = new TypeMap with FollowAliasesMap: - def apply(t: Type): Type = t match - case defn.RefinedFunctionOf(mt) => - val mt1 = apply(mt) - if mt1 ne mt then mt1.toFunctionType(alwaysDependent = true) - else t - case t: MethodType if variance > 0 && t.marksExistentialScope => - val t1 = mapOver(t).asInstanceOf[MethodType] - t1.derivedLambdaType(resType = toResult(t1.resType, t1, fail)) - case CapturingType(parent, refs) => - t.derivedCapturingType(this(parent), refs) - case t: (LazyRef | TypeVar) => - mapConserveSuper(t) - case _ => - try - if keepAliases then mapOver(t) - else mapFollowingAliases(t) - catch case ex: AssertionError => - println(i"error while mapping $t") - throw ex - end toResultInResults - - /** If `refs` contains an occurrence of `cap` or `cap.rd`, the current context - * with an added property PrintFresh. This addition causes all occurrences of - * `Fresh` to be printed as `fresh` instead of `cap`, so that one avoids - * confusion in error messages. - */ - def printContext(refs: (Type | CaptureSet)*)(using Context): Context = - def hasCap = new TypeAccumulator[Boolean]: - def apply(x: Boolean, t: Type) = - x || t.dealiasKeepAnnots.match - case Fresh(_) => false - case t: TermRef => t.isCap || this(x, t.widen) - case x: ThisType => false - case _ => foldOver(x, t) - - def containsCap(x: Type | CaptureSet): Boolean = x match - case tp: Type => - hasCap(false, tp) - case refs: CaptureSet => - refs.elems.exists(_.stripReadOnly.isCap) - - if refs.exists(containsCap) then ctx.withProperty(PrintFresh, Some(())) - else ctx - end printContext -end root \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala index cd44ba27df96..472ff11f21c0 100644 --- a/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala @@ -4,8 +4,6 @@ package dotty.tools package dotc.classpath -import scala.language.unsafeNulls - import java.net.URL import scala.collection.mutable.ArrayBuffer import scala.collection.immutable.ArraySeq diff --git a/compiler/src/dotty/tools/dotc/classpath/ClassPathFactory.scala b/compiler/src/dotty/tools/dotc/classpath/ClassPathFactory.scala index 080f8d4e63d2..f434b365c3c9 100644 --- a/compiler/src/dotty/tools/dotc/classpath/ClassPathFactory.scala +++ b/compiler/src/dotty/tools/dotc/classpath/ClassPathFactory.scala @@ -67,7 +67,7 @@ class ClassPathFactory { for file <- files a <- ClassPath.expandManifestPath(file.absolutePath) - path = java.nio.file.Paths.get(a.toURI()).nn + path = java.nio.file.Paths.get(a.toURI()) if Files.exists(path) yield newClassPath(AbstractFile.getFile(path)) @@ -82,7 +82,7 @@ class ClassPathFactory { if (file.isJarOrZip) ZipAndJarSourcePathFactory.create(file) else if (file.isDirectory) - new DirectorySourcePath(file.file) + new DirectorySourcePath(file.file.nn) else sys.error(s"Unsupported sourcepath element: $file") } @@ -94,7 +94,7 @@ object ClassPathFactory { if (file.isJarOrZip) ZipAndJarClassPathFactory.create(file) else if (file.isDirectory) - new DirectoryClassPath(file.file) + new DirectoryClassPath(file.file.nn) else sys.error(s"Unsupported classpath element: $file") } diff --git a/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala index 2d659b532d7b..622e074b9047 100644 --- a/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala @@ -3,8 +3,6 @@ */ package dotty.tools.dotc.classpath -import scala.language.unsafeNulls - import java.io.{File => JFile} import java.net.{URI, URL} import java.nio.file.{FileSystems, Files} @@ -119,7 +117,7 @@ trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends Directo protected def toAbstractFile(f: JFile): AbstractFile = f.toPath.toPlainFile protected def isPackage(f: JFile): Boolean = f.isPackage - assert(dir != null, "Directory file in DirectoryFileLookup cannot be null") + assert(dir.asInstanceOf[JFile | Null] != null, "Directory file in DirectoryFileLookup cannot be null") def asURLs: Seq[URL] = Seq(dir.toURI.toURL) def asClassPathStrings: Seq[String] = Seq(dir.getPath) @@ -216,7 +214,7 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No final class CtSymClassPath(ctSym: java.nio.file.Path, release: Int) extends ClassPath with NoSourcePaths { import java.nio.file.Path, java.nio.file.* - private val fileSystem: FileSystem = FileSystems.newFileSystem(ctSym, null: ClassLoader) + private val fileSystem: FileSystem = FileSystems.newFileSystem(ctSym, null: ClassLoader | Null) private val root: Path = fileSystem.getRootDirectories.iterator.next private val roots = Files.newDirectoryStream(root).iterator.asScala.toList diff --git a/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala b/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala index 4fe57a722780..e51ba29483cb 100644 --- a/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala +++ b/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala @@ -4,8 +4,6 @@ package dotty.tools package dotc.classpath -import scala.language.unsafeNulls - import java.io.{File => JFile, FileFilter} import java.net.URL import dotty.tools.io.AbstractFile diff --git a/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala index 0616d6c14ba6..9ed69942c596 100644 --- a/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala @@ -11,11 +11,11 @@ case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath wi type F = AbstractFile // From AbstractFileClassLoader - private final def lookupPath(base: AbstractFile)(pathParts: Seq[String], directory: Boolean): AbstractFile = { - var file: AbstractFile = base + private final def lookupPath(base: AbstractFile)(pathParts: Seq[String], directory: Boolean): AbstractFile | Null = { + var file: AbstractFile | Null = base val dirParts = pathParts.init.iterator while (dirParts.hasNext) { - val dirPart = dirParts.next + val dirPart = dirParts.next() file = file.lookupName(dirPart, directory = true) if (file == null) return null diff --git a/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala b/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala index 4595f7978999..65a12c596223 100644 --- a/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala +++ b/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala @@ -3,7 +3,6 @@ */ package dotty.tools.dotc.classpath -import scala.language.unsafeNulls import java.io.File import java.net.URL @@ -21,7 +20,7 @@ trait ZipArchiveFileLookup[FileEntryType <: ClassRepresentation] extends Efficie val zipFile: File def release: Option[String] - assert(zipFile != null, "Zip file in ZipArchiveFileLookup cannot be null") + assert(zipFile ne null, "Zip file in ZipArchiveFileLookup cannot be null") override def asURLs: Seq[URL] = Seq(zipFile.toURI.toURL) override def asClassPathStrings: Seq[String] = Seq(zipFile.getPath) diff --git a/compiler/src/dotty/tools/dotc/config/CliCommand.scala b/compiler/src/dotty/tools/dotc/config/CliCommand.scala index a0edb2b8cded..9c1b0871c144 100644 --- a/compiler/src/dotty/tools/dotc/config/CliCommand.scala +++ b/compiler/src/dotty/tools/dotc/config/CliCommand.scala @@ -1,8 +1,6 @@ package dotty.tools.dotc package config -import scala.language.unsafeNulls - import Settings.* import core.Contexts.* import printing.Highlighting @@ -33,8 +31,8 @@ trait CliCommand: | means one or a comma-separated list of: | - (partial) phase names with an optional "+" suffix to include the next phase | - the string "all" - | example: -Xprint:all prints all phases. - | example: -Xprint:typer,mixin prints the typer and mixin phases. + | example: -Vprint:all prints all phases. + | example: -Vprint:typer,mixin prints the typer and mixin phases. | example: -Ylog:erasure+ logs the erasure phase and the phase after the erasure phase. | This is useful because during the tree transform of phase X, we often | already are in phase X + 1. diff --git a/compiler/src/dotty/tools/dotc/config/CommandLineParser.scala b/compiler/src/dotty/tools/dotc/config/CommandLineParser.scala index 2e76561c9913..50aa89779f50 100644 --- a/compiler/src/dotty/tools/dotc/config/CommandLineParser.scala +++ b/compiler/src/dotty/tools/dotc/config/CommandLineParser.scala @@ -114,12 +114,12 @@ object CommandLineParser: def expandArg(arg: String): List[String] = val path = Paths.get(arg.stripPrefix("@")) if !Files.exists(path) then - System.err.nn.println(s"Argument file ${path.nn.getFileName} could not be found") + System.err.println(s"Argument file ${path.getFileName} could not be found") Nil else def stripComment(s: String) = s.indexOf('#') match { case -1 => s case i => s.substring(0, i) } - val lines = Files.readAllLines(path).nn - val params = lines.asScala.map(stripComment).filter(!_.nn.isEmpty).mkString(" ") + val lines = Files.readAllLines(path) + val params = lines.asScala.map(stripComment).filter(!_.isEmpty).mkString(" ") tokenize(params) class ParseException(msg: String) extends RuntimeException(msg) diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index a53346c8a44d..23305a6b0333 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -32,6 +32,7 @@ object Feature: val saferExceptions = experimental("saferExceptions") val pureFunctions = experimental("pureFunctions") val captureChecking = experimental("captureChecking") + val separationChecking = experimental("separationChecking") val into = experimental("into") val modularity = experimental("modularity") val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions") @@ -40,7 +41,7 @@ object Feature: def experimentalAutoEnableFeatures(using Context): List[TermName] = defn.languageExperimentalFeatures .map(sym => experimental(sym.name)) - .filterNot(_ == captureChecking) // TODO is this correct? + .filterNot(sym => sym == captureChecking || sym == separationChecking) // TODO is this correct? val values = List( (nme.help, "Display all available features"), @@ -60,6 +61,7 @@ object Feature: (saferExceptions, "Enable safer exceptions"), (pureFunctions, "Enable pure functions for capture checking"), (captureChecking, "Enable experimental capture checking"), + (separationChecking, "Enable experimental separation checking (requires captureChecking)"), (into, "Allow into modifier on parameter types"), (modularity, "Enable experimental modularity features"), (packageObjectValues, "Enable experimental package objects as values"), @@ -133,7 +135,7 @@ object Feature: /** Is captureChecking enabled for this compilation unit? */ def ccEnabled(using Context) = enabledBySetting(captureChecking) - || ctx.compilationUnit.needsCaptureChecking + || ctx.originalCompilationUnit.needsCaptureChecking /** Is pureFunctions enabled for any of the currently compiled compilation units? */ def pureFunsEnabledSomewhere(using Context) = @@ -154,6 +156,10 @@ object Feature: case Some(v) => v case none => sourceVersionSetting + /* Should we behave as scala 2?*/ + def shouldBehaveAsScala2(using Context): Boolean = + ctx.settings.YcompileScala2Library.value || sourceVersion.isScala2 + def migrateTo3(using Context): Boolean = sourceVersion == `3.0-migration` diff --git a/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala b/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala index f77aa0b06308..9b1b4b5f07cc 100644 --- a/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala +++ b/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala @@ -31,8 +31,9 @@ enum MigrationVersion(val warnFrom: SourceVersion, val errorFrom: SourceVersion) case ImportRename extends MigrationVersion(future, future) case ParameterEnclosedByParenthesis extends MigrationVersion(future, future) case XmlLiteral extends MigrationVersion(future, future) - case GivenSyntax extends MigrationVersion(future, never) + case GivenSyntax extends MigrationVersion(future, future) case ImplicitParamsWithoutUsing extends MigrationVersion(`3.7`, future) + case Scala2Implicits extends MigrationVersion(future, future) require(warnFrom.ordinal <= errorFrom.ordinal) diff --git a/compiler/src/dotty/tools/dotc/config/OutputDirs.scala b/compiler/src/dotty/tools/dotc/config/OutputDirs.scala index ba121d06e35a..ff0f59cb1309 100644 --- a/compiler/src/dotty/tools/dotc/config/OutputDirs.scala +++ b/compiler/src/dotty/tools/dotc/config/OutputDirs.scala @@ -2,7 +2,6 @@ package dotty.tools package dotc package config -import scala.language.unsafeNulls import io.* @@ -30,10 +29,10 @@ class OutputDirs { /** Check that dir is exists and is a directory. */ private def checkDir(dir: AbstractFile, name: String, allowJar: Boolean = false): AbstractFile = ( - if (dir != null && dir.isDirectory) + if ((dir ne null) && dir.isDirectory) dir // was: else if (allowJar && dir == null && Path.isJarOrZip(name, false)) - else if (allowJar && dir == null && Jar.isJarOrZip(File(name), false)) + else if (allowJar && (dir eq null) && Jar.isJarOrZip(File(name), false)) new PlainFile(Path(name)) else throw new FatalError(name + " does not exist or is not a directory")) diff --git a/compiler/src/dotty/tools/dotc/config/PathResolver.scala b/compiler/src/dotty/tools/dotc/config/PathResolver.scala index 67be0e3587cb..68960d5134ba 100644 --- a/compiler/src/dotty/tools/dotc/config/PathResolver.scala +++ b/compiler/src/dotty/tools/dotc/config/PathResolver.scala @@ -2,8 +2,6 @@ package dotty.tools package dotc package config -import scala.language.unsafeNulls - import WrappedProperties.AccessControl import io.{ClassPath, Directory, Path} import classpath.{AggregateClassPath, ClassPathFactory, JrtClassPath} diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 81fd60497025..4c66e1cdf833 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -5,7 +5,7 @@ import core.Contexts.{Context, ctx} object Printers { class Printer { - def println(msg: => String): Unit = System.out.nn.println(msg) + def println(msg: => String): Unit = System.out.println(msg) } object noPrinter extends Printer { diff --git a/compiler/src/dotty/tools/dotc/config/Properties.scala b/compiler/src/dotty/tools/dotc/config/Properties.scala index c2046899aaef..02e9bd2f53b9 100644 --- a/compiler/src/dotty/tools/dotc/config/Properties.scala +++ b/compiler/src/dotty/tools/dotc/config/Properties.scala @@ -2,7 +2,6 @@ package dotty.tools package dotc package config -import scala.language.unsafeNulls import scala.annotation.internal.sharable @@ -45,10 +44,10 @@ 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 = Option(System.getProperty(name)).getOrElse(alt) + def propOrNone(name: String): Option[String] = Option[String](System.getProperty(name)) + def propOrElse(name: String, alt: => String): String = propOrNone(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)) + def propOrNull(name: String): String|Null = propOrNone(name).orNull def propOrFalse(name: String): Boolean = propOrNone(name) exists (x => List("yes", "on", "true") contains x.toLowerCase) def setProp(name: String, value: String): String = System.setProperty(name, value) def clearProp(name: String): String = System.clearProperty(name) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 0517d5b46b9e..2d048befe171 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -1,7 +1,6 @@ package dotty.tools.dotc package config -import scala.language.unsafeNulls import dotty.tools.dotc.config.PathResolver.Defaults import dotty.tools.dotc.config.Settings.{Setting, SettingGroup, SettingCategory, Deprecation} import dotty.tools.dotc.config.SourceVersion @@ -145,7 +144,7 @@ private sealed trait PluginSettings: private sealed trait VerboseSettings: self: SettingGroup => val Vhelp: Setting[Boolean] = BooleanSetting(VerboseSetting, "V", "Print a synopsis of verbose options.") - val Xprint: Setting[List[String]] = PhasesSetting(VerboseSetting, "Vprint", "Print out program after", aliases = List("-Xprint")) + val Vprint: Setting[List[String]] = PhasesSetting(VerboseSetting, "Vprint", "Print out program after", aliases = List("-Xprint")) val XshowPhases: Setting[Boolean] = BooleanSetting(VerboseSetting, "Vphases", "List compiler phases.", aliases = List("-Xshow-phases")) val Vprofile: Setting[Boolean] = BooleanSetting(VerboseSetting, "Vprofile", "Show metrics about sources and internal representations to estimate compile-time complexity.") @@ -160,7 +159,7 @@ private sealed trait WarningSettings: self: SettingGroup => val Whelp: Setting[Boolean] = BooleanSetting(WarningSetting, "W", "Print a synopsis of warning options.") - val XfatalWarnings: Setting[Boolean] = BooleanSetting(WarningSetting, "Werror", "Fail the compilation if there are any warnings.", aliases = List("-Xfatal-warnings")) + val Werror: Setting[Boolean] = BooleanSetting(WarningSetting, "Werror", "Fail the compilation if there are any warnings.", aliases = List("-Xfatal-warnings")) val Wall: Setting[Boolean] = BooleanSetting(WarningSetting, "Wall", "Enable all warning settings.") private val WvalueDiscard: Setting[Boolean] = BooleanSetting(WarningSetting, "Wvalue-discard", "Warn when non-Unit expression results are unused.") private val WNonUnitStatement = BooleanSetting(WarningSetting, "Wnonunit-statement", "Warn when block statements are non-Unit expressions.") @@ -168,6 +167,8 @@ private sealed trait WarningSettings: private val WimplausiblePatterns = BooleanSetting(WarningSetting, "Wimplausible-patterns", "Warn if comparison with a pattern value looks like it might always fail.") private val WunstableInlineAccessors = BooleanSetting(WarningSetting, "WunstableInlineAccessors", "Warn an inline methods has references to non-stable binary APIs.") private val WtoStringInterpolated = BooleanSetting(WarningSetting, "Wtostring-interpolated", "Warn a standard interpolator used toString on a reference type.") + private val WrecurseWithDefault = BooleanSetting(WarningSetting, "Wrecurse-with-default", "Warn when a method calls itself with a default argument.") + private val WwrongArrow = BooleanSetting(WarningSetting, "Wwrong-arrow", "Warn if function arrow was used instead of context literal ?=>.") private val Wunused: Setting[List[ChoiceWithHelp[String]]] = MultiChoiceHelpSetting( WarningSetting, name = "Wunused", @@ -299,7 +300,7 @@ private sealed trait WarningSettings: def typeParameterShadow(using Context) = allOr("type-parameter-shadow") - val WcheckInit: Setting[Boolean] = BooleanSetting(WarningSetting, "Wsafe-init", "Ensure safe initialization of objects.") + val WsafeInit: Setting[Boolean] = BooleanSetting(WarningSetting, "Wsafe-init", "Ensure safe initialization of objects.") object Whas: def allOr(s: Setting[Boolean])(using Context): Boolean = @@ -310,7 +311,9 @@ private sealed trait WarningSettings: def implausiblePatterns(using Context): Boolean = allOr(WimplausiblePatterns) def unstableInlineAccessors(using Context): Boolean = allOr(WunstableInlineAccessors) def toStringInterpolated(using Context): Boolean = allOr(WtoStringInterpolated) - def checkInit(using Context): Boolean = allOr(WcheckInit) + def recurseWithDefault(using Context): Boolean = allOr(WrecurseWithDefault) + def wrongArrow(using Context): Boolean = allOr(WwrongArrow) + def safeInit(using Context): Boolean = allOr(WsafeInit) /** -X "Extended" or "Advanced" settings */ private sealed trait XSettings: @@ -398,6 +401,7 @@ private sealed trait YSettings: val Ylog: Setting[List[String]] = PhasesSetting(ForkSetting, "Ylog", "Log operations during") val YlogClasspath: Setting[Boolean] = BooleanSetting(ForkSetting, "Ylog-classpath", "Output information about what classpath is being applied.") val YdisableFlatCpCaching: Setting[Boolean] = BooleanSetting(ForkSetting, "YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.") + val YnoStdlibPatches: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-stdlib-patches", "Do not patch stdlib files (temporary and only to be used for the stdlib migration)", false) val Yscala2Unpickler: Setting[String] = StringSetting(ForkSetting, "Yscala2-unpickler", "", "Control where we may get Scala 2 symbols from. This is either \"always\", \"never\", or a classpath.", "always") @@ -444,12 +448,14 @@ private sealed trait YSettings: val YbestEffort: Setting[Boolean] = BooleanSetting(ForkSetting, "Ybest-effort", "Enable best-effort compilation attempting to produce betasty to the META-INF/best-effort directory, regardless of errors, as part of the pickler phase.") 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.") + val YmagicOffsetHeader: Setting[String] = StringSetting(ForkSetting, "Ymagic-offset-header", "header", "Specify the magic header comment that marks the start of the actual code in generated wrapper scripts. Example: -Ymagic-offset-header:SOURCE_CODE_START. Then, in the source, the magic comment `///SOURCE_CODE_START:` marks the start of user code. The comment should be suffixed by `:` to indicate the original file.", "") + // Experimental language features @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.") + val YsafeInitGlobal: Setting[Boolean] = BooleanSetting(ForkSetting, "Ysafe-init-global", "Check safe initialization of global objects.") val YrequireTargetName: Setting[Boolean] = BooleanSetting(ForkSetting, "Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation.") val YrecheckTest: Setting[Boolean] = BooleanSetting(ForkSetting, "Yrecheck-test", "Run basic rechecking (internal test only).") val YccDebug: Setting[Boolean] = BooleanSetting(ForkSetting, "Ycc-debug", "Used in conjunction with captureChecking language import, debug info for captured references.") @@ -465,6 +471,7 @@ private sealed trait YSettings: val Yinstrument: Setting[Boolean] = BooleanSetting(ForkSetting, "Yinstrument", "Add instrumentation code that counts allocations and closure creations.") val YinstrumentDefs: Setting[Boolean] = BooleanSetting(ForkSetting, "Yinstrument-defs", "Add instrumentation code that counts method calls; needs -Yinstrument to be set, too.") + val YimplicitToGiven: Setting[Boolean] = BooleanSetting(ForkSetting, "Yimplicit-to-given", "Allows to rewrite the implicit keywords to their scala-3 given counterparts. Does not adjust imports. Use in conjunction with --rewrite.") // Deprecated: lifted from -Y to -X @deprecated(message = "Lifted to -X, Scheduled for removal.", since = "3.5.0") diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettingsProperties.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettingsProperties.scala index e42d2d53529e..b50fe0b93023 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettingsProperties.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettingsProperties.scala @@ -4,7 +4,6 @@ package config import Settings.Setting.ChoiceWithHelp import dotty.tools.backend.jvm.BackendUtils.classfileVersionMap import dotty.tools.io.{AbstractFile, Directory, JDK9Reflectors, PlainDirectory, NoAbstractFile} -import scala.language.unsafeNulls object ScalaSettingsProperties: @@ -25,7 +24,8 @@ object ScalaSettingsProperties: ScalaRelease.values.toList.map(_.show) def supportedSourceVersions: List[String] = - (SourceVersion.values.toList.diff(SourceVersion.illegalSourceVersionNames)).toList.map(_.toString) + SourceVersion.values.diff(SourceVersion.illegalInSettings) + .map(_.toString).toList def supportedLanguageFeatures: List[ChoiceWithHelp[String]] = Feature.values.map((n, d) => ChoiceWithHelp(n.toString, d)) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaVersion.scala b/compiler/src/dotty/tools/dotc/config/ScalaVersion.scala index 9f603e6792be..a9fffa7a3bb6 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaVersion.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaVersion.scala @@ -3,8 +3,6 @@ package dotty.tools package dotc.config -import scala.language.unsafeNulls - import scala.annotation.internal.sharable import scala.util.{Try, Success, Failure} @@ -82,20 +80,20 @@ case class SpecificScalaVersion(major: Int, minor: Int, rev: Int, build: ScalaBu "The minor and revision parts are optional." )) - def toInt(s: String) = s match { + def toInt(s: String | Null) = s match { case null | "" => 0 - case _ => s.toInt + case _ => s.nn.toInt } def isInt(s: String) = Try(toInt(s)).isSuccess import ScalaBuild.* - def toBuild(s: String) = s match { + def toBuild(s: String | Null) = s match { case null | "FINAL" => Final case s if (s.toUpperCase.startsWith("RC") && isInt(s.substring(2))) => RC(toInt(s.substring(2))) case s if (s.toUpperCase.startsWith("M") && isInt(s.substring(1))) => Milestone(toInt(s.substring(1))) - case _ => Development(s) + case _ => Development(s.nn) } try versionString match { diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala index f85f2cc57de4..7842113b5e48 100644 --- a/compiler/src/dotty/tools/dotc/config/Settings.scala +++ b/compiler/src/dotty/tools/dotc/config/Settings.scala @@ -1,8 +1,6 @@ package dotty.tools.dotc package config -import scala.language.unsafeNulls - import core.Contexts.* import dotty.tools.io.{AbstractFile, Directory, JarArchive, PlainDirectory} diff --git a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala index 30a88fb79f2a..aa2e467289b8 100644 --- a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala +++ b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala @@ -8,7 +8,8 @@ import Feature.isPreviewEnabled import util.Property enum SourceVersion: - case `3.0-migration`, `3.0`, `3.1` // Note: do not add `3.1-migration` here, 3.1 is the same language as 3.0. + case `3.0-migration`, `3.0` + case `3.1-migration`, `3.1` case `3.2-migration`, `3.2` case `3.3-migration`, `3.3` case `3.4-migration`, `3.4` @@ -16,7 +17,9 @@ enum SourceVersion: case `3.6-migration`, `3.6` case `3.7-migration`, `3.7` case `3.8-migration`, `3.8` + // Add 3.x-migration and 3.x here // !!! Keep in sync with scala.runtime.stdlibPatches.language !!! + case `2.13` case `future-migration`, `future` case `never` // needed for MigrationVersion.errorFrom if we never want to issue an error @@ -33,17 +36,29 @@ enum SourceVersion: def isAtMost(v: SourceVersion) = stable.ordinal <= v.ordinal + def isScala2 = this == `2.13` + 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 + def requiresNewSyntax = isAtLeast(future) + object SourceVersion extends Property.Key[SourceVersion]: - def defaultSourceVersion = `3.7` + + /* The default source version used by the built compiler */ + val defaultSourceVersion = `3.7` + + /* Illegal source versions that may not appear in the settings `-source:<...>` */ + val illegalInSettings = List(`2.13`, `3.1-migration`, `never`) + + /* Illegal source versions that may not appear as an import `import scala.language.<...>` */ + val illegalInImports = List(`3.1-migration`, `never`) /** 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) + val illegalSourceVersionNames = illegalInImports.map(_.toString.toTermName) /** language versions that the compiler recognises. */ val validSourceVersionNames = values.toList.map(_.toString.toTermName) diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index 81b03d765676..9f707c8bd2bb 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -48,8 +48,6 @@ object CheckRealizable { def boundsRealizability(tp: Type)(using Context): Realizability = new CheckRealizable().boundsRealizability(tp) - - private val LateInitializedFlags = Lazy | Erased } /** Compute realizability status. @@ -72,7 +70,7 @@ class CheckRealizable(using Context) { /** Is symbol's definitition a lazy or erased val? * (note we exclude modules here, because their realizability is ensured separately) */ - private def isLateInitialized(sym: Symbol) = sym.isOneOf(LateInitializedFlags, butNot = Module) + private def isLateInitialized(sym: Symbol) = sym.is(Lazy, butNot = Module) /** The realizability status of given type `tp`*/ def realizability(tp: Type): Realizability = tp.dealias match { @@ -184,7 +182,7 @@ class CheckRealizable(using Context) { private def memberRealizability(tp: Type) = { def checkField(sofar: Realizability, fld: SingleDenotation): Realizability = sofar andAlso { - if (checkedFields.contains(fld.symbol) || fld.symbol.isOneOf(Private | Mutable | LateInitializedFlags)) + if (checkedFields.contains(fld.symbol) || fld.symbol.isOneOf(Private | Mutable | Lazy)) // if field is private it cannot be part of a visible path // if field is mutable it cannot be part of a path // if field is lazy or erased it does not need to be initialized when the owning object is @@ -196,7 +194,7 @@ class CheckRealizable(using Context) { } } if sourceVersion.isAtLeast(future) then - // check fields only from version 3.x. + // check fields only from version 3.future. // Reason: An embedded field could well be nullable, which means it // should not be part of a path and need not be checked; but we cannot recognize // this situation until we have a typesystem that tracks nullability. diff --git a/compiler/src/dotty/tools/dotc/core/Comments.scala b/compiler/src/dotty/tools/dotc/core/Comments.scala index b1d1e387c2cf..00f5b578b4d1 100644 --- a/compiler/src/dotty/tools/dotc/core/Comments.scala +++ b/compiler/src/dotty/tools/dotc/core/Comments.scala @@ -2,8 +2,6 @@ package dotty.tools package dotc package core -import scala.language.unsafeNulls - import ast.{ untpd, tpd } import Symbols.*, Contexts.* import util.{SourceFile, ReadOnlyMap} diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index e12ab1cc2da2..5a0e03330ef2 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -42,6 +42,7 @@ import plugins.* import java.util.concurrent.atomic.AtomicInteger import java.nio.file.InvalidPathException import dotty.tools.dotc.coverage.Coverage +import scala.annotation.tailrec object Contexts { @@ -135,7 +136,7 @@ object Contexts { def outersIterator: Iterator[Context] = new Iterator[Context] { var current = thiscontext def hasNext = current != NoContext - def next = { val c = current; current = current.outer; c } + def next() = { val c = current; current = current.outer; c } } def period: Period @@ -401,8 +402,8 @@ object Contexts { * * - as owner: The primary constructor of the class * - as outer context: The context enclosing the class context - * - as scope: type parameters, the parameter accessors, and - * the context bound companions in the class context, + * - as scope: type parameters, the parameter accessors, + * the dummy capture parameters and the context bound companions in the class context, * * The reasons for this peculiar choice of attributes are as follows: * @@ -419,7 +420,7 @@ object Contexts { def superCallContext: Context = val locals = owner.typeParams ++ owner.asClass.unforcedDecls.filter: sym => - sym.is(ParamAccessor) || sym.isContextBoundCompanion + sym.is(ParamAccessor) || sym.isContextBoundCompanion || sym.isDummyCaptureParam superOrThisCallContext(owner.primaryConstructor, newScopeWith(locals*)) /** The context for the arguments of a this(...) constructor call. @@ -776,6 +777,14 @@ object Contexts { c end FreshContext + extension (ctx: Context) + /** Get the original compilation unit, ignoring any highlighting wrappers. */ + @tailrec + def originalCompilationUnit: CompilationUnit = + val cu = ctx.compilationUnit + if cu.source.name == SyntaxHighlighting.VirtualSourceName then ctx.outer.originalCompilationUnit + else cu + extension (c: Context) def addNotNullInfo(info: NotNullInfo) = if c.explicitNulls then c.withNotNullInfos(c.notNullInfos.extendWith(info)) else c @@ -787,18 +796,16 @@ object Contexts { if !c.explicitNulls || (c.notNullInfos eq infos) then c else c.fresh.setNotNullInfos(infos) // TODO: Fix issue when converting ModeChanges and FreshModeChanges to extension givens - extension (c: Context) { + extension (c: Context) final def withModeBits(mode: Mode): Context = if (mode != c.mode) c.fresh.setMode(mode) else c final def addMode(mode: Mode): Context = withModeBits(c.mode | mode) final def retractMode(mode: Mode): Context = withModeBits(c.mode &~ mode) - } - extension (c: FreshContext) { + extension (c: FreshContext) final def addMode(mode: Mode): c.type = c.setMode(c.mode | mode) final def retractMode(mode: Mode): c.type = c.setMode(c.mode &~ mode) - } /** Run `op` with a pool-allocated context that has an ExporeTyperState. */ inline def explore[T](inline op: Context ?=> T)(using Context): T = @@ -863,6 +870,13 @@ object Contexts { result.init(ctx) result + def currentComparer(using Context): TypeComparer = + val base = ctx.base + if base.comparersInUse > 0 then + base.comparers(base.comparersInUse - 1) + else + comparer + inline def comparing[T](inline op: TypeComparer => T)(using Context): T = util.Stats.record("comparing") val saved = ctx.base.comparersInUse diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 96a2d45db80d..f8096dddeedd 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -51,7 +51,7 @@ object Decorators { if name.length != 0 then name.getChars(0, name.length, chars, s.length) termName(chars, 0, len) case name: TypeName => s.concat(name.toTermName) - case _ => termName(s.concat(name.toString).nn) + case _ => termName(s.concat(name.toString)) def indented(width: Int): String = val padding = " " * width @@ -289,10 +289,10 @@ object Decorators { case NonFatal(ex) if !ctx.settings.YshowPrintErrors.value => s"... (cannot display due to ${ex.className} ${ex.getMessage}) ..." - case _ => String.valueOf(x).nn + case _ => String.valueOf(x) /** Returns the simple class name of `x`. */ - def className: String = if x == null then "" else x.getClass.getSimpleName.nn + def className: String = if x == null then "" else x.getClass.getSimpleName extension [T](x: T) def assertingErrorsReported(using Context): T = { diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 14f2491214e2..d58c103904b0 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -15,7 +15,7 @@ import Comments.{Comment, docCtx} import util.Spans.NoSpan import config.Feature import Symbols.requiredModuleRef -import cc.{CaptureSet, RetainingType, readOnly} +import cc.{CaptureSet, RetainingType} import ast.tpd.ref import scala.annotation.tailrec @@ -123,7 +123,7 @@ class Definitions { HKTypeLambda(argParamNames :+ "R".toTypeName, argVariances :+ Covariant)( tl => List.fill(arity + 1)(TypeBounds.empty), tl => RetainingType(underlyingClass.typeRef.appliedTo(tl.paramRefs), - ref(captureRoot.termRef) :: Nil) + captureRoot.termRef) )) else val cls = denot.asClass.classSymbol @@ -266,6 +266,7 @@ class Definitions { @tu lazy val CompiletimeOpsDoubleModuleClass: Symbol = requiredModule("scala.compiletime.ops.double").moduleClass @tu lazy val CompiletimeOpsStringModuleClass: Symbol = requiredModule("scala.compiletime.ops.string").moduleClass @tu lazy val CompiletimeOpsBooleanModuleClass: Symbol = requiredModule("scala.compiletime.ops.boolean").moduleClass + @tu lazy val ErasedClass: ClassSymbol = requiredClass("scala.compiletime.Erased") /** Note: We cannot have same named methods defined in Object and Any (and AnyVal, for that matter) * because after erasure the Any and AnyVal references get remapped to the Object methods @@ -428,8 +429,8 @@ class Definitions { @tu lazy val AnyRefAlias: TypeSymbol = enterAliasType(tpnme.AnyRef, ObjectType) def AnyRefType: TypeRef = AnyRefAlias.typeRef - @tu lazy val Object_eq: TermSymbol = enterMethod(ObjectClass, nme.eq, methOfAnyRef(BooleanType), Final) - @tu lazy val Object_ne: TermSymbol = enterMethod(ObjectClass, nme.ne, methOfAnyRef(BooleanType), Final) + @tu lazy val Object_eq: TermSymbol = enterMethod(ObjectClass, nme.eq, methOfAnyRef(BooleanType), Final | Infix) + @tu lazy val Object_ne: TermSymbol = enterMethod(ObjectClass, nme.ne, methOfAnyRef(BooleanType), Final | Infix) @tu lazy val Object_synchronized: TermSymbol = enterPolyMethod(ObjectClass, nme.synchronized_, 1, pt => MethodType(List(pt.paramRefs(0)), pt.paramRefs(0)), Final) @tu lazy val Object_clone: TermSymbol = enterMethod(ObjectClass, nme.clone_, MethodType(Nil, ObjectType), Protected) @@ -543,7 +544,7 @@ class Definitions { // needed as a synthetic class because Scala 2.x refers to it in classfiles // but does not define it as an explicit class. val cls = enterCompleteClassSymbol( - ScalaPackageClass, tpnme.Singleton, PureInterfaceCreationFlags | Final | Erased, + ScalaPackageClass, tpnme.Singleton, PureInterfaceCreationFlags | Final, List(AnyType)) enterTypeField(cls, tpnme.Self, Deferred, cls.info.decls.openForMutations) cls @@ -753,6 +754,9 @@ class Definitions { @tu lazy val StringBuilderClass: ClassSymbol = requiredClass("scala.collection.mutable.StringBuilder") @tu lazy val MatchErrorClass : ClassSymbol = requiredClass("scala.MatchError") @tu lazy val ConversionClass : ClassSymbol = requiredClass("scala.Conversion").typeRef.symbol.asClass + @tu lazy val ConversionModule : Symbol = ConversionClass.companionModule + @tu lazy val ConversionModuleClass: ClassSymbol = ConversionModule.moduleClass.asClass + @tu lazy val Conversion_into : Symbol = ConversionModuleClass.requiredType("into") @tu lazy val StringAddClass : ClassSymbol = requiredClass("scala.runtime.StringAdd") @tu lazy val StringAdd_+ : Symbol = StringAddClass.requiredMethod(nme.raw.PLUS) @@ -1001,22 +1005,20 @@ class Definitions { @tu lazy val Caps_Capability: ClassSymbol = requiredClass("scala.caps.Capability") @tu lazy val Caps_CapSet: ClassSymbol = requiredClass("scala.caps.CapSet") @tu lazy val CapsInternalModule: Symbol = requiredModule("scala.caps.internal") - @tu lazy val Caps_reachCapability: TermSymbol = CapsInternalModule.requiredMethod("reachCapability") - @tu lazy val Caps_readOnlyCapability: TermSymbol = CapsInternalModule.requiredMethod("readOnlyCapability") - @tu lazy val Caps_capsOf: TermSymbol = CapsInternalModule.requiredMethod("capsOf") + @tu lazy val Caps_erasedValue: Symbol = CapsInternalModule.requiredMethod("erasedValue") @tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe") @tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure") @tu lazy val Caps_unsafeAssumeSeparate: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumeSeparate") + @tu lazy val Caps_unsafeErasedValue: Symbol = CapsUnsafeModule.requiredMethod("unsafeErasedValue") @tu lazy val Caps_ContainsTrait: TypeSymbol = CapsModule.requiredType("Contains") @tu lazy val Caps_ContainsModule: Symbol = requiredModule("scala.caps.Contains") @tu lazy val Caps_containsImpl: TermSymbol = Caps_ContainsModule.requiredMethod("containsImpl") @tu lazy val Caps_Mutable: ClassSymbol = requiredClass("scala.caps.Mutable") - @tu lazy val Caps_SharedCapability: ClassSymbol = requiredClass("scala.caps.SharedCapability") + @tu lazy val Caps_Sharable: ClassSymbol = requiredClass("scala.caps.Sharable") + @tu lazy val Caps_Control: ClassSymbol = requiredClass("scala.caps.Control") + @tu lazy val Caps_Classifier: ClassSymbol = requiredClass("scala.caps.Classifier") - /** The same as CaptureSet.universal but generated implicitly for references of Capability subtypes */ - @tu lazy val universalCSImpliedByCapability = CaptureSet(captureRoot.termRef.readOnly) - - @tu lazy val PureClass: Symbol = requiredClass("scala.Pure") + @tu lazy val PureClass: ClassSymbol = requiredClass("scala.Pure") // Annotation base classes @tu lazy val AnnotationClass: ClassSymbol = requiredClass("scala.annotation.Annotation") @@ -1038,9 +1040,8 @@ class Definitions { @tu lazy val DeprecatedInheritanceAnnot: ClassSymbol = requiredClass("scala.deprecatedInheritance") @tu lazy val ImplicitAmbiguousAnnot: ClassSymbol = requiredClass("scala.annotation.implicitAmbiguous") @tu lazy val ImplicitNotFoundAnnot: ClassSymbol = requiredClass("scala.annotation.implicitNotFound") + @tu lazy val InferredDepFunAnnot: ClassSymbol = requiredClass("scala.caps.internal.inferredDepFun") @tu lazy val InlineParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InlineParam") - @tu lazy val IntoAnnot: ClassSymbol = requiredClass("scala.annotation.into") - @tu lazy val IntoParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.$into") @tu lazy val ErasedParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ErasedParam") @tu lazy val MainAnnot: ClassSymbol = requiredClass("scala.main") @tu lazy val MappedAlternativeAnnot: ClassSymbol = requiredClass("scala.annotation.internal.MappedAlternative") @@ -1058,6 +1059,7 @@ class Definitions { // @tu lazy val ScalaStrictFPAnnot: ClassSymbol = requiredClass("scala.annotation.strictfp") @tu lazy val ScalaStaticAnnot: ClassSymbol = requiredClass("scala.annotation.static") @tu lazy val SerialVersionUIDAnnot: ClassSymbol = requiredClass("scala.SerialVersionUID") + @tu lazy val SilentIntoAnnot: ClassSymbol = requiredClass("scala.annotation.internal.$into") @tu lazy val TailrecAnnot: ClassSymbol = requiredClass("scala.annotation.tailrec") @tu lazy val ThreadUnsafeAnnot: ClassSymbol = requiredClass("scala.annotation.threadUnsafe") @tu lazy val ConstructorOnlyAnnot: ClassSymbol = requiredClass("scala.annotation.constructorOnly") @@ -1092,13 +1094,14 @@ class Definitions { @tu lazy val ReachCapabilityAnnot = requiredClass("scala.annotation.internal.reachCapability") @tu lazy val RootCapabilityAnnot = requiredClass("scala.caps.internal.rootCapability") @tu lazy val ReadOnlyCapabilityAnnot = requiredClass("scala.annotation.internal.readOnlyCapability") + @tu lazy val OnlyCapabilityAnnot = requiredClass("scala.annotation.internal.onlyCapability") @tu lazy val RequiresCapabilityAnnot: ClassSymbol = requiredClass("scala.annotation.internal.requiresCapability") @tu lazy val RetainsAnnot: ClassSymbol = requiredClass("scala.annotation.retains") @tu lazy val RetainsCapAnnot: ClassSymbol = requiredClass("scala.annotation.retainsCap") @tu lazy val RetainsByNameAnnot: ClassSymbol = requiredClass("scala.annotation.retainsByName") - @tu lazy val RetainsArgAnnot: ClassSymbol = requiredClass("scala.annotation.retainsArg") @tu lazy val PublicInBinaryAnnot: ClassSymbol = requiredClass("scala.annotation.publicInBinary") @tu lazy val WitnessNamesAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WitnessNames") + @tu lazy val StableNullAnnot: ClassSymbol = requiredClass("scala.annotation.stableNull") @tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable") @@ -1111,13 +1114,13 @@ class Definitions { @tu lazy val NonBeanMetaAnnots: Set[Symbol] = Set(FieldMetaAnnot, GetterMetaAnnot, ParamMetaAnnot, SetterMetaAnnot, CompanionClassMetaAnnot, CompanionMethodMetaAnnot) @tu lazy val NonBeanParamAccessorAnnots: Set[Symbol] = - Set(PublicInBinaryAnnot) + Set(PublicInBinaryAnnot, UseAnnot, ConsumeAnnot) @tu lazy val MetaAnnots: Set[Symbol] = NonBeanMetaAnnots + BeanGetterMetaAnnot + BeanSetterMetaAnnot // Set of annotations that are not printed in types except under -Yprint-debug @tu lazy val SilentAnnots: Set[Symbol] = - Set(InlineParamAnnot, ErasedParamAnnot, RefineOverrideAnnot) + Set(InlineParamAnnot, ErasedParamAnnot, RefineOverrideAnnot, SilentIntoAnnot, UseAnnot, ConsumeAnnot) // A list of annotations that are commonly used to indicate that a field/method argument or return // type is not null. These annotations are used by the nullification logic in JavaNullInterop to @@ -1332,8 +1335,8 @@ class Definitions { */ object ByNameFunction: def apply(tp: Type)(using Context): Type = tp match - case tp @ RetainingType(tp1, refs) if tp.annot.symbol == RetainsByNameAnnot => - RetainingType(apply(tp1), refs) + case tp @ RetainingType(tp1, refSet) if tp.annot.symbol == RetainsByNameAnnot => + RetainingType(apply(tp1), refSet) case _ => defn.ContextFunction0.typeRef.appliedTo(tp :: Nil) def unapply(tp: Type)(using Context): Option[Type] = tp match @@ -1387,6 +1390,9 @@ class Definitions { final def isNamedTuple_From(sym: Symbol)(using Context): Boolean = sym.name == tpnme.From && sym.owner == NamedTupleModule.moduleClass + final def isInto(sym: Symbol)(using Context): Boolean = + sym.name == tpnme.into && sym.owner == ConversionModuleClass + private val compiletimePackageAnyTypes: Set[Name] = Set( tpnme.Equals, tpnme.NotEquals, tpnme.IsConst, tpnme.ToString ) @@ -1469,6 +1475,11 @@ class Definitions { * is read from a classfile. */ def patchStdLibClass(denot: ClassDenotation)(using Context): Unit = + // Do not patch the stdlib files if we explicitly disable it + // This is only to be used during the migration of the stdlib + if ctx.settings.YnoStdlibPatches.value then + return + def patch2(denot: ClassDenotation, patchCls: Symbol): Unit = val scope = denot.info.decls.openForMutations @@ -1537,9 +1548,7 @@ class Definitions { denot.sourceModule.info = denot.typeRef // we run into a cyclic reference when patching if this line is omitted patch2(denot, patchCls) - if ctx.settings.YcompileScala2Library.value then - () - else if denot.name == tpnme.Predef.moduleClassName && denot.symbol == ScalaPredefModuleClass then + if denot.name == tpnme.Predef.moduleClassName && denot.symbol == ScalaPredefModuleClass then patchWith(ScalaPredefModuleClassPatch) else if denot.name == tpnme.language.moduleClassName && denot.symbol == LanguageModuleClass then patchWith(LanguageModuleClassPatch) @@ -1561,8 +1570,10 @@ class Definitions { @tu lazy val pureSimpleClasses = Set(StringClass, NothingClass, NullClass) ++ ScalaValueClasses() - @tu lazy val capabilityWrapperAnnots: Set[Symbol] = - Set(ReachCapabilityAnnot, ReadOnlyCapabilityAnnot, MaybeCapabilityAnnot, RootCapabilityAnnot) + @tu lazy val capsErasedValueMethods = + Set(Caps_erasedValue, Caps_unsafeErasedValue) + @tu lazy val erasedValueMethods = + capsErasedValueMethods + Compiletime_erasedValue @tu lazy val AbstractFunctionType: Array[TypeRef] = mkArityArray("scala.runtime.AbstractFunction", MaxImplementedFunctionArity, 0).asInstanceOf[Array[TypeRef]] val AbstractFunctionClassPerRun: PerRun[Array[Symbol]] = new PerRun(AbstractFunctionType.map(_.symbol.asClass)) @@ -1880,7 +1891,7 @@ class Definitions { || tp.derivesFrom(defn.PolyFunctionClass) // TODO check for refinement? private def withSpecMethods(cls: ClassSymbol, bases: List[Name], paramTypes: Set[TypeRef]) = - if !ctx.settings.YcompileScala2Library.value then + if !Feature.shouldBehaveAsScala2 then for base <- bases; tp <- paramTypes do cls.enter(newSymbol(cls, base.specializedName(List(tp)), Method, ExprType(tp))) cls @@ -1923,7 +1934,7 @@ class Definitions { case List(x, y) => Tuple2SpecializedParamClasses().contains(x.classSymbol) && Tuple2SpecializedParamClasses().contains(y.classSymbol) case _ => false && base.owner.denot.info.member(base.name.specializedName(args)).exists // when dotc compiles the stdlib there are no specialised classes - && !ctx.settings.YcompileScala2Library.value // We do not add the specilized TupleN methods/classes when compiling the stdlib + && !Feature.shouldBehaveAsScala2 // We do not add the specilized TupleN methods/classes when compiling the stdlib def isSpecializableFunction(cls: ClassSymbol, paramTypes: List[Type], retType: Type)(using Context): Boolean = paramTypes.length <= 2 @@ -1945,7 +1956,7 @@ class Definitions { case _ => false }) - && !ctx.settings.YcompileScala2Library.value // We do not add the specilized FunctionN methods/classes when compiling the stdlib + && !Feature.shouldBehaveAsScala2 // We do not add the specilized FunctionN methods/classes when compiling the stdlib @tu lazy val Function0SpecializedApplyNames: List[TermName] = for r <- Function0SpecializedReturnTypes @@ -2007,7 +2018,9 @@ class Definitions { /** A allowlist of Scala-2 classes that are known to be pure */ def isAssuredNoInits(sym: Symbol): Boolean = - (sym `eq` SomeClass) || isTupleClass(sym) + (sym `eq` SomeClass) + || isTupleClass(sym) + || sym.is(Module) && isAssuredNoInits(sym.companionClass) /** If `cls` is Tuple1..Tuple22, add the corresponding *: type as last parent to `parents` */ def adjustForTuple(cls: ClassSymbol, tparams: List[TypeSymbol], parents: List[Type]): List[Type] = { @@ -2044,10 +2057,6 @@ class Definitions { def hasProblematicGetClass(className: Name): Boolean = HasProblematicGetClass.contains(className) - /** Is synthesized symbol with alphanumeric name allowed to be used as an infix operator? */ - def isInfix(sym: Symbol)(using Context): Boolean = - (sym eq Object_eq) || (sym eq Object_ne) - @tu lazy val assumedTransparentNames: Map[Name, Set[Symbol]] = // add these for now, until we had a chance to retrofit 2.13 stdlib // we should do a more through sweep through it then. @@ -2104,7 +2113,7 @@ class Definitions { Caps_Capability, // TODO: Remove when Capability is stabilized RequiresCapabilityAnnot, captureRoot, Caps_CapSet, Caps_ContainsTrait, Caps_ContainsModule, Caps_ContainsModule.moduleClass, UseAnnot, - Caps_Mutable, Caps_SharedCapability, ConsumeAnnot, + Caps_Mutable, Caps_Sharable, Caps_Control, Caps_Classifier, ConsumeAnnot, CapsUnsafeModule, CapsUnsafeModule.moduleClass, CapsInternalModule, CapsInternalModule.moduleClass, RetainsAnnot, RetainsCapAnnot, RetainsByNameAnnot) diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 614a8b691c92..ee832cac7269 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -1310,9 +1310,9 @@ object Denotations { } /** The current denotation of the static reference given by path, - * or a MissingRef or NoQualifyingRef instance, if it does not exist. - * if generateStubs is set, generates stubs for missing top-level symbols - */ + * or a MissingRef or NoQualifyingRef instance, if it does not exist. + * if generateStubs is set, generates stubs for missing top-level symbols + */ def staticRef(path: Name, generateStubs: Boolean = true, isPackage: Boolean = false)(using Context): Denotation = { def select(prefix: Denotation, selector: Name): Denotation = { val owner = prefix.disambiguate(_.info.isParameterless) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 57bf870c6b64..8d69917c8189 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -252,7 +252,7 @@ object Flags { /** A field generated for a primary constructor parameter (no matter if it's a 'val' or not), * or an accessor of such a field. */ - val (_, ParamAccessor @ _, _) = newFlags(14, "") + val (ParamAccessorOrInto @ _, ParamAccessor @ _, Into @ _) = newFlags(14, "", "into") /** A value or class implementing a module */ val (Module @ _, ModuleVal @ _, ModuleClass @ _) = newFlags(15, "module") @@ -433,8 +433,12 @@ object Flags { /** A denotation that is valid in all run-ids */ val (Permanent @ _, _, _) = newFlags(61, "") - /** Symbol is a constructor proxy (either companion, or apply method) */ - val (ConstructorProxy @ _, _, _) = newFlags(62, "") // (could be merged with Lifted) + /** A phantom symbol made up by the compiler to achieve special typing rules. + * Phantom symbols cannot be used as regular values, and will be erased after erasure phase. + * - For constructor proxies (companion or apply method). + * - For dummy capture parameters in capture sets (variables or fields). + */ + val (PhantomSymbol @ _, _, _) = newFlags(62, "") // (could be merged with Lifted) // --------- Combined Flag Sets and Conjunctions ---------------------- @@ -452,7 +456,7 @@ object Flags { commonFlags(Private, Protected, Final, Case, Implicit, Given, Override, JavaStatic, Transparent, Erased) val TypeSourceModifierFlags: FlagSet = - CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open + CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open | Into val TermSourceModifierFlags: FlagSet = CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Tracked @@ -467,11 +471,11 @@ object Flags { * TODO: Should check that FromStartFlags do not change in completion */ val FromStartFlags: FlagSet = commonFlags( - Module, Package, Deferred, Method, Case, Enum, Param, ParamAccessor, + Module, Package, Deferred, Method, Case, Enum, Param, ParamAccessorOrInto, Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic, OuterOrCovariant, LabelOrContravariant, CaseAccessor, Tracked, Extension, NonMember, Implicit, Given, Permanent, Synthetic, Exported, - SuperParamAliasOrScala2x, Inline, Macro, ConstructorProxy, Invisible) + SuperParamAliasOrScala2x, Inline, Macro, PhantomSymbol, Invisible) /** Flags that are not (re)set when completing the denotation, or, if symbol is * a top-level class or object, when completing the denotation once the class @@ -525,18 +529,18 @@ object Flags { val RetainedModuleValAndClassFlags: FlagSet = AccessFlags | Package | Case | Synthetic | JavaDefined | JavaStatic | Artifact | - Lifted | MixedIn | Specialized | ConstructorProxy | Invisible | Erased + Lifted | MixedIn | Specialized | PhantomSymbol | Invisible /** Flags that can apply to a module val */ val RetainedModuleValFlags: FlagSet = RetainedModuleValAndClassFlags | - Override | Final | Method | Implicit | Given | Lazy | + Override | Final | Method | Implicit | Given | Lazy | Erased | Accessor | AbsOverride | StableRealizable | Captured | Synchronized | Transparent /** Flags that can apply to a module class */ val RetainedModuleClassFlags: FlagSet = RetainedModuleValAndClassFlags | Enum /** Flags retained in term export forwarders */ - val RetainedExportTermFlags = Infix | Given | Implicit | Inline | Transparent | Erased | HasDefaultParams | NoDefaultParams | ExtensionMethod + val RetainedExportTermFlags = Infix | Given | Implicit | Inline | Transparent | HasDefaultParams | NoDefaultParams | ExtensionMethod /** Flags retained in parameters of term export forwarders */ val RetainedExportTermParamFlags = Given | Implicit | Erased | HasDefault | Inline @@ -565,8 +569,8 @@ object Flags { val EnumCase: FlagSet = Case | Enum val CovariantLocal: FlagSet = Covariant | Local // A covariant type parameter val ContravariantLocal: FlagSet = Contravariant | Local // A contravariant type parameter - val EffectivelyErased = ConstructorProxy | Erased - val ConstructorProxyModule: FlagSet = ConstructorProxy | Module + val ConstructorProxyModule: FlagSet = PhantomSymbol | Module + val CaptureParam: FlagSet = PhantomSymbol | StableRealizable | Synthetic val DefaultParameter: FlagSet = HasDefault | Param // A Scala 2x default parameter val DeferredInline: FlagSet = Deferred | Inline val DeferredMethod: FlagSet = Deferred | Method @@ -579,7 +583,7 @@ object Flags { val FinalOrInline: FlagSet = Final | Inline val FinalOrModuleClass: FlagSet = Final | ModuleClass // A module class or a final class val EffectivelyFinalFlags: FlagSet = Final | Private - val ExcludedForwarder: Flags.FlagSet = Specialized | Lifted | Protected | JavaStatic | Private | Macro | ConstructorProxy + val ExcludedForwarder: Flags.FlagSet = Specialized | Lifted | Protected | JavaStatic | Private | Macro | PhantomSymbol val FinalOrSealed: FlagSet = Final | Sealed val GivenOrImplicit: FlagSet = Given | Implicit val GivenOrImplicitVal: FlagSet = GivenOrImplicit.toTermFlags diff --git a/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala b/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala index b6b316ac14d9..c4c41a460a70 100644 --- a/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala +++ b/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala @@ -19,8 +19,6 @@ object MacroClassLoader { ctx.setProperty(MacroClassLoaderKey, makeMacroClassLoader(using ctx)) private def makeMacroClassLoader(using Context): ClassLoader = trace("new macro class loader") { - import scala.language.unsafeNulls - val entries = ClassPath.expandPath(ctx.settings.classpath.value, expandStar=true) val urls = entries.map(cp => java.nio.file.Paths.get(cp).toUri.toURL).toArray val out = Option(ctx.settings.outputDir.value.toURL) // to find classes in case of suspended compilation diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 6fd76e37977d..571a786e9106 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -91,6 +91,14 @@ object Mode { */ val ImplicitExploration: Mode = newMode(12, "ImplicitExploration") + /** We are currently inside a capture set. + * A term name could be a capture variable, so we need to + * check that it is valid to use as type name. + * Since this mode is only used during annotation typing, + * we can reuse the value of `ImplicitExploration` to save bits. + */ + val InCaptureSet: Mode = ImplicitExploration + /** We are currently unpickling Scala2 info */ val Scala2Unpickling: Mode = newMode(13, "Scala2Unpickling") diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index e9575c7d6c4a..ff41eeb81ca0 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -312,6 +312,7 @@ object NameKinds { /** Other unique names */ val CanThrowEvidenceName: UniqueNameKind = new UniqueNameKind("canThrow$") + val TryOwnerName: UniqueNameKind = new UniqueNameKind("try$") val TempResultName: UniqueNameKind = new UniqueNameKind("ev$") val DepParamName: UniqueNameKind = new UniqueNameKind("(param)") val LazyImplicitName: UniqueNameKind = new UniqueNameKind("$_lazy_implicit_$") diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index fe3ac68f08c5..766cf4abf8c4 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -15,7 +15,7 @@ import nme.* object NameOps { object compactify { - lazy val md5: MessageDigest = MessageDigest.getInstance("MD5").nn + lazy val md5: MessageDigest = MessageDigest.getInstance("MD5") inline val CLASSFILE_NAME_CHAR_LIMIT = 240 @@ -43,9 +43,9 @@ object NameOps { val suffix = s.takeRight(edge) val cs = s.toArray - val bytes = Codec.toUTF8(CharBuffer.wrap(cs).nn) + val bytes = Codec.toUTF8(CharBuffer.wrap(cs)) md5.update(bytes) - val md5chars = md5.digest().nn.map(b => (b & 0xFF).toHexString).mkString + val md5chars = md5.digest().map(b => (b & 0xFF).toHexString).mkString prefix + marker + md5chars + marker + suffix } diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index dbdb46aba334..b44942d4e7ff 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -8,10 +8,31 @@ import ContextOps.enter import TypeApplications.EtaExpansion import collection.mutable import config.Printers.typr +import rewrites.Rewrites.patch +import util.Spans.Span /** Operations that are shared between Namer and TreeUnpickler */ object NamerOps: + /** A completer supporting cleanup actions. + * Needed to break the loop between completion of class and companion object. + * If we try to complete the class first, and completion needs the companion + * object (for instance for processing an import) then the companion object + * completion would consult the companion class info for constructor that + * need a constructor proxy in the object. This can lead to a cyclic reference. + * We break the cycle by delaying adding constructor proxies to be a cleanuo + * action instead. + */ + trait CompleterWithCleanup extends LazyType: + private var cleanupActions: List[() => Unit] = Nil + def addCleanupAction(op: () => Unit): Unit = + cleanupActions = op :: cleanupActions + def cleanup(): Unit = + if cleanupActions.nonEmpty then + cleanupActions.reverse.foreach(_()) + cleanupActions = Nil + end CompleterWithCleanup + /** The type of the constructed instance is returned * * @param ctor the constructor @@ -39,14 +60,12 @@ 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) + val rinfo1 = rinfo.widenSkolems refinements(rname) = refinements.get(rname) match case Some(tp) => tp & rinfo1 case None => rinfo1 @@ -61,12 +80,19 @@ object NamerOps: * This is done by adding a () in front of a leading old style implicit parameter, * or by adding a () as last -- or only -- parameter list if the constructor has * only using clauses as parameters. + * + * implicitRewritePosition, if included, will point to where `()` should be added if rewriting + * with -Yimplicit-to-given */ - def normalizeIfConstructor(paramss: List[List[Symbol]], isConstructor: Boolean)(using Context): List[List[Symbol]] = + def normalizeIfConstructor(paramss: List[List[Symbol]], isConstructor: Boolean, implicitRewritePosition: Option[Span] = None)(using Context): List[List[Symbol]] = if !isConstructor then paramss else paramss match - case TypeSymbols(tparams) :: paramss1 => tparams :: normalizeIfConstructor(paramss1, isConstructor) - case TermSymbols(vparam :: _) :: _ if vparam.is(Implicit) => Nil :: paramss + case TypeSymbols(tparams) :: paramss1 => tparams :: normalizeIfConstructor(paramss1, isConstructor, implicitRewritePosition) + case TermSymbols(vparam :: _) :: _ if vparam.is(Implicit) => + implicitRewritePosition match + case Some(position) if ctx.settings.YimplicitToGiven.value => patch(position, "()") + case _ => () + Nil :: paramss case _ => if paramss.forall { case TermSymbols(vparams) => vparams.nonEmpty && vparams.head.is(Given) @@ -116,10 +142,10 @@ object NamerOps: private val NoConstructorProxyNeededFlags = Abstract | Trait | Case | Synthetic | Module | Invisible /** The flags of a constructor companion */ - private val ConstructorCompanionFlags = Synthetic | ConstructorProxy + private val ConstructorCompanionFlags = Synthetic | PhantomSymbol /** The flags of an `apply` method that serves as a constructor proxy */ - val ApplyProxyFlags = Synthetic | ConstructorProxy | Inline | Method + val ApplyProxyFlags = Synthetic | PhantomSymbol | Inline | Method /** If this is a reference to a class and the reference has a stable prefix, the reference * otherwise NoType @@ -157,8 +183,14 @@ object NamerOps: ApplyProxyCompleter(constr), cls.privateWithin, constr.coord) - for dcl <- cls.info.decls do + def doAdd() = for dcl <- cls.info.decls do if dcl.isConstructor then scope.enter(proxy(dcl)) + cls.infoOrCompleter match + case completer: CompleterWithCleanup if cls.is(Touched) => + // Taking the info would lead to a cyclic reference here - delay instead until cleanup of `cls` + completer.addCleanupAction(doAdd) + case _ => + doAdd() scope end addConstructorApplies @@ -219,7 +251,7 @@ object NamerOps: underlyingStableClassRef(mbr.info.loBound): @unchecked match case ref: TypeRef => val proxy = ref.symbol.registeredCompanion - if proxy.is(ConstructorProxy) && !memberExists(cls, mbr.name.toTermName) then + if proxy.is(PhantomSymbol) && !memberExists(cls, mbr.name.toTermName) then typeConstructorCompanion(mbr, ref.prefix, proxy).entered if cls.is(Module) @@ -317,4 +349,25 @@ object NamerOps: ann.tree match case ast.tpd.WitnessNamesAnnot(witnessNames) => addContextBoundCompanionFor(sym, witnessNames, Nil) + + /** Add a dummy term symbol for a type def that has capture parameter flag. + * The dummy symbol has the same name as the original type symbol and is stable. + * The underlying info stores the corresponding type reference. + * + * @param param the original type symbol of the capture parameter + */ + def addDummyTermCaptureParam(param: Symbol)(using Context): Unit = + val name = param.name.toTermName + val flags = (param.flagsUNSAFE & AccessFlags).toTermFlags | CaptureParam + val dummy = newSymbol(param.owner, name, flags, param.typeRef) + typr.println(i"Adding dummy term symbol $dummy for $param, flags = $flags") + ctx.enter(dummy) + + /** if `sym` is a term parameter or parameter accessor, map all occurrences of + * `into[T]` in its type to `T @$into`. + */ + extension (tp: Type) + def suppressIntoIfParam(sym: Symbol)(using Context): Type = + if sym.isOneOf(TermParamOrAccessor) then TypeOps.suppressInto(tp) else tp + end NamerOps diff --git a/compiler/src/dotty/tools/dotc/core/Names.scala b/compiler/src/dotty/tools/dotc/core/Names.scala index a31ab0662ee4..25deb3a56889 100644 --- a/compiler/src/dotty/tools/dotc/core/Names.scala +++ b/compiler/src/dotty/tools/dotc/core/Names.scala @@ -393,7 +393,7 @@ object Names { // because asserts are caught in exception handlers which might // cause other failures. In that case the first, important failure // is lost. - System.err.nn.println("Backend should not call Name#toString, Name#mangledString should be used instead.") + System.err.println("Backend should not call Name#toString, Name#mangledString should be used instead.") Thread.dumpStack() assert(false) } @@ -404,8 +404,8 @@ object Names { * 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]] - !trace.exists(_.getClassName.nn.endsWith("GenBCode")) || + val trace: Array[StackTraceElement] = Thread.currentThread.getStackTrace.asInstanceOf[Array[StackTraceElement]] + !trace.exists(_.getClassName.endsWith("GenBCode")) || trace.exists(elem => List( "mangledString", diff --git a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala index 150c39aa8e13..0154c70e7949 100644 --- a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -73,7 +73,7 @@ object OrderingConstraint { } else { val prev_es = entries(prev, poly) - if (prev_es == null || (es.nn ne prev_es.nn)) + if (prev_es == null || (es.nn ne prev_es)) current // can re-use existing entries array. else { es = es.nn.clone diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index e3351628e43e..ee9ee4006919 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -314,7 +314,7 @@ object Phases { * instance, it is possible to print trees after a given phase using: * * ```bash - * $ ./bin/scalac -Xprint: sourceFile.scala + * $ ./bin/scalac -Vprint: sourceFile.scala * ``` */ def phaseName: String @@ -505,15 +505,14 @@ object Phases { * Enrich crash messages. */ final def monitor(doing: String)(body: Context ?=> Unit)(using Context): Boolean = - val unit = ctx.compilationUnit - if ctx.run.enterUnit(unit) then + ctx.run.enterUnit(ctx.compilationUnit) + && { try {body; true} catch case NonFatal(ex) if !ctx.run.enrichedErrorMessage => - report.echo(ctx.run.enrichErrorMessage(s"exception occurred while $doing $unit")) + report.echo(ctx.run.enrichErrorMessage(s"exception occurred while $doing ${ctx.compilationUnit}")) throw ex finally ctx.run.advanceUnit() - else - false + } inline def runSubPhase[T](id: Run.SubPhase)(inline body: (Run.SubPhase, Context) ?=> T)(using Context): T = given Run.SubPhase = id diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index c33c795571e6..9271961d02dd 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -41,8 +41,8 @@ object StdNames { inline val Product = "Product" @sharable - private val disallowed = java.util.regex.Pattern.compile("""[<>]""").nn - def sanitize(str: String): String = disallowed.matcher(str).nn.replaceAll("""\$""").nn + private val disallowed = java.util.regex.Pattern.compile("""[<>]""") + def sanitize(str: String): String = disallowed.matcher(str).replaceAll("""\$""") } abstract class DefinedNames[N <: Name] { @@ -120,8 +120,6 @@ object StdNames { val BITMAP_TRANSIENT: N = s"${BITMAP_PREFIX}trans$$" // initialization bitmap for transient lazy vals val BITMAP_CHECKINIT: N = s"${BITMAP_PREFIX}init$$" // initialization bitmap for checkinit values val BITMAP_CHECKINIT_TRANSIENT: N = s"${BITMAP_PREFIX}inittrans$$" // initialization bitmap for transient checkinit values - val CC_REACH: N = "$reach" - val CC_READONLY: N = "$readOnly" val DEFAULT_GETTER: N = str.DEFAULT_GETTER val DEFAULT_GETTER_INIT: N = "$lessinit$greater" val DO_WHILE_PREFIX: N = "doWhile$" @@ -132,7 +130,6 @@ object StdNames { val EXCEPTION_RESULT_PREFIX: N = "exceptionResult" val EXPAND_SEPARATOR: N = str.EXPAND_SEPARATOR val IMPORT: N = "" - val INTO: N = "$into" val MODULE_SUFFIX: N = str.MODULE_SUFFIX val OPS_PACKAGE: N = "" val OVERLOADED: N = "" @@ -445,7 +442,6 @@ object StdNames { val canEqual_ : N = "canEqual" val canEqualAny : N = "canEqualAny" val caps: N = "caps" - val capsOf: N = "capsOf" val captureChecking: N = "captureChecking" val checkInitialized: N = "checkInitialized" val classOf: N = "classOf" @@ -549,13 +545,12 @@ object StdNames { val ManifestFactory: N = "ManifestFactory" val manifestToTypeTag: N = "manifestToTypeTag" val map: N = "map" - val maybeCapability: N = "maybeCapability" val materializeClassTag: N = "materializeClassTag" val materializeWeakTypeTag: N = "materializeWeakTypeTag" val materializeTypeTag: N = "materializeTypeTag" + val maybeCapability: N = "maybeCapability" val mirror : N = "mirror" val moduleClass : N = "moduleClass" - val mut: N = "mut" val name: N = "name" val nameDollar: N = "$name" val ne: N = "ne" @@ -565,6 +560,7 @@ object StdNames { val next: N = "next" val nmeNewTermName: N = "newTermName" val nmeNewTypeName: N = "newTypeName" + val nn: N = "nn" val noAutoTupling: N = "noAutoTupling" val normalize: N = "normalize" val notifyAll_ : N = "notifyAll" @@ -572,6 +568,7 @@ object StdNames { val null_ : N = "null" val ofDim: N = "ofDim" val on: N = "on" + val only: N = "only" val opaque: N = "opaque" val open: N = "open" val ordinal: N = "ordinal" diff --git a/compiler/src/dotty/tools/dotc/core/Substituters.scala b/compiler/src/dotty/tools/dotc/core/Substituters.scala index 6cd238bb0e19..425b6193f3cd 100644 --- a/compiler/src/dotty/tools/dotc/core/Substituters.scala +++ b/compiler/src/dotty/tools/dotc/core/Substituters.scala @@ -2,6 +2,7 @@ package dotty.tools.dotc package core import Types.*, Symbols.*, Contexts.* +import cc.Capabilities.{Capability, ResultCap} /** Substitution operations on types. See the corresponding `subst` and * `substThis` methods on class Type for an explanation. @@ -163,23 +164,22 @@ object Substituters: } final class SubstBindingMap[BT <: BindingType](val from: BT, val to: BT)(using Context) extends DeepTypeMap, BiTypeMap { + def apply(tp: Type): Type = subst(tp, from, to, this)(using mapCtx) + override def mapCapability(c: Capability, deep: Boolean = false) = c match + case c @ ResultCap(binder: MethodType) if binder eq from => + c.derivedResult(to.asInstanceOf[MethodType]) + case _ => + super.mapCapability(c, deep) + override def fuse(next: BiTypeMap)(using Context) = next match case next: SubstBindingMap[_] => if next.from eq to then Some(SubstBindingMap(from, next.to)) else Some(SubstBindingsMap(Array(from, next.from), Array(to, next.to))) case _ => None - def apply(tp: Type): Type = subst(tp, from, to, this)(using mapCtx) def inverse = SubstBindingMap(to, from) } final class SubstBindingsMap(val from: Array[BindingType], val to: Array[BindingType])(using Context) extends DeepTypeMap, BiTypeMap { - override def fuse(next: BiTypeMap)(using Context) = next match - case next: SubstBindingMap[_] => - var i = 0 - while i < from.length && (to(i) ne next.from) do i += 1 - if i < from.length then Some(SubstBindingsMap(from, to.updated(i, next.to))) - else Some(SubstBindingsMap(from :+ next.from, to :+ next.to)) - case _ => None def apply(tp: Type): Type = tp match case tp: BoundType => @@ -189,6 +189,22 @@ object Substituters: case _ => mapOver(tp) + override def mapCapability(c: Capability, deep: Boolean = false) = c match + case c @ ResultCap(binder: MethodType) => + var i = 0 + while i < from.length && (from(i) ne binder) do i += 1 + if i < from.length then c.derivedResult(to(i).asInstanceOf[MethodType]) else c + case _ => + super.mapCapability(c, deep) + + override def fuse(next: BiTypeMap)(using Context) = next match + case next: SubstBindingMap[_] => + var i = 0 + while i < from.length && (to(i) ne next.from) do i += 1 + if i < from.length then Some(SubstBindingsMap(from, to.updated(i, next.to))) + else Some(SubstBindingsMap(from :+ next.from, to :+ next.to)) + case _ => None + def inverse = SubstBindingsMap(to, from) } diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 6c7b8dcef94a..6700959eee0e 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -252,11 +252,16 @@ object SymDenotations { final def filterAnnotations(p: Annotation => Boolean)(using Context): Unit = annotations = annotations.filterConserve(p) - def annotationsCarrying(meta: Set[Symbol], orNoneOf: Set[Symbol] = Set.empty)(using Context): List[Annotation] = - annotations.filterConserve(_.hasOneOfMetaAnnotation(meta, orNoneOf = orNoneOf)) + def annotationsCarrying(meta: Set[Symbol], + orNoneOf: Set[Symbol] = Set.empty, + andAlso: Set[Symbol] = Set.empty)(using Context): List[Annotation] = + annotations.filterConserve: annot => + annot.hasOneOfMetaAnnotation(meta, orNoneOf = orNoneOf) + || andAlso.contains(annot.symbol) - def keepAnnotationsCarrying(phase: DenotTransformer, meta: Set[Symbol], orNoneOf: Set[Symbol] = Set.empty)(using Context): Unit = - updateAnnotationsAfter(phase, annotationsCarrying(meta, orNoneOf = orNoneOf)) + def keepAnnotationsCarrying(phase: DenotTransformer, meta: Set[Symbol], + orNoneOf: Set[Symbol] = Set.empty, andAlso: Set[Symbol] = Set.empty)(using Context): Unit = + updateAnnotationsAfter(phase, annotationsCarrying(meta, orNoneOf, andAlso)) def updateAnnotationsAfter(phase: DenotTransformer, annots: List[Annotation])(using Context): Unit = if annots ne annotations then @@ -339,8 +344,13 @@ object SymDenotations { def recurWithoutParamss(info: Type): List[List[Symbol]] = info match case info: LambdaType => - val params = info.paramNames.lazyZip(info.paramInfos).map((pname, ptype) => - newSymbol(symbol, pname, SyntheticParam, ptype)) + val commonFlags = + if info.isContextualMethod then Given | SyntheticParam + else if info.isImplicitMethod then Implicit | SyntheticParam + else SyntheticParam + val params = info.paramNames.lazyZip(info.paramInfos).map: (pname, ptype) => + val flags = if ptype.hasAnnotation(defn.ErasedParamAnnot) then commonFlags | Erased else commonFlags + newSymbol(symbol, pname, flags, ptype) val prefs = params.map(_.namedType) for param <- params do param.info = param.info.substParams(info, prefs) @@ -538,7 +548,7 @@ object SymDenotations { // doesn't find them it will invalidate whatever referenced them, so // any reference to a fake companion will lead to extra recompilations. // Instead, use the class name since it's guaranteed to exist at runtime. - val clsFlatName = if isOneOf(JavaDefined | ConstructorProxy) then flatName.stripModuleClassSuffix else flatName + val clsFlatName = if isOneOf(JavaDefined | PhantomSymbol) then flatName.stripModuleClassSuffix else flatName builder.append(clsFlatName.mangledString) builder.toString @@ -685,7 +695,7 @@ object SymDenotations { isAbstractOrAliasType && !isAbstractOrParamType /** Is this symbol an abstract or alias type? */ - final def isAbstractOrAliasType: Boolean = isType & !isClass + final def isAbstractOrAliasType: Boolean = isType && !isClass /** Is this symbol an abstract type or type parameter? */ final def isAbstractOrParamType(using Context): Boolean = this.isOneOf(DeferredOrTypeParam) @@ -786,9 +796,14 @@ object SymDenotations { * * However, a stable member might not yet be initialized (if it is an object or anyhow lazy). * So the first call to a stable member might fail and/or produce side effects. + * + * Note, (f: => T) is treated as a stable TermRef only in Capture Sets. */ final def isStableMember(using Context): Boolean = { - def isUnstableValue = isOneOf(UnstableValueFlags) || info.isInstanceOf[ExprType] || isAllOf(InlineParam) + def isUnstableValue = + isOneOf(UnstableValueFlags) + || !ctx.mode.is(Mode.InCaptureSet) && info.isInstanceOf[ExprType] + || isAllOf(InlineParam) isType || is(StableRealizable) || exists && !isUnstableValue } @@ -850,9 +865,23 @@ object SymDenotations { * and is the denoting symbol also different from `Null` or `Nothing`? * @note erroneous classes are assumed to derive from all other classes * and all classes derive from them. + * @note may return a false negative when `this.info.isInstanceOf[TempClassInfo]`. */ def derivesFrom(base: Symbol)(using Context): Boolean = false + /** Could `this` derive from `base` now or in the future. + * For concistency with derivesFrom, the info is only forced when this is a ClassDenotation. + * If the info is a TempClassInfo then the baseClassSet may be temporarily approximated as empty. + * This is problematic when stability of `!derivesFrom(base)` is assumed for soundness, + * e.g., in `TypeComparer#provablyDisjointClasses`. + * @note may return a false positive when `this.info.isInstanceOf[TempClassInfo]`. + */ + final def mayDeriveFrom(base: Symbol)(using Context): Boolean = + this.isInstanceOf[ClassDenotation] && (info.isInstanceOf[TempClassInfo] || derivesFrom(base)) + + final def derivesFrom(base: Symbol, defaultIfUnknown: Boolean)(using Context): Boolean = + if defaultIfUnknown/*== true*/ then mayDeriveFrom(base) else derivesFrom(base) + /** Is this a Scala or Java annotation ? */ def isAnnotation(using Context): Boolean = isClass && (derivesFrom(defn.AnnotationClass) || is(JavaAnnotation)) @@ -1036,8 +1065,13 @@ object SymDenotations { && owner.ne(defn.StringContextClass) /** An erased value or an erased inline method or field */ + def isErased(using Context): Boolean = + is(Erased) || defn.erasedValueMethods.contains(symbol) + + /** An erased value, a phantom symbol or an erased inline method or field */ def isEffectivelyErased(using Context): Boolean = - isOneOf(EffectivelyErased) + isErased + || is(PhantomSymbol) || is(Inline) && !isRetainedInline && !hasAnnotation(defn.ScalaStaticAnnot) /** Is this a member that will become public in the generated binary */ @@ -1141,7 +1175,7 @@ object SymDenotations { final def ownersIterator(using Context): Iterator[Symbol] = new Iterator[Symbol] { private var current = symbol def hasNext = current.exists - def next: Symbol = { + def next(): Symbol = { val result = current current = current.owner result @@ -1200,7 +1234,7 @@ object SymDenotations { || is(Inline, butNot = Deferred) || is(JavaDefinedVal, butNot = Method) || isConstructor - || !owner.isExtensibleClass && !is(Deferred) + || exists && !owner.isExtensibleClass && !is(Deferred) // Deferred symbols can arise through parent refinements under x.modularity. // For them, the overriding relationship reverses anyway, so // being in a final class does not mean the symbol cannot be @@ -1418,7 +1452,7 @@ object SymDenotations { final def nextOverriddenSymbol(using Context): Symbol = { val overridden = allOverriddenSymbols if (overridden.hasNext) - overridden.next + overridden.next() else NoSymbol } @@ -1496,10 +1530,10 @@ object SymDenotations { val candidates = owner.info.decls.lookupAll(name) def test(sym: Symbol): Symbol = if (sym == symbol || sym.signature == signature) sym - else if (candidates.hasNext) test(candidates.next) + else if (candidates.hasNext) test(candidates.next()) else NoSymbol if (candidates.hasNext) { - val sym = candidates.next + val sym = candidates.next() if (candidates.hasNext) test(sym) else sym } else NoSymbol @@ -2581,7 +2615,7 @@ object SymDenotations { try f.container == chosen.container catch case NonFatal(ex) => true if !ambiguityWarningIssued then for conflicting <- assocFiles.find(!sameContainer(_)) do - report.warning(em"""${ambiguousFilesMsg(conflicting.nn)} + report.warning(em"""${ambiguousFilesMsg(conflicting)} |Keeping only the definition in $chosen""") ambiguityWarningIssued = true multi.filterWithPredicate(_.symbol.associatedFile == chosen) @@ -2705,7 +2739,7 @@ object SymDenotations { || owner.isRefinementClass || owner.is(Scala2x) || owner.unforcedDecls.contains(denot.name, denot.symbol) - || (denot.is(Synthetic) && denot.is(ModuleClass) && stillValidInOwner(denot.companionClass)) + || (denot.is(Synthetic) && denot.is(ModuleClass) && denot.companionClass.exists && stillValidInOwner(denot.companionClass)) || denot.isSelfSym || denot.isLocalDummy) catch case ex: StaleSymbol => false @@ -2920,15 +2954,14 @@ object SymDenotations { private var checkedPeriod: Period = Nowhere protected def invalidateDependents() = { - import scala.language.unsafeNulls if (dependent != null) { - val it = dependent.keySet.iterator() + val it = dependent.nn.keySet.iterator() while (it.hasNext()) it.next().invalidate() } dependent = null } - protected def addDependent(dep: InheritedCache) = { + protected def addDependent(dep: InheritedCache): Unit = { if (dependent == null) dependent = new WeakHashMap dependent.nn.put(dep, ()) } diff --git a/compiler/src/dotty/tools/dotc/core/SymUtils.scala b/compiler/src/dotty/tools/dotc/core/SymUtils.scala index 1b83014e5735..f22002495bb3 100644 --- a/compiler/src/dotty/tools/dotc/core/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/SymUtils.scala @@ -90,6 +90,9 @@ class SymUtils: def isContextBoundCompanion(using Context): Boolean = self.is(Synthetic) && self.infoOrCompleter.typeSymbol == defn.CBCompanion + def isDummyCaptureParam(using Context): Boolean = + self.is(PhantomSymbol) && self.infoOrCompleter.typeSymbol != defn.CBCompanion + /** Is this a case class for which a product mirror is generated? * Excluded are value classes, abstract classes and case classes with more than one * parameter section. @@ -118,6 +121,16 @@ class SymUtils: def isGenericProduct(using Context): Boolean = whyNotGenericProduct.isEmpty + def sanitizedDescription(using Context): String = + if self.isConstructor then + i"constructor of ${self.owner.sanitizedDescription}" + else if self.isAnonymousFunction then + i"anonymous function of type ${self.info}" + else if self.name.toString.contains('$') then + self.owner.sanitizedDescription + else + self.show + /** Is this an old style implicit conversion? * @param directOnly only consider explicitly written methods * @param forImplicitClassOnly only consider methods generated from implicit classes @@ -359,7 +372,6 @@ class SymUtils: /** Is symbol assumed or declared as an infix symbol? */ def isDeclaredInfix(using Context): Boolean = self.is(Infix) - || defn.isInfix(self) || self.name.isUnapplyName && self.owner.is(Module) && self.owner.linkedClass.is(Case) diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index 6a9d70a19f49..7b7eb2d2e5a9 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -158,7 +158,7 @@ object SymbolLoaders { if (!ok) report.warning(i"""$what ${tree.name} is in the wrong directory. |It was declared to be in package ${path.reverse.mkString(".")} - |But it is found in directory ${filePath.reverse.mkString(File.separator.nn)}""", + |But it is found in directory ${filePath.reverse.mkString(File.separator)}""", tree.srcPos.focus) ok } diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index c8ede8bfdec2..d86c50cbe2e3 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -84,8 +84,8 @@ object Symbols extends SymUtils { ctx.settings.YretainTrees.value || denot.owner.isTerm || // no risk of leaking memory after a run for these denot.isOneOf(InlineOrProxy) || // need to keep inline info - ctx.settings.Whas.checkInit || // initialization check - ctx.settings.YcheckInitGlobal.value + ctx.settings.Whas.safeInit || // initialization check + ctx.settings.YsafeInitGlobal.value /** The last denotation of this symbol */ private var lastDenot: SymDenotation = uninitialized diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index b8a8be57ca05..d420590d0c40 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -23,8 +23,10 @@ import typer.Applications.productSelectorTypes import reporting.trace import annotation.constructorOnly import cc.* +import Capabilities.Capability import NameKinds.WildcardParamName import MatchTypes.isConcrete +import scala.util.boundary, boundary.break /** Provides methods to compare types. */ @@ -49,12 +51,20 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling opaquesUsed = false recCount = 0 needsGc = false + maxErrorLevel = -1 + errorNotes = Nil + undoLog.clear() if Config.checkTypeComparerReset then checkReset() private var pendingSubTypes: util.MutableSet[(Type, Type)] | Null = null private var recCount = 0 private var monitored = false + private var maxErrorLevel: Int = -1 + protected var errorNotes: List[(Int, ErrorNote)] = Nil + + val undoLog = mutable.ArrayBuffer[() => Unit]() + private var needsGc = false private var canCompareAtoms: Boolean = true // used for internal consistency checking @@ -147,7 +157,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def testSubType(tp1: Type, tp2: Type): CompareResult = GADTused = false opaquesUsed = false - if !topLevelSubType(tp1, tp2) then CompareResult.Fail + if !topLevelSubType(tp1, tp2) then CompareResult.Fail(Nil) else if GADTused then CompareResult.OKwithGADTUsed else if opaquesUsed then CompareResult.OKwithOpaquesUsed // we cast on GADTused, so handles if both are used else CompareResult.OK @@ -400,7 +410,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling compareErasedValueType case ConstantType(v2) => tp1 match { - case ConstantType(v1) => v1.value == v2.value && recur(v1.tpe, v2.tpe) + case ConstantType(v1) => v1 == v2 && recur(v1.tpe, v2.tpe) case _ => secondTry } case tp2: AnyConstantType => @@ -427,7 +437,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case _ => if isCaptureVarComparison then return CCState.withCapAsRoot: - subCaptures(tp1.captureSet, tp2.captureSet).isOK + subCaptures(tp1.captureSet, tp2.captureSet) if (tp1 eq NothingType) || isBottom(tp1) then return true } @@ -535,8 +545,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case tp1 @ CapturingType(parent1, refs1) => def compareCapturing = if tp2.isAny then true - else if subCaptures(refs1, tp2.captureSet).isOK && sameBoxed(tp1, tp2, refs1) + else if compareCaptures(tp1, refs1, tp2, tp2.captureSet) || !ctx.mode.is(Mode.CheckBoundsOrSelfType) && tp1.isAlwaysPure + || parent1.isSingleton && refs1.elems.forall(parent1 eq _) then val tp2a = if tp1.isBoxedCapturing && !parent1.isBoxedCapturing @@ -577,7 +588,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if isCaptureVarComparison then return CCState.withCapAsRoot: - subCaptures(tp1.captureSet, tp2.captureSet).isOK + subCaptures(tp1.captureSet, tp2.captureSet) isSubApproxHi(tp1, info2.lo) && (trustBounds || isSubApproxHi(tp1, info2.hi)) || compareGADT @@ -662,12 +673,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling && isSubInfo(info1.resultType, info2.resultType.subst(info2, info1)) case (info1 @ CapturingType(parent1, refs1), info2: Type) if info2.stripCapturing.isInstanceOf[MethodOrPoly] => - subCaptures(refs1, info2.captureSet).isOK && sameBoxed(info1, info2, refs1) + compareCaptures(info1, refs1, info2, info2.captureSet) && isSubInfo(parent1, info2) case (info1: Type, CapturingType(parent2, refs2)) if info1.stripCapturing.isInstanceOf[MethodOrPoly] => val refs1 = info1.captureSet - (refs1.isAlwaysEmpty || subCaptures(refs1, refs2).isOK) && sameBoxed(info1, info2, refs1) + (refs1.isAlwaysEmpty || compareCaptures(info1, refs1, info2, refs2)) && isSubInfo(info1, parent2) case _ => isSubType(info1, info2) @@ -855,19 +866,18 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling else // The singletonOK branch is because we sometimes have a larger capture set in a singleton // than in its underlying type. An example is `f: () -> () ->{x} T`, which might be - // the type of a closure. In that case the capture set of `f.type` is `{x}` but the - // capture set of the underlying type is `{}`. So without the `singletonOK` test, a singleton - // might not be a subtype of its underlying type. Examples where this arises is - // capt-capibility.scala and function-combinators.scala + // the type of a closure (in one of the variants we are considering). In that case the + // capture set of `f.type` is `{x}` but the capture set of the underlying type is `{}`. + // So without the `singletonOK` test, a singleton might not be a subtype of its underlying type. + // Eamples where this arises is capt-capibility.scala and function-combinators.scala val singletonOK = tp1 match case tp1: SingletonType - if subCaptures(tp1.underlying.captureSet, refs2, CaptureSet.VarState.Separate).isOK => + if subCaptures(tp1.underlying.captureSet, refs2, CaptureSet.VarState.Separate) => recur(tp1.widen, tp2) case _ => false singletonOK - || subCaptures(refs1, refs2).isOK - && sameBoxed(tp1, tp2, refs1) + || compareCaptures(tp1, refs1, tp2, refs2) && (recur(tp1.widen.stripCapturing, parent2) || tp1.isInstanceOf[SingletonType] && recur(tp1, parent2) // this alternative is needed in case the right hand side is a @@ -1003,7 +1013,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if isCaptureCheckingOrSetup then tp1 .match - case tp1: CaptureRef if tp1.isTracked => + case tp1: Capability if isCaptureCheckingOrSetup && tp1.isTracked => CapturingType(tp1w.stripCapturing, tp1.singletonCaptureSet) case _ => tp1w @@ -1012,6 +1022,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling comparePaths || isSubType(tp1widened, tp2, approx.addLow) case tp1: RefinedType => + if isCaptureCheckingOrSetup then + tp2.stripCapturing match + case defn.RefinedFunctionOf(_) => // was already handled in thirdTry + return false + case _ => isNewSubType(tp1.parent) case tp1: RecType => isNewSubType(tp1.parent) @@ -1567,21 +1582,32 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling && tp1.derivesFrom(defn.Caps_CapSet) && tp2.derivesFrom(defn.Caps_CapSet) + def rollBack(prevSize: Int): Unit = + var i = prevSize + while i < undoLog.size do + undoLog(i)() + i += 1 + undoLog.takeInPlace(prevSize) + assert(undoLog.size == prevSize) + // begin recur if tp2 eq NoType then false else if tp1 eq tp2 then true else val savedCstr = constraint val savedGadt = ctx.gadt + val savedLogSize = undoLog.size inline def restore() = state.constraint = savedCstr ctx.gadtState.restore(savedGadt) + if undoLog.size != savedLogSize then + //println(i"ROLLBACK $tp1 <:< $tp2") + rollBack(savedLogSize) val savedSuccessCount = successCount try - recCount += 1 - if recCount >= Config.LogPendingSubTypesThreshold then monitored = true - val result = if monitored then monitoredIsSubType else firstTry - recCount -= 1 + val result = inNestedLevel: + if recCount >= Config.LogPendingSubTypesThreshold then monitored = true + if monitored then monitoredIsSubType else firstTry if !result then restore() else if recCount == 0 && needsGc then state.gc() @@ -1596,6 +1622,32 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling throw ex } + /** Run `op` in a recursion level (indicated by `recCount`) increased by one. + * This affects when monitoring starts and how error notes are propagated. + * On exit, error notes added at the current level are either + * - promoted to the next outer level (in case of failure), + * - cancelled (in case of success). + */ + inline def inNestedLevel(inline op: Boolean): Boolean = + recCount += 1 + val result = op + recCount -= 1 + if maxErrorLevel > recCount then + if result then + maxErrorLevel = -1 + errorNotes = errorNotes.filterConserve: p => + val (level, note) = p + if level <= recCount then + if level > maxErrorLevel then maxErrorLevel = level + true + else false + else + errorNotes = errorNotes.mapConserve: p => + val (level, note) = p + if level > recCount then (recCount, note) else p + maxErrorLevel = recCount + result + private def nonExprBaseType(tp: Type, cls: Symbol)(using Context): Type = if tp.isInstanceOf[ExprType] then NoType else tp.baseType(cls) @@ -2054,6 +2106,45 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling else op2 end necessaryEither + /** Finds the necessary (the weakest) GADT constraint among a list of them. + * It returns the one being subsumed by all others if exists, and `None` otherwise. + * + * This is used when typechecking pattern alternatives, for instance: + * + * enum Expr[+T]: + * case I1(x: Int) extends Expr[Int] + * case I2(x: Int) extends Expr[Int] + * case B(x: Boolean) extends Expr[Boolean] + * import Expr.* + * + * The following function should compile: + * + * def foo[T](e: Expr[T]): T = e match + * case I1(_) | I2(_) => 42 + * + * since `T >: Int` is subsumed by both alternatives in the first match clause. + * + * However, the following should not: + * + * def foo[T](e: Expr[T]): T = e match + * case I1(_) | B(_) => 42 + * + * since the `I1(_)` case gives the constraint `T >: Int` while `B(_)` gives `T >: Boolean`. + * Neither of the constraints is subsumed by the other. + */ + def necessaryGadtConstraint(constrs: List[GadtConstraint], preGadt: GadtConstraint)(using Context): Option[GadtConstraint] = boundary: + constrs match + case Nil => break(None) + case c0 :: constrs => + var weakest = c0 + for c <- constrs do + if subsumes(weakest.constraint, c.constraint, preGadt.constraint) then + weakest = c + else if !subsumes(c.constraint, weakest.constraint, preGadt.constraint) then + // this two constraints are disjoint + break(None) + break(Some(weakest)) + inline def rollbackConstraintsUnless(inline op: Boolean): Boolean = val saved = constraint var result = false @@ -2297,7 +2388,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling formals2.isEmpty } // If methods have erased parameters, then the erased parameters must match - val erasedValid = (!tp1.hasErasedParams && !tp2.hasErasedParams) || (tp1.erasedParams == tp2.erasedParams) + val erasedValid = (!tp1.hasErasedParams && !tp2.hasErasedParams) || (tp1.paramErasureStatuses == tp2.paramErasureStatuses) erasedValid && loop(tp1.paramInfos, tp2.paramInfos) } @@ -2794,20 +2885,37 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if frozenConstraint then CaptureSet.VarState.Closed() else CaptureSet.VarState() protected def subCaptures(refs1: CaptureSet, refs2: CaptureSet, - vs: CaptureSet.VarState = makeVarState())(using Context): CaptureSet.CompareResult = + vs: CaptureSet.VarState = makeVarState())(using Context): Boolean = try refs1.subCaptures(refs2, vs) catch case ex: AssertionError => println(i"fail while subCaptures $refs1 <:< $refs2") throw ex - /** Is the boxing status of tp1 and tp2 the same, or alternatively, is - * the capture sets `refs1` of `tp1` a subcapture of the empty set? - * In the latter case, boxing status does not matter. + /** + * - Compare capture sets using subCaptures. If the lower type derives from Mutable and the + * upper type does not, make the lower set read-only. + * - Test whether the boxing status of tp1 and tp2 the same, or alternatively, + * whether the capture set `refs1` of `tp1` is subcapture of the empty set? + * In the latter case, boxing status does not matter. */ - protected def sameBoxed(tp1: Type, tp2: Type, refs1: CaptureSet)(using Context): Boolean = - (tp1.isBoxedCapturing == tp2.isBoxedCapturing) - || refs1.subCaptures(CaptureSet.empty, makeVarState()).isOK + protected def compareCaptures(tp1: Type, refs1: CaptureSet, tp2: Type, refs2: CaptureSet): Boolean = + val refs1Adapted = + if tp1.derivesFromMutable && !tp2.derivesFromMutable + then refs1.readOnly + else refs1 + val subc = subCaptures(refs1Adapted, refs2) + if !subc then + errorNotes match + case (level, CaptureSet.MutAdaptFailure(cs, NoType, NoType)) :: rest => + errorNotes = (level, CaptureSet.MutAdaptFailure(cs, tp1, tp2)) :: rest + case _ => + subc + && (tp1.isBoxedCapturing == tp2.isBoxedCapturing) + || refs1.subCaptures(CaptureSet.empty, makeVarState()) + + protected def logUndoAction(action: () => Unit) = + undoLog += action // ----------- Diagnostics -------------------------------------------------- @@ -3023,9 +3131,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling * unique value derives from the class. */ case (tp1: SingletonType, tp2) => - !tp1.derivesFrom(tp2.classSymbol) + !tp1.derivesFrom(tp2.classSymbol, defaultIfUnknown = true) case (tp1, tp2: SingletonType) => - !tp2.derivesFrom(tp1.classSymbol) + !tp2.derivesFrom(tp1.classSymbol, defaultIfUnknown = true) /* Now both sides are possibly-parameterized class types `p.C[Ts]` and `q.D[Us]`. * @@ -3081,7 +3189,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling val cls2BaseClassSet = SymDenotations.BaseClassSet(cls2.classDenot.baseClasses) val commonBaseClasses = cls1.classDenot.baseClasses.filter(cls2BaseClassSet.contains(_)) def isAncestorOfOtherBaseClass(cls: ClassSymbol): Boolean = - commonBaseClasses.exists(other => (other ne cls) && other.derivesFrom(cls)) + commonBaseClasses.exists(other => (other ne cls) && other.mayDeriveFrom(cls)) val result = commonBaseClasses.exists { baseClass => !isAncestorOfOtherBaseClass(baseClass) && isBaseTypeWithDisjointArguments(baseClass, innerPending) } @@ -3122,7 +3230,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling .filter(child => child.exists && child != cls) def eitherDerivesFromOther(cls1: Symbol, cls2: Symbol): Boolean = - cls1.derivesFrom(cls2) || cls2.derivesFrom(cls1) + cls1.mayDeriveFrom(cls2) || cls2.mayDeriveFrom(cls1) def smallestNonTraitBase(cls: Symbol): Symbol = val classes = if cls.isClass then cls.asClass.baseClasses else cls.info.classSymbols @@ -3213,12 +3321,50 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def reduceMatchWith[T](op: MatchReducer => T)(using Context): T = inSubComparer(matchReducer)(op) + + /** Add given ErrorNote note, provided there is not yet an error note with + * the same class as `note`. + */ + def addErrorNote(note: ErrorNote): Unit = + if errorNotes.forall(_._2.kind != note.kind) then + errorNotes = (recCount, note) :: errorNotes + assert(maxErrorLevel <= recCount) + maxErrorLevel = recCount + + private[TypeComparer] inline + def isolated[T](inline op: Boolean, inline mapResult: Boolean => T)(using Context): T = + val savedNotes = errorNotes + val savedLevel = maxErrorLevel + errorNotes = Nil + maxErrorLevel = -1 + try mapResult(op) + finally + errorNotes = savedNotes + maxErrorLevel = savedLevel + + /** Run `op` on current type comparer, maping its Boolean result to + * a CompareResult with possible outcomes OK and Fail(...)`. In case + * of failure pass the accumulated errorNotes of this type comparer to + * in the Fail value. + */ + def compareResult(op: => Boolean)(using Context): CompareResult = + isolated(op, res => + if res then CompareResult.OK else CompareResult.Fail(errorNotes.map(_._2))) } object TypeComparer { + /** A base trait for data producing addenda to error messages */ + trait ErrorNote: + /** A disciminating kind. An error note is not added if it has the same kind + * as an already existing error note. + */ + def kind: Class[?] = getClass + + /** A richer compare result, returned by `testSubType` and `test`. */ enum CompareResult: - case OK, Fail, OKwithGADTUsed, OKwithOpaquesUsed + case OK, OKwithGADTUsed, OKwithOpaquesUsed + case Fail(errorNotes: List[ErrorNote]) /** Class for unification variables used in `natValue`. */ private class AnyConstantType extends UncachedGroundType with ValueType { @@ -3230,7 +3376,6 @@ object TypeComparer { else res match case ClassInfo(_, cls, _, _, _) => cls.showLocated case bounds: TypeBounds => i"type bounds [$bounds]" - case CaptureSet.CompareResult.OK => "OK" case res: printing.Showable => res.show case _ => String.valueOf(res).nn @@ -3376,6 +3521,9 @@ object TypeComparer { def constrainPatternType(pat: Type, scrut: Type, forceInvariantRefinement: Boolean = false)(using Context): Boolean = comparing(_.constrainPatternType(pat, scrut, forceInvariantRefinement)) + def necessaryGadtConstraint(constrs: List[GadtConstraint], preGadt: GadtConstraint)(using Context): Option[GadtConstraint] = + comparing(_.necessaryGadtConstraint(constrs, preGadt)) + def explained[T](op: ExplainingTypeComparer => T, header: String = "Subtype trace:", short: Boolean = false)(using Context): String = comparing(_.explained(op, header, short)) @@ -3385,8 +3533,28 @@ object TypeComparer { def reduceMatchWith[T](op: MatchReducer => T)(using Context): T = comparing(_.reduceMatchWith(op)) - def subCaptures(refs1: CaptureSet, refs2: CaptureSet, vs: CaptureSet.VarState)(using Context): CaptureSet.CompareResult = + def subCaptures(refs1: CaptureSet, refs2: CaptureSet, vs: CaptureSet.VarState)(using Context): Boolean = comparing(_.subCaptures(refs1, refs2, vs)) + + def logUndoAction(action: () => Unit)(using Context): Unit = + currentComparer.logUndoAction(action) + + def inNestedLevel(op: => Boolean)(using Context): Boolean = + currentComparer.inNestedLevel(op) + + def addErrorNote(note: ErrorNote)(using Context): Unit = + currentComparer.addErrorNote(note) + + def updateErrorNotes(f: PartialFunction[ErrorNote, ErrorNote])(using Context): Unit = + currentComparer.errorNotes = currentComparer.errorNotes.mapConserve: p => + val (level, note) = p + if f.isDefinedAt(note) then (level, f(note)) else p + + def compareResult(op: => Boolean)(using Context): CompareResult = + comparing(_.compareResult(op)) + + inline def noNotes(inline op: Boolean)(using Context): Boolean = + currentComparer.isolated(op, x => x) } object MatchReducer: @@ -3791,9 +3959,11 @@ class ExplainingTypeComparer(initctx: Context, short: Boolean) extends TypeCompa private val b = new StringBuilder private var lastForwardGoal: String | Null = null - private def appendFailure(x: String) = + private def appendFailure(notes: List[ErrorNote]) = if lastForwardGoal != null then // last was deepest goal that failed - b.append(s" = $x") + b.append(s" = false") + for case note: printing.Showable <- notes do + b.append(i": $note") lastForwardGoal = null override def traceIndented[T](str: String)(op: => T): T = @@ -3809,9 +3979,9 @@ class ExplainingTypeComparer(initctx: Context, short: Boolean) extends TypeCompa if short then res match case false => - appendFailure("false") - case res: CaptureSet.CompareResult if res != CaptureSet.CompareResult.OK => - appendFailure(show(res)) + appendFailure(errorNotes.map(_._2)) + case CompareResult.Fail(notes) => + appendFailure(notes) case _ => b.length = curLength // don't show successful subtraces else @@ -3861,7 +4031,7 @@ class ExplainingTypeComparer(initctx: Context, short: Boolean) extends TypeCompa super.gadtAddBound(sym, b, isUpper) } - override def subCaptures(refs1: CaptureSet, refs2: CaptureSet, vs: CaptureSet.VarState)(using Context): CaptureSet.CompareResult = + override def subCaptures(refs1: CaptureSet, refs2: CaptureSet, vs: CaptureSet.VarState)(using Context): Boolean = traceIndented(i"subcaptures $refs1 <:< $refs2 in ${vs.toString}") { super.subCaptures(refs1, refs2, vs) } diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 0365b205c5b6..6eafae70c4ee 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -271,7 +271,7 @@ object TypeErasure { if (defn.isPolymorphicAfterErasure(sym)) eraseParamBounds(sym.info.asInstanceOf[PolyType]) else if (sym.isAbstractOrParamType) TypeAlias(WildcardType) - else if sym.is(ConstructorProxy) then NoType + else if sym.is(PhantomSymbol) then NoType else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(using preErasureCtx)) else if (sym.is(Label)) erase.eraseResult(sym.info)(using preErasureCtx) else erase.eraseInfo(tp, sym)(using preErasureCtx) match { @@ -386,6 +386,12 @@ object TypeErasure { case _ => false } + /** Is `tp` of the form `Array^N[T]` where T is generic? */ + def isGenericArrayArg(tp: Type)(using Context): Boolean = tp.dealias match + case defn.ArrayOf(elem) => isGenericArrayArg(elem) + case _ => isGeneric(tp) + end isGenericArrayArg + /** The erased least upper bound of two erased types is computed as follows * - if both argument are arrays of objects, an array of the erased lub of the element types * - if both arguments are arrays of same primitives, an array of this primitive @@ -697,7 +703,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst val (names, formals0) = if tp.hasErasedParams then tp.paramNames .zip(tp.paramInfos) - .zip(tp.erasedParams) + .zip(tp.paramErasureStatuses) .collect{ case (param, isErased) if !isErased => param } .unzip else (tp.paramNames, tp.paramInfos) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index 4761beae8bd0..1b6156ee74e8 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -32,7 +32,7 @@ abstract class TypeError(using creationContext: Context) extends Exception(""): || ctx.settings.YdebugCyclic.value override def fillInStackTrace(): Throwable = - if computeStackTrace then super.fillInStackTrace().nn + if computeStackTrace then super.fillInStackTrace() else this /** Convert to message. This takes an additional Context, so that we @@ -246,7 +246,7 @@ class UnpicklingError(denot: Denotation, where: String, cause: Throwable)(using case cause: UnpicklingError => "" case _ => if ctx.settings.YdebugUnpickling.value then - cause.getStackTrace().nn.mkString("\n ", "\n ", "") + cause.getStackTrace().mkString("\n ", "\n ", "") else "\n\nRun with -Ydebug-unpickling to see full stack trace." em"""Could not read definition $denot$where. Caused by the following exception: |$cause$debugUnpickling""" diff --git a/compiler/src/dotty/tools/dotc/core/TypeEval.scala b/compiler/src/dotty/tools/dotc/core/TypeEval.scala index 03821ad4812a..b7995b1ffba2 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeEval.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeEval.scala @@ -94,7 +94,7 @@ object TypeEval: val result = try op catch case e: Throwable => - throw TypeError(em"${e.getMessage.nn}") + throw TypeError(em"${e.getMessage}") ConstantType(Constant(result)) def fieldsOf: Option[Type] = diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index a1e26c20fdbb..f1936ff8cd01 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -18,8 +18,9 @@ import typer.ForceDegree import typer.Inferencing.* import typer.IfBottom import reporting.TestingReporter +import Annotations.Annotation import cc.{CapturingType, derivedCapturingType, CaptureSet, captureSet, isBoxed, isBoxedCapturing} -import CaptureSet.{CompareResult, IdentityCaptRefMap, VarState} +import CaptureSet.{IdentityCaptRefMap, VarState} import scala.annotation.internal.sharable import scala.annotation.threadUnsafe @@ -124,7 +125,9 @@ object TypeOps: } def isLegalPrefix(pre: Type)(using Context): Boolean = - pre.isStable + // isLegalPrefix is relaxed after typer unless we're doing an implicit + // search (this matters when doing summonInline in an inline def like in tests/pos/i17222.8.scala). + pre.isStable || !ctx.phase.isTyper && ctx.mode.is(Mode.ImplicitsEnabled) /** Implementation of Types#simplified */ def simplify(tp: Type, theMap: SimplifyMap | Null)(using Context): Type = { @@ -161,7 +164,7 @@ object TypeOps: TypeComparer.lub(simplify(l, theMap), simplify(r, theMap), isSoft = tp.isSoft) case tp @ CapturingType(parent, refs) => if !ctx.mode.is(Mode.Type) - && refs.subCaptures(parent.captureSet, VarState.Separate).isOK + && refs.subCaptures(parent.captureSet, VarState.Separate) && (tp.isBoxed || !parent.isBoxedCapturing) // fuse types with same boxed status and outer boxed with any type then @@ -278,7 +281,15 @@ object TypeOps: } case AndType(tp11, tp12) => mergeRefinedOrApplied(tp11, tp2) & mergeRefinedOrApplied(tp12, tp2) - case tp1: TypeParamRef if tp1 == tp2 => tp1 + case tp1: TypeParamRef => + tp2.stripTypeVar match + case tp2: TypeParamRef if tp1 == tp2 => tp1 + case _ => fail + case tp1: TypeVar => + tp2 match + case tp2: TypeVar if tp1 == tp2 => tp1 + case tp2: TypeParamRef if tp1.stripTypeVar == tp2 => tp2 + case _ => fail case _ => fail } } @@ -794,9 +805,13 @@ object TypeOps: prefixTVar.uncheckedNN case ThisType(tref) if !tref.symbol.isStaticOwner => val symbol = tref.symbol + val compatibleSingleton = singletons.valuesIterator.find(_.underlying.derivesFrom(symbol)) if singletons.contains(symbol) then prefixTVar = singletons(symbol) // e.g. tests/pos/i16785.scala, keep Outer.this prefixTVar.uncheckedNN + else if compatibleSingleton.isDefined then + prefixTVar = compatibleSingleton.get + prefixTVar.uncheckedNN else if symbol.is(Module) then TermRef(this(tref.prefix), symbol.sourceModule) else if (prefixTVar != null) @@ -894,10 +909,11 @@ object TypeOps: } val inferThisMap = new InferPrefixMap - val tvars = tp1.etaExpand match + val prefixInferredTp = inferThisMap(tp1) + val tvars = prefixInferredTp.etaExpand match case eta: TypeLambda => constrained(eta) case _ => Nil - val protoTp1 = inferThisMap.apply(tp1).appliedTo(tvars) + val protoTp1 = prefixInferredTp.appliedTo(tvars) if gadtSyms.nonEmpty then ctx.gadtState.addToConstraint(gadtSyms) @@ -936,6 +952,28 @@ object TypeOps: class StripTypeVarsMap(using Context) extends TypeMap: def apply(tp: Type) = mapOver(tp).stripTypeVar + /** Map no-flip covariant occurrences of `into[T]` to `T @$into` */ + def suppressInto(using Context) = new FollowAliasesMap: + def apply(t: Type): Type = t match + case AppliedType(tycon: TypeRef, arg :: Nil) if variance >= 0 && defn.isInto(tycon.symbol) => + AnnotatedType(arg, Annotation(defn.SilentIntoAnnot, util.Spans.NoSpan)) + case _: MatchType | _: LazyRef => + t + case _ => + mapFollowingAliases(t) + + /** Map no-flip covariant occurrences of `T @$into` to `into[T]` */ + def revealInto(using Context) = new FollowAliasesMap: + def apply(t: Type): Type = t match + case AnnotatedType(t1, ann) if variance >= 0 && ann.symbol == defn.SilentIntoAnnot => + AppliedType( + defn.ConversionModule.termRef.select(defn.Conversion_into), // the external reference to the opaque type + t1 :: Nil) + case _: MatchType | _: LazyRef => + t + case _ => + mapFollowingAliases(t) + /** Apply [[Type.stripTypeVar]] recursively. */ def stripTypeVars(tp: Type)(using Context): Type = new StripTypeVarsMap().apply(tp) diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index ab035db68d9f..594249065d98 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -23,13 +23,6 @@ class TypeUtils: def isPrimitiveValueType(using Context): Boolean = self.classSymbol.isPrimitiveValueClass - def isErasedClass(using Context): Boolean = - val cls = self.underlyingClassRef(refinementOK = true).typeSymbol - cls.is(Flags.Erased) - && (cls != defn.SingletonClass || Feature.enabled(Feature.modularity)) - // Singleton counts as an erased class only under x.modularity - - /** Is this type a checked exception? This is the case if the type * derives from Exception but not from RuntimeException. According to * that definition Throwable is unchecked. That makes sense since you should @@ -53,6 +46,11 @@ class TypeUtils: case ps => ps.reduceLeft(AndType(_, _)) } + def widenSkolems(using Context): Type = + val widenSkolemsMap = new TypeMap: + def apply(tp: Type) = mapOver(tp.widenSkolem) + widenSkolemsMap(self) + /** The element types of this tuple type, which can be made up of EmptyTuple, TupleX and `*:` pairs */ def tupleElementTypes(using Context): Option[List[Type]] = @@ -134,7 +132,7 @@ class TypeUtils: 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 defn.NamedTupleDirect(nmes, vals) => extractNamesTypes(nmes, vals) @@ -147,6 +145,17 @@ class TypeUtils: def namedTupleElementTypes(derived: Boolean)(using Context): List[(TermName, Type)] = namedTupleElementTypesUpTo(Int.MaxValue, derived) + /** If this is a generic tuple type with arity <= MaxTupleArity, return the + * corresponding TupleN type, otherwise return this. + */ + def normalizedTupleType(using Context): Type = + if self.isGenericTuple then + self.tupleElementTypes match + case Some(elems) if elems.size <= Definitions.MaxTupleArity => defn.tupleType(elems) + case _ => self + else + self + def isNamedTupleType(using Context): Boolean = self match case defn.NamedTuple(_, _) => true case _ => false @@ -251,5 +260,9 @@ class TypeUtils: self.decl(nme.CONSTRUCTOR).altsWith(isApplicable).map(_.symbol) + def showRef(using Context): String = self match + case self: SingletonType => ctx.printer.toTextRef(self).show + case _ => self.show + end TypeUtils diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index d4345916ba77..c0db3301b8b5 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -28,6 +28,8 @@ object TyperState { opaque type Snapshot = (Constraint, TypeVars, LevelMap) + class BadTyperStateAssertion(msg: String) extends AssertionError(msg) + extension (ts: TyperState) def snapshot()(using Context): Snapshot = (ts.constraint, ts.ownedVars, ts.upLevels) @@ -43,7 +45,7 @@ object TyperState { } class TyperState() { - import TyperState.LevelMap + import TyperState.{LevelMap, BadTyperStateAssertion} private var myId: Int = uninitialized def id: Int = myId @@ -269,8 +271,10 @@ class TyperState() { */ private def includeVar(tvar: TypeVar)(using Context): Unit = val oldState = tvar.owningState.nn.get - assert(oldState == null || !oldState.isCommittable, - i"$this attempted to take ownership of $tvar which is already owned by committable $oldState") + + if oldState != null && oldState.isCommittable then + throw BadTyperStateAssertion( + i"$this attempted to take ownership of $tvar which is already owned by committable $oldState") tvar.owningState = new WeakReference(this) ownedVars += tvar diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 2cd66464da3c..a094bc77a854 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -39,11 +39,11 @@ import reporting.{trace, Message} import java.lang.ref.WeakReference import compiletime.uninitialized import cc.* -import CaptureSet.{CompareResult, IdentityCaptRefMap} +import CaptureSet.IdentityCaptRefMap +import Capabilities.* import scala.annotation.internal.sharable import scala.annotation.threadUnsafe -import dotty.tools.dotc.cc.ccConfig object Types extends TypeUtils { @@ -244,6 +244,10 @@ object Types extends TypeUtils { def isExactlyNothing(using Context): Boolean = this match { case tp: TypeRef => tp.name == tpnme.Nothing && (tp.symbol eq defn.NothingClass) + case AndType(tp1, tp2) => + tp1.isExactlyNothing || tp2.isExactlyNothing + case OrType(tp1, tp2) => + tp1.isExactlyNothing && tp2.isExactlyNothing case _ => false } @@ -251,6 +255,10 @@ object Types extends TypeUtils { def isExactlyAny(using Context): Boolean = this match { case tp: TypeRef => tp.name == tpnme.Any && (tp.symbol eq defn.AnyClass) + case AndType(tp1, tp2) => + tp1.isExactlyAny && tp2.isExactlyAny + case OrType(tp1, tp2) => + tp1.isExactlyAny || tp2.isExactlyAny case _ => false } @@ -270,15 +278,15 @@ object Types extends TypeUtils { /** True if this type is an instance of the given `cls` or an instance of * a non-bottom subclass of `cls`. */ - final def derivesFrom(cls: Symbol)(using Context): Boolean = { + final def derivesFrom(cls: Symbol, defaultIfUnknown: Boolean = false)(using Context): Boolean = { def isLowerBottomType(tp: Type) = tp.isBottomType && (tp.hasClassSymbol(defn.NothingClass) || cls != defn.NothingClass && !cls.isValueClass) - def loop(tp: Type): Boolean = tp match { + def loop(tp: Type): Boolean = try tp match case tp: TypeRef => val sym = tp.symbol - if (sym.isClass) sym.derivesFrom(cls) else loop(tp.superType) + if (sym.isClass) sym.derivesFrom(cls, defaultIfUnknown) else loop(tp.superType) case tp: AppliedType => tp.superType.derivesFrom(cls) case tp: MatchType => @@ -301,7 +309,7 @@ object Types extends TypeUtils { cls == defn.ObjectClass case _ => false - } + catch case ex: Throwable => handleRecursive(i"derivesFrom $cls:", show, ex) loop(this) } @@ -345,6 +353,8 @@ object Types extends TypeUtils { */ def isSingletonBounded(frozen: Boolean)(using Context): Boolean = this.dealias.normalized match case tp: SingletonType => tp.isStable + case tp: TypeParamRef => + ctx.typerState.constraint.bounds(tp).hi.isSingletonBounded(frozen) case tp: TypeRef => tp.name == tpnme.Singleton && tp.symbol == defn.SingletonClass || tp.superType.isSingletonBounded(frozen) @@ -352,7 +362,7 @@ object Types extends TypeUtils { if frozen then tp frozen_<:< defn.SingletonType else tp <:< defn.SingletonType case tp: HKTypeLambda => false case tp: TypeProxy => tp.superType.isSingletonBounded(frozen) - case AndType(tpL, tpR) => tpL.isSingletonBounded(frozen) || tpR.isSingletonBounded(frozen) + case tp: AndType => tp.tp1.isSingletonBounded(frozen) || tp.tp2.isSingletonBounded(frozen) case _ => false /** Is this type of kind `AnyKind`? */ @@ -373,15 +383,26 @@ object Types extends TypeUtils { final def isNotNull(using Context): Boolean = this match { case tp: ConstantType => tp.value.value != null case tp: FlexibleType => false - case tp: ClassInfo => !tp.cls.isNullableClass && tp.cls != defn.NothingClass + case tp: ClassInfo => !tp.cls.isNullableClass && !tp.isNothingType case tp: AppliedType => tp.superType.isNotNull - case tp: TypeBounds => tp.lo.isNotNull + case tp: TypeBounds => tp.hi.isNotNull case tp: TypeProxy => tp.underlying.isNotNull case AndType(tp1, tp2) => tp1.isNotNull || tp2.isNotNull case OrType(tp1, tp2) => tp1.isNotNull && tp2.isNotNull case _ => false } + /** Is `null` a value of this type? */ + def admitsNull(using Context): Boolean = + isNullType || isAny || (this match + case OrType(l, r) => r.admitsNull || l.admitsNull + case AndType(l, r) => r.admitsNull && l.admitsNull + case TypeBounds(lo, hi) => lo.admitsNull + case FlexibleType(lo, hi) => true + case tp: TypeProxy => tp.underlying.admitsNull + case _ => false + ) + /** Is this type produced as a repair for an error? */ final def isError(using Context): Boolean = stripTypeVar.isInstanceOf[ErrorType] @@ -406,7 +427,7 @@ object Types extends TypeUtils { case tp: AndOrType => tp.tp1.unusableForInference || tp.tp2.unusableForInference case tp: LambdaType => tp.resultType.unusableForInference || tp.paramInfos.exists(_.unusableForInference) case WildcardType(optBounds) => optBounds.unusableForInference - case CapturingType(parent, refs) => parent.unusableForInference || refs.elems.exists(_.unusableForInference) + case CapturingType(parent, refs) => parent.unusableForInference || refs.elems.exists(_.coreType.unusableForInference) case _: ErrorType => true case _ => false catch case ex: Throwable => handleRecursive("unusableForInference", show, ex) @@ -446,11 +467,34 @@ object Types extends TypeUtils { def isRepeatedParam(using Context): Boolean = typeSymbol eq defn.RepeatedParamClass - /** Is this a parameter type that allows implicit argument converson? */ + /** Is this type of the form `compiletime.into[T]`, which means it can be the + * target of an implicit converson without requiring a language import? + */ def isInto(using Context): Boolean = this match - case AnnotatedType(_, annot) => annot.symbol == defn.IntoParamAnnot + case AppliedType(tycon: TypeRef, arg :: Nil) => defn.isInto(tycon.symbol) case _ => false + /** Is this type a legal target type for an implicit conversion, so that + * no `implicitConversions` language import is necessary? + */ + def isConversionTargetType(using Context): Boolean = + dealias(KeepOpaques).match + case tp: TypeRef => + (tp.symbol.isClass || tp.symbol.isOpaqueAlias) && tp.symbol.is(Into) + case tp @ AppliedType(tycon, _) => + tp.isInto || tycon.isConversionTargetType + case tp: AndOrType => + tp.tp1.isConversionTargetType && tp.tp2.isConversionTargetType + case tp: TypeVar => + false + case tp: MatchType => + val tp1 = tp.reduced + (tp1 ne tp) && tp1.isConversionTargetType + case tp: RefinedType => + tp.parent.isConversionTargetType + case _ => + false + /** Is this the type of a method that has a repeated parameter type as * last parameter type? */ @@ -813,6 +857,8 @@ object Types extends TypeUtils { goOr(tp) case tp: JavaArrayType => defn.ObjectType.findMember(name, pre, required, excluded) + case tp: WildcardType => + go(tp.bounds) case err: ErrorType => newErrorSymbol(pre.classSymbol.orElse(defn.RootClass), name, err.msg) case _ => @@ -868,10 +914,11 @@ object Types extends TypeUtils { pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, overridingRefinement) else val isRefinedMethod = rinfo.isInstanceOf[MethodOrPoly] - val joint = pdenot.meet( - new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId), pre, isRefinedMethod), - pre, - safeIntersection = ctx.base.pendingMemberSearches.contains(name)) + val joint = CCState.withCollapsedFresh: + pdenot.meet( + new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId), pre, isRefinedMethod), + pre, + safeIntersection = ctx.base.pendingMemberSearches.contains(name)) joint match case joint: SingleDenotation if isRefinedMethod @@ -1012,6 +1059,23 @@ object Types extends TypeUtils { buf.toList } + /** For use in quotes reflect. + * A bit slower than the usual approach due to the use of LinkedHashSet. + **/ + def sortedParents(using Context): mutable.LinkedHashSet[Type] = this match + case tp: ClassInfo => + mutable.LinkedHashSet(tp) | mutable.LinkedHashSet(tp.declaredParents.flatMap(_.sortedParents.toList)*) + case tp: RefinedType => + tp.parent.sortedParents + case tp: TypeProxy => + tp.superType.sortedParents + case tp: AndType => + tp.tp1.sortedParents | tp.tp2.sortedParents + case tp: OrType => + tp.tp1.sortedParents & tp.tp2.sortedParents + case _ => + mutable.LinkedHashSet() + /** The set of abstract term members of this type. */ final def abstractTermMembers(using Context): Seq[SingleDenotation] = { record("abstractTermMembers") @@ -1470,48 +1534,48 @@ object Types extends TypeUtils { case Atoms.Unknown => Atoms.Unknown case _ => Atoms.Unknown - private def dealias1(keep: AnnotatedType => Context ?=> Boolean, keepOpaques: Boolean)(using Context): Type = this match { + def dealias(keeps: Keeps)(using Context): Type = this match case tp: TypeRef => - if (tp.symbol.isClass) tp - else tp.info match { - case TypeAlias(alias) if !(keepOpaques && tp.symbol.is(Opaque)) => - alias.dealias1(keep, keepOpaques) + if tp.symbol.isClass then tp + else tp.info match + case TypeAlias(alias) if (keeps & KeepOpaques) == 0 || !tp.symbol.is(Opaque) => + alias.dealias(keeps) case _ => tp - } case app @ AppliedType(tycon, _) => - val tycon1 = tycon.dealias1(keep, keepOpaques) - if (tycon1 ne tycon) app.superType.dealias1(keep, keepOpaques) + val tycon1 = tycon.dealias(keeps) + if tycon1 ne tycon then app.superType.dealias(keeps) else this case tp: TypeVar => val tp1 = tp.instanceOpt - if (tp1.exists) tp1.dealias1(keep, keepOpaques) else tp + if tp1.exists then tp1.dealias(keeps) else tp case tp: AnnotatedType => - val parent1 = tp.parent.dealias1(keep, keepOpaques) - if keep(tp) then tp.derivedAnnotatedType(parent1, tp.annot) + val parent1 = tp.parent.dealias(keeps) + if (keeps & KeepAnnots) != 0 + || (keeps & KeepRefiningAnnots) != 0 && tp.isRefining + then tp.derivedAnnotatedType(parent1, tp.annot) else tp match case tp @ CapturingType(parent, refs) => tp.derivedCapturingType(parent1, refs) case _ => parent1 case tp: LazyRef => - tp.ref.dealias1(keep, keepOpaques) + tp.ref.dealias(keeps) case _ => this - } /** Follow aliases and dereference LazyRefs, annotated types and instantiated * TypeVars until type is no longer alias type, annotated type, LazyRef, * or instantiated type variable. */ - final def dealias(using Context): Type = dealias1(keepNever, keepOpaques = false) + final def dealias(using Context): Type = dealias(KeepNothing) /** Follow aliases and dereference LazyRefs and instantiated TypeVars until type * is no longer alias type, LazyRef, or instantiated type variable. * Goes through annotated types and rewraps annotations on the result. */ - final def dealiasKeepAnnots(using Context): Type = dealias1(keepAlways, keepOpaques = false) + final def dealiasKeepAnnots(using Context): Type = dealias(KeepAnnots) /** Like `dealiasKeepAnnots`, but keeps only refining annotations */ - final def dealiasKeepRefiningAnnots(using Context): Type = dealias1(keepIfRefining, keepOpaques = false) + final def dealiasKeepRefiningAnnots(using Context): Type = dealias(KeepRefiningAnnots) /** Like dealias, but does not follow aliases if symbol is Opaque. This is * necessary if we want to look at the info of a symbol containing opaque @@ -1529,13 +1593,13 @@ object Types extends TypeUtils { * Here, we dealias symbol infos at the start of capture checking in operation `fluidify`. * We have to be careful not to accidentally reveal opaque aliases when doing so. */ - final def dealiasKeepOpaques(using Context): Type = dealias1(keepNever, keepOpaques = true) + final def dealiasKeepOpaques(using Context): Type = dealias(KeepOpaques) /** Like dealiasKeepAnnots, but does not follow opaque aliases. See `dealiasKeepOpaques` * for why this is sometimes necessary. */ final def dealiasKeepAnnotsAndOpaques(using Context): Type = - dealias1(keepAlways, keepOpaques = true) + dealias(KeepAnnots | KeepOpaques) /** Approximate this type with a type that does not contain skolem types. */ final def deskolemized(using Context): Type = @@ -1567,19 +1631,18 @@ object Types extends TypeUtils { case tp: AppliedType => tp.underlyingNormalizable case _ => NoType - private def widenDealias1(keep: AnnotatedType => Context ?=> Boolean)(using Context): Type = { - val res = this.widen.dealias1(keep, keepOpaques = false) - if (res eq this) res else res.widenDealias1(keep) - } + private def widenDealias(keeps: Keeps)(using Context): Type = + val tp1 = widen.dealias(keeps) + if tp1 eq this then this else tp1.widenDealias(keeps) /** Perform successive widenings and dealiasings until none can be applied anymore */ - final def widenDealias(using Context): Type = widenDealias1(keepNever) + final def widenDealias(using Context): Type = widenDealias(KeepNothing) /** Perform successive widenings and dealiasings while rewrapping annotations, until none can be applied anymore */ - final def widenDealiasKeepAnnots(using Context): Type = widenDealias1(keepAlways) + final def widenDealiasKeepAnnots(using Context): Type = widenDealias(KeepAnnots) /** Perform successive widenings and dealiasings while rewrapping refining annotations, until none can be applied anymore */ - final def widenDealiasKeepRefiningAnnots(using Context): Type = widenDealias1(keepIfRefining) + final def widenDealiasKeepRefiningAnnots(using Context): Type = widenDealias(KeepRefiningAnnots) /** Widen from constant type to its underlying non-constant * base type. @@ -1623,19 +1686,17 @@ object Types extends TypeUtils { NoType } - /** The iterator of underlying types as long as type is a TypeProxy. - * Useful for diagnostics + /** The iterator of underlying types staring with `this` and followed by + * repeatedly applying `f` as long as type is a TypeProxy. Useful for diagnostics. */ - def underlyingIterator(using Context): Iterator[Type] = new Iterator[Type] { + def iterate(f: TypeProxy => Type): Iterator[Type] = new Iterator[Type]: var current = Type.this var hasNext = true - def next = { + def next() = val res = current hasNext = current.isInstanceOf[TypeProxy] - if (hasNext) current = current.asInstanceOf[TypeProxy].underlying + if hasNext then current = f(current.asInstanceOf[TypeProxy]) res - } - } /** A prefix-less refined this or a termRef to a new skolem symbol * that has the given type as info. @@ -1966,8 +2027,7 @@ object Types extends TypeUtils { } defn.FunctionNOf( mt.paramInfos.mapConserve: - _.translateFromRepeated(toArray = isJava) - .mapIntoAnnot(defn.IntoParamAnnot, null), + _.translateFromRepeated(toArray = isJava), result1, isContextual) if mt.hasErasedParams then defn.PolyFunctionOf(mt) @@ -2015,38 +2075,6 @@ object Types extends TypeUtils { case _ => this } - /** A mapping between mapping one kind of into annotation to another or - * dropping into annotations. - * @param from the into annotation to map - * @param to either the replacement annotation symbol, or `null` - * in which case the `from` annotations are dropped. - */ - def mapIntoAnnot(from: ClassSymbol, to: ClassSymbol | Null)(using Context): Type = this match - case self @ AnnotatedType(tp, annot) => - val tp1 = tp.mapIntoAnnot(from, to) - if annot.symbol == from then - if to == null then tp1 - else AnnotatedType(tp1, Annotation(to, annot.tree.span)) - else self.derivedAnnotatedType(tp1, annot) - case AppliedType(tycon, arg :: Nil) if tycon.typeSymbol == defn.RepeatedParamClass => - val arg1 = arg.mapIntoAnnot(from, to) - if arg1 eq arg then this - else AppliedType(tycon, arg1 :: Nil) - case defn.FunctionOf(argTypes, resType, isContextual) => - val resType1 = resType.mapIntoAnnot(from, to) - if resType1 eq resType then this - else defn.FunctionOf(argTypes, resType1, isContextual) - case RefinedType(parent, rname, mt: MethodOrPoly) => - val mt1 = mt.mapIntoAnnot(from, to) - if mt1 eq mt then this - else RefinedType(parent.mapIntoAnnot(from, to), rname, mt1) - case mt: MethodOrPoly => - mt.derivedLambdaType(resType = mt.resType.mapIntoAnnot(from, to)) - case tp: ExprType => - tp.derivedExprType(tp.resType.mapIntoAnnot(from, to)) - case _ => - this - /** The set of distinct symbols referred to by this type, after all aliases are expanded */ def coveringSet(using Context): Set[Symbol] = (new CoveringSetAccumulator).apply(Set.empty[Symbol], this) @@ -2303,7 +2331,7 @@ object Types extends TypeUtils { def _1: Type def _2: Designator - assert(NamedType.validPrefix(prefix), s"invalid prefix $prefix") + if !NamedType.validPrefix(prefix) then throw InvalidPrefix() private var myName: Name | Null = null private var lastDenotation: Denotation | Null = null @@ -2911,7 +2939,7 @@ object Types extends TypeUtils { */ abstract case class TermRef(override val prefix: Type, private var myDesignator: Designator) - extends NamedType, ImplicitRef, SingletonCaptureRef { + extends NamedType, ImplicitRef, SingletonType, ObjectCapability { type ThisType = TermRef type ThisName = TermName @@ -2940,7 +2968,7 @@ object Types extends TypeUtils { abstract case class TypeRef(override val prefix: Type, private var myDesignator: Designator) - extends NamedType, CaptureRef { + extends NamedType, SetCapability { type ThisType = TypeRef type ThisName = TypeName @@ -3068,6 +3096,8 @@ object Types extends TypeUtils { apply(prefix, designatorFor(prefix, name, denot)).withDenot(denot) } + class InvalidPrefix extends Exception + // --- Other SingletonTypes: ThisType/SuperType/ConstantType --------------------------- /** The type cls.this @@ -3076,7 +3106,7 @@ object Types extends TypeUtils { * do not survive runs whereas typerefs do. */ abstract case class ThisType(tref: TypeRef) - extends CachedProxyType, SingletonCaptureRef { + extends CachedProxyType, SingletonType, ObjectCapability { def cls(using Context): ClassSymbol = tref.stableInRunSymbol match { case cls: ClassSymbol => cls case _ if ctx.mode.is(Mode.Interactive) => defn.AnyClass // was observed to happen in IDE mode @@ -3721,7 +3751,8 @@ object Types extends TypeUtils { // is that most poly types are cyclic via poly params, // and therefore two different poly types would never be equal. - trait MethodicType extends TermType + trait MethodicType extends TermType: + def resType: Type /** A by-name parameter type of the form `=> T`, or the type of a method with no parameter list. */ abstract case class ExprType(resType: Type) @@ -3860,7 +3891,7 @@ object Types extends TypeUtils { if tp.prefix eq pre then tp else pre match - case ref: ParamRef if (ref.binder eq self) && tp.symbol.exists => + case ref: ParamRef if (ref.binder eq self) && tp.symbol.exists && tp.symbol.is(Method) => NamedType(pre, tp.name, tp.denot.asSeenFrom(pre)) case _ => tp.derivedSelect(pre) @@ -3931,7 +3962,7 @@ object Types extends TypeUtils { case tp: MethodType => val params = if (hasErasedParams) tp.paramInfos - .zip(tp.erasedParams) + .zip(tp.paramErasureStatuses) .collect { case (param, isErased) if !isErased => param } else tp.paramInfos resultSignature.prependTermParams(params, sourceLanguage) @@ -3991,7 +4022,7 @@ object Types extends TypeUtils { override def resultType(using Context): Type = if (dependencyStatus == FalseDeps) { // dealias all false dependencies - val dealiasMap = new TypeMap with IdentityCaptRefMap { + object dealiasMap extends TypeMap with IdentityCaptRefMap { def apply(tp: Type) = tp match { case tp @ TypeRef(pre, _) => tp.info match { @@ -4041,7 +4072,7 @@ object Types extends TypeUtils { val status1 = (compute(status, parent, theAcc) /: refs.elems): (s, ref) => ref.stripReach match case tp: TermParamRef if tp.binder eq thisLambdaType => combine(s, TrueDeps) - case tp => combine(s, compute(status, tp, theAcc)) + case tp => combine(s, compute(status, tp.coreType, theAcc)) if refs.isConst || forParams // We assume capture set variables in parameters don't generate param dependencies then status1 else combine(status1, Provisional) @@ -4111,16 +4142,12 @@ object Types extends TypeUtils { /** The least supertype of `resultType` that does not contain parameter dependencies */ def nonDependentResultApprox(using Context): Type = if isResultDependent then - val dropDependencies = new ApproximatingTypeMap { + object dropDependencies extends ApproximatingTypeMap { def apply(tp: Type) = tp match { case tp @ TermParamRef(`thisLambdaType`, _) => range(defn.NothingType, atVariance(1)(apply(tp.underlying))) case CapturingType(_, _) => mapOver(tp) - case ReachCapability(tp1) => - apply(tp1) match - case tp1a: CaptureRef if tp1a.isTrackableRef => tp1a.reach - case _ => root.cap case AnnotatedType(parent, ann) if ann.refersToParamOf(thisLambdaType) => val parent1 = mapOver(parent) if ann.symbol.isRetainsLike then @@ -4131,6 +4158,12 @@ object Types extends TypeUtils { parent1 case _ => mapOver(tp) } + override def mapCapability(c: Capability, deep: Boolean = false): Capability | (CaptureSet, Boolean) = c match + case Reach(c1) => + apply(c1) match + case tp1a: ObjectCapability if tp1a.isTrackableRef => tp1a.reach + case _ => GlobalCap + case _ => super.mapCapability(c, deep) } dropDependencies(resultType) else resultType @@ -4161,7 +4194,7 @@ object Types extends TypeUtils { final override def isContextualMethod: Boolean = companion.eq(ContextualMethodType) - def erasedParams(using Context): List[Boolean] = + def paramErasureStatuses(using Context): List[Boolean] = paramInfos.map(p => p.hasAnnotation(defn.ErasedParamAnnot)) def nonErasedParamCount(using Context): Int = @@ -4218,11 +4251,11 @@ object Types extends TypeUtils { /** Produce method type from parameter symbols, with special mappings for repeated * and inline parameters: - * - replace @repeated annotations on Seq or Array types by types + * - replace `@repeated` annotations on Seq or Array types by types * - map into annotations to $into annotations - * - add @inlineParam to inline parameters - * - add @erasedParam to erased parameters - * - wrap types of parameters that have an @allowConversions annotation with Into[_] + * - add `@inlineParam` to inline parameters + * - add `@erasedParam` to erased parameters + * - map `T @$into` types to `into[T]` */ def fromSymbols(params: List[Symbol], resultType: Type)(using Context): MethodType = apply(params.map(_.name.asTermName))( @@ -4236,13 +4269,16 @@ object Types extends TypeUtils { def addAnnotation(tp: Type, cls: ClassSymbol, param: Symbol): Type = tp match case ExprType(resType) => ExprType(addAnnotation(resType, cls, param)) case _ => AnnotatedType(tp, Annotation(cls, param.span)) - var paramType = pinfo - .annotatedToRepeated - .mapIntoAnnot(defn.IntoAnnot, defn.IntoParamAnnot) + var paramType = TypeOps.revealInto(pinfo).annotatedToRepeated if param.is(Inline) then paramType = addAnnotation(paramType, defn.InlineParamAnnot, param) if param.is(Erased) then paramType = addAnnotation(paramType, defn.ErasedParamAnnot, param) + // Copy `@use` and `@consume` annotations from parameter symbols to the type. + if param.hasAnnotation(defn.UseAnnot) then + paramType = addAnnotation(paramType, defn.UseAnnot, param) + if param.hasAnnotation(defn.ConsumeAnnot) then + paramType = addAnnotation(paramType, defn.ConsumeAnnot, param) paramType def adaptParamInfo(param: Symbol)(using Context): Type = @@ -4279,7 +4315,7 @@ object Types extends TypeUtils { ps.get(elemName) match case Some(elemRef) => assert(elemRef eq elem, i"bad $mt") case _ => - case root.Result(binder) if binder ne mt => + case ResultCap(binder: MethodType) if binder ne mt => assert(binder.paramNames.toList != mt.paramNames.toList, i"bad $mt") case _ => checkRefs(refs) @@ -4785,7 +4821,7 @@ object Types extends TypeUtils { override def hashIsStable: Boolean = false } - abstract class ParamRef extends BoundType, CaptureRef { + abstract class ParamRef extends BoundType, CoreCapability { type BT <: LambdaType def paramNum: Int def paramName: binder.ThisName = binder.paramNames(paramNum) @@ -4820,7 +4856,7 @@ object Types extends TypeUtils { * refer to `TermParamRef(binder, paramNum)`. */ abstract case class TermParamRef(binder: TermLambda, paramNum: Int) - extends ParamRef, SingletonCaptureRef { + extends ParamRef, SingletonType, ObjectCapability { type BT = TermLambda def kindString: String = "Term" def copyBoundType(bt: BT): Type = bt.paramRefs(paramNum) @@ -4832,7 +4868,7 @@ object Types extends TypeUtils { * refer to `TypeParamRef(binder, paramNum)`. */ abstract case class TypeParamRef(binder: TypeLambda, paramNum: Int) - extends ParamRef { + extends ParamRef, SetCapability { type BT = TypeLambda def kindString: String = "Type" def copyBoundType(bt: BT): Type = bt.paramRefs(paramNum) @@ -5789,7 +5825,7 @@ object Types extends TypeUtils { // ----- Annotated and Import types ----------------------------------------------- /** An annotated type tpe @ annot */ - abstract case class AnnotatedType(parent: Type, annot: Annotation) extends CachedProxyType, CaptureRef { + abstract case class AnnotatedType(parent: Type, annot: Annotation) extends CachedProxyType, ValueType { override def underlying(using Context): Type = parent @@ -6046,14 +6082,14 @@ object Types extends TypeUtils { def takesNoArgs(tp: Type) = !tp.classSymbol.primaryConstructor.exists // e.g. `ContextFunctionN` does not have constructors - || tp.applicableConstructors(Nil, adaptVarargs = true).lengthCompare(1) == 0 + || tp.applicableConstructors(argTypes = Nil, adaptVarargs = true).lengthCompare(1) == 0 // we require a unique constructor so that SAM expansion is deterministic val noArgsNeeded: Boolean = takesNoArgs(tp) - && (!tp.cls.is(Trait) || takesNoArgs(tp.parents.head)) + && (!cls.is(Trait) || takesNoArgs(tp.parents.head)) def isInstantiable = - !tp.cls.isOneOf(FinalOrSealed) && (tp.appliedRef <:< tp.selfType) - if noArgsNeeded && isInstantiable then tp.cls + !cls.isOneOf(FinalOrSealed) && (tp.appliedRef <:< tp.selfType) + if noArgsNeeded && isInstantiable then cls else NoSymbol case tp: AppliedType => samClass(tp.superType) @@ -6148,31 +6184,38 @@ object Types extends TypeUtils { trait BiTypeMap extends TypeMap: thisMap => + /** Hook to control behavior on capture set variables. + * If true, install the map on capture set variables so that future elements are also mapped. + * If false, just map the elements currently present in the capture set variable. + */ + def mapFutureElements: Boolean = true + /** The inverse of the type map */ def inverse: BiTypeMap - /** A restriction of this map to a function on tracked CaptureRefs */ - def forward(ref: CaptureRef): CaptureRef = - val result = this(ref) - def ensureTrackable(tp: Type): CaptureRef = tp match - case tp: CaptureRef => - if tp.isTrackableRef then tp - else ensureTrackable(tp.underlying) - case tp: TypeAlias => - ensureTrackable(tp.alias) - case _ => - assert(false, i"not a trackable CaptureRef: $result with underlying ${result.underlyingIterator.toList}") - ensureTrackable(result) - - /** A restriction of the inverse to a function on tracked CaptureRefs */ - def backward(ref: CaptureRef): CaptureRef = inverse(ref) match - case result: CaptureRef if result.isTrackableRef => result + /** A restriction of this map to a function on tracked Capabilities */ + override def mapCapability(c: Capability, deep: Boolean): Capability = + super.mapCapability(c, deep) match + case c1: Capability => c1 + case (cs, _) => assert(false, i"bimap $toString should map $c to a capability, but result = $cs") /** Fuse with another map */ def fuse(next: BiTypeMap)(using Context): Option[TypeMap] = None end BiTypeMap + /** A typemap that follows aliases and keeps their transformed results if + * there is a change. + */ + trait FollowAliasesMap(using Context) extends TypeMap: + def mapFollowingAliases(t: Type): Type = + val t1 = t.dealiasKeepAnnots + if t1 ne t then + val t2 = apply(t1) + if t2 ne t1 then t2 + else t + else mapOver(t) + abstract class TypeMap(implicit protected var mapCtx: Context) extends VariantTraversal with (Type => Type) { thisMap => @@ -6251,6 +6294,48 @@ object Types extends TypeUtils { try derivedCapturingType(tp, this(parent), refs.map(this)) finally variance = saved + def toTrackableRef(tp: Type): Capability | Null = tp match + case CapturingType(_) => + null + case tp: CoreCapability => + if tp.isTrackableRef then tp + else toTrackableRef(tp.underlying) + case tp: TypeAlias => + toTrackableRef(tp.alias) + case _ => + null + + def mapCapability(c: Capability, deep: Boolean = false): Capability | (CaptureSet, Boolean) = c match + case c: RootCapability => c + case Reach(c1) => + mapCapability(c1, deep = true) + case Restricted(c1, cls) => + mapCapability(c1) match + case c2: Capability => c2.restrict(cls) + case (cs: CaptureSet, exact) => (cs.restrict(cls), exact) + case ReadOnly(c1) => + assert(!deep) + mapCapability(c1) match + case c2: Capability => c2.readOnly + case (cs: CaptureSet, exact) => (cs.readOnly, exact) + case Maybe(c1) => + assert(!deep) + mapCapability(c1) match + case c2: Capability => c2.maybe + case (cs: CaptureSet, exact) => (cs.maybe, exact) + case ref: CoreCapability => + val tp1 = apply(ref) + val ref1 = toTrackableRef(tp1) + if ref1 != null then + if deep then ref1.reach + else ref1 + else + val isLiteral = tp1.typeSymbol == defn.Caps_CapSet + val cs = + if deep && !isLiteral then CaptureSet.ofTypeDeeply(tp1) + else CaptureSet.ofType(tp1, followResult = false) + (cs, isLiteral) + /** Utility method. Maps the supertype of a type proxy. Returns the * type proxy itself if the mapping leaves the supertype unchanged. * This avoids needless changes in mapped types. @@ -6428,9 +6513,30 @@ object Types extends TypeUtils { abstract class ApproximatingTypeMap(using Context) extends TypeMap { thisMap => protected def range(lo: Type, hi: Type): Type = - if (variance > 0) hi - else if (variance < 0) lo - else if (lo `eq` hi) lo + if variance > 0 then hi + else if variance < 0 then + if (lo eq defn.NothingType) then + // Approximate by Nothing & hi instead of just Nothing, in case the + // approximated type is used as the prefix of another type (this would + // lead to a type with a `NoDenotation` denot and a possible + // MissingType in `TypeErasure#sigName`). + // + // Note that we cannot simply check for a `Nothing` prefix in + // `derivedSelect`, because the substitution might be done lazily (for + // example if Nothing is the type of a parameter being depended on in + // a MethodType) + // + // Test case in tests/pos/i23530.scala (and tests/pos/i23627.scala for + // the higher-kinded case which requires eta-expansion) + hi.etaExpand match + case expandedHi: HKTypeLambda => + expandedHi.derivedLambdaType(resType = AndType(lo, expandedHi.resType)) + case _ => + // simple-kinded case + AndType(lo, hi) + else + lo + else if lo `eq` hi then lo else Range(lower(lo), upper(hi)) protected def emptyRange = range(defn.NothingType, defn.AnyType) @@ -6824,7 +6930,8 @@ object Types extends TypeUtils { foldOver(x2, tp.cases) case CapturingType(parent, refs) => - (this(x, parent) /: refs.elems)(this) + (this(x, parent) /: refs.elems): (x, elem) => + this(x, elem.coreType) case AnnotatedType(underlying, annot) => this(applyToAnnot(x, annot), underlying) @@ -7084,6 +7191,15 @@ object Types extends TypeUtils { def isStable = true } + // ----- Dealias keep flags -------------------------------------------- + + private type Keeps = Int + + private val KeepNothing = 0 + private val KeepAnnots = 1 + private val KeepRefiningAnnots = 2 + private val KeepOpaques = 4 + // ----- Debug --------------------------------------------------------- @sharable var debugTrace: Boolean = false diff --git a/compiler/src/dotty/tools/dotc/core/Uniques.scala b/compiler/src/dotty/tools/dotc/core/Uniques.scala index da6b0aba88bd..b50905c22c98 100644 --- a/compiler/src/dotty/tools/dotc/core/Uniques.scala +++ b/compiler/src/dotty/tools/dotc/core/Uniques.scala @@ -3,6 +3,7 @@ package core import Types.*, Contexts.*, util.Stats.*, Hashable.*, Names.* import config.Config +import Symbols.Symbol import Decorators.* import util.{WeakHashSet, Stats} import WeakHashSet.Entry @@ -41,8 +42,10 @@ object Uniques: val h = doHash(null, designator, prefix) if monitored then recordCaching(h, classOf[NamedType]) def newType = - if (isTerm) new CachedTermRef(prefix, designator, h) - else new CachedTypeRef(prefix, designator, h) + try + if isTerm then new CachedTermRef(prefix, designator, h) + else new CachedTypeRef(prefix, designator, h) + catch case ex: InvalidPrefix => badPrefix(prefix, designator) if h == NotCached then newType else // Inlined from WeakHashSet#put @@ -61,6 +64,14 @@ object Uniques: linkedListLoop(oldHead) end if + end enterIfNew + + private def badPrefix(prefix: Type, desig: Designator)(using Context): Nothing = + def name = desig match + case desig: Name => desig + case desig: Symbol => desig.name + throw TypeError(em"invalid prefix $prefix when trying to form $prefix . $name") + end NamedTypeUniques final class AppliedUniques extends WeakHashSet[AppliedType](Config.initialUniquesCapacity * 2) with Hashable: diff --git a/compiler/src/dotty/tools/dotc/core/classfile/AbstractFileReader.scala b/compiler/src/dotty/tools/dotc/core/classfile/AbstractFileReader.scala index 46581d00714e..d4f781e22126 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/AbstractFileReader.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/AbstractFileReader.scala @@ -3,8 +3,6 @@ package dotc package core package classfile -import scala.language.unsafeNulls - import java.lang.Float.intBitsToFloat import java.lang.Double.longBitsToDouble import java.io.{ByteArrayInputStream, DataInputStream} diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index cfbdc854a88f..a1a4d56abb15 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -369,27 +369,21 @@ class ClassfileParser( * Updates the read pointer of 'in'. */ def parseParents: List[Type] = { val superType = - if (classRoot.symbol == defn.ComparableClass || - classRoot.symbol == defn.JavaCloneableClass || - classRoot.symbol == defn.JavaSerializableClass) { - // Treat these interfaces as universal traits - in.nextChar + val superClass = in.nextChar + // Treat these interfaces as universal traits + if classRoot.symbol == defn.ComparableClass + || classRoot.symbol == defn.JavaCloneableClass + || classRoot.symbol == defn.JavaSerializableClass + then defn.AnyType - } else - pool.getSuperClass(in.nextChar).typeRef + pool.getSuperClass(superClass).typeRef val ifaceCount = in.nextChar - var ifaces = for (i <- (0 until ifaceCount).toList) yield pool.getSuperClass(in.nextChar).typeRef - // Dotty deviation: was - // var ifaces = for (i <- List.range(0, ifaceCount)) ... - // This does not typecheck because the type parameter of List is now lower-bounded by Int | Char. - // Consequently, no best implicit for the "Integral" evidence parameter of "range" - // is found. Previously, this worked because of weak conformance, which has been dropped. - + val ifaces = List.fill(ifaceCount.toInt): + pool.getSuperClass(in.nextChar).typeRef superType :: ifaces } - val result = unpickleOrParseInnerClasses() if (!result.isDefined) { var classInfo: Type = TempClassInfoType(parseParents, instanceScope, classRoot.symbol) @@ -408,8 +402,8 @@ class ClassfileParser( moduleRoot.setPrivateWithin(privateWithin) moduleRoot.sourceModule.setPrivateWithin(privateWithin) - for (i <- 0 until in.nextChar) parseMember(method = false) - for (i <- 0 until in.nextChar) parseMember(method = true) + for (_ <- 0 until in.nextChar) parseMember(method = false) + for (_ <- 0 until in.nextChar) parseMember(method = true) classRoot.registerCompanion(moduleRoot.symbol) moduleRoot.registerCompanion(classRoot.symbol) diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ReusableDataReader.scala b/compiler/src/dotty/tools/dotc/core/classfile/ReusableDataReader.scala index e9bb7337c948..35716feb7a42 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ReusableDataReader.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ReusableDataReader.scala @@ -3,8 +3,6 @@ package dotc package core package classfile -import scala.language.unsafeNulls - import java.io.{DataInputStream, InputStream} import java.nio.{BufferUnderflowException, ByteBuffer} diff --git a/compiler/src/dotty/tools/dotc/core/tasty/AttributeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/AttributeUnpickler.scala index 43a2bea27216..c5770d69b002 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/AttributeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/AttributeUnpickler.scala @@ -1,7 +1,6 @@ package dotty.tools.dotc package core.tasty -import scala.language.unsafeNulls import scala.collection.immutable.BitSet import scala.collection.immutable.TreeMap diff --git a/compiler/src/dotty/tools/dotc/core/tasty/BestEffortTastyWriter.scala b/compiler/src/dotty/tools/dotc/core/tasty/BestEffortTastyWriter.scala index 13a6a274ed96..a027da8a8a29 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/BestEffortTastyWriter.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/BestEffortTastyWriter.scala @@ -2,7 +2,6 @@ package dotty.tools.dotc package core package tasty -import scala.language.unsafeNulls import java.nio.file.{Path as JPath, Files as JFiles} import java.nio.channels.ClosedByInterruptException import java.io.DataOutputStream diff --git a/compiler/src/dotty/tools/dotc/core/tasty/CommentUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/CommentUnpickler.scala index d3b5c647b9c5..42c572f0a50e 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/CommentUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/CommentUnpickler.scala @@ -1,8 +1,6 @@ package dotty.tools.dotc package core.tasty -import scala.language.unsafeNulls - import core.Comments.Comment import util.Spans.Span import util.HashMap diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala index e35ed5bb2466..6fc397efea5a 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala @@ -3,8 +3,6 @@ package dotc package core package tasty -import scala.language.unsafeNulls - import dotty.tools.tasty.{TastyBuffer, TastyFormat, TastyHash} import dotty.tools.tasty.besteffort.BestEffortTastyFormat import TastyFormat.* diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala index 72f6895f122c..df3ac411989b 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala @@ -53,14 +53,14 @@ object TastyPrinter: else if arg.endsWith(".tasty") || (allowBetasty && arg.endsWith(".betasty")) then val path = Paths.get(arg) if Files.exists(path) then - printTasty(arg, Files.readAllBytes(path).nn, arg.endsWith(".betasty")) + printTasty(arg, Files.readAllBytes(path), arg.endsWith(".betasty")) else println("File not found: " + arg) System.exit(1) else if arg.endsWith(".jar") then val jar = JarArchive.open(Path(arg), create = false) try - for file <- jar.iterator() if file.hasTastyExtension do + for file <- jar.iterator if file.hasTastyExtension do printTasty(s"$arg ${file.path}", file.toByteArray, isBestEffortTasty = false) finally jar.close() else @@ -123,7 +123,7 @@ class TastyPrinter(bytes: Array[Byte], isBestEffortTasty: Boolean, val testPickl unpickle0(new PositionSectionUnpickler(sb)) unpickle0(new CommentSectionUnpickler(sb)) unpickle0(new AttributesSectionUnpickler(sb)) - sb.result + sb.result() } def unpickle0[R](sec: PrinterSectionUnpickler[R])(using NameRefs): Option[R] = @@ -266,7 +266,7 @@ class TastyPrinter(bytes: Array[Byte], isBestEffortTasty: Boolean, val testPickl val value = nameAtRef(utf8Ref).toString sb.append(nameStr(s" ${utf8Ref.index} [$value]")) sb.append("\n") - sb.result + sb.result() } } @@ -295,7 +295,7 @@ class TastyPrinter(bytes: Array[Byte], isBestEffortTasty: Boolean, val testPickl for ((_, nameRef) <- sources.iterator) { buf += nameRef } - NameRefs(buf.result) + NameRefs(buf.result()) } } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala index f034f03298b1..6ce27b8b77af 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala @@ -3,8 +3,6 @@ package core package tasty import java.util.UUID -import scala.language.unsafeNulls - import dotty.tools.tasty.{TastyFormat, TastyVersion, TastyBuffer, TastyReader, TastyHeaderUnpickler, UnpicklerConfig} import dotty.tools.tasty.besteffort.{BestEffortTastyHeader, BestEffortTastyHeaderUnpickler} diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index cf9885d16d1f..4e63c7e973fe 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -716,8 +716,13 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { if passesConditionForErroringBestEffortCode(tree.hasType) then pickleType(tree.tpe) else pickleErrorType() case SingletonTypeTree(ref) => - writeByte(SINGLETONtpt) - pickleTree(ref) + val tp = ref.tpe + val tp1 = tp.deskolemized + if tp1 ne tp then + pickleType(tp1) + else + writeByte(SINGLETONtpt) + pickleTree(ref) case RefinedTypeTree(parent, refinements) => if (refinements.isEmpty) pickleTree(parent) else { @@ -852,6 +857,9 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { if (flags.is(ParamAccessor) && sym.isTerm && !sym.isSetter) flags = flags &~ ParamAccessor // we only generate a tag for parameter setters pickleFlags(flags, sym.isTerm) + if flags.is(Into) then + // Temporary measure until we can change TastyFormat to include an INTO tag + pickleAnnotation(sym, mdef, Annotation(defn.SilentIntoAnnot, util.Spans.NoSpan)) val annots = sym.annotations.foreach(pickleAnnotation(sym, mdef, _)) } @@ -958,7 +966,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { val it = mp.keysIterator var i = 0 while i < keys.length do - keys(i) = it.next + keys(i) = it.next() i += 1 assert(!it.hasNext) i = 0 diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index d6f2812dad0d..ad9d485b5ee5 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -148,7 +148,8 @@ class TreeUnpickler(reader: TastyReader, } } - class Completer(reader: TastyReader)(using @constructorOnly _ctx: Context) extends LazyType { + class Completer(reader: TastyReader)(using @constructorOnly _ctx: Context) + extends LazyType, CompleterWithCleanup { import reader.* val owner = ctx.owner val mode = ctx.mode @@ -168,6 +169,8 @@ class TreeUnpickler(reader: TastyReader, case ex: CyclicReference => throw ex case ex: AssertionError => fail(ex) case ex: Exception => fail(ex) + finally + cleanup() } class TreeReader(val reader: TastyReader) { @@ -667,7 +670,11 @@ class TreeUnpickler(reader: TastyReader, } val annotOwner = if sym.owner.isClass then newLocalDummy(sym.owner) else sym.owner - val annots = annotFns.map(_(annotOwner)) + var annots = annotFns.map(_(annotOwner)) + if annots.exists(_.hasSymbol(defn.SilentIntoAnnot)) then + // Temporary measure until we can change TastyFormat to include an INTO tag + sym.setFlag(Into) + annots = annots.filterNot(_.symbol == defn.SilentIntoAnnot) sym.annotations = annots if sym.isOpaqueAlias then sym.setFlag(Deferred) val isScala2MacroDefinedInScala3 = flags.is(Macro, butNot = Inline) && flags.is(Erased) @@ -933,7 +940,7 @@ class TreeUnpickler(reader: TastyReader, DefDef(paramDefss, tpt) case VALDEF => val tpt = readTpt()(using localCtx) - sym.info = tpt.tpe + sym.info = tpt.tpe.suppressIntoIfParam(sym) ValDef(tpt) case TYPEDEF | TYPEPARAM => if (sym.isClass) { @@ -978,7 +985,7 @@ class TreeUnpickler(reader: TastyReader, case PARAM => val tpt = readTpt()(using localCtx) assert(nothingButMods(end)) - sym.info = tpt.tpe + sym.info = tpt.tpe.suppressIntoIfParam(sym) ValDef(tpt) } goto(end) @@ -1186,7 +1193,6 @@ class TreeUnpickler(reader: TastyReader, inline def readImportOrExport(inline mkTree: (Tree, List[untpd.ImportSelector]) => Tree)()(using Context): Tree = { val start = currentAddr - assert(sourcePathAt(start).isEmpty) readByte() readEnd() val expr = readTree() diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 25245f5ca1b6..e97e73dc5760 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -63,9 +63,15 @@ object Scala2Unpickler { denot.info = PolyType.fromParams(denot.owner.typeParams, denot.info) } - def ensureConstructor(cls: ClassSymbol, clsDenot: ClassDenotation, scope: Scope)(using Context): Unit = { - if (scope.lookup(nme.CONSTRUCTOR) == NoSymbol) { - val constr = newDefaultConstructor(cls) + def ensureConstructor(cls: ClassSymbol, clsDenot: ClassDenotation, scope: Scope)(using Context): Unit = + doEnsureConstructor(cls, clsDenot, scope, fromScala2 = true) + + private def doEnsureConstructor(cls: ClassSymbol, clsDenot: ClassDenotation, scope: Scope, fromScala2: Boolean) + (using Context): Unit = + if scope.lookup(nme.CONSTRUCTOR) == NoSymbol then + val constr = + if fromScala2 || cls.isAllOf(Trait | JavaDefined) then newDefaultConstructor(cls) + else newConstructor(cls, Private, paramNames = Nil, paramTypes = Nil) // Scala 2 traits have a constructor iff they have initialization code // In dotc we represent that as !StableRealizable, which is also owner.is(NoInits) if clsDenot.flagsUNSAFE.is(Trait) then @@ -73,8 +79,6 @@ object Scala2Unpickler { clsDenot.setFlag(NoInits) addConstructorTypeParams(constr) cls.enter(constr, scope) - } - } def setClassInfo(denot: ClassDenotation, info: Type, fromScala2: Boolean, selfInfo: Type = NoType)(using Context): Unit = { val cls = denot.classSymbol @@ -108,7 +112,7 @@ object Scala2Unpickler { if (tsym.exists) tsym.setFlag(TypeParam) else denot.enter(tparam, decls) } - if (!denot.flagsUNSAFE.isAllOf(JavaModule)) ensureConstructor(cls, denot, decls) + if (!denot.flagsUNSAFE.isAllOf(JavaModule)) doEnsureConstructor(cls, denot, decls, fromScala2) val scalacCompanion = denot.classSymbol.scalacLinkedClass diff --git a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala index 3061bfa4ee5c..7df2e503e3f4 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala @@ -19,7 +19,7 @@ class Coverage: def addStatement(stmt: Statement): Unit = statementsById(stmt.id) = stmt - def removeStatementsFromFile(sourcePath: Path) = + def removeStatementsFromFile(sourcePath: Path | Null) = val removedIds = statements.filter(_.location.sourcePath == sourcePath).map(_.id.toLong) removedIds.foreach(statementsById.remove) diff --git a/compiler/src/dotty/tools/dotc/coverage/Location.scala b/compiler/src/dotty/tools/dotc/coverage/Location.scala index de1a3db710a3..de8eb7a607db 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Location.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Location.scala @@ -46,5 +46,5 @@ object Location: s"$packageName.$className", classType, methodName, - source.file.absolute.jpath + source.file.absolute.jpath.nn ) diff --git a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala index 26efa8934e00..de9c29965ded 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala @@ -3,7 +3,6 @@ package coverage import java.nio.file.{Path, Paths, Files} import java.io.Writer -import scala.language.unsafeNulls import scala.collection.mutable.StringBuilder /** diff --git a/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala b/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala index 16e7cc9c7adc..6fe6d4ec22f7 100644 --- a/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala +++ b/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala @@ -1,8 +1,6 @@ package dotty.tools.dotc package decompiler -import scala.language.unsafeNulls - import java.io.{OutputStream, PrintStream} import java.nio.charset.StandardCharsets @@ -26,8 +24,8 @@ class DecompilationPrinter extends Phase { if (ctx.settings.outputDir.isDefault) printToOutput(System.out) else { val outputDir = ctx.settings.outputDir.value - var os: OutputStream = null - var ps: PrintStream = null + var os: OutputStream|Null = null + var ps: PrintStream|Null = null try { os = File(outputDir.fileNamed("decompiled.scala").path)(Codec.UTF8).outputStream(append = true) ps = new PrintStream(os, /* autoFlush = */ false, StandardCharsets.UTF_8.name) diff --git a/compiler/src/dotty/tools/dotc/decompiler/IDEDecompilerDriver.scala b/compiler/src/dotty/tools/dotc/decompiler/IDEDecompilerDriver.scala index 18c5ceb5f346..29acc58c09f4 100644 --- a/compiler/src/dotty/tools/dotc/decompiler/IDEDecompilerDriver.scala +++ b/compiler/src/dotty/tools/dotc/decompiler/IDEDecompilerDriver.scala @@ -2,8 +2,6 @@ package dotty.tools package dotc package decompiler -import scala.language.unsafeNulls - import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.* import dotty.tools.dotc.core.tasty.TastyHTMLPrinter diff --git a/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala b/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala index 2e6b699b4e36..336d424e545d 100644 --- a/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala +++ b/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala @@ -2,8 +2,6 @@ package dotty.tools package dotc package fromtasty -import scala.language.unsafeNulls - import scala.util.control.NonFatal import dotty.tools.io.Directory diff --git a/compiler/src/dotty/tools/dotc/fromtasty/TASTYRun.scala b/compiler/src/dotty/tools/dotc/fromtasty/TASTYRun.scala index d01f60571601..4ed586e23249 100644 --- a/compiler/src/dotty/tools/dotc/fromtasty/TASTYRun.scala +++ b/compiler/src/dotty/tools/dotc/fromtasty/TASTYRun.scala @@ -2,8 +2,6 @@ package dotty.tools package dotc package fromtasty -import scala.language.unsafeNulls - import io.{JarArchive, AbstractFile, Path, FileExtension} import core.Contexts.* import core.Decorators.em diff --git a/compiler/src/dotty/tools/dotc/fromtasty/TastyFileUtil.scala b/compiler/src/dotty/tools/dotc/fromtasty/TastyFileUtil.scala index b1277accc621..21c603af89a3 100644 --- a/compiler/src/dotty/tools/dotc/fromtasty/TastyFileUtil.scala +++ b/compiler/src/dotty/tools/dotc/fromtasty/TastyFileUtil.scala @@ -1,8 +1,6 @@ package dotty.tools.dotc package fromtasty -import scala.language.unsafeNulls - import dotty.tools.dotc.core.tasty.TastyClassName import dotty.tools.dotc.core.StdNames.nme.EMPTY_PACKAGE import dotty.tools.io.AbstractFile @@ -17,7 +15,7 @@ object TastyFileUtil { * package foo * class Foo * ``` - * then `getClassName("./out/foo/Foo.tasty") returns `Some("./out")` + * then `getClassPath("./out/foo/Foo.tasty") returns `Some("./out")` */ def getClassPath(file: AbstractFile, fromBestEffortTasty: Boolean = false): Option[String] = getClassName(file, fromBestEffortTasty).map { className => @@ -35,19 +33,16 @@ object TastyFileUtil { * ``` * then `getClassName("./out/foo/Foo.tasty") returns `Some("foo.Foo")` */ - def getClassName(file: AbstractFile, withBestEffortTasty: Boolean = false): Option[String] = { + def getClassName(file: AbstractFile, withBestEffortTasty: Boolean = false): Option[String] = assert(file.exists) assert(file.hasTastyExtension || (withBestEffortTasty && file.hasBetastyExtension)) val bytes = file.toByteArray val names = new TastyClassName(bytes, file.hasBetastyExtension).readName() - names.map { case (packageName, className) => - val fullName = packageName match { - case EMPTY_PACKAGE => s"${className.lastPart}" - case _ => s"$packageName.${className.lastPart}" - } - fullName - } - } + names.map: (packageName, className) => + if packageName == EMPTY_PACKAGE then + s"${className.lastPart.encode}" + else + s"${packageName.encode}.${className.lastPart.encode}" } diff --git a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala index 3edb323e6b3b..3692df7560b2 100644 --- a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala +++ b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala @@ -172,6 +172,19 @@ class InlineReducer(inliner: Inliner)(using Context): val isImplicit = scrutinee.isEmpty + val unusable: util.EqHashSet[Symbol] = util.EqHashSet() + + /** Adjust internaly generated value definitions; + * - If the RHS refers to an erased symbol, mark the val as erased + * - If the RHS refers to an unusable symbol, mark the val as unusable + */ + def adjustErased(sym: TermSymbol, rhs: Tree): Unit = + rhs.foreachSubTree: + case id: Ident if id.symbol.isErased => + sym.setFlag(Erased) + if unusable.contains(id.symbol) then unusable += sym + case _ => + /** Try to match pattern `pat` against scrutinee reference `scrut`. If successful add * bindings for variables bound in this pattern to `caseBindingMap`. */ @@ -184,10 +197,11 @@ class InlineReducer(inliner: Inliner)(using Context): /** Create a binding of a pattern bound variable with matching part of * scrutinee as RHS and type that corresponds to RHS. */ - def newTermBinding(sym: TermSymbol, rhs: Tree): Unit = { - val copied = sym.copy(info = rhs.tpe.widenInlineScrutinee, coord = sym.coord, flags = sym.flags &~ Case).asTerm + def newTermBinding(sym: TermSymbol, rhs: Tree): Unit = + val copied = sym.copy(info = rhs.tpe.widenInlineScrutinee, coord = sym.coord, + flags = sym.flags &~ Case).asTerm + adjustErased(copied, rhs) caseBindingMap += ((sym, ValDef(copied, constToLiteral(rhs)).withSpan(sym.span))) - } def newTypeBinding(sym: TypeSymbol, alias: Type): Unit = { val copied = sym.copy(info = TypeAlias(alias), coord = sym.coord).asType @@ -306,6 +320,7 @@ class InlineReducer(inliner: Inliner)(using Context): case (Nil, Nil) => true case (pat :: pats1, selector :: selectors1) => val elem = newSym(InlineBinderName.fresh(), Synthetic, selector.tpe.widenInlineScrutinee).asTerm + adjustErased(elem, selector) val rhs = constToLiteral(selector) elem.defTree = rhs caseBindingMap += ((NoSymbol, ValDef(elem, rhs).withSpan(elem.span))) @@ -341,6 +356,19 @@ class InlineReducer(inliner: Inliner)(using Context): val scrutineeSym = newSym(InlineScrutineeName.fresh(), Synthetic, scrutType).asTerm val scrutineeBinding = normalizeBinding(ValDef(scrutineeSym, scrutinee)) + // If scrutinee has embedded references to `compiletime.erasedValue` or to + // other erased values, mark scrutineeSym as Erased. In addition, if scrutinee + // is not a pure expression, mark scrutineeSym as unusable. The reason is that + // scrutinee would then fail the tests in erasure that demand that the RHS of + // an erased val is a pure expression. At the end of the inline match reduction + // we throw out all unusable vals and check that the remaining code does not refer + // to unusable symbols. + // Note that compiletime.erasedValue is treated as erased but not pure, so scrutinees + // containing references to it becomes unusable. + if scrutinee.existsSubTree(_.symbol.isErased) then + scrutineeSym.setFlag(Erased) + if !tpd.isPureExpr(scrutinee) then unusable += scrutineeSym + def reduceCase(cdef: CaseDef): MatchReduxWithGuard = { val caseBindingMap = new mutable.ListBuffer[(Symbol, MemberDef)]() @@ -382,7 +410,25 @@ class InlineReducer(inliner: Inliner)(using Context): case _ => None } - recur(cases) + for (bindings, expr) <- recur(cases) yield + // drop unusable vals and check that no referenes to unusable symbols remain + val cleanupUnusable = new TreeMap: + override def transform(tree: Tree)(using Context): Tree = + tree match + case tree: ValDef if unusable.contains(tree.symbol) => EmptyTree + case id: Ident if unusable.contains(id.symbol) => + report.error( + em"""${id.symbol} is unusable in ${ctx.owner} because it refers to an erased expression + |in the selector of an inline match that reduces to + | + |${Block(bindings, expr)}""", + tree.srcPos) + tree + case _ => super.transform(tree) + + val bindings1 = bindings.mapConserve(cleanupUnusable.transform).collect: + case mdef: MemberDef => mdef + (bindings1, cleanupUnusable.transform(expr)) } end InlineReducer diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 41d3f5bbd676..2b2f012c688c 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -58,12 +58,12 @@ object Inliner: case Ident(_) => isPureRef(tree) || tree.symbol.isAllOf(InlineParam) case Select(qual, _) => - if (tree.symbol.is(Erased)) true + if tree.symbol.isErased then true else isPureRef(tree) && apply(qual) case New(_) | Closure(_, _, _) => true case TypeApply(fn, _) => - if (fn.symbol.is(Erased) || fn.symbol == defn.QuotedTypeModule_of) true else apply(fn) + if fn.symbol.isErased || fn.symbol == defn.QuotedTypeModule_of then true else apply(fn) case Apply(fn, args) => val isCaseClassApply = { val cls = tree.tpe.classSymbol @@ -165,28 +165,9 @@ 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 recType = RecType.closeOver ( recType => - mapRecTermRefReferences(recType, refinedType) - ) - - val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, recType, span) + val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, refinedType, span) refiningSym.termRef def unapply(refiningRef: TermRef)(using Context): Option[TermRef] = @@ -405,9 +386,6 @@ 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 */ @@ -435,15 +413,12 @@ class Inliner(val call: tpd.Tree)(using Context): for cls <- ref.widen.baseClasses do if cls.containsOpaques && (forThisProxy || inlinedMethod.isContainedIn(cls)) - && !visitedTermRefs.contains(ref) + && mapRef(ref).isEmpty then - visitedTermRefs += ref val refiningRef = OpaqueProxy(ref, cls, call.span) val refiningSym = refiningRef.symbol.asTerm val refinedType = refiningRef.info - val refiningDef = addProxiesForRecurrentOpaques( - ValDef(refiningSym, tpd.ref(ref).cast(refinedType), inferred = true).withSpan(span) - ) + val refiningDef = 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)) @@ -463,27 +438,6 @@ 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: @@ -729,7 +683,7 @@ class Inliner(val call: tpd.Tree)(using Context): // call. This way, a defensively written rewrite method can always // report bad inputs at the point of call instead of revealing its internals. val callToReport = if (enclosingInlineds.nonEmpty) enclosingInlineds.last else call - val ctxToReport = ctx.outersIterator.dropWhile(enclosingInlineds(using _).nonEmpty).next + val ctxToReport = ctx.outersIterator.dropWhile(enclosingInlineds(using _).nonEmpty).next() // The context in which we report should still use the existing context reporter val ctxOrigReporter = ctxToReport.fresh.setReporter(ctx.reporter) inContext(ctxOrigReporter) { @@ -1190,9 +1144,9 @@ class Inliner(val call: tpd.Tree)(using Context): else ctx.compilationUnit.suspend(hints.nn.toList.mkString(", ")) // this throws a SuspendException - val evaluatedSplice = inContext(quoted.MacroExpansion.context(inlinedFrom)) { - Splicer.splice(body, splicePos, inlinedFrom.srcPos, MacroClassLoader.fromContext) - } + val evaluatedSplice = + inContext(quoted.MacroExpansion.context(inlinedFrom)): + Splicer.splice(body, splicePos, inlinedFrom.srcPos, MacroClassLoader.fromContext) val inlinedNormalizer = new TreeMap { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match { case tree @ Inlined(_, Nil, expr) if tree.inlinedFromOuterScope && enclosingInlineds.isEmpty => transform(expr) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index a7269c83bccb..59386dd9bd4d 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -100,7 +100,7 @@ object Inlines: * @return An `Inlined` node that refers to the original call and the inlined bindings * and body that replace it. */ - def inlineCall(tree: Tree)(using Context): Tree = + def inlineCall(tree: Tree)(using Context): Tree = ctx.profiler.onInlineCall(tree.symbol): if tree.symbol.denot != SymDenotations.NoDenotation && tree.symbol.effectiveOwner == defn.CompiletimeTestingPackage.moduleClass then diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 36f6fa18e94b..7b2c5977fdbd 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -35,6 +35,8 @@ import dotty.tools.dotc.core.Constants import dotty.tools.dotc.core.TypeOps import dotty.tools.dotc.core.StdNames +import java.util.logging.Logger + /** * One of the results of a completion query. * @@ -48,14 +50,16 @@ case class Completion(label: String, description: String, symbols: List[Symbol]) object Completion: + private val logger = Logger.getLogger(this.getClass.getName) + def scopeContext(pos: SourcePosition)(using Context): CompletionResult = val tpdPath = Interactive.pathTo(ctx.compilationUnit.tpdTree, pos.span) val completionContext = Interactive.contextOfPath(tpdPath).withPhase(Phases.typerPhase) inContext(completionContext): val untpdPath = Interactive.resolveTypedOrUntypedPath(tpdPath, pos) - val mode = completionMode(untpdPath, pos, forSymbolSearch = true) val rawPrefix = completionPrefix(untpdPath, pos) - val completer = new Completer(mode, pos, untpdPath, _ => true) + // Lazy mode is to avoid too many checks as it's mostly for printing types + val completer = new Completer(Mode.Lazy, pos, untpdPath, _ => true) completer.scopeCompletions /** Get possible completions from tree at `pos` @@ -98,7 +102,7 @@ object Completion: * * Otherwise, provide no completion suggestion. */ - def completionMode(path: List[untpd.Tree], pos: SourcePosition, forSymbolSearch: Boolean = false): Mode = path match + 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) => @@ -111,14 +115,9 @@ object Completion: case untpd.Literal(Constants.Constant(_: String)) :: _ => Mode.Term | Mode.Scope // literal completions case (ref: untpd.RefTree) :: _ => val maybeSelectMembers = if ref.isInstanceOf[untpd.Select] then Mode.Member else Mode.Scope - if (forSymbolSearch) then Mode.Term | Mode.Type | maybeSelectMembers - else if (ref.name.isTermName) Mode.Term | maybeSelectMembers + if (ref.name.isTermName) Mode.Term | maybeSelectMembers else if (ref.name.isTypeName) Mode.Type | maybeSelectMembers else Mode.None - - case (_: tpd.TypeTree | _: tpd.MemberDef) :: _ if forSymbolSearch => Mode.Type | Mode.Term - case (_: tpd.CaseDef) :: _ if forSymbolSearch => Mode.Type | Mode.Term - case Nil if forSymbolSearch => Mode.Type | Mode.Term case _ => Mode.None /** When dealing with in varios palces we check to see if they are @@ -192,6 +191,15 @@ object Completion: Some(qual) case _ => None + private object NamedTupleSelection: + def unapply(path: List[tpd.Tree])(using Context): Option[tpd.Tree] = + path match + case (tpd.Apply(tpd.Apply(tpd.TypeApply(fun, _), List(qual)), _)) :: _ + if fun.symbol.exists && fun.symbol.name == nme.apply && + fun.symbol.owner.exists && fun.symbol.owner == defn.NamedTupleModule.moduleClass => + Some(qual) + case _ => None + /** Inspect `path` to determine the offset where the completion result should be inserted. */ def completionOffset(untpdPath: List[untpd.Tree]): Int = @@ -200,7 +208,7 @@ object Completion: case _ => 0 /** Handle case when cursor position is inside extension method construct. - * The extension method construct is then desugared into methods, and consturct parameters + * The extension method construct is then desugared into methods, and construct parameters * are no longer a part of a typed tree, but instead are prepended to method parameters. * * @param untpdPath The typed or untyped path to the tree that is being completed @@ -245,10 +253,9 @@ object Completion: case tpd.Select(qual @ tpd.This(_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions.names case StringContextApplication(qual) => completer.scopeCompletions.names ++ completer.selectionCompletions(qual) - case tpd.Select(qual, _) :: _ if qual.typeOpt.hasSimpleKind => - completer.selectionCompletions(qual) - case tpd.Select(qual, _) :: _ => Map.empty + case tpd.Select(qual, _) :: _ => completer.selectionCompletions(qual) case (tree: tpd.ImportOrExport) :: _ => completer.directMemberCompletions(tree.expr) + case NamedTupleSelection(qual) => completer.selectionCompletions(qual) case _ => completer.scopeCompletions.names interactiv.println(i"""completion info with pos = $pos, @@ -331,15 +338,15 @@ object Completion: * 8. symbol is not a constructor proxy module when in type completion mode * 9. have same term/type kind as name prefix given so far */ - def isValidCompletionSymbol(sym: Symbol, completionMode: Mode, isNew: Boolean)(using Context): Boolean = - + def isValidCompletionSymbol(sym: Symbol, completionMode: Mode, isNew: Boolean)(using Context): Boolean = try lazy val isEnum = sym.is(Enum) || (sym.companionClass.exists && sym.companionClass.is(Enum)) sym.exists && - !sym.isAbsent() && + !sym.isAbsent(canForce = false) && !sym.isPrimaryConstructor && - sym.sourceSymbol.exists && + // running sourceSymbol on ExportedTerm will force a lot of computation from collectSubTrees + (sym.is(ExportedTerm) || sym.sourceSymbol.exists) && (!sym.is(Package) || sym.is(ModuleClass)) && !sym.isAllOf(Mutable | Accessor) && !sym.isPackageObject && @@ -350,6 +357,9 @@ object Completion: (completionMode.is(Mode.Term) && (sym.isTerm || sym.is(ModuleClass)) || (completionMode.is(Mode.Type) && (sym.isType || sym.isStableMember))) ) + catch + case NonFatal(ex) => + false end isValidCompletionSymbol given ScopeOrdering(using Context): Ordering[Seq[SingleDenotation]] with @@ -370,7 +380,7 @@ object Completion: * For the results of all `xyzCompletions` methods term names and type names are always treated as different keys in the same map * and they never conflict with each other. */ - class Completer(val mode: Mode, pos: SourcePosition, untpdPath: List[untpd.Tree], matches: Name => Boolean): + class Completer(val mode: Mode, pos: SourcePosition, untpdPath: List[untpd.Tree], matches: Name => Boolean)(using Context): /** Completions for terms and types that are currently in scope: * the members of the current class, local definitions and the symbols that have been imported, * recursively adding completions from outer scopes. @@ -384,7 +394,7 @@ object Completion: * (even if the import follows it syntactically) * - a more deeply nested import shadowing a member or a local definition causes an ambiguity */ - def scopeCompletions(using context: Context): CompletionResult = + lazy val scopeCompletions: CompletionResult = /** Temporary data structure representing denotations with the same name introduced in a given scope * as a member of a type, by a local definition or by an import clause @@ -478,17 +488,16 @@ object Completion: def selectionCompletions(qual: tpd.Tree)(using Context): CompletionMap = val adjustedQual = widenQualifier(qual) - val implicitConversionMembers = implicitConversionMemberCompletions(adjustedQual) - val extensionMembers = extensionCompletions(adjustedQual) - val directMembers = directMemberCompletions(adjustedQual) - val namedTupleMembers = namedTupleCompletions(adjustedQual) + if qual.symbol.is(Package) then + directMemberCompletions(adjustedQual) + else if qual.typeOpt.hasSimpleKind then + implicitConversionMemberCompletions(adjustedQual) ++ + extensionCompletions(adjustedQual) ++ + directMemberCompletions(adjustedQual) ++ + namedTupleCompletions(adjustedQual) + else + Map.empty - List( - implicitConversionMembers, - extensionMembers, - directMembers, - namedTupleMembers - ).reduce(_ ++ _) /** Completions for members of `qual`'s type. * These include inherited definitions but not members added by extensions or implicit conversions @@ -596,12 +605,19 @@ object Completion: case _: MethodOrPoly => tpe case _ => ExprType(tpe) + // Try added due to https://github.com/scalameta/metals/issues/7872 def tryApplyingReceiverToExtension(termRef: TermRef): Option[SingleDenotation] = - ctx.typer.tryApplyingExtensionMethod(termRef, qual) - .map { tree => - val tpe = asDefLikeType(tree.typeOpt.dealias) - termRef.denot.asSingleDenotation.mapInfo(_ => tpe) - } + try + ctx.typer.tryApplyingExtensionMethod(termRef, qual) + .map { tree => + val tpe = asDefLikeType(tree.typeOpt.dealias) + termRef.denot.asSingleDenotation.mapInfo(_ => tpe) + } + catch case NonFatal(ex) => + logger.warning( + s"Exception when trying to apply extension method:\n ${ex.getMessage()}\n${ex.getStackTrace().mkString("\n")}" + ) + None def extractMemberExtensionMethods(types: Seq[Type]): Seq[(TermRef, TermName)] = object DenotWithMatchingName: @@ -619,10 +635,10 @@ object Completion: // There are four possible ways for an extension method to be applicable // 1. The extension method is visible under a simple name, by being defined or inherited or imported in a scope enclosing the reference. - val termCompleter = new Completer(Mode.Term, pos, untpdPath, matches) - val extMethodsInScope = termCompleter.scopeCompletions.names.toList.flatMap: - case (name, denots) => denots.collect: - case d: SymDenotation if d.isTerm && d.termRef.symbol.is(Extension) => (d.termRef, name.asTermName) + val extMethodsInScope = scopeCompletions.names.toList.flatMap: + case (name, denots) => + denots.collect: + case d if d.isTerm && d.symbol.is(Extension) => (d.symbol.termRef, name.asTermName) // 2. The extension method is a member of some given instance that is visible at the point of the reference. val givensInScope = ctx.implicits.eligible(defn.AnyType).map(_.implicitRef.underlyingRef) @@ -655,7 +671,7 @@ object Completion: private def include(denot: SingleDenotation, nameInScope: Name)(using Context): Boolean = matches(nameInScope) && completionsFilter(NoType, nameInScope) && - isValidCompletionSymbol(denot.symbol, mode, isNew) + (mode.is(Mode.Lazy) || isValidCompletionSymbol(denot.symbol, mode, isNew)) private def extractRefinements(site: Type)(using Context): Seq[SingleDenotation] = site match @@ -685,7 +701,7 @@ object Completion: val members = site.memberDenots(completionsFilter, appendMemberSyms).collect { case mbr if include(mbr, mbr.name) - && mbr.symbol.isAccessibleFrom(site) => mbr + && (mode.is(Mode.Lazy) || mbr.symbol.isAccessibleFrom(site)) => mbr } val refinements = extractRefinements(site).filter(mbr => include(mbr, mbr.name)) @@ -699,13 +715,17 @@ object Completion: * @param qual The argument to which the implicit conversion should be applied. * @return The set of types after `qual` implicit conversion. */ - private def implicitConversionTargets(qual: tpd.Tree)(using Context): Set[SearchSuccess] = { + private def implicitConversionTargets(qual: tpd.Tree)(using Context): Set[SearchSuccess] = try { val typer = ctx.typer val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span, Set.empty).allImplicits interactiv.println(i"implicit conversion targets considered: ${conversions.toList}%, %") conversions - } + } catch case NonFatal(ex) => + logger.warning( + s"Exception when searching for implicit conversions:\n ${ex.getMessage()}\n${ex.getStackTrace().mkString("\n")}" + ) + Set.empty /** Filter for names that should appear when looking for completions. */ private object completionsFilter extends NameFilter: @@ -748,3 +768,5 @@ object Completion: val Member: Mode = new Mode(16) + val Lazy: Mode = new Mode(32) + diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index 973fc2f3d79d..ff73761ad688 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -2,8 +2,6 @@ package dotty.tools package dotc package interactive -import scala.language.unsafeNulls - import scala.collection.* import ast.{NavigateAST, Trees, tpd, untpd} diff --git a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala index 673874ae2769..40c6667fdd24 100644 --- a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala +++ b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala @@ -234,7 +234,7 @@ class InteractiveDriver(val settings: List[String]) extends Driver { private def classesFromDir(dir: Path, buffer: mutable.ListBuffer[TypeName]): Unit = try Files.walkFileTree(dir, new SimpleFileVisitor[Path] { - override def visitFile(path: Path, attrs: BasicFileAttributes) = { + override def visitFile(path: Path, attrs: BasicFileAttributes): FileVisitResult = { if (!attrs.isDirectory) { val name = path.getFileName.toString if name.endsWith(tastySuffix) then diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index d1164d4742af..ef20eafe367f 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -879,9 +879,14 @@ object JavaParsers { fieldsByName -= name end for + // accessor for record's vararg field (T...) returns array type (T[]) + def adaptVarargsType(tpt: Tree) = tpt match + case PostfixOp(tpt2, Ident(tpnme.raw.STAR)) => arrayOf(tpt2) + case _ => tpt + val accessors = (for (name, (tpt, annots)) <- fieldsByName yield - DefDef(name, List(Nil), tpt, unimplementedExpr) + DefDef(name, List(Nil), adaptVarargsType(tpt), unimplementedExpr) .withMods(Modifiers(Flags.JavaDefined | Flags.Method | Flags.Synthetic)) ).toList diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala b/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala index 3f993195e4f3..64f586ca0410 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala @@ -666,7 +666,7 @@ object JavaScanners { val limit: Double = if (token == DOUBLELIT) Double.MaxValue else Float.MaxValue try { - val value: Double = java.lang.Double.valueOf(strVal.toString).nn.doubleValue() + val value: Double = java.lang.Double.valueOf(strVal.toString).doubleValue() if (value > limit) error(em"floating point number too large") if (negated) -value else value diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 3a43f53f3ca4..c9a3f04371a2 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -68,8 +68,8 @@ object Parsers { this == Given || this == ExtensionFollow def acceptsVariance = this == Class || this == CaseClass || this == Hk - def acceptsCtxBounds = - !(this == Type || this == Hk) + def acceptsCtxBounds(using Context) = + !(this == Type || this == Hk) || (sourceVersion.enablesNewGivens && this == Type) def acceptsWildcard = this == Type || this == Hk @@ -78,9 +78,6 @@ object Parsers { enum ParseKind: case Expr, Type, Pattern - enum IntoOK: - case Yes, No, Nested - type StageKind = Int object StageKind { val None = 0 @@ -217,9 +214,8 @@ object Parsers { def isIdent(name: Name) = in.isIdent(name) def isPureArrow(name: Name): Boolean = isIdent(name) && Feature.pureFunsEnabled def isPureArrow: Boolean = isPureArrow(nme.PUREARROW) || isPureArrow(nme.PURECTXARROW) - def isErased = isIdent(nme.erased) && in.erasedEnabled - // Are we seeing an `erased` soft keyword that will not be an identifier? - def isErasedKw = isErased && in.isSoftModifierInParamModifierPosition + def isErased = + isIdent(nme.erased) && in.erasedEnabled && in.isSoftModifierInParamModifierPosition def isSimpleLiteral = simpleLiteralTokens.contains(in.token) || isIdent(nme.raw.MINUS) && numericLitTokens.contains(in.lookahead.token) @@ -227,6 +223,7 @@ object Parsers { def isNumericLit = numericLitTokens contains in.token def isTemplateIntro = templateIntroTokens contains in.token def isDclIntro = dclIntroTokens contains in.token + def isDclIntroNext = dclIntroTokens contains in.lookahead.token def isStatSeqEnd = in.isNestedEnd || in.token == EOF || in.token == RPAREN def mustStartStat = mustStartStatTokens contains in.token @@ -395,10 +392,9 @@ object Parsers { syntaxError(em"""This construct is not allowed under $option.${rewriteNotice(`3.0-migration`, option)}""", span) def rewriteToNewSyntax(span: Span = Span(in.offset)): Boolean = { - if (in.newSyntax) { - if (in.rewrite) return true - syntaxVersionError("-new-syntax", span) - } + if in.newSyntax then + if in.rewrite then return true + syntaxVersionError("-new-syntax or -language:future", span) false } @@ -720,7 +716,7 @@ object Parsers { def checkNextNotIndented(): Unit = if in.isNewLine then val nextIndentWidth = in.indentWidth(in.next.offset) - if in.currentRegion.indentWidth < nextIndentWidth then + if in.currentRegion.indentWidth < nextIndentWidth && in.currentRegion.closedBy == OUTDENT then warning(em"Line is indented too far to the right, or a `{` or `:` is missing", in.next.offset) /* -------- REWRITES ----------------------------------------------------------- */ @@ -1331,16 +1327,6 @@ object Parsers { */ def qualId(): Tree = dotSelectors(termIdent()) - /** Singleton ::= SimpleRef - * | SimpleLiteral - * | Singleton ‘.’ id - * -- not yet | Singleton ‘(’ Singletons ‘)’ - * -- not yet | Singleton ‘[’ Types ‘]’ - */ - def singleton(): Tree = - if isSimpleLiteral then simpleLiteral() - else dotSelectors(simpleRef()) - /** SimpleLiteral ::= [‘-’] integerLiteral * | [‘-’] floatingPointLiteral * | booleanLiteral @@ -1591,8 +1577,8 @@ object Parsers { /** Same as [[typ]], but if this results in a wildcard it emits a syntax error and * returns a tree for type `Any` instead. */ - def toplevelTyp(intoOK: IntoOK = IntoOK.No, inContextBound: Boolean = false): Tree = - rejectWildcardType(typ(intoOK, inContextBound)) + def toplevelTyp(inContextBound: Boolean = false): Tree = + rejectWildcardType(typ(inContextBound)) private def getFunction(tree: Tree): Option[Function] = tree match { case Parens(tree1) => getFunction(tree1) @@ -1601,32 +1587,36 @@ object Parsers { case _ => None } - /** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`] [`.` rd] - * | [ { SimpleRef `.` } SimpleRef `.` ] id `^` + /** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`] [CapFilter] [`.` `rd`] -- under captureChecking + * CapFilter ::= `.` `as` `[` QualId `]` */ def captureRef(): Tree = - def derived(ref: Tree, name: TermName) = - in.nextToken() - atSpan(startOffset(ref)) { PostfixOp(ref, Ident(name)) } + def derived(ref: Tree): Tree = + atSpan(startOffset(ref)): + if in.isIdent(nme.raw.STAR) then + in.nextToken() + Annotated(ref, makeReachAnnot()) + else if in.isIdent(nme.rd) then + in.nextToken() + Annotated(ref, makeReadOnlyAnnot()) + else if in.isIdent(nme.only) then + in.nextToken() + Annotated(ref, makeOnlyAnnot(inBrackets(convertToTypeId(qualId())))) + else assert(false) def recur(ref: Tree): Tree = if in.token == DOT then in.nextToken() - if in.isIdent(nme.rd) then derived(ref, nme.CC_READONLY) + if in.isIdent(nme.rd) || in.isIdent(nme.only) then derived(ref) else recur(selector(ref)) else if in.isIdent(nme.raw.STAR) then - val reachRef = derived(ref, nme.CC_REACH) - if in.token == DOT && in.lookahead.isIdent(nme.rd) then + val reachRef = derived(ref) + val next = in.lookahead + if in.token == DOT && (next.isIdent(nme.rd) || next.isIdent(nme.only)) then in.nextToken() - derived(reachRef, nme.CC_READONLY) + derived(reachRef) else reachRef - else if isIdent(nme.UPARROW) then - in.nextToken() - atSpan(startOffset(ref)): - convertToTypeId(ref) match - case ref: RefTree => makeCapsOf(ref) - case ref => ref else ref recur(simpleRef()) @@ -1634,9 +1624,10 @@ object Parsers { /** CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` -- under captureChecking */ - def captureSet(): List[Tree] = inBraces { - if in.token == RBRACE then Nil else commaSeparated(captureRef) - } + def captureSet(): List[Tree] = + inBraces { + if in.token == RBRACE then Nil else commaSeparated(captureRef) + } def capturesAndResult(core: () => Tree): Tree = if Feature.ccEnabled && in.token == LBRACE && canStartCaptureSetContentsTokens.contains(in.lookahead.token) @@ -1650,28 +1641,19 @@ object Parsers { * | InfixType * FunType ::= (MonoFunType | PolyFunType) * MonoFunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type - * | (‘->’ | ‘?->’ ) [CaptureSet] Type -- under pureFunctions + * | (‘->’ | ‘?->’ ) [CaptureSet] Type -- under pureFunctions and captureChecking * PolyFunType ::= TypTypeParamClause '=>' Type - * | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions + * | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions and captureChecking * FunTypeArgs ::= InfixType * | `(' [ FunArgType {`,' FunArgType } ] `)' * | '(' [ TypedFunParam {',' TypedFunParam } ')' * MatchType ::= InfixType `match` <<< TypeCaseClauses >>> - * IntoType ::= [‘into’] IntoTargetType - * | ‘( IntoType ‘)’ - * IntoTargetType ::= Type - * | FunTypeArgs (‘=>’ | ‘?=>’) IntoType */ - def typ(intoOK: IntoOK = IntoOK.No, inContextBound: Boolean = false): Tree = + def typ(inContextBound: Boolean = false): Tree = val start = in.offset var imods = Modifiers() val erasedArgs: ListBuffer[Boolean] = ListBuffer() - def nestedIntoOK(token: Int) = - if token == TLARROW then IntoOK.No - else if intoOK == IntoOK.Nested then IntoOK.Yes - else intoOK - def functionRest(params: List[Tree]): Tree = val paramSpan = Span(start, in.lastOffset) atSpan(start, in.offset) { @@ -1700,9 +1682,8 @@ object Parsers { else accept(ARROW) - def resType() = typ(nestedIntoOK(token)) val resultType = - if isPure then capturesAndResult(resType) else resType() + if isPure then capturesAndResult(() => typ()) else typ() if token == TLARROW then for case ValDef(_, tpt, _) <- params do if isByNameType(tpt) then @@ -1737,12 +1718,6 @@ object Parsers { syntaxError(ErasedTypesCanOnlyBeFunctionTypes(), implicitKwPos(start)) t - def isIntoPrefix: Boolean = - intoOK == IntoOK.Yes - && in.isIdent(nme.into) - && in.featureEnabled(Feature.into) - && canStartTypeTokens.contains(in.lookahead.token) - def convertToElem(t: Tree): Tree = t match case ByNameTypeTree(t1) => syntaxError(ByNameParameterNotSupported(t), t.span) @@ -1759,8 +1734,8 @@ object Parsers { else val paramStart = in.offset def addErased() = - erasedArgs.addOne(isErasedKw) - if isErasedKw then in.skipToken() + erasedArgs.addOne(isErased) + if isErased then in.skipToken() addErased() val args = in.currentRegion.withCommasExpected: @@ -1779,32 +1754,6 @@ object Parsers { funArgType() commaSeparatedRest(t, funArg) accept(RPAREN) - - val intoAllowed = - intoOK == IntoOK.Yes - && args.lengthCompare(1) == 0 - && (!canFollowSimpleTypeTokens.contains(in.token) || followingIsVararg()) - val byNameAllowed = in.isArrow || isPureArrow - - def sanitize(arg: Tree): Tree = arg match - case ByNameTypeTree(t) if !byNameAllowed => - syntaxError(ByNameParameterNotSupported(t), t.span) - t - case PrefixOp(id @ Ident(tpnme.into), t) if !intoAllowed => - syntaxError(em"no `into` modifier allowed here", id.span) - t - case Parens(t) => - cpy.Parens(arg)(sanitize(t)) - case arg: FunctionWithMods => - val body1 = sanitize(arg.body) - if body1 eq arg.body then arg - else FunctionWithMods(arg.args, body1, arg.mods, arg.erasedParams).withSpan(arg.span) - case Function(args, res) if !intoAllowed => - cpy.Function(arg)(args, sanitize(res)) - case arg => - arg - val args1 = args.mapConserve(sanitize) - if in.isArrow || isPureArrow || erasedArgs.contains(true) then functionRest(args) else @@ -1825,15 +1774,13 @@ object Parsers { LambdaTypeTree(tparams.mapConserve(stripContextBounds("type lambdas")), toplevelTyp()) else if in.token == ARROW || isPureArrow(nme.PUREARROW) then val arrowOffset = in.skipToken() - val body = toplevelTyp(nestedIntoOK(in.token)) + val body = toplevelTyp() makePolyFunction(tparams, body, "type", Ident(nme.ERROR.toTypeName), start, arrowOffset) else accept(TLARROW) typ() else if in.token == INDENT then enclosed(INDENT, typ()) - else if isIntoPrefix then - PrefixOp(typeIdent(), typ(IntoOK.Nested)) else typeRest(infixType(inContextBound)) end typ @@ -1895,7 +1842,7 @@ object Parsers { if in.token == LPAREN then funParamClause() :: funParamClauses() else Nil /** InfixType ::= RefinedType {id [nl] RefinedType} - * | RefinedType `^` // under capture checking + * | RefinedType `^` -- under captureChecking */ def infixType(inContextBound: Boolean = false): Tree = infixTypeRest(inContextBound)(refinedType()) @@ -1926,6 +1873,12 @@ object Parsers { || !canStartInfixTypeTokens.contains(ahead.token) || ahead.lineOffset > 0 + inline def gobbleHat(): Boolean = + if Feature.ccEnabled && isIdent(nme.UPARROW) then + in.nextToken() + true + else false + def refinedTypeRest(t: Tree): Tree = { argumentStart() if in.isNestedStart then @@ -2051,7 +2004,7 @@ object Parsers { /** SimpleType ::= SimpleLiteral * | ‘?’ TypeBounds * | SimpleType1 - * | SimpleType ‘(’ Singletons ‘)’ -- under language.experimental.dependent, checked in Typer + * | SimpleType ‘(’ Singletons ‘)’ * Singletons ::= Singleton {‘,’ Singleton} */ def simpleType(): Tree = @@ -2063,15 +2016,19 @@ object Parsers { Ident(tpnme.USCOREkw).withSpan(Span(start, in.lastOffset, start)) else if !inMatchPattern then - report.errorOrMigrationWarning( - em"`_` is deprecated for wildcard arguments of types: use `?` instead${rewriteNotice(`3.4-migration`)}", - in.sourcePos(), - MigrationVersion.WildcardType) - if MigrationVersion.WildcardType.needsPatch then - patch(source, Span(in.offset, in.offset + 1), "?") - end if + val msg = + em"`_` is deprecated for wildcard arguments of types: use `?` instead${rewriteNotice(`3.4-migration`)}" + report.errorOrMigrationWarning(msg, in.sourcePos(), MigrationVersion.WildcardType) val start = in.skipToken() typeBounds().withSpan(Span(start, in.lastOffset, start)) + .tap: tbt => + if !inMatchPattern && MigrationVersion.WildcardType.needsPatch then + val offset_? = tbt.span.start + if Chars.isOperatorPart(source(offset_? + 1)) then + patch(source, tbt.span, "?" + ctx.printer.toText(tbt).mkString()) + else + patch(source, Span(offset_?, offset_? + 1), "?") + // Allow symbols -_ and +_ through for compatibility with code written using kind-projector in Scala 3 underscore mode. // While these signify variant type parameters in Scala 2 + kind-projector, we ignore their variance markers since variance is inferred. else if (isIdent(nme.MINUS) || isIdent(nme.PLUS)) && in.lookahead.token == USCORE && ctx.settings.XkindProjector.value == "underscores" then @@ -2083,11 +2040,11 @@ object Parsers { val start = in.skipToken() typeBounds().withSpan(Span(start, in.lastOffset, start)) else - def singletonArgs(t: Tree): Tree = - if in.token == LPAREN && in.featureEnabled(Feature.dependent) - then singletonArgs(AppliedTypeTree(t, inParensWithCommas(commaSeparated(singleton)))) - else t - singletonArgs(simpleType1()) + val tpt = simpleType1() + if in.featureEnabled(Feature.modularity) && in.token == LPAREN then + parArgumentExprss(wrapNew(tpt)) + else + tpt /** SimpleType1 ::= id * | Singleton `.' id @@ -2182,35 +2139,45 @@ object Parsers { atSpan(startOffset(t), startOffset(id)) { Select(t, id.name) } } - /** ArgTypes ::= Type {`,' Type} - * | NamedTypeArg {`,' NamedTypeArg} - * NamedTypeArg ::= id `=' Type + /** ArgTypes ::= TypeArg {‘,’ TypeArg} + * | NamedTypeArg {‘,’ NamedTypeArg} + * TypeArg ::= Type + * | CaptureSet -- under captureChecking + * NamedTypeArg ::= id ‘=’ TypeArg * NamesAndTypes ::= NameAndType {‘,’ NameAndType} - * NameAndType ::= id ':' Type + * NameAndType ::= id ‘:’ Type */ def argTypes(namedOK: Boolean, wildOK: Boolean, tupleOK: Boolean): List[Tree] = - def argType() = - val t = typ() + def wildCardCheck(gen: Tree): Tree = + val t = gen if wildOK then t else rejectWildcardType(t) - def namedArgType() = + def argType() = wildCardCheck(typ()) + + def typeArg() = wildCardCheck: + if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then // is this a capture set and not a refinement type? + // This case is ambiguous w.r.t. an Object literal {}. But since CC is enabled, we probably expect it to designate the empty set + concreteCapsType(captureSet()) + else typ() + + def namedTypeArg() = atSpan(in.offset): val name = ident() accept(EQUALS) - NamedArg(name.toTypeName, argType()) + NamedArg(name.toTypeName, typeArg()) - def namedElem() = + def nameAndType() = atSpan(in.offset): val name = ident() acceptColon() NamedArg(name, argType()) - if namedOK && isIdent && in.lookahead.token == EQUALS then - commaSeparated(() => namedArgType()) + if namedOK && (isIdent && in.lookahead.token == EQUALS) then + commaSeparated(() => namedTypeArg()) else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.enablesNamedTuples then - commaSeparated(() => namedElem()) + commaSeparated(() => nameAndType()) else - commaSeparated(() => argType()) + commaSeparated(() => typeArg()) end argTypes def paramTypeOf(core: () => Tree): Tree = @@ -2227,9 +2194,7 @@ object Parsers { * | `=>' Type * | `->' [CaptureSet] Type */ - val funArgType: () => Tree = - () => paramTypeOf(() => typ(IntoOK.Yes)) - // We allow intoOK and filter out afterwards in typ() + val funArgType: () => Tree = () => paramTypeOf(() => typ()) /** ParamType ::= ParamValueType * | `=>' ParamValueType @@ -2238,23 +2203,16 @@ object Parsers { def paramType(): Tree = paramTypeOf(paramValueType) /** ParamValueType ::= Type [`*'] - * | IntoType - * | ‘(’ IntoType ‘)’ `*' */ def paramValueType(): Tree = - val t = toplevelTyp(IntoOK.Yes) + val t = toplevelTyp() if isIdent(nme.raw.STAR) then - if !t.isInstanceOf[Parens] && isInto(t) then - syntaxError( - em"""`*` cannot directly follow `into` parameter - |the `into` parameter needs to be put in parentheses""", - in.offset) in.nextToken() atSpan(startOffset(t)): PostfixOp(t, Ident(tpnme.raw.STAR)) else t - /** TypeArgs ::= `[' Type {`,' Type} `]' + /** TypeArgs ::= `[' TypeArg {`,' TypeArg} `]' * NamedTypeArgs ::= `[' NamedTypeArg {`,' NamedTypeArg} `]' */ def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] = @@ -2268,21 +2226,28 @@ object Parsers { else inBraces(refineStatSeq()) - /** TypeBounds ::= [`>:' Type] [`<:' Type] - * | `^` -- under captureChecking + /** TypeBounds ::= [`>:' TypeBound ] [`<:' TypeBound ] + * TypeBound ::= Type + * | CaptureSet -- under captureChecking */ def typeBounds(): TypeBoundsTree = atSpan(in.offset): - if in.isIdent(nme.UPARROW) && Feature.ccEnabled then - in.nextToken() - makeCapsBound() - else - TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE)) + TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE)) private def bound(tok: Int): Tree = - if (in.token == tok) { in.nextToken(); toplevelTyp() } + if in.token == tok then + in.nextToken() + if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then + capsBound(captureSet(), isLowerBound = tok == SUPERTYPE) + else toplevelTyp() else EmptyTree + private def capsBound(refs: List[Tree], isLowerBound: Boolean = false): Tree = + if isLowerBound && refs.isEmpty then // lower bounds with empty capture sets become a pure CapSet + Select(scalaDot(nme.caps), tpnme.CapSet) + else + concreteCapsType(refs) + /** TypeAndCtxBounds ::= TypeBounds [`:` ContextBounds] */ def typeAndCtxBounds(pname: TypeName): Tree = { @@ -2405,6 +2370,7 @@ object Parsers { * | ForExpr * | [SimpleExpr `.'] id `=' Expr * | PrefixOperator SimpleExpr `=' Expr + * | InfixExpr id [nl] `=' Expr -- only if language.postfixOps is enabled * | SimpleExpr1 ArgumentExprs `=' Expr * | PostfixExpr [Ascription] * | ‘inline’ InfixExpr MatchClause @@ -2425,6 +2391,9 @@ object Parsers { val start = in.offset in.token match case IMPLICIT => + report.errorOrMigrationWarning( + em"`implicit` lambdas are no longer supported, use a lambda with `?=>` instead", + in.sourcePos(), MigrationVersion.Scala2Implicits) closure(start, location, modifiers(BitSet(IMPLICIT))) case LBRACKET => val start = in.offset @@ -2557,7 +2526,7 @@ object Parsers { def expr1Rest(t: Tree, location: Location): Tree = if in.token == EQUALS then t match - case Ident(_) | Select(_, _) | Apply(_, _) | PrefixOp(_, _) => + case Ident(_) | Select(_, _) | Apply(_, _) | PrefixOp(_, _) | PostfixOp(_, _) => atSpan(startOffset(t), in.skipToken()) { val loc = if location.inArgs then location else Location.ElseWhere Assign(t, subPart(() => expr(loc))) @@ -2604,7 +2573,7 @@ object Parsers { } } - /** `if' `(' Expr `)' {nl} Expr [[semi] else Expr] + /** `if' `(' Expr `)' {nl} Expr [[semi] else Expr] -- Scala 2 compat * `if' Expr `then' Expr [[semi] else Expr] */ def ifExpr(start: Offset, mkIf: (Tree, Tree, Tree) => If): If = @@ -2670,7 +2639,7 @@ object Parsers { */ def binding(mods: Modifiers): Tree = atSpan(in.offset) { - val mods1 = if isErasedKw then addModifier(mods) else mods + val mods1 = if isErased then addModifier(mods) else mods makeParameter(bindingName(), typedOpt(), mods1) } @@ -2873,7 +2842,7 @@ object Parsers { else in.currentRegion.withCommasExpected { var isFormalParams = false def exprOrBinding() = - if isErasedKw then isFormalParams = true + if isErased then isFormalParams = true if isFormalParams then binding(Modifiers()) else val t = maybeNamed(exprInParens)() @@ -3187,14 +3156,18 @@ object Parsers { def pattern1(location: Location = Location.InPattern): Tree = val p = pattern2(location) if in.isColon then - val isVariableOrNumber = isVarPattern(p) || p.isInstanceOf[Number] + val isVariable = unsplice(p) match { + case x: Ident => x.name.isVarPattern + case _ => false + } + val isVariableOrNumber = isVariable || p.isInstanceOf[Number] if !isVariableOrNumber then report.errorOrMigrationWarning( em"""Type ascriptions after patterns other than: | * variable pattern, e.g. `case x: String =>` | * number literal pattern, e.g. `case 10.5: Double =>` |are no longer supported. Remove the type ascription or move it to a separate variable pattern.""", - in.sourcePos(), + p.sourcePos, MigrationVersion.AscriptionAfterPattern) in.nextToken() ascription(p, location) @@ -3328,13 +3301,14 @@ object Parsers { case IDENTIFIER => name match { case nme.inline => Mod.Inline() + case nme.into => Mod.Into() case nme.opaque => Mod.Opaque() case nme.open => Mod.Open() case nme.transparent => Mod.Transparent() case nme.infix => Mod.Infix() case nme.tracked => Mod.Tracked() case nme.erased if in.erasedEnabled => Mod.Erased() - case nme.mut if Feature.ccEnabled => Mod.Mut() + case nme.update if Feature.ccEnabled => Mod.Update() } } @@ -3403,7 +3377,7 @@ object Parsers { * | opaque * LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased | * inline | transparent | infix | - * mut -- under cc + * mut -- under captureChecking */ def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = { @tailrec @@ -3492,22 +3466,25 @@ object Parsers { recur(numLeadParams, firstClause = true, prevIsTypeClause = false) end typeOrTermParamClauses - /** ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ - * ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] - * id [HkTypeParamClause] TypeAndCtxBounds + * ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] + * id [HkTypeParamClause] TypeAndCtxBounds + * | {Annotation} [‘+’ | ‘-’] id `^` TypeAndCtxBounds -- under captureChecking * * DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ - * DefTypeParam ::= {Annotation} - * id [HkTypeParamClause] TypeAndCtxBounds + * DefTypeParam ::= {Annotation} + * id [HkTypeParamClause] TypeAndCtxBounds + * | {Annotation} id `^` TypeAndCtxBounds -- under captureChecking * * TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ - * TypTypeParam ::= {Annotation} - * (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds + * TypTypeParam ::= {Annotation} + * (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds + * | {Annotation} (id | ‘_’) `^` TypeAndCtxBounds -- under captureChecking * * HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ - * HkTypeParam ::= {Annotation} [‘+’ | ‘-’] - * (id | ‘_’) [HkTypeParamClause] TypeBounds + * HkTypeParam ::= {Annotation} [‘+’ | ‘-’] + * (id | ‘_’) [HkTypePamClause] TypeBounds + * | {Annotation} [‘+’ | ‘-’] (id | ‘_’) `^` TypeBounds -- under captureChecking */ def typeParamClause(paramOwner: ParamOwner): List[TypeDef] = inBracketsWithCommas { @@ -3532,12 +3509,20 @@ object Parsers { in.nextToken() WildcardParamName.fresh().toTypeName else ident().toTypeName + val isCap = gobbleHat() val hkparams = typeParamClauseOpt(ParamOwner.Hk) - val bounds = - if paramOwner.acceptsCtxBounds then typeAndCtxBounds(name) - else if sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then typeAndCtxBounds(name) - else typeBounds() - TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods) + val bounds = typeAndCtxBounds(name) match + case bounds: TypeBoundsTree => bounds + case bounds: ContextBounds if paramOwner.acceptsCtxBounds => bounds + case ContextBounds(bounds, cxBounds) => + for cbound <- cxBounds do report.error(IllegalContextBounds(), cbound.srcPos) + bounds + val res = TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods) + if isCap then + res.pushAttachment(CaptureVar, ()) + // putting the attachment here as well makes post-processing in the typer easier + bounds.pushAttachment(CaptureVar, ()) + res } } commaSeparated(() => typeParam()) @@ -3589,7 +3574,17 @@ object Parsers { def paramMods() = if in.token == IMPLICIT then - addParamMod(() => Mod.Implicit()) + report.errorOrMigrationWarning( + em"`implicit` parameters are no longer supported, use a `using` clause instead${rewriteNotice(`future-migration`)}", + in.sourcePos(), MigrationVersion.Scala2Implicits) + val startImplicit = in.offset + addParamMod(() => + if ctx.settings.YimplicitToGiven.value then + patch(Span(in.lastOffset - 8, in.lastOffset), "using") + Mod.Implicit() + ) + if MigrationVersion.Scala2Implicits.needsPatch then + patch(source, Span(startImplicit, in.lastOffset), "using") else if isIdent(nme.using) then if initialMods.is(Given) then syntaxError(em"`using` is already implied here, should not be given explicitly", in.offset) @@ -3598,7 +3593,7 @@ object Parsers { def param(): ValDef = { val start = in.offset var mods = impliedMods.withAnnotations(annotations()) - if isErasedKw then + if isErased then mods = addModifier(mods) if paramOwner.isClass then mods = addFlag(modifiers(start = mods), ParamAccessor) @@ -3652,7 +3647,10 @@ object Parsers { // begin termParamClause inParensWithCommas { if in.token == RPAREN && paramOwner != ParamOwner.ExtensionPrefix && !impliedMods.is(Given) - then Nil + then + if paramOwner.takesOnlyUsingClauses then + syntaxError(em"`using` expected") + Nil else val clause = if paramOwner == ParamOwner.ExtensionPrefix @@ -4058,12 +4056,22 @@ object Parsers { argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs())) } - /** TypeDef ::= id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ Type] + /** TypeDef ::= id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ TypeDefRHS ] + * | id `^` TypeAndCtxBounds [‘=’ TypeDefRHS ] -- under captureChecking + * TypeDefRHS ::= Type + * | CaptureSet -- under captureChecking */ def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = { + + def typeDefRHS(): Tree = + if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then + concreteCapsType(captureSet()) + else toplevelTyp() + newLinesOpt() atSpan(start, nameStart) { val nameIdent = typeIdent() + val isCapDef = gobbleHat() val tname = nameIdent.name.asTypeName val tparams = typeParamClauseOpt(ParamOwner.Hk) val vparamss = funParamClauses() @@ -4071,20 +4079,24 @@ object Parsers { def makeTypeDef(rhs: Tree): Tree = { val rhs1 = lambdaAbstractAll(tparams :: vparamss, rhs) val tdef = TypeDef(nameIdent.name.toTypeName, rhs1) - if (nameIdent.isBackquoted) + if nameIdent.isBackquoted then tdef.pushAttachment(Backquoted, ()) + if isCapDef then + tdef.pushAttachment(CaptureVar, ()) + // putting the attachment here as well makes post-processing in the typer easier + rhs.pushAttachment(CaptureVar, ()) finalizeDef(tdef, mods, start) } in.token match { case EQUALS => in.nextToken() - makeTypeDef(toplevelTyp()) + makeTypeDef(typeDefRHS()) case SUBTYPE | SUPERTYPE => typeAndCtxBounds(tname) match case bounds: TypeBoundsTree if in.token == EQUALS => val eqOffset = in.skipToken() - var rhs = toplevelTyp() + var rhs = typeDefRHS() rhs match { case mtt: MatchTypeTree => bounds match { @@ -4113,6 +4125,9 @@ object Parsers { } } + private def concreteCapsType(refs: List[Tree]): Tree = + makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, tpnme.retains) + /** TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef * | [‘case’] ‘object’ ObjectDef * | ‘enum’ EnumDef @@ -4374,7 +4389,7 @@ object Parsers { accept(EQUALS) mods1 |= Final if !hasParams && !mods.is(Inline) then - mods1 |= Lazy + if !mods.is(Erased) then mods1 |= Lazy ValDef(name, parents.head, subExpr()) else DefDef(name, adjustDefParams(joinParams(tparams, vparamss)), parents.head, subExpr()) @@ -4430,7 +4445,10 @@ object Parsers { leadParamss += extParams isUsingClause(extParams) do () - leadParamss ++= termParamClauses(ParamOwner.ExtensionFollow, numLeadParams) + // Empty parameter clauses are filtered out. They are already reported as syntax errors and are not + // allowed here. + val extFollowParams = termParamClauses(ParamOwner.ExtensionFollow, numLeadParams).filterNot(_.isEmpty) + leadParamss ++= extFollowParams if in.isColon then syntaxError(em"no `:` expected here") in.nextToken() @@ -4717,7 +4735,7 @@ object Parsers { Nil tree match case tree: MemberDef - if !(tree.mods.flags & ModifierFlags).isEmpty && !tree.mods.isMutableVar => // vars are OK, mut defs are not + if !(tree.mods.flags & ModifierFlags).isEmpty && !tree.mods.isMutableVar => // vars are OK, update defs are not fail(em"refinement cannot be ${(tree.mods.flags & ModifierFlags).flagStrings().mkString("`", "`, `", "`")}") case tree: DefDef if tree.termParamss.nestedExists(!_.rhs.isEmpty) => fail(em"refinement cannot have default arguments") @@ -4774,6 +4792,9 @@ object Parsers { else if (isExprIntro) stats += expr(Location.InBlock) else if in.token == IMPLICIT && !in.inModifierPosition() then + report.errorOrMigrationWarning( + em"`implicit` lambdas are no longer supported, use a lambda with `?=>` instead", + in.sourcePos(), MigrationVersion.Scala2Implicits) stats += closure(in.offset, Location.InBlock, modifiers(BitSet(IMPLICIT))) else if isIdent(nme.extension) && followingIsExtension() then stats += extension() diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 31f074c3f633..ca2f5da3df3c 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 +import config.Feature.{migrateTo3, sourceVersion} import config.SourceVersion.{`3.0`, `3.0-migration`} import config.MigrationVersion import reporting.{NoProfile, Profile, Message} @@ -184,7 +184,7 @@ object Scanners { val rewrite = ctx.settings.rewrite.value.isDefined val oldSyntax = ctx.settings.oldSyntax.value - val newSyntax = ctx.settings.newSyntax.value + val newSyntax = ctx.settings.newSyntax.value || sourceVersion.requiresNewSyntax val rewriteToIndent = ctx.settings.indent.value && rewrite val rewriteNoIndent = ctx.settings.noindent.value && rewrite @@ -1148,7 +1148,7 @@ object Scanners { val lookahead = LookaheadScanner() while lookahead.nextToken() - lookahead.isNewLine || lookahead.isSoftModifier + lookahead.token == NEWLINE || lookahead.isSoftModifier do () modifierFollowers.contains(lookahead.token) } @@ -1228,7 +1228,7 @@ object Scanners { && (softModifierNames.contains(name) || name == nme.erased && erasedEnabled || name == nme.tracked && trackedEnabled - || name == nme.mut && Feature.ccEnabled) + || name == nme.update && Feature.ccEnabled) def isSoftModifierInModifierPosition: Boolean = isSoftModifier && inModifierPosition() @@ -1236,8 +1236,6 @@ object Scanners { def isSoftModifierInParamModifierPosition: Boolean = isSoftModifier && !lookahead.isColon - def isErased: Boolean = isIdent(nme.erased) && erasedEnabled - def canStartStatTokens = if migrateTo3 then canStartStatTokens2 else canStartStatTokens3 diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index bc55371ec96a..d47e6dab005f 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -299,7 +299,7 @@ object Tokens extends TokensCommon { final val closingParens = BitSet(RPAREN, RBRACKET, RBRACE) - final val softModifierNames = Set(nme.inline, nme.opaque, nme.open, nme.transparent, nme.infix) + final val softModifierNames = Set(nme.inline, nme.into, nme.opaque, nme.open, nme.transparent, nme.infix, nme.update) def showTokenDetailed(token: Int): String = debugString(token) diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index a6672d475129..f3a58cde1a0c 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -1,8 +1,6 @@ package dotty.tools.dotc package plugins -import scala.language.unsafeNulls - import core.* import Contexts.* import Decorators.em @@ -40,20 +38,19 @@ trait Plugins { // Explicit parameterization of recover to avoid -Xlint warning about inferred Any errors foreach (_.recover[Any] { // legacy behavior ignores altogether, so at least warn devs - case e: MissingPluginException => report.warning(e.getMessage.nn) - case e: Exception => report.inform(e.getMessage.nn) + case e: MissingPluginException => report.warning(e.getMessage) + case e: Exception => report.inform(e.getMessage) }) goods map (_.get) } - private var _roughPluginsList: List[Plugin] = uninitialized + private var _roughPluginsList: List[Plugin] | Null = null protected def roughPluginsList(using Context): List[Plugin] = if (_roughPluginsList == null) { _roughPluginsList = loadRoughPluginsList - _roughPluginsList } - else _roughPluginsList + _roughPluginsList.nn /** Load all available plugins. Skips plugins that * either have the same name as another one, or which @@ -99,13 +96,12 @@ trait Plugins { plugs } - private var _plugins: List[Plugin] = uninitialized + private var _plugins: List[Plugin] | Null = null def plugins(using Context): List[Plugin] = if (_plugins == null) { _plugins = loadPlugins - _plugins } - else _plugins + _plugins.nn /** A description of all the plugins that are loaded */ def pluginDescriptions(using Context): String = diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index 741b997d9926..daf4e70ad25a 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -13,6 +13,17 @@ import Highlighting.* object Formatting { + /** Essentially, a function Context => T, which can be created with `delay` */ + abstract class Delay[T]: + def apply(c: Context): T + + /** Delay a Context => T computation so that it is generated from the embedded + * context of a string formatter instead of the enclosing context. This is needed + * to make disambiguation work for such embedded computatons. + */ + def delay[T](fn: Context ?=> T): Delay[T] = new Delay[T]: + def apply(c: Context) = fn(using c) + object ShownDef: /** Represents a value that has been "shown" and can be consumed by StringFormatter. * Not just a string because it may be a Seq that StringFormatter will intersperse with the trailing separator. @@ -52,7 +63,7 @@ object Formatting { class ShowImplicits4: given [X: Show]: Show[X | Null] with - def show(x: X | Null) = if x == null then "null" else CtxShow(toStr(x.nn)) + def show(x: X | Null) = if x == null then "null" else CtxShow(toStr(x)) class ShowImplicits3 extends ShowImplicits4: given Show[Product] = ShowAny @@ -76,6 +87,9 @@ object Formatting { given [X: Show]: Show[Seq[X]] with def show(x: Seq[X]) = CtxShow(x.map(toStr)) + given [X: Show]: Show[Delay[X]] = new Show: + def show(x: Delay[X]) = CtxShow(c ?=> x(c)) + given Show[Seq[Nothing]] with def show(x: Seq[Nothing]) = CtxShow(x) @@ -130,6 +144,7 @@ object Formatting { given Show[Class[?]] = ShowAny given Show[Throwable] = ShowAny given Show[StringBuffer] = ShowAny + given Show[StringBuilder] = ShowAny given Show[CompilationUnit] = ShowAny given Show[Phases.Phase] = ShowAny given Show[TyperState] = ShowAny diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 0dcb06ae8c87..03bffc65fbc7 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -15,7 +15,10 @@ import util.SourcePosition import scala.util.control.NonFatal import scala.annotation.switch import config.{Config, Feature} +import ast.tpd import cc.* +import CaptureSet.Mutability +import Capabilities.* class PlainPrinter(_ctx: Context) extends Printer { @@ -30,8 +33,10 @@ class PlainPrinter(_ctx: Context) extends Printer { /** Print Fresh instances as */ protected def ccVerbose = ctx.settings.YccVerbose.value - /** Print Fresh instances as "fresh" */ - protected def printFresh = ccVerbose || ctx.property(PrintFresh).isDefined + /** Elide redundant ^ and ^{cap.rd} when printing instances of Capability + * classes. Gets set when singletons are printed as `(x: T)` to reduce verbosity. + */ + private var elideCapabilityCaps = false private var openRecs: List[RecType] = Nil @@ -130,6 +135,8 @@ class PlainPrinter(_ctx: Context) extends Printer { protected def argText(arg: Type, isErased: Boolean = false): Text = keywordText("erased ").provided(isErased) + ~ specialAnnotText(defn.UseAnnot, arg) + ~ specialAnnotText(defn.ConsumeAnnot, arg) ~ homogenizeArg(arg).match case arg: TypeBounds => "?" ~ toText(arg) case arg => toText(arg) @@ -166,37 +173,62 @@ class PlainPrinter(_ctx: Context) extends Printer { else val core: Text = if !cs.isConst && cs.elems.isEmpty then "?" - else "{" ~ Text(cs.processElems(_.toList.map(toTextCaptureRef)), ", ") ~ "}" - // ~ Str("?").provided(!cs.isConst) + else "{" ~ Text(cs.processElems(_.toList.map(toTextCapability)), ", ") ~ "}" + ~ Str(".reader").provided(ccVerbose && cs.mutability == Mutability.Reader) + ~ Str("?").provided(ccVerbose && !cs.isConst) + ~ Str(s"#${cs.asVar.id}").provided(showUniqueIds && !cs.isConst) core ~ cs.optionalInfo - private def toTextRetainedElem[T <: Untyped](ref: Tree[T]): Text = ref match - case ref: RefTree[?] if ref.typeOpt.exists => - toTextCaptureRef(ref.typeOpt) - case TypeApply(fn, arg :: Nil) if fn.symbol == defn.Caps_capsOf => - toTextRetainedElem(arg) - case ReachCapabilityApply(ref1) => toTextRetainedElem(ref1) ~ "*" - case ReadOnlyCapabilityApply(ref1) => toTextRetainedElem(ref1) ~ ".rd" - case _ => toText(ref) + private def toTextRetainedElem(ref: Type): Text = ref match + case c: Capability => toTextCapability(c) + case _ => + try toTextCapability(ref.toCapability) + catch case _ => + toText(ref) - private def toTextRetainedElems[T <: Untyped](refs: List[Tree[T]]): Text = + private def toTextRetainedElems(refs: List[Type]): Text = "{" ~ Text(refs.map(ref => toTextRetainedElem(ref)), ", ") ~ "}" + type GeneralCaptureSet = CaptureSet | List[Type] + + protected def isUniversalCaptureSet(refs: GeneralCaptureSet): Boolean = refs match + case refs: CaptureSet => + // The set if universal if it consists only of caps.cap or + // only of an existential Fresh that is bound to the immediately enclosing method. + val isUniversal = + refs.elems.size == 1 + && (refs.isUniversal + || !printDebug && !ccVerbose && !showUniqueIds && refs.elems.nth(0).match + case ResultCap(binder) => + CCState.openExistentialScopes match + case b :: _ => binder eq b + case _ => false + case _ => + false + ) + isUniversal + || !refs.elems.isEmpty && refs.elems.forall(_.isCapOrFresh) && !ccVerbose + case ref :: Nil => ref.isCapRef + case _ => false + + protected def toTextGeneralCaptureSet(refs: GeneralCaptureSet): Text = refs match + case refs: CaptureSet => toTextCaptureSet(refs) + case refs: List[Type] => toTextRetainedElems(refs) + /** Print capturing type, overridden in RefinedPrinter to account for * capturing function types. */ - protected def toTextCapturing(parent: Type, refsText: Text, boxText: Text): Text = + protected def toTextCapturing(parent: Type, refs: GeneralCaptureSet, boxText: Text): Text = changePrec(InfixPrec): - boxText ~ toTextLocal(parent) ~ "^" ~ (refsText provided refsText != rootSetText) - - final protected def rootSetText = Str("{cap}") // TODO Use disambiguation + boxText + ~ toTextLocal(parent) + ~ "^" + ~ toTextGeneralCaptureSet(refs).provided(!isUniversalCaptureSet(refs) || ccVerbose) def toText(tp: Type): Text = controlled { homogenize(tp) match { case tp: TypeType => toTextRHS(tp) - case tp: TermRef if tp.isCap => - toTextCaptureRef(tp) case tp: TermRef if !tp.denotationIsCurrent && !homogenizedView // always print underlying when testing picklers @@ -250,39 +282,16 @@ class PlainPrinter(_ctx: Context) extends Printer { (" <: " ~ toText(bound) provided !bound.isAny) }.close case tp @ CapturingType(parent, refs) => - val boxText: Text = Str("box ") provided tp.isBoxed //&& ctx.settings.YccDebug.value - if parent.derivesFrom(defn.Caps_Capability) - && refs.containsRootCapability && refs.isReadOnly && !printDebug - then - toText(parent) - else - // The set if universal if it consists only of caps.cap or - // only of an existential Fresh that is bound to the immediately enclosing method. - def isUniversal = - refs.elems.size == 1 - && (refs.isUniversal - || !printDebug && !printFresh && !showUniqueIds && refs.elems.nth(0).match - case root.Result(binder) => - CCState.openExistentialScopes match - case b :: _ => binder eq b - case _ => false - case _ => - false - ) - val refsText = - if isUniversal then - rootSetText - else if !refs.elems.isEmpty && refs.elems.forall(_.isCapOrFresh) && !printFresh then - rootSetText - else - toTextCaptureSet(refs) - toTextCapturing(parent, refsText, boxText) - case tp @ RetainingType(parent, refs) => + val boxText: Text = Str("box ") provided tp.isBoxed && ccVerbose + if elideCapabilityCaps + && parent.derivesFrom(defn.Caps_Capability) + && refs.containsTerminalCapability + && refs.isReadOnly + then toText(parent) + else toTextCapturing(parent, refs, boxText) + case tp @ RetainingType(parent, refSet) => if Feature.ccEnabledSomewhere then - val refsText = refs match - case ref :: Nil if ref.symbol == defn.captureRoot => rootSetText - case _ => toTextRetainedElems(refs) - toTextCapturing(parent, refsText, "") ~ Str("R").provided(printDebug) + toTextCapturing(parent, refSet.retainedElementsRaw, "") ~ Str("R").provided(printDebug) else toText(parent) case tp: PreviousErrorType if ctx.settings.XprintTypes.value => "" // do not print previously reported error message because they may try to print this error type again recursively @@ -307,8 +316,8 @@ class PlainPrinter(_ctx: Context) extends Printer { case ExprType(restp) => def arrowText: Text = restp match case AnnotatedType(parent, ann) if ann.symbol == defn.RetainsByNameAnnot => - ann.tree.retainedElems match - case ref :: Nil if ref.symbol == defn.captureRoot => Str("=>") + ann.tree.retainedSet.retainedElementsRaw match + case ref :: Nil if ref.isCapRef => Str("=>") case refs => Str("->") ~ toTextRetainedElems(refs) case _ => if Feature.pureFunsEnabled then "->" else "=>" @@ -326,9 +335,6 @@ class PlainPrinter(_ctx: Context) extends Printer { case AnnotatedType(tpe, annot) => if defn.SilentAnnots.contains(annot.symbol) && !printDebug then toText(tpe) - else if (annot.symbol == defn.IntoAnnot || annot.symbol == defn.IntoParamAnnot) - && !printDebug - then atPrec(GlobalPrec)( Str("into ") ~ toText(tpe) ) else if annot.isInstanceOf[CaptureAnnotation] then toTextLocal(tpe) ~ "^" ~ toText(annot) else @@ -365,12 +371,24 @@ class PlainPrinter(_ctx: Context) extends Printer { }.close def toTextSingleton(tp: SingletonType): Text = - "(" ~ toTextRef(tp) ~ " : " ~ toTextGlobal(tp.underlying) ~ ")" - - protected def paramsText(lam: LambdaType): Text = { + val saved = elideCapabilityCaps + elideCapabilityCaps = !ccVerbose && !ctx.settings.explain.value + // don't elide capability capture sets under -Ycc-verbose or -explain + try "(" ~ toTextRef(tp) ~ " : " ~ toTextGlobal(tp.underlying) ~ ")" + finally elideCapabilityCaps = saved + + /** Print the annotation that are meant to be on the parameter symbol but was moved + * to parameter types. Examples are `@use` and `@consume`. */ + protected def specialAnnotText(sym: ClassSymbol, tp: Type): Text = + Str(s"@${sym.name} ").provided(tp.hasAnnotation(sym)) + + def paramsText(lam: LambdaType): Text = { def paramText(ref: ParamRef) = val erased = ref.underlying.hasAnnotation(defn.ErasedParamAnnot) - keywordText("erased ").provided(erased) ~ ParamRefNameString(ref) ~ hashStr(lam) ~ toTextRHS(ref.underlying, isParameter = true) + keywordText("erased ").provided(erased) + ~ specialAnnotText(defn.UseAnnot, ref.underlying) + ~ specialAnnotText(defn.ConsumeAnnot, ref.underlying) + ~ ParamRefNameString(ref) ~ hashStr(lam) ~ toTextRHS(ref.underlying, isParameter = true) Text(lam.paramRefs.map(paramText), ", ") } @@ -429,7 +447,7 @@ class PlainPrinter(_ctx: Context) extends Printer { case tp: ThisType => nameString(tp.cls) + ".this" case SuperType(thistpe: SingletonType, _) => - toTextRef(thistpe).map(_.replaceAll("""\bthis$""", "super").nn) + toTextRef(thistpe).map(_.replaceAll("""\bthis$""", "super")) case SuperType(thistpe, _) => "Super(" ~ toTextGlobal(thistpe) ~ ")" case tp @ ConstantType(value) => @@ -441,34 +459,41 @@ class PlainPrinter(_ctx: Context) extends Printer { if (idx >= 0) selfRecName(idx + 1) else "{...}.this" // TODO move underlying type to an addendum, e.g. ... z3 ... where z3: ... case tp: SkolemType => - if (homogenizedView) toText(tp.info) - else if (ctx.settings.XprintTypes.value) "<" ~ toText(tp.repr) ~ ":" ~ toText(tp.info) ~ ">" - else toText(tp.repr) + def reprStr = toText(tp.repr) ~ hashStr(tp) + if homogenizedView then + toText(tp.info) + else if ctx.settings.XprintTypes.value then + "<" ~ reprStr ~ ":" ~ toText(tp.info) ~ ">" + else + reprStr } } - def toTextCaptureRef(tp: Type): Text = - homogenize(tp) match - case tp: TermRef if tp.symbol == defn.captureRoot => "cap" - case tp: SingletonType => toTextRef(tp) - case tp: (TypeRef | TypeParamRef) => toText(tp) ~ "^" - case ReadOnlyCapability(tp1) => toTextCaptureRef(tp1) ~ ".rd" - case ReachCapability(tp1) => toTextCaptureRef(tp1) ~ "*" - case MaybeCapability(tp1) => toTextCaptureRef(tp1) ~ "?" - case tp @ root.Result(binder) => - val idStr = s"##${tp.rootAnnot.id}" - // TODO: Better printing? USe a mode where we print more detailed - val vbleText: Text = CCState.openExistentialScopes.indexOf(binder) match - case -1 => - "" - case n => "outer_" * n ++ (if printFresh then "localcap" else "cap") - vbleText ~ hashStr(binder) ~ Str(idStr).provided(showUniqueIds) - case tp @ root.Fresh(hidden) => - val idStr = if showUniqueIds then s"#${tp.rootAnnot.id}" else "" - if ccVerbose then s"" - else if printFresh then "fresh" - else "cap" - case tp => toText(tp) + def toTextCapability(c: Capability): Text = c match + case ReadOnly(c1) => toTextCapability(c1) ~ ".rd" + case Restricted(c1, cls) => toTextCapability(c1) ~ s".only[${nameString(cls)}]" + case Reach(c1) => toTextCapability(c1) ~ "*" + case Maybe(c1) => toTextCapability(c1) ~ "?" + case GlobalCap => "cap" + case c: ResultCap => + def idStr = s"##${c.rootId}" + // TODO: Better printing? USe a mode where we print more detailed + val vbleText: Text = CCState.openExistentialScopes.indexOf(c.binder) match + case -1 => + "" + case n => "outer_" * n ++ (if ccVerbose then "localcap" else "cap") + vbleText ~ Str(hashStr(c.binder)).provided(printDebug) ~ Str(idStr).provided(showUniqueIds) + case c: FreshCap => + val idStr = if showUniqueIds then s"#${c.rootId}" else "" + def classified = + if c.hiddenSet.classifier == defn.AnyClass then "" + else s" classified as ${c.hiddenSet.classifier.name.show}" + if ccVerbose then s"" + else "cap" + case tp: TypeProxy => + homogenize(tp) match + case tp: SingletonType => toTextRef(tp) + case tp => toText(tp) protected def isOmittablePrefix(sym: Symbol): Boolean = defn.unqualifiedOwnerTypes.exists(_.symbol == sym) || isEmptyPrefix(sym) @@ -677,7 +702,7 @@ class PlainPrinter(_ctx: Context) extends Printer { case '"' => "\\\"" case '\'' => "\\\'" case '\\' => "\\\\" - case _ => if ch.isControl then f"${"\\"}u${ch.toInt}%04x" else String.valueOf(ch).nn + case _ => if ch.isControl then f"${"\\"}u${ch.toInt}%04x" else String.valueOf(ch) } def toText(const: Constant): Text = const.tag match { @@ -687,7 +712,7 @@ class PlainPrinter(_ctx: Context) extends Printer { case LongTag => literalText(const.longValue.toString + "L") case DoubleTag => literalText(const.doubleValue.toString + "d") case FloatTag => literalText(const.floatValue.toString + "f") - case _ => literalText(String.valueOf(const.value).nn) + case _ => literalText(String.valueOf(const.value)) } /** Usual target for `Annotation#toText`, overridden in RefinedPrinter */ diff --git a/compiler/src/dotty/tools/dotc/printing/Printer.scala b/compiler/src/dotty/tools/dotc/printing/Printer.scala index 9f485ee84cda..761e6a6bb6ba 100644 --- a/compiler/src/dotty/tools/dotc/printing/Printer.scala +++ b/compiler/src/dotty/tools/dotc/printing/Printer.scala @@ -4,13 +4,14 @@ package printing import core.* import Texts.*, ast.Trees.* -import Types.{Type, SingletonType, LambdaParam, NamedType, RefinedType}, +import Types.{Type, SingletonType, LambdaParam, LambdaType, NamedType, RefinedType}, Symbols.Symbol, Scopes.Scope, Constants.Constant, Names.Name, Denotations._, Annotations.Annotation, Contexts.Context import typer.Implicits.* import util.SourcePosition import typer.ImportInfo import cc.CaptureSet +import cc.Capabilities.Capability import scala.annotation.internal.sharable @@ -108,7 +109,7 @@ abstract class Printer { def toTextRefinement(rt: RefinedType): Text /** Textual representation of a reference in a capture set */ - def toTextCaptureRef(tp: Type): Text + def toTextCapability(ref: Capability): Text /** Textual representation of a reference in a capture set */ def toTextCaptureSet(cs: CaptureSet): Text @@ -146,6 +147,9 @@ abstract class Printer { /** Textual representation of lambda param */ def toText(tree: LambdaParam): Text + /** textual representation of parameters of function type */ + def paramsText(lam: LambdaType): Text + /** Textual representation of all symbols in given list, * using `dclText` for displaying each. */ diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 3d987982cc20..1e58268c2e38 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -156,20 +156,25 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else simpleNameString(tsym) } - private def arrow(isGiven: Boolean, isPure: Boolean): String = + protected def arrow(isGiven: Boolean, isPure: Boolean): String = (if isGiven then "?" else "") + (if isPure then "->" else "=>") - private def toTextFunction(tp: AppliedType, refs: Text = Str("")): Text = + private def toTextFunction(tp: AppliedType, refs: GeneralCaptureSet | Null): Text = val AppliedType(tycon, args) = (tp: @unchecked) val tsym = tycon.typeSymbol - val isContextual = tsym.name.isContextFunction - val capturesRoot = refs == rootSetText - val isPure = - Feature.pureFunsEnabled && !tsym.name.isImpureFunction && !capturesRoot - toTextFunction(args.init, args.last, tp, refs.provided(!capturesRoot), isContextual, isPure) - - private def toTextFunction(args: List[Type], res: Type, fn: MethodType | AppliedType, refs: Text, - isContextual: Boolean, isPure: Boolean): Text = + toTextFunction(args.init, args.last, tp, refs, + isContextual = tsym.name.isContextFunction, + isPure = Feature.pureFunsEnabled && !tsym.name.isImpureFunction) + + protected def funMiddleText(isContextual: Boolean, isPure: Boolean, refs: GeneralCaptureSet | Null): Text = + val (printPure, refsText) = + if refs == null then (isPure, Str("")) + else if isUniversalCaptureSet(refs) then (false, Str("")) + else (isPure, toTextGeneralCaptureSet(refs)) + arrow(isContextual, printPure) ~ refsText + + private def toTextFunction(args: List[Type], res: Type, fn: MethodType | AppliedType, + refs: GeneralCaptureSet | Null, isContextual: Boolean, isPure: Boolean): Text = changePrec(GlobalPrec): val argStr: Text = args match case arg :: Nil if !defn.isDirectTupleNType(arg) && !isContextual => @@ -179,30 +184,29 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "(" ~ argsText(args) ~ ")" - argStr ~ " " ~ arrow(isContextual, isPure) ~ refs ~ " " + argStr ~ " " + ~ funMiddleText(isContextual, isPure, refs) ~ " " ~ fn.match case fn: MethodType => CCState.inNewExistentialScope(fn)(argText(res)) case _ => argText(res) - protected def toTextMethodAsFunction(info: Type, isPure: Boolean, refs: Text = Str("")): Text = + protected def toTextMethodAsFunction(info: Type, isPure: Boolean, refs: GeneralCaptureSet | Null): Text = def recur(tp: Type, enclInfo: MethodType | Null): Text = tp match case tp: MethodType => val isContextual = tp.isImplicitMethod - val capturesRoot = refs == rootSetText if cc.isCaptureCheckingOrSetup && tp.allParamNamesSynthetic && !tp.looksResultDependent && !tp.looksParamDependent - && !showUniqueIds && !printDebug && !printFresh - then + && !showUniqueIds && !printDebug && !ccVerbose + then // cc.Setup converts all functions to dependent functions. Undo that when printing. - toTextFunction(tp.paramInfos, tp.resType, tp, refs.provided(!capturesRoot), isContextual, isPure && !capturesRoot) + toTextFunction(tp.paramInfos, tp.resType, tp, refs, isContextual, isPure) else changePrec(GlobalPrec): "(" ~ paramsText(tp) ~ ") " - ~ arrow(isContextual, isPure && !capturesRoot) - ~ refs.provided(!capturesRoot) + ~ funMiddleText(isContextual, isPure, refs) ~ " " ~ recur(tp.resultType, tp) case tp: PolyType => @@ -274,7 +278,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case _ => val tsym = tycon.typeSymbol if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*" - else if defn.isFunctionSymbol(tsym) then toTextFunction(tp) + else if defn.isFunctionSymbol(tsym) then toTextFunction(tp, null) else if isInfixType(tp) then val l :: r :: Nil = args: @unchecked val opName = tyconName(tycon) @@ -303,8 +307,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { toTextMethodAsFunction(tp.refinedInfo, isPure = Feature.pureFunsEnabled && !tp.typeSymbol.name.isImpureFunction, refs = tp.parent match - case CapturingType(_, cs) => toTextCaptureSet(cs) - case _ => "") + case CapturingType(_, cs) => cs + case _ => null) case tp: TypeRef => if (tp.symbol.isAnonymousClass && !showUniqueIds) toText(tp.info) @@ -320,7 +324,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case tp: ClassInfo => if tp.cls.derivesFrom(defn.PolyFunctionClass) then tp.member(nme.apply).info match - case info: PolyType => toTextMethodAsFunction(info, isPure = false) + case info: PolyType => toTextMethodAsFunction(info, isPure = false, refs = null) case _ => toTextParents(tp.parents) ~~ "{...}" else toTextParents(tp.parents) ~~ "{...}" case JavaArrayType(elemtp) => @@ -347,8 +351,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "?" ~ (("(ignored: " ~ toText(ignored) ~ ")") provided printDebug) case tp @ PolyProto(targs, resType) => "[applied to [" ~ toTextGlobal(targs, ", ") ~ "] returning " ~ toText(resType) - case tp: AnnotatedType if tp.isTrackableRef => - toTextCaptureRef(tp) case _ => super.toText(tp) } @@ -700,9 +702,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { && Feature.ccEnabled && !printDebug && Phases.checkCapturesPhase.exists // might be missing on -Ytest-pickler then toTextRetainsAnnot - else if annot.symbol.enclosingClass == defn.IntoAnnot && !printDebug then - atPrec(GlobalPrec): - Str("into ") ~ toText(arg) else toTextAnnot case EmptyTree => "" @@ -763,12 +762,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val opPrec = parsing.precedence(op.name) changePrec(opPrec) { toText(l) ~ " " ~ toText(op) ~ " " ~ toText(r) } case PostfixOp(l, op) => - if op.name == nme.CC_REACH then - changePrec(DotPrec) { toText(l) ~ "*" } - else if op.name == nme.CC_READONLY then - changePrec(DotPrec) { toText(l) ~ ".rd" } - else - changePrec(InfixPrec) { toText(l) ~ " " ~ toText(op) } + changePrec(InfixPrec) { toText(l) ~ " " ~ toText(op) } case PrefixOp(op, r) => changePrec(DotPrec) { toText(op) ~ " " ~ toText(r) } case Parens(t) => @@ -849,13 +843,13 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } } - override protected def toTextCapturing(tp: Type, refsText: Text, boxText: Text): Text = tp match + override protected def toTextCapturing(tp: Type, refs: GeneralCaptureSet, boxText: Text): Text = tp match case tp: AppliedType if defn.isFunctionSymbol(tp.typeSymbol) && !printDebug => - boxText ~ toTextFunction(tp, refsText) + boxText ~ toTextFunction(tp, refs) case tp: RefinedType if defn.isFunctionType(tp) && !printDebug => - boxText ~ toTextMethodAsFunction(tp.refinedInfo, isPure = !tp.typeSymbol.name.isImpureFunction, refsText) + boxText ~ toTextMethodAsFunction(tp.refinedInfo, isPure = !tp.typeSymbol.name.isImpureFunction, refs) case _ => - super.toTextCapturing(tp, refsText, boxText) + super.toTextCapturing(tp, refs, boxText) override def toText[T <: Untyped](tree: Tree[T]): Text = controlled { import untpd.* @@ -995,8 +989,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def valDefToText[T <: Untyped](tree: ValDef[T]): Text = { dclTextOr(tree) { modText(tree.mods, tree.symbol, keywordStr(if (tree.mods.is(Mutable)) "var" else "val"), isType = false) ~~ - valDefText(nameIdText(tree)) ~ optAscription(tree.tpt) ~ - withEnclosingDef(tree) { rhsValDef(tree) } + valDefText(nameIdText(tree)) + ~ optAscription(tree.tpt) + ~ withEnclosingDef(tree) { rhsValDef(tree) } } } @@ -1171,6 +1166,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (rawFlags.is(Param)) flagMask = flagMask &~ Given val flags = rawFlags & flagMask var flagsText = toTextFlags(sym, flags) + if sym.isUpdateMethod then flagsText ~~= keywordStr("update") val annotTexts = if sym.exists then sym.annotationsUNSAFE.filterNot(ann => dropAnnotForModText(ann.symbol)).map(toText) diff --git a/compiler/src/dotty/tools/dotc/printing/Showable.scala b/compiler/src/dotty/tools/dotc/printing/Showable.scala index 4480aa9c76a4..490778a7413a 100644 --- a/compiler/src/dotty/tools/dotc/printing/Showable.scala +++ b/compiler/src/dotty/tools/dotc/printing/Showable.scala @@ -25,7 +25,7 @@ trait Showable extends Any { /** The string representation with each line after the first one indented * by the given given margin (in spaces). */ - def showIndented(margin: Int)(using Context): String = show.replace("\n", "\n" + " " * margin).nn + def showIndented(margin: Int)(using Context): String = show.replace("\n", "\n" + " " * margin) /** The summarized string representation of this showable element. * Recursion depth is limited to some smallish value. Default is diff --git a/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index ce925e336b53..41ab7412d748 100644 --- a/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -1,8 +1,6 @@ package dotty.tools.dotc package printing -import scala.language.unsafeNulls - import dotty.tools.dotc.ast.untpd import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.StdNames.* @@ -18,6 +16,9 @@ import java.util.Arrays /** This object provides functions for syntax highlighting in the REPL */ object SyntaxHighlighting { + /** The name of the virtual source file used for highlighting */ + val VirtualSourceName = "" + /** if true, log erroneous positions being highlighted */ private inline val debug = true @@ -35,7 +36,7 @@ object SyntaxHighlighting { def freshCtx = ctx.fresh.setReporter(Reporter.NoReporter) if (in.isEmpty || ctx.settings.color.value == "never") in else { - val source = SourceFile.virtual("", in) + val source = SourceFile.virtual(VirtualSourceName, in) given Context = freshCtx .setCompilationUnit(CompilationUnit(source, mustExist = false)(using freshCtx)) diff --git a/compiler/src/dotty/tools/dotc/printing/Texts.scala b/compiler/src/dotty/tools/dotc/printing/Texts.scala index 475e2c6900d5..a4b2e3305742 100644 --- a/compiler/src/dotty/tools/dotc/printing/Texts.scala +++ b/compiler/src/dotty/tools/dotc/printing/Texts.scala @@ -5,7 +5,7 @@ import scala.annotation.internal.sharable object Texts { @sharable - private val ansi = java.util.regex.Pattern.compile("\u001b\\[\\d+m").nn + private val ansi = java.util.regex.Pattern.compile("\u001b\\[\\d+m") sealed abstract class Text { @@ -74,7 +74,7 @@ object Texts { else appendIndented(that)(width) private def lengthWithoutAnsi(str: String): Int = - ansi.matcher(str).nn.replaceAll("").nn.length + ansi.matcher(str).replaceAll("").length def layout(width: Int): Text = this match { case Str(s, _) => @@ -137,9 +137,10 @@ object Texts { case _ => relems.foldLeft(-1)((acc, relem) => acc max relem.maxLine) } - def mkString(width: Int, withLineNumbers: Boolean): String = { + def mkString(width: Int = Int.MaxValue, withLineNumbers: Boolean = false): String = { val sb = new StringBuilder - val numberWidth = if (withLineNumbers) (2 * maxLine.toString.length) + 2 else 0 + // width display can be upto a range "n-n" where 1 <= n <= maxLine+1 + val numberWidth = if (withLineNumbers) (2 * (maxLine + 1).toString.length) + 2 else 0 layout(width - numberWidth).print(sb, numberWidth) sb.toString } diff --git a/compiler/src/dotty/tools/dotc/profile/ChromeTrace.scala b/compiler/src/dotty/tools/dotc/profile/ChromeTrace.scala index 4950f439640f..8ee81c94f735 100644 --- a/compiler/src/dotty/tools/dotc/profile/ChromeTrace.scala +++ b/compiler/src/dotty/tools/dotc/profile/ChromeTrace.scala @@ -13,8 +13,6 @@ package dotty.tools.dotc.profile -import scala.language.unsafeNulls - import java.io.Closeable import java.lang.management.ManagementFactory import java.nio.file.{Files, Path} diff --git a/compiler/src/dotty/tools/dotc/profile/Profiler.scala b/compiler/src/dotty/tools/dotc/profile/Profiler.scala index ab3e73468385..a4625829c7d0 100644 --- a/compiler/src/dotty/tools/dotc/profile/Profiler.scala +++ b/compiler/src/dotty/tools/dotc/profile/Profiler.scala @@ -1,8 +1,6 @@ package dotty.tools.dotc.profile import scala.annotation.* -import scala.language.unsafeNulls - import java.io.{FileWriter, PrintWriter} import java.nio.file.Paths import java.lang.management.{ManagementFactory, GarbageCollectorMXBean, RuntimeMXBean, MemoryMXBean, ClassLoadingMXBean, CompilationMXBean} @@ -117,12 +115,12 @@ sealed trait Profiler { protected def beforeImplicitSearch(pt: Type): TracedEventId = TracedEventId.Empty protected def afterImplicitSearch(event: TracedEventId): Unit = () - inline def onMacroSplice[T](macroSym: Symbol)(inline body: T): T = - val event = beforeMacroSplice(macroSym) + inline def onInlineCall[T](inlineSym: Symbol)(inline body: T): T = + val event = beforeInlineCall(inlineSym) try body - finally afterMacroSplice(event) - protected def beforeMacroSplice(macroSym: Symbol): TracedEventId = TracedEventId.Empty - protected def afterMacroSplice(event: TracedEventId): Unit = () + finally afterInlineCall(event) + protected def beforeInlineCall(inlineSym: Symbol): TracedEventId = TracedEventId.Empty + protected def afterInlineCall(event: TracedEventId): Unit = () inline def onCompletion[T](root: Symbol, associatedFile: => AbstractFile)(inline body: T): T = val (event, completionName) = beforeCompletion(root, associatedFile) @@ -178,7 +176,7 @@ private [profile] class RealProfiler(reporter : ProfileReporter)(using Context) enum Category: def name: String = this.toString().toLowerCase() - case Run, Phase, File, TypeCheck, Implicit, Macro, Completion + case Run, Phase, File, TypeCheck, Implicit, Inline, Completion private [profile] val chromeTrace = if ctx.settings.YprofileTrace.isDefault then null @@ -317,8 +315,8 @@ private [profile] class RealProfiler(reporter : ProfileReporter)(using Context) override def beforeImplicitSearch(pt: Type): TracedEventId = traceDurationStart(Category.Implicit, s"?[${symbolName(pt.typeSymbol)}]", colour = "yellow") override def afterImplicitSearch(event: TracedEventId): Unit = traceDurationEnd(Category.Implicit, event, colour = "yellow") - override def beforeMacroSplice(macroSym: Symbol): TracedEventId = traceDurationStart(Category.Macro, s"«${symbolName(macroSym)}»", colour = "olive") - override def afterMacroSplice(event: TracedEventId): Unit = traceDurationEnd(Category.Macro, event, colour = "olive") + override def beforeInlineCall(inlineSym: Symbol): TracedEventId = traceDurationStart(Category.Inline, s"«${symbolName(inlineSym)}»", colour = "olive") + override def afterInlineCall(event: TracedEventId): Unit = traceDurationEnd(Category.Inline, event, colour = "olive") override def beforeCompletion(root: Symbol, associatedFile: => AbstractFile): (TracedEventId, String) = if chromeTrace == null diff --git a/compiler/src/dotty/tools/dotc/profile/ThreadPoolFactory.scala b/compiler/src/dotty/tools/dotc/profile/ThreadPoolFactory.scala index 1a81153b9b08..d07a38c37485 100644 --- a/compiler/src/dotty/tools/dotc/profile/ThreadPoolFactory.scala +++ b/compiler/src/dotty/tools/dotc/profile/ThreadPoolFactory.scala @@ -1,7 +1,5 @@ package dotty.tools.dotc.profile -import scala.language.unsafeNulls - import java.util.concurrent.ThreadPoolExecutor.AbortPolicy import java.util.concurrent.* import java.util.concurrent.atomic.AtomicInteger diff --git a/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala b/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala index e34d35065476..816bac14ddd2 100644 --- a/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala +++ b/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala @@ -1,8 +1,6 @@ package dotty.tools.dotc package quoted -import scala.language.unsafeNulls - import scala.collection.mutable import scala.reflect.ClassTag @@ -129,8 +127,8 @@ class Interpreter(pos: SrcPos, classLoader0: ClassLoader)(using Context): case fnType: MethodType => val argTypes = fnType.paramInfos assert(argss.head.size == argTypes.size) - val nonErasedArgs = argss.head.lazyZip(fnType.erasedParams).collect { case (arg, false) => arg }.toList - val nonErasedArgTypes = fnType.paramInfos.lazyZip(fnType.erasedParams).collect { case (arg, false) => arg }.toList + val nonErasedArgs = argss.head.lazyZip(fnType.paramErasureStatuses).collect { case (arg, false) => arg }.toList + val nonErasedArgTypes = fnType.paramInfos.lazyZip(fnType.paramErasureStatuses).collect { case (arg, false) => arg }.toList assert(nonErasedArgs.size == nonErasedArgTypes.size) interpretArgsGroup(nonErasedArgs, nonErasedArgTypes) ::: interpretArgs(argss.tail, fnType.resType) case fnType: AppliedType if defn.isContextFunctionType(fnType) => diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 3ee52624710e..b47c20b3e5cd 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -22,6 +22,7 @@ import scala.collection.mutable import QuoteUtils.* import dotty.tools.io.NoAbstractFile +import dotty.tools.dotc.ast.TreeMapWithImplicits object PickledQuotes { import tpd.* @@ -99,7 +100,7 @@ object PickledQuotes { /** Replace all term holes with the spliced terms */ private def spliceTerms(tree: Tree, typeHole: TypeHole, termHole: ExprHole)(using Context): Tree = { - def evaluateHoles = new TreeMapWithPreciseStatContexts { + def evaluateHoles = new TreeMapWithImplicits { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match { case Hole(isTerm, idx, args, _) => inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) { diff --git a/compiler/src/dotty/tools/dotc/quoted/TastyString.scala b/compiler/src/dotty/tools/dotc/quoted/TastyString.scala index 369e646e883e..528414654969 100644 --- a/compiler/src/dotty/tools/dotc/quoted/TastyString.scala +++ b/compiler/src/dotty/tools/dotc/quoted/TastyString.scala @@ -1,7 +1,5 @@ package dotty.tools.dotc.quoted -import scala.language.unsafeNulls - import java.util.Base64 import java.nio.charset.StandardCharsets.UTF_8 diff --git a/compiler/src/dotty/tools/dotc/report.scala b/compiler/src/dotty/tools/dotc/report.scala index 7f1eeb8e22eb..61bcc9c8d780 100644 --- a/compiler/src/dotty/tools/dotc/report.scala +++ b/compiler/src/dotty/tools/dotc/report.scala @@ -21,7 +21,7 @@ object report: ctx.reporter.report(warning) def deprecationWarning(msg: Message, pos: SrcPos, origin: String = "")(using Context): Unit = - issueWarning(new DeprecationWarning(msg, pos.sourcePos, origin)) + issueWarning(DeprecationWarning(msg, addInlineds(pos), origin)) def migrationWarning(msg: Message, pos: SrcPos)(using Context): Unit = issueWarning(new MigrationWarning(msg, pos.sourcePos)) @@ -84,8 +84,8 @@ object report: def bestEffortError(ex: Throwable, msg: String)(using Context): Unit = val stackTrace = Option(ex.getStackTrace()).map { st => - if st.nn.isEmpty then "" - else s"Stack trace: \n ${st.nn.mkString("\n ")}".stripMargin + if st.isEmpty then "" + else s"Stack trace: \n ${st.mkString("\n ")}".stripMargin }.getOrElse("") // Build tools and dotty's test framework may check precisely for // "Unsuccessful best-effort compilation." error text. @@ -138,7 +138,9 @@ object report: private def addInlineds(pos: SrcPos)(using Context): SourcePosition = def recur(pos: SourcePosition, inlineds: List[Trees.Tree[?]]): SourcePosition = inlineds match - case inlined :: inlineds1 => pos.withOuter(recur(inlined.sourcePos, inlineds1)) + case inlined :: inlineds => + val outer = recur(inlined.sourcePos, inlineds) + pos.withOuter(outer) case Nil => pos recur(pos.sourcePos, tpd.enclosingInlineds) diff --git a/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 3dc73983056a..3604b5b6ebf9 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -23,9 +23,9 @@ class ConsoleReporter( super.doReport(dia) if ctx.settings.Xprompt.value then dia match - case _: Error => Reporter.displayPrompt(reader, writer) - case _: Warning if ctx.settings.XfatalWarnings.value => Reporter.displayPrompt(reader, writer) - case _ => + case _: Error => Reporter.displayPrompt(reader, writer) + case _: Warning => if ctx.settings.Werror.value then Reporter.displayPrompt(reader, writer) + case _ => } } diff --git a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala index 20be33716831..30a9b8f60cf8 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala @@ -2,8 +2,6 @@ package dotty.tools package dotc package reporting -import scala.language.unsafeNulls - import dotty.tools.dotc.config.Settings.Setting import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.interfaces.Diagnostic.{ERROR, INFO, WARNING} diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index cafe99973701..103687abdbff 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -208,7 +208,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case UnstableInlineAccessorID // errorNumber: 192 case VolatileOnValID // errorNumber: 193 case ExtensionNullifiedByMemberID // errorNumber: 194 - case ConstructorProxyNotValueID // errorNumber: 195 + case PhantomSymbolNotValueID // errorNumber: 195 case ContextBoundCompanionNotValueID // errorNumber: 196 case InlinedAnonClassWarningID // errorNumber: 197 case UnusedSymbolID // errorNumber: 198 @@ -225,6 +225,16 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case FormatInterpolationErrorID // errorNumber: 209 case ValueClassCannotExtendAliasOfAnyValID // errorNumber: 210 case MatchIsNotPartialFunctionID // errorNumber: 211 + case OnlyFullyDependentAppliedConstructorTypeID // errorNumber: 212 + case PointlessAppliedConstructorTypeID // errorNumber: 213 + case IllegalContextBoundsID // errorNumber: 214 + case NamedPatternNotApplicableID // errorNumber: 215 + case UnnecessaryNN // errorNumber: 216 + case ErasedNotPureID // errorNumber: 217 + case IllegalErasedDefID // errorNumber: 218 + case CannotInstantiateQuotedTypeVarID // errorNumber: 219 + case DefaultShadowsGivenID // errorNumber: 220 + case RecurseWithDefaultID // errorNumber: 221 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index 1ac5c6ecf407..de0d9fdd7131 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -8,8 +8,9 @@ import printing.{RefinedPrinter, MessageLimiter, ErrorMessageLimiter} import printing.Texts.Text import printing.Formatting.hl import config.SourceVersion +import cc.CaptureSet +import cc.Capabilities.* -import scala.language.unsafeNulls import scala.annotation.threadUnsafe /** ## Tips for error message generation @@ -40,7 +41,18 @@ object Message: i"\n$what can be rewritten automatically under -rewrite $optionStr." else "" - private type Recorded = Symbol | ParamRef | SkolemType + enum Disambiguation: + case All + case AllExcept(strs: List[String]) + case None + + def recordOK(str: String): Boolean = this match + case All => true + case AllExcept(strs) => !strs.contains(str) + case None => false + end Disambiguation + + private type Recorded = Symbol | ParamRef | SkolemType | RootCapability private case class SeenKey(str: String, isType: Boolean) @@ -48,7 +60,7 @@ object Message: * adds superscripts for disambiguations, and can explain recorded symbols * in ` where` clause */ - private class Seen(disambiguate: Boolean): + private class Seen(disambiguate: Disambiguation): /** The set of lambdas that were opened at some point during printing. */ private val openedLambdas = new collection.mutable.HashSet[LambdaType] @@ -62,12 +74,14 @@ object Message: var nonSensical = false /** If false, stop all recordings */ - private var recordOK = disambiguate + private var disambi = disambiguate + + def isActive = disambi != Disambiguation.None /** Clear all entries and stop further entries to be added */ def disable() = seen.clear() - recordOK = false + disambi = Disambiguation.None /** Record an entry `entry` with given String representation `str` and a * type/term namespace identified by `isType`. @@ -76,63 +90,65 @@ object Message: * and following recordings get consecutive superscripts starting with 2. * @return The possibly superscripted version of `str`. */ - def record(str: String, isType: Boolean, entry: Recorded)(using Context): String = if !recordOK then str else - //println(s"recording $str, $isType, $entry") - - /** If `e1` is an alias of another class of the same name, return the other - * class symbol instead. This normalization avoids recording e.g. scala.List - * and scala.collection.immutable.List as two different types - */ - def followAlias(e1: Recorded): Recorded = e1 match { - case e1: Symbol if e1.isAliasType => - val underlying = e1.typeRef.underlyingClassRef(refinementOK = false).typeSymbol - if (underlying.name == e1.name) underlying else e1.namedType.dealias.typeSymbol - case _ => e1 - } - val key = SeenKey(str, isType) - val existing = seen(key) - lazy val dealiased = followAlias(entry) - - /** All lambda parameters with the same name are given the same superscript as - * long as their corresponding binder has been printed. - * See tests/neg/lambda-rename.scala for test cases. - */ - def sameSuperscript(cur: Recorded, existing: Recorded) = - (cur eq existing) || - (cur, existing).match - case (cur: ParamRef, existing: ParamRef) => - (cur.paramName eq existing.paramName) && - openedLambdas.contains(cur.binder) && - openedLambdas.contains(existing.binder) - case _ => - false - - // The length of alts corresponds to the number of superscripts we need to print. - var alts = existing.dropWhile(alt => !sameSuperscript(dealiased, followAlias(alt))) - if alts.isEmpty then - alts = entry :: existing - seen(key) = alts - - val suffix = alts.length match { - case 1 => "" - case n => n.toString.toCharArray.map { - case '0' => '⁰' - case '1' => '¹' - case '2' => '²' - case '3' => '³' - case '4' => '⁴' - case '5' => '⁵' - case '6' => '⁶' - case '7' => '⁷' - case '8' => '⁸' - case '9' => '⁹' - }.mkString - } - str + suffix + def record(str: String, isType: Boolean, entry: Recorded)(using Context): String = + if disambi.recordOK(str) then + //println(s"recording $str, $isType, $entry") + + /** If `e1` is an alias of another class of the same name, return the other + * class symbol instead. This normalization avoids recording e.g. scala.List + * and scala.collection.immutable.List as two different types + */ + def followAlias(e1: Recorded): Recorded = e1 match { + case e1: Symbol if e1.isAliasType => + val underlying = e1.typeRef.underlyingClassRef(refinementOK = false).typeSymbol + if (underlying.name == e1.name) underlying else e1.namedType.dealias.typeSymbol + case _ => e1 + } + val key = SeenKey(str, isType) + val existing = seen(key) + lazy val dealiased = followAlias(entry) + + /** All lambda parameters with the same name are given the same superscript as + * long as their corresponding binder has been printed. + * See tests/neg/lambda-rename.scala for test cases. + */ + def sameSuperscript(cur: Recorded, existing: Recorded) = + (cur eq existing) || + (cur, existing).match + case (cur: ParamRef, existing: ParamRef) => + (cur.paramName eq existing.paramName) && + openedLambdas.contains(cur.binder) && + openedLambdas.contains(existing.binder) + case _ => + false + + // The length of alts corresponds to the number of superscripts we need to print. + var alts = existing.dropWhile(alt => !sameSuperscript(dealiased, followAlias(alt))) + if alts.isEmpty then + alts = entry :: existing + seen(key) = alts + + val suffix = alts.length match { + case 1 => "" + case n => n.toString.toCharArray.map { + case '0' => '⁰' + case '1' => '¹' + case '2' => '²' + case '3' => '³' + case '4' => '⁴' + case '5' => '⁵' + case '6' => '⁶' + case '7' => '⁷' + case '8' => '⁸' + case '9' => '⁹' + }.mkString + } + str + suffix + else str end record /** Create explanation for single `Recorded` type or symbol */ - private def explanation(entry: AnyRef)(using Context): String = + private def explanation(entry: AnyRef, key: String)(using Context): String = def boundStr(bound: Type, default: ClassSymbol, cmp: String) = if (bound.isRef(default)) "" else i"$cmp $bound" @@ -152,7 +168,7 @@ object Message: "" } - entry match { + entry match case param: TypeParamRef => s"is a type variable${addendum("constraint", TypeComparer.bounds(param))}" case param: TermParamRef => @@ -160,13 +176,17 @@ object Message: case sym: Symbol => val info = if (ctx.gadt.contains(sym)) - sym.info & ctx.gadt.fullBounds(sym) + sym.info & ctx.gadt.fullBounds(sym).nn else sym.info s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", info)}" case tp: SkolemType => s"is an unknown value of type ${tp.widen.show}" - } + case ref: RootCapability => + val relation = + if List("^", "=>", "?=>").exists(key.startsWith) then "refers to" + else "is" + s"$relation ${ref.descr}" end explanation /** Produce a where clause with explanations for recorded iterms. @@ -177,6 +197,7 @@ object Message: case param: ParamRef => false case skolem: SkolemType => true case sym: Symbol => ctx.gadt.contains(sym) && ctx.gadt.fullBounds(sym) != TypeBounds.empty + case ref: Capability => ref.isTerminalCapability } val toExplain: List[(String, Recorded)] = seen.toList.flatMap { kvs => @@ -201,7 +222,7 @@ object Message: } } - val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) } + val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry, str)) } val explainLines = columnar(explainParts) if (explainLines.isEmpty) "" else i"where: $explainLines%\n %\n" end explanations @@ -225,7 +246,27 @@ object Message: case tp: SkolemType => seen.record(tp.repr.toString, isType = true, tp) case _ => super.toTextRef(tp) - override def toTextMethodAsFunction(info: Type, isPure: Boolean, refs: Text): Text = + override def toTextCapability(c: Capability): Text = c match + case c: RootCapability if seen.isActive => seen.record("cap", isType = false, c) + case _ => super.toTextCapability(c) + + override def toTextCapturing(parent: Type, refs: GeneralCaptureSet, boxText: Text) = refs match + case refs: CaptureSet + if isUniversalCaptureSet(refs) && !defn.isFunctionType(parent) && !printDebug && seen.isActive => + boxText + ~ toTextLocal(parent) + ~ seen.record("^", isType = true, refs.elems.nth(0).asInstanceOf[RootCapability]) + case _ => + super.toTextCapturing(parent, refs, boxText) + + override def funMiddleText(isContextual: Boolean, isPure: Boolean, refs: GeneralCaptureSet | Null): Text = + refs match + case refs: CaptureSet if isUniversalCaptureSet(refs) && seen.isActive => + seen.record(arrow(isContextual, isPure = false), isType = true, refs.elems.nth(0).asInstanceOf[RootCapability]) + case _ => + super.funMiddleText(isContextual, isPure, refs) + + override def toTextMethodAsFunction(info: Type, isPure: Boolean, refs: GeneralCaptureSet | Null): Text = info match case info: LambdaType => seen.openLambda(info) @@ -348,13 +389,15 @@ abstract class Message(val errorId: ErrorMessageID)(using Context) { self => */ def isNonSensical: Boolean = { message; myIsNonSensical } - private var disambiguate: Boolean = true + private var disambiguate: Disambiguation = Disambiguation.All - def withoutDisambiguation(): this.type = - disambiguate = false + def withDisambiguation(disambi: Disambiguation): this.type = + disambiguate = disambi this - private def inMessageContext(disambiguate: Boolean)(op: Context ?=> String): String = + def withoutDisambiguation(): this.type = withDisambiguation(Disambiguation.None) + + private def inMessageContext(disambiguate: Disambiguation)(op: Context ?=> String): String = if ctx eq NoContext then op else val msgContext = ctx.printer match @@ -373,7 +416,7 @@ abstract class Message(val errorId: ErrorMessageID)(using Context) { self => /** The explanation to report. tags are filtered out */ @threadUnsafe lazy val explanation: String = - inMessageContext(disambiguate = false)(explain) + inMessageContext(disambiguate = Disambiguation.None)(explain) /** The implicit `Context` in messages is a large thing that we don't want * persisted. This method gets around that by duplicating the message, @@ -424,12 +467,17 @@ trait NoDisambiguation extends Message: withoutDisambiguation() /** The fallback `Message` containing no explanation and having no `kind` */ -final class NoExplanation(msgFn: Context ?=> String)(using Context) extends Message(ErrorMessageID.NoExplanationID) { +final class NoExplanation(msgFn: Context ?=> String, actions: List[CodeAction] = List.empty)(using Context) extends Message(ErrorMessageID.NoExplanationID) { def msg(using Context): String = msgFn def explain(using Context): String = "" val kind: MessageKind = MessageKind.NoKind + override def actions(using Context): List[CodeAction] = actions + override def toString(): String = msg + + def withActions(actions: CodeAction*): NoExplanation = + new NoExplanation(msgFn, actions.toList) } /** The extractor for `NoExplanation` can be used to check whether any error diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index 7fddfc8d6ed0..0414bdb3c58b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -11,7 +11,7 @@ import core.Decorators.* import printing.Highlighting.{Blue, Red, Yellow} import printing.SyntaxHighlighting import Diagnostic.* -import util.{ SourcePosition, NoSourcePosition } +import util.{SourcePosition, NoSourcePosition} import util.Chars.{ LF, CR, FF, SU } import scala.annotation.switch diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 61f842800b78..af92d0e0efdf 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -95,6 +95,7 @@ abstract class Reporter extends interfaces.ReporterResult { private var _errorCount = 0 private var _warningCount = 0 + private var _infoCount = 0 /** The number of errors reported by this reporter (ignoring outer reporters) */ def errorCount: Int = _errorCount @@ -112,12 +113,17 @@ abstract class Reporter extends interfaces.ReporterResult { private var warnings: List[Warning] = Nil + private var infos: List[Info] = Nil + /** All errors reported by this reporter (ignoring outer reporters) */ def allErrors: List[Error] = errors /** All warnings reported by this reporter (ignoring outer reporters) */ def allWarnings: List[Warning] = warnings + /** All infos reported by this reporter (ignoring outer reporters) */ + def allInfos: List[Info] = infos + /** Were sticky errors reported? Overridden in StoreReporter. */ def hasStickyErrors: Boolean = false @@ -171,7 +177,9 @@ abstract class Reporter extends interfaces.ReporterResult { _errorCount += 1 if ctx.typerState.isGlobalCommittable then ctx.base.errorsToBeReported = true - case _: Info => // nothing to do here + case i: Info => + infos = i :: infos + _infoCount += 1 // match error if d is something else } markReported(dia) @@ -216,7 +224,7 @@ abstract class Reporter extends interfaces.ReporterResult { incompleteHandler(dia, ctx) def finalizeReporting()(using Context) = - if (hasWarnings && ctx.settings.XfatalWarnings.value) + if (hasWarnings && ctx.settings.Werror.value) report(new Error("No warnings can be incurred under -Werror (or -Xfatal-warnings)", NoSourcePosition)) /** Summary of warnings and errors */ diff --git a/compiler/src/dotty/tools/dotc/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index ac25f2f6cd30..cff15aa6dc38 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -10,6 +10,7 @@ import dotty.tools.dotc.interfaces.SourceFile import dotty.tools.dotc.reporting.MessageFilter.SourcePattern import java.util.regex.PatternSyntaxException +import scala.PartialFunction.cond import scala.annotation.internal.sharable import scala.util.matching.Regex @@ -136,13 +137,20 @@ object WConf: if (parseErrorss.nonEmpty) Left(parseErrorss.flatten) else Right(WConf(configs)) -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 +class Suppression(val annotPos: SourcePosition, val filters: List[MessageFilter], val start: Int, val end: Int, val verbose: Boolean): + inline def unusedState = 0 + inline def usedState = 1 + inline def supersededState = 2 + private var _used = unusedState + def used: Boolean = _used == usedState + def superseded: Boolean = _used == supersededState def markUsed(): Unit = - _used = true + _used = usedState + def markSuperseded(): Unit = + _used = supersededState 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(", ")}]" +end Suppression diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index ab9fc507b052..86d1ce65c032 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -14,7 +14,8 @@ import printing.Highlighting.* import printing.Formatting import ErrorMessageID.* import ast.Trees -import config.{Feature, ScalaVersion} +import ast.desugar +import config.{Feature, MigrationVersion, ScalaVersion} import transform.patmat.Space import transform.patmat.SpaceEngine import typer.ErrorReporting.{err, matchReductionAddendum, substitutableTypeSymbolsInScope} @@ -23,7 +24,7 @@ import typer.Implicits.* import typer.Inferencing import scala.util.control.NonFatal import StdNames.nme -import printing.Formatting.hl +import Formatting.{hl, delay} import ast.Trees.* import ast.untpd import ast.tpd @@ -37,6 +38,7 @@ import scala.jdk.CollectionConverters.* import dotty.tools.dotc.util.SourceFile import dotty.tools.dotc.config.SourceVersion import DidYouMean.* +import Message.Disambiguation /** Messages * ======== @@ -61,7 +63,7 @@ trait ShowMatchTrace(tps: Type*)(using Context) extends Message: override def msgPostscript(using Context): String = super.msgPostscript ++ matchReductionAddendum(tps*) -abstract class TypeMismatchMsg(found: Type, expected: Type)(errorId: ErrorMessageID)(using Context) +abstract class TypeMismatchMsg(found: Type, val expected: Type)(errorId: ErrorMessageID)(using Context) extends Message(errorId), ShowMatchTrace(found, expected): def kind = MessageKind.TypeMismatch def explain(using Context) = err.whyNoMatchStr(found, expected) @@ -92,7 +94,7 @@ abstract class CyclicMsg(errorId: ErrorMessageID)(using Context) extends Message protected def debugInfo = if ctx.settings.YdebugCyclic.value then - "\n\nStacktrace:" ++ ex.getStackTrace().nn.mkString("\n ", "\n ", "") + "\n\nStacktrace:" ++ ex.getStackTrace().mkString("\n ", "\n ", "") else "\n\n Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace." protected def context: String = ex.optTrace match @@ -299,6 +301,11 @@ extends NotFoundMsg(MissingIdentID) { class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tree], addenda: => String*)(using Context) extends TypeMismatchMsg(found, expected)(TypeMismatchID): + private val shouldSuggestNN = + if ctx.mode.is(Mode.SafeNulls) && expected.isValueType then + found frozen_<:< OrNull(expected) + else false + def msg(using Context) = // replace constrained TypeParamRefs and their typevars by their bounds where possible // and the bounds are not f-bounds. @@ -356,9 +363,29 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre ++ addenda.dropWhile(_.isEmpty).headOption.getOrElse(importSuggestions) override def explain(using Context) = - val treeStr = inTree.map(x => s"\nTree: ${x.show}").getOrElse("") + val treeStr = inTree.map(x => s"\nTree:\n\n${x.show}\n").getOrElse("") treeStr + "\n" + super.explain + override def actions(using Context) = + inTree match { + case Some(tree) if shouldSuggestNN => + val content = tree.source.content().slice(tree.srcPos.startPos.start, tree.srcPos.endPos.end).mkString + val replacement = tree match + case a @ Apply(_, _) if !a.hasAttachment(desugar.WasTypedInfix) => + content + ".nn" + case _ @ (Select(_, _) | Ident(_)) => content + ".nn" + case _ => "(" + content + ").nn" + List( + CodeAction(title = """Add .nn""", + description = None, + patches = List( + ActionPatch(tree.srcPos.sourcePos, replacement) + ) + ) + ) + case _ => + List() + } end TypeMismatch class NotAMember(site: Type, val name: Name, selected: String, proto: Type, addendum: => String = "")(using Context) @@ -541,7 +568,6 @@ extends SyntaxMsg(RepeatedModifierID) { } override def actions(using Context) = - import scala.language.unsafeNulls List( CodeAction(title = s"""Remove repeated modifier: "$modifier"""", description = None, @@ -886,7 +912,6 @@ extends Message(PatternMatchExhaustivityID) { |""" override def actions(using Context) = - import scala.language.unsafeNulls val endPos = tree.cases.lastOption.map(_.endPos) .getOrElse(tree.selector.endPos) val startColumn = tree.cases.lastOption @@ -909,7 +934,6 @@ extends Message(PatternMatchExhaustivityID) { private def indent(text:String, margin: Int): String = { - import scala.language.unsafeNulls " " * margin + text } } @@ -1172,6 +1196,7 @@ class OverrideError( member: Symbol, other: Symbol, memberTp: Type, otherTp: Type)(using Context) extends DeclarationMsg(OverrideErrorID), NoDisambiguation: + withDisambiguation(Disambiguation.AllExcept(List(member.name.toString))) def msg(using Context) = val isConcreteOverAbstract = (other.owner isSubClass member.owner) && other.is(Deferred) && !member.is(Deferred) @@ -1181,8 +1206,8 @@ extends DeclarationMsg(OverrideErrorID), NoDisambiguation: |(Note that ${err.infoStringWithLocation(other, base)} is abstract, |and is therefore overridden by concrete ${err.infoStringWithLocation(member, base)})""" else "" - i"""error overriding ${err.infoStringWithLocation(other, base)}; - | ${err.infoString(member, base, showLocation = member.owner != base.typeSymbol)} $core$addendum""" + i"""error overriding ${delay(err.infoStringWithLocation(other, base))}; + | ${delay(err.infoString(member, base, showLocation = member.owner != base.typeSymbol))} $core$addendum""" override def canExplain = memberTp.exists && otherTp.exists def explain(using Context) = @@ -1593,6 +1618,14 @@ class MissingArgument(pname: Name, methString: String)(using Context) else s"missing argument for parameter $pname of $methString" def explain(using Context) = "" +class MissingImplicitParameterInEmptyArguments(pname: Name, methString: String)(using Context) + extends MissingArgument(pname, methString): + override def msg(using Context) = + val mv = MigrationVersion.ImplicitParamsWithoutUsing + super.msg.concat(Message.rewriteNotice("This code", mv.patchFrom)) // patch emitted up the stack + override def explain(using Context) = + "Old-style implicit argument lists may be omitted but not empty; this syntax was corrected in 3.7." + class MissingArgumentList(method: String, sym: Symbol)(using Context) extends TypeMsg(MissingArgumentListID) { def msg(using Context) = @@ -1989,7 +2022,6 @@ class OnlyFunctionsCanBeFollowedByUnderscore(tp: Type, tree: untpd.PostfixOp)(us |To convert to a function value, you need to explicitly write ${hl("() => x")}""" override def actions(using Context) = - import scala.language.unsafeNulls val untpd.PostfixOp(qual, Ident(nme.WILDCARD)) = tree: @unchecked List( CodeAction(title = "Rewrite to function value", @@ -2019,7 +2051,6 @@ class MissingEmptyArgumentList(method: String, tree: tpd.Tree)(using Context) } override def actions(using Context) = - import scala.language.unsafeNulls List( CodeAction(title = "Insert ()", description = None, @@ -2094,8 +2125,27 @@ extends NamingMsg(AlreadyDefinedID): i" in ${conflicting.associatedFile}" else if conflicting.owner == owner then "" else i" in ${conflicting.owner}" + def print(tpe: Type): String = + def addParams(tpe: Type): List[String] = tpe match + case tpe: MethodType => + val s = if tpe.isContextualMethod then i"(${tpe.paramInfos}%, %) =>" else "" + s :: addParams(tpe.resType) + case tpe: PolyType => + i"[${tpe.paramNames}%, %] =>" :: addParams(tpe.resType) + case tpe => + i"$tpe" :: Nil + addParams(tpe).mkString(" ") def note = - if owner.is(Method) || conflicting.is(Method) then + if conflicting.is(Given) && name.startsWith("given_") then + i"""| + | + |Provide an explicit, unique name to given definitions, + |since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: ${print(atPhase(typerPhase)(conflicting.info))} // define an instance + | given myGiven @ ${print(atPhase(typerPhase)(conflicting.info))} // as a pattern variable + |""" + else if owner.is(Method) || conflicting.is(Method) then "\n\nNote that overloaded methods must all be defined in the same group of toplevel definitions" else "" if conflicting.isTerm != name.isTermName then @@ -2333,13 +2383,17 @@ class SymbolIsNotAValue(symbol: Symbol)(using Context) extends TypeMsg(SymbolIsN } class DoubleDefinition(decl: Symbol, previousDecl: Symbol, base: Symbol)(using Context) -extends NamingMsg(DoubleDefinitionID) { +extends NamingMsg(DoubleDefinitionID): + import Signature.MatchDegree.* + + private def erasedType: Type = + if ctx.erasedTypes then decl.info + else TypeErasure.transformInfo(decl, decl.info) + def msg(using Context) = { def nameAnd = if (decl.name != previousDecl.name) " name and" else "" - def erasedType = if ctx.erasedTypes then i" ${decl.info}" else "" def details(using Context): String = if (decl.isRealMethod && previousDecl.isRealMethod) { - import Signature.MatchDegree.* // compare the signatures when both symbols represent methods decl.signature.matchDegree(previousDecl.signature) match { @@ -2364,7 +2418,7 @@ extends NamingMsg(DoubleDefinitionID) { |Consider adding a @targetName annotation to one of the conflicting definitions |for disambiguation.""" else "" - i"have the same$nameAnd type$erasedType after erasure.$hint" + i"have the same$nameAnd type $erasedType after erasure.$hint" } } else "" @@ -2377,7 +2431,7 @@ extends NamingMsg(DoubleDefinitionID) { } val clashDescription = if (decl.owner eq previousDecl.owner) - "Double definition" + "Conflicting definitions" else if ((decl.owner eq base) || (previousDecl eq base)) "Name clash between defined and inherited member" else @@ -2390,8 +2444,62 @@ extends NamingMsg(DoubleDefinitionID) { |""" } + details } - def explain(using Context) = "" -} + def explain(using Context) = + def givenAddendum = + def isGivenName(sym: Symbol) = sym.name.startsWith("given_") // Desugar.inventGivenName + def print(tpe: Type): String = + def addParams(tpe: Type): List[String] = tpe match + case tpe: MethodType => + val s = if tpe.isContextualMethod then i"(${tpe.paramInfos}%, %) =>" else "" + s :: addParams(tpe.resType) + case tpe: PolyType => + i"[${tpe.paramNames}%, %] =>" :: addParams(tpe.resType) + case tpe => + i"$tpe" :: Nil + addParams(tpe).mkString(" ") + if decl.is(Given) && previousDecl.is(Given) && isGivenName(decl) && isGivenName(previousDecl) then + i"""| Provide an explicit, unique name to given definitions, + | since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: ${print(atPhase(typerPhase)(decl.info))} + |""" + else "" + decl.signature.matchDegree(previousDecl.signature) match + case FullMatch => + i""" + |As part of the Scala compilation pipeline every type is reduced to its erased + |(runtime) form. In this phase, among other transformations, generic parameters + |disappear and separate parameter-list boundaries are flattened. + | + |For example, both `f[T](x: T)(y: String): Unit` and `f(x: Any, z: String): Unit` + |erase to the same runtime signature `f(x: Object, y: String): Unit`. Note that + |parameter names are irrelevant. + | + |In your code the two declarations + | + | ${atPhase(typerPhase)(previousDecl.showDcl)} + | ${atPhase(typerPhase)(decl.showDcl)} + | + |erase to the identical signature + | + | ${erasedType} + | + |so the compiler cannot keep both: the generated bytecode symbols would collide. + | + |To fix this error, you must disambiguate the two definitions by doing one of the following: + | + |1. Rename one of the definitions.$givenAddendum + |2. Keep the same names in source but give one definition a distinct + | bytecode-level name via `@targetName`; for example: + | + | @targetName("${decl.name.show}_2") + | ${atPhase(typerPhase)(decl.showDcl)} + | + |Choose the `@targetName` argument carefully: it is the name that will be used + |when calling the method externally, so it should be unique and descriptive. + |""" + case _ => "" +end DoubleDefinition class ImportedTwice(sel: Name)(using Context) extends SyntaxMsg(ImportedTwiceID) { def msg(using Context) = s"${sel.show} is imported twice on the same import line." @@ -2779,7 +2887,7 @@ class AnonymousInstanceCannotBeEmpty(impl: untpd.Template)(using Context) |""" } -class ModifierNotAllowedForDefinition(flag: Flag, explanation: String = "")(using Context) +class ModifierNotAllowedForDefinition(flag: Flag, explanation: => String = "")(using Context) extends SyntaxMsg(ModifierNotAllowedForDefinitionID) { def msg(using Context) = i"Modifier ${hl(flag.flagsString)} is not allowed for this definition" def explain(using Context) = explanation @@ -3140,7 +3248,7 @@ extends ReferenceMsg(CannotBeAccessedID): case _ => i"none of the overloaded alternatives named $name can" val where = if (ctx.owner.exists) i" from ${ctx.owner.enclosingClass}" else "" - val whyNot = new StringBuffer + val whyNot = new StringBuilder for alt <- alts do val cls = alt.owner.enclosingSubClass val owner = if cls.exists then cls else alt.owner @@ -3148,10 +3256,10 @@ extends ReferenceMsg(CannotBeAccessedID): if alt.is(Protected) then if alt.privateWithin.exists && alt.privateWithin != owner then if owner.is(Final) then alt.privateWithin.showLocated - else alt.privateWithin.showLocated + ", or " + owner.showLocated + " or one of its subclasses" + else s"${alt.privateWithin.showLocated}, or ${owner.showLocated} or one of its subclasses" else if owner.is(Final) then owner.showLocated - else owner.showLocated + " or one of its subclasses" + else s"${owner.showLocated} or one of its subclasses" else alt.privateWithin.orElse(owner).showLocated val accessMod = if alt.is(Protected) then "protected" else "private" @@ -3275,7 +3383,7 @@ extends SyntaxMsg(VolatileOnValID): protected def explain(using Context): String = "" class ConstructorProxyNotValue(sym: Symbol)(using Context) -extends TypeMsg(ConstructorProxyNotValueID): +extends TypeMsg(PhantomSymbolNotValueID): protected def msg(using Context): String = i"constructor proxy $sym cannot be used as a value" protected def explain(using Context): String = @@ -3290,7 +3398,7 @@ extends TypeMsg(ConstructorProxyNotValueID): |but not as a stand-alone value.""" class ContextBoundCompanionNotValue(sym: Symbol)(using Context) -extends TypeMsg(ConstructorProxyNotValueID): +extends TypeMsg(PhantomSymbolNotValueID): protected def msg(using Context): String = i"context bound companion $sym cannot be used as a value" protected def explain(using Context): String = @@ -3309,24 +3417,48 @@ 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 DummyCaptureParamNotValue(sym: Symbol)(using Context) +extends TypeMsg(PhantomSymbolNotValueID): + protected def msg(using Context): String = + i"dummy term capture parameter $sym cannot be used as a value" + protected def explain(using Context): String = + i"""A term capture parameter is a symbol made up by the compiler to represent a reference + |to a real capture parameter in capture sets. For instance, in + | + | class A: + | type C^ + | + |there is just a type `A` declared but not a value `A`. Nevertheless, one can write + |the selection `(a: A).C` and use a a value, which works because the compiler created a + |term capture parameter for `C`. However, these term capture parameters are not real values, + |they can only be referred in capture sets.""" + class UnusedSymbol(errorText: String, val actions: List[CodeAction] = Nil)(using Context) -extends Message(UnusedSymbolID) { +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(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 localVars(using Context): UnusedSymbol = UnusedSymbol(i"local variable was mutated but not read") + def explicitParams(sym: Symbol)(using Context): UnusedSymbol = + UnusedSymbol(i"unused explicit parameter${paramAddendum(sym)}") + def implicitParams(sym: Symbol)(using Context): UnusedSymbol = + UnusedSymbol(i"unused implicit parameter${paramAddendum(sym)}") def privateMembers(using Context): UnusedSymbol = UnusedSymbol(i"unused private member") + def privateVars(using Context): UnusedSymbol = UnusedSymbol(i"private variable was mutated but not read") 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") + 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") + private def paramAddendum(sym: Symbol)(using Context): String = + if sym.denot.owner.is(ExtensionMethod) then i" in extension ${sym.denot.owner}" + else "" class NonNamedArgumentInJavaAnnotation(using Context) extends SyntaxMsg(NonNamedArgumentInJavaAnnotationID): @@ -3368,6 +3500,18 @@ final class QuotedTypeMissing(tpe: Type)(using Context) extends StagingMessage(Q end QuotedTypeMissing +final class CannotInstantiateQuotedTypeVar(symbol: Symbol)(using patternCtx: Context) extends StagingMessage(CannotInstantiateQuotedTypeVarID): + override protected def msg(using Context): String = + i"""Quoted pattern type variable `${symbol.name}` cannot be instantiated. + |If you meant to refer to a class named `${symbol.name}`, wrap it in backticks. + |If you meant to introduce a binding, this is not allowed after `new`. You might + |want to use the lower-level `quotes.reflect` API instead. + |Read more about type variables in quoted pattern in the Scala documentation: + |https://docs.scala-lang.org/scala3/guides/macros/quotes.html#type-variables-in-quoted-patterns + """ + + override protected def explain(using Context): String = "" + final class DeprecatedAssignmentSyntax(key: Name, value: untpd.Tree)(using Context) extends SyntaxMsg(DeprecatedAssignmentSyntaxID): override protected def msg(using Context): String = i"""Deprecated syntax: since 3.7 this is interpreted as a named tuple with one element, @@ -3487,3 +3631,113 @@ class MatchIsNotPartialFunction(using Context) extends SyntaxMsg(MatchIsNotParti | |Efficient operations will use `applyOrElse` to avoid computing the match twice, |but the `apply` body would be executed "per element" in the example.""" + +final class PointlessAppliedConstructorType(tpt: untpd.Tree, args: List[untpd.Tree], tpe: Type)(using Context) extends TypeMsg(PointlessAppliedConstructorTypeID): + override protected def msg(using Context): String = + val act = i"$tpt(${args.map(_.show).mkString(", ")})" + i"""|Applied constructor type $act has no effect. + |The resulting type of $act is the same as its base type, namely: $tpe""".stripMargin + + override protected def explain(using Context): String = + i"""|Applied constructor types are used to ascribe specialized types of constructor applications. + |To benefit from this feature, the constructor in question has to have a more specific type than the class itself. + | + |If you want to track a precise type of any of the class parameters, make sure to mark the parameter as `tracked`. + |Otherwise, you can safely remove the argument list from the type. + |""" + +final class OnlyFullyDependentAppliedConstructorType()(using Context) + extends TypeMsg(OnlyFullyDependentAppliedConstructorTypeID): + override protected def msg(using Context): String = + i"Applied constructor type can only be used with classes where all parameters in the first parameter list are tracked" + + override protected def explain(using Context): String = "" + +final class IllegalContextBounds(using Context) extends SyntaxMsg(IllegalContextBoundsID): + override protected def msg(using Context): String = + i"Context bounds are not allowed in this position" + + override protected def explain(using Context): String = "" + +final class NamedPatternNotApplicable(selectorType: Type)(using Context) extends PatternMatchMsg(NamedPatternNotApplicableID): + override protected def msg(using Context): String = + i"Named patterns cannot be used with $selectorType, because it is not a named tuple or case class" + + override protected def explain(using Context): String = "" + +/** @param reason The reason for the unnecessary null. The warning given to the user will be i""""Unncessary .nn: $reason""" + * @param sourcePosition The sourcePosition of the qualifier + */ +class UnnecessaryNN(reason: String, sourcePosition: SourcePosition)(using Context) extends SyntaxMsg(UnnecessaryNN) { + override def msg(using Context) = i"""Unnecessary .nn: $reason""" + + override def explain(using Context) = "" + + private val nnSourcePosition = SourcePosition(sourcePosition.source, Span(sourcePosition.span.end, sourcePosition.span.end + 3, sourcePosition.span.end), sourcePosition.outer) + + override def actions(using Context) = + List( + CodeAction(title = """Remove unnecessary .nn""", + description = None, + patches = List( + ActionPatch(nnSourcePosition, "") + ) + ) + ) +} + +final class ErasedNotPure(tree: tpd.Tree, isArgument: Boolean, isImplicit: Boolean)(using Context) extends TypeMsg(ErasedNotPureID): + def what = + if isArgument then s"${if isImplicit then "implicit " else ""}argument to an erased parameter" + else "right-hand-side of an erased value" + override protected def msg(using Context): String = + i"$what fails to be a pure expression" + + override protected def explain(using Context): String = + def alternatives = + if tree.symbol == defn.Compiletime_erasedValue then + i"""An accepted (but unsafe) alternative for this expression uses function + | + | caps.unsafe.unsafeErasedValue + | + |instead.""" + else + """A pure expression is an expression that is clearly side-effect free and terminating. + |Some examples of pure expressions are: + | - literals, + | - references to values, + | - side-effect-free instance creations, + | - applications of inline functions to pure arguments.""" + + i"""The $what must be a pure expression, but I found: + | + | $tree + | + |This expression is not classified to be pure. + |$alternatives""" +end ErasedNotPure + +final class IllegalErasedDef(sym: Symbol)(using Context) extends TypeMsg(IllegalErasedDefID): + override protected def msg(using Context): String = + def notAllowed = "`erased` is not allowed for this kind of definition." + def result = if sym.is(Method) then " result" else "" + if sym.is(Erased) then notAllowed + else + i"""$sym is implicitly `erased` since its$result type extends trait `compiletime.Erased`. + |But $notAllowed""" + + override protected def explain(using Context): String = + "Only non-lazy immutable values can be `erased`" +end IllegalErasedDef + +final class DefaultShadowsGiven(name: Name)(using Context) extends TypeMsg(DefaultShadowsGivenID): + override protected def msg(using Context): String = + i"Argument for implicit parameter $name was supplied using a default argument." + override protected def explain(using Context): String = + "Usually the given in scope is intended, but you must specify it after explicit `using`." + +final class RecurseWithDefault(name: Name)(using Context) extends TypeMsg(RecurseWithDefaultID): + override protected def msg(using Context): String = + i"Recursive call used a default argument for parameter $name." + override protected def explain(using Context): String = + "It's more explicit to pass current or modified arguments in a recursion." diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala index 7f05cffb422a..c557a6e4544c 100644 --- a/compiler/src/dotty/tools/dotc/reporting/trace.scala +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -2,8 +2,6 @@ package dotty.tools package dotc package reporting -import scala.language.unsafeNulls - import core.*, Contexts.*, Decorators.* import config.* import printing.Formatting.* diff --git a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala index 3c7216625a7c..da6788901f0d 100644 --- a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala +++ b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala @@ -7,7 +7,7 @@ import core.Contexts.* import collection.mutable import scala.annotation.tailrec import dotty.tools.dotc.reporting.Reporter -import dotty.tools.dotc.util.SourcePosition; +import dotty.tools.dotc.util.SourcePosition import java.io.OutputStreamWriter import java.nio.charset.StandardCharsets.UTF_8 @@ -43,8 +43,8 @@ object Rewrites { pbuf.filterInPlace(x => !p(x.span)) def apply(cs: Array[Char]): Array[Char] = { - val delta = pbuf.map(_.delta).sum - val patches = pbuf.toList.sortBy(_.span.start) + val patches = pbuf.toList.distinct.sortBy(_.span.start) + val delta = patches.map(_.delta).sum if (patches.nonEmpty) patches.reduceLeft {(p1, p2) => assert(p1.span.end <= p2.span.start, s"overlapping patches in $source: $p1 and $p2") diff --git a/compiler/src/dotty/tools/dotc/sbt/APIUtils.scala b/compiler/src/dotty/tools/dotc/sbt/APIUtils.scala index 07fa2027fbe6..42843a89c0ab 100644 --- a/compiler/src/dotty/tools/dotc/sbt/APIUtils.scala +++ b/compiler/src/dotty/tools/dotc/sbt/APIUtils.scala @@ -1,7 +1,6 @@ package dotty.tools.dotc package sbt -import scala.language.unsafeNulls import core.* import Contexts.* @@ -18,10 +17,10 @@ import xsbti.api.SafeLazy.strict */ object APIUtils { private object Constants { - val PublicAccess = api.Public.create() - val EmptyModifiers = new api.Modifiers(false, false, false, false, false, false, false, false) - val EmptyStructure = api.Structure.of(strict(Array.empty), strict(Array.empty), strict(Array.empty)) - val EmptyType = api.EmptyType.of() + val PublicAccess: api.Public = api.Public.create() + val EmptyModifiers: api.Modifiers = new api.Modifiers(false, false, false, false, false, false, false, false) + val EmptyStructure: api.Structure = api.Structure.of(strict(Array.empty), strict(Array.empty), strict(Array.empty)) + val EmptyType: api.EmptyType = api.EmptyType.of() } import Constants.* diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 4d915b57df1b..5f3eeff91ff3 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -232,13 +232,13 @@ private class ExtractAPICollector(nonLocalClassSymbols: mutable.HashSet[Symbol]) private object Constants { val emptyStringArray = Array[String]() - val local = api.ThisQualifier.create() - val public = api.Public.create() - val privateLocal = api.Private.create(local) - val protectedLocal = api.Protected.create(local) - val unqualified = api.Unqualified.create() - val thisPath = api.This.create() - val emptyType = api.EmptyType.create() + val local: api.ThisQualifier = api.ThisQualifier.create() + val public: api.Public = api.Public.create() + val privateLocal: api.Private = api.Private.create(local) + val protectedLocal: api.Protected = api.Protected.create(local) + val unqualified: api.Unqualified = api.Unqualified.create() + val thisPath: api.This = api.This.create() + val emptyType: api.EmptyType = api.EmptyType.create() val emptyModifiers = new api.Modifiers(false, false, false, false, false,false, false, false) } diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala index 154d50f8ebc2..8b3b217cb0fc 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala @@ -577,7 +577,7 @@ class DependencyRecorder { clazz } - private var _responsibleForImports: Symbol = uninitialized + private[dotc] var _responsibleForImports: Symbol | Null = null /** Top level import dependencies are registered as coming from a first top level * class/trait/object declared in the compilation unit. If none exists, issue a warning and return NoSymbol. diff --git a/compiler/src/dotty/tools/dotc/sbt/ShowAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ShowAPI.scala index 61baebbe9517..a16c135deef7 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ShowAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ShowAPI.scala @@ -9,8 +9,6 @@ package dotty.tools.dotc package sbt -import scala.language.unsafeNulls - import xsbti.api.* import scala.util.Try diff --git a/compiler/src/dotty/tools/dotc/sbt/package.scala b/compiler/src/dotty/tools/dotc/sbt/package.scala index 8efa25569325..e835a4b95fee 100644 --- a/compiler/src/dotty/tools/dotc/sbt/package.scala +++ b/compiler/src/dotty/tools/dotc/sbt/package.scala @@ -39,6 +39,6 @@ extension (sym: Symbol) // names in the global chars array. But we would need to restructure // ExtractDependencies caches to avoid expensive `toString` on // each member reference. - termName(sym.owner.fullName.mangledString.replace(".", ";").nn ++ ";init;") + termName(sym.owner.fullName.mangledString.replace(".", ";") ++ ";init;") else sym.name.stripModuleClassSuffix diff --git a/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala b/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala index 05dff8ffadbc..b07963b28182 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala @@ -63,7 +63,7 @@ class ExtractSemanticDB private (phaseMode: ExtractSemanticDB.PhaseMode) extends private def computeDiagnostics( sourceRoot: String, - warnings: Map[SourceFile, List[Warning]], + warnings: Map[SourceFile, List[dotty.tools.dotc.reporting.Diagnostic]], append: ((Path, List[Diagnostic])) => Unit)(using Context): Boolean = monitor(phaseName) { val unit = ctx.compilationUnit warnings.get(unit.source).foreach { ws => @@ -104,14 +104,14 @@ class ExtractSemanticDB private (phaseMode: ExtractSemanticDB.PhaseMode) extends val appendDiagnostics = phaseMode == ExtractSemanticDB.PhaseMode.AppendDiagnostics val unitContexts = units.map(ctx.fresh.setCompilationUnit(_).withRootImports) if (appendDiagnostics) - val warnings = ctx.reporter.allWarnings.groupBy(w => w.pos.source) + val warningsAndInfos = (ctx.reporter.allWarnings ++ ctx.reporter.allInfos).groupBy(w => w.pos.source) val buf = mutable.ListBuffer.empty[(Path, Seq[Diagnostic])] val units0 = - for unitCtx <- unitContexts if computeDiagnostics(sourceRoot, warnings, buf += _)(using unitCtx) + for unitCtx <- unitContexts if computeDiagnostics(sourceRoot, warningsAndInfos, buf += _)(using unitCtx) yield unitCtx.compilationUnit cancellable { - buf.toList.asJava.parallelStream().forEach { case (out, warnings) => - ExtractSemanticDB.appendDiagnostics(warnings, out) + buf.toList.asJava.parallelStream().forEach { case (out, diagnostics) => + ExtractSemanticDB.appendDiagnostics(diagnostics, out) } } units0 @@ -262,7 +262,7 @@ object ExtractSemanticDB: private def excludeSymbol(sym: Symbol)(using Context): Boolean = !sym.exists - || sym.is(ConstructorProxy) + || sym.is(PhantomSymbol) || sym.name.isWildcard || excludeQual(sym) @@ -320,7 +320,7 @@ object ExtractSemanticDB: registerDefinition(tree.symbol, selectSpan(tree), Set.empty, tree.source) case tree => registerDefinition(tree.symbol, tree.span, Set.empty, tree.source) case tree: NamedDefTree => - if !tree.symbol.isAllOf(ModuleValCreationFlags) then + if tree.symbol.exists && !tree.symbol.isAllOf(ModuleValCreationFlags) then tree match { case tree: ValDef if tree.symbol.isAllOf(EnumValue) => tree.rhs match @@ -379,7 +379,7 @@ object ExtractSemanticDB: traverseAnnotsOfDefinition(ctorSym) ctorParams(tree.constr.termParamss, tree.constr.leadingTypeParams, tree.body) registerDefinition(ctorSym, tree.constr.nameSpan.startPos, Set.empty, tree.source) - case tree: Apply => + case tree: Apply if tree.fun.symbol.exists => @tu lazy val genParamSymbol: Name => String = tree.fun.symbol.funParamSymbol traverse(tree.fun) synth.tryFindSynthetic(tree).foreach(synthetics.addOne) diff --git a/compiler/src/dotty/tools/dotc/semanticdb/Scala3.scala b/compiler/src/dotty/tools/dotc/semanticdb/Scala3.scala index f49b00089712..a20a0d181a55 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/Scala3.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/Scala3.scala @@ -420,7 +420,7 @@ object Scala3: else Descriptor.None def unescapeUnicode = - unicodeEscape.replaceAllIn(symbol, m => String.valueOf(Integer.parseInt(m.group(1), 16).toChar).nn) + unicodeEscape.replaceAllIn(symbol, m => String.valueOf(Integer.parseInt(m.group(1), 16).toChar)) def isJavaIdent = symbol.nonEmpty && isJavaIdentifierStart(symbol.head) && symbol.tail.forall(isJavaIdentifierPart) diff --git a/compiler/src/dotty/tools/dotc/semanticdb/SyntheticsExtractor.scala b/compiler/src/dotty/tools/dotc/semanticdb/SyntheticsExtractor.scala index af38315a857e..c6cf834c6959 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/SyntheticsExtractor.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/SyntheticsExtractor.scala @@ -72,11 +72,14 @@ class SyntheticsExtractor: range(tree.span, tree.source), s.ApplyTree( tree.fun.toSemanticOriginal, - tree.args.map(_.toSemanticTree) + tree.args.map(_.toSemanticTree), + SymbolInformation.Property.GIVEN.value ) ).toOpt - case tree: Apply if tree.fun.symbol.is(Implicit) => + case tree: Apply + if tree.fun.symbol.is(Implicit) || + (tree.fun.symbol.name == nme.apply && tree.fun.span.isSynthetic) => val pos = range(tree.span, tree.source) s.Synthetic( pos, diff --git a/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala b/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala index 2d98535657a2..11467e216aba 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala @@ -96,7 +96,11 @@ class TypeOps: // We try to find the "actual" binder of : `inner`, // and register them to the symbol table with `(, inner) -> ` // instead of `("y", outer) -> ` - if lam.paramNames.contains(sym.name) then + // We must also check for parameter shadowing such as def shadowParam(x: Int) = {val x = true} + // We skip param symbol check if owner is not a LambdaType for proper MatchType paramRef entry in the paramRefSymtab + // for more information: https://github.com/scala/scala3/pull/23161#discussion_r2097755983 + + if (sym.is(Flags.Param) || !sym.owner.info.isInstanceOf[LambdaType]) && lam.paramNames.contains(sym.name) then paramRefSymtab((lam, sym.name)) = sym else enterParamRef(lam.resType) diff --git a/compiler/src/dotty/tools/dotc/semanticdb/generated/Tree.scala b/compiler/src/dotty/tools/dotc/semanticdb/generated/Tree.scala index 310e9c010826..550b839b67c4 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/generated/Tree.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/generated/Tree.scala @@ -318,10 +318,14 @@ object TreeMessage extends SemanticdbGeneratedMessageCompanion[dotty.tools.dotc // @@protoc_insertion_point(GeneratedMessageCompanion[dotty.tools.dotc.semanticdb.Tree]) } +/** @param properties + * bitmask of SymbolInformation.Property + */ @SerialVersionUID(0L) final case class ApplyTree( function: dotty.tools.dotc.semanticdb.Tree = dotty.tools.dotc.semanticdb.ApplyTree._typemapper_function.toCustom(dotty.tools.dotc.semanticdb.TreeMessage.defaultInstance), - arguments: _root_.scala.Seq[dotty.tools.dotc.semanticdb.Tree] = _root_.scala.Seq.empty + arguments: _root_.scala.Seq[dotty.tools.dotc.semanticdb.Tree] = _root_.scala.Seq.empty, + properties: _root_.scala.Int = 0 ) extends dotty.tools.dotc.semanticdb.Tree.NonEmpty with SemanticdbGeneratedMessage derives CanEqual { @transient @sharable private var __serializedSizeMemoized: _root_.scala.Int = 0 @@ -338,6 +342,13 @@ final case class ApplyTree( val __value = dotty.tools.dotc.semanticdb.ApplyTree._typemapper_arguments.toBase(__item) __size += 1 + SemanticdbOutputStream.computeUInt32SizeNoTag(__value.serializedSize) + __value.serializedSize } + + { + val __value = properties + if (__value != 0) { + __size += SemanticdbOutputStream.computeInt32Size(3, __value) + } + }; __size } override def serializedSize: _root_.scala.Int = { @@ -364,12 +375,19 @@ final case class ApplyTree( _output__.writeUInt32NoTag(__m.serializedSize) __m.writeTo(_output__) }; + { + val __v = properties + if (__v != 0) { + _output__.writeInt32(3, __v) + } + }; } def withFunction(__v: dotty.tools.dotc.semanticdb.Tree): ApplyTree = copy(function = __v) def clearArguments = copy(arguments = _root_.scala.Seq.empty) def addArguments(__vs: dotty.tools.dotc.semanticdb.Tree *): ApplyTree = addAllArguments(__vs) def addAllArguments(__vs: Iterable[dotty.tools.dotc.semanticdb.Tree]): ApplyTree = copy(arguments = arguments ++ __vs) def withArguments(__v: _root_.scala.Seq[dotty.tools.dotc.semanticdb.Tree]): ApplyTree = copy(arguments = __v) + def withProperties(__v: _root_.scala.Int): ApplyTree = copy(properties = __v) @@ -382,6 +400,7 @@ object ApplyTree extends SemanticdbGeneratedMessageCompanion[dotty.tools.dotc.s def parseFrom(`_input__`: SemanticdbInputStream): dotty.tools.dotc.semanticdb.ApplyTree = { var __function: _root_.scala.Option[dotty.tools.dotc.semanticdb.TreeMessage] = _root_.scala.None val __arguments: _root_.scala.collection.immutable.VectorBuilder[dotty.tools.dotc.semanticdb.Tree] = new _root_.scala.collection.immutable.VectorBuilder[dotty.tools.dotc.semanticdb.Tree] + var __properties: _root_.scala.Int = 0 var _done__ = false while (!_done__) { val _tag__ = _input__.readTag() @@ -391,12 +410,15 @@ object ApplyTree extends SemanticdbGeneratedMessageCompanion[dotty.tools.dotc.s __function = _root_.scala.Some(__function.fold(LiteParser.readMessage[dotty.tools.dotc.semanticdb.TreeMessage](_input__))(LiteParser.readMessage(_input__, _))) case 18 => __arguments += dotty.tools.dotc.semanticdb.ApplyTree._typemapper_arguments.toCustom(LiteParser.readMessage[dotty.tools.dotc.semanticdb.TreeMessage](_input__)) + case 24 => + __properties = _input__.readInt32() case tag => _input__.skipField(tag) } } dotty.tools.dotc.semanticdb.ApplyTree( function = dotty.tools.dotc.semanticdb.ApplyTree._typemapper_function.toCustom(__function.getOrElse(dotty.tools.dotc.semanticdb.TreeMessage.defaultInstance)), - arguments = __arguments.result() + arguments = __arguments.result(), + properties = __properties ) } @@ -407,20 +429,24 @@ object ApplyTree extends SemanticdbGeneratedMessageCompanion[dotty.tools.dotc.s lazy val defaultInstance = dotty.tools.dotc.semanticdb.ApplyTree( function = dotty.tools.dotc.semanticdb.ApplyTree._typemapper_function.toCustom(dotty.tools.dotc.semanticdb.TreeMessage.defaultInstance), - arguments = _root_.scala.Seq.empty + arguments = _root_.scala.Seq.empty, + properties = 0 ) final val FUNCTION_FIELD_NUMBER = 1 final val ARGUMENTS_FIELD_NUMBER = 2 + final val PROPERTIES_FIELD_NUMBER = 3 @transient @sharable private[semanticdb] val _typemapper_function: SemanticdbTypeMapper[dotty.tools.dotc.semanticdb.TreeMessage, dotty.tools.dotc.semanticdb.Tree] = implicitly[SemanticdbTypeMapper[dotty.tools.dotc.semanticdb.TreeMessage, dotty.tools.dotc.semanticdb.Tree]] @transient @sharable private[semanticdb] val _typemapper_arguments: SemanticdbTypeMapper[dotty.tools.dotc.semanticdb.TreeMessage, dotty.tools.dotc.semanticdb.Tree] = implicitly[SemanticdbTypeMapper[dotty.tools.dotc.semanticdb.TreeMessage, dotty.tools.dotc.semanticdb.Tree]] def of( function: dotty.tools.dotc.semanticdb.Tree, - arguments: _root_.scala.Seq[dotty.tools.dotc.semanticdb.Tree] + arguments: _root_.scala.Seq[dotty.tools.dotc.semanticdb.Tree], + properties: _root_.scala.Int ): _root_.dotty.tools.dotc.semanticdb.ApplyTree = _root_.dotty.tools.dotc.semanticdb.ApplyTree( function, - arguments + arguments, + properties ) // @@protoc_insertion_point(GeneratedMessageCompanion[dotty.tools.dotc.semanticdb.ApplyTree]) } diff --git a/compiler/src/dotty/tools/dotc/staging/HealType.scala b/compiler/src/dotty/tools/dotc/staging/HealType.scala index a73f884fbac9..509049e131c8 100644 --- a/compiler/src/dotty/tools/dotc/staging/HealType.scala +++ b/compiler/src/dotty/tools/dotc/staging/HealType.scala @@ -76,7 +76,7 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap { tp match case tp @ NamedType(NoPrefix, _) if level > levelOf(tp.symbol) => tp.symbol case tp: NamedType if !tp.symbol.isStatic => levelInconsistentRootOfPath(tp.prefix) - case tp: ThisType if level > levelOf(tp.cls) => tp.cls + case tp: ThisType if level > levelOf(tp.cls) && !tp.cls.isRefinementClass => tp.cls case _ => NoSymbol /** Try to heal reference to type `T` used in a higher level than its definition. diff --git a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala index 1f9334164496..820651f0ce04 100644 --- a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala +++ b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala @@ -141,8 +141,13 @@ abstract class AccessProxies { if accessorClass.is(Package) then accessorClass = ctx.owner.topLevelClass val accessorName = accessorNameOf(accessed.name, accessorClass) + val mappedInfo = accessed.info match + // TypeRef pointing to module class seems to not be stable, so we remap that to a TermRef + // see test i22593.scala (and issue #i22593) + case tref @ TypeRef(prefix, _) if tref.symbol.is(Module) => TermRef(prefix, tref.symbol.companionModule) + case other => other val accessorInfo = - accessed.info.ensureMethodic.asSeenFrom(accessorClass.thisType, accessed.owner) + mappedInfo.ensureMethodic.asSeenFrom(accessorClass.thisType, accessed.owner) val accessor = accessorSymbol(accessorClass, accessorName, accessorInfo, accessed) rewire(reference, accessor) } diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index fafb8ea72c1c..b4098ef24221 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -1,27 +1,26 @@ -package dotty.tools.dotc.transform - -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.Flags.* -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.{CodeAction, UnusedSymbol} -import dotty.tools.dotc.rewrites.Rewrites -import dotty.tools.dotc.transform.MegaPhase.MiniPhase -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.* +package dotty.tools.dotc +package transform + +import ast.*, desugar.{ForArtifact, PatternVar}, tpd.*, untpd.ImportSelector +import config.ScalaSettings +import core.*, Contexts.*, Decorators.*, Flags.* +import Names.{Name, SimpleName, DerivedName, TermName, termName} +import NameKinds.{BodyRetainerName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName} +import NameOps.{isAnonymousFunctionName, isReplWrapperName, setterName} +import Scopes.newScope +import StdNames.nme +import Symbols.{ClassSymbol, NoSymbol, Symbol, defn, isDeprecated, requiredClass, requiredModule} +import Types.* +import reporting.{CodeAction, UnusedSymbol} +import rewrites.Rewrites + +import MegaPhase.MiniPhase +import typer.{ImportInfo, Typer} +import typer.Deriving.OriginalTypeClass +import typer.Implicits.{ContextualImplicits, RenamedImplicitRef} +import util.{Property, Spans, SrcPos}, Spans.Span +import util.Chars.{isLineBreakChar, isWhitespace} +import util.chaining.* import java.util.IdentityHashMap @@ -52,26 +51,28 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha tree override def transformIdent(tree: Ident)(using Context): tree.type = + refInfos.isAssignment = tree.hasAttachment(AssignmentTarget) if tree.symbol.exists then // 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 + tree.srcPos.isUserCode(using if tree.hasAttachment(InlinedParameter) then ctx.outer else ctx) + || tree.srcPos.isZeroExtentSynthetic // take as summonInline + if !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) + resolveUsage(prefix.classSymbol, nme.NO_NAME, NoPrefix, imports = resolving) 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) + resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject, imports = resolving) else if tree.hasType then resolveUsage(tree.tpe.classSymbol, tree.name, tree.tpe.importPrefix.skipPackageObject) + refInfos.isAssignment = false tree // import x.y; y may be rewritten x.y, also import x.z as y override def transformSelect(tree: Select)(using Context): tree.type = + refInfos.isAssignment = tree.hasAttachment(AssignmentTarget) val name = tree.removeAttachment(OriginalName).getOrElse(nme.NO_NAME) inline def isImportable = tree.qualifier.srcPos.isSynthetic && tree.qualifier.tpe.match @@ -92,6 +93,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha resolveUsage(tree.symbol, name, tree.qualifier.tpe) else if !ignoreTree(tree) then refUsage(tree.symbol) + refInfos.isAssignment = false tree override def transformLiteral(tree: Literal)(using Context): tree.type = @@ -113,13 +115,10 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha ctx override def prepareForAssign(tree: Assign)(using Context): Context = - tree.lhs.putAttachment(Ignore, ()) // don't take LHS reference as a read + tree.lhs.putAttachment(AssignmentTarget, ()) // 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.lhs.removeAttachment(AssignmentTarget) tree override def prepareForMatch(tree: Match)(using Context): Context = @@ -142,17 +141,18 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha tree override def prepareForInlined(tree: Inlined)(using Context): Context = - refInfos.inlined.push(tree.call.srcPos) + if tree.inlinedFromOuterScope then + tree.expansion.putAttachment(InlinedParameter, ()) 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) + if !tree.call.isEmpty then + if !refInfos.calls.containsKey(tree.call) then + refInfos.calls.put(tree.call, ()) + transformAllDeep(tree.call) tree override def prepareForBind(tree: Bind)(using Context): Context = - refInfos.register(tree) + register(tree) ctx /* cf QuotePattern override def transformBind(tree: Bind)(using Context): tree.type = @@ -170,10 +170,8 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha 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 _ => + register(tree) + relax(tree.rhs, tree.tpt.tpe) ctx override def transformValDef(tree: ValDef)(using Context): tree.type = traverseAnnotations(tree.symbol) @@ -196,10 +194,8 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha 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 _ => + register(tree) + relax(tree.rhs, tree.tpt.tpe) ctx override def transformDefDef(tree: DefDef)(using Context): tree.type = traverseAnnotations(tree.symbol) @@ -212,23 +208,13 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha 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) + register(tree) tree - 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) + register(imp) transformAllDeep(imp.expr) for selector <- imp.selectors do if selector.isGiven then @@ -289,7 +275,6 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha case ByNameTypeTree(result) => transformAllDeep(result) //case _: InferredTypeTree => // do nothing - //case _: Export => // nothing to do //case _ if tree.isType => case _ => tree @@ -301,7 +286,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha // 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) + refInfos.addRef(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. @@ -310,16 +295,21 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha * 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. + * + * The `imports` flag is whether an identifier can mark an import as used: the flag is false + * for inlined code, except for `summonInline` (and related constructs) which are resolved at inlining. */ - def resolveUsage(sym0: Symbol, name: Name, prefix: Type)(using Context): Unit = + def resolveUsage(sym0: Symbol, name: Name, prefix: Type, imports: Boolean = true)(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: alt => - alt.symbol == sym - || nm.isTypeName && alt.symbol.isAliasType && alt.info.dealias.typeSymbol == sym + val sameSym = + alt.symbol == sym + || nm.isTypeName && alt.symbol.isAliasType && alt.info.dealias.typeSymbol == sym + sameSym && alt.symbol.isAccessibleFrom(qtpe) def loop(sels: List[ImportSelector]): ImportSelector | Null = sels match case sel :: sels => val matches = @@ -350,56 +340,28 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha && 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. + // Selections C.this.toString should be already excluded, but backstopped 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 enclosed = false // true if sym is owner of an enclosing context 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.userSymbol == sym && !sym.is(Package) then + enclosed = true // found enclosing definition, don't record the reference + 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 + if cur.isImportContext then val sel = matchingSelector(cur.importInfo.nn) if sel != null then if cur.importInfo.nn.isRootImport then @@ -419,7 +381,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha candidate = cur importer = sel else if checkMember(cur.owner) then - if sym.srcPos.sourcePos.source == ctx.source then + if sym.is(Package) || sym.srcPos.sourcePos.source == ctx.source then precedence = Definition candidate = cur importer = null // ignore import in same scope; we can't check nesting level @@ -429,17 +391,17 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha candidate = cur end while // record usage and possibly an import - refInfos.refs.addOne(sym) - if candidate != NoContext && candidate.isImportContext && importer != null then + if !enclosed then + refInfos.addRef(sym) + if imports && 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 + + /** Register new element for warnings only at typer */ + def register(tree: Tree)(using Context): Unit = + if phaseMode eq PhaseMode.Aggregate then + refInfos.register(tree) + end CheckUnused object CheckUnused: @@ -450,15 +412,8 @@ object CheckUnused: val refInfosKey = Property.StickyKey[RefInfos] - val resolvedKey = Property.Key[Resolved] - inline def refInfos(using Context): RefInfos = ctx.property(refInfosKey).get - inline def resolved(using Context): Resolved = - ctx.property(resolvedKey) match - case Some(res) => res - case _ => throw new MatchError("no Resolved for context") - /** Attachment holding the name of an Ident as written by the user. */ val OriginalName = Property.StickyKey[Name] @@ -468,6 +423,12 @@ object CheckUnused: /** Ignore reference. */ val Ignore = Property.StickyKey[Unit] + /** Tree is LHS of Assign. */ + val AssignmentTarget = Property.StickyKey[Unit] + + /** Tree is an inlined parameter. */ + val InlinedParameter = Property.StickyKey[Unit] + class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper") class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining") @@ -479,15 +440,17 @@ object CheckUnused: 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 calls = new IdentityHashMap[Tree, Unit] // inlined call already seen 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 + def register(tree: Tree)(using Context): Unit = if tree.srcPos.isUserCode then tree match case imp: Import => if inliners == 0 && languageImport(imp.expr).isEmpty && !imp.isGeneratedByEnum - && !ctx.outer.owner.name.isReplWrapperName + && !imp.isCompiletimeTesting + && !ctx.owner.name.isReplWrapperName then imps.put(imp, ()) case tree: Bind => @@ -510,27 +473,16 @@ object CheckUnused: 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) + // instead of refs.addOne, use addRef to distinguish a read from a write to var + var isAssignment = false + def addRef(sym: Symbol): Unit = + if isAssignment then + asss.addOne(sym) + else + refs.addOne(sym) + end RefInfos // Names are resolved by definitions and imports, which have four precedence levels: object PrecedenceLevels: @@ -558,11 +510,21 @@ object CheckUnused: def warnAt(pos: SrcPos)(msg: UnusedSymbol, origin: String = ""): Unit = warnings.addOne((msg, pos, origin)) val infos = refInfos + // non-local sym was target of assignment or has a sibling setter that was referenced + def isMutated(sym: Symbol): Boolean = + infos.asss(sym) + || infos.refs(sym.owner.info.member(sym.name.asTermName.setterName).symbol) + 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 + else if ctx.settings.WunusedHas.privates + && sym.is(Mutable) + && (sym.is(Private) || sym.isEffectivelyPrivate) + && !sym.isSetter // tracks sym.underlyingSymbol sibling getter, check setter below + && !isMutated(sym) + then warnAt(pos)(UnusedSymbol.unsetPrivates) def checkPrivate(sym: Symbol, pos: SrcPos) = @@ -571,10 +533,16 @@ object CheckUnused: && !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 + && !( sym.is(Mutable) + && sym.isSetter // tracks sym.underlyingSymbol sibling getter + && (sym.owner.is(Trait) || sym.owner.isAnonymousClass) + ) && !infos.nowarn(sym) then - warnAt(pos)(UnusedSymbol.privateMembers) + if sym.is(Mutable) && isMutated(sym) then + warnAt(pos)(UnusedSymbol.privateVars) + else + warnAt(pos)(UnusedSymbol.privateMembers) def checkParam(sym: Symbol, pos: SrcPos) = val m = sym.owner @@ -589,29 +557,33 @@ object CheckUnused: // 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. + // Avoid warning for case class elements because they are aliased via unapply (i.e. may be extracted). 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.isAllOf(PrivateParamAccessor, butNot = CaseAccessor) + && !infos.refs(alias.symbol) + && !usedByDefaultGetter(sym, m) + then if aliasSym.is(Local) then if ctx.settings.WunusedHas.explicits then - warnAt(pos)(UnusedSymbol.explicitParams) + warnAt(pos)(UnusedSymbol.explicitParams(aliasSym)) 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 + && !(sym.owner.is(ExtensionMethod) && + m.paramSymss.dropWhile(_.exists(_.isTypeParam)).match + case (h :: Nil) :: _ => h == sym // param is the extended receiver case _ => false - }) + ) && !sym.name.isInstanceOf[DerivedName] && !ctx.platform.isMainMethod(m) + && !usedByDefaultGetter(sym, m) then - warnAt(pos)(UnusedSymbol.explicitParams) + warnAt(pos)(UnusedSymbol.explicitParams(sym)) end checkExplicit // begin if !infos.skip(m) @@ -621,6 +593,16 @@ object CheckUnused: checkExplicit() end checkParam + // does the param have an alias in a default arg method that is used? + def usedByDefaultGetter(param: Symbol, meth: Symbol): Boolean = + val cls = if meth.isConstructor then meth.enclosingClass.companionModule else meth.enclosingClass + val MethName = meth.name + cls.info.decls.exists: d => + d.name match + case DefaultGetterName(MethName, _) => + d.paramSymss.exists(_.exists(p => p.name == param.name && infos.refs(p))) + case _ => false + def checkImplicit(sym: Symbol, pos: SrcPos) = val m = sym.owner def allowed = @@ -630,14 +612,14 @@ object CheckUnused: || m.hasAnnotation(dd.UnusedAnnot) // param of unused method || sym.name.is(ContextFunctionParamName) // a ubiquitous parameter || sym.isCanEqual - || sym.info.typeSymbol.match // more ubiquity + || sym.info.dealias.typeSymbol.match // more ubiquity case dd.DummyImplicitClass | dd.SubTypeClass | dd.SameTypeClass => true case tps => tps.isMarkerTrait // no members to use; was only if sym.name.is(ContextBoundParamName) || // but consider NotGiven tps.hasAnnotation(dd.LanguageFeatureMetaAnnot) || sym.info.isSingleton // DSL friendly - || sym.info.isInstanceOf[RefinedType] // can't be expressed as a context bound + || sym.info.dealias.isInstanceOf[RefinedType] // can't be expressed as a context bound if ctx.settings.WunusedHas.implicits && !infos.skip(m) && !m.isEffectivelyOverride @@ -650,17 +632,23 @@ object CheckUnused: 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) + if checking + && !infos.refs(alias.symbol) + && !usedByDefaultGetter(sym, m) + then + warnAt(pos)(UnusedSymbol.implicitParams(aliasSym)) + else if !usedByDefaultGetter(sym, m) then + warnAt(pos)(UnusedSymbol.implicitParams(sym)) def checkLocal(sym: Symbol, pos: SrcPos) = if ctx.settings.WunusedHas.locals - && !sym.is(InlineProxy) + && !sym.isOneOf(InlineProxy | Synthetic) && !sym.isCanEqual then - warnAt(pos)(UnusedSymbol.localDefs) + if sym.is(Mutable) && infos.asss(sym) then + warnAt(pos)(UnusedSymbol.localVars) + else + warnAt(pos)(UnusedSymbol.localDefs) def checkPatvars() = // convert the one non-synthetic span so all are comparable; filter NoSpan below @@ -702,6 +690,7 @@ object CheckUnused: 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.) + // If deleting a blank line and surrounded by blank lines, remove an adjoining blank line. def editPosAt(srcPos: SrcPos, forDeletion: Boolean): SrcPos = val start = srcPos.span.start val end = srcPos.span.end @@ -714,7 +703,21 @@ object CheckUnused: 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 + val p2 = + if deleteLine then + var newStart = prev + 1 + if srcPos.line > 1 then + val source = srcPos.sourcePos.source + import source.{lineToOffset, lineToOffsetOpt, offsetToLine} + val startLine = offsetToLine(start) + val endLine = offsetToLine(end) + val preceding = lineToOffset(startLine - 1) + lineToOffsetOpt(endLine + 2) match + case Some(succeeding) if lineToOffset(startLine) - preceding == 1 && succeeding - end == 2 => + newStart = preceding + case _ => + p1.withStart(newStart) + else p1 srcPos.sourcePos.withSpan(p2) def actionsOf(actions: (SrcPos, String)*): List[CodeAction] = val patches = actions.map((srcPos, replacement) => ActionPatch(srcPos.sourcePos, replacement)).toList @@ -882,7 +885,7 @@ object CheckUnused: 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 ref: TypeParamRef => termName(ref.binder.paramNames(ref.paramNum).toString.toLowerCase) case _ => Nil allowVariableBindings(ok, args) else if fun.symbol == defn.TypeTest_unapply then @@ -904,15 +907,21 @@ object CheckUnused: 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) + def relax(tree: Tree, tpe: Type)(using Context): Unit = + def refinements(tpe: Type, names: List[Name]): List[Name] = + tpe match + case RefinedType(parent, refinedName, refinedInfo) => refinedName :: refinements(parent, names) + case _ => names + val refinedNames = refinements(tpe, Nil) + if !refinedNames.isEmpty then + val names = refinedNames.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) @@ -945,7 +954,7 @@ object CheckUnused: def isCanEqual: Boolean = sym.isOneOf(GivenOrImplicit) && sym.info.finalResultType.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) def isMarkerTrait: Boolean = - sym.isClass && sym.info.allMembers.forall: d => + sym.info.hiBound.resultType.allMembers.forall: d => val m = d.symbol !m.isTerm || m.isSelfSym || m.is(Method) && (m.owner == defn.AnyClass || m.owner == defn.ObjectClass) def isEffectivelyPrivate: Boolean = @@ -998,9 +1007,17 @@ object CheckUnused: then imp.expr.tpe.allMembers.exists(_.symbol.isCanEqual) else imp.expr.tpe.member(sel.name.toTermName).hasAltWith(_.symbol.isCanEqual) + /** No mechanism for detection yet. */ + def isCompiletimeTesting: Boolean = + imp.expr.symbol == defn.CompiletimeTestingPackage//.moduleClass + extension (pos: SrcPos) def isZeroExtentSynthetic: Boolean = pos.span.isSynthetic && pos.span.isZeroExtent def isSynthetic: Boolean = pos.span.isSynthetic && pos.span.exists + def isUserCode(using Context): Boolean = + val inlineds = enclosingInlineds // per current context + inlineds.isEmpty + || inlineds.exists(_.srcPos.sourcePos.contains(pos.sourcePos)) // include intermediate inlinings or quotes extension [A <: AnyRef](arr: Array[A]) // returns `until` if not satisfied diff --git a/compiler/src/dotty/tools/dotc/transform/CollectNullableFields.scala b/compiler/src/dotty/tools/dotc/transform/CollectNullableFields.scala index 9433f7949163..8c3355d5f56b 100644 --- a/compiler/src/dotty/tools/dotc/transform/CollectNullableFields.scala +++ b/compiler/src/dotty/tools/dotc/transform/CollectNullableFields.scala @@ -60,7 +60,7 @@ class CollectNullableFields extends MiniPhase { val sym = tree.symbol val isNullablePrivateField = sym.isField && - !sym.is(Lazy) && + !sym.isOneOf(Lazy | Erased) && !sym.owner.is(Trait) && sym.initial.isAllOf(PrivateLocal) && // We need `isNullableClassAfterErasure` and not `isNullable` because diff --git a/compiler/src/dotty/tools/dotc/transform/Dependencies.scala b/compiler/src/dotty/tools/dotc/transform/Dependencies.scala index 9084930b6815..a4c3550441f5 100644 --- a/compiler/src/dotty/tools/dotc/transform/Dependencies.scala +++ b/compiler/src/dotty/tools/dotc/transform/Dependencies.scala @@ -45,9 +45,9 @@ abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Co /** A map from local methods and classes to the owners to which they will be lifted as members. * For methods and classes that do not have any dependencies this will be the enclosing package. - * symbols with packages as lifted owners will subsequently represented as static + * Symbols with packages as lifted owners will be subsequently represented as static * members of their toplevel class, unless their enclosing class was already static. - * Note: During tree transform (which runs at phase LambdaLift + 1), liftedOwner + * Note: During tree transform (which runs at phase LambdaLift + 1), logicOwner * is also used to decide whether a method had a term owner before. */ private val logicOwner = new LinkedHashMap[Symbol, Symbol] @@ -75,8 +75,8 @@ abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Co || owner.is(Trait) && isLocal(owner) || sym.isConstructor && isLocal(owner) - /** Set `liftedOwner(sym)` to `owner` if `owner` is more deeply nested - * than the previous value of `liftedowner(sym)`. + /** Set `logicOwner(sym)` to `owner` if `owner` is more deeply nested + * than the previous value of `logicOwner(sym)`. */ private def narrowLogicOwner(sym: Symbol, owner: Symbol)(using Context): Unit = if sym.maybeOwner.isTerm @@ -89,7 +89,7 @@ abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Co /** Mark symbol `sym` as being free in `enclosure`, unless `sym` is defined * in `enclosure` or there is an intermediate class properly containing `enclosure` - * in which `sym` is also free. Also, update `liftedOwner` of `enclosure` so + * in which `sym` is also free. Also, update `logicOwner` of `enclosure` so * that `enclosure` can access `sym`, or its proxy in an intermediate class. * This means: * @@ -284,7 +284,7 @@ abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Co changedFreeVars do () - /** Compute final liftedOwner map by closing over caller dependencies */ + /** Compute final logicOwner map by closing over caller dependencies */ private def computeLogicOwners()(using Context): Unit = while changedLogicOwner = false diff --git a/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala b/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala index 2deb50956537..e97bf1ed6dd1 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala @@ -107,11 +107,15 @@ class ElimErasedValueType extends MiniPhase with InfoTransformer { thisPhase => (sym1.owner.derivesFrom(defn.PolyFunctionClass) || sym2.owner.derivesFrom(defn.PolyFunctionClass)) + def oneErasedInline = + sym1.isInlineMethod && !sym1.isRetainedInlineMethod + || sym2.isInlineMethod && !sym2.isRetainedInlineMethod + // super-accessors start as private, and their expanded name can clash after // erasure. TODO: Verify that this is OK. def bothSuperAccessors = sym1.name.is(SuperAccessorName) && sym2.name.is(SuperAccessorName) - if (sym1.name != sym2.name && !bothSuperAccessors || - !info1.matchesLoosely(info2) && !bothPolyApply) + if (sym1.name != sym2.name && !bothSuperAccessors + || !info1.matchesLoosely(info2) && !bothPolyApply && !oneErasedInline) report.error(DoubleDefinition(sym1, sym2, root), root.srcPos) } while (opc.hasNext) { diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 25239aee59cf..c743e757b8b4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -105,12 +105,6 @@ class Erasure extends Phase with DenotTransformer { if oldSymbol.isRetainedInlineMethod then newFlags = newFlags &~ Flags.Inline newAnnotations = newAnnotations.filterConserve(!_.isInstanceOf[BodyAnnotation]) - oldSymbol match - case cls: ClassSymbol if cls.is(Flags.Erased) => - newFlags = newFlags | Flags.Trait | Flags.JavaInterface - newAnnotations = Nil - newInfo = erasedClassInfo(cls) - case _ => // TODO: define derivedSymDenotation? if ref.is(Flags.PackageClass) || !ref.isClass // non-package classes are always copied since their base types change @@ -550,8 +544,11 @@ object Erasure { case _ => tree.symbol.isEffectivelyErased } - /** Check that Java statics and packages can only be used in selections. - */ + /** Check that + * - erased values are not referred to from normal code + * - inline method applications were inlined + * - Java statics and packages can only be used in selections. + */ private def checkNotErased(tree: Tree)(using Context): tree.type = if !ctx.mode.is(Mode.Type) then if isErased(tree) then @@ -579,24 +576,17 @@ object Erasure { |it should have been processed and eliminated during expansion of an enclosing macro or term erasure.""" report.error(message, tree.srcPos) case _ => // OK - - checkNotErasedClass(tree) + tree end checkNotErased - private def checkNotErasedClass(tp: Type, tree: untpd.Tree)(using Context): Unit = tp match - case JavaArrayType(et) => - checkNotErasedClass(et, tree) - case _ => - if tp.isErasedClass then - val (kind, tree1) = tree match - case tree: untpd.ValOrDefDef => ("definition", tree.tpt) - case tree: untpd.DefTree => ("definition", tree) - case _ => ("expression", tree) - report.error(em"illegal reference to erased ${tp.typeSymbol} in $kind that is not itself erased", tree1.srcPos) - - private def checkNotErasedClass(tree: Tree)(using Context): tree.type = - checkNotErasedClass(tree.tpe.widen.finalResultType, tree) - tree + /** Check that initializers of erased vals and arguments to erased parameters + * are pure expressions. + */ + def checkPureErased(tree: untpd.Tree, isArgument: Boolean, isImplicit: Boolean = false)(using Context): Unit = + val tree1 = tree.asInstanceOf[tpd.Tree] + inContext(preErasureCtx): + if !tpd.isPureExpr(tree1) then + report.error(ErasedNotPure(tree1, isArgument, isImplicit), tree1.srcPos) def erasedDef(sym: Symbol)(using Context): Tree = if sym.isClass then @@ -625,7 +615,7 @@ object Erasure { * are handled separately by [[typedDefDef]], [[typedValDef]] and [[typedTyped]]. */ override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): TypeTree = - checkNotErasedClass(tree.withType(erasure(tree.typeOpt))) + tree.withType(erasure(tree.typeOpt)) /** This override is only needed to semi-erase type ascriptions */ override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree = @@ -644,7 +634,7 @@ object Erasure { if (tree.typeOpt.isRef(defn.UnitClass)) tree.withType(tree.typeOpt) else if (tree.const.tag == Constants.ClazzTag) - checkNotErasedClass(clsOf(tree.const.typeValue)) + clsOf(tree.const.typeValue) else super.typedLiteral(tree) @@ -695,7 +685,7 @@ object Erasure { val owner = sym.maybeOwner if defn.specialErasure.contains(owner) then assert(sym.isConstructor, s"${sym.showLocated}") - defn.specialErasure(owner).nn + defn.specialErasure(owner) else if defn.isSyntheticFunctionClass(owner) then defn.functionTypeErasure(owner).typeSymbol else @@ -848,7 +838,13 @@ object Erasure { val origFunType = origFun.tpe.widen(using preErasureCtx) val ownArgs = origFunType match case mt: MethodType if mt.hasErasedParams => - args.zip(mt.erasedParams).collect { case (arg, false) => arg } + args.lazyZip(mt.paramErasureStatuses).flatMap: (arg, isErased) => + if isErased then + checkPureErased(arg, isArgument = true, + isImplicit = mt.isImplicitMethod && arg.span.isSynthetic) + Nil + else + arg :: Nil case _ => args val fun1 = typedExpr(fun, AnyFunctionProto) fun1.tpe.widen match @@ -916,9 +912,10 @@ object Erasure { } override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = - if (sym.isEffectivelyErased) erasedDef(sym) - else - checkNotErasedClass(sym.info, vdef) + if sym.isEffectivelyErased then + checkPureErased(vdef.rhs, isArgument = false) + erasedDef(sym) + else trace(i"erasing $vdef"): super.typedValDef(untpd.cpy.ValDef(vdef)( tpt = untpd.TypedSplice(TypeTree(sym.info).withSpan(vdef.tpt.span))), sym) @@ -930,7 +927,6 @@ object Erasure { if sym.isEffectivelyErased || sym.name.is(BodyRetainerName) then erasedDef(sym) else - checkNotErasedClass(sym.info.finalResultType, ddef) val restpe = if sym.isConstructor then defn.UnitType else sym.info.resultType var vparams = outerParamDefs(sym) ::: ddef.paramss.collect { @@ -1049,29 +1045,24 @@ object Erasure { adaptClosure(implClosure) } - override def typedNew(tree: untpd.New, pt: Type)(using Context): Tree = - checkNotErasedClass(super.typedNew(tree, pt)) - override def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(using Context): Tree = EmptyTree override def typedClassDef(cdef: untpd.TypeDef, cls: ClassSymbol)(using Context): Tree = - if cls.is(Flags.Erased) then erasedDef(cls) - else - val typedTree@TypeDef(name, impl @ Template(constr, _, self, _)) = super.typedClassDef(cdef, cls): @unchecked - // In the case where a trait extends a class, we need to strip any non trait class from the signature - // and accept the first one (see tests/run/mixins.scala) - val newTraits = impl.parents.tail.filterConserve: tree => - def isTraitConstructor = tree match - case Trees.Block(_, expr) => // Specific management for trait constructors (see tests/pos/i9213.scala) - expr.symbol.isConstructor && expr.symbol.owner.is(Flags.Trait) - case _ => tree.symbol.isConstructor && tree.symbol.owner.is(Flags.Trait) - tree.symbol.is(Flags.Trait) || isTraitConstructor - - val newParents = - if impl.parents.tail eq newTraits then impl.parents - else impl.parents.head :: newTraits - cpy.TypeDef(typedTree)(rhs = cpy.Template(impl)(parents = newParents)) + val typedTree@TypeDef(name, impl @ Template(constr, _, self, _)) = super.typedClassDef(cdef, cls): @unchecked + // In the case where a trait extends a class, we need to strip any non trait class from the signature + // and accept the first one (see tests/run/mixins.scala) + val newTraits = impl.parents.tail.filterConserve: tree => + def isTraitConstructor = tree match + case Trees.Block(_, expr) => // Specific management for trait constructors (see tests/pos/i9213.scala) + expr.symbol.isConstructor && expr.symbol.owner.is(Flags.Trait) + case _ => tree.symbol.isConstructor && tree.symbol.owner.is(Flags.Trait) + tree.symbol.is(Flags.Trait) || isTraitConstructor + + val newParents = + if impl.parents.tail eq newTraits then impl.parents + else impl.parents.head :: newTraits + cpy.TypeDef(typedTree)(rhs = cpy.Template(impl)(parents = newParents)) override def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree = typed(tree.arg, pt) diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala index 68f911f06963..4288a82e0191 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -10,6 +10,7 @@ import Names.TypeName import NullOpsDecorator.* import ast.untpd +import scala.collection.mutable.ListBuffer /** Expand SAM closures that cannot be represented by the JVM as lambdas to anonymous classes. * These fall into five categories @@ -17,7 +18,7 @@ import ast.untpd * 1. Partial function closures, we need to generate isDefinedAt and applyOrElse methods for these. * 2. Closures implementing non-trait classes * 3. Closures implementing classes that inherit from a class other than Object - * (a lambda cannot not be a run-time subtype of such a class) + * (a lambda cannot be a run-time subtype of such a class) * 4. Closures that implement traits which run initialization code. * 5. Closures that get synthesized abstract methods in the transformation pipeline. These methods can be * (1) superaccessors, (2) outer references, (3) accessors for fields. @@ -59,7 +60,7 @@ class ExpandSAMs extends MiniPhase: // A SAM type is allowed to have type aliases refinements (see // SAMType#samParent) which must be converted into type members if // the closure is desugared into a class. - val refinements = collection.mutable.ListBuffer[(TypeName, TypeAlias)]() + val refinements = ListBuffer.empty[(TypeName, TypeAlias)] def collectAndStripRefinements(tp: Type): Type = tp match case RefinedType(parent, name, info: TypeAlias) => val res = collectAndStripRefinements(parent) @@ -81,34 +82,40 @@ class ExpandSAMs extends MiniPhase: tree } - /** A partial function literal: + /** A pattern-matching anonymous function: * * ``` * val x: PartialFunction[A, B] = { case C1 => E1; ...; case Cn => En } * ``` + * or + * ``` + * x => e(x) { case C1 => E1; ...; case Cn => En } + * ``` + * where the expression `e(x)` may be trivially `x` * * which desugars to: * * ``` * val x: PartialFunction[A, B] = { - * def $anonfun(x: A): B = x match { case C1 => E1; ...; case Cn => En } + * def $anonfun(x: A): B = e(x) match { case C1 => E1; ...; case Cn => En } * closure($anonfun: PartialFunction[A, B]) * } * ``` + * where the expression `e(x)` defaults to `x` for a simple block of cases * * is expanded to an anonymous class: * * ``` * val x: PartialFunction[A, B] = { * class $anon extends AbstractPartialFunction[A, B] { - * final def isDefinedAt(x: A): Boolean = x match { + * final def isDefinedAt(x: A): Boolean = e(x) match { * case C1 => true * ... * case Cn => true * case _ => false * } * - * final def applyOrElse[A1 <: A, B1 >: B](x: A1, default: A1 => B1): B1 = x match { + * final def applyOrElse[A1 <: A, B1 >: B](x: A1, default: A1 => B1): B1 = e(x) match { * case C1 => E1 * ... * case Cn => En @@ -120,7 +127,7 @@ class ExpandSAMs extends MiniPhase: * } * ``` */ - private def toPartialFunction(tree: Block, tpe: Type)(using Context): Tree = { + private def toPartialFunction(tree: Block, tpe: Type)(using Context): Tree = val closureDef(anon @ DefDef(_, List(List(param)), _, _)) = tree: @unchecked // The right hand side from which to construct the partial function. This is always a Match. @@ -146,7 +153,7 @@ class ExpandSAMs extends MiniPhase: defn.AbstractPartialFunctionClass.typeRef.appliedTo(anonTpe.firstParamTypes.head, anonTpe.resultType), defn.SerializableType) - AnonClass(anonSym.owner, parents, tree.span) { pfSym => + AnonClass(anonSym.owner, parents, tree.span): pfSym => def overrideSym(sym: Symbol) = sym.copy( owner = pfSym, flags = Synthetic | Method | Final | Override, @@ -155,7 +162,8 @@ class ExpandSAMs extends MiniPhase: val isDefinedAtFn = overrideSym(defn.PartialFunction_isDefinedAt) val applyOrElseFn = overrideSym(defn.PartialFunction_applyOrElse) - def translateMatch(tree: Match, pfParam: Symbol, cases: List[CaseDef], defaultValue: Tree)(using Context) = { + def translateMatch(owner: Symbol)(pfParam: Symbol, cases: List[CaseDef], defaultValue: Tree)(using Context) = + val tree: Match = pfRHS val selector = tree.selector val cases1 = if cases.exists(isDefaultCase) then cases else @@ -165,31 +173,27 @@ class ExpandSAMs extends MiniPhase: cases :+ defaultCase cpy.Match(tree)(selector, cases1) .subst(param.symbol :: Nil, pfParam :: Nil) - // Needed because a partial function can be written as: + // Needed because a partial function can be written as: // param => param match { case "foo" if foo(param) => param } // And we need to update all references to 'param' - } + .changeOwner(anonSym, owner) - def isDefinedAtRhs(paramRefss: List[List[Tree]])(using Context) = { + def isDefinedAtRhs(paramRefss: List[List[Tree]])(using Context) = val tru = Literal(Constant(true)) - def translateCase(cdef: CaseDef) = - cpy.CaseDef(cdef)(body = tru).changeOwner(anonSym, isDefinedAtFn) + def translateCase(cdef: CaseDef) = cpy.CaseDef(cdef)(body = tru) val paramRef = paramRefss.head.head val defaultValue = Literal(Constant(false)) - translateMatch(pfRHS, paramRef.symbol, pfRHS.cases.map(translateCase), defaultValue) - } + translateMatch(isDefinedAtFn)(paramRef.symbol, pfRHS.cases.map(translateCase), defaultValue) - def applyOrElseRhs(paramRefss: List[List[Tree]])(using Context) = { + def applyOrElseRhs(paramRefss: List[List[Tree]])(using Context) = val List(paramRef, defaultRef) = paramRefss(1) - def translateCase(cdef: CaseDef) = - cdef.changeOwner(anonSym, applyOrElseFn) val defaultValue = defaultRef.select(nme.apply).appliedTo(paramRef) - translateMatch(pfRHS, paramRef.symbol, pfRHS.cases.map(translateCase), defaultValue) - } + translateMatch(applyOrElseFn)(paramRef.symbol, pfRHS.cases, defaultValue) - val isDefinedAtDef = transformFollowingDeep(DefDef(isDefinedAtFn, isDefinedAtRhs(_)(using ctx.withOwner(isDefinedAtFn)))) - val applyOrElseDef = transformFollowingDeep(DefDef(applyOrElseFn, applyOrElseRhs(_)(using ctx.withOwner(applyOrElseFn)))) + val isDefinedAtDef = transformFollowingDeep: + DefDef(isDefinedAtFn, isDefinedAtRhs(_)(using ctx.withOwner(isDefinedAtFn))) + val applyOrElseDef = transformFollowingDeep: + DefDef(applyOrElseFn, applyOrElseRhs(_)(using ctx.withOwner(applyOrElseFn))) List(isDefinedAtDef, applyOrElseDef) - } - } + end toPartialFunction end ExpandSAMs diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 8fc9f02c1e38..81cc73c26ed6 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -20,6 +20,8 @@ import StdNames.* import config.Feature import inlines.Inlines.inInlineMethod import util.Property +import inlines.Inlines +import reporting.InlinedAnonClassWarning object FirstTransform { val name: String = "firstTransform" @@ -207,6 +209,11 @@ class FirstTransform extends MiniPhase with SymTransformer { thisPhase => case _ => tree } + override def transformTypeDef(tree: TypeDef)(using Context): Tree = + if tree.symbol.isAnonymousClass && Inlines.inInlineMethod then + report.warning(InlinedAnonClassWarning(), tree.symbol.sourcePos) + tree + /** Perform one of the following simplification if applicable: * * true && y ==> y diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala index 1798d938272c..9d72588dc326 100644 --- a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -171,7 +171,7 @@ object GenericSignatures { def fullNameInSig(sym: Symbol): Unit = { assert(sym.isClass) val name = atPhase(genBCodePhase) { sanitizeName(sym.fullName).replace('.', '/') } - builder.append('L').nn.append(name) + builder.append('L').append(name) } def classSig(sym: Symbol, pre: Type = NoType, args: List[Type] = Nil): Unit = { @@ -216,7 +216,7 @@ object GenericSignatures { // TODO revisit this. Does it align with javac for code that can be expressed in both languages? val delimiter = if (builder.charAt(builder.length() - 1) == '>') '.' else '$' - builder.append(delimiter).nn.append(sanitizeName(sym.name)) + builder.append(delimiter).append(sanitizeName(sym.name)) } else fullNameInSig(sym) } @@ -234,6 +234,7 @@ object GenericSignatures { @noinline def jsig(tp0: Type, toplevel: Boolean = false, unboxedVCs: Boolean = true): Unit = { + inline def jsig1(tp0: Type): Unit = jsig(tp0, toplevel = false, unboxedVCs = true) val tp = tp0.dealias tp match { @@ -242,41 +243,41 @@ object GenericSignatures { val erasedUnderlying = fullErasure(ref.underlying.bounds.hi) // don't emit type param name if the param is upper-bounded by a primitive type (including via a value class) if erasedUnderlying.isPrimitiveValueType then - jsig(erasedUnderlying, toplevel, unboxedVCs) + jsig(erasedUnderlying, toplevel = toplevel, unboxedVCs = unboxedVCs) else typeParamSig(ref.paramName.lastPart) case defn.ArrayOf(elemtp) => if (isGenericArrayElement(elemtp, isScala2 = false)) - jsig(defn.ObjectType) + jsig1(defn.ObjectType) else builder.append(ClassfileConstants.ARRAY_TAG) elemtp match - case TypeBounds(lo, hi) => jsig(hi.widenDealias) - case _ => jsig(elemtp) + case TypeBounds(lo, hi) => jsig1(hi.widenDealias) + case _ => jsig1(elemtp) case RefOrAppliedType(sym, pre, args) => if (sym == defn.PairClass && tupleArity(tp) > Definitions.MaxTupleArity) - jsig(defn.TupleXXLClass.typeRef) + jsig1(defn.TupleXXLClass.typeRef) else if (isTypeParameterInSig(sym, sym0)) { assert(!sym.isAliasType || sym.info.isLambdaSub, "Unexpected alias type: " + sym) typeParamSig(sym.name.lastPart) } else if (defn.specialErasure.contains(sym)) - jsig(defn.specialErasure(sym).nn.typeRef) + jsig1(defn.specialErasure(sym).typeRef) else if (sym == defn.UnitClass || sym == defn.BoxedUnitModule) - jsig(defn.BoxedUnitClass.typeRef) + jsig1(defn.BoxedUnitClass.typeRef) else if (sym == defn.NothingClass) builder.append("Lscala/runtime/Nothing$;") else if (sym == defn.NullClass) builder.append("Lscala/runtime/Null$;") else if (sym.isPrimitiveValueClass) - if (!unboxedVCs) jsig(defn.ObjectType) - else if (sym == defn.UnitClass) jsig(defn.BoxedUnitClass.typeRef) + if (!unboxedVCs) jsig1(defn.ObjectType) + else if (sym == defn.UnitClass) jsig1(defn.BoxedUnitClass.typeRef) else builder.append(defn.typeTag(sym.info)) else if (sym.isDerivedValueClass) { if (unboxedVCs) { val erasedUnderlying = fullErasure(tp) - jsig(erasedUnderlying, toplevel) + jsig(erasedUnderlying, toplevel = toplevel, unboxedVCs = true) } else classSig(sym, pre, args) } else if (defn.isSyntheticFunctionClass(sym)) { @@ -286,20 +287,20 @@ object GenericSignatures { else if sym.isClass then classSig(sym, pre, args) else - jsig(erasure(tp), toplevel, unboxedVCs) + jsig(erasure(tp), toplevel = toplevel, unboxedVCs = unboxedVCs) case ExprType(restpe) if toplevel => builder.append("()") methodResultSig(restpe) case ExprType(restpe) => - jsig(defn.FunctionType(0).appliedTo(restpe)) + jsig1(defn.FunctionType(0).appliedTo(restpe)) case mtd: MethodOrPoly => val (tparams, vparams, rte) = collectMethodParams(mtd) if (toplevel && !sym0.isConstructor) polyParamSig(tparams) builder.append('(') - for vparam <- vparams do jsig(vparam) + for vparam <- vparams do jsig1(vparam) builder.append(')') methodResultSig(rte) @@ -316,7 +317,7 @@ object GenericSignatures { val (reprParents, _) = splitIntersection(parents) val repr = reprParents.find(_.typeSymbol.is(TypeParam)).getOrElse(reprParents.head) - jsig(repr, unboxedVCs = unboxedVCs) + jsig(repr, toplevel = false, unboxedVCs = unboxedVCs) case ci: ClassInfo => val tParams = tp.typeParams diff --git a/compiler/src/dotty/tools/dotc/transform/Getters.scala b/compiler/src/dotty/tools/dotc/transform/Getters.scala index a58dffa04223..11adf4da83d5 100644 --- a/compiler/src/dotty/tools/dotc/transform/Getters.scala +++ b/compiler/src/dotty/tools/dotc/transform/Getters.scala @@ -89,7 +89,8 @@ class Getters extends MiniPhase with SymTransformer { thisPhase => else d1 = d1.copySymDenotation(initFlags = d1.flags &~ Local) d1 } - private val NoGetterNeededFlags = Method | Param | JavaDefined | JavaStatic + + private val NoGetterNeededFlags = Method | Param | JavaDefined | JavaStatic | PhantomSymbol | Erased val newSetters = util.HashSet[Symbol]() diff --git a/compiler/src/dotty/tools/dotc/transform/InlineVals.scala b/compiler/src/dotty/tools/dotc/transform/InlineVals.scala index cff1632ffcd2..c300352a162e 100644 --- a/compiler/src/dotty/tools/dotc/transform/InlineVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/InlineVals.scala @@ -2,13 +2,12 @@ package dotty.tools package dotc package transform -import dotty.tools.dotc.core.Contexts.* -import dotty.tools.dotc.core.Decorators.* -import dotty.tools.dotc.core.Symbols.* -import dotty.tools.dotc.core.Flags.* -import dotty.tools.dotc.core.Types.* -import dotty.tools.dotc.transform.MegaPhase.MiniPhase -import dotty.tools.dotc.inlines.Inlines +import core.* +import Contexts.*, Decorators.*, Symbols.*, Flags.*, Types.* +import MegaPhase.MiniPhase +import inlines.Inlines +import ast.tpd + /** Check that `tree.rhs` can be right hand-side of an `inline` value definition. */ class InlineVals extends MiniPhase: @@ -38,7 +37,10 @@ class InlineVals extends MiniPhase: tpt.tpe.widenTermRefExpr.dealiasKeepOpaques.normalized match case tp: ConstantType => if !isPureExpr(rhs) then - def details = if enclosingInlineds.isEmpty then "" else i"but was: $rhs" + def details = + if enclosingInlineds.nonEmpty || rhs.isInstanceOf[tpd.Inlined] + then i" but was: $rhs" + else "" report.error(em"inline value must be pure$details", rhs.srcPos) case tp => if tp.typeSymbol.is(Opaque) then diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index 0229284a1b5f..449402f17fce 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -50,9 +50,9 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: if !newlyCreated then // If the directory existed before, let's clean it up. - dataDir.listFiles.nn - .filter(_.nn.getName.nn.startsWith("scoverage")) - .foreach(_.nn.delete()) + dataDir.listFiles + .filter(_.getName.startsWith("scoverage")) + .foreach(_.delete()) end if // Initialise a coverage object if it does not exist yet @@ -70,13 +70,13 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: private def isClassIncluded(sym: Symbol)(using Context): Boolean = val fqn = sym.fullName.toText(ctx.printerFn(ctx)).show coverageExcludeClasslikePatterns.isEmpty || !coverageExcludeClasslikePatterns.exists( - _.matcher(fqn).nn.matches + _.matcher(fqn).matches ) private def isFileIncluded(file: SourceFile)(using Context): Boolean = val normalizedPath = file.path.replace(".scala", "") coverageExcludeFilePatterns.isEmpty || !coverageExcludeFilePatterns.exists( - _.matcher(normalizedPath).nn.matches + _.matcher(normalizedPath).matches ) override protected def newTransformer(using Context) = @@ -122,7 +122,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: line = pos.line + 1, desc = sourceFile.content.slice(pos.start, pos.end).mkString, symbolName = tree.symbol.name.toSimpleName.show, - treeName = tree.getClass.getSimpleName.nn, + treeName = tree.getClass.getSimpleName, branch ) ctx.base.coverage.nn.addStatement(statement) @@ -145,6 +145,31 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: val span = pos.span.toSynthetic invokeCall(statementId, span) + private def transformApplyArgs(trees: List[Tree])(using Context): List[Tree] = + if allConstArgs(trees) then trees else transform(trees) + + private def transformInnerApply(tree: Tree)(using Context): Tree = tree match + case a: Apply if a.fun.symbol == defn.StringContextModule_apply => + a + case a: Apply => + cpy.Apply(a)( + transformInnerApply(a.fun), + transformApplyArgs(a.args) + ) + case a: TypeApply => + cpy.TypeApply(a)( + transformInnerApply(a.fun), + transformApplyArgs(a.args) + ) + case s: Select => + cpy.Select(s)(transformInnerApply(s.qualifier), s.name) + case i: (Ident | This) => i + case t: Typed => + cpy.Typed(t)(transformInnerApply(t.expr), t.tpt) + case other => transform(other) + + private def allConstArgs(args: List[Tree]) = + args.forall(arg => arg.isInstanceOf[Literal] || arg.isInstanceOf[Ident]) /** * Tries to instrument an `Apply`. * These "tryInstrument" methods are useful to tweak the generation of coverage instrumentation, @@ -158,10 +183,12 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: // Create a call to Invoker.invoked(coverageDirectory, newStatementId) val coverageCall = createInvokeCall(tree, tree.sourcePos) - if needsLift(tree) then - // Transform args and fun, i.e. instrument them if needed (and if possible) - val app = cpy.Apply(tree)(transform(tree.fun), tree.args.map(transform)) + // Transform args and fun, i.e. instrument them if needed (and if possible) + val app = + if tree.fun.symbol eq defn.throwMethod then tree + else cpy.Apply(tree)(transformInnerApply(tree.fun), transformApplyArgs(tree.args)) + if needsLift(tree) then // Lifts the arguments. Note that if only one argument needs to be lifted, we lift them all. // Also, tree.fun can be lifted too. // See LiftCoverage for the internal working of this lifting. @@ -171,11 +198,10 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: InstrumentedParts(liftedDefs.toList, coverageCall, liftedApp) else // Instrument without lifting - val transformed = cpy.Apply(tree)(transform(tree.fun), transform(tree.args)) - InstrumentedParts.singleExpr(coverageCall, transformed) + InstrumentedParts.singleExpr(coverageCall, app) else // Transform recursively but don't instrument the tree itself - val transformed = cpy.Apply(tree)(transform(tree.fun), transform(tree.args)) + val transformed = cpy.Apply(tree)(transformInnerApply(tree.fun), transform(tree.args)) InstrumentedParts.notCovered(transformed) private def tryInstrument(tree: Ident)(using Context): InstrumentedParts = @@ -187,9 +213,14 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: else InstrumentedParts.notCovered(tree) + private def tryInstrument(tree: Literal)(using Context): InstrumentedParts = + val coverageCall = createInvokeCall(tree, tree.sourcePos) + InstrumentedParts.singleExpr(coverageCall, tree) + private def tryInstrument(tree: Select)(using Context): InstrumentedParts = val sym = tree.symbol - val transformed = cpy.Select(tree)(transform(tree.qualifier), tree.name) + val qual = transform(tree.qualifier).ensureConforms(tree.qualifier.tpe) + val transformed = cpy.Select(tree)(qual, tree.name) if canInstrumentParameterless(sym) then // call to a parameterless method val coverageCall = createInvokeCall(tree, tree.sourcePos) @@ -202,6 +233,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: tree match case t: Apply => tryInstrument(t) case t: Ident => tryInstrument(t) + case t: Literal => tryInstrument(t) case t: Select => tryInstrument(t) case _ => InstrumentedParts.notCovered(transform(tree)) @@ -223,10 +255,14 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: inContext(transformCtx(tree)) { // necessary to position inlined code properly tree match // simple cases - case tree: (Import | Export | Literal | This | Super | New) => tree + case tree: (Import | Export | This | Super | New) => tree case tree if tree.isEmpty || tree.isType => tree // empty Thicket, Ident (referring to a type), TypeTree, ... case tree if !tree.span.exists || tree.span.isZeroExtent => tree // no meaningful position + case tree: Literal => + val rest = tryInstrument(tree).toTree + rest + // identifier case tree: Ident => tryInstrument(tree).toTree @@ -280,6 +316,9 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: case tree: CaseDef => transformCaseDef(tree) + case tree: ValDef if tree.symbol.is(Inline) => + tree // transforming inline vals will result in `inline value must be pure` errors + case tree: ValDef => // only transform the rhs val rhs = transform(tree.rhs) @@ -323,13 +362,13 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: ) case tree: Inlined => - // Ideally, tree.call would provide precise information about the inlined call, - // and we would use this information for the coverage report. - // But PostTyper simplifies tree.call, so we can't report the actual method that was inlined. - // In any case, the subtrees need to be repositioned right now, otherwise the - // coverage statement will point to a potentially unreachable source file. - val dropped = Inlines.dropInlined(tree) // drop and reposition - transform(dropped) // transform the content of the Inlined + // Inlined code contents might come from another file (or project), + // which means that we cannot clearly designate which part of the inlined code + // was run using the API we are given. + // At best, we can show that the Inlined tree itself was reached. + // Additionally, Scala 2's coverage ignores macro calls entirely, + // so let's do that here too, also for regular inlined calls. + tree // For everything else just recurse and transform case _ => @@ -559,15 +598,14 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: private def isCompilerIntrinsicMethod(sym: Symbol)(using Context): Boolean = val owner = sym.maybeOwner owner.exists && ( - owner.eq(defn.AnyClass) || - owner.isPrimitiveValueClass || + (owner.eq(defn.AnyClass) && (sym == defn.Any_asInstanceOf || sym == defn.Any_isInstanceOf)) || owner.maybeOwner == defn.CompiletimePackageClass ) object InstrumentCoverage: val name: String = "instrumentCoverage" val description: String = "instrument code for coverage checking" - val ExcludeMethodFlags: FlagSet = Synthetic | Artifact | Erased + val ExcludeMethodFlags: FlagSet = Artifact | Erased /** * An instrumented Tree, in 3 parts. diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index 2fd777f715d9..76bc09e07540 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -477,12 +477,12 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { newSymbol(claz, offsetName(info.defs.size), Synthetic, defn.LongType).enteredAfter(this) case None => newSymbol(claz, offsetName(0), Synthetic, defn.LongType).enteredAfter(this) - offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot, offsetSymbol.nn.span)) + offsetSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot, offsetSymbol.span)) val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerName.mangledString))) - val offsetTree = ValDef(offsetSymbol.nn, getOffset.appliedTo(fieldTree)) + val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(fieldTree)) val offsetInfo = appendOffsetDefs.getOrElseUpdate(claz, new OffsetInfo(Nil)) offsetInfo.defs = offsetTree :: offsetInfo.defs - val offset = ref(offsetSymbol.nn) + val offset = ref(offsetSymbol) val swapOver = This(claz) @@ -617,23 +617,23 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { .symbol.asTerm else { // need to create a new flag offsetSymbol = newSymbol(claz, offsetById, Synthetic, defn.LongType).enteredAfter(this) - offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot, offsetSymbol.nn.span)) + offsetSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot, offsetSymbol.span)) val flagName = LazyBitMapName.fresh(id.toString.toTermName) val flagSymbol = newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this) flag = ValDef(flagSymbol, Literal(Constant(0L))) val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(flagName.toString))) - val offsetTree = ValDef(offsetSymbol.nn, getOffsetStatic.appliedTo(fieldTree)) + val offsetTree = ValDef(offsetSymbol, getOffsetStatic.appliedTo(fieldTree)) info.defs = offsetTree :: info.defs } case None => offsetSymbol = newSymbol(claz, offsetName(0), Synthetic, defn.LongType).enteredAfter(this) - offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot, offsetSymbol.nn.span)) + offsetSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot, offsetSymbol.span)) val flagName = LazyBitMapName.fresh("0".toTermName) val flagSymbol = newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this) flag = ValDef(flagSymbol, Literal(Constant(0L))) val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(flagName.toString))) - val offsetTree = ValDef(offsetSymbol.nn, getOffsetStatic.appliedTo(fieldTree)) + val offsetTree = ValDef(offsetSymbol, getOffsetStatic.appliedTo(fieldTree)) appendOffsetDefs += (claz -> new OffsetInfo(List(offsetTree), ord)) } @@ -641,7 +641,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { val containerSymbol = newSymbol(claz, containerName, x.symbol.flags &~ containerFlagsMask | containerFlags, tpe, coord = x.symbol.coord).enteredAfter(this) val containerTree = ValDef(containerSymbol, defaultValue(tpe)) - val offset = ref(offsetSymbol.nn) + val offset = ref(offsetSymbol) val getFlag = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.get) val setFlag = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.setFlag) val wait = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.wait4Notification) diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index 9a19c0dc414f..b3285f62c062 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -250,7 +250,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => case Some((_, _, args)) => args.iterator case _ => Iterator.empty def nextArgument() = - if argsIt.hasNext then argsIt.next + if argsIt.hasNext then argsIt.next() else assert( impl.parents.forall(_.tpe.typeSymbol != mixin), @@ -264,7 +264,6 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => for getter <- mixin.info.decls.toList if getter.isGetter - && !getter.isEffectivelyErased && !wasOneOf(getter, Deferred) && !getter.isConstExprFinalVal yield diff --git a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala index 20b0c8534920..16c50b9cd474 100644 --- a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala +++ b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala @@ -225,6 +225,7 @@ object OverridingPairs: ) else member.name.is(DefaultGetterName) // default getters are not checked for compatibility - || memberTp.overrides(otherTp, member.matchNullaryLoosely || other.matchNullaryLoosely || fallBack) + || + memberTp.overrides(otherTp, member.matchNullaryLoosely || other.matchNullaryLoosely || fallBack) end OverridingPairs diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index e2505144abda..10aeea40b13b 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -14,9 +14,11 @@ import Flags.*, Constants.* import Decorators.* import NameKinds.{PatMatStdBinderName, PatMatAltsName, PatMatResultName} import config.Printers.patmatch +import config.Feature import reporting.* import ast.* import util.Property.* +import cc.{CapturingType, Capabilities} import scala.annotation.tailrec import scala.collection.mutable @@ -267,8 +269,14 @@ object PatternMatcher { def matchArgsPatternPlan(args: List[Tree], syms: List[Symbol]): Plan = args match { case arg :: args1 => - val sym :: syms1 = syms: @unchecked - patternPlan(sym, arg, matchArgsPatternPlan(args1, syms1)) + if (args.length != syms.length) + report.error(UnapplyInvalidNumberOfArguments(tree, tree.tpe :: Nil), arg.srcPos) + // Generate a throwaway but type-correct plan. + // This plan will never execute because it'll be guarded by a `NonNullTest`. + ResultPlan(tpd.Throw(tpd.nullLiteral)) + else + val sym :: syms1 = syms: @unchecked + patternPlan(sym, arg, matchArgsPatternPlan(args1, syms1)) case Nil => assert(syms.isEmpty) onSuccess @@ -343,7 +351,13 @@ object PatternMatcher { receiver.ensureConforms(defn.NonEmptyTupleTypeRef), // If scrutinee is a named tuple, cast to underlying tuple Literal(Constant(i))) - if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length) + def getOfGetMatch(gm: Tree) = gm.select(nme.get, _.info.isParameterless) + // Disable Scala2Unapply optimization if the argument is a named argument for a single-element named tuple to + // enable selecting the field. See i23131.scala for test cases. + val wasUnaryNamedTupleSelectArgForNamedTuple = + args.length == 1 && args.head.removeAttachment(FirstTransform.WasNamedArg).isDefined && + isGetMatch(unappType) && getOfGetMatch(unapp).tpe.widenDealias.isNamedTupleType + if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length && !wasUnaryNamedTupleSelectArgForNamedTuple) def tupleSel(sym: Symbol) = // If scrutinee is a named tuple, cast to underlying tuple, so that we can // continue to select with _1, _2, ... @@ -376,7 +390,7 @@ object PatternMatcher { else { assert(isGetMatch(unappType)) val argsPlan = { - val get = ref(unappResult).select(nme.get, _.info.isParameterless) + val get = getOfGetMatch(ref(unappResult)) val arity = productArity(get.tpe.stripNamedTuple, unapp.srcPos) if (isUnapplySeq) letAbstract(get) { getResult => @@ -386,9 +400,6 @@ object PatternMatcher { } else letAbstract(get) { getResult => - 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. @@ -396,7 +407,7 @@ object PatternMatcher { // 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) => + case arg :: Nil if !wasUnaryNamedTupleSelectArgForNamedTuple => ref(getResult) :: Nil case _ => productSelectors(getResult.info).map(ref(getResult).select(_)) @@ -418,8 +429,11 @@ object PatternMatcher { && !hasExplicitTypeArgs(extractor) case _ => false } + val castTp = if Feature.ccEnabled + then CapturingType(tpt.tpe, scrutinee.termRef.singletonCaptureSet) + else tpt.tpe TestPlan(TypeTest(tpt, isTrusted), scrutinee, tree.span, - letAbstract(ref(scrutinee).cast(tpt.tpe)) { casted => + letAbstract(ref(scrutinee).cast(castTp)) { casted => nonNull += casted patternPlan(casted, pat, onSuccess) }) @@ -464,7 +478,13 @@ object PatternMatcher { onSuccess ) } - case WildcardPattern() => + // When match against a `this.type` (say case a: this.type => ???), + // the typer will transform the pattern to a `Bind(..., Typed(Ident(a), ThisType(...)))`, + // then post typer will change all the `Ident` with a `ThisType` to a `This`. + // Therefore, after pattern matching, we will have the following tree `Bind(..., Typed(This(...), ThisType(...)))`. + // We handle now here the case were the pattern was transformed to a `This`, relying on the fact that the logic for + // `Typed` above will create the correct type test. + case WildcardPattern() | This(_) => onSuccess case SeqLiteral(pats, _) => matchElemsPlan(scrutinee, pats, exact = true, onSuccess) diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 624ec9628d87..d9a1ea9ad9af 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -139,7 +139,7 @@ class PickleQuotes extends MacroTransform { /** Get the holeContents of the transformed tree */ def getContents() = - val res = holeContents.result + val res = holeContents.result() holeContents.clear() res end HoleContentExtractor diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index fcf1b384fda1..7aeaeb4e319d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -258,7 +258,7 @@ class Pickler extends Phase { } private def computeInternalName(cls: ClassSymbol)(using Context): String = - if cls.is(Module) then cls.binaryClassName.stripSuffix(str.MODULE_SUFFIX).nn + if cls.is(Module) then cls.binaryClassName.stripSuffix(str.MODULE_SUFFIX) else cls.binaryClassName override def run(using Context): Unit = { @@ -291,7 +291,7 @@ class Pickler extends Phase { val isOutline = isJavaAttr // TODO: later we may want outline for Scala sources too val attributes = Attributes( sourceFile = sourceRelativePath, - scala2StandardLibrary = ctx.settings.YcompileScala2Library.value, + scala2StandardLibrary = Feature.shouldBehaveAsScala2, explicitNulls = ctx.settings.YexplicitNulls.value, captureChecked = Feature.ccEnabled, withPureFuns = Feature.pureFunsEnabled, @@ -413,11 +413,11 @@ class Pickler extends Phase { ) if ctx.isBestEffort then val outpath = - ctx.settings.outputDir.value.jpath.toAbsolutePath.nn.normalize.nn - .resolve("META-INF").nn + ctx.settings.outputDir.value.jpath.nn.toAbsolutePath.normalize + .resolve("META-INF") .resolve("best-effort") Files.createDirectories(outpath) - BestEffortTastyWriter.write(outpath.nn, result) + BestEffortTastyWriter.write(outpath, result) result } diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index a7d1e70ea90e..9f79c063dc03 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -101,7 +101,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => private var compilingScala2StdLib = false override def initContext(ctx: FreshContext): Unit = initContextCalled = true - compilingScala2StdLib = ctx.settings.YcompileScala2Library.value(using ctx) + compilingScala2StdLib = Feature.shouldBehaveAsScala2(using ctx) val superAcc: SuperAccessors = new SuperAccessors(thisPhase) val synthMbr: SyntheticMembers = new SyntheticMembers(thisPhase) @@ -196,7 +196,10 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => val saved = inJavaAnnot inJavaAnnot = annot.symbol.is(JavaDefined) if (inJavaAnnot) checkValidJavaAnnotation(annot) - try transform(annot) + try + val annotCtx = if annot.hasAttachment(untpd.RetainsAnnot) + then ctx.addMode(Mode.InCaptureSet) else ctx + transform(annot)(using annotCtx) finally inJavaAnnot = saved } @@ -252,10 +255,8 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => sym.keepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) unusing.foreach(sym.addAnnotation) else if sym.is(ParamAccessor) then - // @publicInBinary is not a meta-annotation and therefore not kept by `keepAnnotationsCarrying` - val publicInBinaryAnnotOpt = sym.getAnnotation(defn.PublicInBinaryAnnot) - sym.keepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot)) - for publicInBinaryAnnot <- publicInBinaryAnnotOpt do sym.addAnnotation(publicInBinaryAnnot) + sym.keepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot), + andAlso = defn.NonBeanParamAccessorAnnots) else sym.keepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) if sym.isScala2Macro && !ctx.settings.XignoreScala2Macros.value && @@ -334,20 +335,14 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => } } - private object dropInlines extends TreeMap { - override def transform(tree: Tree)(using Context): Tree = tree match { - case tree @ Inlined(call, _, expansion) => - val newExpansion = PruneErasedDefs.trivialErasedTree(tree) - cpy.Inlined(tree)(call, Nil, newExpansion) - case _ => super.transform(tree) - } - } - def checkUsableAsValue(tree: Tree)(using Context): Tree = def unusable(msg: Symbol => Message) = errorTree(tree, msg(tree.symbol)) - if tree.symbol.is(ConstructorProxy) then - unusable(ConstructorProxyNotValue(_)) + if tree.symbol.is(PhantomSymbol) then + if tree.symbol.isDummyCaptureParam then + unusable(DummyCaptureParamNotValue(_)) + else + unusable(ConstructorProxyNotValue(_)) else if tree.symbol.isContextBoundCompanion then unusable(ContextBoundCompanionNotValue(_)) else @@ -410,26 +405,13 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => checkUsableAsValue(tree) match case tree1: Select => transformSelect(tree1, Nil) case tree1 => tree1 - case tree: Apply => - val methType = tree.fun.tpe.widen.asInstanceOf[MethodType] - val app = - if (methType.hasErasedParams) - tpd.cpy.Apply(tree)( - tree.fun, - tree.args.zip(methType.erasedParams).map((arg, isErased) => - if !isErased then arg - else - if methType.isResultDependent then - Checking.checkRealizable(arg.tpe, arg.srcPos, "erased argument") - if (methType.isImplicitMethod && arg.span.isSynthetic) - arg match - case _: RefTree | _: Apply | _: TypeApply if arg.symbol.is(Erased) => - dropInlines.transform(arg) - case _ => - PruneErasedDefs.trivialErasedTree(arg) - else dropInlines.transform(arg))) - else - tree + case app: Apply => + val methType = app.fun.tpe.widen.asInstanceOf[MethodType] + if (methType.hasErasedParams) + for (arg, isErased) <- app.args.lazyZip(methType.paramErasureStatuses) do + if isErased then + if methType.isResultDependent then + Checking.checkRealizable(arg.tpe, arg.srcPos, "erased argument") def app1 = // reverse order of transforming args and fun. This way, we get a chance to see other // well-formedness errors before reporting errors in possible inferred type args of fun. @@ -493,18 +475,16 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => case tree: ValDef => annotateExperimentalCompanion(tree.symbol) registerIfHasMacroAnnotations(tree) - checkErasedDef(tree) Checking.checkPolyFunctionType(tree.tpt) - val tree1 = cpy.ValDef(tree)(tpt = makeOverrideTypeDeclared(tree.symbol, tree.tpt), rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) + val tree1 = cpy.ValDef(tree)(tpt = makeOverrideTypeDeclared(tree.symbol, tree.tpt)) if tree1.removeAttachment(desugar.UntupledParam).isDefined then checkStableSelection(tree.rhs) processValOrDefDef(super.transform(tree1)) case tree: DefDef => registerIfHasMacroAnnotations(tree) - checkErasedDef(tree) Checking.checkPolyFunctionType(tree.tpt) annotateContextResults(tree) - val tree1 = cpy.DefDef(tree)(tpt = makeOverrideTypeDeclared(tree.symbol, tree.tpt), rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) + val tree1 = cpy.DefDef(tree)(tpt = makeOverrideTypeDeclared(tree.symbol, tree.tpt)) processValOrDefDef(superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef])) case tree: TypeDef => registerIfHasMacroAnnotations(tree) @@ -628,12 +608,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => Checking.checkAndAdaptExperimentalImports(trees) super.transformStats(trees, exprOwner, wrapResult) - /** Transforms the rhs tree into a its default tree if it is in an `erased` val/def. - * Performed to shrink the tree that is known to be erased later. - */ - private def normalizeErasedRhs(rhs: Tree, sym: Symbol)(using Context) = - if (sym.isEffectivelyErased) dropInlines.transform(rhs) else rhs - private def registerNeedsInlining(tree: Tree)(using Context): Unit = if tree.symbol.is(Inline) && !Inlines.inInlineMethod && !ctx.mode.is(Mode.NoInline) then ctx.compilationUnit.needsInlining = true @@ -648,21 +622,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => if sym.derivesFrom(defn.MacroAnnotationClass) && !sym.isStatic then report.error("classes that extend MacroAnnotation must not be inner/local classes", sym.srcPos) - private def checkErasedDef(tree: ValOrDefDef)(using Context): Unit = - def checkOnlyErasedParams(): Unit = tree match - case tree: DefDef => - for params <- tree.paramss; param <- params if !param.symbol.isType && !param.symbol.is(Erased) do - report.error("erased definition can only have erased parameters", param.srcPos) - case _ => - - if tree.symbol.is(Erased, butNot = Macro) then - checkOnlyErasedParams() - val tpe = tree.rhs.tpe - if tpe.derivesFrom(defn.NothingClass) then - report.error("`erased` definition cannot be implemented with en expression of type Nothing", tree.srcPos) - else if tpe.derivesFrom(defn.NullClass) then - report.error("`erased` definition cannot be implemented with en expression of type Null", tree.srcPos) - private def annotateExperimentalCompanion(sym: Symbol)(using Context): Unit = if sym.is(Module) then ExperimentalAnnotation.copy(sym.companionClass).foreach(sym.addAnnotation) diff --git a/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala b/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala index 47eb70cb46d4..a72c0f9bed7c 100644 --- a/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala +++ b/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala @@ -9,24 +9,13 @@ import SymDenotations.* import Symbols.* import typer.RefChecks import MegaPhase.MiniPhase -import ast.tpd -import reporting.InlinedAnonClassWarning - -import config.Feature -import Decorators.* -import dotty.tools.dotc.core.Types.MethodType /** This phase makes all erased term members of classes private so that they cannot * conflict with non-erased members. This is needed so that subsequent phases like - * ResolveSuper that inspect class members work correctly. - * The phase also replaces all expressions that appear in an erased context by - * default values. This is necessary so that subsequent checking phases such - * as IsInstanceOfChecker don't give false negatives. + * ResolveSuper that inspect class members work correctly and so that we do not + * generate bridges for such members. See pos/i23451.scala for a test case. */ -class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform => - import tpd.* - import PruneErasedDefs.* - +class PruneErasedDefs extends MiniPhase with SymTransformer: override def phaseName: String = PruneErasedDefs.name override def description: String = PruneErasedDefs.description @@ -36,53 +25,11 @@ class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform => override def runsAfterGroupsOf: Set[String] = Set(RefChecks.name, ExplicitOuter.name) override def transformSym(sym: SymDenotation)(using Context): SymDenotation = - if !sym.isEffectivelyErased || !sym.isTerm || sym.is(Private) || !sym.owner.isClass then sym - else sym.copySymDenotation(initFlags = sym.flags | Private) - - override def transformApply(tree: Apply)(using Context): Tree = - tree.fun.tpe.widen match - case mt: MethodType if mt.hasErasedParams => - cpy.Apply(tree)(tree.fun, tree.args.zip(mt.erasedParams).map((a, e) => if e then trivialErasedTree(a) else a)) - case _ => - tree - - override def transformValDef(tree: ValDef)(using Context): Tree = - checkErasedInExperimental(tree.symbol) - if !tree.symbol.isEffectivelyErased || tree.rhs.isEmpty then tree - else cpy.ValDef(tree)(rhs = trivialErasedTree(tree.rhs)) - - override def transformDefDef(tree: DefDef)(using Context): Tree = - def checkNoInlineAnnoClasses(tree: DefDef)(using Context): Unit = - if tree.symbol.is(Inline) then - new TreeTraverser { - def traverse(tree: Tree)(using Context): Unit = - tree match - case tree: TypeDef if tree.symbol.isAnonymousClass => - report.warning(new InlinedAnonClassWarning(), tree.symbol.sourcePos) - case _ => traverseChildren(tree) - }.traverse(tree) + if !sym.is(Private) && sym.isEffectivelyErased && sym.isTerm && sym.owner.isClass + then sym.copySymDenotation(initFlags = sym.flags | Private) + else sym - checkNoInlineAnnoClasses(tree) - checkErasedInExperimental(tree.symbol) - if !tree.symbol.isEffectivelyErased || tree.rhs.isEmpty then tree - else cpy.DefDef(tree)(rhs = trivialErasedTree(tree.rhs)) - - override def transformTypeDef(tree: TypeDef)(using Context): Tree = - checkErasedInExperimental(tree.symbol) - tree - - def checkErasedInExperimental(sym: Symbol)(using Context): Unit = - // Make an exception for Scala 2 experimental macros to allow dual Scala 2/3 macros under non experimental mode - if sym.is(Erased, butNot = Macro) && sym != defn.Compiletime_erasedValue && !sym.isInExperimentalScope then - Feature.checkExperimentalFeature("erased", sym.sourcePos) -} - -object PruneErasedDefs { - import tpd.* - - val name: String = "pruneErasedDefs" +object PruneErasedDefs: + val name: String = "pruneErasedDefs" val description: String = "drop erased definitions and simplify erased expressions" - - def trivialErasedTree(tree: Tree)(using Context): Tree = - ref(defn.Compiletime_erasedValue).appliedToType(tree.tpe).withSpan(tree.span) -} +end PruneErasedDefs \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index f057959a52b4..51ccdfe57274 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -75,7 +75,7 @@ object Recheck: * as by-name arguments of applied types. See note in doc comment for * ElimByName phase. Test case is bynamefun.scala. */ - private def mapExprType(tp: Type)(using Context): Type = tp match + def mapExprType(tp: Type)(using Context): Type = tp match case ExprType(rt) => defn.ByNameFunction(rt) case _ => tp @@ -183,10 +183,10 @@ abstract class Recheck extends Phase, SymTransformer: /** If true, remember the new types of nodes in this compilation unit * as an attachment in the unit's tpdTree node. By default, this is - * enabled when -Xprint:cc is set. Can be overridden. + * enabled when -Vprint:cc is set. Can be overridden. */ def keepNuTypes(using Context): Boolean = - ctx.settings.Xprint.value.containsPhase(thisPhase) + ctx.settings.Vprint.value.containsPhase(thisPhase) def resetNuTypes()(using Context): Unit = nuTypes.clear(resetToInitial = false) @@ -269,7 +269,7 @@ abstract class Recheck extends Phase, SymTransformer: def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Type = inContext(linkConstructorParams(sym).withOwner(sym)): val resType = recheck(tree.tpt) - if tree.rhs.isEmpty || sym.isInlineMethod || sym.isEffectivelyErased + if tree.rhs.isEmpty || sym.isInlineMethod then resType else recheck(tree.rhs, resType) @@ -304,7 +304,7 @@ abstract class Recheck extends Phase, SymTransformer: /** A hook to massage the type of an applied method */ protected def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = funtpe - protected def recheckArg(arg: Tree, formal: Type)(using Context): Type = + protected def recheckArg(arg: Tree, formal: Type, pref: ParamRef, app: Apply)(using Context): Type = recheck(arg, formal) /** A hook to check all the parts of an application: @@ -336,7 +336,7 @@ abstract class Recheck extends Phase, SymTransformer: else fntpe.paramInfos def recheckArgs(args: List[Tree], formals: List[Type], prefs: List[ParamRef]): List[Type] = args match case arg :: args1 => - val argType = recheckArg(arg, normalizeByName(formals.head)) + val argType = recheckArg(arg, normalizeByName(formals.head), prefs.head, tree) val formals1 = if fntpe.isParamDependent then formals.tail.map(_.substParam(prefs.head, argType)) @@ -449,9 +449,11 @@ abstract class Recheck extends Phase, SymTransformer: defn.UnitType def recheckTry(tree: Try, pt: Type)(using Context): Type = - val bodyType = recheck(tree.expr, pt) - val casesTypes = tree.cases.map(recheckCase(_, defn.ThrowableType, pt)) - val finalizerType = recheck(tree.finalizer, defn.UnitType) + recheckTryRest(recheck(tree.expr, pt), tree.cases, tree.finalizer, pt) + + protected def recheckTryRest(bodyType: Type, cases: List[CaseDef], finalizer: Tree, pt: Type)(using Context): Type = + val casesTypes = cases.map(recheckCase(_, defn.ThrowableType, pt)) + val finalizerType = recheck(finalizer, defn.UnitType) TypeComparer.lub(bodyType :: casesTypes) def seqLiteralElemProto(tree: SeqLiteral, pt: Type, declared: Type)(using Context): Type = @@ -524,12 +526,14 @@ abstract class Recheck extends Phase, SymTransformer: if !skipRecheck(sym) then recheckDef(tree, sym) sym.termRef case tree: TypeDef => - // TODO: Should we allow for completers as for ValDefs or DefDefs? - tree.rhs match - case impl: Template => - recheckClassDef(tree, impl, sym.asClass)(using ctx.localContext(tree, sym)) - case _ => - recheckTypeDef(tree, sym)(using ctx.localContext(tree, sym)) + if !skipRecheck(sym) then + // TODO: Should we allow for completers as for ValDefs or DefDefs? + tree.rhs match + case impl: Template => + recheckClassDef(tree, impl, sym.asClass)(using ctx.localContext(tree, sym)) + case _ => + recheckTypeDef(tree, sym)(using ctx.localContext(tree, sym)) + sym.typeRef case tree: Labeled => recheckLabeled(tree, pt) def recheckUnnamed(tree: Tree, pt: Type): Type = tree match diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeApplyMethods.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeApplyMethods.scala index fd314b94e50c..c85f06e6075f 100644 --- a/compiler/src/dotty/tools/dotc/transform/SpecializeApplyMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeApplyMethods.scala @@ -5,6 +5,7 @@ import ast.Trees.*, ast.tpd, core.* import Contexts.*, Types.*, Decorators.*, Symbols.*, DenotTransformers.* import SymDenotations.*, Scopes.*, StdNames.*, NameOps.*, Names.* import MegaPhase.MiniPhase +import config.Feature import scala.collection.mutable @@ -25,7 +26,7 @@ class SpecializeApplyMethods extends MiniPhase with InfoTransformer { override def description: String = SpecializeApplyMethods.description override def isEnabled(using Context): Boolean = - !ctx.settings.scalajs.value && !ctx.settings.YcompileScala2Library.value + !ctx.settings.scalajs.value && !Feature.shouldBehaveAsScala2 private def specApplySymbol(sym: Symbol, args: List[Type], ret: Type)(using Context): Symbol = { val name = nme.apply.specializedFunction(ret, args) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index b5386d5bd1df..9e60ece4cc67 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -1,7 +1,6 @@ package dotty.tools.dotc package transform -import scala.language.unsafeNulls import java.io.{PrintWriter, StringWriter} import java.lang.reflect.{InvocationTargetException, Method => JLRMethod} @@ -54,7 +53,7 @@ object Splicer { inContext(sliceContext) { val oldContextClassLoader = Thread.currentThread().getContextClassLoader Thread.currentThread().setContextClassLoader(classLoader) - try ctx.profiler.onMacroSplice(owner){ + try { val interpreter = new SpliceInterpreter(splicePos, classLoader) // Some parts of the macro are evaluated during the unpickling performed in quotedExprToTree diff --git a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala index 5a63235fc3c0..0077cb969e3a 100644 --- a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -155,8 +155,7 @@ class SuperAccessors(thisPhase: DenotTransformer) { val needAccessor = name.isTermName // Types don't need super accessors - && !sym.isEffectivelyErased // Erased and concrete inline methods are not called at runtime - && !sym.isInlineMethod // so they don't need superaccessors. + && !sym.isInlineMethod // Inline methods are not called at runtime so they don't need superaccessors. && (clazz != currentClass || !validCurrentClass || mix.name.isEmpty && clazz.is(Trait)) if (needAccessor) atPhase(thisPhase.next)(superAccessorCall(sel, mix.name)) diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 4bc9575996d1..fbff51acb514 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -10,6 +10,7 @@ import NameOps.* import Annotations.Annotation import typer.ProtoTypes.constrained import ast.untpd +import config.Feature import util.Property import util.Spans.Span @@ -68,7 +69,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { myCaseSymbols = defn.caseClassSynthesized myCaseModuleSymbols = myCaseSymbols.filter(_ ne defn.Any_equals) myEnumValueSymbols = List(defn.Product_productPrefix) - myNonJavaEnumValueSymbols = myEnumValueSymbols :+ defn.Any_toString :+ defn.Enum_ordinal + myNonJavaEnumValueSymbols = myEnumValueSymbols :+ defn.Any_toString :+ defn.Enum_ordinal :+ defn.Any_hashCode } def valueSymbols(using Context): List[Symbol] = { initSymbols; myValueSymbols } @@ -79,7 +80,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { private def existingDef(sym: Symbol, clazz: ClassSymbol)(using Context): Symbol = val existing = sym.matchingMember(clazz.thisType) - if ctx.settings.YcompileScala2Library.value && clazz.isValueClass && (sym == defn.Any_equals || sym == defn.Any_hashCode) then + if Feature.shouldBehaveAsScala2 && clazz.isValueClass && (sym == defn.Any_equals || sym == defn.Any_hashCode) then NoSymbol else if existing != sym && !existing.is(Deferred) then existing @@ -116,6 +117,12 @@ class SyntheticMembers(thisPhase: DenotTransformer) { def syntheticDefIfMissing(sym: Symbol): List[Tree] = if (existingDef(sym, clazz).exists) Nil else syntheticDef(sym) :: Nil + def identifierRef: Tree = + if isSimpleEnumValue then // owner is `def $new(_$ordinal: Int, $name: String) = new MyEnum { ... }` + ref(clazz.owner.paramSymss.head.find(_.name == nme.nameDollar).get) + else // assume owner is `val Foo = new MyEnum { def ordinal = 0 }` + Literal(Constant(clazz.owner.name.toString)) + def syntheticDef(sym: Symbol): Tree = { val synthetic = sym.copy( owner = clazz, @@ -135,12 +142,6 @@ class SyntheticMembers(thisPhase: DenotTransformer) { else identifierRef - def identifierRef: Tree = - if isSimpleEnumValue then // owner is `def $new(_$ordinal: Int, $name: String) = new MyEnum { ... }` - ref(clazz.owner.paramSymss.head.find(_.name == nme.nameDollar).get) - else // assume owner is `val Foo = new MyEnum { def ordinal = 0 }` - Literal(Constant(clazz.owner.name.toString)) - def ordinalRef: Tree = if isSimpleEnumValue then // owner is `def $new(_$ordinal: Int, $name: String) = new MyEnum { ... }` ref(clazz.owner.paramSymss.head.find(_.name == nme.ordinalDollar_).get) @@ -168,7 +169,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { case nme.productPrefix if isEnumValue => nameRef case nme.productPrefix => ownNameLit case nme.productElement => - if ctx.settings.YcompileScala2Library.value then productElementBodyForScala2Compat(accessors.length, vrefss.head.head) + if Feature.shouldBehaveAsScala2 then productElementBodyForScala2Compat(accessors.length, vrefss.head.head) else productElementBody(accessors.length, vrefss.head.head) case nme.productElementName => productElementNameBody(accessors.length, vrefss.head.head) } @@ -357,7 +358,8 @@ class SyntheticMembers(thisPhase: DenotTransformer) { * For case classes with primitive paramters, see [[caseHashCodeBody]]. */ def chooseHashcode(using Context) = - if (accessors.isEmpty) Literal(Constant(ownName.hashCode)) + if (isNonJavaEnumValue) identifierRef.select(nme.hashCode_).appliedToTermArgs(Nil) + else if (accessors.isEmpty) Literal(Constant(ownName.hashCode)) else if (accessors.exists(_.info.finalResultType.classSymbol.isPrimitiveValueClass)) caseHashCodeBody else @@ -720,7 +722,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { val syntheticMembers = serializableObjectMethod(clazz) ::: serializableEnumValueMethod(clazz) ::: caseAndValueMethods(clazz) checkInlining(syntheticMembers) val impl1 = cpy.Template(impl)(body = syntheticMembers ::: impl.body) - if ctx.settings.YcompileScala2Library.value then impl1 + if Feature.shouldBehaveAsScala2 then impl1 else addMirrorSupport(impl1) } diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index b8052721ff27..690a180e52ca 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -4,9 +4,9 @@ package transform import ast.{TreeTypeMap, tpd} import config.Printers.tailrec import core.* -import Contexts.*, Flags.*, Symbols.*, Decorators.em +import Contexts.*, Flags.*, Symbols.*, Decorators.* import Constants.Constant -import NameKinds.{TailLabelName, TailLocalName, TailTempName} +import NameKinds.{DefaultGetterName, TailLabelName, TailLocalName, TailTempName} import StdNames.nme import reporting.* import transform.MegaPhase.MiniPhase @@ -196,7 +196,8 @@ class TailRec extends MiniPhase { def isInfiniteRecCall(tree: Tree): Boolean = { def tailArgOrPureExpr(stat: Tree): Boolean = stat match { case stat: ValDef if stat.name.is(TailTempName) || !stat.symbol.is(Mutable) => tailArgOrPureExpr(stat.rhs) - case Assign(lhs: Ident, rhs) if lhs.symbol.name.is(TailLocalName) => tailArgOrPureExpr(rhs) + case Assign(lhs: Ident, rhs) if lhs.symbol.name.is(TailLocalName) => + tailArgOrPureExpr(rhs) || varForRewrittenThis.exists(_ == lhs.symbol && rhs.tpe.isStable) case Assign(lhs: Ident, rhs: Ident) => lhs.symbol == rhs.symbol case stat: Ident if stat.symbol.name.is(TailLocalName) => true case _ => tpd.isPureExpr(stat) @@ -324,7 +325,14 @@ class TailRec extends MiniPhase { method.matches(calledMethod) && enclosingClass.appliedRef.widen <:< prefix.tpe.widenDealias - if (isRecursiveCall) + if isRecursiveCall then + if ctx.settings.Whas.recurseWithDefault then + tree.args.find(_.symbol.name.is(DefaultGetterName)) match + case Some(arg) => + val DefaultGetterName(_, index) = arg.symbol.name: @unchecked + report.warning(RecurseWithDefault(calledMethod.info.firstParamNames(index)), tree.srcPos) + case _ => + if (inTailPosition) { tailrec.println("Rewriting tail recursive call: " + tree.span) rewrote = true @@ -345,6 +353,12 @@ class TailRec extends MiniPhase { case prefix: This if prefix.symbol == enclosingClass => // Avoid assigning `this = this` assignParamPairs + case prefix + if prefix.symbol.is(Module) + && prefix.symbol.moduleClass == enclosingClass + && isPurePath(prefix) => + // Avoid assigning `this = MyObject` + assignParamPairs case _ => (getVarForRewrittenThis(), noTailTransform(prefix)) :: assignParamPairs diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 2f4ad3a83e4e..b43acce78d35 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -63,7 +63,7 @@ class TreeChecker extends Phase with SymTransformer { } if (prev.exists) - assert(cur.exists || prev.is(ConstructorProxy), i"companion disappeared from $symd") + assert(cur.exists || prev.is(PhantomSymbol), i"companion disappeared from $symd") } def transformSym(symd: SymDenotation)(using Context): SymDenotation = { @@ -387,7 +387,7 @@ object TreeChecker { } def isSymWithoutDef(sym: Symbol)(using Context): Boolean = - sym.is(ConstructorProxy) || sym.isContextBoundCompanion + sym.is(PhantomSymbol) || sym.isContextBoundCompanion /** Exclude from double definition checks any erased symbols that were * made `private` in phase `UnlinkErasedDecls`. These symbols will be removed @@ -858,7 +858,7 @@ object TreeChecker { 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", "") + else err.getStackTrace.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. @@ -894,4 +894,4 @@ object TreeChecker { case _ => Nil } -} +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala b/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala index fb1dd04bd6ad..55b26d89b5a0 100644 --- a/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala @@ -7,6 +7,7 @@ import core.* import Contexts.*, Trees.*, Types.* import DenotTransformers.*, MegaPhase.* import ExtensionMethods.*, ValueClasses.* +import Decorators.* /** This phase inlines calls to methods of value classes. @@ -58,12 +59,18 @@ class VCInlineMethods extends MiniPhase with IdentityDenotTransformer { */ private def rewire(tree: Tree, mtArgs: List[Tree] = Nil, mArgss: List[List[Tree]] = Nil) (using Context): Tree = + def noTypeApplyIn(tree: Tree): Boolean = tree match + case _: TypeApply => false + case Apply(fn, _) => noTypeApplyIn(fn) + case _ => true tree match { case Apply(qual, mArgs) => rewire(qual, mtArgs, mArgs :: mArgss) case TypeApply(qual, mtArgs2) => - assert(mtArgs == Nil) - rewire(qual, mtArgs2, mArgss) + if noTypeApplyIn(qual) then + rewire(qual, mtArgs2, mArgss) + else + rewire(qual, mtArgs, mtArgs2 :: mArgss) case sel @ Select(qual, _) => val origMeth = sel.symbol val origCls = origMeth.enclosingClass diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 4d5c467cf4fe..d34f95bedef1 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -29,7 +29,7 @@ class Checker extends Phase: override val runsAfter = Set(Pickler.name) override def isEnabled(using Context): Boolean = - super.isEnabled && (ctx.settings.Whas.checkInit || ctx.settings.YcheckInitGlobal.value) + super.isEnabled && (ctx.settings.Whas.safeInit || ctx.settings.YsafeInitGlobal.value) def traverse(traverser: InitTreeTraverser)(using Context): Boolean = monitor(phaseName): val unit = ctx.compilationUnit @@ -50,10 +50,10 @@ class Checker extends Phase: cancellable { val classes = traverser.getClasses() - if ctx.settings.Whas.checkInit then + if ctx.settings.Whas.safeInit then Semantic.checkClasses(classes)(using checkCtx) - if ctx.settings.YcheckInitGlobal.value then + if ctx.settings.YsafeInitGlobal.value then val obj = new Objects obj.checkClasses(classes)(using checkCtx) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala index 85feb609c90a..0b843b4b33fd 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala @@ -31,7 +31,7 @@ object Errors: report.warning(show, this.pos) end Error - override def toString() = this.getClass.getName.nn + override def toString() = this.getClass.getName /** Access non-initialized field */ case class AccessNonInit(field: Symbol)(val trace: Trace) extends Error: diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 226e8dbf3fb4..92262d528487 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -18,6 +18,7 @@ import util.{ SourcePosition, NoSourcePosition } import config.Printers.init as printer import reporting.StoreReporter import reporting.trace as log +import reporting.trace.force as forcelog import typer.Applications.* import Errors.* @@ -55,16 +56,9 @@ import dotty.tools.dotc.util.SrcPos * 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 "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. + * 2. It is inter-procedural and flow-sensitive. * - * 3. It is inter-procedural and flow-sensitive. - * - * 4. It is object-sensitive by default and parameter-sensitive on-demand. - * - * 5. The check is modular in the sense that each object is checked separately and there is no + * 3. The check is modular in the sense that each object is checked separately and there is no * whole-program analysis. However, the check is not modular in terms of project boundaries. * */ @@ -90,117 +84,129 @@ class Objects(using Context @constructorOnly): /** Syntax for the data structure abstraction used in abstract domain: * * ve ::= ObjectRef(class) // global object - * | 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 + * | InstanceRef(class, ownerObject, ctor, regions) // instance of a class + * | ArrayRef(ownerObject, regions) // represents values of native array class in Array.scala + * | Fun(code, thisV, scope) // 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 | 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 | 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 - * varsMap = valsym -> addr // each mutable field has an abstract address - * outersMap = class -> val // maps outer objects to their values - * - * arrayMap = OfArray -> addr // an array has one address that stores the join value of every element - * - * heap = addr -> val // heap is mutable - * - * env = (valsMap, Option[env]) // stores local variables in the residing method, and possibly outer environments - * - * addr ::= localVarAddr(regions, valsym, owner) - * | fieldVarAddr(regions, valsym, owner) // independent of OfClass/ObjectRef - * | arrayAddr(regions, owner) // independent of array element type + * | UnknownValue // values whose source are unknown at compile time + * vs ::= ValueSet(Set(ve)) // set of abstract values + * Value ::= ve | vs | Package + * Ref ::= ObjectRef | InstanceRef | ArrayRef // values that represent a reference to some (global or instance) object + * RefSet ::= Set(ref) // set of refs + * Bottom ::= RefSet(Empty) // unreachable code + * ThisValue ::= Ref | RefSet // possible values for 'this' + * EnvRef(meth, ownerObject) // represents environments for methods or functions + * EnvSet ::= Set(EnvRef) + * InstanceBody ::= (valsMap: Map[Symbol, Value], + outersMap: Map[ClassSymbol, Value], + outerEnv: EnvSet) // represents combined information of all instances represented by a ref + * Heap ::= Ref -> InstanceBody // heap is mutable + * EnvBody ::= (valsMap: Map[Symbol, Value], + * thisV: Value, + * outerEnv: EnvSet) // represents combined information of all instances represented by an env + * EnvMap ::= EnvRef -> EnvBody + * Scope ::= Ref | EnvRef + * Config ::= (thisV: Value, scope: Scope, Heap, EnvMap) + * Cache ::= Config -> (Heap, EnvMap) * * regions ::= List(sourcePosition) */ - sealed abstract class Value: + sealed trait Value: def show(using Context): String - /** ValueElement are elements that can be contained in a RefSet */ - sealed abstract class ValueElement extends Value + /** ValueElement are elements that can be contained in a ValueSet */ + sealed trait ValueElement extends Value /** - * A reference caches the values for outers and immutable fields. + * Represents the possible values of the current enclosing scope when evaluating an expression */ - sealed abstract class Ref( - valsMap: mutable.Map[Symbol, Value], - varsMap: mutable.Map[Symbol, Heap.Addr], - outersMap: mutable.Map[ClassSymbol, Value]) - extends ValueElement: - protected val vals: mutable.Map[Symbol, Value] = valsMap - protected val vars: mutable.Map[Symbol, Heap.Addr] = varsMap - protected val outers: mutable.Map[ClassSymbol, Value] = outersMap + sealed abstract class Scope(using trace: Trace): // TODO: rename it to reflect that it is key to the heap + def getTrace: Trace = trace - def isObjectRef: Boolean = this.isInstanceOf[ObjectRef] + def isRef = this.isInstanceOf[Ref] - def klass: ClassSymbol + def isEnv = this.isInstanceOf[Env.EnvRef] + + def asRef: Ref = this.asInstanceOf[Ref] + + def asEnv: Env.EnvRef = this.asInstanceOf[Env.EnvRef] - def valValue(sym: Symbol): Value = vals(sym) + def owner: ClassSymbol - def varAddr(sym: Symbol): Heap.Addr = vars(sym) + def show(using Context): String + end Scope + + sealed abstract class Ref(using Trace) extends Scope with ValueElement: + def klass: ClassSymbol - def outerValue(cls: ClassSymbol): Value = outers(cls) + def isObjectRef: Boolean = this.isInstanceOf[ObjectRef] + + def valValue(sym: Symbol)(using Heap.MutableData): Value = Heap.readVal(this, sym) - def hasVal(sym: Symbol): Boolean = vals.contains(sym) + def varValue(sym: Symbol)(using Heap.MutableData): Value = Heap.readVal(this, sym) - def hasVar(sym: Symbol): Boolean = vars.contains(sym) + def hasVal(sym: Symbol)(using Heap.MutableData): Boolean = Heap.containsVal(this, sym) - def hasOuter(cls: ClassSymbol): Boolean = outers.contains(cls) + def hasVar(sym: Symbol)(using Heap.MutableData): Boolean = Heap.containsVal(this, sym) - def initVal(field: Symbol, value: Value)(using Context) = log("Initialize " + field.show + " = " + value + " for " + this, printer) { + def initVal(field: Symbol, value: Value)(using Context, Heap.MutableData) = log("Initialize " + field.show + " = " + value + " for " + this, printer) { assert(!field.is(Flags.Mutable), "Field is mutable: " + field.show) - assert(!vals.contains(field), "Field already set: " + field.show) - vals(field) = value + Heap.writeJoinVal(this, field, value) } - def initVar(field: Symbol, addr: Heap.Addr)(using Context) = log("Initialize " + field.show + " = " + addr + " for " + this, printer) { + def initVar(field: Symbol, value: Value)(using Context, Heap.MutableData) = log("Initialize " + field.show + " = " + value + " for " + this, printer) { assert(field.is(Flags.Mutable), "Field is not mutable: " + field.show) - assert(!vars.contains(field), "Field already set: " + field.show) - vars(field) = addr + Heap.writeJoinVal(this, field, value) } - def initOuter(cls: ClassSymbol, value: Value)(using Context) = log("Initialize outer " + cls.show + " = " + value + " for " + this, printer) { - assert(!outers.contains(cls), "Outer already set: " + cls) - outers(cls) = value + def initOuter(sym: Symbol, outers: Value)(using Context, Heap.MutableData) = log("Initialize outer " + sym.show + " = " + outers + " for " + this, printer) { + Heap.writeJoinOuter(this, sym, outers) } + def initOuterEnv(outerEnvs: Env.EnvSet)(using Context, Heap.MutableData) = + Heap.writeJoinOuterEnv(this, outerEnvs) + + def outerValue(sym: Symbol)(using Heap.MutableData): Value = Heap.readOuter(this, sym) + + def outer(using Heap.MutableData): Value = this.outerValue(klass) + + def outerEnv(using Heap.MutableData): Env.EnvSet = Heap.readOuterEnv(this) + end Ref + /** A reference to a static object */ - case class ObjectRef(klass: ClassSymbol) - extends Ref(valsMap = mutable.Map.empty, varsMap = mutable.Map.empty, outersMap = mutable.Map.empty): - val owner = klass + case class ObjectRef private (klass: ClassSymbol)(using Trace) extends Ref: + def owner = klass def show(using Context) = "ObjectRef(" + klass.show + ")" + object ObjectRef: + def apply(klass: ClassSymbol)(using Context, Heap.MutableData, Trace): ObjectRef = + val obj = new ObjectRef(klass) + obj.initOuter(klass, Bottom) + obj.initOuterEnv(Env.NoEnv) + obj + /** * Represents values that are instances of the specified class. * * Note that the 2nd parameter block does not take part in the definition of equality. */ - case class OfClass private ( - klass: ClassSymbol, outer: Value, ctor: Symbol, args: List[Value], env: Env.Data)( - valsMap: mutable.Map[Symbol, Value], varsMap: mutable.Map[Symbol, Heap.Addr], outersMap: mutable.Map[ClassSymbol, Value]) - extends Ref(valsMap, varsMap, outersMap): - def widenedCopy(outer: Value, args: List[Value], env: Env.Data): OfClass = - new OfClass(klass, outer, ctor, args, env)(this.valsMap, this.varsMap, this.outersMap) - + case class InstanceRef private ( + klass: ClassSymbol, owner: ClassSymbol, ctor: Symbol, regions: Regions.Data)(using Trace) + extends Ref: def show(using Context) = - val valFields = vals.map(_.show + " -> " + _.show) - "OfClass(" + klass.show + ", outer = " + outer + ", args = " + args.map(_.show) + " env = " + env.show + ", vals = " + valFields + ")" + "InstanceRef(" + klass.show + ", ctor = " + ctor.show + ", owner = " + owner + ")" - object OfClass: + object InstanceRef: def apply( - klass: ClassSymbol, outer: Value, ctor: Symbol, args: List[Value], env: Env.Data)( - using Context - ): OfClass = - val instance = new OfClass(klass, outer, ctor, args, env)( - valsMap = mutable.Map.empty, varsMap = mutable.Map.empty, outersMap = mutable.Map.empty - ) + klass: ClassSymbol, outer: Value, outerEnv: Env.EnvSet, ctor: Symbol)( + using Context, Heap.MutableData, State.Data, Regions.Data, Trace + ): InstanceRef = + val owner = State.currentObject + val instance = new InstanceRef(klass, owner, ctor, summon[Regions.Data]) instance.initOuter(klass, outer) + instance.initOuterEnv(outerEnv) instance /** @@ -215,21 +221,35 @@ class Objects(using Context @constructorOnly): * * @param owner The static object whose initialization creates the array. */ - case class OfArray(owner: ClassSymbol, regions: Regions.Data)(using @constructorOnly ctx: Context, @constructorOnly trace: Trace) extends ValueElement: - val klass: ClassSymbol = defn.ArrayClass - val addr: Heap.Addr = Heap.arrayAddr(regions, owner) - def show(using Context) = "OfArray(owner = " + owner.show + ")" + case class ArrayRef private (owner: ClassSymbol, regions: Regions.Data)(using Trace) extends Ref: + val elementSymbol = defn.ArrayConstructor + + def klass: ClassSymbol = defn.ArrayClass + + def show(using Context) = "ArrayRef(owner = " + owner.show + ")" + + def readElement(using Heap.MutableData) = valValue(elementSymbol) + + def writeElement(value: Value)(using Heap.MutableData) = Heap.writeJoinVal(this, elementSymbol, value) + + object ArrayRef: + def apply(owner: ClassSymbol, regions: Regions.Data)(using Context, Trace, Heap.MutableData): ArrayRef = + val arr = new ArrayRef(owner, regions) + arr.initVal(arr.elementSymbol, Bottom) + arr.initOuter(arr.klass, Bottom) + arr.initOuterEnv(Env.NoEnv) + arr /** * Represents a lambda expression * @param klass The enclosing class of the anonymous function's creation site */ - 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 + ")" + case class Fun(code: Tree, thisV: ThisValue, klass: ClassSymbol, scope: Scope) extends ValueElement: + def show(using Context) = "Fun(" + code.show + ", " + scope.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 + * Assumption: all field initializers and methods calls on such values should not trigger initialization of global objects * or read/write mutable fields */ case class SafeValue(typeSymbol: Symbol) extends ValueElement: @@ -264,6 +284,11 @@ class Objects(using Context @constructorOnly): assert(typeSymbol.isDefined, "Invalid creation of SafeValue with type " + tpe) new SafeValue(typeSymbol.get) + /** Represents values unknown to the checker, such as values loaded without source + */ + case object UnknownValue extends ValueElement: + def show(using Context): String = "UnknownValue" + /** * Represents a set of values * @@ -272,7 +297,20 @@ class Objects(using Context @constructorOnly): case class ValueSet(values: Set[ValueElement]) extends Value: def show(using Context) = values.map(_.show).mkString("[", ",", "]") - case class Package(packageModuleClass: ClassSymbol) extends Value: + def isRefSet = this.isInstanceOf[RefSet] + + /** + * Represents a set of Refs. The `equals` method inherits from `ValueSet`, + * so no more fields should be added to `RefSet` + */ + class RefSet(val refs: Set[Ref]) extends ValueSet(refs.asInstanceOf[Set[ValueElement]]): + def joinOuters(sym: ClassSymbol)(using Heap.MutableData): ThisValue = + refs.map(_.outerValue(sym)).join.asInstanceOf[ThisValue] + + def joinOuterEnvs(using Heap.MutableData): Env.EnvSet = + refs.map(_.outerEnv).join + + case class Package(packageModuleClass: ClassSymbol) extends Value: // TODO: try to remove packages def show(using Context): String = "Package(" + packageModuleClass.show + ")" object Package: @@ -280,29 +318,10 @@ class Objects(using Context @constructorOnly): 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 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) + val Bottom = new RefSet(Set.empty) /** Possible types for 'this' */ - type ThisValue = Ref | Top.type + type ThisValue = Ref | RefSet /** Checking state */ object State: @@ -315,7 +334,7 @@ class Objects(using Context @constructorOnly): def currentObject(using data: Data): ClassSymbol = data.checkingObjects.last.klass - private def doCheckObject(classSym: ClassSymbol)(using ctx: Context, data: Data) = + private def doCheckObject(classSym: ClassSymbol)(using ctx: Context, data: Data, heap: Heap.MutableData, envMap: EnvMap.EnvMapMutableData) = val tpl = classSym.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] var count = 0 @@ -326,12 +345,12 @@ class Objects(using Context @constructorOnly): count += 1 given Trace = Trace.empty.add(classSym.defTree) - given Env.Data = Env.emptyEnv(tpl.constr.symbol) - given Heap.MutableData = Heap.empty() + // given Heap.MutableData = Heap.empty given returns: Returns.Data = Returns.empty() given regions: Regions.Data = Regions.empty // explicit name to avoid naming conflict val obj = ObjectRef(classSym) + given Scope = obj log("Iteration " + count) { data.checkingObjects += obj init(tpl, obj, classSym) @@ -356,8 +375,8 @@ class Objects(using Context @constructorOnly): obj end doCheckObject - def checkObjectAccess(clazz: ClassSymbol)(using data: Data, ctx: Context, pendingTrace: Trace): ObjectRef = - val index = data.checkingObjects.indexOf(ObjectRef(clazz)) + def checkObjectAccess(clazz: ClassSymbol)(using data: Data, ctx: Context, pendingTrace: Trace, heap: Heap.MutableData, envMap: EnvMap.EnvMapMutableData): ObjectRef = + val index = data.checkingObjects.indexWhere(_.klass == clazz) if index != -1 then if data.checkingObjects.size - 1 > index then @@ -384,202 +403,205 @@ class Objects(using Context @constructorOnly): /** Environment for parameters */ object Env: - abstract class Data: - private[Env] def getVal(x: Symbol)(using Context): Option[Value] - private[Env] def getVar(x: Symbol)(using Context): Option[Heap.Addr] - - def widen(height: Int)(using Context): Data - - def level: Int - - def show(using Context): String - /** Local environments can be deeply nested, therefore we need `outer`. * * For local variables in rhs of class field definitions, the `meth` is the primary constructor. */ - private case class LocalEnv - (private[Env] val params: Map[Symbol, Value], meth: Symbol, outer: Data) - (valsMap: mutable.Map[Symbol, Value], varsMap: mutable.Map[Symbol, Heap.Addr]) - (using Context) - extends Data: - val level = outer.level + 1 + case class EnvRef(meth: Symbol, owner: ClassSymbol)(using Trace) extends Scope: + def show(using Context) = + "meth: " + meth.show + "\n" + + "owner: " + owner.show - if (level > 3) - report.warning("[Internal error] Deeply nested environment, level = " + level + ", " + meth.show + " in " + meth.enclosingClass.show, meth.defTree) + def valValue(sym: Symbol)(using EnvMap.EnvMapMutableData): Value = EnvMap.readVal(this, sym) - private[Env] val vals: mutable.Map[Symbol, Value] = valsMap - private[Env] val vars: mutable.Map[Symbol, Heap.Addr] = varsMap + def varValue(sym: Symbol)(using EnvMap.EnvMapMutableData): Value = EnvMap.readVal(this, sym) - private[Env] def getVal(x: Symbol)(using Context): Option[Value] = - if x.is(Flags.Param) then params.get(x) - else vals.get(x) + def hasVal(sym: Symbol)(using EnvMap.EnvMapMutableData): Boolean = EnvMap.containsVal(this, sym) - private[Env] def getVar(x: Symbol)(using Context): Option[Heap.Addr] = - vars.get(x) + def hasVar(sym: Symbol)(using EnvMap.EnvMapMutableData): Boolean = EnvMap.containsVal(this, sym) - def widen(height: Int)(using Context): Data = - new LocalEnv(params.map(_ -> _.widen(height)), meth, outer.widen(height))(this.vals, this.vars) + def initVal(field: Symbol, value: Value)(using Context, EnvMap.EnvMapMutableData) = log("Initialize " + field.show + " = " + value + " for " + this, printer) { + assert(!field.is(Flags.Mutable), "Field is mutable: " + field.show) + EnvMap.writeJoinVal(this, field, value) + } - def show(using Context) = - "owner: " + meth.show + "\n" + - "params: " + params.map(_.show + " ->" + _.show).mkString("{", ", ", "}") + "\n" + - "vals: " + vals.map(_.show + " ->" + _.show).mkString("{", ", ", "}") + "\n" + - "vars: " + vars.map(_.show + " ->" + _).mkString("{", ", ", "}") + "\n" + - "outer = {\n" + outer.show + "\n}" + def initVar(field: Symbol, value: Value)(using Context, EnvMap.EnvMapMutableData) = log("Initialize " + field.show + " = " + value + " for " + this, printer) { + assert(field.is(Flags.Mutable), "Field is not mutable: " + field.show) + EnvMap.writeJoinVal(this, field, value) + } + + def initThisV(thisV: ThisValue)(using EnvMap.EnvMapMutableData) = + EnvMap.writeJoinThisV(this, thisV) + + def initOuterEnvs(outerEnvs: EnvSet)(using EnvMap.EnvMapMutableData) = + EnvMap.writeJoinOuterEnv(this, outerEnvs) - end LocalEnv + def thisV(using EnvMap.EnvMapMutableData): ThisValue = EnvMap.getThisV(this) - object NoEnv extends Data: - val level = 0 + def outerEnvs(using EnvMap.EnvMapMutableData): EnvSet = EnvMap.getOuterEnvs(this) + end EnvRef - private[Env] def getVal(x: Symbol)(using Context): Option[Value] = - throw new RuntimeException("Invalid usage of non-existent env") + case class EnvSet(envs: Set[EnvRef]): + def show(using Context) = envs.map(_.show).mkString("[", ",", "]") - private[Env] def getVar(x: Symbol)(using Context): Option[Heap.Addr] = - throw new RuntimeException("Invalid usage of non-existent env") + def lookupSymbol(sym: Symbol)(using EnvMap.EnvMapMutableData): Value = envs.map(_.valValue(sym)).join - def widen(height: Int)(using Context): Data = this + def joinThisV(using EnvMap.EnvMapMutableData): ThisValue = envs.map(_.thisV).join.asInstanceOf[ThisValue] - def show(using Context): String = "NoEnv" - end NoEnv + def joinOuterEnvs(using EnvMap.EnvMapMutableData): EnvSet = envs.map(_.outerEnvs).join + + val NoEnv = EnvSet(Set.empty) /** An empty environment can be used for non-method environments, e.g., field initializers. * * The owner for the local environment for field initializers is the primary constructor of the * enclosing class. */ - def emptyEnv(meth: Symbol)(using Context): Data = - new LocalEnv(Map.empty, meth, NoEnv)(valsMap = mutable.Map.empty, varsMap = mutable.Map.empty) + def emptyEnv(meth: Symbol)(using Context, State.Data, EnvMap.EnvMapMutableData, Trace): EnvRef = + _of(Map.empty, meth, Bottom, NoEnv) - def valValue(x: Symbol)(using data: Data, ctx: Context, trace: Trace): Value = - data.getVal(x) match - case Some(theValue) => - theValue - case _ => - report.warning("[Internal error] Value not found " + x.show + "\nenv = " + data.show + ". " + Trace.show, Trace.position) + def valValue(x: Symbol)(using env: EnvRef, ctx: Context, trace: Trace, envMap: EnvMap.EnvMapMutableData): Value = + if env.hasVal(x) then + env.valValue(x) + else + report.warning("[Internal error] Value not found " + x.show + "\nenv = " + env.show + ". " + Trace.show, Trace.position) Bottom - def getVal(x: Symbol)(using data: Data, ctx: Context): Option[Value] = data.getVal(x) + private[Env] def _of(argMap: Map[Symbol, Value], meth: Symbol, thisV: ThisValue, outerEnv: EnvSet) + (using State.Data, EnvMap.EnvMapMutableData, Trace): EnvRef = + val env = EnvRef(meth, State.currentObject) + argMap.foreach(env.initVal(_, _)) + env.initThisV(thisV) + env.initOuterEnvs(outerEnv) + env - def getVar(x: Symbol)(using data: Data, ctx: Context): Option[Heap.Addr] = data.getVar(x) + /** + * The main procedure for searching through the outer chain + * @param target The symbol to search for if `bySymbol = true`; otherwise the method symbol of the target environment + * @param scopeSet The set of scopes as starting point + * @return The scopes that contains symbol `target` or whose method is `target`, + * and the value for `C.this` where C is the enclosing class of the result scopes + */ + private[Env] def resolveEnvRecur( + target: Symbol, envSet: EnvSet, bySymbol: Boolean = true) + : Contextual[Option[EnvSet]] = log("Resolving environment, target = " + target + ", envSet = " + envSet, printer) { + if envSet == Env.NoEnv then None + else + val filter = + if bySymbol then + envSet.envs.filter(_.hasVal(target)) + else + envSet.envs.filter(_.meth == target) + + assert(filter.isEmpty || filter.size == envSet.envs.size, "Either all scopes or no scopes contain " + target) + if (!filter.isEmpty) then + val resultSet = EnvSet(filter) + Some(resultSet) + else + val outerEnvs = envSet.joinOuterEnvs + if outerEnvs != NoEnv then // Search for the outerEnvs of the current envSet + resolveEnvRecur(target, outerEnvs, bySymbol) + else + // Search through the outerEnvs of the instances represented by `this` + // in case that `target` is in outer methods separated by local class definitions + // See `tests/init-global/warn/local-class.scala` + val thisV = envSet.joinThisV + val outerEnvsOfThis = thisV match { + case ref: Ref => ref.outerEnv + case refSet: RefSet => refSet.joinOuterEnvs + } + resolveEnvRecur(target, outerEnvsOfThis, bySymbol) + } - private[Env] def _of(argMap: Map[Symbol, Value], meth: Symbol, outer: Data): Data = - new LocalEnv(argMap, meth, outer)(valsMap = mutable.Map.empty, varsMap = mutable.Map.empty) - def ofDefDef(ddef: DefDef, args: List[Value], outer: Data)(using Context): Data = + def ofDefDef(ddef: DefDef, args: List[Value], thisV: ThisValue, outerEnv: EnvSet) + (using State.Data, EnvMap.EnvMapMutableData, Trace): EnvRef = val params = ddef.termParamss.flatten.map(_.symbol) assert(args.size == params.size, "arguments = " + args.size + ", params = " + params.size) - assert(ddef.symbol.owner.isClass ^ (outer != NoEnv), "ddef.owner = " + ddef.symbol.owner.show + ", outer = " + outer + ", " + ddef.source) - _of(params.zip(args).toMap, ddef.symbol, outer) + // assert(ddef.symbol.owner.is(Method) ^ (outerEnv == NoEnv), "ddef.owner = " + ddef.symbol.owner.show + ", outerEnv = " + outerEnv + ", " + ddef.source) + _of(params.zip(args).toMap, ddef.symbol, thisV, outerEnv) - def ofByName(byNameParam: Symbol, outer: Data): Data = + def ofByName(byNameParam: Symbol, thisV: ThisValue, outerEnv: EnvSet) + (using State.Data, EnvMap.EnvMapMutableData, Trace): EnvRef = assert(byNameParam.is(Flags.Param) && byNameParam.info.isInstanceOf[ExprType]); - _of(Map.empty, byNameParam, outer) + _of(Map.empty, byNameParam, thisV, outerEnv) - def setLocalVal(x: Symbol, value: Value)(using data: Data, ctx: Context): Unit = + def setLocalVal(x: Symbol, value: Value)(using scope: Scope, ctx: Context, heap: Heap.MutableData, envMap: EnvMap.EnvMapMutableData): Unit = assert(!x.isOneOf(Flags.Param | Flags.Mutable), "Only local immutable variable allowed") - data match - case localEnv: LocalEnv => - assert(!localEnv.vals.contains(x), "Already initialized local " + x.show) - localEnv.vals(x) = value - case _ => - throw new RuntimeException("Incorrect local environment for initializing " + x.show) + scope match + case env: EnvRef => + env.initVal(x, value) + case ref: Ref => + ref.initVal(x, value) // This is possible for match statement in class body. - def setLocalVar(x: Symbol, addr: Heap.Addr)(using data: Data, ctx: Context): Unit = + def setLocalVar(x: Symbol, value: Value)(using scope: Scope, ctx: Context, heap: Heap.MutableData, envMap: EnvMap.EnvMapMutableData): Unit = assert(x.is(Flags.Mutable, butNot = Flags.Param), "Only local mutable variable allowed") - data match - case localEnv: LocalEnv => - assert(!localEnv.vars.contains(x), "Already initialized local " + x.show) - localEnv.vars(x) = addr - case _ => - throw new RuntimeException("Incorrect local environment for initializing " + x.show) + scope match + case env: EnvRef => + env.initVar(x, value) + case ref: Ref => + ref.initVar(x, value) // This is possible for match statement in class body. /** * 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. + * Searches for the environment that defines `target`, starting from `env` as the innermost. * * @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. + * @param scope The scope where the local variable is referenced. * - * @return the environment that owns the `target` and value for `this` owned by the given method. + * @return the environment that owns the `target`. */ - 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 + def resolveEnvByValue(target: Symbol, thisV: ThisValue, scope: Scope): Contextual[Option[EnvSet]] = log("Resolving env by value for " + target.show + ", this = " + thisV.show + ", scope = " + scope.show, printer) { + val currentEnv = scope match { + case ref: Ref => ref.outerEnv + case env: Env.EnvRef => EnvSet(Set(env)) + } + resolveEnvRecur(target, currentEnv) } /** - * Resolve the environment owned by the given method `enclosing`. + * Resolve the environment associated by the given method `enclosing`, starting from `env` as the innermost. * * The method could be located in outer scope with intermixed classes between its definition * site and usage site. * - * 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 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. + * @param scope The scope where the local variable is referenced. * - * @return the environment and value for `this` owned by the given method. + * @return the environment whose symbol == `enclosing`. */ - 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) { + def resolveEnvByMethod(enclosing: Symbol, thisV: ThisValue, scope: Scope): Contextual[EnvSet] = log("Resolving env which corresponds to method " + enclosing.show + ", this = " + thisV.show + ", scope = " + scope.show, printer) { assert(enclosing.is(Flags.Method), "Only method symbols allows, got " + enclosing.show) - env match - case localEnv: LocalEnv => - 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 => - resolveEnvByOwner(enclosing, outer, ref.env) - case _ => - // TODO: properly handle the case where ref.outer is ValueSet - None - case _ => - None + val currentEnv = scope match { + case ref: Ref => ref.outerEnv + case env: Env.EnvRef => EnvSet(Set(env)) + } + val result = resolveEnvRecur(enclosing, currentEnv, bySymbol = false) + assert(!result.isEmpty, "Failed to find environment for " + enclosing + "!") + result.get } - def withEnv[T](env: Data)(fn: Data ?=> T): T = fn(using env) + def withEnv[T](env: EnvRef)(fn: EnvRef ?=> T): T = fn(using env) end Env /** Abstract heap for mutable fields */ object Heap: - abstract class Addr: - /** The static object which owns the mutable slot */ - def owner: ClassSymbol - def getTrace: Trace = Trace.empty - - /** The address for mutable fields of objects. */ - private case class FieldAddr(regions: Regions.Data, field: Symbol, owner: ClassSymbol)(trace: Trace) extends Addr: - override def getTrace: Trace = trace - - /** The address for mutable local variables . */ - private case class LocalVarAddr(regions: Regions.Data, sym: Symbol, owner: ClassSymbol) extends Addr + private case class InstanceBody( + valsMap: Map[Symbol, Value], + outersMap: Map[Symbol, Value], + outerEnvs: Env.EnvSet + ) + + private def emptyInstanceBody(): InstanceBody = InstanceBody( + valsMap = Map.empty, + outersMap = Map.empty, + outerEnvs = Env.NoEnv + ) /** Immutable heap data used in the cache. * @@ -587,61 +609,202 @@ class Objects(using Context @constructorOnly): * * TODO: speed up equality check for heap. */ - opaque type Data = Map[Addr, Value] + opaque type Data = Map[Ref, InstanceBody] /** Store the heap as a mutable field to avoid threading it through the program. */ class MutableData(private[Heap] var heap: Data): - private[Heap] def writeJoin(addr: Addr, value: Value): Unit = - heap.get(addr) match + private[Heap] def writeJoinVal(ref: Ref, valSymbol: Symbol, value: Value): Unit = + heap.get(ref) match + case None => + heap = heap.updated(ref, Heap.emptyInstanceBody()) + writeJoinVal(ref, valSymbol, value) + + case Some(current) => + val newValsMap = current.valsMap.join(valSymbol, value) + heap = heap.updated(ref, new InstanceBody( + valsMap = newValsMap, + outersMap = current.outersMap, + outerEnvs = current.outerEnvs + )) + + private[Heap] def writeJoinOuter(ref: Ref, parentSymbol: Symbol, outers: Value): Unit = + heap.get(ref) match case None => - heap = heap.updated(addr, value) + heap = heap.updated(ref, Heap.emptyInstanceBody()) + writeJoinOuter(ref, parentSymbol, outers) case Some(current) => - val value2 = value.join(current) - if value2 != current then - heap = heap.updated(addr, value2) + val newOutersMap = current.outersMap.join(parentSymbol, outers) + heap = heap.updated(ref, new InstanceBody( + valsMap = current.valsMap, + outersMap = newOutersMap, + outerEnvs = current.outerEnvs + )) + + private[Heap] def writeJoinOuterEnv(ref: Ref, outerEnvs: Env.EnvSet): Unit = + heap.get(ref) match + case None => + heap = heap.updated(ref, Heap.emptyInstanceBody()) + writeJoinOuterEnv(ref, outerEnvs) + + case Some(current) => + val newOuterEnvs = current.outerEnvs.join(outerEnvs) + heap = heap.updated(ref, new InstanceBody( + valsMap = current.valsMap, + outersMap = current.outersMap, + outerEnvs = newOuterEnvs + )) end MutableData - def empty(): MutableData = new MutableData(Map.empty) + def empty: MutableData = new MutableData(Map.empty) + + def contains(ref: Ref)(using mutable: MutableData): Boolean = + mutable.heap.contains(ref) - def contains(addr: Addr)(using mutable: MutableData): Boolean = - mutable.heap.contains(addr) + def containsVal(ref: Ref, value: Symbol)(using mutable: MutableData): Boolean = + if mutable.heap.contains(ref) then + mutable.heap(ref).valsMap.contains(value) + else + false - def read(addr: Addr)(using mutable: MutableData): Value = - mutable.heap(addr) + def readVal(ref: Ref, value: Symbol)(using mutable: MutableData): Value = + mutable.heap(ref).valsMap(value) - def writeJoin(addr: Addr, value: Value)(using mutable: MutableData): Unit = - mutable.writeJoin(addr, value) + def readOuter(ref: Ref, parent: Symbol)(using mutable: MutableData): Value = + mutable.heap(ref).outersMap(parent) - def localVarAddr(regions: Regions.Data, sym: Symbol, owner: ClassSymbol): Addr = - LocalVarAddr(regions, sym, owner) + def readOuterEnv(ref: Ref)(using mutable: MutableData): Env.EnvSet = + mutable.heap(ref).outerEnvs - def fieldVarAddr(regions: Regions.Data, sym: Symbol, owner: ClassSymbol)(using Trace): Addr = - FieldAddr(regions, sym, owner)(summon[Trace]) + def writeJoinVal(ref: Ref, valSymbol: Symbol, value: Value)(using mutable: MutableData): Unit = + mutable.writeJoinVal(ref, valSymbol, value) - def arrayAddr(regions: Regions.Data, owner: ClassSymbol)(using Trace, Context): Addr = - FieldAddr(regions, defn.ArrayClass, owner)(summon[Trace]) + def writeJoinOuter(ref: Ref, outer: Symbol, outers: Value)(using mutable: MutableData): Unit = + mutable.writeJoinOuter(ref, outer, outers) + + def writeJoinOuterEnv(ref: Ref, outerEnvs: Env.EnvSet)(using mutable: MutableData): Unit = + mutable.writeJoinOuterEnv(ref, outerEnvs) def getHeapData()(using mutable: MutableData): Data = mutable.heap def setHeap(newHeap: Data)(using mutable: MutableData): Unit = mutable.heap = newHeap + end Heap + + object EnvMap: + private case class EnvBody( + valsMap: Map[Symbol, Value], + thisV: ThisValue, + outerEnvs: Env.EnvSet + ) + + private def emptyEnvBody(): EnvBody = EnvBody( + valsMap = Map.empty, + thisV = Bottom, + outerEnvs = Env.NoEnv + ) + + /** Immutable env map data used in the cache. + * + * We need to use structural equivalence so that in different iterations the cache can be effective. + */ + opaque type Data = Map[Env.EnvRef, EnvBody] + + /** Store the heap as a mutable field to avoid threading it through the program. */ + class EnvMapMutableData(private[EnvMap] var envMap: Data): + private[EnvMap] def writeJoinVal(env: Env.EnvRef, valSymbol: Symbol, value: Value): Unit = + envMap.get(env) match + case None => + envMap = envMap.updated(env, EnvMap.emptyEnvBody()) + writeJoinVal(env, valSymbol, value) + + case Some(current) => + val newValsMap = current.valsMap.join(valSymbol, value) + envMap = envMap.updated(env, new EnvBody( + valsMap = newValsMap, + thisV = current.thisV, + outerEnvs = current.outerEnvs + )) + + private[EnvMap] def writeJoinThisV(env: Env.EnvRef, thisV: ThisValue): Unit = + envMap.get(env) match + case None => + envMap = envMap.updated(env, EnvMap.emptyEnvBody()) + writeJoinThisV(env, thisV) + + case Some(current) => + val newThisV = current.thisV.join(thisV).asInstanceOf[ThisValue] + envMap = envMap.updated(env, new EnvBody( + valsMap = current.valsMap, + thisV = newThisV, + outerEnvs = current.outerEnvs + )) + + private[EnvMap] def writeJoinOuterEnv(env: Env.EnvRef, outerEnvs: Env.EnvSet): Unit = + envMap.get(env) match + case None => + envMap = envMap.updated(env, EnvMap.emptyEnvBody()) + writeJoinOuterEnv(env, outerEnvs) + + case Some(current) => + val newOuterEnvs = current.outerEnvs.join(outerEnvs) + envMap = envMap.updated(env, new EnvBody( + valsMap = current.valsMap, + thisV = current.thisV, + outerEnvs = newOuterEnvs + )) + end EnvMapMutableData + + def empty: EnvMapMutableData = new EnvMapMutableData(Map.empty) + + def contains(env: Env.EnvRef)(using mutable: EnvMapMutableData): Boolean = + mutable.envMap.contains(env) + + def containsVal(env: Env.EnvRef, value: Symbol)(using mutable: EnvMapMutableData): Boolean = + if mutable.envMap.contains(env) then + mutable.envMap(env).valsMap.contains(value) + else + false + + def readVal(env: Env.EnvRef, value: Symbol)(using mutable: EnvMapMutableData): Value = + mutable.envMap(env).valsMap(value) + + def getThisV(env: Env.EnvRef)(using mutable: EnvMapMutableData): ThisValue = + mutable.envMap(env).thisV + + def getOuterEnvs(env: Env.EnvRef)(using mutable: EnvMapMutableData): Env.EnvSet = + mutable.envMap(env).outerEnvs + + def writeJoinVal(env: Env.EnvRef, valSymbol: Symbol, value: Value)(using mutable: EnvMapMutableData): Unit = + mutable.writeJoinVal(env, valSymbol, value) + + def writeJoinThisV(env: Env.EnvRef, thisV: ThisValue)(using mutable: EnvMapMutableData): Unit = + mutable.writeJoinThisV(env, thisV) + + def writeJoinOuterEnv(env: Env.EnvRef, outerEnvs: Env.EnvSet)(using mutable: EnvMapMutableData): Unit = + mutable.writeJoinOuterEnv(env, outerEnvs) + + def getEnvMapData()(using mutable: EnvMapMutableData): Data = mutable.envMap + + def setEnvMap(newEnvMap: Data)(using mutable: EnvMapMutableData): Unit = mutable.envMap = newEnvMap + end EnvMap /** Cache used to terminate the check */ object Cache: - case class Config(thisV: Value, env: Env.Data, heap: Heap.Data) - case class Res(value: Value, heap: Heap.Data) + case class Config(thisV: Value, scope: Scope, heap: Heap.Data, envMap: EnvMap.Data) + case class Res(value: Value, heap: Heap.Data, envMap: EnvMap.Data) class Data extends Cache[Config, Res]: - def get(thisV: Value, expr: Tree)(using Heap.MutableData, Env.Data): Option[Value] = - val config = Config(thisV, summon[Env.Data], Heap.getHeapData()) + def get(thisV: Value, expr: Tree)(using Heap.MutableData, Scope, EnvMap.EnvMapMutableData): Option[Value] = + val config = Config(thisV, summon[Scope], Heap.getHeapData(), EnvMap.getEnvMapData()) super.get(config, expr).map(_.value) - def cachedEval(thisV: ThisValue, expr: Tree, cacheResult: Boolean)(fun: Tree => Value)(using Heap.MutableData, Env.Data): Value = - val config = Config(thisV, summon[Env.Data], Heap.getHeapData()) - val result = super.cachedEval(config, expr, cacheResult, default = Res(Bottom, Heap.getHeapData())) { expr => - Res(fun(expr), Heap.getHeapData()) + def cachedEval(thisV: ThisValue, expr: Tree, cacheResult: Boolean)(fun: Tree => Value)(using Heap.MutableData, Scope, EnvMap.EnvMapMutableData): Value = + val config = Config(thisV, summon[Scope], Heap.getHeapData(), EnvMap.getEnvMapData()) + val result = super.cachedEval(config, expr, cacheResult, default = Res(Bottom, Heap.getHeapData(), EnvMap.getEnvMapData())) { expr => + Res(fun(expr), Heap.getHeapData(), EnvMap.getEnvMapData()) } Heap.setHeap(result.heap) + EnvMap.setEnvMap(result.envMap) result.value end Cache @@ -685,28 +848,35 @@ class Objects(using Context @constructorOnly): case None => report.warning("[Internal error] Unhandled return for method " + meth + " in " + meth.owner.show + ". Trace:\n" + Trace.show, Trace.position) - type Contextual[T] = (Context, State.Data, Env.Data, Cache.Data, Heap.MutableData, Regions.Data, Returns.Data, Trace) ?=> T + type Contextual[T] = (Context, State.Data, Scope, Cache.Data, Heap.MutableData, EnvMap.EnvMapMutableData, Regions.Data, Returns.Data, Trace) ?=> T // --------------------------- domain operations ----------------------------- case class ArgInfo(value: Value, trace: Trace, tree: Tree) - 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 (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(Set(a, b)) - case _ => Bottom + trait Join[V]: + extension (v1: V) + def join(v2: V): V + + given Join[Value] with + 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 (Bottom, b) => b + case (a, Bottom) => a + // ThisValue join ThisValue => ThisValue + case (refSet1: RefSet, refSet2: RefSet) => new RefSet(refSet1.refs ++ refSet2.refs) + case (ref: Ref, refSet: RefSet) => new RefSet(refSet.refs + ref) + case (refSet: RefSet, ref: Ref) => new RefSet(refSet.refs + ref) + case (ref1: Ref, ref2: Ref) => new RefSet(Set(ref1, ref2)) + 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(Set(a, b)) + case _ => Bottom + extension (a: Value) 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)) @@ -715,27 +885,6 @@ class Objects(using Context @constructorOnly): 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 - - case ValueSet(values) => - values.map(ref => ref.widen(height)).join - - case Fun(code, thisV, klass, env) => - Fun(code, thisV.widenThisValue(height), klass, env.widen(height - 1)) - - case ref @ OfClass(klass, outer, _, args, env) => - val outer2 = outer.widen(height - 1) - val args2 = args.map(_.widen(height - 1)) - val env2 = env.widen(height - 1) - ref.widenedCopy(outer2, args2, env2) - - case _ => a - } - def filterType(tpe: Type)(using Context): Value = tpe match case t @ SAMType(_, _) if a.isInstanceOf[Fun] => a // if tpe is SAMType and a is Fun, allow it @@ -751,7 +900,7 @@ class Objects(using Context @constructorOnly): else val klass = sym.asClass a match - case UnknownValue | Top => a + case UnknownValue => a case Package(packageModuleClass) => // the typer might mistakenly set the receiver to be a package instead of package object. // See pos/packageObjectStringInterpolator.scala @@ -759,19 +908,36 @@ class Objects(using Context @constructorOnly): 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 (thisV: ThisValue) + def toValueSet: ValueSet = thisV match + case ref: Ref => ValueSet(Set(ref)) + case vs: ValueSet => vs + + given Join[Env.EnvSet] with + extension (a: Env.EnvSet) + def join(b: Env.EnvSet): Env.EnvSet = Env.EnvSet(a.envs ++ b.envs) extension (values: Iterable[Value]) - def join: Value = if values.isEmpty then Bottom else values.reduce { (v1, v2) => v1.join(v2) } + def join: Value = + if values.isEmpty then + Bottom + else + values.reduce { (v1, v2) => v1.join(v2) } + + extension (scopes: Iterable[Env.EnvSet]) + def join: Env.EnvSet = + if scopes.isEmpty then + Env.NoEnv + else + scopes.reduce { (s1, s2) => s1.join(s2) } - def widen(height: Int): Contextual[List[Value]] = values.map(_.widen(height)).toList + extension [V : Join](map: Map[Symbol, V]) + def join(sym: Symbol, value: V): Map[Symbol, V] = + if !map.contains(sym) then map.updated(sym, value) + else map.updated(sym, map(sym).join(value)) /** Check if the checker option reports warnings about unknown code */ @@ -795,9 +961,6 @@ 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 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) @@ -823,15 +986,7 @@ class Objects(using Context @constructorOnly): // 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 returnType = targetType.finalResultType val typeSymbol = SafeValue.getSafeTypeSymbol(returnType) if typeSymbol.isDefined then // since method is pure and return type is safe, no need to analyze method body @@ -841,8 +996,8 @@ class Objects(using Context @constructorOnly): 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) + // convert SafeType to an InstanceRef before analyzing method body + val ref = InstanceRef(cls, Bottom, Env.NoEnv, NoSymbol) call(ref, meth, args, receiver, superType, needResolve) case Bottom => @@ -852,28 +1007,27 @@ class Objects(using Context @constructorOnly): case _ if args.map(_.value).contains(Bottom) => Bottom - case arr: OfArray => + case arr: ArrayRef => val target = resolve(defn.ArrayClass, meth) if target == defn.Array_apply || target == defn.Array_clone then - if arr.addr.owner == State.currentObject then - Heap.read(arr.addr) + if arr.owner == State.currentObject then + arr.readElement else - errorReadOtherStaticObject(State.currentObject, arr.addr) + errorReadOtherStaticObject(State.currentObject, arr) Bottom else if target == defn.Array_update then assert(args.size == 2, "Incorrect number of arguments for Array update, found = " + args.size) - if arr.addr.owner != State.currentObject then - errorMutateOtherStaticObject(State.currentObject, arr.addr) + if arr.owner != State.currentObject then + errorMutateOtherStaticObject(State.currentObject, arr) else - Heap.writeJoin(arr.addr, args.tail.head.value) + arr.writeElement(args.tail.head.value) Bottom else // Array.length is OK SafeValue(defn.IntType) case ref: Ref => - val isLocal = !meth.owner.isClass val target = if !needResolve then meth @@ -886,8 +1040,8 @@ class Objects(using Context @constructorOnly): if target.isOneOf(Flags.Method) then if target.owner == defn.ArrayModuleClass && target.name == nme.apply then - val arr = OfArray(State.currentObject, summon[Regions.Data]) - Heap.writeJoin(arr.addr, args.map(_.value).join) + val arr = ArrayRef(State.currentObject, summon[Regions.Data]) + arr.writeElement(args.map(_.value).join) arr else if target.equals(defn.Predef_classOf) then // Predef.classOf is a stub method in tasty and is replaced in backend @@ -896,16 +1050,18 @@ class Objects(using Context @constructorOnly): val cls = target.owner.enclosingClass.asClass val ddef = target.defTree.asInstanceOf[DefDef] val meth = ddef.symbol - val (thisV : ThisValue, outerEnv) = - if meth.owner.isClass then + if meth.owner.enclosingMethod == cls.primaryConstructor then + // meth is top-level method (ref, Env.NoEnv) else - Env.resolveEnvByOwner(meth.owner.enclosingMethod, ref, summon[Env.Data]).getOrElse(Top -> Env.NoEnv) + val enclosingMethod = meth.owner.enclosingMethod + val outerEnvs = Env.resolveEnvByMethod(enclosingMethod, ref, summon[Scope]) + (outerEnvs.joinThisV, outerEnvs) - val env2 = Env.ofDefDef(ddef, args.map(_.value), outerEnv) + val env2 = Env.ofDefDef(ddef, args.map(_.value), thisV, outerEnv) extendTrace(ddef) { - given Env.Data = env2 + given Scope = env2 cache.cachedEval(ref, ddef.rhs, cacheResult = true) { expr => Returns.installHandler(meth) val res = cases(expr, thisV, cls) @@ -926,7 +1082,7 @@ class Objects(using Context @constructorOnly): // See tests/init/pos/Type.scala Bottom - case Fun(code, thisV, klass, env) => + case Fun(code, thisVOfClosure, klass, scope) => // meth == NoSymbol for poly functions if meth.name == nme.tupled then value // a call like `fun.tupled` @@ -934,8 +1090,12 @@ class Objects(using Context @constructorOnly): code match case ddef: DefDef => if meth.name == nme.apply then - given Env.Data = Env.ofDefDef(ddef, args.map(_.value), env) - extendTrace(code) { eval(ddef.rhs, thisV, klass, cacheResult = true) } + val funEnv = scope match { + case ref: Ref => Env.ofDefDef(ddef, args.map(_.value), thisVOfClosure, Env.NoEnv) + case env: Env.EnvRef => Env.ofDefDef(ddef, args.map(_.value), thisVOfClosure, Env.EnvSet(Set(env))) + } + given Scope = funEnv + extendTrace(code) { eval(ddef.rhs, thisVOfClosure, klass, cacheResult = true) } else // The methods defined in `Any` and `AnyRef` are trivial and don't affect initialization. if meth.owner == defn.AnyClass || meth.owner == defn.ObjectClass then @@ -968,11 +1128,13 @@ class Objects(using Context @constructorOnly): if ctor.hasSource then val cls = ctor.owner.enclosingClass.asClass val ddef = ctor.defTree.asInstanceOf[DefDef] - val argValues = args.map(_.value) - given Env.Data = Env.ofDefDef(ddef, argValues, Env.NoEnv) + given Scope = ref if ctor.isPrimaryConstructor then val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] + val params = tpl.constr.termParamss.flatten.map(_.symbol) + val paramMap = params.zip(args.map(_.value)) + paramMap.foreach(ref.initVal(_, _)) extendTrace(cls.defTree) { eval(tpl, ref, cls, cacheResult = true) } else extendTrace(ddef) { // The return values for secondary constructors can be ignored @@ -985,6 +1147,9 @@ class Objects(using Context @constructorOnly): // no source code available UnknownValue + case ValueSet(values) if values.size == 1 => + callConstructor(values.head, ctor, args) + case _ => report.warning("[Internal error] unexpected constructor call, meth = " + ctor + ", this = " + value + Trace.show, Trace.position) Bottom @@ -999,12 +1164,13 @@ 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 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 arr: ArrayRef => + report.warning("[Internal error] unexpected tree in selecting an array, array = " + arr.show + Trace.show, Trace.position) + Bottom + case v @ SafeValue(_) => if v.typeSymbol != defn.NullClass then // selection on Null is sensible on AST level; no warning for it @@ -1024,21 +1190,24 @@ class Objects(using Context @constructorOnly): case ref: Ref => val target = if needResolve then resolve(ref.klass, field) else field if target.is(Flags.Lazy) then - given Env.Data = Env.emptyEnv(target.owner.asInstanceOf[ClassSymbol].primaryConstructor) - if target.hasSource then + given Scope = Env.emptyEnv(target.owner.asInstanceOf[ClassSymbol].primaryConstructor) + if ref.hasVal(target) then + ref.valValue(target) + else if target.hasSource then val rhs = target.defTree.asInstanceOf[ValDef].rhs - eval(rhs, ref, target.owner.asClass, cacheResult = true) + val result = eval(rhs, ref, target.owner.asClass, cacheResult = true) + ref.initVal(target, result) + result else UnknownValue else if target.exists then def isNextFieldOfColonColon: Boolean = ref.klass == defn.ConsClass && target.name.toString == "next" if target.isMutableVarOrAccessor && !isNextFieldOfColonColon then if ref.hasVar(target) then - val addr = ref.varAddr(target) - if addr.owner == State.currentObject then - Heap.read(addr) + if ref.owner == State.currentObject then + ref.varValue(target) else - errorReadOtherStaticObject(State.currentObject, addr) + errorReadOtherStaticObject(State.currentObject, ref) Bottom else if ref.isObjectRef && ref.klass.hasSource then report.warning("Access uninitialized field " + field.show + ". " + Trace.show, Trace.position) @@ -1068,10 +1237,6 @@ class Objects(using Context @constructorOnly): report.warning("[Internal error] unexpected tree in selecting a function, fun = " + fun.code.show + Trace.show, fun.code) Bottom - case arr: OfArray => - report.warning("[Internal error] unexpected tree in selecting an array, array = " + arr.show + Trace.show, Trace.position) - Bottom - case Bottom => Bottom case ValueSet(values) => @@ -1087,15 +1252,13 @@ 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 => + case arr: ArrayRef => report.warning("[Internal error] unexpected tree in assignment, array = " + arr.show + " field = " + field + Trace.show, Trace.position) case SafeValue(_) => @@ -1106,11 +1269,10 @@ class Objects(using Context @constructorOnly): case ref: Ref => if ref.hasVar(field) then - val addr = ref.varAddr(field) - if addr.owner != State.currentObject then - errorMutateOtherStaticObject(State.currentObject, addr) + if ref.owner != State.currentObject then + errorMutateOtherStaticObject(State.currentObject, ref) else - Heap.writeJoin(addr, rhs) + Heap.writeJoinVal(ref, field, rhs) else report.warning("Mutating a field before its initialization: " + field.show + ". " + Trace.show, Trace.position) end match @@ -1130,41 +1292,42 @@ 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 | SafeValue(_) => + case _ : Fun | _: ArrayRef | SafeValue(_) => report.warning("[Internal error] unexpected outer in instantiating a class, outer = " + outer.show + ", class = " + klass.show + ", " + Trace.show, Trace.position) Bottom case UnknownValue => reportWarningForUnknownValue("Instantiating when outer is unknown. " + Trace.show, Trace.position) - case outer: (Ref | Top.type | Package) => + case outer: (Ref | Package) => if klass == defn.ArrayClass then args.head.tree.tpe match case ConstantType(Constants.Constant(0)) => // new Array(0) Bottom case _ => - val arr = OfArray(State.currentObject, summon[Regions.Data]) - Heap.writeJoin(arr.addr, Bottom) + val arr = ArrayRef(State.currentObject, summon[Regions.Data]) arr else - // Widen the outer to finitize the domain. Arguments already widened in `evalArgs`. - val (outerWidened, envWidened) = + val (outerThis, outerEnv) = outer match 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 `Package` as outer, class = " + klass.show + ", outer = " + outer.show + ", " + Trace.show, Trace.position) - (Bottom, Env.NoEnv) - else - (thisV.widenThisValue(1), Env.NoEnv) + (Bottom, Env.NoEnv) + case outer : ThisValue => + if klass.owner.is(Flags.Package) then + 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 - // klass.enclosingMethod returns its primary constructor - Env.resolveEnvByOwner(klass.owner.enclosingMethod, thisV, summon[Env.Data]).getOrElse(UnknownValue -> Env.NoEnv) + // enclosingClass is specially handled for java static terms, so use `lexicallyEnclosingClass` here + val outerCls = klass.owner.lexicallyEnclosingClass.asClass + // When `klass` is directly nested in `outerCls`, `outerCls`.enclosingMethod returns its primary constructor + if klass.owner.enclosingMethod == outerCls.primaryConstructor then + (outer, Env.NoEnv) + else + val outerEnvs = Env.resolveEnvByMethod(klass.owner.enclosingMethod, outer, summon[Scope]) + (outer, outerEnvs) - val instance = OfClass(klass, outerWidened, ctor, args.map(_.value), envWidened) + val instance = InstanceRef(klass, outerThis, outerEnv, ctor) callConstructor(instance, ctor, args) case ValueSet(values) => @@ -1178,9 +1341,7 @@ class Objects(using Context @constructorOnly): */ def initLocal(sym: Symbol, value: Value): Contextual[Unit] = log("initialize local " + sym.show + " with " + value.show, printer) { if sym.is(Flags.Mutable) then - val addr = Heap.localVarAddr(summon[Regions.Data], sym, State.currentObject) - Env.setLocalVar(sym, addr) - Heap.writeJoin(addr, value) + Env.setLocalVar(sym, value) else Env.setLocalVal(sym, value) } @@ -1192,46 +1353,48 @@ 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] + def evalByNameParam(value: Value): Contextual[Value] = value match + case Fun(code, thisV, klass, scope) => + val byNameEnv = scope match { + case ref: Ref => Env.ofByName(sym, thisV, Env.NoEnv) + case env: Env.EnvRef => Env.ofByName(sym, thisV, Env.EnvSet(Set(env))) + } + given Scope = byNameEnv + eval(code, thisV, klass) + case UnknownValue => + reportWarningForUnknownValue("Calling on unknown value. " + Trace.show, Trace.position) + case Bottom => Bottom + case ValueSet(values) if values.size == 1 => + evalByNameParam(values.head) + case _: ValueSet | _: Ref | _: ArrayRef | _: Package | SafeValue(_) => + report.warning("[Internal error] Unexpected by-name value " + value.show + ". " + Trace.show, Trace.position) + Bottom + end evalByNameParam + // 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) => + Env.resolveEnvByValue(sym, thisV, summon[Scope]) match + case Some(envSet) => if sym.is(Flags.Mutable) then // Assume forward reference check is doing a good job - given Env.Data = env - Env.getVar(sym) match - case Some(addr) => - if addr.owner == State.currentObject then - Heap.read(addr) - else - errorReadOtherStaticObject(State.currentObject, addr) - Bottom - end if - case _ => - // Only vals can be lazy - report.warning("[Internal error] Variable not found " + sym.show + "\nenv = " + env.show + ". " + Trace.show, Trace.position) + val envsOwnedByOthers = envSet.envs.filter(_.owner != State.currentObject) + if envsOwnedByOthers.isEmpty then + envSet.lookupSymbol(sym) + else + errorReadOtherStaticObject(State.currentObject, envsOwnedByOthers.head) Bottom + end if else - given Env.Data = env if sym.is(Flags.Lazy) then + val outerThis = envSet.joinThisV + given Scope = Env.ofByName(sym, outerThis, envSet) val rhs = sym.defTree.asInstanceOf[ValDef].rhs - eval(rhs, thisV, sym.enclosingClass.asClass, cacheResult = true) + eval(rhs, outerThis, sym.enclosingClass.asClass, cacheResult = true) else // Assume forward reference check is doing a good job - val value = Env.valValue(sym) + val value = envSet.lookupSymbol(sym) if isByNameParam(sym) then - value match - case fun: Fun => - given Env.Data = Env.ofByName(sym, fun.env) - eval(fun.code, fun.thisV, fun.klass) - 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 | _: Package | SafeValue(_) => - report.warning("[Internal error] Unexpected by-name value " + value.show + ". " + Trace.show, Trace.position) - Bottom + evalByNameParam(value) else value @@ -1253,17 +1416,13 @@ class Objects(using Context @constructorOnly): assert(sym.is(Flags.Mutable), "Writing to immutable variable " + sym.show) // 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 - case Some(addr) => - if addr.owner != State.currentObject then - errorMutateOtherStaticObject(State.currentObject, addr) - else - Heap.writeJoin(addr, value) - case _ => - report.warning("[Internal error] Variable not found " + sym.show + "\nenv = " + env.show + ". " + Trace.show, Trace.position) + Env.resolveEnvByValue(sym, thisV, summon[Scope]) match + case Some(envSet) => + val envsOwnedByOthers = envSet.envs.filter(_.owner != State.currentObject) + if !envsOwnedByOthers.isEmpty then + errorMutateOtherStaticObject(State.currentObject, envsOwnedByOthers.head) + else + envSet.envs.foreach(EnvMap.writeJoinVal(_, sym, value)) case _ => report.warning("Assigning to variables in outer scope. " + Trace.show, Trace.position) @@ -1274,7 +1433,7 @@ class Objects(using Context @constructorOnly): // -------------------------------- algorithm -------------------------------- /** Check an individual object */ - private def accessObject(classSym: ClassSymbol)(using Context, State.Data, Trace): ObjectRef = log("accessing " + classSym.show, printer, (_: Value).show) { + private def accessObject(classSym: ClassSymbol)(using Context, State.Data, Trace, Heap.MutableData, EnvMap.EnvMapMutableData): ObjectRef = log("accessing " + classSym.show, printer, (_: Value).show) { if classSym.hasSource then State.checkObjectAccess(classSym) else @@ -1285,6 +1444,8 @@ class Objects(using Context @constructorOnly): def checkClasses(classes: List[ClassSymbol])(using Context): Unit = given State.Data = new State.Data given Trace = Trace.empty + given Heap.MutableData = Heap.empty // TODO: do garbage collection on the heap + given EnvMap.EnvMapMutableData = EnvMap.empty for classSym <- classes if classSym.isStaticObject @@ -1410,7 +1571,7 @@ class Objects(using Context @constructorOnly): case OuterSelectName(_, _) => val current = qualifier.tpe.classSymbol val target = expr.tpe.widenSingleton.classSymbol.asClass - withTrace(trace2) { resolveThis(target, qual, current.asClass) } + withTrace(trace2) { resolveThis(target, qual.asInstanceOf[ThisValue], klass) } case _ => withTrace(trace2) { select(qual, expr.symbol, receiver = qualifier.tpe) } @@ -1444,18 +1605,17 @@ class Objects(using Context @constructorOnly): extendTrace(id) { evalType(prefix, thisV, klass) } val value = eval(rhs, thisV, klass) - val widened = widenEscapedValue(value, rhs) if isLocal then - writeLocal(thisV, lhs.symbol, widened) + writeLocal(thisV, lhs.symbol, value) else - withTrace(trace2) { assign(receiver, lhs.symbol, widened, rhs.tpe) } + withTrace(trace2) { assign(receiver, lhs.symbol, value, rhs.tpe) } case closureDef(ddef) => - Fun(ddef, thisV, klass, summon[Env.Data]) + Fun(ddef, thisV, klass, summon[Scope]) case PolyFun(ddef) => - Fun(ddef, thisV, klass, summon[Env.Data]) + Fun(ddef, thisV, klass, summon[Scope]) case Block(stats, expr) => evalExprs(stats, thisV, klass) @@ -1498,8 +1658,8 @@ class Objects(using Context @constructorOnly): 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) + val arr = ArrayRef(State.currentObject, summon[Regions.Data]) + arr.writeElement(args.map(_.value).join) call(ObjectRef(module), meth, List(ArgInfo(arr, summon[Trace], EmptyTree)), module.typeRef, NoType) case Inlined(call, bindings, expansion) => @@ -1577,6 +1737,7 @@ class Objects(using Context @constructorOnly): case bind @ Bind(_, pat) => val (tpe, value) = evalPattern(scrutinee, pat) + initLocal(bind.symbol, value) (tpe, value) @@ -1643,8 +1804,12 @@ class Objects(using Context @constructorOnly): val seqPats = pats.drop(selectors.length - 1) val toSeqRes = call(resToMatch, selectors.last, Nil, resultTp, superType = NoType, needResolve = true) val toSeqResTp = resultTp.memberInfo(selectors.last).finalResultType + elemTp = unapplySeqTypeElemTp(toSeqResTp) + // elemTp must conform to the signature in sequence match + assert(elemTp.exists, "Product sequence match fails on " + pat + " since last element type of product is " + toSeqResTp) evalSeqPatterns(toSeqRes, toSeqResTp, elemTp, seqPats) end if + // TODO: refactor the code of product sequence match, avoid passing NoType to parameter elemTp in evalSeqPatterns else // distribute unapply to patterns @@ -1809,48 +1974,17 @@ class Objects(using Context @constructorOnly): throw new Exception("unexpected type: " + tp + ", Trace:\n" + Trace.show) } - /** Widen the escaped value (a method argument or rhs of an assignment) - * - * The default widening is 1 for most values, 2 for function values. - * User-specified widening annotations are repected. - */ - def widenEscapedValue(value: Value, annotatedTree: Tree): Contextual[Value] = - def parseAnnotation: Option[Int] = - annotatedTree.tpe.getAnnotation(defn.InitWidenAnnot).flatMap: annot => - annot.argument(0).get match - case arg @ Literal(c: Constants.Constant) => - val height = c.intValue - if height < 0 then - report.warning("The argument should be positive", arg) - None - else - Some(height) - case arg => - report.warning("The argument should be a constant integer value", arg) - None - end parseAnnotation - - parseAnnotation match - case Some(i) => - value.widen(i) - - case None => - if value.isInstanceOf[Fun] - then value.widen(2) - else value.widen(1) - /** Evaluate arguments of methods and constructors */ def evalArgs(args: List[Arg], thisV: ThisValue, klass: ClassSymbol): Contextual[List[ArgInfo]] = val argInfos = new mutable.ArrayBuffer[ArgInfo] args.foreach { arg => val res = if arg.isByName then - Fun(arg.tree, thisV, klass, summon[Env.Data]) + Fun(arg.tree, thisV, klass, summon[Scope]) else eval(arg.tree, thisV, klass) - val widened = widenEscapedValue(res, arg.tree) - argInfos += ArgInfo(widened, trace.add(arg.tree), arg.tree) + argInfos += ArgInfo(res, trace.add(arg.tree), arg.tree) } argInfos.toList @@ -1862,16 +1996,14 @@ class Objects(using Context @constructorOnly): */ def init(tpl: Template, thisV: Ref, klass: ClassSymbol): Contextual[Ref] = log("init " + klass.show, printer, (_: Value).show) { val paramsMap = tpl.constr.termParamss.flatten.map { vdef => - vdef.name -> Env.valValue(vdef.symbol) + vdef.name -> thisV.valValue(vdef.symbol) }.toMap // init param fields klass.paramGetters.foreach { acc => val value = paramsMap(acc.name.toTermName) if acc.is(Flags.Mutable) then - val addr = Heap.fieldVarAddr(summon[Regions.Data], acc, State.currentObject) - thisV.initVar(acc, addr) - Heap.writeJoin(addr, value) + thisV.initVar(acc, value) else thisV.initVal(acc, value) printer.println(acc.show + " initialized with " + value) @@ -1884,7 +2016,17 @@ class Objects(using Context @constructorOnly): val cls = tref.classSymbol.asClass // update outer for super class val res = outerValue(tref, thisV, klass) - thisV.initOuter(cls, res) + res match { + case ref: Ref => thisV.initOuter(cls, ref) + case vs: ValueSet if vs.isRefSet => + thisV.initOuter(cls, vs) + case _: Package => + thisV.initOuter(cls, Bottom) + case _ => + val error = "[Internal error] Invalid outer value, cls = " + cls + ", value = " + res + Trace.show + report.warning(error, Trace.position) + return + } // follow constructor if cls.hasSource then @@ -1895,7 +2037,7 @@ class Objects(using Context @constructorOnly): } // parents - def initParent(parent: Tree, tasks: Tasks) = + def initParent(parent: Tree, tasks: Tasks) = // TODO: store the parent objects and resolve `p.this` for parent classes `p` parent match case tree @ Block(stats, NewExpr(tref, New(tpt), ctor, argss)) => // can happen evalExprs(stats, thisV, klass) @@ -1964,9 +2106,7 @@ class Objects(using Context @constructorOnly): val sym = vdef.symbol 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) - Heap.writeJoin(addr, res) + thisV.initVar(sym, res) else thisV.initVal(sym, res) @@ -1979,44 +2119,36 @@ class Objects(using Context @constructorOnly): thisV } - - /** Resolve C.this that appear in `klass` + /** Resolve C.this that appear in `D.this` * * @param target The class symbol for `C` for which `C.this` is to be resolved. - * @param thisV The value for `D.this` where `D` is represented by the parameter `klass`. - * @param klass The enclosing class where the type `C.this` is located. + * @param thisV The value for `D.this`. + * @param klass The enclosing class `D` where `C.this` appears * @param elideObjectAccess Whether object access should be omitted. * * Object access elision happens when the object access is used as a prefix * in `new o.C` and `C` does not need an outer. */ - def resolveThis(target: ClassSymbol, thisV: Value, klass: ClassSymbol, elideObjectAccess: Boolean = false): Contextual[Value] = log("resolveThis target = " + target.show + ", this = " + thisV.show, printer, (_: Value).show) { - if target == klass then - thisV - else if target.is(Flags.Package) then - Package(target) // TODO: What is the semantics for package.this? + def resolveThis(target: ClassSymbol, thisV: ThisValue, klass: ClassSymbol, elideObjectAccess: Boolean = false): Contextual[ThisValue] = log("resolveThis target = " + target.show + ", this = " + thisV.show + ", klass = " + klass.show, printer, (_: Value).show) { + if target.is(Flags.Package) then + val error = "[Internal error] target cannot be packages, target = " + target + Trace.show + report.warning(error, Trace.position) + Bottom else if target.isStaticObject then val res = ObjectRef(target.moduleClass.asClass) if elideObjectAccess then res else accessObject(target) + else if target == klass then + thisV else - thisV match - case Bottom => Bottom - case UnknownValue => UnknownValue - case Top => Top - case ref: Ref => - val outerCls = klass.owner.lexicallyEnclosingClass.asClass - if !ref.hasOuter(klass) then - val error = "[Internal error] outer not yet initialized, target = " + target + ", klass = " + klass + Trace.show - report.warning(error, Trace.position) - Bottom - else - resolveThis(target, ref.outerValue(klass), outerCls) - case ValueSet(values) => - values.map(ref => resolveThis(target, ref, klass)).join - case _: Fun | _ : OfArray | _: Package | SafeValue(_) => - report.warning("[Internal error] unexpected thisV = " + thisV + ", target = " + target.show + ", klass = " + klass.show + Trace.show, Trace.position) - Bottom + // `target` must enclose `klass` + assert(klass.enclosingClassNamed(target.name) != NoSymbol, target.show + " does not enclose " + klass.show) + val outerThis = thisV match { + case ref: Ref => ref.outerValue(klass) + case refSet: RefSet => refSet.joinOuters(klass) + } + val outerCls = klass.owner.enclosingClass.asClass + resolveThis(target, outerThis.asInstanceOf[ThisValue], outerCls, elideObjectAccess) } /** Compute the outer value that corresponds to `tref.prefix` @@ -2025,14 +2157,19 @@ class Objects(using Context @constructorOnly): * @param thisV The value for `C.this` where `C` is represented by the parameter `klass`. * @param klass The enclosing class where the type `tref` is located. */ - def outerValue(tref: TypeRef, thisV: ThisValue, klass: ClassSymbol): Contextual[Value] = + + def outerValue(tref: TypeRef, thisV: ThisValue, klass: ClassSymbol): Contextual[Value] = log("Evaluating outer value of = " + tref.show + ", this = " + thisV.show, printer, (_: Value).show) { val cls = tref.classSymbol.asClass if tref.prefix == NoPrefix then val enclosing = cls.owner.lexicallyEnclosingClass.asClass - resolveThis(enclosing, thisV, klass, elideObjectAccess = cls.isStatic) + if enclosing.is(Flags.Package) then // `cls` is top-level class + Bottom + else // `cls` is local class + resolveThis(enclosing, thisV, klass, elideObjectAccess = cls.isStatic) else if cls.isAllOf(Flags.JavaInterface) then Bottom else evalType(tref.prefix, thisV, klass, elideObjectAccess = cls.isStatic) + } def printTraceWhenMultiple(trace: Trace)(using Context): String = if trace.toVector.size > 1 then @@ -2040,25 +2177,25 @@ class Objects(using Context @constructorOnly): else "" val mutateErrorSet: mutable.Set[(ClassSymbol, ClassSymbol)] = mutable.Set.empty - def errorMutateOtherStaticObject(currentObj: ClassSymbol, addr: Heap.Addr)(using Trace, Context) = - val otherObj = addr.owner - val addr_trace = addr.getTrace + def errorMutateOtherStaticObject(currentObj: ClassSymbol, scope: Scope)(using Trace, Context) = + val otherObj = scope.owner + val scope_trace = scope.getTrace if mutateErrorSet.add((currentObj, otherObj)) then val msg = s"Mutating ${otherObj.show} during initialization of ${currentObj.show}.\n" + "Mutating other static objects during the initialization of one static object is forbidden. " + Trace.show + - printTraceWhenMultiple(addr_trace) + printTraceWhenMultiple(scope_trace) report.warning(msg, Trace.position) val readErrorSet: mutable.Set[(ClassSymbol, ClassSymbol)] = mutable.Set.empty - def errorReadOtherStaticObject(currentObj: ClassSymbol, addr: Heap.Addr)(using Trace, Context) = - val otherObj = addr.owner - val addr_trace = addr.getTrace + def errorReadOtherStaticObject(currentObj: ClassSymbol, scope: Scope)(using Trace, Context) = + val otherObj = scope.owner + val scope_trace = scope.getTrace if readErrorSet.add((currentObj, otherObj)) then val msg = "Reading mutable state of " + otherObj.show + " during initialization of " + currentObj.show + ".\n" + "Reading mutable state of other static objects is forbidden as it breaks initialization-time irrelevance. " + Trace.show + - printTraceWhenMultiple(addr_trace) + printTraceWhenMultiple(scope_trace) report.warning(msg, Trace.position) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Trace.scala b/compiler/src/dotty/tools/dotc/transform/init/Trace.scala index ffaccad963af..9a00589197d5 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Trace.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Trace.scala @@ -51,7 +51,7 @@ object Trace: val line = if pos.source.exists then val loc = "[ " + pos.source.file.name + ":" + (pos.line + 1) + " ]" - val code = SyntaxHighlighting.highlight(pos.lineContent.trim.nn) + val code = SyntaxHighlighting.highlight(pos.lineContent.trim) i"$code\t$loc" else tree match @@ -59,7 +59,7 @@ object Trace: // The definition can be huge, avoid printing the whole definition. defDef.symbol.showFullName case _ => - tree.show.split(System.lineSeparator(), 2).nn.head.nn + tree.show.split(System.lineSeparator(), 2).head val positionMarkerLine = if pos.exists && pos.source.exists then @@ -86,7 +86,7 @@ object Trace: */ private def positionMarker(pos: SourcePosition): String = val trimmed = pos.source.lineContent(pos.start).takeWhile(c => c.isWhitespace).length - val padding = pos.startColumnPadding.substring(trimmed).nn + val padding = pos.startColumnPadding.substring(trimmed) val carets = if (pos.startLine == pos.endLine) "^" * math.max(1, pos.endColumn - pos.startColumn) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Util.scala b/compiler/src/dotty/tools/dotc/transform/init/Util.scala index 3280c289f926..9a1c38bcac36 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Util.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Util.scala @@ -17,7 +17,7 @@ import Trace.* object Util: /** Exception used for errors encountered when reading TASTy. */ case class TastyTreeException(msg: String) extends RuntimeException(msg) - + /** Utility definition used for better error-reporting of argument errors */ case class TraceValue[T](value: T, trace: Trace) @@ -96,7 +96,7 @@ object Util: else sym.matchingMember(cls.appliedRef) extension (sym: Symbol) - def hasSource(using Context): Boolean = !sym.defTree.isEmpty + def hasSource(using Context): Boolean = !sym.is(Flags.JavaDefined) && !sym.defTree.isEmpty def isStaticObject(using Context) = sym.is(Flags.Module, butNot = Flags.Package) && sym.isStatic diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala b/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala index b0ce1d6614fd..83ec7fb8399e 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala @@ -109,14 +109,14 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List @tailrec def loop(remaining: List[String], n: Int): Unit = remaining match case part0 :: remaining => - def badPart(t: Throwable): String = "".tap(_ => report.partError(t.getMessage.nn, index = n, offset = 0)) + def badPart(t: Throwable): String = "".tap(_ => report.partError(t.getMessage, index = n, offset = 0)) val part = try StringContext.processEscapes(part0) catch badPart checkPart(part, n) loop(remaining, n + 1) case Nil => loop(parts, n = 0) - if reported then (Nil, Nil) + if reported then (Nil, Nil) // on error, Transform.checked will revert to unamended inputs else assert(argc == actuals.size, s"Expected ${argc} args but got ${actuals.size} for [${parts.mkString(", ")}]") (amended.toList, actuals.toList) @@ -228,8 +228,20 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List case _ => true def lintToString(arg: Type): Unit = - if ctx.settings.Whas.toStringInterpolated && kind == StringXn && !(arg.widen =:= defn.StringType) && !arg.isPrimitiveValueType - then warningAt(CC)("interpolation uses toString") + def checkIsStringify(tp: Type): Boolean = tp.widen match + case OrType(tp1, tp2) => + checkIsStringify(tp1) || checkIsStringify(tp2) + case tp => + !(tp =:= defn.StringType) + && { + tp =:= defn.UnitType + && { warningAt(CC)("interpolated Unit value"); true } + || + !tp.isPrimitiveValueType + && { warningAt(CC)("interpolation uses toString"); true } + } + if ctx.settings.Whas.toStringInterpolated && kind == StringXn then + checkIsStringify(arg): Unit // what arg type if any does the conversion accept def acceptableVariants: List[Type] = @@ -308,5 +320,4 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List .tap(_ => reported = true) def partWarning(message: String, index: Int, offset: Int, end: Int): Unit = r.warning(BadFormatInterpolation(message), partPosAt(index, offset, end)) - .tap(_ => reported = true) end TypedFormatChecker diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/FormatInterpolatorTransform.scala b/compiler/src/dotty/tools/dotc/transform/localopt/FormatInterpolatorTransform.scala deleted file mode 100644 index 79d94c26c692..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/FormatInterpolatorTransform.scala +++ /dev/null @@ -1,39 +0,0 @@ -package dotty.tools.dotc -package transform.localopt - -import dotty.tools.dotc.ast.tpd.* -import dotty.tools.dotc.core.Constants.Constant -import dotty.tools.dotc.core.Contexts.* - -object FormatInterpolatorTransform: - - /** For f"${arg}%xpart", check format conversions and return (format, args) - * suitable for String.format(format, args). - */ - def checked(fun: Tree, args0: Tree)(using Context): (Tree, Tree) = - val (partsExpr, parts) = fun match - case TypeApply(Select(Apply(_, (parts: SeqLiteral) :: Nil), _), _) => - (parts.elems, parts.elems.map { case Literal(Constant(s: String)) => s }) - case _ => - report.error("Expected statically known StringContext", fun.srcPos) - (Nil, Nil) - val (args, elemtpt) = args0 match - case seqlit: SeqLiteral => (seqlit.elems, seqlit.elemtpt) - case _ => - report.error("Expected statically known argument list", args0.srcPos) - (Nil, EmptyTree) - - def literally(s: String) = Literal(Constant(s)) - if parts.lengthIs != args.length + 1 then - val badParts = - if parts.isEmpty then "there are no parts" - else s"too ${if parts.lengthIs > args.length + 1 then "few" else "many"} arguments for interpolated string" - report.error(badParts, fun.srcPos) - (literally(""), args0) - else - val checker = TypedFormatChecker(partsExpr, parts, args) - val (format, formatArgs) = checker.checked - if format.isEmpty then (literally(parts.mkString), args0) - else (literally(format.mkString), SeqLiteral(formatArgs.toList, elemtpt)) - end checked -end FormatInterpolatorTransform diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala b/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala index 1afcfbac6206..db3a0c6c71f2 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala @@ -10,6 +10,8 @@ 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.printing.Formatting.* +import dotty.tools.dotc.reporting.BadFormatInterpolation import dotty.tools.dotc.transform.MegaPhase.MiniPhase import dotty.tools.dotc.typer.ConstFold @@ -22,8 +24,9 @@ import dotty.tools.dotc.typer.ConstFold */ class StringInterpolatorOpt extends MiniPhase: import tpd.* + import StringInterpolatorOpt.* - override def phaseName: String = StringInterpolatorOpt.name + override def phaseName: String = name override def description: String = StringInterpolatorOpt.description @@ -31,7 +34,7 @@ class StringInterpolatorOpt extends MiniPhase: tree match case tree: RefTree => val sym = tree.symbol - assert(!StringInterpolatorOpt.isCompilerIntrinsic(sym), + assert(!isCompilerIntrinsic(sym), i"$tree in ${ctx.owner.showLocated} should have been rewritten by phase $phaseName") case _ => @@ -105,13 +108,25 @@ class StringInterpolatorOpt extends MiniPhase: lintToString(elem) concat(elem) val str = stri.next() - if !str.const.stringValue.isEmpty then concat(str) + if !str.const.stringValue.isEmpty then + concat(str) result end mkConcat def lintToString(t: Tree): Unit = - val arg: Type = t.tpe - if ctx.settings.Whas.toStringInterpolated && !(arg.widen =:= defn.StringType) && !arg.isPrimitiveValueType - then report.warning("interpolation uses toString", t.srcPos) + def checkIsStringify(tp: Type): Boolean = tp.widen match + case OrType(tp1, tp2) => + checkIsStringify(tp1) || checkIsStringify(tp2) + case tp => + !(tp =:= defn.StringType) + && { + tp =:= defn.UnitType + && { report.warning(bfi"interpolated Unit value", t.srcPos); true } + || + !tp.isPrimitiveValueType + && { report.warning(bfi"interpolation uses toString", t.srcPos); true } + } + if ctx.settings.Whas.toStringInterpolated then + checkIsStringify(t.tpe): Unit val sym = tree.symbol // Test names first to avoid loading scala.StringContext if not used, and common names first val isInterpolatedMethod = @@ -122,10 +137,38 @@ class StringInterpolatorOpt extends MiniPhase: case _ => false // Perform format checking and normalization, then make it StringOps(fmt).format(args1) with tweaked args def transformF(fun: Tree, args: Tree): Tree = - val (fmt, args1) = FormatInterpolatorTransform.checked(fun, args) + // For f"${arg}%xpart", check format conversions and return (format, args) for String.format(format, args). + def checked(args0: Tree)(using Context): (Tree, Tree) = + val (partsExpr, parts) = fun match + case TypeApply(Select(Apply(_, (parts: SeqLiteral) :: Nil), _), _) => + (parts.elems, parts.elems.map { case Literal(Constant(s: String)) => s }) + case _ => + report.error("Expected statically known StringContext", fun.srcPos) + (Nil, Nil) + val (args, elemtpt) = args0 match + case seqlit: SeqLiteral => (seqlit.elems, seqlit.elemtpt) + case _ => + report.error("Expected statically known argument list", args0.srcPos) + (Nil, EmptyTree) + + def literally(s: String) = Literal(Constant(s)) + if parts.lengthIs != args.length + 1 then + val badParts = + if parts.isEmpty then "there are no parts" + else s"too ${if parts.lengthIs > args.length + 1 then "few" else "many"} arguments for interpolated string" + report.error(badParts, fun.srcPos) + (literally(""), args0) + else + val checker = TypedFormatChecker(partsExpr, parts, args) + val (format, formatArgs) = checker.checked + if format.isEmpty then (literally(parts.mkString), args0) // on error just use unchecked inputs + else (literally(format.mkString), SeqLiteral(formatArgs.toList, elemtpt)) + end checked + val (fmt, args1) = checked(args) resolveConstructor(defn.StringOps.typeRef, List(fmt)) .select(nme.format) .appliedTo(args1) + end transformF // Starting with Scala 2.13, s and raw are macros in the standard // library, so we need to expand them manually. // sc.s(args) --> standardInterpolator(processEscapes, args, sc.parts) @@ -174,3 +217,7 @@ object StringInterpolatorOpt: sym == defn.StringContext_s || sym == defn.StringContext_f || sym == defn.StringContext_raw + + extension (sc: StringContext) + def bfi(args: Shown*)(using Context): BadFormatInterpolation = + BadFormatInterpolation(i(sc)(args*)) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index ab5885e6278c..4379002d2210 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -171,6 +171,14 @@ object SpaceEngine { /** Is `a` a subspace of `b`? Equivalent to `simplify(simplify(a) - simplify(b)) == Empty`, but faster */ def computeIsSubspace(a: Space, b: Space)(using Context): Boolean = trace(i"isSubspace($a, $b)") { + /** Is decomposition allowed on the right-hand side of a pattern? */ + /** We only allow decomposition on the right-hand side of a pattern if the type is not a type parameter, a type parameter reference, or a deferred type reference */ + /** This is because decomposition on the right-hand side of a pattern can lead to false positive warnings */ + inline def rhsDecompositionAllowed(tp: Type): Boolean = tp.dealias match + case _: TypeParamRef => false + case tr: TypeRef if tr.symbol.is(TypeParam) || (tr.symbol.is(Deferred) && !tr.symbol.isClass) => false + case _ => true + val a2 = simplify(a) val b2 = simplify(b) if (a ne a2) || (b ne b2) then isSubspace(a2, b2) @@ -185,7 +193,7 @@ object SpaceEngine { case (a @ Typ(tp1, _), b @ Typ(tp2, _)) => isSubType(tp1, tp2) || canDecompose(a) && isSubspace(Or(decompose(a)), b) - || canDecompose(b) && isSubspace(a, Or(decompose(b))) + || (canDecompose(b) && rhsDecompositionAllowed(tp2)) && isSubspace(a, Or(decompose(b))) case (Prod(tp1, _, _), Typ(tp2, _)) => isSubType(tp1, tp2) case (a @ Typ(tp1, _), Prod(tp2, fun, ss)) => @@ -455,8 +463,8 @@ object SpaceEngine { val inArray = tycon.isRef(defn.ArrayClass) || tp.translucentSuperType.isRef(defn.ArrayClass) val args2 = if isTyped && !inArray then args.map(_ => WildcardType) - else args.map(arg => erase(arg, inArray = inArray, isValue = false)) - tp.derivedAppliedType(erase(tycon, inArray, isValue = false), args2) + else args.map(arg => erase(arg, inArray = inArray, isValue = false, isTyped = false)) + tp.derivedAppliedType(erase(tycon, inArray = inArray, isValue = false, isTyped = false), args2) case tp @ OrType(tp1, tp2) => OrType(erase(tp1, inArray, isValue, isTyped), erase(tp2, inArray, isValue, isTyped), tp.isSoft) @@ -570,7 +578,10 @@ object SpaceEngine { // Case unapplySeq: // 1. return the type `List[T]` where `T` is the element type of the unapplySeq return type `Seq[T]` - val resTp = wildApprox(ctx.typeAssigner.safeSubstMethodParams(mt, scrutineeTp :: Nil).finalResultType) + var resTp0 = mt.resultType + if mt.isResultDependent then + resTp0 = ctx.typeAssigner.safeSubstParam(resTp0, mt.paramRefs.head, scrutineeTp) + val resTp = wildApprox(resTp0.finalResultType) val sig = if (resTp.isRef(defn.BooleanClass)) @@ -661,49 +672,38 @@ object SpaceEngine { // we get // <== refineUsingParent(NatT, class Succ, []) = Succ[NatT] // <== isSub(Succ[NatT] <:< Succ[Succ[]]) = false - def getAppliedClass(tp: Type): (Type, List[Type]) = tp match - case tp @ AppliedType(_: HKTypeLambda, _) => (tp, Nil) - case tp @ AppliedType(tycon: TypeRef, _) if tycon.symbol.isClass => (tp, tp.args) + def getAppliedClass(tp: Type): Type = tp match + case tp @ AppliedType(_: HKTypeLambda, _) => tp + case tp @ AppliedType(tycon: TypeRef, _) if tycon.symbol.isClass => tp case tp @ AppliedType(tycon: TypeProxy, _) => getAppliedClass(tycon.superType.applyIfParameterized(tp.args)) - case tp => (tp, Nil) - val (tp, typeArgs) = getAppliedClass(tpOriginal) - // This function is needed to get the arguments of the types that will be applied to the class. - // This is necessary because if the arguments of the types contain Nothing, - // then this can affect whether the class will be taken into account during the exhaustiveness check - def getTypeArgs(parent: Symbol, child: Symbol, typeArgs: List[Type]): List[Type] = - 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.argInfos.map(_.typeSymbol).zip(typeArgs)) - val substArgs = child.typeRef.typeParamSymbols.map(param => paramTypeMap.getOrElse(param, WildcardType)) - substArgs - else Nil - def getChildren(sym: Symbol, typeArgs: List[Type]): List[Symbol] = + case tp => tp + val tp = getAppliedClass(tpOriginal) + def getChildren(sym: Symbol): List[Symbol] = sym.children.flatMap { child => if child eq sym then List(sym) // i3145: sealed trait Baz, val x = new Baz {}, Baz.children returns Baz... else if tp.classSymbol == defn.TupleClass || tp.classSymbol == defn.NonEmptyTupleClass then List(child) // TupleN and TupleXXL classes are used for Tuple, but they aren't Tuple's children - else if (child.is(Private) || child.is(Sealed)) && child.isOneOf(AbstractOrTrait) then - getChildren(child, getTypeArgs(sym, child, typeArgs)) - else - val childSubstTypes = child.typeRef.applyIfParameterized(getTypeArgs(sym, child, typeArgs)) - // if a class contains a field of type Nothing, - // then it can be ignored in pattern matching, because it is impossible to obtain an instance of it - val existFieldWithBottomType = childSubstTypes.fields.exists(_.info.isBottomType) - if existFieldWithBottomType then Nil else List(child) + else if (child.is(Private) || child.is(Sealed)) && child.isOneOf(AbstractOrTrait) then getChildren(child) + else List(child) } - val children = trace(i"getChildren($tp)")(getChildren(tp.classSymbol, typeArgs)) + val children = trace(i"getChildren($tp)")(getChildren(tp.classSymbol)) val parts = children.map { sym => val sym1 = if (sym.is(ModuleClass)) sym.sourceModule else sym val refined = trace(i"refineUsingParent($tp, $sym1, $mixins)")(TypeOps.refineUsingParent(tp, sym1, mixins)) + def containsUninhabitedField(tp: Type): Boolean = + !tp.typeSymbol.is(ModuleClass) && tp.fields.exists { field => + !field.symbol.flags.is(Lazy) && field.info.dealias.isBottomType + } + def inhabited(tp: Type): Boolean = tp.dealias match case AndType(tp1, tp2) => !TypeComparer.provablyDisjoint(tp1, tp2) case OrType(tp1, tp2) => inhabited(tp1) || inhabited(tp2) case tp: RefinedType => inhabited(tp.parent) - case tp: TypeRef => inhabited(tp.prefix) - case _ => true + case tp: TypeRef => !containsUninhabitedField(tp) && inhabited(tp.prefix) + case tp: AppliedType => !containsUninhabitedField(tp) && inhabited(tp.tycon) + case _ => !containsUninhabitedField(tp) if inhabited(refined) then refined else NoType diff --git a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala index c7316482c193..13fcbe542448 100644 --- a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala +++ b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala @@ -651,7 +651,7 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP val dotIndex = pathName.indexOf('.') val globalRef = if (dotIndex < 0) pathName - else pathName.substring(0, dotIndex).nn + else pathName.substring(0, dotIndex) checkGlobalRefName(globalRef) } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 54f033fe6fd8..d8320ba1e389 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -23,7 +23,7 @@ import ProtoTypes.* import Inferencing.* import reporting.* import Nullables.*, NullOpsDecorator.* -import config.{Feature, SourceVersion} +import config.{Feature, MigrationVersion, SourceVersion} import collection.mutable import config.Printers.{overload, typr, unapp} @@ -34,9 +34,10 @@ import Constants.{Constant, IntTag} import Denotations.SingleDenotation import annotation.threadUnsafe +import scala.annotation.tailrec import scala.util.control.NonFatal +import dotty.tools.dotc.cc.isRetains import dotty.tools.dotc.inlines.Inlines -import scala.annotation.tailrec object Applications { import tpd.* @@ -130,22 +131,27 @@ object Applications { else productSelectorTypes(tp, NoSourcePosition) def productSelectorTypes(tp: Type, errorPos: SrcPos)(using Context): List[Type] = { - val sels = for (n <- Iterator.from(0)) yield extractorMemberType(tp, nme.selectorName(n), errorPos) - sels.takeWhile(_.exists).toList + if tp.isError then + Nil + else + val sels = for (n <- Iterator.from(0)) yield extractorMemberType(tp, nme.selectorName(n), errorPos) + sels.takeWhile(_.exists).toList } def tupleComponentTypes(tp: Type)(using Context): List[Type] = tp.widenExpr.dealias.normalized match - case tp: AppliedType => - if defn.isTupleClass(tp.tycon.typeSymbol) then - tp.args - else if tp.tycon.derivesFrom(defn.PairClass) then - val List(head, tail) = tp.args - head :: tupleComponentTypes(tail) - else + case defn.NamedTuple(_, vals) => + tupleComponentTypes(vals) + case tp: AppliedType => + if defn.isTupleClass(tp.tycon.typeSymbol) then + tp.args + else if tp.tycon.derivesFrom(defn.PairClass) then + val List(head, tail) = tp.args + head :: tupleComponentTypes(tail) + else + Nil + case _ => Nil - case _ => - Nil def productArity(tp: Type, errorPos: SrcPos = NoSourcePosition)(using Context): Int = if (defn.isProductSubType(tp)) productSelectorTypes(tp, errorPos).size else -1 @@ -206,10 +212,10 @@ object Applications { case List(defn.NamedTuple(_, _))=> // if the product types list is a singleton named tuple, autotupling might be applied, so don't fail eagerly tryEither[Option[List[untpd.Tree]]] - (Some(desugar.adaptPatternArgs(elems, pt))) + (Some(desugar.adaptPatternArgs(elems, pt, pos))) ((_, _) => None) case pts => - Some(desugar.adaptPatternArgs(elems, pt)) + Some(desugar.adaptPatternArgs(elems, pt, pos)) private def getUnapplySelectors(tp: Type)(using Context): List[Type] = // We treat patterns as product elements if @@ -603,7 +609,7 @@ trait Applications extends Compatibility { fail(TypeMismatch(methType.resultType, resultType, None)) // match all arguments with corresponding formal parameters - if success then matchArgs(orderedArgs, methType.paramInfos, 0) + if success then matchArgs(orderedArgs, methType.paramInfos, n = 0) case _ => if (methType.isError) ok = false else fail(em"$methString does not take parameters") @@ -725,11 +731,14 @@ trait Applications extends Compatibility { def addTyped(arg: Arg): List[Type] = if !formal.isRepeatedParam then checkNoVarArg(arg) addArg(typedArg(arg, formal), formal) - if methodType.isParamDependent && typeOfArg(arg).exists then - // `typeOfArg(arg)` could be missing because the evaluation of `arg` produced type errors - formals1.mapconserve(safeSubstParam(_, methodType.paramRefs(n), typeOfArg(arg))) - else - formals1 + if methodType.looksParamDependent + // need to handle also false dependencies since we generate TypeTrees from + // formal parameters in makeVarArg. These are not de-aliased, so they might contain + // stray parameter references. Test case is i23266.scala. + && typeOfArg(arg).exists + // `typeOfArg(arg)` could be missing because the evaluation of `arg` produced type errors + then formals1.mapconserve(safeSubstParam(_, methodType.paramRefs(n), typeOfArg(arg))) + else formals1 def missingArg(n: Int): Unit = fail(MissingArgument(methodType.paramNames(n), methString)) @@ -758,13 +767,24 @@ trait Applications extends Compatibility { } else defaultArgument(normalizedFun, n, testOnly) + // a bug allowed empty parens to expand to implicit args: fail empty args for rewrite on migration + def canSupplyImplicits = + inline def failEmptyArgs: false = + if Application.this.args.isEmpty then + fail(MissingImplicitParameterInEmptyArguments(methodType.paramNames(n), methString)) + false + methodType.isImplicitMethod && (applyKind == ApplyKind.Using || failEmptyArgs) + if !defaultArg.isEmpty then + if methodType.isImplicitMethod && ctx.mode.is(Mode.ImplicitsEnabled) + && !inferImplicitArg(formal, appPos.span).tpe.isError + then + report.warning(DefaultShadowsGiven(methodType.paramNames(n)), appPos) + defaultArg.tpe.widen match case _: MethodOrPoly if testOnly => matchArgs(args1, formals1, n + 1) case _ => matchArgs(args1, addTyped(treeToArg(defaultArg)), n + 1) - else if (methodType.isContextualMethod || applyKind == ApplyKind.Using && methodType.isImplicitMethod) - && ctx.mode.is(Mode.ImplicitsEnabled) - then + else if (methodType.isContextualMethod || canSupplyImplicits) && ctx.mode.is(Mode.ImplicitsEnabled) then val implicitArg = implicitArgTree(formal, appPos.span) matchArgs(args1, addTyped(treeToArg(implicitArg)), n + 1) else @@ -929,7 +949,7 @@ trait Applications extends Compatibility { def makeVarArg(n: Int, elemFormal: Type): Unit = { val args = typedArgBuf.takeRight(n).toList typedArgBuf.dropRightInPlace(n) - val elemtpt = TypeTree(elemFormal, inferred = true) + val elemtpt = TypeTree(elemFormal.normalizedTupleType, inferred = true) typedArgBuf += seqToRepeated(SeqLiteral(args, elemtpt)) } @@ -1097,6 +1117,21 @@ trait Applications extends Compatibility { then originalProto.tupledDual else originalProto + /* TODO (*) Get rid of this case. It is still syntax-based, therefore unreliable. + * It is necessary for things like `someDynamic[T](...)`, because in that case, + * somehow typedFunPart returns a tree that was typed as `TryDynamicCallType`, + * so clearly with the view that an apply insertion was necessary, but doesn't + * actually insert the apply! + * This is probably something wrong in apply insertion, but I (@sjrd) am out of + * my depth there. + * In the meantime, this makes tests pass. + */ + def isInsertedApply = fun1 match + case Select(_, nme.apply) => fun1.span.isSynthetic + case TypeApply(sel @ Select(_, nme.apply), _) => sel.span.isSynthetic + case TypeApply(fun, _) => !fun.isInstanceOf[Select] // (*) see explanatory comment + case _ => false + /** Type application where arguments come from prototype, and no implicits are inserted */ def simpleApply(fun1: Tree, proto: FunProto)(using Context): Tree = methPart(fun1).tpe match { @@ -1142,51 +1177,59 @@ trait Applications extends Compatibility { } } + def tryWithUsing(fun1: Tree, proto: FunProto)(using Context): Option[Tree] = + tryEither(Option(simpleApply(fun1, proto.withApplyKind(ApplyKind.Using)))): (_, _) => + None + /** If the applied function is an automatically inserted `apply` - * method and one of its arguments has a type mismatch , append - * a note to the error message that explains where the required - * type comes from. See #19680 and associated test case. + * method and one of its arguments has a type mismatch , append + * a note to the error message that explains where the required + * type comes from. See #19680 and associated test case. */ def maybeAddInsertedApplyNote(failedState: TyperState, fun1: Tree)(using Context): Unit = if fun1.symbol.name == nme.apply && fun1.span.isSynthetic then fun1 match - case Select(qualifier, _) => - def mapMessage(dia: Diagnostic): Diagnostic = - dia match - case dia: Diagnostic.Error => - dia.msg match - case msg: TypeMismatch => - msg.inTree match - case Some(arg) if tree.args.exists(_.span == arg.span) => - val noteText = - i"""The required type comes from a parameter of the automatically - |inserted `apply` method of `${qualifier.tpe}`.""".stripMargin - Diagnostic.Error(msg.appendExplanation("\n\n" + noteText), dia.pos) - case _ => dia - case msg => dia - case dia => dia - failedState.reporter.mapBufferedMessages(mapMessage) - case _ => () - else () + case Select(qualifier, _) => + failedState.reporter.mapBufferedMessages: + case dia: Diagnostic.Error => + dia.msg match + case msg: TypeMismatch => + msg.inTree match + case Some(arg) if tree.args.exists(_.span == arg.span) => + val noteText = + i"""The required type comes from a parameter of the automatically + |inserted `apply` method of `${qualifier.tpe}`.""".stripMargin + Diagnostic.Error(msg.appendExplanation("\n\n" + noteText), dia.pos) + case _ => dia + case msg => dia + case dia => dia + case _ => () + end if + + def maybePatchBadParensForImplicit(failedState: TyperState)(using Context): Boolean = + def rewrite(): Unit = + val replace = + if isInsertedApply then ".apply" // x() -> x.apply + else "" // f() -> f where fun1.span.end == tree.span.point + rewrites.Rewrites.patch(tree.span.withStart(fun1.span.end), replace) + var retry = false + failedState.reporter.mapBufferedMessages: + case err: Diagnostic.Error => + err.msg match + case msg: MissingImplicitParameterInEmptyArguments => + val mv = MigrationVersion.ImplicitParamsWithoutUsing + if mv.needsPatch then + retry = true + rewrite() + Diagnostic.Warning(err.msg, err.pos) + else err + case _ => err + case dia => dia + retry val result = fun1.tpe match { case err: ErrorType => cpy.Apply(tree)(fun1, proto.typedArgs()).withType(err) case TryDynamicCallType => - val isInsertedApply = fun1 match { - case Select(_, nme.apply) => fun1.span.isSynthetic - case TypeApply(sel @ Select(_, nme.apply), _) => sel.span.isSynthetic - /* TODO Get rid of this case. It is still syntax-based, therefore unreliable. - * It is necessary for things like `someDynamic[T](...)`, because in that case, - * somehow typedFunPart returns a tree that was typed as `TryDynamicCallType`, - * so clearly with the view that an apply insertion was necessary, but doesn't - * actually insert the apply! - * This is probably something wrong in apply insertion, but I (@sjrd) am out of - * my depth there. - * In the meantime, this makes tests pass. - */ - case TypeApply(fun, _) => !fun.isInstanceOf[Select] - case _ => false - } val tree1 = fun1 match case Select(_, nme.apply) => tree case _ => untpd.Apply(fun1, tree.args) @@ -1224,10 +1267,14 @@ trait Applications extends Compatibility { errorTree(tree, em"argument to summonFrom must be a pattern matching closure") } else - tryEither { - simpleApply(fun1, proto) - } { - (failedVal, failedState) => + tryEither(simpleApply(fun1, proto)): (failedVal, failedState) => + // a bug allowed empty parens to expand to implicit args, offer rewrite only on migration, + // then retry with using to emulate the bug since rewrites are ignored on error. + if proto.args.isEmpty && maybePatchBadParensForImplicit(failedState) then + tryWithUsing(fun1, proto).getOrElse: + failedState.commit() + failedVal + else def fail = maybeAddInsertedApplyNote(failedState, fun1) failedState.commit() @@ -1237,10 +1284,9 @@ trait Applications extends Compatibility { // The reason we need to try both is that the decision whether to use tupled // or not was already taken but might have to be revised when an implicit // is inserted on the qualifier. - tryWithImplicitOnQualifier(fun1, originalProto).getOrElse( + tryWithImplicitOnQualifier(fun1, originalProto).getOrElse: if (proto eq originalProto) fail - else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail)) - } + else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail) } if result.tpe.isNothingType then @@ -1282,6 +1328,10 @@ trait Applications extends Compatibility { } else { val app = tree.fun match + case _ if ctx.mode.is(Mode.Type) && Feature.enabled(Feature.modularity) && !ctx.isAfterTyper => + untpd.methPart(tree.fun) match + case Select(nw @ New(_), _) => typedAppliedConstructorType(nw, tree.args, tree) + case _ => realApply case untpd.TypeApply(_: untpd.SplicePattern, _) if Feature.quotedPatternsWithPolymorphicFunctionsEnabled => typedAppliedSpliceWithTypes(tree, pt) case _: untpd.SplicePattern => typedAppliedSplice(tree, pt) @@ -1379,14 +1429,11 @@ trait Applications extends Compatibility { def convertNewGenericArray(tree: Tree)(using Context): Tree = tree match { case Apply(TypeApply(tycon, targs@(targ :: Nil)), args) if tycon.symbol == defn.ArrayConstructor => fullyDefinedType(tree.tpe, "array", tree.srcPos) - - def newGenericArrayCall = + if TypeErasure.isGenericArrayArg(targ.tpe) then ref(defn.DottyArraysModule) - .select(defn.newGenericArrayMethod).withSpan(tree.span) - .appliedToTypeTrees(targs).appliedToTermArgs(args) - - if (TypeErasure.isGeneric(targ.tpe)) - newGenericArrayCall + .select(defn.newGenericArrayMethod).withSpan(tree.span) + .appliedToTypeTrees(targs) + .appliedToTermArgs(args) else tree case _ => tree @@ -1678,7 +1725,8 @@ trait Applications extends Compatibility { if selType <:< unapplyArgType then unapp.println(i"case 1 $unapplyArgType ${ctx.typerState.constraint}") fullyDefinedType(unapplyArgType, "pattern selector", tree.srcPos) - selType.dropAnnot(defn.UncheckedAnnot) // need to drop @unchecked. Just because the selector is @unchecked, the pattern isn't. + if selType.isBottomType then unapplyArgType + else selType.dropAnnot(defn.UncheckedAnnot) // need to drop @unchecked. Just because the selector is @unchecked, the pattern isn't. else if !ctx.mode.is(Mode.InTypeTest) then checkMatchable(selType, tree.srcPos, pattern = true) @@ -1700,7 +1748,7 @@ trait Applications extends Compatibility { val unapplyPatterns = UnapplyArgs(unapplyApp.tpe, unapplyFn, unadaptedArgs, tree.srcPos) .typedPatterns(qual, this) val result = assignType(cpy.UnApply(tree)(newUnapplyFn, unapplyImplicits(dummyArg, unapplyApp), unapplyPatterns), ownType) - if (ownType.stripped eq selType.stripped) || ownType.isError then result + if (ownType.stripped eq selType.stripped) || selType.isBottomType || ownType.isError then result else tryWithTypeTest(Typed(result, TypeTree(ownType)), selType) case tp => val unapplyErr = if (tp.isError) unapplyFn else notAnExtractor(unapplyFn) @@ -1715,6 +1763,28 @@ trait Applications extends Compatibility { def typedUnApply(tree: untpd.UnApply, selType: Type)(using Context): UnApply = throw new UnsupportedOperationException("cannot type check an UnApply node") + /** Typecheck an applied constructor type – An Apply node in Type mode. + * This expands to the type this term would have if it were typed as an expression. + * + * e.g. + * ```scala + * // class C(tracked val v: Any) + * val c: C(42) = ??? + * ``` + */ + def typedAppliedConstructorType(nw: untpd.New, args: List[untpd.Tree], tree: untpd.Apply)(using Context) = + val tree1 = typedExpr(tree) + val preciseTp = tree1.tpe.widenSkolems + val classTp = typedType(nw.tpt).tpe + def classSymbolHasOnlyTrackedParameters = + !classTp.classSymbol.primaryConstructor.paramSymss.nestedExists: param => + param.isTerm && !param.is(Tracked) + if !preciseTp.isError && !classSymbolHasOnlyTrackedParameters then + report.warning(OnlyFullyDependentAppliedConstructorType(), tree.srcPos) + if !preciseTp.isError && (preciseTp frozen_=:= classTp) then + report.warning(PointlessAppliedConstructorType(nw.tpt, args, classTp), tree.srcPos) + TypeTree(preciseTp) + /** Is given method reference applicable to argument trees `args`? * @param resultType The expected result type of the application */ @@ -2083,8 +2153,8 @@ trait Applications extends Compatibility { else 0 end compareWithTypes - if alt1.symbol.is(ConstructorProxy) && !alt2.symbol.is(ConstructorProxy) then -1 - else if alt2.symbol.is(ConstructorProxy) && !alt1.symbol.is(ConstructorProxy) then 1 + if alt1.symbol.is(PhantomSymbol) && !alt2.symbol.is(PhantomSymbol) then -1 + else if alt2.symbol.is(PhantomSymbol) && !alt1.symbol.is(PhantomSymbol) then 1 else val fullType1 = widenGiven(alt1.widen, alt1) val fullType2 = widenGiven(alt2.widen, alt2) @@ -2140,27 +2210,16 @@ trait Applications extends Compatibility { def resolveOverloaded(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = record("resolveOverloaded") - /** Is `alt` a method or polytype whose approximated result type after the first value parameter + /** Is `alt` a method or polytype whose result type after the first value parameter * section conforms to the expected type `resultType`? If `resultType` * is a `IgnoredProto`, pick the underlying type instead. - * - * Using an approximated result type is necessary to avoid false negatives - * due to incomplete type inference such as in tests/pos/i21410.scala and tests/pos/i21410b.scala. */ def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(using Context): Boolean = resultType.revealIgnored match { case resultType: ValueType => altType.widen match { case tp: PolyType => resultConforms(altSym, instantiateWithTypeVars(tp), resultType) - case tp: MethodType => - val wildRes = wildApprox(tp.resultType) - - class ResultApprox extends AvoidWildcardsMap: - // Avoid false negatives by approximating to a lower bound - variance = -1 - - val approx = ResultApprox()(wildRes) - constrainResult(altSym, approx, resultType) + case tp: MethodType => constrainResult(altSym, tp.resultType, resultType) case _ => true } case _ => true @@ -2501,7 +2560,7 @@ trait Applications extends Compatibility { /** Is `formal` a product type which is elementwise compatible with `params`? */ def ptIsCorrectProduct(formal: Type, params: List[untpd.ValDef])(using Context): Boolean = isFullyDefined(formal, ForceDegree.flipBottom) - && defn.isProductSubType(formal) + && (defn.isProductSubType(formal) || formal.isNamedTupleType) && tupleComponentTypes(formal).corresponds(params): (argType, param) => param.tpt.isEmpty || argType.widenExpr <:< typedAheadType(param.tpt).tpe @@ -2532,7 +2591,6 @@ trait Applications extends Compatibility { if t.exists && alt.symbol.exists then val (trimmed, skipped) = trimParamss(t.stripPoly, alt.symbol.rawParamss) val mappedSym = alt.symbol.asTerm.copy(info = t) - mappedSym.annotations = alt.symbol.annotations mappedSym.rawParamss = trimmed val (pre, totalSkipped) = mappedAltInfo(alt.symbol) match case Some((pre, prevSkipped)) => diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index cc43e950ec10..990eb01b999e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -144,25 +144,30 @@ object Checking { def checkAppliedTypesIn(tpt: TypeTree)(using Context): Unit = val checker = new TypeTraverser: def traverse(tp: Type) = - tp match - case AppliedType(tycon, argTypes) - if !(tycon.typeSymbol.is(JavaDefined) && ctx.compilationUnit.isJava) - // Don't check bounds in Java units that refer to Java type constructors. - // Scala is not obliged to do Java type checking and in fact i17763 goes wrong - // if we attempt to check bounds of F-bounded mutually recursive Java interfaces. - // Do check all bounds in Scala units and those bounds in Java units that - // occur in applications of Scala type constructors. - && !isCaptureChecking || tycon.typeSymbol.is(CaptureChecked) - // Don't check bounds when capture checking type constructors that were not - // themselves capture checked. Since the type constructor could not foresee - // possible capture sets, it's better to be lenient for backwards compatibility. - => - checkAppliedType( - untpd.AppliedTypeTree(TypeTree(tycon), argTypes.map(TypeTree(_))) - .withType(tp).withSpan(tpt.span.toSynthetic), - tpt) + tp.normalized match + case tp @ AppliedType(tycon, argTypes) => + if !(isCaptureChecking && defn.MatchCase.isInstance(tp)) then + // Don't check match type cases under cc. For soundness it's enough + // to check bounds in reduced match types. + // See tuple-ops.scala and tuple-ops-2.scala for examples that would fail otherwise. + if !(tycon.typeSymbol.is(JavaDefined) && ctx.compilationUnit.isJava) + // Don't check bounds in Java units that refer to Java type constructors. + // Scala is not obliged to do Java type checking and in fact i17763 goes wrong + // if we attempt to check bounds of F-bounded mutually recursive Java interfaces. + // Do check all bounds in Scala units and those bounds in Java units that + // occur in applications of Scala type constructors. + && (!isCaptureChecking || tycon.typeSymbol.is(CaptureChecked)) + // When capture checking, types that are not themselves capture-checked + // are exempted. Since the type constructor could not foresee possible + // capture sets, it's better to be lenient for backwards compatibility. + then + checkAppliedType( + untpd.AppliedTypeTree(TypeTree(tycon), argTypes.map(TypeTree(_))) + .withType(tp).withSpan(tpt.span.toSynthetic), + tpt) + traverseChildren(tp) case _ => - traverseChildren(tp) + traverseChildren(tp) checker.traverse(tpt.tpe) def checkNoWildcard(tree: Tree)(using Context): Tree = tree.tpe match { @@ -512,6 +517,86 @@ object Checking { } } + def checkScala2Implicit(sym: Symbol)(using Context): Unit = + def migration(msg: Message) = + report.errorOrMigrationWarning(msg, sym.srcPos, MigrationVersion.Scala2Implicits) + def info = sym match + case sym: ClassSymbol => sym.primaryConstructor.info + case _ => sym.info + def paramName = info.firstParamNames match + case pname :: _ => pname.show + case _ => "x" + def paramTypeStr = info.firstParamTypes match + case pinfo :: _ => pinfo.show + case _ => "T" + def toFunctionStr(info: Type): String = info match + case ExprType(resType) => + i"() => $resType" + case info: MethodType => + i"(${ctx.printer.paramsText(info).mkString()}) => ${toFunctionStr(info.resType)}" + case info: PolyType => + i"[${ctx.printer.paramsText(info).mkString()}] => ${toFunctionStr(info.resType)}" + case _ => + info.show + + if sym.isClass then + migration( + em"""`implicit` classes are no longer supported. They can usually be replaced + |by extension methods. Example: + | + | extension ($paramName: $paramTypeStr) + | // class methods go here, replace `this` by `$paramName` + | + |Alternatively, convert to a regular class and define + |a given `Conversion` instance into that class. Example: + | + | class ${sym.name} ... + | given Conversion[$paramTypeStr, ${sym.name}] = ${sym.name}($paramName) + | + |""") + else if sym.isOldStyleImplicitConversion(directOnly = true) then + migration( + em"""`implicit` conversion methods are no longer supported. They can usually be + |replaced by given instances of class `Conversion`. Example: + | + | given Conversion[$paramTypeStr, ${sym.info.finalResultType}] = $paramName => ... + | + |""") + else if sym.is(Method) then + if !sym.isOldStyleImplicitConversion(forImplicitClassOnly = true) then + migration( + em"""`implicit` defs are no longer supported, use a `given` clause instead. Example: + | + | given ${sym.name}: ${toFunctionStr(sym.info)} = ... + | + |""") + else if sym.isTerm && !sym.isOneOf(TermParamOrAccessor) then + def note = + if sym.is(Lazy) then "" + else + i""" + | + |Note: given clauses are evaluated lazily unless the right hand side is + |a simple reference. If eager evaluation of the value's right hand side + |is important, you can define a regular val and a given instance like this: + | + | val ${sym.name} = ... + | given ${sym.info} = ${sym.name}""" + + migration( + em"""`implicit` vals are no longer supported, use a `given` clause instead. Example: + | + | given ${sym.name}: ${sym.info} = ...$note + | + |""") + end checkScala2Implicit + + def checkErasedOK(sym: Symbol)(using Context): Unit = + if sym.is(Method, butNot = Macro) + || sym.isOneOf(Mutable | Lazy) + || sym.isType + then report.error(IllegalErasedDef(sym), sym.srcPos) + /** Check that symbol's definition is well-formed. */ def checkWellFormed(sym: Symbol)(using Context): Unit = { def fail(msg: Message) = report.error(msg, sym.srcPos) @@ -537,11 +622,11 @@ object Checking { fail(ParamsNoInline(sym.owner)) if sym.isInlineMethod && !sym.is(Deferred) && sym.allOverriddenSymbols.nonEmpty then checkInlineOverrideParameters(sym) - if (sym.is(Implicit)) { + if sym.is(Implicit) then assert(!sym.owner.is(Package), s"top-level implicit $sym should be wrapped by a package after typer") if sym.isType && (!sym.isClass || sym.is(Trait)) then fail(TypesAndTraitsCantBeImplicit()) - } + else checkScala2Implicit(sym) if sym.is(Transparent) then if sym.isType then if !sym.isExtensibleClass then fail(em"`transparent` can only be used for extensible classes and traits") @@ -607,10 +692,7 @@ object Checking { fail(ModifierNotAllowedForDefinition(Flags.Infix, s"A top-level ${sym.showKind} cannot be infix.")) if sym.isUpdateMethod && !sym.owner.derivesFrom(defn.Caps_Mutable) then fail(em"Update methods can only be used as members of classes extending the `Mutable` trait") - checkApplicable(Erased, - !sym.is(Lazy, butNot = Given) - && !sym.isMutableVarOrAccessor - && (!sym.isType || sym.isClass)) + if sym.is(Erased) then checkErasedOK(sym) checkCombination(Final, Open) checkCombination(Sealed, Open) checkCombination(Final, Sealed) @@ -631,10 +713,11 @@ object Checking { */ def checkWellFormedModule(mdef: untpd.ModuleDef)(using Context) = val mods = mdef.mods - def flagSourcePos(flag: FlagSet) = - mods.mods.find(_.flags == flag).getOrElse(mdef).srcPos + def flagSourcePos(flag: Flag) = untpd.flagSourcePos(mdef, flag) if mods.is(Open) then report.error(ModifierNotAllowedForDefinition(Open), flagSourcePos(Open)) + if mods.is(Into) then + report.error(ModifierNotAllowedForDefinition(Into), flagSourcePos(Open)) if mods.is(Abstract) then report.error(ModifierNotAllowedForDefinition(Abstract), flagSourcePos(Abstract)) if mods.is(Sealed) then @@ -848,6 +931,7 @@ object Checking { val name = Feature.experimental(sel.name) name == Feature.scala2macros || name == Feature.captureChecking + || name == Feature.separationChecking trees.filter { case Import(qual, selectors) => languageImport(qual) match @@ -1157,7 +1241,7 @@ trait Checking { if sym.name == nme.apply && sym.owner.derivesFrom(defn.ConversionClass) && !sym.info.isErroneous - && !expected.isInto + && !expected.isConversionTargetType then def conv = methPart(tree) match case Select(qual, _) => qual.symbol.orElse(sym.owner) diff --git a/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala b/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala index f0d4d617bb74..81a67d6a1df7 100644 --- a/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala @@ -78,7 +78,8 @@ class CrossVersionChecks extends MiniPhase: do val msg = annot.argumentConstantString(0).map(msg => s": $msg").getOrElse("") val since = annot.argumentConstantString(1).map(version => s" (since: $version)").getOrElse("") - report.deprecationWarning(em"inheritance from $psym is deprecated$since$msg", parent.srcPos, origin=psym.showFullName) + val composed = em"inheritance from $psym is deprecated$since$msg" + report.deprecationWarning(composed, parent.srcPos, origin = psym.showFullName) } private def unrollError(pos: SrcPos)(using Context): Unit = @@ -203,16 +204,19 @@ object CrossVersionChecks: * Also check for deprecation of the companion class for synthetic methods in the companion module. */ private[CrossVersionChecks] def checkDeprecatedRef(sym: Symbol, pos: SrcPos)(using Context): Unit = - def maybeWarn(annotee: Symbol, annot: Annotation) = if !skipWarning(sym) then + def warn(annotee: Symbol, annot: Annotation) = val message = annot.argumentConstantString(0).filter(!_.isEmpty).map(": " + _).getOrElse("") val since = annot.argumentConstantString(1).filter(!_.isEmpty).map(" since " + _).getOrElse("") - report.deprecationWarning(em"${annotee.showLocated} is deprecated${since}${message}", pos, origin=annotee.showFullName) + val composed = em"${annotee.showLocated} is deprecated${since}${message}" + report.deprecationWarning(composed, pos, origin = annotee.showFullName) sym.getAnnotation(defn.DeprecatedAnnot) match - case Some(annot) => maybeWarn(sym, annot) + case Some(annot) => if !skipWarning(sym) then warn(sym, annot) case _ => if sym.isAllOf(SyntheticMethod) then val companion = sym.owner.companionClass - if companion.is(CaseClass) then companion.getAnnotation(defn.DeprecatedAnnot).foreach(maybeWarn(companion, _)) + if companion.is(CaseClass) then + for annot <- companion.getAnnotation(defn.DeprecatedAnnot) if !skipWarning(sym) do + warn(companion, annot) /** Decide whether the deprecation of `sym` should be ignored in this context. * diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 58119981dfc4..a6254c0d5c00 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -294,7 +294,7 @@ object ErrorReporting { def dependentMsg = """Term-dependent types are experimental, - |they must be enabled with a `experimental.dependent` language import or setting""".stripMargin.toMessage + |they must be enabled with a `experimental.modularity` language import or setting""".stripMargin.toMessage def err(using Context): Errors = new Errors } diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index 26d03db4b7dc..55778017b76f 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -300,9 +300,9 @@ object EtaExpansion extends LiftImpure { val body = Apply(lifted, ids) if (mt.isContextualMethod) body.setApplyKind(ApplyKind.Using) val fn = - if (mt.isContextualMethod) new untpd.FunctionWithMods(params, body, Modifiers(Given), mt.erasedParams) - else if (mt.isImplicitMethod) new untpd.FunctionWithMods(params, body, Modifiers(Implicit), mt.erasedParams) - else if (mt.hasErasedParams) new untpd.FunctionWithMods(params, body, Modifiers(), mt.erasedParams) + if (mt.isContextualMethod) new untpd.FunctionWithMods(params, body, Modifiers(Given), mt.paramErasureStatuses) + else if (mt.isImplicitMethod) new untpd.FunctionWithMods(params, body, Modifiers(Implicit), mt.paramErasureStatuses) + else if (mt.hasErasedParams) new untpd.FunctionWithMods(params, body, Modifiers(), mt.paramErasureStatuses) else untpd.Function(params, body) if (defs.nonEmpty) untpd.Block(defs.toList map (untpd.TypedSplice(_)), fn) else fn } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 25fed4e62de9..e1bd045ff3a5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -558,8 +558,8 @@ object Implicits: var str1 = err.refStr(alt1.ref) var str2 = err.refStr(alt2.ref) if str1 == str2 then - str1 = ctx.printer.toTextRef(alt1.ref).show - str2 = ctx.printer.toTextRef(alt2.ref).show + str1 = alt1.ref.showRef + str2 = alt2.ref.showRef em"both $str1 and $str2 $qualify".withoutDisambiguation() override def toAdd(using Context) = @@ -832,7 +832,7 @@ trait ImplicitRunInfo: WildcardType else seen += t - t.superType match + t.underlying match case TypeBounds(lo, hi) => if lo.isBottomTypeAfterErasure then apply(hi) else AndType.make(apply(lo), apply(hi)) @@ -844,6 +844,10 @@ trait ImplicitRunInfo: case t: TypeVar => apply(t.underlying) case t: ParamRef => applyToUnderlying(t) case t: ConstantType => apply(t.underlying) + case t @ AppliedType(tycon, args) if !tycon.typeSymbol.isClass => + // To prevent arguments to be reduced away when re-applying the tycon bounds, + // we collect all parts as elements of a tuple. See i21951.scala for a test case. + apply(defn.tupleType(tycon :: args)) case t => mapOver(t) end liftToAnchors val liftedTp = liftToAnchors(tp) @@ -1294,11 +1298,15 @@ trait Implicits: val history = ctx.searchHistory.nest(cand, pt) val typingCtx = searchContext().setNewTyperState().setFreshGADTBounds.setSearchHistory(history) + val alreadyStoppedInlining = ctx.base.stopInlining val result = typedImplicit(cand, pt, argument, span)(using typingCtx) result match case res: SearchSuccess => ctx.searchHistory.defineBynameImplicit(wideProto, res) case _ => + if !alreadyStoppedInlining && ctx.base.stopInlining then + // a call overflowed as part of the expansion when typing the implicit + ctx.base.stopInlining = false // Since the search failed, the local typerstate will be discarded // without being committed, but type variables local to that state // might still appear in an error message, so we run `gc()` here to @@ -1724,7 +1732,7 @@ trait Implicits: "argument" def showResult(r: SearchResult) = r match - case r: SearchSuccess => ctx.printer.toTextRef(r.ref).show + case r: SearchSuccess => r.ref.showRef case r => r.show result match diff --git a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala index 98fbede5f5ba..f9027cf7a961 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -336,7 +336,7 @@ trait ImportSuggestions: if ref.symbol.is(ExtensionMethod) then s"${ctx.printer.toTextPrefixOf(ref).show}${ref.symbol.name}" else - ctx.printer.toTextRef(ref).show + ref.showRef s" import $imported" val suggestions = suggestedRefs .zip(suggestedRefs.map(importString)) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 520c8bf62ba4..c581dac5ec52 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -13,6 +13,7 @@ import Decorators._ import config.Printers.{gadts, typr} import annotation.tailrec import reporting.* +import TypeAssigner.SkolemizedArgs import collection.mutable import scala.annotation.internal.sharable @@ -444,7 +445,9 @@ object Inferencing { } /** The instantiation decision for given poly param computed from the constraint. */ - enum Decision { case Min; case Max; case ToMax; case Skip; case Fail } + enum Decision: + case Min, Max, ToMax, Skip, Fail + private def instDecision(tvar: TypeVar, v: Int, minimizeSelected: Boolean, ifBottom: IfBottom)(using Context): Decision = import Decision.* val direction = instDirection(tvar.origin) @@ -839,22 +842,33 @@ trait Inferencing { this: Typer => if tvar.origin.paramName.is(NameKinds.DepParamName) then representedParamRef(tvar.origin) match case ref: TermParamRef => - def findArg(tree: Tree)(using Context): Tree = tree match - case Apply(fn, args) => + def findArg(tree: Tree)(using Context): Option[(Tree, Apply)] = tree match + case app @ Apply(fn, args) => if fn.tpe.widen eq ref.binder then - if ref.paramNum < args.length then args(ref.paramNum) - else EmptyTree + if ref.paramNum < args.length then Some((args(ref.paramNum), app)) + else None else findArg(fn) case TypeApply(fn, _) => findArg(fn) case Block(_, expr) => findArg(expr) case Inlined(_, _, expr) => findArg(expr) - case _ => EmptyTree - - val arg = findArg(call) - if !arg.isEmpty then - var argType = arg.tpe.widenIfUnstable - if !argType.isSingleton then argType = SkolemType(argType) - argType <:< tvar + case _ => None + + findArg(call) match + case Some((arg, app)) => + var argType = arg.tpe.widenIfUnstable + if !argType.isSingleton then + argType = app.getAttachment(SkolemizedArgs) match + case Some(mapping) => + mapping.get(arg) match + case Some(sk @ SkolemType(at)) => + assert(argType frozen_=:= at) + sk + case _ => + SkolemType(argType) + case _ => + SkolemType(argType) + argType <:< tvar + case _ => case _ => end constrainIfDependentParamRef } diff --git a/compiler/src/dotty/tools/dotc/typer/Migrations.scala b/compiler/src/dotty/tools/dotc/typer/Migrations.scala index acf9e7668917..31bb29e0e6c5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Migrations.scala +++ b/compiler/src/dotty/tools/dotc/typer/Migrations.scala @@ -21,6 +21,8 @@ import NameKinds.ContextBoundParamName import rewrites.Rewrites.patch import util.Spans.Span import rewrites.Rewrites +import dotty.tools.dotc.rewrites.Rewrites.ActionPatch +import dotty.tools.dotc.util.SourcePosition /** A utility trait containing source-dependent deprecation messages * and migrations. @@ -129,26 +131,100 @@ trait Migrations: /** 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 + if tp.companion == ImplicitMethodType && pt.applyKind != ApplyKind.Using && pt.args.nonEmpty && pt.args.head.span.exists then // The application can only be rewritten if it uses parentheses syntax. // See issue #22927 and related tests. - val hasParentheses = - ctx.source.content - .slice(tree.span.end, pt.args.head.span.start) - .exists(_ == '(') + val hasParentheses = checkParentheses(tree, pt) val rewriteMsg = if hasParentheses then Message.rewriteNotice("This code", mversion.patchFrom) - else - "" - report.errorOrMigrationWarning( + else "" + val message = em"""Implicit parameters should be provided with a `using` clause.$rewriteMsg - |To disable the warning, please use the following option: + |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) + |""" + val codeAction = CodeAction( + title = "Add `using` clause", + description = None, + patches = List(ActionPatch(pt.args.head.startPos.sourcePos, "using ")) + ) + val withActions = message.withActions(codeAction) + report.errorOrMigrationWarning( + withActions, + pt.args.head.srcPos, + mversion + ) if hasParentheses && mversion.needsPatch then - patch(Span(pt.args.head.span.start), "using ") + patchImplicitParams(tree, pt) end implicitParams + private def checkParentheses(tree: Tree, pt: FunProto)(using Context): Boolean = + val ptSpan = pt.args.head.span + ptSpan.exists + && tree.span.exists + && ctx.source.content + .slice(tree.span.end, ptSpan.start) + .exists(_ == '(') + + private def patchImplicitParams(tree: Tree, pt: FunProto)(using Context): Unit = + patch(Span(pt.args.head.span.start), "using ") + + object ImplicitToGiven: + def valDef(vdef: ValDef)(using Context): Unit = + if ctx.settings.YimplicitToGiven.value + && vdef.symbol.is(Implicit) + && !vdef.symbol.isParamOrAccessor + then + val implicitSpan = + vdef.mods.mods.collectFirst { + case mod: untpd.Mod.Implicit => mod.span + }.get + patch( + Span(implicitSpan.start, implicitSpan.end + 1), + "" + ) + patch( + Span(vdef.mods.mods.last.span.end + 1, vdef.namePos.span.start), "given " + ) + + def defDef(ddef: DefDef)(using Context): Unit = + if + ctx.settings.YimplicitToGiven.value + && ddef.symbol.is(Implicit) + && !ddef.symbol.isParamOrAccessor + && !ddef.symbol.isOldStyleImplicitConversion() + then + val implicitSpan = + ddef.mods.mods.collectFirst { + case mod: untpd.Mod.Implicit => mod.span + }.get + patch( + Span(implicitSpan.start, implicitSpan.end + 1), "" + ) + patch( + Span(ddef.mods.mods.last.span.end + 1, ddef.namePos.span.start), "given " + ) + // remove empty parentheses + patch( + Span(ddef.namePos.span.end, ddef.tpt.span.start), ": " + ) + ddef.tpt match + case refinedType: untpd.RefinedTypeTree => + patch(refinedType.span.startPos, "(") + patch(refinedType.span.endPos, ")") + case _ => + + def implicitParams(tree: Tree, tp: MethodOrPoly, pt: FunProto)(using Context): Unit = + if + ctx.settings.YimplicitToGiven.value + && !mv.ExplicitContextBoundArgument.needsPatch // let's not needlessly repeat the patch + && tp.companion == ImplicitMethodType + && pt.applyKind != ApplyKind.Using + && pt.args.nonEmpty + && checkParentheses(tree, pt) + then + patchImplicitParams(tree, pt) + + end Migrations diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 5d6be936f11b..e86414cc2183 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -140,7 +140,7 @@ class Namer { typer: Typer => def conflict(conflicting: Symbol) = val other = - if conflicting.is(ConstructorProxy) then conflicting.companionClass + if conflicting.is(PhantomSymbol) then conflicting.companionClass else conflicting report.error(AlreadyDefined(name, owner, other), ctx.source.atSpan(span)) conflictsDetected = true @@ -247,7 +247,7 @@ class Namer { typer: Typer => tree match { case tree: TypeDef if tree.isClassDef => var flags = checkFlags(tree.mods.flags) - if ctx.settings.YcompileScala2Library.value then + if Feature.shouldBehaveAsScala2 then flags |= Scala2x val name = checkNoConflict(tree.name, tree.span).asTypeName val cls = @@ -833,7 +833,9 @@ class Namer { typer: Typer => protected def typeSig(sym: Symbol): Type = original match case original: ValDef => if (sym.is(Module)) moduleValSig(sym) - else valOrDefDefSig(original, sym, Nil, identity)(using localContext(sym).setNewScope) + else + valOrDefDefSig(original, sym, Nil, identity)(using localContext(sym).setNewScope) + .suppressIntoIfParam(sym) case original: DefDef => // For the primary constructor DefDef, it is: // * indexed as a part of completing the class, with indexConstructor; and @@ -945,7 +947,7 @@ class Namer { typer: Typer => !sd.symbol.is(Deferred) && sd.matches(denot))) val isClashingSynthetic = - denot.is(Synthetic, butNot = ConstructorProxy) && + denot.is(Synthetic, butNot = PhantomSymbol) && ( (desugar.isRetractableCaseClassMethodName(denot.name) && isCaseClassOrCompanion(denot.owner) @@ -1132,11 +1134,15 @@ class Namer { typer: Typer => ensureUpToDate(sym.typeRef, dummyInfo1) if (dummyInfo2 `ne` dummyInfo1) ensureUpToDate(sym.typeRef, dummyInfo2) + if original.hasAttachment(Trees.CaptureVar) then + addDummyTermCaptureParam(sym)(using ictx) + sym.info end typeSig } - class ClassCompleter(cls: ClassSymbol, original: TypeDef)(ictx: Context) extends Completer(original)(ictx) { + class ClassCompleter(cls: ClassSymbol, original: TypeDef)(ictx: Context) + extends Completer(original)(ictx), CompleterWithCleanup { withDecls(newScope(using ictx)) protected given completerCtx: Context = localContext(cls) @@ -1195,7 +1201,7 @@ class Namer { typer: Typer => 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 + else if sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(PhantomSymbol) || sym.isAllOf(JavaModule) then Skip // if the cls is a subclass or mixes in the owner of the symbol // and either @@ -1320,6 +1326,7 @@ class Namer { typer: Typer => else mbr.info.ensureMethodic (EmptyFlags, mbrInfo) var mbrFlags = MandatoryExportTermFlags | maybeStable | (sym.flags & RetainedExportTermFlags) + if sym.is(Erased) then mbrFlags |= Inline if pathMethod.exists then mbrFlags |= ExtensionMethod val forwarderName = checkNoConflict(alias, span) newSymbol(cls, forwarderName, mbrFlags, mbrInfo, coord = span) @@ -1344,7 +1351,7 @@ class Namer { typer: Typer => val ddef = tpd.DefDef(forwarder.asTerm, prefss => { val forwarderCtx = ctx.withOwner(forwarder) val (pathRefss, methRefss) = prefss.splitAt(extensionParamsCount(path.tpe.widen)) - val ref = path.appliedToArgss(pathRefss).select(sym.asTerm) + val ref = path.appliedToArgss(pathRefss).select(sym.asTerm).withSpan(span.focus) val rhs = ref.appliedToArgss(adaptForwarderParams(Nil, sym.info, methRefss)) .etaExpandCFT(using forwarderCtx) if forwarder.isInlineMethod then @@ -1388,7 +1395,7 @@ class Namer { typer: Typer => def addWildcardForwardersNamed(name: TermName, span: Span): Unit = List(name, name.toTypeName) - .flatMap(pathType.memberBasedOnFlags(_, excluded = Private|Given|ConstructorProxy).alternatives) + .flatMap(pathType.memberBasedOnFlags(_, excluded = Private|Given|PhantomSymbol).alternatives) .foreach(addForwarder(name, _, span)) // ignore if any are not added def addWildcardForwarders(seen: List[TermName], span: Span): Unit = @@ -1759,6 +1766,7 @@ class Namer { typer: Typer => processExports(using localCtx) defn.patchStdLibClass(cls) addConstructorProxies(cls) + cleanup() } } @@ -1901,6 +1909,12 @@ class Namer { typer: Typer => case _ => val mbrTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe) + // Add an erased to the using clause generated from a `: Singleton` context bound + mdef.tpt match + case tpt: untpd.ContextBoundTypeTree if mbrTpe.typeSymbol == defn.SingletonClass => + sym.setFlag(Erased) + sym.resetFlag(Lazy) + case _ => if (ctx.explicitNulls && mdef.mods.is(JavaDefined)) JavaNullInterop.nullifyMember(sym, mbrTpe, mdef.mods.isAllOf(JavaEnumValue)) else mbrTpe @@ -1941,7 +1955,7 @@ class Namer { typer: Typer => if completedTypeParams.forall(_.isType) then completer.setCompletedTypeParams(completedTypeParams.asInstanceOf[List[TypeSymbol]]) completeTrailingParamss(ddef, sym, indexingCtor = false) - val paramSymss = normalizeIfConstructor(ddef.paramss.nestedMap(symbolOfTree), isConstructor) + val paramSymss = normalizeIfConstructor(ddef.paramss.nestedMap(symbolOfTree), isConstructor, Some(ddef.nameSpan.startPos)) sym.setParamss(paramSymss) def wrapMethType(restpe: Type): Type = @@ -2240,8 +2254,9 @@ class Namer { typer: Typer => // it would be erased to BoxedUnit. def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp - def cookedRhsType = dealiasIfUnit(rhsType).deskolemized + def cookedRhsType = dealiasIfUnit(rhsType) def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.srcPos) + .deskolemized //if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType") if (inherited.exists) if sym.isInlineVal || isTracked then lhsType else inherited diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 86b9a337e69a..609dad894b6c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -186,15 +186,21 @@ object Nullables: * Check `usedOutOfOrder` to see the explaination and example of "out of order". * See more examples in `tests/explicit-nulls/neg/var-ref-in-closure.scala`. */ - def isTracked(ref: TermRef)(using Context) = + def isTracked(ref: TermRef)(using Context) = // true + val sym = ref.symbol + + def isNullStableField: Boolean = + ref.prefix.isStable + && sym.isField + && sym.hasAnnotation(defn.StableNullAnnot) + ref.isStable - || { val sym = ref.symbol - val unit = ctx.compilationUnit + || isNullStableField + || { val unit = ctx.compilationUnit !ref.usedOutOfOrder && sym.span.exists && (unit ne NoCompilationUnit) // could be null under -Ytest-pickler - && unit.assignmentSpans.contains(sym.span.start) - } + && unit.assignmentSpans.contains(sym.span.start) } /** The nullability context to be used after a case that matches pattern `pat`. * If `pat` is `null`, this will assert that the selector `sel` is not null afterwards. diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 379bdbc8d6a0..0b6688c6f5fe 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -427,21 +427,28 @@ object ProtoTypes { * - t2 is a ascription (t22: T) and t1 is at the outside of t22 * - t2 is a closure (...) => t22 and t1 is at the outside of t22 */ - def hasInnerErrors(t: Tree)(using Context): Boolean = t match - case Typed(expr, tpe) => hasInnerErrors(expr) - case closureDef(mdef) => hasInnerErrors(mdef.rhs) + def hasInnerErrors(t: Tree, argType: Type)(using Context): Boolean = t match + case Typed(expr, tpe) => hasInnerErrors(expr, argType) + case closureDef(mdef) => hasInnerErrors(mdef.rhs, argType) case _ => t.existsSubTree { t1 => if t1.typeOpt.isError && t.span.toSynthetic != t1.span.toSynthetic && t.typeOpt != t1.typeOpt then typr.println(i"error subtree $t1 of $t with ${t1.typeOpt}, spans = ${t1.span}, ${t.span}") - true + t1.typeOpt match + case errorType: ErrorType if errorType.msg.isInstanceOf[TypeMismatchMsg] => + // if error is caused by an argument type mismatch, + // then return false to try to find an extension. + // see i20335.scala for test case. + val typeMismtachMsg = errorType.msg.asInstanceOf[TypeMismatchMsg] + argType != typeMismtachMsg.expected + case _ => true else false } - private def cacheTypedArg(arg: untpd.Tree, typerFn: untpd.Tree => Tree, force: Boolean)(using Context): Tree = { + private def cacheTypedArg(arg: untpd.Tree, typerFn: untpd.Tree => Tree, force: Boolean, argType: Type)(using Context): Tree = { var targ = state.typedArg(arg) if (targ == null) untpd.functionWithUnknownParamType(arg) match { @@ -459,13 +466,13 @@ object ProtoTypes { targ = typerFn(arg) // TODO: investigate why flow typing is not working on `targ` if ctx.reporter.hasUnreportedErrors then - if hasInnerErrors(targ.nn) then + if hasInnerErrors(targ, argType) then state.errorArgs += arg else - state.typedArg = state.typedArg.updated(arg, targ.nn) + state.typedArg = state.typedArg.updated(arg, targ) state.errorArgs -= arg } - targ.nn + targ } /** The typed arguments. This takes any arguments already typed using @@ -487,7 +494,7 @@ object ProtoTypes { val protoTyperState = ctx.typerState val oldConstraint = protoTyperState.constraint val args1 = args.mapWithIndexConserve((arg, idx) => - cacheTypedArg(arg, arg => typer.typed(norm(arg, idx)), force = false)) + cacheTypedArg(arg, arg => typer.typed(norm(arg, idx)), force = false, NoType)) val newConstraint = protoTyperState.constraint if !args1.exists(arg => isUndefined(arg.tpe)) then state.typedArgs = args1 @@ -534,7 +541,8 @@ object ProtoTypes { val locked = ctx.typerState.ownedVars val targ = cacheTypedArg(arg, typer.typedUnadapted(_, wideFormal, locked)(using argCtx), - force = true) + force = true, + wideFormal) val targ1 = typer.adapt(targ, wideFormal, locked) if wideFormal eq formal then targ1 else checkNoWildcardCaptureForCBN(targ1) @@ -615,6 +623,10 @@ object ProtoTypes { override def withContext(newCtx: Context): ProtoType = if newCtx `eq` protoCtx then this else new FunProto(args, resType)(typer, applyKind, state)(using newCtx) + + def withApplyKind(applyKind: ApplyKind) = + if applyKind == this.applyKind then this + else new FunProto(args, resType)(typer, applyKind, state) } /** A prototype for expressions that appear in function position diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 4e7c4336b852..f979654e9811 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -222,7 +222,7 @@ trait QuotesAndSplices { if ctx.mode.is(Mode.InPatternAlternative) then report.error(IllegalVariableInPatternAlternative(tree.name), tree.srcPos) val typeSym = inContext(quotePatternOuterContext(ctx)) { - newSymbol(ctx.owner, tree.name.toTypeName, Case, typeSymInfo, NoSymbol, tree.span) + newSymbol(ctx.owner, tree.name.toTypeName, Synthetic | Case, typeSymInfo, NoSymbol, tree.span) } addQuotedPatternTypeVariable(typeSym) Bind(typeSym, untpd.Ident(nme.WILDCARD).withType(typeSymInfo)).withSpan(tree.span) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index f81c1bf19cb1..ba472512a953 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -329,6 +329,15 @@ object RefChecks { val mixinOverrideErrors = new mutable.ListBuffer[MixinOverrideError]() + /** Returns a `SourcePosition` containing the full span (with the correct + * end) of the class name. */ + def clazzNamePos = + if clazz.name == tpnme.ANON_CLASS then + clazz.srcPos + else + val clazzNameEnd = clazz.srcPos.span.start + clazz.name.stripModuleClassSuffix.lastPart.length + clazz.srcPos.sourcePos.copy(span = clazz.srcPos.span.withEnd(clazzNameEnd)) + def printMixinOverrideErrors(): Unit = mixinOverrideErrors.toList match { case Nil => @@ -374,52 +383,6 @@ object RefChecks { && atPhase(typerPhase): loop(member.info.paramInfoss, other.info.paramInfoss) - /** A map of all occurrences of `into` in a member type. - * Key: number of parameter carrying `into` annotation(s) - * Value: A list of all depths of into annotations, where each - * function arrow increases the depth. - * Example: - * def foo(x: into A, y: => [X] => into (x: X) => into B): C - * produces the map - * (0 -> List(0), 1 -> List(1, 2)) - */ - type IntoOccurrenceMap = immutable.Map[Int, List[Int]] - - def intoOccurrences(tp: Type): IntoOccurrenceMap = - - def traverseInfo(depth: Int, tp: Type): List[Int] = tp match - case AnnotatedType(tp, annot) if annot.symbol == defn.IntoParamAnnot => - depth :: traverseInfo(depth, tp) - case AppliedType(tycon, arg :: Nil) if tycon.typeSymbol == defn.RepeatedParamClass => - traverseInfo(depth, arg) - case defn.FunctionOf(_, resType, _) => - traverseInfo(depth + 1, resType) - case RefinedType(parent, rname, mt: MethodOrPoly) => - traverseInfo(depth, mt) - case tp: MethodOrPoly => - traverseInfo(depth + 1, tp.resType) - case tp: ExprType => - traverseInfo(depth, tp.resType) - case _ => - Nil - - def traverseParams(n: Int, formals: List[Type], acc: IntoOccurrenceMap): IntoOccurrenceMap = - if formals.isEmpty then acc - else - val occs = traverseInfo(0, formals.head) - traverseParams(n + 1, formals.tail, if occs.isEmpty then acc else acc + (n -> occs)) - - def traverse(n: Int, tp: Type, acc: IntoOccurrenceMap): IntoOccurrenceMap = tp match - case tp: PolyType => - traverse(n, tp.resType, acc) - case tp: MethodType => - traverse(n + tp.paramInfos.length, tp.resType, traverseParams(n, tp.paramInfos, acc)) - case _ => - acc - - traverse(0, tp, immutable.Map.empty) - end intoOccurrences - val checker = if makeOverridingPairsChecker == null then OverridingPairsChecker(clazz, self) else makeOverridingPairsChecker(clazz, self) @@ -480,13 +443,12 @@ object RefChecks { } def emitOverrideError(fullmsg: Message) = - if (!(hasErrors && member.is(Synthetic) && member.is(Module))) { - // suppress errors relating toi synthetic companion objects if other override + if !(hasErrors && member.is(Synthetic) && member.is(Module) || member.isDummyCaptureParam) then + // suppress errors relating to synthetic companion objects and capture parameters if other override // errors (e.g. relating to the companion class) have already been reported. if (member.owner == clazz) report.error(fullmsg, member.srcPos) else mixinOverrideErrors += new MixinOverrideError(member, fullmsg) hasErrors = true - } def overrideError(msg: String, compareTypes: Boolean = false) = if trueMatch && noErrorType then @@ -653,8 +615,6 @@ object RefChecks { overrideError(i"needs to be declared with @targetName(${"\""}${other.targetName}${"\""}) so that external names match") else overrideError("cannot have a @targetName annotation since external names would be different") - else if intoOccurrences(memberTp) != intoOccurrences(otherTp) then - overrideError("has different occurrences of `into` modifiers", compareTypes = true) else if other.is(ParamAccessor) && !isInheritedAccessor(member, other) && !member.is(Tracked) // see remark on tracked members above then // (1.12) @@ -752,10 +712,16 @@ object RefChecks { // to consolidate getters and setters. val grouped = missing.groupBy(_.underlyingSymbol.name) + def isDuplicateSetter(sym: Symbol): Boolean = + sym.isSetter && { + val field = sym.accessedFieldOrGetter + grouped.getOrElse(field.name, Nil).contains(field) + } + val missingMethods = grouped.toList flatMap { case (name, syms) => lastOverrides(syms) - .filterConserve(!_.isSetter) + .filterConserve(!isDuplicateSetter(_)) // Avoid reporting override error for both `x` and setter `x_=` .distinctBy(_.signature) // Avoid duplication for similar definitions (#19731) } @@ -957,7 +923,7 @@ object RefChecks { checkNoAbstractDecls(clazz) if (abstractErrors.nonEmpty) - report.error(abstractErrorMessage, clazz.srcPos) + report.error(abstractErrorMessage, clazzNamePos) checkMemberTypesOK() checkCaseClassInheritanceInvariant() @@ -1228,6 +1194,18 @@ object RefChecks { report.warning(ExtensionNullifiedByMember(sym, target), sym.srcPos) end checkExtensionMethods + /** Check that public (and protected) methods/fields do not expose flexible types. */ + def checkPublicFlexibleTypes(sym: Symbol)(using Context): Unit = + if ctx.explicitNulls && !ctx.isJava + && sym.exists && !sym.is(Private) && sym.owner.isClass + && !sym.isOneOf(Synthetic | InlineProxy | Param | Exported) then + val resTp = sym.info.finalResultType + if resTp.existsPart(_.isInstanceOf[FlexibleType], StopAt.Static) then + report.warning( + em"${sym.show} exposes a flexible type in its inferred result type ${resTp}. Consider annotating the type explicitly", + sym.srcPos + ) + /** Verify that references in the user-defined `@implicitNotFound` message are valid. * (i.e. they refer to a type variable that really occurs in the signature of the annotated symbol.) */ @@ -1302,9 +1280,9 @@ object RefChecks { val matches = referencePattern.findAllIn(s) for reference <- matches do val referenceOffset = matches.start - val prefixlessReference = reference.replaceFirst("""\$\{\s*""", "").nn + val prefixlessReference = reference.replaceFirst("""\$\{\s*""", "") val variableOffset = referenceOffset + reference.length - prefixlessReference.length - val variableName = prefixlessReference.replaceFirst("""\s*\}""", "").nn + val variableName = prefixlessReference.replaceFirst("""\s*\}""", "") f(variableName, variableOffset) end checkImplicitNotFoundAnnotation @@ -1364,6 +1342,7 @@ class RefChecks extends MiniPhase { thisPhase => val sym = tree.symbol checkNoPrivateOverrides(sym) checkVolatile(sym) + checkPublicFlexibleTypes(sym) if (sym.exists && sym.owner.isTerm) { tree.rhs match { case Ident(nme.WILDCARD) => report.error(UnboundPlaceholderParameter(), sym.srcPos) @@ -1379,6 +1358,7 @@ class RefChecks extends MiniPhase { thisPhase => checkImplicitNotFoundAnnotation.defDef(sym.denot) checkUnaryMethods(sym) checkExtensionMethods(sym) + checkPublicFlexibleTypes(sym) tree } diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 5111a9517fab..0c35d0377e51 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -20,6 +20,7 @@ import ast.tpd.* import Synthesizer.* import sbt.ExtractDependencies.* import xsbti.api.DependencyContext.* +import TypeComparer.{fullLowerBound, fullUpperBound} /** Synthesize terms for special classes */ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): @@ -29,17 +30,43 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): private type SpecialHandlers = List[(ClassSymbol, SpecialHandler)] val synthesizedClassTag: SpecialHandler = (formal, span) => - def instArg(tp: Type): Type = tp.stripTypeVar match - // Special case to avoid instantiating `Int & S` to `Int & Nothing` in - // i16328.scala. The intersection comes from an earlier instantiation - // to an upper bound. - // The dual situation with unions is harder to trigger because lower - // bounds are usually widened during instantiation. + def instArg(tp: Type): Type = tp.dealias match case tp: AndOrType if tp.tp1 =:= tp.tp2 => + // Special case to avoid instantiating `Int & S` to `Int & Nothing` in + // i16328.scala. The intersection comes from an earlier instantiation + // to an upper bound. + // The dual situation with unions is harder to trigger because lower + // bounds are usually widened during instantiation. instArg(tp.tp1) + case tvar: TypeVar if ctx.typerState.constraint.contains(tvar) => + // If tvar has a lower or upper bound: + // 1. If the bound is not another type variable, use this as approximation. + // 2. Otherwise, if the type can be forced to be fully defined, use that type + // as approximation. + // 3. Otherwise leave argument uninstantiated. + // The reason for (2) is that we observed complicated constraints in i23611.scala + // that get better types if a fully defined type is computed than if several type + // variables are approximated incrementally. This is a minimization of some ZIO code. + // So in order to keep backwards compatibility (where before we _only_ did 2) we + // add that special case. + def isGroundConstr(tp: Type): Boolean = tp.dealias match + case tvar: TypeVar if ctx.typerState.constraint.contains(tvar) => false + case pref: TypeParamRef if ctx.typerState.constraint.contains(pref) => false + case tp: AndOrType => isGroundConstr(tp.tp1) && isGroundConstr(tp.tp2) + case _ => true + instArg( + if tvar.hasLowerBound then + if isGroundConstr(fullLowerBound(tvar.origin)) then tvar.instantiate(fromBelow = true) + else if isFullyDefined(tp, ForceDegree.all) then tp + else NoType + else if tvar.hasUpperBound then + if isGroundConstr(fullUpperBound(tvar.origin)) then tvar.instantiate(fromBelow = false) + else if isFullyDefined(tp, ForceDegree.all) then tp + else NoType + else + NoType) case _ => - if isFullyDefined(tp, ForceDegree.all) then tp - else NoType // this happens in tests/neg/i15372.scala + tp val tag = formal.argInfos match case arg :: Nil => @@ -53,7 +80,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): if defn.SpecialClassTagClasses.contains(sym) then classTagModul.select(sym.name.toTermName).withSpan(span) else - val ctype = escapeJavaArray(erasure(tp)) + val ctype = escapeJavaArray(erasure(tp.normalizedTupleType)) if ctype.exists then classTagModul.select(nme.apply) .appliedToType(tp) @@ -176,19 +203,6 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): cmpWithBoxed(cls1, cls2) else if cls2.isPrimitiveValueClass then cmpWithBoxed(cls2, cls1) - else if ctx.mode.is(Mode.SafeNulls) then - // If explicit nulls is enabled, and unsafeNulls is not enabled, - // we want to disallow comparison between Object and Null. - // If we have to check whether a variable with a non-nullable type has null value - // (for example, a NotNull java method returns null for some reasons), - // we can still cast it to a nullable type then compare its value. - // - // Example: - // val x: String = null.asInstanceOf[String] - // if (x == null) {} // error: x is non-nullable - // if (x.asInstanceOf[String|Null] == null) {} // ok - if cls1 == defn.NullClass || cls2 == defn.NullClass then cls1 == cls2 - else cls1 == defn.NothingClass || cls2 == defn.NothingClass else if cls1 == defn.NullClass then cls1 == cls2 || cls2.derivesFrom(defn.ObjectClass) else if cls2 == defn.NullClass then @@ -232,6 +246,8 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): withNoErrors(success(Literal(Constant(())))) case n: TermRef => withNoErrors(success(ref(n))) + case ts: ThisType => + withNoErrors(success(This(ts.cls))) case tp => EmptyTreeNoError case _ => @@ -242,7 +258,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): case PreciseConstrained(tp, true) => if tp.isSingletonBounded(frozen = false) then withNoErrors: - ref(defn.Compiletime_erasedValue).appliedToType(formal).withSpan(span) + ref(defn.Caps_erasedValue).appliedToType(formal).withSpan(span) else withErrors(i"$tp is not a singleton") case _ => @@ -251,7 +267,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val synthesizedPrecise: SpecialHandler = (formal, span) => formal match case PreciseConstrained(tp, false) => withNoErrors: - ref(defn.Compiletime_erasedValue).appliedToType(formal).withSpan(span) + ref(defn.Caps_erasedValue).appliedToType(formal).withSpan(span) case _ => EmptyTreeNoError @@ -576,9 +592,8 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): resType <:< target val tparams = poly.paramRefs val variances = childClass.typeParams.map(_.paramVarianceSign) - val instanceTypes = tparams.lazyZip(variances).map((tparam, variance) => + val instanceTypes = tparams.lazyZip(variances).map: (tparam, variance) => TypeComparer.instanceType(tparam, fromBelow = variance < 0, Widen.Unions) - ) val instanceType = resType.substParams(poly, instanceTypes) // this is broken in tests/run/i13332intersection.scala, // because type parameters are not correctly inferred. diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 4d16a342f484..f1ad0f8520f1 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 util.Property import transform.Splicer trait TypeAssigner { @@ -270,36 +271,43 @@ trait TypeAssigner { untpd.cpy.Super(tree)(qual, tree.mix) .withType(superType(qual.tpe, tree.mix, mixinClass, tree.srcPos)) + private type SkolemBuffer = mutable.ListBuffer[(Tree, SkolemType)] + /** Substitute argument type `argType` for parameter `pref` in type `tp`, * skolemizing the argument type if it is not stable and `pref` occurs in `tp`. + * If skolemization happens the new SkolemType is passed to `recordSkolem` + * provided the latter is non-null. */ - def safeSubstParam(tp: Type, pref: ParamRef, argType: Type)(using Context): Type = { + def safeSubstParam(tp: Type, pref: ParamRef, argType: Type, + recordSkolem: (SkolemType => Unit) | Null = null)(using Context): Type = val tp1 = tp.substParam(pref, argType) - if ((tp1 eq tp) || argType.isStable) tp1 - else tp.substParam(pref, SkolemType(argType.widen)) - } + if (tp1 eq tp) || argType.isStable then tp1 + else + val narrowed = SkolemType(argType.widen) + if recordSkolem != null then recordSkolem(narrowed) + tp.substParam(pref, narrowed) /** Substitute types of all arguments `args` for corresponding `params` in `tp`. * The number of parameters `params` may exceed the number of arguments. * In this case, only the common prefix is substituted. + * Skolems generated by `safeSubstParam` are stored in `skolems`. */ - def safeSubstParams(tp: Type, params: List[ParamRef], argTypes: List[Type])(using Context): Type = argTypes match { - case argType :: argTypes1 => - val tp1 = safeSubstParam(tp, params.head, argType) - safeSubstParams(tp1, params.tail, argTypes1) + private def safeSubstParams(tp: Type, params: List[ParamRef], + args: List[Tree], skolems: SkolemBuffer)(using Context): Type = args match + case arg :: args1 => + val tp1 = safeSubstParam(tp, params.head, arg.tpe, sk => skolems += ((arg, sk))) + safeSubstParams(tp1, params.tail, args1, skolems) case Nil => tp - } - - def safeSubstMethodParams(mt: MethodType, argTypes: List[Type])(using Context): Type = - if mt.isResultDependent then safeSubstParams(mt.resultType, mt.paramRefs, argTypes) - else mt.resultType def assignType(tree: untpd.Apply, fn: Tree, args: List[Tree])(using Context): Apply = { + var skolems: SkolemBuffer | Null = null val ownType = fn.tpe.widen match { case fntpe: MethodType => if fntpe.paramInfos.hasSameLengthAs(args) || ctx.phase.prev.relaxedTyping then - if fntpe.isResultDependent then safeSubstMethodParams(fntpe, args.tpes) + if fntpe.isResultDependent then + skolems = new mutable.ListBuffer() + safeSubstParams(fntpe.resultType, fntpe.paramRefs, args, skolems.nn) else fntpe.resultType // fast path optimization else val erroringPhase = @@ -312,7 +320,13 @@ trait TypeAssigner { if (ctx.settings.Ydebug.value) new FatalError("").printStackTrace() errorType(err.takesNoParamsMsg(fn, ""), tree.srcPos) } - ConstFold.Apply(tree.withType(ownType)) + val app = tree.withType(ownType) + if skolems != null + && skolems.nn.nonEmpty // @notional why is `.nn` needed here? + && skolems.nn.size == skolems.nn.toSet.size // each skolemized argument is unique + then + app.putAttachment(SkolemizedArgs, skolems.nn.toMap) + ConstFold.Apply(app) } def assignType(tree: untpd.TypeApply, fn: Tree, args: List[Tree])(using Context): TypeApply = { @@ -570,6 +584,12 @@ trait TypeAssigner { } object TypeAssigner extends TypeAssigner: + + /** An attachment on an application indicating a map from arguments to the skolem types + * that were created in safeSubstParams. + */ + private[typer] val SkolemizedArgs = new Property.Key[Map[tpd.Tree, SkolemType]] + def seqLitType(tree: untpd.SeqLiteral, elemType: Type)(using Context) = tree match case tree: untpd.JavaSeqLiteral => defn.ArrayOf(elemType) case _ => if ctx.erasedTypes then defn.SeqType else defn.SeqType.appliedTo(elemType) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 79aaf367851a..da37367c0f3a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -53,6 +53,7 @@ import transform.CheckUnused.OriginalName import scala.annotation.{unchecked as _, *} import dotty.tools.dotc.util.chaining.* +import dotty.tools.dotc.ast.untpd.Mod object Typer { @@ -210,7 +211,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer altImports: mutable.ListBuffer[TermRef] | Null = null)(using Context): Type = { val refctx = ctx val noImports = ctx.mode.is(Mode.InPackageClauseName) - def suppressErrors = excluded.is(ConstructorProxy) + def suppressErrors = excluded.is(PhantomSymbol) // when searching for references shadowed by a constructor proxy, do not report errors def fail(msg: Message) = if !suppressErrors then report.error(msg, pos) @@ -649,8 +650,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer */ def checkNotShadowed(ownType: Type): Type = ownType match - case ownType: TermRef if ownType.symbol.is(ConstructorProxy) => - findRef(name, pt, EmptyFlags, ConstructorProxy, tree.srcPos) match + case ownType: TermRef + if ownType.symbol.is(PhantomSymbol) + && !(ctx.mode.is(Mode.InCaptureSet) && ownType.symbol.isDummyCaptureParam) => + findRef(name, pt, EmptyFlags, PhantomSymbol, tree.srcPos) match case shadowed: TermRef if !shadowed.symbol.maybeOwner.isEmptyPackage => pt match case pt: FunOrPolyProto => @@ -670,7 +673,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val checkedType = checkNotShadowed(ownType) val tree1 = checkedType match case checkedType: NamedType if !prefixIsElidable(checkedType) => - ref(checkedType).withSpan(tree.span) + ref(checkedType).withSpan(tree.span).withAttachmentsFrom(tree) case _ => def isScalaModuleRef = checkedType match case moduleRef: TypeRef if moduleRef.symbol.is(ModuleClass, butNot = JavaDefined) => true @@ -681,25 +684,33 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer tree.withType(checkedType) checkLegalValue(toNotNullTermRef(tree1, pt), pt) - def isLocalExtensionMethodRef: Boolean = rawType match - case rawType: TermRef => - rawType.denot.hasAltWith(_.symbol.is(ExtensionMethod)) - && !pt.isExtensionApplyProto - && { + // extensionParam + def leadParamOf(m: SymDenotation): Symbol = + def leadParam(paramss: List[List[Symbol]]): Symbol = paramss match + case (param :: _) :: paramss if param.isType => leadParam(paramss) + case _ :: (param :: Nil) :: _ if m.name.isRightAssocOperatorName => param + case (param :: Nil) :: _ => param + case _ => NoSymbol + leadParam(m.rawParamss) + + val localExtensionSelection: untpd.Tree = + var select: untpd.Tree = EmptyTree + if ctx.mode.is(Mode.InExtensionMethod) then + rawType match + case rawType: TermRef + if rawType.denot.hasAltWith(_.symbol.is(ExtensionMethod)) && !pt.isExtensionApplyProto => val xmethod = ctx.owner.enclosingExtensionMethod - rawType.denot.hasAltWith { alt => - alt.symbol.is(ExtensionMethod) - && alt.symbol.extensionParam.span == xmethod.extensionParam.span - } - } - case _ => - false + val xparam = leadParamOf(xmethod) + if rawType.denot.hasAltWith: alt => + alt.symbol.is(ExtensionMethod) + && alt.symbol.extensionParam.span == xparam.span // forces alt.symbol (which might be xmethod) + then + select = untpd.cpy.Select(tree)(untpd.ref(xparam), name) + case _ => + select - if ctx.mode.is(Mode.InExtensionMethod) && isLocalExtensionMethodRef then - val xmethod = ctx.owner.enclosingExtensionMethod - val qualifier = untpd.ref(xmethod.extensionParam.termRef) - val selection = untpd.cpy.Select(tree)(qualifier, name) - typed(selection, pt) + if !localExtensionSelection.isEmpty then + typed(localExtensionSelection, pt) else if rawType.exists then val ref = setType(ensureAccessible(rawType, superAccess = false, tree.srcPos)) if ref.symbol.name != name then @@ -836,10 +847,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Otherwise, map combinations of A *: B *: .... EmptyTuple with nesting levels <= 22 // to the Tuple class of the right arity and select from that one def trySmallGenericTuple(qual: Tree, withCast: Boolean) = - if qual.tpe.isSmallGenericTuple then + val tp = qual.tpe.widenTermRefExpr + val tpNormalized = tp.normalizedTupleType + if tp ne tpNormalized then if withCast then - val elems = qual.tpe.widenTermRefExpr.tupleElementTypes.getOrElse(Nil) - typedSelectWithAdapt(tree, pt, qual.cast(defn.tupleType(elems))) + typedSelectWithAdapt(tree, pt, qual.cast(tpNormalized)) else typedSelectWithAdapt(tree, pt, qual) else EmptyTree @@ -1076,7 +1088,19 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer errorTree(tree, em"cannot convert to type selection") // will never be printed due to fallback } - if (tree.qualifier.isType) { + def warnUnnecessaryNN(tree: Tree): Unit = { + if ctx.explicitNulls then { + val symbol = tree.symbol + if symbol.exists && symbol.owner == defn.ScalaPredefModuleClass && symbol.name == nme.nn then + tree match + case Apply(_, args) => + if(args.head.tpe.isNotNull) then report.warning(UnnecessaryNN("qualifier is already not null", args.head.sourcePos), tree) + if pt.admitsNull then report.warning(UnnecessaryNN("expected type admits null", args.head.sourcePos), tree) + case _ => + } + } + + val tree1 = if (tree.qualifier.isType) { val qual1 = typedType(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan)) assignType(cpy.Select(tree)(qual1, tree.name), qual1) } @@ -1086,6 +1110,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer tryAlternatively(typeSelectOnTerm)(tryJavaSelectOnType) else typeSelectOnTerm + + warnUnnecessaryNN(tree1) + tree1 } def typedThis(tree: untpd.This)(using Context): Tree = { @@ -1210,6 +1237,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => var tpt1 = typedType(tree.tpt) val tsym = tpt1.tpe.underlyingClassRef(refinementOK = false).typeSymbol + if ctx.mode.isQuotedPattern && tpt1.tpe.typeSymbol.isAllOf(Synthetic | Case) then + val errorTp = errorType(CannotInstantiateQuotedTypeVar(tpt1.tpe.typeSymbol), tpt1.srcPos) + return cpy.New(tree)(tpt1).withType(errorTp) if tsym.is(Package) then report error(em"$tsym cannot be instantiated", tpt1.srcPos) tpt1 = tpt1.withType(ensureAccessible(tpt1.tpe, superAccess = false, tpt1.srcPos)) @@ -1653,11 +1683,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val restpe = mt.resultType match case mt: MethodType => mt.toFunctionType(isJava = samParent.classSymbol.is(JavaDefined)) case tp => tp - (formals, - if (mt.isResultDependent) - untpd.InLambdaTypeTree(isResult = true, (_, syms) => restpe.substParams(mt, syms.map(_.termRef))) - else - typeTree(restpe)) + val tree = + if (mt.isResultDependent) { + if (formals.length != defaultArity) + typeTree(WildcardType) + else + untpd.InLambdaTypeTree(isResult = true, (_, syms) => restpe.substParams(mt, syms.map(_.termRef))) + } else + typeTree(restpe) + (formals, tree) case _ => (List.tabulate(defaultArity)(alwaysWildcardType), untpd.TypeTree()) } @@ -1705,11 +1739,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else typedFunctionValue(tree, pt) def typedFunctionType(tree: untpd.Function, pt: Type)(using Context): Tree = { - val untpd.Function(args, body) = tree - body match - case untpd.CapturesAndResult(refs, result) => + val untpd.Function(args, result) = tree + result match + case untpd.CapturesAndResult(refs, result1) => return typedUnadapted(untpd.makeRetaining( - cpy.Function(tree)(args, result), refs, tpnme.retains), pt) + cpy.Function(tree)(args, result1), refs, tpnme.retains), pt) case _ => var (funFlags, erasedParams) = tree match { case tree: untpd.FunctionWithMods => (tree.mods.flags, tree.erasedParams) @@ -1721,63 +1755,65 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val isImpure = funFlags.is(Impure) /** Typechecks dependent function type with given parameters `params` */ - def typedDependent(params: List[untpd.ValDef])(using Context): Tree = - val fixThis = new untpd.UntypedTreeMap: - // pretype all references of this in outer context, - // so that they do not refer to the refined type being constructed - override def transform(tree: untpd.Tree)(using Context): untpd.Tree = tree match - case This(id) => untpd.TypedSplice(typedExpr(tree)(using ctx.outer)) - case _ => super.transform(tree) - + def typedDependent(params: List[untpd.ValDef], result: untpd.Tree)(using Context): Tree = val params1 = if funFlags.is(Given) then params.map(_.withAddedFlags(Given)) else params - val params2 = params1.map(fixThis.transformSub) - val params3 = params2.zipWithConserve(erasedParams) { (arg, isErased) => + val params2 = params1.zipWithConserve(erasedParams): (arg, isErased) => if isErased then arg.withAddedFlags(Erased) else arg - } - val appDef0 = untpd.DefDef(nme.apply, List(params3), body, EmptyTree).withSpan(tree.span) + val appDef0 = untpd.DefDef(nme.apply, List(params2), result, EmptyTree).withSpan(tree.span) index(appDef0 :: Nil) val appDef = typed(appDef0).asInstanceOf[DefDef] val mt = appDef.symbol.info.asInstanceOf[MethodType] if (mt.isParamDependent) report.error(em"$mt is an illegal function type because it has inter-parameter dependencies", tree.srcPos) // Restart typechecking if there are erased classes that we want to mark erased - if mt.erasedParams.zip(mt.paramInfos.map(_.isErasedClass)).exists((paramErased, classErased) => classErased && !paramErased) then - val newParams = params3.zipWithConserve(mt.paramInfos.map(_.isErasedClass)) { (arg, isErasedClass) => - if isErasedClass then arg.withAddedFlags(Erased) else arg - } - return typedDependent(newParams) - val core = - if mt.hasErasedParams then TypeTree(defn.PolyFunctionClass.typeRef) - else - val resTpt = TypeTree(mt.nonDependentResultApprox).withSpan(body.span) - val paramTpts = appDef.termParamss.head.map(p => TypeTree(p.tpt.tpe).withSpan(p.tpt.span)) - val funSym = defn.FunctionSymbol(numArgs, isContextual) - val tycon = TypeTree(funSym.typeRef) - AppliedTypeTree(tycon, paramTpts :+ resTpt) - val res = RefinedTypeTree(core, List(appDef), ctx.owner.asClass) - if isImpure then - typed(untpd.makeRetaining(untpd.TypedSplice(res), Nil, tpnme.retainsCap), pt) + if mt.paramErasureStatuses.lazyZip(mt.paramInfos).exists: (paramErased, info) => + !paramErased && info.derivesFrom(defn.ErasedClass) + then + val newParams = params2.zipWithConserve(mt.paramInfos): (param, info) => + if info.derivesFrom(defn.ErasedClass) then param.withAddedFlags(Erased) else param + typedDependent(newParams, result) else - res + val core = + if mt.hasErasedParams then TypeTree(defn.PolyFunctionClass.typeRef) + else + val resTpt = TypeTree(mt.nonDependentResultApprox).withSpan(result.span) + val paramTpts = appDef.termParamss.head.map(p => TypeTree(p.tpt.tpe).withSpan(p.tpt.span)) + val funSym = defn.FunctionSymbol(numArgs, isContextual) + val tycon = TypeTree(funSym.typeRef) + AppliedTypeTree(tycon, paramTpts :+ resTpt) + val res = RefinedTypeTree(core, List(appDef), ctx.owner.asClass) + if isImpure then + typed(untpd.makeRetaining(untpd.TypedSplice(res), Nil, tpnme.retainsCap), pt) + else + res end typedDependent args match { case ValDef(_, _, _) :: _ => - typedDependent(args.asInstanceOf[List[untpd.ValDef]])( + val fixThis = new untpd.UntypedTreeMap: + // pretype all references of this so that they do not refer to the + // refined type being constructed + override def transform(tree: untpd.Tree)(using Context): untpd.Tree = tree match + case This(id) => untpd.TypedSplice(typedExpr(tree)) + case _ => super.transform(tree) + + val untpd.Function(fixedArgs: List[untpd.ValDef] @unchecked, fixedResult) = + fixThis.transform(tree): @unchecked + typedDependent(fixedArgs, fixedResult)( using ctx.fresh.setOwner(newRefinedClassSymbol(tree.span)).setNewScope) case _ => if erasedParams.contains(true) then typedFunctionType(desugar.makeFunctionWithValDefs(tree, pt), pt) else val funSym = defn.FunctionSymbol(numArgs, isContextual, isImpure) - val result = typed(cpy.AppliedTypeTree(tree)(untpd.TypeTree(funSym.typeRef), args :+ body), pt) + val funTpt = typed(cpy.AppliedTypeTree(tree)(untpd.TypeTree(funSym.typeRef), args :+ result), pt) // if there are any erased classes, we need to re-do the typecheck. - result match - case r: AppliedTypeTree if r.args.exists(_.tpe.isErasedClass) => + funTpt match + case r: AppliedTypeTree if r.args.init.exists(_.tpe.derivesFrom(defn.ErasedClass)) => typedFunctionType(desugar.makeFunctionWithValDefs(tree, pt), pt) - case _ => result + case _ => funTpt } } @@ -1897,8 +1933,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val firstFormal = protoFormals.head.loBound if ptIsCorrectProduct(firstFormal, params) then val isGenericTuple = - firstFormal.derivesFrom(defn.TupleClass) - && !defn.isTupleClass(firstFormal.typeSymbol) + firstFormal.isNamedTupleType + || (firstFormal.derivesFrom(defn.TupleClass) + && !defn.isTupleClass(firstFormal.typeSymbol)) desugared = desugar.makeTupledFunction(params, fnBody, isGenericTuple) else if protoFormals.length > 1 && params.length == 1 then def isParamRef(scrut: untpd.Tree): Boolean = scrut match @@ -1912,9 +1949,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // to // (a1, ..., aN) => e val params1 = desugar.patternsToParams(elems) + val matchCheck = scrut.getAttachment(desugar.CheckIrrefutable) + .getOrElse(desugar.MatchCheck.IrrefutablePatDef) desugared = if params1.hasSameLengthAs(elems) then cpy.Function(tree)(params1, rhs) - else desugar.makeCaseLambda(cases, desugar.MatchCheck.IrrefutablePatDef, protoFormals.length) + else desugar.makeCaseLambda(cases, matchCheck, protoFormals.length) case _ => if desugared.isEmpty then @@ -2018,7 +2057,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case mt: MethodType => pt.findFunctionType match { case SAMType(samMeth, samParent) - if !defn.isFunctionNType(samParent) && mt <:< samMeth => + if !ctx.erasedTypes && !defn.isFunctionNType(samParent) + && mt <:< samMeth && !mt.isImplicitMethod => if defn.isContextFunctionType(mt.resultType) then report.error( em"""Implementation restriction: cannot convert this expression to `$samParent` @@ -2365,7 +2405,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer untpd.ValDef( CanThrowEvidenceName.fresh(), untpd.TypeTree(defn.CanThrowClass.typeRef.appliedTo(tp)), - untpd.ref(defn.Compiletime_erasedValue)) + untpd.ref(defn.Caps_erasedValue)) .withFlags(Given | Final | Erased) .withSpan(expr.span) val caughtExceptions = @@ -2512,6 +2552,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val tycon = typedType(tree.tycon) def spliced(tree: Tree) = untpd.TypedSplice(tree) val tparam = untpd.Ident(tree.paramName).withSpan(tree.span.withEnd(tree.span.point)) + if Feature.ccEnabled && typed(tparam).tpe.derivesFrom(defn.Caps_CapSet) then + report.error(em"Capture variable `${tree.paramName}` cannot have a context bound.", tycon.srcPos) if tycon.tpe.typeParams.nonEmpty then val tycon0 = tycon.withType(tycon.tpe.etaCollapse) typed(untpd.AppliedTypeTree(spliced(tycon0), tparam :: Nil)) @@ -2526,8 +2568,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer errorTree(tree, em"Illegal context bound: ${tycon.tpe} does not take type parameters$selfNote.") - def typedSingletonTypeTree(tree: untpd.SingletonTypeTree)(using Context): SingletonTypeTree = { + def typedSingletonTypeTree(tree: untpd.SingletonTypeTree)(using Context): Tree = { val ref1 = typedExpr(tree.ref, SingletonTypeProto) + if ctx.mode.is(Mode.InCaptureSet) && ref1.symbol.isDummyCaptureParam then + // When a dummy term capture variable is found, it is replaced with + // the corresponding type references (stored in the underling types). + return Ident(ref1.tpe.widen.asInstanceOf[TypeRef]).withSpan(tree.span) checkStable(ref1.tpe, tree.srcPos, "singleton type") assignType(cpy.SingletonTypeTree(tree)(ref1), ref1) } @@ -2557,17 +2603,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } def typedAppliedTypeTree(tree: untpd.AppliedTypeTree)(using Context): Tree = { - tree.args match - case arg :: _ if arg.isTerm => - if Feature.dependentEnabled then - return errorTree(tree, em"Not yet implemented: T(...)") - else - return errorTree(tree, dependentMsg) - case _ => - - val tpt1 = withoutMode(Mode.Pattern) { + val tpt1 = withoutMode(Mode.Pattern): typed(tree.tpt, AnyTypeConstructorProto) - } + val tparams = tpt1.tpe.typeParams if tpt1.tpe.isError then val args1 = tree.args.mapconserve(typedType(_)) @@ -2691,7 +2729,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer typeIndexedLambdaTypeTree(tree, tparams, body) def typedTermLambdaTypeTree(tree: untpd.TermLambdaTypeTree)(using Context): Tree = - if Feature.dependentEnabled then + if Feature.enabled(Feature.modularity) then errorTree(tree, em"Not yet implemented: (...) =>> ...") else errorTree(tree, dependentMsg) @@ -2730,18 +2768,47 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer assignType(cpy.ByNameTypeTree(tree)(result1), result1) def typedTypeBoundsTree(tree: untpd.TypeBoundsTree, pt: Type)(using Context): Tree = + lazy val CapSetBot = untpd.TypeTree(defn.Caps_CapSet.typeRef) + lazy val CapSetTop = untpd.makeRetaining(untpd.TypeTree(defn.Caps_CapSet.typeRef), Nil, tpnme.retainsCap).withSpan(tree.span) + val TypeBoundsTree(lo, hi, alias) = tree val lo1 = typed(lo) val hi1 = typed(hi) val alias1 = typed(alias) - val lo2 = if (lo1.isEmpty) typed(untpd.TypeTree(defn.NothingType)) else lo1 - val hi2 = if (hi1.isEmpty) typed(untpd.TypeTree(defn.AnyType)) else hi1 + val isCap = tree.hasAttachment(CaptureVar) + val lo2 = + if lo1.isEmpty then + if Feature.ccEnabled && (isCap || hi1.tpe.derivesFrom(defn.Caps_CapSet)) then + typed(CapSetBot) + else typed(untpd.TypeTree(defn.NothingType)) + else lo1 + val hi2 = + if hi1.isEmpty then + if Feature.ccEnabled && (isCap || lo1.tpe.derivesFrom(defn.Caps_CapSet)) then + typed(CapSetTop) + else typed(untpd.TypeTree(defn.AnyType)) + else hi1 assignType(cpy.TypeBoundsTree(tree)(lo2, hi2, alias1), lo2, hi2, alias1) + end typedTypeBoundsTree def typedBind(tree: untpd.Bind, pt: Type)(using Context): Tree = { if !isFullyDefined(pt, ForceDegree.all) then return errorTree(tree, em"expected type of $tree is not fully defined") val body1 = typed(tree.body, pt) + + // When we pattern match a named tuple, both the named tuple pattern and the + // regular tuple pattern are desugared to a regular tuple unapply. + // If the pattern (body) is a named tuple pattern, we give the binding + // a named tuple type using pt; otherwise we give it the regular tuple type. + // For example, in `case x @ (a = 1, b = 2)`, the type of `x` will be `(a: Int, b: Int)`; + // in `case x @ (a, b)`, the type of `x` will be `(Int, Int)`. + def isNamedTuplePattern = + ctx.mode.is(Mode.Pattern) + && pt.dealias.isNamedTupleType + && tree.body.match + case untpd.Tuple((_: NamedArg) :: _) => true + case _ => false + body1 match { case UnApply(fn, Nil, arg :: Nil) if fn.symbol.exists && (fn.symbol.owner.derivesFrom(defn.TypeTestClass) || fn.symbol.owner == defn.ClassTagClass) && !body1.tpe.isError => @@ -2767,10 +2834,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer body1.isInstanceOf[RefTree] && !isWildcardArg(body1) || body1.isInstanceOf[Literal] val symTp = - if isStableIdentifierOrLiteral || pt.dealias.isNamedTupleType then pt - // need to combine tuple element types with expected named type + if isStableIdentifierOrLiteral || isNamedTuplePattern then pt else if isWildcardStarArg(body1) || pt == defn.ImplicitScrutineeTypeRef + || pt.isBottomType || body1.tpe <:< pt // There is some strange interaction with gadt matching. // and implicit scopes. // run/t2755.scala fails to compile if this subtype test is omitted @@ -2805,10 +2872,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else assert(ctx.reporter.errorsReported) tree.withType(defn.AnyType) - val savedGadt = nestedCtx.gadt - val trees1 = tree.trees.mapconserve(typed(_, pt)(using nestedCtx)) + val preGadt = nestedCtx.gadt + var gadtConstrs: mutable.ArrayBuffer[GadtConstraint] = mutable.ArrayBuffer.empty + val trees1 = tree.trees.mapconserve: t => + nestedCtx.gadtState.restore(preGadt) + val res = typed(t, pt)(using nestedCtx) + gadtConstrs += nestedCtx.gadt + res .mapconserve(ensureValueTypeOrWildcard) - nestedCtx.gadtState.restore(savedGadt) // Disable GADT reasoning for pattern alternatives + // Look for the necessary constraint that is subsumed by all alternatives. + // Use that constraint as the outcome if possible, otherwise fallback to not using + // GADT reasoning for soundness. + TypeComparer.necessaryGadtConstraint(gadtConstrs.toList, preGadt) match + case Some(constr) => nestedCtx.gadtState.restore(constr) + case None => nestedCtx.gadtState.restore(preGadt) assignType(cpy.Alternative(tree)(trees1), trees1) } @@ -2881,6 +2958,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer completeAnnotations(vdef, sym) if sym.is(Implicit) then checkImplicitConversionDefOK(sym) if sym.is(Module) then checkNoModuleClash(sym) + else if sym.info.derivesFrom(defn.ErasedClass) then + if sym.isAllOf(Given | Lazy) && !vdef.mods.mods.exists(_.flags.is(Lazy)) then + // reset implied Lazy flag of givens, but keep explicit modifier + sym.resetFlag(Lazy) + checkErasedOK(sym) + sym.setFlag(Erased) val tpt1 = checkSimpleKinded(typedType(tpt)) val rhs1 = vdef.rhs match case rhs @ Ident(nme.WILDCARD) => @@ -2891,9 +2974,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) postProcessInfo(vdef1, sym) vdef1.setDefTree + + migrate(ImplicitToGiven.valDef(vdef1)) + val nnInfo = rhs1.notNullInfo vdef1.withNotNullInfo(if sym.is(Lazy) then nnInfo.retractedInfo else nnInfo) } + private def retractDefDef(sym: Symbol)(using Context): Tree = // it's a discarded method (synthetic case class method or synthetic java record constructor or overridden member), drop it val canBeInvalidated: Boolean = @@ -2905,14 +2992,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer EmptyTree def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree = if !sym.info.exists then retractDefDef(sym) else ctx.profiler.onTypedDef(sym) { - - // TODO: - Remove this when `scala.language.experimental.erasedDefinitions` is no longer experimental. - // - Modify signature to `erased def erasedValue[T]: T` - if sym.eq(defn.Compiletime_erasedValue) then - // scala.compiletime.erasedValue should be `erased` but we cannot add this in the source. - // The library cannot use experimental language features, - // hence we special case it until `erased` is no longer experimental. - sym.setFlag(Erased) val DefDef(name, paramss, tpt, _) = ddef checkNonRootName(ddef.name, ddef.nameSpan) completeAnnotations(ddef, sym) @@ -3003,21 +3082,19 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val ddef2 = assignType(cpy.DefDef(ddef)(name, paramss1, tpt1, rhs1), sym) + migrate(ImplicitToGiven.defDef(ddef2)) + postProcessInfo(ddef2, sym) //todo: make sure dependent method types do not depend on implicits or by-name params } /** (1) Check that the signature of the class member does not return a repeated parameter type - * (2) If info is an erased class, set erased flag of member - * (3) Check that erased classes are not parameters of polymorphic functions. - * (4) Make sure the definition's symbol is `sym`. - * (5) Set the `defTree` of `sym` to be `mdef`. + * (2) Make sure the definition's symbol is `sym`. + * (3) Set the `defTree` of `sym` to be `mdef`. */ private def postProcessInfo(mdef: MemberDef, sym: Symbol)(using Context): MemberDef = if (!sym.isOneOf(Synthetic | InlineProxy | Param) && sym.info.finalResultType.isRepeatedParam) report.error(em"Cannot return repeated parameter type ${sym.info.finalResultType}", sym.srcPos) - if !sym.is(Module) && !sym.isConstructor && sym.info.finalResultType.isErasedClass then - sym.setFlag(Erased) mdef.ensureHasSym(sym) mdef.setDefTree @@ -3032,7 +3109,25 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if sym.isOpaqueAlias then checkFullyAppliedType(rhs1, "Opaque type alias must be fully applied, but ") checkNoContextFunctionType(rhs1) - assignType(cpy.TypeDef(tdef)(name, rhs1), sym) + var attachCap = false + if Feature.ccEnabled then + val isCap = tdef.hasAttachment(CaptureVar) + rhs1 match + case TypeBoundsTree(lo, hi, _) => + val loIsCap = lo.tpe.derivesFrom(defn.Caps_CapSet) + val hiIsCap = hi.tpe.derivesFrom(defn.Caps_CapSet) + if !isCap && (loIsCap ^ hiIsCap) then + report.error(em"Illegal type bounds: >: $lo <: $hi. Capture-set bounds cannot be mixed with type bounds of other kinds", rhs.srcPos) + if isCap && !(loIsCap && hiIsCap) then + report.error(em"Illegal type bounds: >: $lo <: $hi. $name^ can only have capture sets as bounds", rhs.srcPos) + attachCap = !isCap && loIsCap && hiIsCap + case LambdaTypeTree(_, _) if isCap => + report.error(em"`$name` cannot have type parameters, because it ranges over capture sets", rhs.srcPos) + case _ => + val res = assignType(cpy.TypeDef(tdef)(name, rhs1), sym) + if Feature.ccEnabled && attachCap then + res.putAttachment(CaptureVar, ()) + res } def typedClassDef(cdef: untpd.TypeDef, cls: ClassSymbol)(using Context): Tree = ctx.profiler.onTypedDef(cls) { @@ -3166,10 +3261,26 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val usingParamAccessors = cls.paramAccessors.filter(_.is(Given)) val paramScope = newScopeWith(usingParamAccessors*) val searchCtx = ctx.outer.fresh.setScope(paramScope) + + // Before losing the reference to ctx.owner + // when calling implicitArgTree with searchCtx, + // let's store ctx.owner as the fallback "responsibleForImports" + // in DependencyRecorder. That way, if we end up recording any dependencies + // we use ctx.owner as the "fromClass" rather than emitting a warning + // (because ctx.compilationUnit.tpdTree is still EmptyTree during typer). + // For example, to record mirror dependencies, see i23049. + val depRecorder = ctx.compilationUnit.depRecorder + val responsibleForImports = depRecorder._responsibleForImports + if responsibleForImports == null then + depRecorder._responsibleForImports = ctx.owner + val rhs = implicitArgTree(target, cdef.span, where = i"inferring the implementation of the deferred ${dcl.showLocated}" )(using searchCtx) + if responsibleForImports == null then + depRecorder._responsibleForImports = null + val impl = dcl.copy(cls, flags = dcl.flags &~ (HasDefault | Deferred) | Final | Override, info = target, @@ -3206,7 +3317,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer checkEnumParent(cls, firstParent) - if defn.ScalaValueClasses()(cls) && ctx.settings.YcompileScala2Library.value then + if defn.ScalaValueClasses()(cls) && Feature.shouldBehaveAsScala2 then constr1.symbol.resetFlag(Private) val self1 = typed(self)(using ctx.outer).asInstanceOf[ValDef] // outer context where class members are not visible @@ -3365,7 +3476,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer end typedPackageDef def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree = { - val annot0 = withMode(Mode.InAnnotation)(typedExpr(tree.annot)) + var annotCtx = ctx.addMode(Mode.InAnnotation) + if tree.annot.hasAttachment(untpd.RetainsAnnot) then + annotCtx = annotCtx.addMode(Mode.InCaptureSet) + val annot0 = typedExpr(tree.annot)(using annotCtx) val annot1 = checkAnnotClass(annot0) val annotCls = Annotations.annotClass(annot1) if annotCls == defn.NowarnAnnot then @@ -3636,12 +3750,18 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val ifpt = defn.asContextFunctionType(pt) val result = if ifpt.exists - && defn.functionArity(ifpt) > 0 // ContextFunction0 is only used after ElimByName + && !ctx.isAfterTyper + && { + // ContextFunction0 is only used after ElimByName + val arity = defn.functionArity(ifpt) + if arity == 0 then + report.error(em"context function types require at least one parameter", xtree.srcPos) + arity > 0 + } && xtree.isTerm && !untpd.isContextualClosure(xtree) && !ctx.mode.is(Mode.Pattern) && !xtree.isInstanceOf[SplicePattern] - && !ctx.isAfterTyper && !ctx.isInlineContext then makeContextualFunction(xtree, ifpt) @@ -3704,13 +3824,23 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } val erasedParams = pt match { - case defn.PolyFunctionOf(mt: MethodType) => mt.erasedParams + case defn.PolyFunctionOf(mt: MethodType) => mt.paramErasureStatuses case _ => paramTypes.map(_ => false) } val ifun = desugar.makeContextualFunction(paramTypes, paramNamesOrNil, tree, erasedParams) typr.println(i"make contextual function $tree / $pt ---> $ifun") typedFunctionValue(ifun, pt) + .tap: + case tree @ Block((m1: DefDef) :: _, _: Closure) if ctx.settings.Whas.wrongArrow => + m1.rhs match + case Block((m2: DefDef) :: _, _: Closure) if m1.paramss.lengthCompare(m2.paramss) == 0 => + val p1s = m1.symbol.info.asInstanceOf[MethodType].paramInfos + val p2s = m2.symbol.info.asInstanceOf[MethodType].paramInfos + if p1s.corresponds(p2s)(_ =:= _) then + report.warning(em"Context function adapts a lambda with the same parameter types, possibly ?=> was intended.", tree.srcPos) + case _ => + case _ => } /** Typecheck and adapt tree, returning a typed tree. Parameters as for `typedUnadapted` */ @@ -4200,6 +4330,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case wtp: MethodOrPoly => def methodStr = methPart(tree).symbol.showLocated if matchingApply(wtp, pt) then + migrate(ImplicitToGiven.implicitParams(tree, wtp, pt)) migrate(contextBoundParams(tree, wtp, pt)) migrate(implicitParams(tree, wtp, pt)) if needsTupledDual(wtp, pt) then adapt(tree, pt.tupledDual, locked) @@ -4251,22 +4382,40 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer implicitArgs(formals2, argIndex + 1, pt) val arg = inferImplicitArg(formal, tree.span.endPos) + + lazy val defaultArg = findDefaultArgument(argIndex) + .showing(i"default argument: for $formal, $tree, $argIndex = $result", typr) + def argHasDefault = hasDefaultParams && !defaultArg.isEmpty + + def canProfitFromMoreConstraints = + arg.tpe.isInstanceOf[AmbiguousImplicits] + // Ambiguity could be decided by more constraints + || !isFullyDefined(formal, ForceDegree.none) && !argHasDefault + // More context might constrain type variables which could make implicit scope larger. + // But in this case we should search with additional arguments typed only if there + // is no default argument. + + // Try to constrain the result using `pt1`, but back out if a BadTyperStateAssertion + // is thrown. TODO Find out why the bad typer state arises and prevent it. The try-catch + // is a temporary hack to keep projects compiling that would fail otherwise due to + // searching more arguments to instantiate implicits (PR #23532). A failing project + // is described in issue #23609. + def tryConstrainResult(pt: Type): Boolean = + try constrainResult(tree.symbol, wtp, pt) + catch case ex: TyperState.BadTyperStateAssertion => false + arg.tpe match - case failed: AmbiguousImplicits => + case failed: SearchFailureType if canProfitFromMoreConstraints => val pt1 = pt.deepenProtoTrans - if (pt1 `ne` pt) && (pt1 ne sharpenedPt) && constrainResult(tree.symbol, wtp, pt1) - then implicitArgs(formals, argIndex, pt1) - else arg :: implicitArgs(formals1, argIndex + 1, pt1) + if (pt1 `ne` pt) && (pt1 ne sharpenedPt) && tryConstrainResult(pt1) then + return implicitArgs(formals, argIndex, pt1) + case _ => + + arg.tpe match + case failed: AmbiguousImplicits => + arg :: implicitArgs(formals1, argIndex + 1, pt) case failed: SearchFailureType => - 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 - // is that we need to know whether there are further errors. - // If there are none, we have to propagate the ambiguity to the caller. - arg :: formals1.map(dummyArg) - else + if argHasDefault then // This is tricky. On the one hand, we need the defaultArg to // correctly type subsequent formal parameters in the same using // clause in case there are parameter dependencies. On the other hand, @@ -4277,6 +4426,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // `if propFail.exists` where we re-type the whole using clause with named // arguments for all implicits that were found. arg :: inferArgsAfter(defaultArg) + else + // no need to search further, the adapt fails in any case + // the reason why we continue inferring arguments in case of an AmbiguousImplicits + // is that we need to know whether there are further errors. + // If there are none, we have to propagate the ambiguity to the caller. + arg :: formals1.map(dummyArg) case _ => arg :: inferArgsAfter(arg) end implicitArgs @@ -4477,15 +4632,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer true } - def isRetainsArg(pt: Type) = pt match - case AnnotatedType(arg, annot) => annot.symbol == defn.RetainsArgAnnot - case _ => false - if (implicitFun || caseCompanion) && !isApplyProto(pt) && pt != SingletonTypeProto && pt != LhsProto - && !isRetainsArg(pt) && !ctx.mode.is(Mode.Pattern) && !tree.isInstanceOf[SplicePattern] && !ctx.isAfterTyper @@ -4526,7 +4676,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer tree } else TypeComparer.testSubType(tree.tpe.widenExpr, pt) match - case CompareResult.Fail => + case CompareResult.Fail(_) => wtp match case wtp: MethodType => missingArgs(wtp) case _ => @@ -4663,7 +4813,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer && !ctx.isAfterTyper && !tree.isInstanceOf[Inlined] && !isThisTypeResult(tree) - && !tree.hasAttachment(AscribedToUnit) then + && !isAscribedToUnit(tree) + then report.warning(ValueDiscarding(tree.tpe), tree.srcPos) return tpd.Block(tree1 :: Nil, unitLiteral) diff --git a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala index 0c2929283ee3..354f09382d82 100644 --- a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -178,7 +178,20 @@ class VarianceChecker(using Context) { i"\n${hl("enum case")} ${towner.name} requires explicit declaration of $tvar to resolve this issue.\n$example" else "" - em"${varianceLabel(tvar.flags)} $tvar occurs in ${varianceLabel(required)} position in type ${sym.info} of $sym$enumAddendum" + val privateParamAddendum = + if sym.flags.is(ParamAccessor) && sym.flags.is(Private) then + val varOrVal = if sym.is(Mutable) then "var" else "val" + val varFieldInstead = if sym.is(Mutable) then " and add\na field inside the class instead" else "" + s""" + | + |Implementation limitation: ${hl(f"private $varOrVal")} parameters cannot be inferred to be local + |and therefore are always variance-checked. + | + |Potential fix: remove the ${hl(f"private $varOrVal")} modifiers on the parameter ${sym.name}$varFieldInstead. + """.stripMargin + else + "" + em"${varianceLabel(tvar.flags)} $tvar occurs in ${varianceLabel(required)} position in type ${sym.info} of $sym$enumAddendum$privateParamAddendum" if (migrateTo3 && (sym.owner.isConstructor || sym.ownersIterator.exists(_.isAllOf(ProtectedLocal)))) report.migrationWarning( diff --git a/compiler/src/dotty/tools/dotc/util/DiffUtil.scala b/compiler/src/dotty/tools/dotc/util/DiffUtil.scala index 31acc91caa2e..126cff9b9c65 100644 --- a/compiler/src/dotty/tools/dotc/util/DiffUtil.scala +++ b/compiler/src/dotty/tools/dotc/util/DiffUtil.scala @@ -103,7 +103,6 @@ object DiffUtil { case Deleted(str) => deleted(str) }.mkString - (expectedDiff, actualDiff) val pad = " " * 0.max(expectedSize - expected.length) expectedDiff + pad + " | " + actualDiff diff --git a/compiler/src/dotty/tools/dotc/util/LRUCache.scala b/compiler/src/dotty/tools/dotc/util/LRUCache.scala index e124159575e7..4abe8962ae5b 100644 --- a/compiler/src/dotty/tools/dotc/util/LRUCache.scala +++ b/compiler/src/dotty/tools/dotc/util/LRUCache.scala @@ -1,6 +1,5 @@ package dotty.tools.dotc.util -import scala.language.unsafeNulls import reflect.ClassTag import annotation.tailrec @@ -17,7 +16,7 @@ import annotation.tailrec * get promoted to be first in the queue. Elements are evicted * at the `last` position. */ -class LRUCache[Key >: Null <: AnyRef : ClassTag, Value >: Null: ClassTag] { +class LRUCache[Key >: Null <: AnyRef | Null : ClassTag, Value >: Null: ClassTag] { import LRUCache.* val keys: Array[Key] = new Array[Key](Retained) val values: Array[Value] = new Array(Retained) diff --git a/compiler/src/dotty/tools/dotc/util/NameTransformer.scala b/compiler/src/dotty/tools/dotc/util/NameTransformer.scala index f641ea90dcdd..1d8ef6150e46 100644 --- a/compiler/src/dotty/tools/dotc/util/NameTransformer.scala +++ b/compiler/src/dotty/tools/dotc/util/NameTransformer.scala @@ -2,7 +2,6 @@ package dotty.tools package dotc package util -import scala.language.unsafeNulls import core.Names.* @@ -55,7 +54,7 @@ object NameTransformer { * the Scala spec as well as the Java spec. */ def encode(name: SimpleName): SimpleName = { - var buf: StringBuilder = null + var buf: StringBuilder | Null = null val len = name.length var i = 0 while (i < len) { @@ -88,11 +87,11 @@ object NameTransformer { */ def decode(name: SimpleName): SimpleName = { //System.out.println("decode: " + name);//DEBUG - var buf: StringBuilder = null + var buf: StringBuilder | Null = null val len = name.length var i = 0 while (i < len) { - var ops: OpCodes = null + var ops: OpCodes | Null = null var unicode = false val c = name(i) if (c == '$' && i + 2 < len) { diff --git a/compiler/src/dotty/tools/dotc/util/ParsedComment.scala b/compiler/src/dotty/tools/dotc/util/ParsedComment.scala index ee56a74d5eb5..acced8dd857c 100644 --- a/compiler/src/dotty/tools/dotc/util/ParsedComment.scala +++ b/compiler/src/dotty/tools/dotc/util/ParsedComment.scala @@ -1,7 +1,5 @@ package dotty.tools.dotc.util -import scala.language.unsafeNulls - import dotty.tools.dotc.core.Comments.{Comment, docCtx} import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.Names.TermName diff --git a/compiler/src/dotty/tools/dotc/util/ShowPickled.scala b/compiler/src/dotty/tools/dotc/util/ShowPickled.scala index 8d5d0c27ab0e..15d553e703b3 100644 --- a/compiler/src/dotty/tools/dotc/util/ShowPickled.scala +++ b/compiler/src/dotty/tools/dotc/util/ShowPickled.scala @@ -113,7 +113,7 @@ object ShowPickled { result.toInt } - def printFile(buf: PickleBuffer, out: PrintStream = System.out.nn): Unit = { + def printFile(buf: PickleBuffer, out: PrintStream = System.out): Unit = { out.println("Version " + buf.readNat() + "." + buf.readNat()) val index = buf.createIndex val entryList = makeEntryList(buf, index) diff --git a/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala index 714e3a5fc0d6..1d8dc484c989 100644 --- a/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala +++ b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala @@ -1,6 +1,5 @@ package dotty.tools.dotc.util -import scala.language.unsafeNulls import collection.mutable @@ -54,7 +53,10 @@ abstract class SimpleIdentitySet[+Elem <: AnyRef] { else this.filter(that.contains) def == [E >: Elem <: AnyRef](that: SimpleIdentitySet[E]): Boolean = - this.size == that.size && forall(that.contains) + (this eq that) || this.size == that.size && forall(that.contains) + + def != [E >: Elem <: AnyRef](that: SimpleIdentitySet[E]): Boolean = + !(this == that) override def toString: String = toList.mkString("{", ", ", "}") } @@ -219,7 +221,7 @@ object SimpleIdentitySet { override def ++ [E >: Elem <: AnyRef](that: SimpleIdentitySet[E]): SimpleIdentitySet[E] = that match { case that: SetN[?] => - var toAdd: mutable.ArrayBuffer[AnyRef] = null + var toAdd: mutable.ArrayBuffer[AnyRef] | Null = null var i = 0 val limit = that.xs.length while (i < limit) { @@ -249,7 +251,7 @@ object SimpleIdentitySet { case that: SetN[?] => // both sets are large, optimize assuming they are similar // by starting from empty set and adding elements - var toAdd: mutable.ArrayBuffer[AnyRef] = null + var toAdd: mutable.ArrayBuffer[AnyRef] | Null = null val thisSize = this.size val thatSize = that.size val thatElems = that.xs diff --git a/compiler/src/dotty/tools/dotc/util/SourceFile.scala b/compiler/src/dotty/tools/dotc/util/SourceFile.scala index 1c264b395689..9cc3dc5e731d 100644 --- a/compiler/src/dotty/tools/dotc/util/SourceFile.scala +++ b/compiler/src/dotty/tools/dotc/util/SourceFile.scala @@ -7,6 +7,7 @@ import scala.language.unsafeNulls import dotty.tools.io.* import Spans.* import core.Contexts.* +import core.Decorators.* import scala.io.Codec import Chars.* @@ -61,6 +62,36 @@ object ScriptSourceFile { } } +object WrappedSourceFile: + enum MagicHeaderInfo: + case HasHeader(offset: Int, originalFile: SourceFile) + case NoHeader + import MagicHeaderInfo.* + + private val cache: mutable.HashMap[SourceFile, MagicHeaderInfo] = mutable.HashMap.empty + + def locateMagicHeader(sourceFile: SourceFile)(using Context): MagicHeaderInfo = + def findOffset: MagicHeaderInfo = + val magicHeader = ctx.settings.YmagicOffsetHeader.value + if magicHeader.isEmpty then NoHeader + else + val text = new String(sourceFile.content) + val headerQuoted = java.util.regex.Pattern.quote("///" + magicHeader) + val regex = s"(?m)^$headerQuoted:(.+)$$".r + regex.findFirstMatchIn(text) match + case Some(m) => + val markerOffset = m.start + val sourceStartOffset = sourceFile.nextLine(markerOffset) + val file = ctx.getFile(m.group(1)) + if file.exists then + HasHeader(sourceStartOffset, ctx.getSource(file)) + else + report.warning(em"original source file not found: ${file.path}") + NoHeader + case None => NoHeader + val result = cache.getOrElseUpdate(sourceFile, findOffset) + result + class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends interfaces.SourceFile { import SourceFile.* @@ -229,8 +260,7 @@ object SourceFile { * It relies on SourceFile#virtual implementation to create the virtual file. */ def virtual(uri: URI, content: String): SourceFile = - val path = Paths.get(uri).toString - SourceFile.virtual(path, content) + SourceFile(new VirtualFile(Paths.get(uri), content.getBytes(StandardCharsets.UTF_8)), content.toCharArray) /** Returns the relative path of `source` within the `reference` path * diff --git a/compiler/src/dotty/tools/dotc/util/SourcePosition.scala b/compiler/src/dotty/tools/dotc/util/SourcePosition.scala index a7358755043c..e4f56bd85c0b 100644 --- a/compiler/src/dotty/tools/dotc/util/SourcePosition.scala +++ b/compiler/src/dotty/tools/dotc/util/SourcePosition.scala @@ -2,7 +2,6 @@ package dotty.tools package dotc package util -import scala.language.unsafeNulls import printing.{Showable, Printer} import printing.Texts.* @@ -67,7 +66,7 @@ extends SrcPos, interfaces.SourcePosition, Showable { def toSynthetic: SourcePosition = withSpan(span.toSynthetic) def outermost: SourcePosition = - if outer == null || outer == NoSourcePosition then this else outer.outermost + if (outer eq null) || outer == NoSourcePosition then this else outer.outermost /** Inner most position that is contained within the `outermost` position. * Most precise position that comes from the call site. @@ -86,7 +85,7 @@ extends SrcPos, interfaces.SourcePosition, Showable { } /** A sentinel for a non-existing source position */ -@sharable object NoSourcePosition extends SourcePosition(NoSource, NoSpan, null) { +@sharable object NoSourcePosition extends SourcePosition(NoSource, NoSpan) { override def line: Int = -1 override def column: Int = -1 override def toString: String = "?" diff --git a/compiler/src/dotty/tools/dotc/util/Spans.scala b/compiler/src/dotty/tools/dotc/util/Spans.scala index 7d4bbe0e8180..33346ad6da17 100644 --- a/compiler/src/dotty/tools/dotc/util/Spans.scala +++ b/compiler/src/dotty/tools/dotc/util/Spans.scala @@ -42,19 +42,19 @@ object Spans { /** The start of this span. */ def start: Int = { - assert(exists) + assert(exists, "start of NoSpan") (coords & StartEndMask).toInt } /** The end of this span */ def end: Int = { - assert(exists) + assert(exists, "end of NoSpan") ((coords >>> StartEndBits) & StartEndMask).toInt } /** The point of this span, returns start for synthetic spans */ def point: Int = { - assert(exists) + assert(exists, "point of NoSpan") val poff = pointDelta if (poff == SyntheticPointDelta) start else start + poff } diff --git a/compiler/src/dotty/tools/io/AbstractFile.scala b/compiler/src/dotty/tools/io/AbstractFile.scala index ee72297c2a4f..1bc9c051cdc6 100644 --- a/compiler/src/dotty/tools/io/AbstractFile.scala +++ b/compiler/src/dotty/tools/io/AbstractFile.scala @@ -115,7 +115,7 @@ abstract class AbstractFile extends Iterable[AbstractFile] { def container : AbstractFile /** Returns the underlying File if any and null otherwise. */ - def file: JFile = try { + def file: JFile | Null = try { if (jpath == null) null else jpath.toFile } catch { @@ -123,7 +123,7 @@ abstract class AbstractFile extends Iterable[AbstractFile] { } /** Returns the underlying Path if any and null otherwise. */ - def jpath: JPath + def jpath: JPath | Null /** An underlying source, if known. Mostly, a zip/jar file. */ def underlyingSource: Option[AbstractFile] = None @@ -196,12 +196,12 @@ abstract class AbstractFile extends Iterable[AbstractFile] { } /** Returns all abstract subfiles of this abstract directory. */ - def iterator(): Iterator[AbstractFile] + def iterator: Iterator[AbstractFile] /** Drill down through subdirs looking for the target, as in lookupName. * Ths target name is the last of parts. */ - final def lookupPath(parts: Seq[String], directory: Boolean): AbstractFile = + final def lookupPath(parts: Seq[String], directory: Boolean): AbstractFile | Null = var file: AbstractFile = this var i = 0 val n = parts.length - 1 diff --git a/compiler/src/dotty/tools/io/File.scala b/compiler/src/dotty/tools/io/File.scala index 59e4a2ee451b..ea48966de6fb 100644 --- a/compiler/src/dotty/tools/io/File.scala +++ b/compiler/src/dotty/tools/io/File.scala @@ -8,8 +8,6 @@ package dotty.tools.io -import scala.language.unsafeNulls - import java.io.{File => JavaIoFile, _} import java.nio.file.{Files, Paths} import java.nio.file.StandardOpenOption.* diff --git a/compiler/src/dotty/tools/io/FileWriters.scala b/compiler/src/dotty/tools/io/FileWriters.scala index 5fdf43cfe8e3..849b80c5e745 100644 --- a/compiler/src/dotty/tools/io/FileWriters.scala +++ b/compiler/src/dotty/tools/io/FileWriters.scala @@ -66,7 +66,7 @@ object FileWriters { def warning(message: Context ?=> Message): Unit = warning(message, NoSourcePosition) final def exception(reason: Context ?=> Message, throwable: Throwable): Unit = error({ - val trace = throwable.getStackTrace().nn.mkString("\n ") + val trace = throwable.getStackTrace().mkString("\n ") em"An unhandled exception was thrown in the compiler while\n ${reason.message}.\n${throwable}\n $trace" }, NoSourcePosition) } @@ -189,7 +189,7 @@ object FileWriters { def close(): Unit protected def classToRelativePath(className: InternalName): String = - className.replace('.', '/').nn + ".tasty" + className.replace('.', '/') + ".tasty" } object TastyWriter { @@ -234,7 +234,7 @@ object FileWriters { new JarEntryWriter(jarFile, jarManifestMainClass, jarCompressionLevel) } else if (file.isVirtual) new VirtualFileWriter(file) - else if (file.isDirectory) new DirEntryWriter(file.file.toPath.nn) + else if (file.isDirectory) new DirEntryWriter(file.file.toPath) else throw new IllegalStateException(s"don't know how to handle an output of $file [${file.getClass}]") } @@ -248,7 +248,7 @@ object FileWriters { val jarWriter: JarOutputStream = { import scala.util.Properties.* val manifest = new Manifest - val attrs = manifest.getMainAttributes.nn + val attrs = manifest.getMainAttributes attrs.put(MANIFEST_VERSION, "1.0") attrs.put(ScalaCompilerVersion, versionNumberString) mainClass.foreach(c => attrs.put(MAIN_CLASS, c)) diff --git a/compiler/src/dotty/tools/io/Path.scala b/compiler/src/dotty/tools/io/Path.scala index 6f97e03ca4d7..64137d691898 100644 --- a/compiler/src/dotty/tools/io/Path.scala +++ b/compiler/src/dotty/tools/io/Path.scala @@ -5,8 +5,6 @@ package dotty.tools.io -import scala.language.unsafeNulls - import java.io.RandomAccessFile import java.nio.file.* import java.net.{URI, URL} @@ -46,7 +44,7 @@ object Path { def fileName(name: String): String = { val i = name.lastIndexOf('.') if (i < 0) name - else name.substring(0, i).nn + else name.substring(0, i) } def onlyDirs(xs: Iterator[Path]): Iterator[Directory] = xs.filter(_.isDirectory).map(_.toDirectory) @@ -247,12 +245,12 @@ class Path private[io] (val jpath: JPath) { if (!exists) false else { Files.walkFileTree(jpath, new SimpleFileVisitor[JPath]() { - override def visitFile(file: JPath, attrs: BasicFileAttributes) = { + override def visitFile(file: JPath, attrs: BasicFileAttributes): FileVisitResult = { Files.delete(file) FileVisitResult.CONTINUE } - override def postVisitDirectory(dir: JPath, exc: IOException) = { + override def postVisitDirectory(dir: JPath, exc: IOException): FileVisitResult = { Files.delete(dir) FileVisitResult.CONTINUE } diff --git a/compiler/src/dotty/tools/io/PlainFile.scala b/compiler/src/dotty/tools/io/PlainFile.scala index a6a39d9ff3eb..ff637c913cd8 100644 --- a/compiler/src/dotty/tools/io/PlainFile.scala +++ b/compiler/src/dotty/tools/io/PlainFile.scala @@ -14,7 +14,7 @@ import java.nio.file.{InvalidPathException, Paths} /** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */ class PlainDirectory(givenPath: Directory) extends PlainFile(givenPath) { override val isDirectory: Boolean = true - override def iterator(): Iterator[PlainFile] = givenPath.list.filter(_.exists).map(new PlainFile(_)) + override def iterator: Iterator[PlainFile] = givenPath.list.filter(_.exists).map(new PlainFile(_)) } /** This class implements an abstract file backed by a File. diff --git a/compiler/src/dotty/tools/io/Streamable.scala b/compiler/src/dotty/tools/io/Streamable.scala index 328ce03f7853..fd72854a9127 100644 --- a/compiler/src/dotty/tools/io/Streamable.scala +++ b/compiler/src/dotty/tools/io/Streamable.scala @@ -5,7 +5,6 @@ package dotty.tools.io -import scala.language.unsafeNulls import java.net.URL import java.io.{ BufferedInputStream, InputStream } diff --git a/compiler/src/dotty/tools/io/VirtualDirectory.scala b/compiler/src/dotty/tools/io/VirtualDirectory.scala index 949f2d0e61dd..28c4bc308cae 100644 --- a/compiler/src/dotty/tools/io/VirtualDirectory.scala +++ b/compiler/src/dotty/tools/io/VirtualDirectory.scala @@ -43,7 +43,7 @@ extends AbstractFile { // the toList is so that the directory may continue to be // modified while its elements are iterated - def iterator(): Iterator[AbstractFile] = files.values.toList.iterator + def iterator: Iterator[AbstractFile] = files.values.toList.iterator override def lookupName(name: String, directory: Boolean): AbstractFile = (files get name filter (_.isDirectory == directory)).orNull diff --git a/compiler/src/dotty/tools/io/VirtualFile.scala b/compiler/src/dotty/tools/io/VirtualFile.scala index 6fb9859503f2..93788990d368 100644 --- a/compiler/src/dotty/tools/io/VirtualFile.scala +++ b/compiler/src/dotty/tools/io/VirtualFile.scala @@ -40,15 +40,34 @@ class VirtualFile(val name: String, override val path: String) extends AbstractF this.content = content } + /** + * Initializes this instance with the specified path + * and a name taken from the last path element. + * + * @param path the path of the virtual file to be created + * @param content the initial contents of the virtual file + * @return the created virtual file + */ + def this(path: JPath, content: Array[Byte]) = { + this(path.getFileName().toString(), path.toString()) + this.content = content + this.jpath_ = path + } + private var content = Array.emptyByteArray + private var jpath_ : JPath = null + def absolute: AbstractFile = this - /** Returns null. */ - def jpath: JPath = null + /** Returns path, which might be a non-existing file or null. */ + def jpath: JPath = jpath_ override def sizeOption: Option[Int] = Some(content.length) + /** Always returns true, even if jpath is a non-existing file. */ + override def exists: Boolean = true + def input : InputStream = new ByteArrayInputStream(content) override def output: OutputStream = { diff --git a/compiler/src/dotty/tools/io/ZipArchive.scala b/compiler/src/dotty/tools/io/ZipArchive.scala index a23bde8faaed..1ee79bd8036a 100644 --- a/compiler/src/dotty/tools/io/ZipArchive.scala +++ b/compiler/src/dotty/tools/io/ZipArchive.scala @@ -220,7 +220,7 @@ final class FileZipArchive(jpath: JPath, release: Option[String]) extends ZipArc } final class ManifestResources(val url: URL) extends ZipArchive(null, None) { - def iterator(): Iterator[AbstractFile] = { + def iterator: Iterator[AbstractFile] = { val root = new DirEntry("/", null) val dirs = mutable.HashMap[String, DirEntry]("/" -> root) val stream = input diff --git a/compiler/src/dotty/tools/repl/AbstractFileClassLoader.scala b/compiler/src/dotty/tools/repl/AbstractFileClassLoader.scala index 7a457a1d7546..1796a7dc68b5 100644 --- a/compiler/src/dotty/tools/repl/AbstractFileClassLoader.scala +++ b/compiler/src/dotty/tools/repl/AbstractFileClassLoader.scala @@ -26,7 +26,7 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) exten // on JDK 20 the URL constructor we're using is deprecated, // but the recommended replacement, URL.of, doesn't exist on JDK 8 @annotation.nowarn("cat=deprecation") - override protected def findResource(name: String) = + override protected def findResource(name: String): URL | Null = findAbstractFile(name) match case null => null case file => new URL(null, s"memory:${file.path}", new URLStreamHandler { @@ -35,13 +35,13 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) exten override def getInputStream = file.input } }) - override protected def findResources(name: String) = + override protected def findResources(name: String): java.util.Enumeration[URL] = findResource(name) match case null => Collections.enumeration(Collections.emptyList[URL]) //Collections.emptyEnumeration[URL] case url => Collections.enumeration(Collections.singleton(url)) override def findClass(name: String): Class[?] = { - var file: AbstractFile = root + var file: AbstractFile | Null = root val pathParts = name.split("[./]").toList for (dirPart <- pathParts.init) { file = file.lookupName(dirPart, true) diff --git a/compiler/src/dotty/tools/repl/JLineTerminal.scala b/compiler/src/dotty/tools/repl/JLineTerminal.scala index e4ac1626525e..2680704d326e 100644 --- a/compiler/src/dotty/tools/repl/JLineTerminal.scala +++ b/compiler/src/dotty/tools/repl/JLineTerminal.scala @@ -106,7 +106,7 @@ class JLineTerminal extends java.io.Closeable { ) extends reader.ParsedLine { // Using dummy values, not sure what they are used for def wordIndex = -1 - def words = java.util.Collections.emptyList[String] + def words: java.util.List[String] = java.util.Collections.emptyList[String] } def parse(input: String, cursor: Int, context: ParseContext): reader.ParsedLine = { diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index 087eb836dfcb..89b2491d9af8 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -12,7 +12,7 @@ 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.{CheckUnused, CheckShadowing, PostTyper} +import dotty.tools.dotc.transform.{CheckUnused, CheckShadowing, PostTyper, UnrollDefinitions} import dotty.tools.dotc.typer.ImportInfo.{withRootImports, RootRef} import dotty.tools.dotc.typer.TyperPhase import dotty.tools.dotc.util.Spans.* @@ -40,6 +40,7 @@ class ReplCompiler extends Compiler: List(CheckUnused.PostTyper(), CheckShadowing()), List(CollectTopLevelImports()), List(PostTyper()), + List(UnrollDefinitions()), ) def newRun(initCtx: Context, state: State): Run = diff --git a/compiler/src/dotty/tools/repl/ScriptEngine.scala b/compiler/src/dotty/tools/repl/ScriptEngine.scala index 7d385daa43e4..cce16000577f 100644 --- a/compiler/src/dotty/tools/repl/ScriptEngine.scala +++ b/compiler/src/dotty/tools/repl/ScriptEngine.scala @@ -64,20 +64,21 @@ class ScriptEngine extends AbstractScriptEngine { object ScriptEngine { import java.util.Arrays + import java.util.List import scala.util.Properties class Factory extends ScriptEngineFactory { def getEngineName = "Scala REPL" def getEngineVersion = "3.0" - def getExtensions = Arrays.asList("scala") + def getExtensions: List[String] = Arrays.asList("scala") def getLanguageName = "Scala" def getLanguageVersion = Properties.versionString - def getMimeTypes = Arrays.asList("application/x-scala") - def getNames = Arrays.asList("scala") + def getMimeTypes: List[String] = Arrays.asList("application/x-scala") + def getNames: List[String] = Arrays.asList("scala") - def getMethodCallSyntax(obj: String, m: String, args: String*) = s"$obj.$m(${args.mkString(", ")})" + def getMethodCallSyntax(obj: String, m: String, args: String*): String = s"$obj.$m(${args.mkString(", ")})" - def getOutputStatement(toDisplay: String) = s"""print("$toDisplay")""" + def getOutputStatement(toDisplay: String): String = s"""print("$toDisplay")""" def getParameter(key: String): Object = key match { case JScriptEngine.ENGINE => getEngineName @@ -88,7 +89,7 @@ object ScriptEngine { case _ => null } - def getProgram(statements: String*) = statements.mkString("; ") + def getProgram(statements: String*): String = statements.mkString("; ") def getScriptEngine: JScriptEngine = new ScriptEngine } diff --git a/compiler/src/dotty/tools/runner/ScalaClassLoader.scala b/compiler/src/dotty/tools/runner/ScalaClassLoader.scala index a8cd36cba6bd..0cb31189be5f 100644 --- a/compiler/src/dotty/tools/runner/ScalaClassLoader.scala +++ b/compiler/src/dotty/tools/runner/ScalaClassLoader.scala @@ -1,7 +1,6 @@ package dotty.tools package runner -import scala.language.unsafeNulls import java.lang.ClassLoader import java.lang.invoke.{MethodHandles, MethodType} @@ -64,7 +63,7 @@ object ScalaClassLoader { def fromURLsParallelCapable(urls: Seq[URL], parent: ClassLoader | Null = null): URLClassLoader = new URLClassLoader(urls.toArray, if parent == null then bootClassLoader else parent) - @sharable private val bootClassLoader: ClassLoader = + @sharable private val bootClassLoader: ClassLoader | Null = if scala.util.Properties.isJavaAtLeast("9") then try ClassLoader.getSystemClassLoader.getParent diff --git a/compiler/src/dotty/tools/scripting/Main.scala b/compiler/src/dotty/tools/scripting/Main.scala index 0b954cb79b26..16e2fe35e6f1 100755 --- a/compiler/src/dotty/tools/scripting/Main.scala +++ b/compiler/src/dotty/tools/scripting/Main.scala @@ -1,7 +1,5 @@ package dotty.tools.scripting -import scala.language.unsafeNulls - import java.io.File import java.nio.file.{Path, Paths} import dotty.tools.dotc.config.Properties.isWin diff --git a/compiler/src/dotty/tools/scripting/StringDriver.scala b/compiler/src/dotty/tools/scripting/StringDriver.scala index eccd8445c87d..6d4fac719be2 100755 --- a/compiler/src/dotty/tools/scripting/StringDriver.scala +++ b/compiler/src/dotty/tools/scripting/StringDriver.scala @@ -1,7 +1,5 @@ package dotty.tools.scripting -import scala.language.unsafeNulls - import java.nio.file.{ Files, Paths, Path } import dotty.tools.dotc.Driver diff --git a/compiler/src/dotty/tools/scripting/Util.scala b/compiler/src/dotty/tools/scripting/Util.scala index fbbeb13ca13c..99af967143d3 100755 --- a/compiler/src/dotty/tools/scripting/Util.scala +++ b/compiler/src/dotty/tools/scripting/Util.scala @@ -1,7 +1,5 @@ package dotty.tools.scripting -import scala.language.unsafeNulls - import java.nio.file.{ Path } import java.io.File import java.net.{ URLClassLoader } diff --git a/compiler/src/dotty/tools/tasty/besteffort/BestEffortTastyFormat.scala b/compiler/src/dotty/tools/tasty/besteffort/BestEffortTastyFormat.scala index 99a24ce5f346..41876016b4ec 100644 --- a/compiler/src/dotty/tools/tasty/besteffort/BestEffortTastyFormat.scala +++ b/compiler/src/dotty/tools/tasty/besteffort/BestEffortTastyFormat.scala @@ -38,7 +38,7 @@ object BestEffortTastyFormat { // added AST tag - Best Effort TASTy only final val ERRORtype = 50 - def astTagToString(tag: Int) = tag match { + def astTagToString(tag: Int): String = tag match { case ERRORtype => "ERRORtype" case _ => TastyFormat.astTagToString(tag) } diff --git a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala index 3790174526b3..221bcfc71c91 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala @@ -448,7 +448,7 @@ class QuoteMatcher(debug: Boolean) { def matchErasedParams(sctype: Type, pttype: Type): optional[MatchingExprs] = (sctype, pttype) match case (sctpe: MethodType, pttpe: MethodType) => - if sctpe.erasedParams.sameElements(pttpe.erasedParams) then + if sctpe.paramErasureStatuses.sameElements(pttpe.paramErasureStatuses) then matchErasedParams(sctpe.resType, pttpe.resType) else notMatched @@ -460,10 +460,10 @@ class QuoteMatcher(debug: Boolean) { */ def matchTypeDef(sctypedef: TypeDef, pttypedef: TypeDef): MatchingExprs = sctypedef match case TypeDef(_, TypeBoundsTree(sclo, schi, EmptyTree)) - if sclo.tpe == defn.NothingType && schi.tpe == defn.AnyType => + if sclo.tpe.isNothingType && schi.tpe.isAny => pttypedef match case TypeDef(_, TypeBoundsTree(ptlo, pthi, EmptyTree)) - if sclo.tpe == defn.NothingType && schi.tpe == defn.AnyType => + if sclo.tpe.isNothingType && schi.tpe.isAny => matched case _ => notMatched case _ => notMatched diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index a93e010ddc34..212c7173fec3 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -11,6 +11,7 @@ import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.Decorators.* import dotty.tools.dotc.core.NameKinds import dotty.tools.dotc.core.NameOps.* +import dotty.tools.dotc.core.Scopes.* import dotty.tools.dotc.core.StdNames.* import dotty.tools.dotc.core.Types import dotty.tools.dotc.NoCompilationUnit @@ -2287,7 +2288,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler case _ => MethodTypeKind.Plain def param(idx: Int): TypeRepr = self.newParamRef(idx) - def erasedParams: List[Boolean] = self.erasedParams + def erasedParams: List[Boolean] = self.paramErasureStatuses def hasErasedParams: Boolean = self.hasErasedParams end extension end MethodTypeMethods @@ -2529,7 +2530,12 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end StringConstantTypeTest object StringConstant extends StringConstantModule: - def apply(x: String): StringConstant = dotc.core.Constants.Constant(x) + def apply(x: String): StringConstant = + require(x != null, "value of StringConstant cannot be `null`") + // A `null` constant must be represented as a `NullConstant`, c.f. a + // constant with `tag == NullTag`, which is not a `StringConstant`. + // See issue 23008. + dotc.core.Constants.Constant(x) def unapply(constant: StringConstant): Some[String] = Some(constant.stringValue) end StringConstant @@ -2818,7 +2824,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler modFlags | dotc.core.Flags.ModuleValCreationFlags, clsFlags | dotc.core.Flags.ModuleClassCreationFlags, parents, - dotc.core.Scopes.newScope, + newScope, privateWithin, NoCoord, compUnitInfo = null @@ -2834,7 +2840,9 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler xCheckMacroAssert(!privateWithin.exists || privateWithin.isType, "privateWithin must be a type symbol or `Symbol.noSymbol`") val privateWithin1 = if privateWithin.isTerm then Symbol.noSymbol else privateWithin checkValidFlags(flags.toTermFlags, Flags.validMethodFlags) - dotc.core.Symbols.newSymbol(owner, name.toTermName, flags | dotc.core.Flags.Method, tpe, privateWithin1) + val method = dotc.core.Symbols.newSymbol(owner, name.toTermName, flags | dotc.core.Flags.Method, tpe, privateWithin1) + method.setParamss(method.paramSymss) + method def newVal(owner: Symbol, name: String, tpe: TypeRepr, flags: Flags, privateWithin: Symbol): Symbol = xCheckMacroAssert(!privateWithin.exists || privateWithin.isType, "privateWithin must be a type symbol or `Symbol.noSymbol`") val privateWithin1 = if privateWithin.isTerm then Symbol.noSymbol else privateWithin @@ -3010,7 +3018,33 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def memberTypes: List[Symbol] = self.typeRef.decls.filter(_.isType) def typeMembers: List[Symbol] = - lookupPrefix.typeMembers.map(_.symbol).toList + // lookupPrefix.typeMembers currently returns a Set wrapped into a unsorted Seq, + // so we try to sort that here (see discussion: https://github.com/scala/scala3/issues/22472), + // without adding too much of a performance hit. + // It first sorts by parents, then for type params by their positioning, then for members + // derived from declarations it sorts them by their name lexicographically + val parentsMap = lookupPrefix.sortedParents.map(_.typeSymbol).zipWithIndex.toList.toMap + val unsortedTypeMembers = lookupPrefix.typeMembers.map(_.symbol).filter(_.exists).toList + unsortedTypeMembers.sortWith { + case (typeA, typeB) => + val msg = "Unknown type member found. Please consider reporting the issue to the compiler. " + assert(parentsMap.contains(typeA.owner), msg) + assert(parentsMap.contains(typeB.owner), msg) + val parentPlacementA = parentsMap(typeA.owner) + val parentPlacementB = parentsMap(typeB.owner) + if (parentPlacementA == parentPlacementB) then + if typeA.isTypeParam && typeB.isTypeParam then + // put type params at the beginning (and sort them by declaration order) + val pl = typeA.owner + val typeParamPositionMap = pl.typeParams.map(_.asInstanceOf[Symbol]).zipWithIndex.toMap + typeParamPositionMap(typeA) < typeParamPositionMap(typeB) + else if typeA.isTypeParam then true + else if typeB.isTypeParam then false + else + // sort by name lexicographically + typeA.name.toString().compareTo(typeB.name.toString()) < 0 + else parentPlacementA < parentPlacementB + }.map(_.asInstanceOf[Symbol]) def declarations: List[Symbol] = self.typeRef.info.decls.toList @@ -3061,7 +3095,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def asQuotes: Nested = assert(self.ownersIterator.contains(ctx.owner), s"$self is not owned by ${ctx.owner}") - new QuotesImpl(using ctx.withOwner(self)) + val newCtx = if ctx.owner eq self then ctx else + val newCtx = ctx.fresh.setOwner(self) + if !self.flags.is(Flags.Method) then newCtx + else newCtx.setScope(newScopeWith(self.paramSymss.flatten*)) + new QuotesImpl(using newCtx) end extension @@ -3178,6 +3216,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def Implicit: Flags = dotc.core.Flags.Implicit def Infix: Flags = dotc.core.Flags.Infix def Inline: Flags = dotc.core.Flags.Inline + def Into: Flags = dotc.core.Flags.Into def Invisible: Flags = dotc.core.Flags.Invisible def JavaDefined: Flags = dotc.core.Flags.JavaDefined def JavaStatic: Flags = dotc.core.Flags.JavaStatic @@ -3203,6 +3242,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def StableRealizable: Flags = dotc.core.Flags.StableRealizable @deprecated("Use JavaStatic instead", "3.3.0") def Static: Flags = dotc.core.Flags.JavaStatic def Synthetic: Flags = dotc.core.Flags.Synthetic + def Tracked: Flags = dotc.core.Flags.Tracked def Trait: Flags = dotc.core.Flags.Trait def Transparent: Flags = dotc.core.Flags.Transparent diff --git a/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala b/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala index 82be54a9d793..b43b6e23e8ca 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala @@ -1,6 +1,7 @@ package scala.quoted package runtime.impl.printers +import scala.collection.mutable import scala.quoted.* object Extractors { @@ -67,6 +68,12 @@ object Extractors { import quotes.reflect.* private val sb: StringBuilder = new StringBuilder + private var recTypeCounter = 0 + private val recTypeIds = mutable.Map.empty[RecursiveType, Int] + + private def nextRecTypeId(): Int = + recTypeCounter += 1 + recTypeCounter def result(): String = sb.result() @@ -226,9 +233,14 @@ object Extractors { case SuperType(thistpe, supertpe) => this += "SuperType(" += thistpe += ", " += supertpe += ")" case RecursiveThis(binder) => - this += "RecursiveThis(" += binder += ")" - case RecursiveType(underlying) => - this += "RecursiveType(" += underlying += ")" + val id = recTypeIds.getOrElse(binder, -1) + if (id == -1) + this += "RecursiveThis(" += binder += ")" + else + this += "RecursiveThis( + val id = recTypeIds.getOrElseUpdate(rt, nextRecTypeId()) + this += "RecursiveType(rec" += id += " => " += underlying += ")" case MethodType(argNames, argTypes, resType) => this += "MethodType(" ++= argNames += ", " ++= argTypes += ", " += resType += ")" case PolyType(argNames, argBounds, resType) => diff --git a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala index 7c7a688eccb2..6e0d65bc044e 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala @@ -1454,7 +1454,7 @@ object SourceCode { case '"' => "\\\"" case '\'' => "\\\'" case '\\' => "\\\\" - case _ => if ch.isControl then f"${"\\"}u${ch.toInt}%04x" else String.valueOf(ch).nn + case _ => if ch.isControl then f"${"\\"}u${ch.toInt}%04x" else String.valueOf(ch) } private def escapedString(str: String): String = str flatMap escapedChar @@ -1470,7 +1470,7 @@ object SourceCode { namesIndex(name0) = index + 1 val name = if index == 1 then name0 - else s"`$name0${index.toString.toCharArray.nn.map {x => (x - '0' + '₀').toChar}.mkString}`" + else s"`$name0${index.toString.toCharArray.map {x => (x - '0' + '₀').toChar}.mkString}`" names(sym) = name Some(name) } diff --git a/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala b/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala index 86b22009d15a..f44774141947 100644 --- a/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala +++ b/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala @@ -159,7 +159,7 @@ object CoursierScalaTests: def csScalaCmd(options: String*): List[String] = csScalaCmdWithStdin(options, None) - + def csScalaCmdWithStdin(options: Seq[String], stdin: Option[String]): List[String] = csCmd("dotty.tools.MainGenericRunner", options, stdin) diff --git a/compiler/test-resources/type-printer/test-definitions b/compiler/test-resources/type-printer/test-definitions index cdda5f65cb0e..6566496d3181 100644 --- a/compiler/test-resources/type-printer/test-definitions +++ b/compiler/test-resources/type-printer/test-definitions @@ -18,8 +18,3 @@ scala> trait E scala> implicit def x: Int = 1 def x: Int - -scala> import scala.language.experimental.erasedDefinitions - -scala> erased def y: Int = 1 -def y: Int diff --git a/compiler/test/dotc/neg-best-effort-unpickling.excludelist b/compiler/test/dotc/neg-best-effort-unpickling.excludelist index d57f7e0176e8..9e27e6133947 100644 --- a/compiler/test/dotc/neg-best-effort-unpickling.excludelist +++ b/compiler/test/dotc/neg-best-effort-unpickling.excludelist @@ -18,3 +18,15 @@ i18750.scala # Crash on invalid prefix ([A] =>> Int) i22357a.scala + +# `110 (of class java.lang.Integer)` +context-function-syntax.scala + +# Failure to disambiguate overloaded reference +i23402b.scala + +# Unhandled TypeError exception +i23504.scala + +# owner of anon, where package object has funky name +i20511-1.scala diff --git a/compiler/test/dotc/pos-test-pickling.excludelist b/compiler/test/dotc/pos-test-pickling.excludelist index 28bce963bfd1..9706f95cdfb9 100644 --- a/compiler/test/dotc/pos-test-pickling.excludelist +++ b/compiler/test/dotc/pos-test-pickling.excludelist @@ -139,6 +139,7 @@ parsercombinators-new-syntax.scala hylolib-deferred-given hylolib-cb hylolib +i23299.scala # typecheckErrors method unpickling i21415.scala diff --git a/compiler/test/dotc/run-test-pickling.excludelist b/compiler/test/dotc/run-test-pickling.excludelist index 1597487da668..3cc9bc5a4a9e 100644 --- a/compiler/test/dotc/run-test-pickling.excludelist +++ b/compiler/test/dotc/run-test-pickling.excludelist @@ -50,3 +50,6 @@ named-tuples-strawman-2.scala typeCheckErrors.scala i18150.scala i22968.scala + +# Pickling differences with local parameters export forwarders of methods with into parameters. But their external type is the same +Parser.scala diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala index 8a9611a9b165..baae40841508 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala @@ -133,6 +133,15 @@ trait DottyBytecodeTest { }, l.stringLines) } + def assertNoInvoke(m: MethodNode, receiver: String, method: String): Unit = + assertNoInvoke(instructionsFromMethod(m), receiver, method) + def assertNoInvoke(l: List[Instruction], receiver: String, method: String): Unit = { + assert(!l.exists { + case Invoke(_, `receiver`, `method`, _, _) => true + case _ => false + }, s"Found unexpected invoke of $receiver.$method in:\n${l.stringLines}") + } + def diffInstructions(isa: List[Instruction], isb: List[Instruction]): String = { val len = Math.max(isa.length, isb.length) val sb = new StringBuilder diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index e92c4c26adb8..2e48b33ec624 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala @@ -1606,6 +1606,28 @@ class DottyBytecodeTests extends DottyBytecodeTest { } } + @Test + def simpleTupleExtraction(): Unit = { + val code = + """class C { + | def f1(t: (Int, String)) = + | val (i, s) = t + | i + s.length + |} + """.stripMargin + checkBCode(code) { dir => + val c = loadClassNode(dir.lookupName("C.class", directory = false).input) + val f1 = getMethod(c, "f1") + assertNoInvoke(f1, "scala/Tuple2$", "apply") // no Tuple2.apply call + // no `new` instruction + val hasNew = instructionsFromMethod(f1).exists { + case Op(Opcodes.NEW) => true + case _ => false + } + assertFalse("f1 should not have NEW instruction", hasNew) + } + } + @Test def deprecation(): Unit = { val code = diff --git a/compiler/test/dotty/tools/backend/jvm/StringConcatTest.scala b/compiler/test/dotty/tools/backend/jvm/StringConcatTest.scala index 14352106c5e8..613e72b32e52 100644 --- a/compiler/test/dotty/tools/backend/jvm/StringConcatTest.scala +++ b/compiler/test/dotty/tools/backend/jvm/StringConcatTest.scala @@ -122,7 +122,7 @@ class StringConcatTest extends DottyBytecodeTest { chsq: java.lang.CharSequence, chrs: Array[Char]) = { val s1 = str + obj + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs - val s2 = String.valueOf(obj).nn + str + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs + val s2 = String.valueOf(obj) + str + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs s1 + "//" + s2 } def sbuf = { val r = new java.lang.StringBuffer(); r.append("sbuf"); r } diff --git a/compiler/test/dotty/tools/dotc/BestEffortOptionsTests.scala b/compiler/test/dotty/tools/dotc/BestEffortOptionsTests.scala index da5440331068..038ca914fb5f 100644 --- a/compiler/test/dotty/tools/dotc/BestEffortOptionsTests.scala +++ b/compiler/test/dotty/tools/dotc/BestEffortOptionsTests.scala @@ -2,44 +2,42 @@ package dotty package tools package dotc -import scala.concurrent.duration._ -import dotty.tools.vulpix._ -import org.junit.{ Test, AfterClass } +import dotty.tools.vulpix.* import reporting.TestReporter -import java.io.{File => JFile} +import scala.concurrent.duration.* import scala.language.unsafeNulls +import java.io.{File => JFile} +import org.junit.{AfterClass, Test} + class BestEffortOptionsTests { - import ParallelTesting._ - import vulpix.TestConfiguration._ - import BestEffortOptionsTests._ - import CompilationTest.aggregateTests + import ParallelTesting.* + import vulpix.TestConfiguration.* + import BestEffortOptionsTests.* // Since TASTy and beTASTy files are read in a lazy manner (only when referenced by the source .scala file) // we test by using the "-from-tasty" option. This guarantees that the tasty files will be read // (and that the Best Effort TASTy reader will be tested), but we unfortunately skip the useful // interactions a tree derived from beTASTy could have with other frontend phases. - @Test def negTestFromBestEffortTasty: Unit = { + @Test def negTestFromBestEffortTasty: Unit = // Can be reproduced with // > sbt - // > scalac --Ybest-effort -Xsemanticdb - // > scalac --from-tasty -Ywith-best-effort-tasty META_INF/best-effort/ + // > scalac -Ybest-effort -Xsemanticdb + // > scalac -from-tasty -Ywith-best-effort-tasty META_INF/best-effort/ - implicit val testGroup: TestGroup = TestGroup("negTestFromBestEffortTasty") + given TestGroup = TestGroup("negTestFromBestEffortTasty") compileBestEffortTastyInDir(s"tests${JFile.separator}neg", bestEffortBaselineOptions, picklingFilter = FileFilter.exclude(TestSources.negBestEffortPicklingExcludelisted), unpicklingFilter = FileFilter.exclude(TestSources.negBestEffortUnpicklingExcludelisted) ).checkNoCrash() - } // Tests an actual use case of this compilation mode, where symbol definitions of the downstream // projects depend on the best effort tasty files generated with the Best Effort dir option - @Test def bestEffortIntergrationTest: Unit = { - implicit val testGroup: TestGroup = TestGroup("bestEffortIntegrationTests") + @Test def bestEffortIntegrationTest: Unit = + given TestGroup = TestGroup("bestEffortIntegrationTests") compileBestEffortIntegration(s"tests${JFile.separator}best-effort", bestEffortBaselineOptions) .noCrashWithCompilingDependencies() - } } object BestEffortOptionsTests extends ParallelTesting { diff --git a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala index 23980508f17d..190f635b454b 100644 --- a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala @@ -155,11 +155,15 @@ class BootstrappedOnlyCompilationTests { @Test def picklingWithCompiler: Unit = { implicit val testGroup: TestGroup = TestGroup("testPicklingWithCompiler") + // Exclude this file from the test as it contains some changes that require scala 2.13.17 + // This filter can be dropped once we drop the dependency to Scala 2 (in 3.8.0) + val rlibscala3 = FileFilter.exclude(List("ScalaRunTime.scala")) + aggregateTests( compileDir("compiler/src/dotty/tools", picklingWithCompilerOptions, recursive = false), compileDir("compiler/src/dotty/tools/dotc", picklingWithCompilerOptions, recursive = false), compileDir("library/src/scala/runtime/function", picklingWithCompilerOptions), - compileFilesInDir("library/src/scala/runtime", picklingWithCompilerOptions), + compileFilesInDir("library/src/scala/runtime", picklingWithCompilerOptions, rlibscala3), compileFilesInDir("compiler/src/dotty/tools/backend/jvm", picklingWithCompilerOptions), compileDir("compiler/src/dotty/tools/dotc/ast", picklingWithCompilerOptions), compileDir("compiler/src/dotty/tools/dotc/core", picklingWithCompilerOptions, recursive = false), diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 93238647d1a3..cb8768a1cb81 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -37,7 +37,7 @@ class CompilationTests { compileFilesInDir("tests/pos-special/sourcepath/outer", defaultOptions.and("-sourcepath", "tests/pos-special/sourcepath")), compileFile("tests/pos-special/sourcepath/outer/nested/Test4.scala", defaultOptions.and("-sourcepath", "tests/pos-special/sourcepath")), compileFilesInDir("tests/pos-scala2", defaultOptions.and("-source", "3.0-migration")), - compileFilesInDir("tests/pos-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking")), + compileFilesInDir("tests/pos-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking", "-language:experimental.separationChecking")), compileFile("tests/pos-special/utf8encoded.scala", defaultOptions.and("-encoding", "UTF8")), compileFile("tests/pos-special/utf16encoded.scala", defaultOptions.and("-encoding", "UTF16")), compileDir("tests/pos-special/i18589", defaultOptions.and("-Wsafe-init").without("-Ycheck:all")), @@ -62,7 +62,7 @@ class CompilationTests { aggregateTests( compileFile("tests/rewrites/rewrites.scala", defaultOptions.and("-source", "3.0-migration").and("-rewrite", "-indent")), compileFile("tests/rewrites/rewrites3x.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), - compileFile("tests/rewrites/rewrites3x-fatal-warnings.scala", defaultOptions.and("-rewrite", "-source", "future-migration", "-Xfatal-warnings")), + compileFile("tests/rewrites/rewrites3x-fatal-warnings.scala", defaultOptions.and("-rewrite", "-source", "future-migration", "-Werror")), compileFile("tests/rewrites/i21394.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), compileFile("tests/rewrites/uninitialized-var.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), compileFile("tests/rewrites/with-type-operator.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), @@ -86,6 +86,10 @@ class CompilationTests { compileFile("tests/rewrites/i22440.scala", defaultOptions.and("-rewrite")), compileFile("tests/rewrites/i22731.scala", defaultOptions.and("-rewrite", "-source:3.7-migration")), compileFile("tests/rewrites/i22731b.scala", defaultOptions.and("-rewrite", "-source:3.7-migration")), + compileFile("tests/rewrites/implicit-to-given.scala", defaultOptions.and("-rewrite", "-Yimplicit-to-given")), + compileFile("tests/rewrites/i22792.scala", defaultOptions.and("-rewrite")), + compileFile("tests/rewrites/i23449.scala", defaultOptions.and("-rewrite", "-source:3.4-migration")), + compileFile("tests/rewrites/i24213.scala", defaultOptions.and("-rewrite", "-source:3.4-migration")), ).checkRewrites() } @@ -146,9 +150,9 @@ class CompilationTests { aggregateTests( 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")), + compileFilesInDir("tests/neg-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking", "-language:experimental.separationChecking")), compileFile("tests/neg-custom-args/sourcepath/outer/nested/Test1.scala", defaultOptions.and("-sourcepath", "tests/neg-custom-args/sourcepath")), - compileDir("tests/neg-custom-args/sourcepath2/hi", defaultOptions.and("-sourcepath", "tests/neg-custom-args/sourcepath2", "-Xfatal-warnings")), + compileDir("tests/neg-custom-args/sourcepath2/hi", defaultOptions.and("-sourcepath", "tests/neg-custom-args/sourcepath2", "-Werror")), compileList("duplicate source", List( "tests/neg-custom-args/toplevel-samesource/S.scala", "tests/neg-custom-args/toplevel-samesource/nested/S.scala"), @@ -169,7 +173,7 @@ class CompilationTests { aggregateTests( compileFilesInDir("tests/run", defaultOptions.and("-Wsafe-init")), compileFilesInDir("tests/run-deep-subtype", allowDeepSubtypes), - compileFilesInDir("tests/run-custom-args/captures", allowDeepSubtypes.and("-language:experimental.captureChecking")), + compileFilesInDir("tests/run-custom-args/captures", allowDeepSubtypes.and("-language:experimental.captureChecking", "-language:experimental.separationChecking")), // Run tests for legacy lazy vals. compileFilesInDir("tests/run", defaultOptions.and("-Wsafe-init", "-Ylegacy-lazy-vals", "-Ycheck-constraint-deps"), FileFilter.include(TestSources.runLazyValsAllowlist)), ).checkRuns() @@ -234,21 +238,21 @@ class CompilationTests { @Test def checkInitGlobal: Unit = { implicit val testGroup: TestGroup = TestGroup("checkInitGlobal") 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() + compileFilesInDir("tests/init-global/pos", defaultOptions.and("-Ysafe-init-global", "-Werror"), 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() + compileFilesInDir("tests/init-global/pos-tasty", defaultOptions.and("-Ysafe-init-global", "-Werror"), FileFilter.exclude(TestSources.posInitGlobalScala2LibraryTastyExcludelisted)).checkCompile() end if } // initialization tests - @Test def checkInit: Unit = { - implicit val testGroup: TestGroup = TestGroup("checkInit") - val options = defaultOptions.and("-Wsafe-init", "-Xfatal-warnings") + @Test def safeInit: Unit = { + given TestGroup = TestGroup("safeInit") + val options = defaultOptions.and("-Wsafe-init", "-Werror") compileFilesInDir("tests/init/neg", options).checkExpectedErrors() compileFilesInDir("tests/init/warn", defaultOptions.and("-Wsafe-init")).checkWarnings() compileFilesInDir("tests/init/pos", options).checkCompile() - compileFilesInDir("tests/init/crash", options.without("-Xfatal-warnings")).checkCompile() + compileFilesInDir("tests/init/crash", options.without("-Werror")).checkCompile() // The regression test for i12128 has some atypical classpath requirements. // The test consists of three files: (a) Reflect_1 (b) Macro_2 (c) Test_3 // which must be compiled separately. In addition: @@ -257,7 +261,7 @@ class CompilationTests { // - the output from (a) _must not_ be on the classpath while compiling (c) locally { val i12128Group = TestGroup("checkInit/i12128") - val i12128Options = options.without("-Xfatal-warnings") + val i12128Options = options.without("-Werror") val outDir1 = defaultOutputDir + i12128Group + "/Reflect_1/i12128/Reflect_1" val outDir2 = defaultOutputDir + i12128Group + "/Macro_2/i12128/Macro_2" @@ -276,7 +280,7 @@ class CompilationTests { * an error when reading the files' TASTy trees. */ locally { val tastyErrorGroup = TestGroup("checkInit/tasty-error/val-or-defdef") - val tastyErrorOptions = options.without("-Xfatal-warnings") + val tastyErrorOptions = options.without("-Werror") val classA0 = defaultOutputDir + tastyErrorGroup + "/A/v0/A" val classA1 = defaultOutputDir + tastyErrorGroup + "/A/v1/A" @@ -299,7 +303,7 @@ class CompilationTests { * an error when reading the files' TASTy trees. This fact is demonstrated by the compilation of Main. */ locally { val tastyErrorGroup = TestGroup("checkInit/tasty-error/typedef") - val tastyErrorOptions = options.without("-Xfatal-warnings").without("-Ycheck:all") + val tastyErrorOptions = options.without("-Werror").without("-Ycheck:all") val classC = defaultOutputDir + tastyErrorGroup + "/C/typedef/C" val classA0 = defaultOutputDir + tastyErrorGroup + "/A/v0/A" diff --git a/compiler/test/dotty/tools/dotc/SettingsTests.scala b/compiler/test/dotty/tools/dotc/SettingsTests.scala index 996ab22f67b1..24549ade4d23 100644 --- a/compiler/test/dotty/tools/dotc/SettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/SettingsTests.scala @@ -1,8 +1,6 @@ package dotty.tools package dotc -import scala.language.unsafeNulls - import reporting.StoreReporter import vulpix.TestConfiguration @@ -205,7 +203,7 @@ class SettingsTests { } @Test def `Output setting is overriding existing jar`: Unit = - val result = Using.resource(Files.createTempFile("myfile", ".jar").nn){ file => + val result = Using.resource(Files.createTempFile("myfile", ".jar")){ file => object Settings extends SettingGroup: val defaultDir = new PlainDirectory(Directory(".")) val testOutput = OutputSetting(RootSetting, "testOutput", "testOutput", "", defaultDir) @@ -224,7 +222,7 @@ class SettingsTests { @Test def `Output setting is respecting previous setting`: Unit = val result = Using.resources( - Files.createTempFile("myfile", ".jar").nn, Files.createTempFile("myfile2", ".jar").nn + Files.createTempFile("myfile", ".jar"), Files.createTempFile("myfile2", ".jar") ){ (file1, file2) => object Settings extends SettingGroup: val defaultDir = new PlainDirectory(Directory(".")) @@ -247,10 +245,10 @@ class SettingsTests { assertNotEquals(file1StateBefore, String(Files.readAllBytes(file1))) assertEquals(file2StateBefore, String(Files.readAllBytes(file2))) - }(Files.deleteIfExists(_), Files.deleteIfExists(_)) + }(using Files.deleteIfExists(_), Files.deleteIfExists(_)) @Test def `Output side effect is not present when setting is deprecated`: Unit = - val result = Using.resource(Files.createTempFile("myfile", ".jar").nn){ file => + val result = Using.resource(Files.createTempFile("myfile", ".jar")){ file => object Settings extends SettingGroup: val defaultDir = new PlainDirectory(Directory(".")) val testOutput = OutputSetting(RootSetting, "testOutput", "testOutput", "", defaultDir, preferPrevious = true, deprecation = Deprecation.renamed("XtestOutput")) diff --git a/compiler/test/dotty/tools/dotc/TastyBootstrapTests.scala b/compiler/test/dotty/tools/dotc/TastyBootstrapTests.scala index 27311497de9c..046c8cf4754e 100644 --- a/compiler/test/dotty/tools/dotc/TastyBootstrapTests.scala +++ b/compiler/test/dotty/tools/dotc/TastyBootstrapTests.scala @@ -4,7 +4,7 @@ package dotc import scala.language.unsafeNulls -import org.junit.{ Test, BeforeClass, AfterClass } +import org.junit.{ Test, Ignore, BeforeClass, AfterClass } import org.junit.Assert._ import org.junit.Assume._ import org.junit.experimental.categories.Category @@ -29,7 +29,7 @@ class TastyBootstrapTests { * bootstrapped, and making sure that TASTY can link against a compiled * version of Dotty, and compiling the compiler using the SemanticDB generation */ - @Test def tastyBootstrap: Unit = { + @Ignore @Test def tastyBootstrap: Unit = { implicit val testGroup: TestGroup = TestGroup("tastyBootstrap/tests") val libGroup = TestGroup("tastyBootstrap/lib") val tastyCoreGroup = TestGroup("tastyBootstrap/tastyCore") diff --git a/compiler/test/dotty/tools/dotc/TupleShowTests.scala b/compiler/test/dotty/tools/dotc/TupleShowTests.scala index 2d76c480b001..3aa9d0d84b42 100644 --- a/compiler/test/dotty/tools/dotc/TupleShowTests.scala +++ b/compiler/test/dotty/tools/dotc/TupleShowTests.scala @@ -54,24 +54,28 @@ class TupleShowTests extends DottyTest: @Test def tup3_show10 = chkEq("(Int,\n Long,\n Short)".normEOL, tup3.toText(ctx.printer).mkString(10, false)) - val res21 = """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, - | Int, Long, Long, Long, Long, Long, Int)""".stripMargin.normEOL + val res21: String = + """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, + | Int, Long, Long, Long, Long, Long, Int)""".stripMargin.normEOL - val res22 = """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, - | Int, Long, Long, Long, Long, Long, Int, Long)""".stripMargin.normEOL + val res22: String = + """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, + | Int, Long, Long, Long, Long, Long, Int, Long)""".stripMargin.normEOL - val res23 = """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, - | Int, Long, Long, Long, Long, Long, Int, Long, Short)""".stripMargin.normEOL + val res23: String = + """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, + | Int, Long, Long, Long, Long, Long, Int, Long, Short)""".stripMargin.normEOL - val res24 = """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, - | Int, Long, Long, Long, Long, Long, Int, Long, Short, Short)""".stripMargin.normEOL + val res24: String = + """|(Int, Int, Int, Int, Int, Long, Long, Long, Long, Long, Int, Int, Int, Int, + | Int, Long, Long, Long, Long, Long, Int, Long, Short, Short)""".stripMargin.normEOL def chkEq[A](expected: A, obtained: A) = assert(expected == obtained, diff(s"$expected", s"$obtained")) /** On Windows the string literal in this test source file will be read with `\n` (b/c of "-encoding UTF8") * but the compiler will correctly emit \r\n as the line separator. * So we align the expected result to faithfully compare test results. */ - extension (str: String) def normEOL = if EOL == "\n" then str else str.replace("\n", EOL).nn + extension (str: String) def normEOL: String = if EOL == "\n" then str else str.replace("\n", EOL) def diff(exp: String, obt: String) = val min = math.min(exp.length, obt.length) diff --git a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala index 07834684d33b..c7b030f0805c 100644 --- a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala @@ -111,7 +111,7 @@ class ScalaSettingsTests: // createTestCase(settings.YjavaTasty , settings.XjavaTasty), // createTestCase(settings.YearlyTastyOutput , settings.XearlyTastyOutput, ":./"), // createTestCase(settings.YallowOutlineFromTasty, settings.XallowOutlineFromTasty), - createTestCase(settings.YcheckInit , settings.WcheckInit), + createTestCase(settings.YcheckInit , settings.WsafeInit), // createTestCase(settings.Xlint , settings.Wshadow, ":all"), // this setting is not going to be mapped to replacement. Read more in the commit message ).map: (deprecatedArgument, newSetting) => val args = List(deprecatedArgument) @@ -140,7 +140,7 @@ class ScalaSettingsTests: // createTestCase(settings.YjavaTasty , settings.XjavaTasty), // createTestCase(settings.YearlyTastyOutput , settings.XearlyTastyOutput), // createTestCase(settings.YallowOutlineFromTasty, settings.XallowOutlineFromTasty), - createTestCase(settings.YcheckInit , settings.WcheckInit), + createTestCase(settings.YcheckInit , settings.WsafeInit), createTestCase(settings.Xlint , settings.Wshadow), ).map: (deprecatedArgument, newSetting) => val args = List(deprecatedArgument) @@ -181,7 +181,7 @@ class ScalaSettingsTests: // createTestCase(settings.YjavaTasty , settings.XjavaTasty), // createTestCase(settings.YearlyTastyOutput , settings.XearlyTastyOutput, ":./"), // createTestCase(settings.YallowOutlineFromTasty, settings.XallowOutlineFromTasty), - createTestCase(settings.YcheckInit , settings.WcheckInit), + createTestCase(settings.YcheckInit , settings.WsafeInit), // createTestCase(settings.Xlint , settings.Wshadow, ":all"), // this setting is not going to be mapped to replacement. Read more in the commit message ).flatten.map: (deprecatedArgument, newSetting) => val args = List(deprecatedArgument) @@ -299,4 +299,11 @@ class ScalaSettingsTests: ) assertEquals(result, Right(reporting.Action.Error)) + @Test def `illegal source versions are not accepted when parsing the settings`: Unit = + for source <- SourceVersion.illegalInSettings do + val settings = ScalaSettings + val result = settings.processArguments(List("-source", source.toString()), true) + assertEquals(0, result.warnings.length) + assertEquals(1, result.errors.length) + end ScalaSettingsTests diff --git a/compiler/test/dotty/tools/dotc/core/tasty/PathPicklingTest.scala b/compiler/test/dotty/tools/dotc/core/tasty/PathPicklingTest.scala index 326a2dc87b2a..12d8425a32d6 100644 --- a/compiler/test/dotty/tools/dotc/core/tasty/PathPicklingTest.scala +++ b/compiler/test/dotty/tools/dotc/core/tasty/PathPicklingTest.scala @@ -47,7 +47,7 @@ class PathPicklingTest { val sb = new StringBuffer val jar = JarArchive.open(Path(s"$out/out.jar"), create = false) try - for file <- jar.iterator() if file.name.endsWith(".tasty") do + for file <- jar.iterator if file.name.endsWith(".tasty") do sb.append(TastyPrinter.showContents(file.toByteArray, noColor = true, isBestEffortTasty = false)) finally jar.close() sb.toString() diff --git a/compiler/test/dotty/tools/dotc/core/tasty/TastyHeaderUnpicklerTest.scala b/compiler/test/dotty/tools/dotc/core/tasty/TastyHeaderUnpicklerTest.scala index bde4246ef0f0..ddc88803854f 100644 --- a/compiler/test/dotty/tools/dotc/core/tasty/TastyHeaderUnpicklerTest.scala +++ b/compiler/test/dotty/tools/dotc/core/tasty/TastyHeaderUnpicklerTest.scala @@ -265,7 +265,7 @@ class TastyHeaderUnpicklerTest { object TastyHeaderUnpicklerTest { def fillHeader(maj: Int, min: Int, exp: Int, compiler: String): TastyBuffer = { - val compilerBytes = compiler.getBytes(java.nio.charset.StandardCharsets.UTF_8).nn + val compilerBytes = compiler.getBytes(java.nio.charset.StandardCharsets.UTF_8) val buf = new TastyBuffer(header.length + 32 + compilerBytes.length) for (ch <- header) buf.writeByte(ch.toByte) buf.writeNat(maj) @@ -304,7 +304,7 @@ object TastyHeaderUnpicklerTest { fail() } catch { - case err: UnpickleException => assert(err.getMessage.nn.contains(message)) + case err: UnpickleException => assert(err.getMessage.contains(message)) } } diff --git a/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala b/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala index 15522d61e31f..1a8e55b2fdcb 100644 --- a/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala +++ b/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala @@ -27,7 +27,7 @@ class PrintingTest { def options(phase: String, flags: List[String]) = val outDir = ParallelTesting.defaultOutputDir + "printing" + File.pathSeparator File(outDir).mkdirs() - List(s"-Xprint:$phase", "-color:never", "-nowarn", "-d", outDir, "-classpath", TestConfiguration.basicClasspath) ::: flags + List(s"-Vprint:$phase", "-color:never", "-nowarn", "-d", outDir, "-classpath", TestConfiguration.basicClasspath) ::: flags private def compileFile(path: JPath, phase: String): Boolean = { val baseFilePath = path.toString.stripSuffix(".scala") diff --git a/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala b/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala index 870da08dcfba..91ff958a7889 100644 --- a/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala +++ b/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala @@ -4,6 +4,7 @@ import dotty.tools.DottyTest import dotty.tools.dotc.rewrites.Rewrites import dotty.tools.dotc.rewrites.Rewrites.ActionPatch import dotty.tools.dotc.util.SourceFile +import dotty.tools.dotc.core.Contexts._ import scala.annotation.tailrec import scala.jdk.CollectionConverters.* @@ -54,6 +55,19 @@ class CodeActionTest extends DottyTest: |""".stripMargin ) + @Test def addUsingClause = + checkCodeAction( + """|object Test: + | def foo(implicit a: Int) = a + | foo(123) + |""".stripMargin, + "Add `using` clause", + """|object Test: + | def foo(implicit a: Int) = a + | foo(using 123) + |""".stripMargin + ) + @Test def insertMissingCases = checkCodeAction( code = @@ -136,14 +150,167 @@ class CodeActionTest extends DottyTest: afterPhase = "patternMatcher" ) + @Test def removeNN = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """|val s: String|Null = "foo".nn + |""".stripMargin, + title = "Remove unnecessary .nn", + expected = + """|val s: String|Null = "foo" + |""".stripMargin, + ctxx = ctxx + ) + + + @Test def removeNN2 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """val s: String|Null = null.nn + |""".stripMargin, + title = "Remove unnecessary .nn", + expected = + """val s: String|Null = null + |""".stripMargin, + ctxx = ctxx + ) + + @Test def addNN1 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """val s: String|Null = ??? + | val t: String = s""".stripMargin, + title = "Add .nn", + expected = + """val s: String|Null = ??? + | val t: String = s.nn""".stripMargin, + ctxx = ctxx + ) + + @Test def addNN2 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """implicit class infixOpTest(val s1: String) extends AnyVal { + | def q(s2: String): String | Null = null + |} + | val s: String = ??? + | val t: String = s q s""".stripMargin, + title = "Add .nn", + expected = + """implicit class infixOpTest(val s1: String) extends AnyVal { + | def q(s2: String): String | Null = null + |} + | val s: String = ??? + | val t: String = (s q s).nn""".stripMargin, + ctxx = ctxx + ) + + @Test def addNN3 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """implicit class infixOpTest(val s1: String) extends AnyVal { + | def q(s2: String, s3: String): String | Null = null + |} + | val s: String = ??? + | val t: String = s q (s, s)""".stripMargin, + title = "Add .nn", + expected = + """implicit class infixOpTest(val s1: String) extends AnyVal { + | def q(s2: String, s3: String): String | Null = null + |} + | val s: String = ??? + | val t: String = (s q (s, s)).nn""".stripMargin, + ctxx = ctxx + ) + + @Test def addNN4 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """implicit class infixOpTest(val s1: String) extends AnyVal { + | def q(s2: String, s3: String): String | Null = null + |} + | val s: String = ??? + | val t: String = s.q(s, s)""".stripMargin, + title = "Add .nn", + expected = + """implicit class infixOpTest(val s1: String) extends AnyVal { + | def q(s2: String, s3: String): String | Null = null + |} + | val s: String = ??? + | val t: String = s.q(s, s).nn""".stripMargin, + ctxx = ctxx + ) + + @Test def addNN5 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """val s: String | Null = ??? + |val t: String = s match { + | case _: String => "foo" + | case _ => s + |}""".stripMargin, + title = "Add .nn", + expected = + """val s: String | Null = ??? + |val t: String = s match { + | case _: String => "foo" + | case _ => s.nn + |}""".stripMargin, + ctxx = ctxx + ) + + @Test def addNN6 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """val s: String | Null = ??? + |val t: String = if (s != null) "foo" else s""".stripMargin, + title = "Add .nn", + expected = + """val s: String | Null = ??? + |val t: String = if (s != null) "foo" else s.nn""".stripMargin, + ctxx = ctxx + ) + + @Test def addNN7 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """given ctx: String | Null = null + |def f(using c: String): String = c + |val s: String = f(using ctx)""".stripMargin, + title = "Add .nn", + expected = + """given ctx: String | Null = null + |def f(using c: String): String = c + |val s: String = f(using ctx.nn)""".stripMargin, + ctxx = ctxx + ) + // Make sure we're not using the default reporter, which is the ConsoleReporter, // meaning they will get reported in the test run and that's it. private def newContext = val rep = new StoreReporter(null) with UniqueMessagePositions with HideNonSensicalMessages initialCtx.setReporter(rep).withoutColors - private def checkCodeAction(code: String, title: String, expected: String, afterPhase: String = "typer") = - ctx = newContext + private def checkCodeAction(code: String, title: String, expected: String, afterPhase: String = "typer", ctxx: Context = newContext) = + ctx = ctxx val source = SourceFile.virtual("test", code).content val runCtx = checkCompile(afterPhase, code) { (_, _) => () } val diagnostics = runCtx.reporter.removeBufferedMessages diff --git a/compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala b/compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala index 4db047d0951e..6df830905603 100644 --- a/compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala +++ b/compiler/test/dotty/tools/dotc/semanticdb/SemanticdbTests.scala @@ -33,7 +33,7 @@ import dotty.tools.dotc.util.SourceFile * only 1 semanticdb file should be present * @param source the single source file producing the semanticdb */ -@main def metac(root: String, source: String) = +@main def metac(root: String, source: String): Unit = val rootSrc = Paths.get(root) val sourceSrc = Paths.get(source) val semanticFile = FileSystems.getDefault.getPathMatcher("glob:**.semanticdb") @@ -53,13 +53,13 @@ import dotty.tools.dotc.util.SourceFile @Category(Array(classOf[BootstrappedOnlyTests])) class SemanticdbTests: - val javaFile = FileSystems.getDefault.getPathMatcher("glob:**.java") - val scalaFile = FileSystems.getDefault.getPathMatcher("glob:**.scala") - val expectFile = FileSystems.getDefault.getPathMatcher("glob:**.expect.scala") - val rootSrc = Paths.get(System.getProperty("dotty.tools.dotc.semanticdb.test")) - val expectSrc = rootSrc.resolve("expect") - val javaRoot = rootSrc.resolve("javacp") - val metacExpectFile = rootSrc.resolve("metac.expect") + val javaFile: PathMatcher = FileSystems.getDefault.getPathMatcher("glob:**.java") + val scalaFile: PathMatcher = FileSystems.getDefault.getPathMatcher("glob:**.scala") + val expectFile: PathMatcher = FileSystems.getDefault.getPathMatcher("glob:**.expect.scala") + val rootSrc: Path = Paths.get(System.getProperty("dotty.tools.dotc.semanticdb.test")) + val expectSrc: Path = rootSrc.resolve("expect") + val javaRoot: Path = rootSrc.resolve("javacp") + val metacExpectFile: Path = rootSrc.resolve("metac.expect") @Category(Array(classOf[dotty.SlowTests])) @Test def expectTests: Unit = if (!scala.util.Properties.isWin) runExpectTest(updateExpectFiles = false) @@ -138,7 +138,7 @@ class SemanticdbTests: "-feature", "-deprecation", // "-Ydebug-flags", - // "-Xprint:extractSemanticDB", + // "-Vprint:extractSemanticDB", "-sourceroot", expectSrc.toString, "-classpath", target.toString, "-Xignore-scala2-macros", diff --git a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala index 7c6a27ea7422..c0a609b226a8 100644 --- a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala +++ b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala @@ -35,7 +35,7 @@ class PatmatExhaustivityTest { e.printStackTrace(printWriter) } - stringBuffer.toString.trim.nn.replaceAll("\\s+\n", "\n") match { + stringBuffer.toString.trim.replaceAll("\\s+\n", "\n") match { case "" => Nil case s => s.linesIterator.toSeq } diff --git a/compiler/test/dotty/tools/repl/AbstractFileClassLoaderTest.scala b/compiler/test/dotty/tools/repl/AbstractFileClassLoaderTest.scala index 27796feb819b..44540629016e 100644 --- a/compiler/test/dotty/tools/repl/AbstractFileClassLoaderTest.scala +++ b/compiler/test/dotty/tools/repl/AbstractFileClassLoaderTest.scala @@ -29,7 +29,7 @@ class AbstractFileClassLoaderTest: // cf ScalaClassLoader#classBytes extension (loader: ClassLoader) // An InputStream representing the given class name, or null if not found. - def classAsStream(className: String) = loader.getResourceAsStream { + def classAsStream(className: String): InputStream = loader.getResourceAsStream { if className.endsWith(".class") then className else s"${className.replace('.', '/')}.class" // classNameToPath } diff --git a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala index 0592cbbed1be..0ca29fe6e15e 100644 --- a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala +++ b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala @@ -533,7 +533,7 @@ object ReplCompilerTests: end ReplCompilerTests -class ReplXPrintTyperTests extends ReplTest(ReplTest.defaultOptions :+ "-Xprint:typer"): +class ReplXPrintTyperTests extends ReplTest(ReplTest.defaultOptions :+ "-Vprint:typer"): @Test def i9111 = initially { run("""|enum E { | case A @@ -582,3 +582,34 @@ class ReplHighlightTests extends ReplTest(ReplTest.defaultOptions.filterNot(_.st case class Tree(left: Tree, right: Tree) def deepTree(depth: Int): Tree deepTree(300)""") + +class ReplUnrollTests extends ReplTest(ReplTest.defaultOptions ++ Seq("-experimental", "-Xprint:pickler")): + override val redirectOutput = true + @Test def i23408: Unit = initially: + run(""" + import scala.annotation.unroll + case class Foo(x: Int, @unroll y: Option[String] = None)""" + ) + val expected = List( + "def copy(x: Int, y: Option[String]): Foo = new Foo(x, y)", + "def copy(x: Int): Foo = this.copy(x, this.copy$default$2)", + "def copy$default$1: Int @uncheckedVariance = Foo.this.x", + "def copy$default$2: Option[String] @uncheckedVariance = Foo.this.y", + "def apply(x: Int, y: Option[String]): Foo = new Foo(x, y)", + "def apply(x: Int): Foo = this.apply(x, Foo.$lessinit$greater$default$2)", + """def fromProduct(x$0: Product): Foo.MirroredMonoType = { + val arity: Int = x$0.productArity + val x$1: Int = x$0.productElement(0).$asInstanceOf[Int] + val y$1: Option[String] = (if arity > 1 then x$0.productElement(1) else Foo.$lessinit$greater$default$2).$asInstanceOf[Option[String]] + new Foo(x$1, y$1) + }""" + ) + def trimWhitespaces(input: String): String = input.replaceAll("\\s+", " ") + val output = storedOutput() + val normalizedOutput = trimWhitespaces(output) + expected.foreach: defn => + val normalizedDefn = trimWhitespaces(defn) + assertTrue( + s"Output: '$output' did not contain expected definition: ${defn}", + normalizedOutput.contains(normalizedDefn) + ) diff --git a/compiler/test/dotty/tools/scripting/ScriptTestEnv.scala b/compiler/test/dotty/tools/scripting/ScriptTestEnv.scala index 771c3ba14af0..387e309f6aec 100644 --- a/compiler/test/dotty/tools/scripting/ScriptTestEnv.scala +++ b/compiler/test/dotty/tools/scripting/ScriptTestEnv.scala @@ -282,7 +282,7 @@ object ScriptTestEnv { } extension(f: File) { - def name = f.getName + def name: String = f.getName def norm: String = f.toPath.normalize.norm def absPath: String = f.getAbsolutePath.norm def relpath: Path = f.toPath.relpath @@ -305,7 +305,7 @@ object ScriptTestEnv { // dist[*]/target/universal/stage, if present // else, SCALA_HOME if defined // else, not defined - lazy val envScalaHome = + lazy val envScalaHome: String = printf("scalacPath: %s\n", scalacPath.norm) if scalacPath.isFile then scalacPath.replaceAll("/bin/scalac", "") else envOrElse("SCALA_HOME", "not-found").norm diff --git a/compiler/test/dotty/tools/utils.scala b/compiler/test/dotty/tools/utils.scala index 03e25630c421..5dd949643f7f 100644 --- a/compiler/test/dotty/tools/utils.scala +++ b/compiler/test/dotty/tools/utils.scala @@ -33,7 +33,7 @@ def scriptsDir(path: String): File = { dir } -extension (f: File) def absPath = +extension (f: File) def absPath: String = f.getAbsolutePath.replace('\\', '/') extension (str: String) def dropExtension = @@ -41,7 +41,7 @@ extension (str: String) def dropExtension = private def withFile[T](file: File)(action: Source => T): T = resource(Source.fromFile(file, UTF_8.name))(action) -def readLines(f: File): List[String] = withFile(f)(_.getLines.toList) +def readLines(f: File): List[String] = withFile(f)(_.getLines().toList) def readFile(f: File): String = withFile(f)(_.mkString) private object Unthrown extends ControlThrowable diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 12a53a19931d..5ec53fcb207f 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -41,8 +41,8 @@ import dotty.tools.vulpix.TestConfiguration.defaultOptions * using this, you should be running your JUnit tests **sequentially**, as the * test suite itself runs with a high level of concurrency. */ -trait ParallelTesting extends RunnerOrchestration { self => - import ParallelTesting._ +trait ParallelTesting extends RunnerOrchestration: + import ParallelTesting.* /** If the running environment supports an interactive terminal, each `Test` * will be run with a progress bar and real time feedback @@ -353,8 +353,8 @@ trait ParallelTesting extends RunnerOrchestration { self => import summaryReport._ - protected final val realStdout = System.out - protected final val realStderr = System.err + protected final val realStdout: PrintStream = System.out + protected final val realStderr: PrintStream = System.err /** A runnable that logs its contents in a buffer */ trait LoggedRunnable extends Runnable { @@ -838,7 +838,7 @@ trait ParallelTesting extends RunnerOrchestration { self => count += 1 for file <- files if isSourceFile(file) do Using.resource(Source.fromFile(file, StandardCharsets.UTF_8.name)) { source => - source.getLines.zipWithIndex.foreach: (line, lineNbr) => + source.getLines().zipWithIndex.foreach: (line, lineNbr) => comment.findAllMatchIn(line).foreach: case comment("nopos-") => bump("nopos") case _ => bump(s"${file.getPath}:${lineNbr+1}") @@ -980,7 +980,7 @@ trait ParallelTesting extends RunnerOrchestration { self => expectedErrors += 1 files.filter(isSourceFile).foreach { file => Using(Source.fromFile(file, StandardCharsets.UTF_8.name)) { source => - source.getLines.zipWithIndex.foreach { case (line, lineNbr) => + source.getLines().zipWithIndex.foreach { case (line, lineNbr) => comment.findAllMatchIn(line).foreach { m => m.group(2) match case prefix if m.group(1).isEmpty => @@ -996,29 +996,33 @@ trait ParallelTesting extends RunnerOrchestration { self => (errorMap, expectedErrors) end getErrorMapAndExpectedCount - // return unfulfilled expected errors and unexpected diagnostics + // return unfulfilled expected errors and unexpected diagnostics. + // the errorMap of expected errors is drained and returned as unfulfilled. + // a diagnostic at EOF after NL is recorded at the preceding line, + // to obviate `anypos-error` in that case. def getMissingExpectedErrors(errorMap: HashMap[String, Integer], reporterErrors: Iterator[Diagnostic]): (List[String], List[String]) = val unexpected, unpositioned = ListBuffer.empty[String] // For some reason, absolute paths leak from the compiler itself... def relativize(path: String): String = path.split(JFile.separatorChar).dropWhile(_ != "tests").mkString(JFile.separator) 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 null => false + 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 => - val key = s"${relativize(srcpos.source.file.toString)}:${srcpos.line + 1}" - if !seenAt(key) then unexpected += key - case srcpos => - if !seenAt("nopos") then unpositioned += relativize(srcpos.source.file.toString) + val srcpos = d.pos.nonInlined.adjustedAtEOF + val relatively = relativize(srcpos.source.file.toString) + if srcpos.exists then + val key = s"${relatively}:${srcpos.line + 1}" + if !seenAt(key) then unexpected += key + else + if !seenAt("nopos") then unpositioned += relatively reporterErrors.foreach(sawDiagnostic) - errorMap.get("anypos") match - case n if n == unexpected.size => errorMap.remove("anypos") ; unexpected.clear() - case _ => + if errorMap.get("anypos") == unexpected.size then + errorMap.remove("anypos") + unexpected.clear() (errorMap.asScala.keys.toList, (unexpected ++ unpositioned).toList) end getMissingExpectedErrors @@ -1838,9 +1842,8 @@ trait ParallelTesting extends RunnerOrchestration { self => def isUserDebugging: Boolean = val mxBean = ManagementFactory.getRuntimeMXBean mxBean.getInputArguments.asScala.exists(_.contains("jdwp")) -} -object ParallelTesting { +object ParallelTesting: def defaultOutputDir: String = "out"+JFile.separator @@ -1855,4 +1858,14 @@ object ParallelTesting { def isBestEffortTastyFile(f: JFile): Boolean = f.getName.endsWith(".betasty") -} + extension (pos: SourcePosition) + private def adjustedAtEOF: SourcePosition = + if pos.span.isSynthetic + && pos.span.isZeroExtent + && pos.span.exists + && pos.span.start == pos.source.length + && pos.source(pos.span.start - 1) == '\n' + then + pos.withSpan(pos.span.shift(-1)) + else + pos diff --git a/docs/_docs/contributing/debugging/ide-debugging.md b/docs/_docs/contributing/debugging/ide-debugging.md index 8548235672af..175f7969439c 100644 --- a/docs/_docs/contributing/debugging/ide-debugging.md +++ b/docs/_docs/contributing/debugging/ide-debugging.md @@ -140,7 +140,7 @@ And concatenate the output into the classpath argument, which should already con In the `args` you can add any additional compiler option you want. -For instance you can add `-Xprint:all` to print all the generated trees after each mega phase. +For instance you can add `-Vprint:all` to print all the generated trees after each mega phase. Run `scalac -help` to get an overview of the available compiler options. diff --git a/docs/_docs/contributing/debugging/inspection.md b/docs/_docs/contributing/debugging/inspection.md index 7cb1fa68abff..f1a60da9f905 100644 --- a/docs/_docs/contributing/debugging/inspection.md +++ b/docs/_docs/contributing/debugging/inspection.md @@ -61,9 +61,9 @@ Sometimes you may want to stop the compiler after a certain phase, for example t knock-on errors from occurring from a bug in an earlier phase. Use the flag `-Ystop-after:` to prevent any phases executing afterwards. -> e.g. `-Xprint:` where `phase` is a miniphase, will print after +> e.g. `-Vprint:` where `phase` is a miniphase, will print after > the whole phase group is complete, which may be several miniphases after `phase`. -> Instead you can use `-Ystop-after: -Xprint:` to stop +> Instead you can use `-Ystop-after: -Vprint:` to stop > immediately after the miniphase and see the trees that you intended. ## Printing TASTy of a Class diff --git a/docs/_docs/contributing/debugging/other-debugging.md b/docs/_docs/contributing/debugging/other-debugging.md index db32a25dabd7..d0d0c2431a04 100644 --- a/docs/_docs/contributing/debugging/other-debugging.md +++ b/docs/_docs/contributing/debugging/other-debugging.md @@ -72,19 +72,19 @@ assertPositioned(tree.reporting(s"Tree is: $result")) To print out the trees you are compiling after the FrontEnd (scanner, parser, namer, typer) phases: ```shell -scalac -Xprint:typer ../issues/Playground.scala +scalac -Vprint:typer ../issues/Playground.scala ``` To print out the trees after Frontend and CollectSuperCalls phases: ```shell -scalac -Xprint:typer,collectSuperCalls ../issues/Playground.scala +scalac -Vprint:typer,collectSuperCalls ../issues/Playground.scala ``` To print out the trees after all phases: ```shell -scalac -Xprint:all ../issues/Playground.scala +scalac -Vprint:all ../issues/Playground.scala ``` To find out the list of all the phases and their names, check out [this](https://github.com/scala/scala3/blob/10526a7d0aa8910729b6036ee51942e05b71abf6/compiler/src/dotty/tools/dotc/Compiler.scala#L34) line in `Compiler.scala`. Each `Phase` object has `phaseName` defined on it, this is the phase name. @@ -154,7 +154,7 @@ And is to be used as: scalac -Yprint-pos ../issues/Playground.scala ``` -If used, all the trees output with `show` or via `-Xprint:typer` will also have positions attached to them, e.g.: +If used, all the trees output with `show` or via `-Vprint:typer` will also have positions attached to them, e.g.: ```scala package @ { @@ -182,7 +182,7 @@ package @ { Every [Positioned](https://github.com/scala/scala3/blob/10526a7d0aa8910729b6036ee51942e05b71abf6/compiler/src/dotty/tools/dotc/ast/Positioned.scala) (a parent class of `Tree`) object has a `uniqueId` field. It is an integer that is unique for that tree and doesn't change from compile run to compile run. You can output these IDs from any printer (such as the ones used by `.show` and `-Xprint`) via `-Yshow-tree-ids` flag, e.g.: ```shell -scalac -Xprint:typer -Yshow-tree-ids ../issues/Playground.scala +scalac -Vprint:typer -Yshow-tree-ids ../issues/Playground.scala ``` Gives: diff --git a/docs/_docs/contributing/issues/cause.md b/docs/_docs/contributing/issues/cause.md index f96d3b6d2f8a..9f55a174174c 100644 --- a/docs/_docs/contributing/issues/cause.md +++ b/docs/_docs/contributing/issues/cause.md @@ -18,10 +18,10 @@ As described in the [compiler lifecycle](../architecture/lifecycle.md#phases-2), each phase transforms the trees and types that represent your code in a certain way. -To print the code as it is transformed through the compiler, use the compiler flag `-Xprint:all`. +To print the code as it is transformed through the compiler, use the compiler flag `-Vprint:all`. After each phase group is completed, you will see the resulting trees representing the code. -> It is recommended to test `-Xprint:all` on a single, small file, otherwise a lot of unnecessary +> It is recommended to test `-Vprint:all` on a single, small file, otherwise a lot of unnecessary > output will be generated. ### Trace a Tree Creation Site @@ -31,7 +31,7 @@ your search to the code of that phase. For example if you found a problematic tr `posttyper`, the problem most likely appears in the code of [PostTyper]. We can trace the exact point the tree was generated by looking for its unique ID, and then generating a stack trace at its creation: -1. Run the compiler with `-Xprint:posttyper` and `-Yshow-tree-ids` flags. +1. Run the compiler with `-Vprint:posttyper` and `-Yshow-tree-ids` flags. This will only print the trees of the `posttyper` phase. This time you should see the tree in question be printed alongside its ID. You'll see something like `println#223("Hello World"#37)`. 2. Copy the ID of the desired tree. @@ -43,7 +43,7 @@ Do not use a conditional breakpoint, the time overhead is very significant for a ### Enhanced Tree Printing -As seen above `-Xprint:` can be enhanced with further configuration flags, found in +As seen above `-Vprint:` can be enhanced with further configuration flags, found in [ScalaSettings]. For example, you can additionally print the type of a tree with `-Xprint-types`. ## Increasing Logging Output diff --git a/docs/_docs/contributing/issues/reproduce.md b/docs/_docs/contributing/issues/reproduce.md index ae031a44d76f..dd282166bd57 100644 --- a/docs/_docs/contributing/issues/reproduce.md +++ b/docs/_docs/contributing/issues/reproduce.md @@ -39,9 +39,9 @@ $ scalac Here are some useful debugging ``: -* `-Xprint:PHASE1,PHASE2,...` or `-Xprint:all`: prints the `AST` after each +* `-Vprint:PHASE1,PHASE2,...` or `-Vprint:all`: prints the `AST` after each specified phase. Phase names can be found by examining the - `dotty.tools.dotc.transform.*` classes for their `phaseName` field e.g., `-Xprint:erasure`. + `dotty.tools.dotc.transform.*` classes for their `phaseName` field e.g., `-Vprint:erasure`. You can discover all phases in the `dotty.tools.dotc.Compiler` class * `-Ylog:PHASE1,PHASE2,...` or `-Ylog:all`: enables `ctx.log("")` logging for the specified phase. @@ -142,7 +142,7 @@ $ (rm -rv out || true) && mkdir out # clean up compiler output, create `out` dir scalac # Invoke the compiler task defined by the Dotty sbt project -d $here/out # All the artefacts go to the `out` folder created earlier - # -Xprint:typer # Useful debug flags, commented out and ready for quick usage. Should you need one, you can quickly access it by uncommenting it. + # -Vprint:typer # Useful debug flags, commented out and ready for quick usage. Should you need one, you can quickly access it by uncommenting it. # -Ydebug-error # -Yprint-debug # -Yprint-debug-owners diff --git a/docs/_docs/internals/exclusive-capabilities.md b/docs/_docs/internals/exclusive-capabilities.md index 97c6592ac693..938eb5ccdd55 100644 --- a/docs/_docs/internals/exclusive-capabilities.md +++ b/docs/_docs/internals/exclusive-capabilities.md @@ -18,27 +18,27 @@ We introduce a new trait trait Mutable ``` It is used as a base trait for types that define _update methods_ using -a new modifier `mut`. +a new soft modifier `update`. -`mut` can only be used in classes or objects extending `Mutable`. An update method is allowed to access exclusive capabilities in the method's environment. By contrast, a normal method in a type extending `Mutable` may access exclusive capabilities only if they are defined locally or passed to it in parameters. +`update` can only be used in classes or objects extending `Mutable`. An update method is allowed to access exclusive capabilities in the method's environment. By contrast, a normal method in a type extending `Mutable` may access exclusive capabilities only if they are defined locally or passed to it in parameters. **Example:** ```scala class Ref(init: Int) extends Mutable: private var current = init def get: Int = current - mut def put(x: Int): Unit = current = x + update def put(x: Int): Unit = current = x ``` Here, `put` needs to be declared as an update method since it accesses the exclusive write capability of the variable `current` in its environment. -`mut` can also be used on an inner class of a class or object extending `Mutable`. It gives all code in the class the right +`update` can also be used on an inner class of a class or object extending `Mutable`. It gives all code in the class the right to access exclusive capabilities in the class environment. Normal classes can only access exclusive capabilities defined in the class or passed to it in parameters. ```scala object Registry extends Mutable: var count = 0 - mut class Counter: - mut def next: Int = + update class Counter: + update def next: Int = count += 1 count ``` @@ -79,22 +79,22 @@ Consider trait `IterableOnce` from the standard library. ```scala trait IterableOnce[+T] extends Mutable: def iterator: Iterator[T]^{this} - mut def foreach(op: T => Unit): Unit - mut def exists(op: T => Boolean): Boolean + update def foreach(op: T => Unit): Unit + update def exists(op: T => Boolean): Boolean ... ``` -The trait is a mutable type with many update methods, among them `foreach` and `exists`. These need to be classified as `mut` because their implementation in the subtrait `Iterator` uses the update method `next`. +The trait is a mutable type with many update methods, among them `foreach` and `exists`. These need to be classified as `update` because their implementation in the subtrait `Iterator` uses the update method `next`. ```scala trait Iterator[T] extends IterableOnce[T]: def iterator = this def hasNext: Boolean - mut def next(): T - mut def foreach(op: T => Unit): Unit = ... - mut def exists(op; T => Boolean): Boolean = ... + update def next(): T + update def foreach(op: T => Unit): Unit = ... + update def exists(op; T => Boolean): Boolean = ... ... ``` But there are other implementations of `IterableOnce` that are not mutable types (even though they do indirectly extend the `Mutable` trait). Notably, collection classes implement `IterableOnce` by creating a fresh -`iterator` each time one is required. The mutation via `next()` is then restricted to the state of that iterator, whereas the underlying collection is unaffected. These implementations would implement each `mut` method in `IterableOnce` by a normal method without the `mut` modifier. +`iterator` each time one is required. The mutation via `next()` is then restricted to the state of that iterator, whereas the underlying collection is unaffected. These implementations would implement each `update` method in `IterableOnce` by a normal method without the `update` modifier. ```scala trait Iterable[T] extends IterableOnce[T]: @@ -140,7 +140,7 @@ For instance, a matrix multiplication method can be expressed as follows: ```scala class Matrix(nrows: Int, ncols: Int) extends Mutable: - mut def update(i: Int, j: Int, x: Double): Unit = ... + update def update(i: Int, j: Int, x: Double): Unit = ... def apply(i: Int, j: Int): Double = ... def mul(a: Matrix, b: Matrix, c: Matrix^): Unit = @@ -435,7 +435,7 @@ we'd deal with ```scala val x$ = Ref[T](init) def x = x$.get -mut def x_=(y: T) = x$.put(y) +update def x_=(y: T) = x$.put(y) ``` There should be a way to exclude a mutable variable or field from tracking. Maybe an annotation or modifier such as `transparent` or `untracked`? diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index 6c144f436690..50cc178b86a5 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -141,7 +141,7 @@ type val var while with yield ### Soft keywords ``` -as derives end erased extension infix inline opaque open throws tracked transparent using | * + - +as derives end erased extension infix inline mut opaque open throws tracked transparent using | * + - ``` See the [separate section on soft keywords](../reference/soft-modifier.md) for additional @@ -182,7 +182,9 @@ Type ::= FunType | MatchType | InfixType FunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type Function(ts, t) | FunctionWithMods(ts, t, mods, erasedParams) - | TypTypeParamClause '=>' Type PolyFunction(ps, t) + | FunTypeArgs (‘->’ | ‘?->’) [CaptureSet] Type -- under pureFunctions and captureChecking + | TypTypeParamClause ‘=>’ Type PolyFunction(ps, t) + | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions and captureChecking FunTypeArgs ::= InfixType | ‘(’ [ FunArgTypes ] ‘)’ | FunParamClause @@ -190,13 +192,15 @@ FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’ TypedFunParam ::= [`erased`] id ‘:’ Type MatchType ::= InfixType `match` <<< TypeCaseClauses >>> InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2) + | RefinedType ‘^’ -- under captureChecking RefinedType ::= AnnotType {[nl] Refinement} RefinedTypeTree(t, ds) + | AnnotType {[nl] Refinement} ‘^’ CaptureSet -- under captureChecking AnnotType ::= SimpleType {Annotation} Annotated(t, annot) AnnotType1 ::= SimpleType1 {Annotation} Annotated(t, annot) SimpleType ::= SimpleLiteral SingletonTypeTree(l) | ‘?’ TypeBounds - | SimpleType1 + | SimpleType1 {ParArgumentExprs} SimpleType1 ::= id Ident(name) | Singleton ‘.’ id Select(t, name) | Singleton ‘.’ ‘type’ SingletonTypeTree(p) @@ -210,26 +214,29 @@ Singleton ::= SimpleRef | Singleton ‘.’ id FunArgType ::= Type | ‘=>’ Type PrefixOp(=>, t) + | ‘->’ [CaptureSet] Type -- under captureChecking FunArgTypes ::= FunArgType { ‘,’ FunArgType } ParamType ::= [‘=>’] ParamValueType + | ‘->’ [CaptureSet] ParamValueType -- under captureChecking ParamValueType ::= Type [‘*’] PostfixOp(t, "*") - | IntoType - | ‘(’ IntoType ‘)’ ‘*’ PostfixOp(t, "*") -IntoType ::= [‘into’] IntoTargetType Into(t) - | ‘(’ IntoType ‘)’ -IntoTargetType ::= Type - | FunTypeArgs (‘=>’ | ‘?=>’) IntoType -TypeArgs ::= ‘[’ Types ‘]’ ts +TypeArgs ::= ‘[’ TypeArg {‘,’ TypeArg} ‘]’ ts Refinement ::= :<<< [RefineDcl] {semi [RefineDcl]} >>> ds -TypeBounds ::= [‘>:’ Type] [‘<:’ Type] TypeBoundsTree(lo, hi) +TypeBounds ::= [‘>:’ TypeBound] [‘<:’ TypeBound] TypeBoundsTree(lo, hi) TypeAndCtxBounds ::= TypeBounds [‘:’ ContextBounds] ContextBounds(typeBounds, tps) ContextBounds ::= ContextBound | ContextBound `:` ContextBounds -- to be deprecated | '{' ContextBound {',' ContextBound} '}' ContextBound ::= Type ['as' id] Types ::= Type {‘,’ Type} +TypeArg ::= Type + | CaptureSet -- under captureChecking +TypeBound ::= Type + | CaptureSet -- under captureChecking NamesAndTypes ::= NameAndType {‘,’ NameAndType} NameAndType ::= id ':' Type +CaptureSet ::= ‘{’ CaptureRef {‘,’ CaptureRef} ‘}’ -- under captureChecking +CaptureRef ::= { SimpleRef ‘.’ } SimpleRef [‘*’] [CapFilter] [‘.’ ‘rd’] -- under captureChecking +CapFilter ::= ‘.’ ‘as’ ‘[’ QualId ’]’ -- under captureChecking ``` ### Expressions @@ -254,6 +261,7 @@ Expr1 ::= [‘inline’] ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[ | ForExpr | [SimpleExpr ‘.’] id ‘=’ Expr Assign(expr, expr) | PrefixOperator SimpleExpr ‘=’ Expr Assign(expr, expr) + | InfixExpr id [nl] `=' Expr Assign(expr, expr) -- only if language.postfixOps is enabled | SimpleExpr ArgumentExprs ‘=’ Expr Assign(expr, expr) | PostfixExpr [Ascription] | ‘inline’ InfixExpr MatchClause @@ -365,16 +373,20 @@ ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] TypeDef(Modifiers, name, tparams, bounds) id [HkTypeParamClause] TypeAndCtxBounds Bound(below, above, context) + | {Annotation} [‘+’ | ‘-’] id `^` TypeAndCtxBounds -- under captureChecking DefTypeParamClause::= [nl] ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeAndCtxBounds + | {Annotation} id `^` TypeAndCtxBounds -- under captureChecking TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ -TypTypeParam ::= {Annotation} (id | ‘_’) [HkTypeParamClause] TypeBounds +TypTypeParam ::= {Annotation} (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds + | {Annotation} id `^` TypeAndCtxBounds -- under captureChecking HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (id | ‘_’) [HkTypeParamClause] TypeBounds + | {Annotation} [‘+’ | ‘-’] id `^` TypeBounds -- under captureChecking ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’] ClsParamClause ::= [nl] ‘(’ ClsParams ‘)’ @@ -419,6 +431,7 @@ LocalModifier ::= ‘abstract’ | ‘infix’ | ‘erased’ | ‘tracked’ + | ‘mut’ -- under captureChecking AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] AccessQualifier ::= ‘[’ id ‘]’ @@ -462,7 +475,10 @@ DefDef ::= DefSig [‘:’ Type] [‘=’ Expr] | ‘this’ ConstrParamClauses [DefImplicitClause] ‘=’ ConstrExpr DefDef(_, , vparamss, EmptyTree, expr | Block) DefSig ::= id [DefParamClauses] [DefImplicitClause] TypeDef ::= id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds TypeDefTree(_, name, tparams, bound - [‘=’ Type] + [‘=’ TypeDefRHS] + | id `^` TypeAndCtxBounds [‘=’ TypeDefRHS] -- under captureChecking +TypeDefRHS ::= Type + | CaptureSet -- under captureChecking TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef | [‘case’] ‘object’ ObjectDef diff --git a/docs/_docs/reference/contextual/context-bounds.md b/docs/_docs/reference/contextual/context-bounds.md index 60357b3f098d..c0891aa1800e 100644 --- a/docs/_docs/reference/contextual/context-bounds.md +++ b/docs/_docs/reference/contextual/context-bounds.md @@ -47,7 +47,7 @@ Since the context parameter now has a name, it can be referred to in the body of `reduce`. An example is the `m.unit` reference in the definition above. If the context bound does not carry an `as` clause, the generated witness parameter gets a compiler-synthesized name. However, a [currently experimental -language extension](../experimental/default-names-context-bounds.md) would in this case give the context parameter the same name as the bound type parameter. +language extension](../experimental/typeclasses.md#better-default-names-for-context-bounds) would in this case give the context parameter the same name as the bound type parameter. Named context bounds were introduced in Scala 3.6. diff --git a/docs/_docs/reference/experimental/cc-advanced.md b/docs/_docs/reference/experimental/cc-advanced.md new file mode 100644 index 000000000000..52d1956caf49 --- /dev/null +++ b/docs/_docs/reference/experimental/cc-advanced.md @@ -0,0 +1,78 @@ +--- +layout: doc-page +title: "Capture Checking -- Advanced Use Cases" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-advanced.html +--- + + +## Access Control +Analogously to type parameters, we can lower- and upper-bound capability parameters where the bounds consist of concrete capture sets: +```scala +def main() = + // We can close over anything branded by the 'trusted' capability, but nothing else + def runSecure[C^ >: {trusted} <: {trusted}](block: () ->{C} Unit): Unit = ... + + // This is a 'brand" capability to mark what can be mentioned in trusted code + object trusted extends caps.Capability + + // These capabilities are trusted: + val trustedLogger: Logger^{trusted} + val trustedChannel: Channel[String]^{trusted} + // These aren't: + val untrustedLogger: Logger^ + val untrustedChannel: Channel[String]^ + + runSecure: () => + trustedLogger.log("Hello from trusted code") // ok + + runSecure: () => + trustedChannel.send("I can send") // ok + trustedLogger.log(trustedChannel.recv()) // ok + + runSecure: () => "I am pure and that's ok" // ok + + runSecure: () => + untrustedLogger.log("I can't be used") // error + untrustedChannel.send("I can't be used") // error +``` +The idea is that every capability derived from the marker capability `trusted` (and only those) are eligible to be used in the `block` closure +passed to `runSecure`. We can enforce this by an explicit capability parameter `C` constraining the possible captures of `block` to the interval `>: {trusted} <: {trusted}`. + +Note that since capabilities of function types are covariant, we could have equivalently specified `runSecure`'s signature using implicit capture polymorphism to achieve the same behavior: +```scala +def runSecure(block: () ->{trusted} Unit): Unit +``` + +## Capture-safe Lexical Control + +Capability members and paths to these members can prevent leakage +of labels for lexically-delimited control operators: +```scala +trait Label extends Capability: + type Fv^ // the capability set occurring freely in the `block` passed to `boundary` below. + +def boundary[T, C^](block: Label{type Fv = {C} } ->{C} T): T = ??? // ensure free caps of label and block match +def suspend[U](label: Label)[D^ <: {label.Fv}](handler: () ->{D} U): U = ??? // may only capture the free capabilities of label + +def test = + val x = 1 + boundary: outer => + val y = 2 + boundary: inner => + val z = 3 + val w = suspend(outer) {() => z} // ok + val v = suspend(inner) {() => y} // ok + val u = suspend(inner): () => + suspend(outer) {() => w + v} // ok + y + suspend(outer): () => + println(inner) // error (would leak the inner label) + x + y + z +``` +A key property is that `suspend` (think `shift` from delimited continuations) targeting a specific label (such as `outer`) should not accidentally close over labels from a nested `boundary` (such as `inner`), because they would escape their defining scope this way. +By leveraging capability polymorphism, capability members, and path-dependent capabilities, we can prevent such leaks from occurring at compile time: + +* `Label`s store the free capabilities `C` of the `block` passed to `boundary` in their capability member `Fv`. +* When suspending on a given label, the suspension handler can capture at most the capabilities that occur freely at the `boundary` that introduced the label. That prevents mentioning nested bound labels. + +[Back to Capture Checking](cc.md) \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc.md b/docs/_docs/reference/experimental/cc.md index ff480ffb638b..04c9d440a427 100644 --- a/docs/_docs/reference/experimental/cc.md +++ b/docs/_docs/reference/experimental/cc.md @@ -726,34 +726,45 @@ Reach capabilities take the form `x*` where `x` is syntactically a regular capab It is sometimes convenient to write operations that are parameterized with a capture set of capabilities. For instance consider a type of event sources `Source` on which `Listener`s can be registered. Listeners can hold certain capabilities, which show up as a parameter to `Source`: ```scala - class Source[X^]: - private var listeners: Set[Listener^{X^}] = Set.empty - def register(x: Listener^{X^}): Unit = - listeners += x +class Source[X^]: + private var listeners: Set[Listener^{X}] = Set.empty + def register(x: Listener^{X}): Unit = + listeners += x - def allListeners: Set[Listener^{X^}] = listeners + def allListeners: Set[Listener^{X}] = listeners ``` The type variable `X^` can be instantiated with a set of capabilities. It can occur in capture sets in its scope. For instance, in the example above -we see a variable `listeners` that has as type a `Set` of `Listeners` capturing `X^`. The `register` method takes a listener of this type +we see a variable `listeners` that has as type a `Set` of `Listeners` capturing `X`. The `register` method takes a listener of this type and assigns it to the variable. -Capture set variables `X^` are represented as regular type variables with a -special upper bound `CapSet`. For instance, `Source` could be equivalently +Capture-set variables `X^` without user-annotated bounds by default range over the interval `>: {} <: {caps.cap}` which is the universe of capture sets instead of regular types. + +Under the hood, such capture-set variables are represented as regular type variables within the special interval + `>: CapSet <: CapSet^`. +For instance, `Source` from above could be equivalently defined as follows: ```scala - class Source[X <: CapSet^]: - ... +class Source[X >: CapSet <: CapSet^]: + ... ``` -`CapSet` is a sealed trait in the `caps` object. It cannot be instantiated or inherited, so its only purpose is to identify capture set type variables and types. Capture set variables can be inferred like regular type variables. When they should be instantiated explicitly one uses a capturing -type `CapSet`. For instance: +`CapSet` is a sealed trait in the `caps` object. It cannot be instantiated or inherited, so its only +purpose is to identify type variables which are capture sets. In non-capture-checked +usage contexts, the type system will treat `CapSet^{a}` and `CapSet^{a,b}` as the type `CapSet`, whereas +with capture checking enabled, it will take the annotated capture sets into account, +so that `CapSet^{a}` and `CapSet^{a,b}` are distinct. +This representation based on `CapSet` is subject to change and +its direct use is discouraged. + +Capture-set variables can be inferred like regular type variables. When they should be instantiated +explicitly one supplies a concrete capture set. For instance: ```scala - class Async extends caps.Capability +class Async extends caps.Capability - def listener(async: Async): Listener^{async} = ??? +def listener(async: Async): Listener^{async} = ??? - def test1(async1: Async, others: List[Async]) = - val src = Source[CapSet^{async1, others*}] - ... +def test1(async1: Async, others: List[Async]) = + val src = Source[{async1, others*}] + ... ``` Here, `src` is created as a `Source` on which listeners can be registered that refer to the `async` capability or to any of the capabilities in list `others`. So we can continue the example code above as follows: ```scala @@ -761,12 +772,50 @@ Here, `src` is created as a `Source` on which listeners can be registered that r others.map(listener).foreach(src.register) val ls: Set[Listener^{async, others*}] = src.allListeners ``` +A common use-case for explicit capture parameters is describing changes to the captures of mutable fields, such as concatenating +effectful iterators: +```scala +class ConcatIterator[A, C^](var iterators: mutable.List[IterableOnce[A]^{C}]): + def concat(it: IterableOnce[A]^): ConcatIterator[A, {C, it}]^{this, it} = + iterators ++= it // ^ + this // track contents of `it` in the result +``` +In such a scenario, we also should ensure that any pre-existing alias of a `ConcatIterator` object should become +inaccessible after invoking its `concat` method. This is achieved with mutation and separation tracking which are +currently in development. + + +## Capability Members + +Just as parametrization by types can be equally expressed with type members, we could +also define the `Source[X^]` class above could using a _capability member_: +```scala +class Source: + type X^ + private var listeners: Set[Listener^{this.X}] = Set.empty + ... // as before +``` +Here, we can refer to capability members using paths in capture sets (such as `{this.X}`). Similarly to type members, +capability members can be upper- and lower-bounded with capture sets: +```scala +trait Thread: + type Cap^ + def run(block: () ->{this.Cap} -> Unit): Unit + +trait GPUThread extends Thread: + type Cap^ >: {cudaMalloc, cudaFree} <: {caps.cap} +``` +Since `caps.cap` is the top element for subcapturing, we could have also left out the +upper bound: `type Cap^ >: {cudaMalloc, cudaFree}`. + + +[More Advanced Use Cases](cc-advanced.md) ## Compilation Options The following options are relevant for capture checking. - - **-Xprint:cc** Prints the program with capturing types as inferred by capture checking. + - **-Vprint:cc** Prints the program with capturing types as inferred by capture checking. - **-Ycc-debug** Gives more detailed, implementation-oriented information about capture checking, as described in the next section. The implementation supporting capture checking with these options is currently in branch `cc-experiment` on dotty.epfl.ch. diff --git a/docs/_docs/reference/experimental/erased-defs-spec.md b/docs/_docs/reference/experimental/erased-defs-spec.md index 1861b734bb47..60c76dfcf5e5 100644 --- a/docs/_docs/reference/experimental/erased-defs-spec.md +++ b/docs/_docs/reference/experimental/erased-defs-spec.md @@ -4,67 +4,52 @@ title: "Erased Definitions - More Details" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/erased-defs-spec.html --- -TODO: complete ## Rules -1. `erased` is a soft modifier. It can appear: - * At the start of a parameter block of a method, function or class - * In a method definition - * In a `val` definition (but not `lazy val` or `var`) - * In a `class` or `trait` definition +1. `erased` is a soft modifier. It can appear in a `val` definition or in a parameter. + * `erased` cannot appear in a `lazy` `val` definition. + * `erased` _can_ appear for a parameterless given that expands to a value + definition. In that case the `given` is expanded to a non-lazy `val`. + * `erased` cannot appear in a call-by-name parameter. + * `erased` cannot appear in a mutable `var` definition. + * `erased` cannot appear in an `object` definition. - ```scala - erased val x = ... - erased def f = ... +2. Values or parameters that have a type that extends the `scala.compiletime.Erased` trait are + implicitly `erased`. - def g(erased x: Int) = ... + * The restrictions of point (1) apply. + * Parameterless givens are treated like values. + * Mutable variables cannot have a time that extends `scala.compiletime.Erased`. - (erased x: Int, y: Int) => ... - def h(x: (Int, erased Int) => Int) = ... +3. A reference to an `erased` value can only be used in an *erased context*: + * Inside the expression of an argument to an `erased` parameter + * Inside the body of an `erased` `val` + * Inside the path of a dependent type expression - class K(erased x: Int) { ... } - erased class E {} - ``` +4. `erased` can also be used in a function type, e.g. + * `(erased T1, T2) => R` + * `(x: T1, y: erased T2) ?=> T` -2. A reference to an `erased` val or def can only be used - * Inside the expression of argument to an `erased` parameter - * Inside the body of an `erased` `val` or `def` + Note that there is no subtype relation between `(erased T) => R` and `T => R` (or `(erased T) ?=> R` and `T ?=> R`). The `erased` parameters must match exactly in their respective positions. - -3. Functions - * `(erased x1: T1, x2: T2, ..., xN: TN) => y : (erased T1, T2, ..., TN) => R` - * `(using x1: T1, erased x2: T2, ..., xN: TN) => y: (using T1, erased T2, ..., TN) => R` - * `(using erased T1) => R <:< erased T1 => R` - * `(using T1, erased T2) => R <:< (T1, erased T2) => R` - * ... - - Note that there is no subtype relation between `(erased T) => R` and `T => R` (or `(given erased T) => R` and `(given T) => R`). The `erased` parameters must match exactly in their respective positions. - - -4. Eta expansion +5. Eta expansion if `def f(erased x: T): U` then `f: (erased T) => U`. - -5. Erasure semantics +6. Erasure semantics * All `erased` parameters are removed from the function - * All argument to `erased` parameters are not passed to the function - * All `erased` definitions are removed - * `(erased ET1, erased ET2, T1, ..., erased ETN, TM) => R` are erased to `(T1, ..., TM) => R`. - * `(given erased ET1, erased ET2, T1, ..., erased ETN, TM) => R` are erased to `(given T1, ..., TM) => R`. - + * All arguments to `erased` parameters are not passed to the function + * All `erased` value definitions are removed + * All `erased` argument types are removed from a function type -6. Overloading +7. Overloading Method with `erased` parameters will follow the normal overloading constraints after erasure. - -7. Overriding +8. Overriding * Member definitions overriding each other must both be `erased` or not be `erased`. * `def foo(x: T): U` cannot be overridden by `def foo(erased x: T): U` and vice-versa. -8. Type Restrictions - * For dependent functions, `erased` parameters are limited to realizable types, that is, types that are inhabited by non-null values. - This restriction stops us from using a bad bound introduced by an erased value, which leads to unsoundness (see #4060). - * Polymorphic functions with erased parameters are currently not supported, and will be rejected by the compiler. This is purely an implementation restriction, and might be lifted in the future. +9. Type Restrictions + * Polymorphic function literals with erased parameters are currently not supported, and will be rejected by the compiler. This is purely an implementation restriction, and might be lifted in the future. diff --git a/docs/_docs/reference/experimental/erased-defs.md b/docs/_docs/reference/experimental/erased-defs.md index d266cd6c9d19..065cc8b7074c 100644 --- a/docs/_docs/reference/experimental/erased-defs.md +++ b/docs/_docs/reference/experimental/erased-defs.md @@ -4,54 +4,98 @@ title: "Erased Definitions" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/erased-defs.html --- -`erased` is a modifier that expresses that some definition or expression is erased by the compiler instead of being represented in the compiled output. It is not yet part of the Scala language standard. To enable `erased`, turn on the language feature +`erased` is a modifier that expresses that some value or parameter is erased by the compiler instead of being represented in the compiled output. It is not yet part of the Scala language standard. To enable `erased`, turn on the language feature [`experimental.erasedDefinitions`](https://scala-lang.org/api/3.x/scala/runtime/stdLibPatches/language$$experimental$$erasedDefinitions$.html). This can be done with a language import ```scala import scala.language.experimental.erasedDefinitions ``` or by setting the command line option `-language:experimental.erasedDefinitions`. -Erased definitions must be in an experimental scope (see [Experimental definitions](../other-new-features/experimental-defs.md)). -## Why erased terms? +## Introduction -Let's describe the motivation behind erased terms with an example. In the -following we show a simple state machine which can be in a state `On` or `Off`. -The machine can change state from `Off` to `On` with `turnedOn` only if it is -currently `Off`. This last constraint is captured with the `IsOff[S]` contextual -evidence which only exists for `IsOff[Off]`. For example, not allowing calling -`turnedOn` on in an `On` state as we would require an evidence of type -`IsOff[On]` that will not be found. +## Why erased? +Sometimes, we need a value only to present evidence that some type can be constructed, whereas at runtime that value would not be referenced. For example, say we want to make Java serialization safe. This means that, when serializing values of some type, we want to have evidence that serialization of such a type will not fail at runtime. Java defines the `java.io.Serializable` interface to mark extending types as serializable. But this alone is not safe, since a `Serializable` class might well have unserializable fields. For instance Scala's `List` extends `Serializable` since we want to be able to serialize `List` data. But a particular list might have elements that are not serializable, for instance it might be a list of functions. If we try to serialize such a value, a `NotSerializableException` will be thrown. + +We can make serialization safe by defining an additional type class that has instances precisely for those types that are deeply serializable. For instance, like this: ```scala -sealed trait State -final class On extends State -final class Off extends State +/** Type class for types that are deeply serializable */ +trait CanSerialize[T] -@implicitNotFound("State must be Off") -class IsOff[S <: State] -object IsOff: - given isOff: IsOff[Off] = new IsOff[Off] +inline given CanSerialize[String] = CanSerialize() +inline given [T: CanSerialize] => CanSerialize[List[T]] = CanSerialize() +``` +We find a given instance of `CanSerialize` for strings, since strings are serializable. We also find a conditional given instance that says lists are serializable if their elements are. We would assume to have further instances for all types that are serializable (perhaps conditionally). -class Machine[S <: State]: - def turnedOn(using IsOff[S]): Machine[On] = new Machine[On] +Now, we can formulate a method `safeWriteObject` that serializes an object to an `ObjectOutputStream`: +```scala +def safeWriteObject[T <: java.io.Serializable] + (out: java.io.ObjectOutputStream, x: T) + (using CanSerialize[T]): Unit = + out.writeObject(x) +``` +The method works for objects of its type parameter `T`. `T` is required to conform to `java.io.Serializable` so that we can use the `writeObject` method of the output stream `out` on it. In addition we need a type class instance `CanSerialize[T]` that serves as evidence that the Java serialization will not fail at runtime. We can specialize the method to list arguments, as in the following: +```scala +def writeList[T] + (out: java.io.ObjectOutputStream, xs: List[T]) + (using CanSerialize[T]): Unit = + safeWriteObject(out, xs) +``` +We can test `writeList` by applying it to different types of lists: +```scala +@main def Test(out: java.io.ObjectOutputStream) = + writeList(out, List("a", "b")) // ok + writeList(out, List[Int => Int](x => x + 1, y => y * 2)) // error +``` +The first call will pass, but the second call will be rejected with a type error: +``` +No given instance of type CanSerialize[Int => Int] was found for parameter x$3 of method writeList +``` -val m = new Machine[Off] -m.turnedOn -m.turnedOn.turnedOn // ERROR -// ^ -// State must be Off +So far, this is a standard typeclass pattern to set up evidence that certain operations can be performed safely. But there is a problem with this scheme: The type class instances are passed as +additional parameters to methods `safeWriteObject` and `writeList` even though at run-time these objects will not be used anywhere. The only role of these parameters is to provide compile-time evidence that serialization for a particular type is safe. It would be nice if we could somehow "erase" these parameters so that they do not show up at run-time. This is precisely what erased does. Using erased, our example would look like this: +```scala +import language.experimental.erasedDefinitions + +class CanSerialize[T] + +inline given CanSerialize[String] = CanSerialize() +inline given [T: CanSerialize] => CanSerialize[List[T]] = CanSerialize() + +def safeWriteObject[T <: java.io.Serializable](out: java.io.ObjectOutputStream, x: T)(using erased CanSerialize[T]) = + out.writeObject(x) + +def writeList[T](out: java.io.ObjectOutputStream, xs: List[T])(using erased CanSerialize[T]) = + safeWriteObject(out, xs) + +@main def Test(out: java.io.ObjectOutputStream) = + writeList(out, List("a", "b")) // ok + writeList(out, List[Int => Int](x => x + 1, y => y * 2)) // error +``` +Note the two parameters to `safeWriteObject` and `writeList` are now `erased`. This means the parameters and their arguments are not present in the generated code. + +A safety requirement for `erased` is that we cannot simply make up evidence. For instance, say we want to make the second `writeList` pass by making up a given of the problematic type: +```scala +writeList(out, List[Int => Int](x => x + 1, y => y * 2)) + (using null.asInstanceOfCanSerialize[Int => Int]) +``` +This is just one way to do it, here is another: +```scala +def fakeEvidence: CanSerialize[Int => Int] = fakeEvidence +writeList(out, List[Int => Int](x => x + 1, y => y * 2)) + (using fakeEvidence) ``` +To rule out these attacks, we demand that the argument to an erased parameter is +a _pure expression_. Only a few expressions in Scala are pure, including + + - constants, + - non-lazy, immutable vals, + - constructors of classes that don't have an initializer, applied to pure arguments, + - `apply` methods of case classes that don't have an initializer, applied to pure arguments. -Note that in the code above the actual context arguments for `IsOff` are never -used at runtime; they serve only to establish the right constraints at compile -time. As these terms are never used at runtime there is not real need to have -them around, but they still need to be present in some form in the generated -code to be able to do separate compilation and retain binary compatibility. We -introduce _erased terms_ to overcome this limitation: we are able to enforce the -right constrains on terms at compile time. These terms have no run time -semantics and they are completely erased. +Other function calls are not classified as pure expressions. That's why the two given instances in the erased version of our examples are inline methods. After inlining, the arguments to the erased parameters are simple class constructions of `CanSerialize` which count as pure expressions. -## How to define erased terms? +## Details Parameters of methods and functions can be declared as erased, placing `erased` in front of each erased parameter (like `inline`). @@ -74,51 +118,68 @@ def methodWithErasedInt2(erased i: Int): Int = methodWithErasedInt1(i) // OK ``` -Not only parameters can be marked as erased, `val` and `def` can also be marked -with `erased`. These will also only be usable as arguments to `erased` -parameters. +The arguments to erased parameters must be pure expressions. +```scala +def f(x: Int): Int = + if x == 0 then 1 else x * f(x - 1) + +inline def g(x: Int): Int = + if x == 0 then 1 else x * g(x - 1) + +methodWithErasedInt2(5) // ok +methodWithErasedInt2(f(5)) // error, f(22) is not a pure expression +methodWithErasedInt2(g(5)) // ok since `g` is `inline`. + +Besides parameters, `val` definitions can also be marked with `erased`. +These will also only be usable as arguments to `erased` parameters or +as part of the definitions of other erased `val`s. Furthermore, the +defining right hand side of such `val` must be a pure expression. ```scala -erased val erasedEvidence: Ev = ... +erased val erasedEvidence: Ev = Ev() methodWithErasedEv(erasedEvidence, 40) // 42 ``` -## What happens with erased values at runtime? - -As `erased` are guaranteed not to be used in computations, they can and will be -erased. +## The Erased Trait +In some cases we would expect all instances of a trait to be erased. For instance, one could argue that it does not make sense to ever have a `CanSerialize[T]` instance at runtime. In that case we +can make `CanSerialize` extend from a new trait `compiletimetime.Erased` and avoid the explicit +`erased` modifiers in erased parameters and vals. Here is an alternative version of our example using this scheme: ```scala -// becomes def methodWithErasedEv(x: Int): Int at runtime -def methodWithErasedEv(x: Int, erased ev: Ev): Int = ... - -def evidence1: Ev = ... -erased def erasedEvidence2: Ev = ... // does not exist at runtime -erased val erasedEvidence3: Ev = ... // does not exist at runtime +class CanSerialize[T] extends compiletime.Erased +... +def safeWriteObject[T <: java.io.Serializable](out: java.io.ObjectOutputStream, x: T)(using CanSerialize[T]) = ... -// evidence1 is not evaluated and only `x` is passed to methodWithErasedEv -methodWithErasedEv(x, evidence1) +def writeList[T: CanSerialize](out: java.io.ObjectOutputStream, xs: List[T]) = ... ``` +Because `CanSerialize` extends `Erased` we can elide the explicit `erased` modifier in the using clause of `safeWriteObject`. It now also becomes possible to use a context bound for `CanSerialize` as is shown in the `writeList` method above. The context bound expands to a +using clause `(using CanSerialize[T])` which gets implicitly tagged with `erased`. + +## Uses of `Erased` in existing Code + + - The `CanThrow[T]` type class is used to declare that an exception can be thrown. The compiler generates a `CanThrow[E]` instances for exceptions that are handled in a `try`. Methods take an implicit `CanThrow[E]` parameter to indicate that they might throw exception `E`. `CanThrow` is declared to be an `Erased` capability class, so no actual evidence of `CanThrow` remains at run-time. -## State machine with erased evidence example + - The `CanEqual` evidence of [multiversal equality](../contextual/multiversal-equality.html) checks that two types can be compared. The actual comparison is done by the universal `equals` method of class `Object` or an overriding instance, it does not rely on the `CanEqual` value. +So far, `CanEqual` is handled specially in the compiler. With erased definitions, we could +avoid some of the special treatment by making `CanEqual` extend `compiletime.Erased`. + +- The conforms `<:<` typeclass asserts that we can prove that two types are in a subtype relation. `<:<` does offer a method to upcast values, but that could be also provided as a compiler-generated +cast operation. In that case, run-time instances of `<:<` (and also `=:=`) would be no longer needed and could be erased. + + +## Example: State machine with erased evidence The following example is an extended implementation of a simple state machine which can be in a state `On` or `Off`. The machine can change state from `Off` -to `On` with `turnedOn` only if it is currently `Off`, conversely from `On` to -`Off` with `turnedOff` only if it is currently `On`. These last constraint are -captured with the `IsOff[S]` and `IsOn[S]` given evidence only exist for -`IsOff[Off]` and `IsOn[On]`. For example, not allowing calling `turnedOff` on in -an `Off` state as we would require an evidence `IsOn[Off]` that will not be -found. - -As the given evidences of `turnedOn` and `turnedOff` are not used in the -bodies of those functions we can mark them as `erased`. This will remove the -evidence parameters at runtime, but we would still evaluate the `isOn` and -`isOff` givens that were found as arguments. As `isOn` and `isOff` are not -used except as `erased` arguments, we can mark them as `erased`, hence removing -the evaluation of the `isOn` and `isOff` evidences. +to `On` with `turnOn` only if it is currently `Off`, conversely from `On` to +`Off` with `turnOff` only if it is currently `On`. These constraints are +captured represented with two typeclass traits `IsOn[T]` and `IsOff[T]`. Two given instances for these traits exist only for the right kinds of state. There is a given instance for `IsOn[On]` and one for `IsOff[Off]` but there are no given instances for the other combinations. + +The `turnOn` and `turnOff` methods each require one of these given instances to ensure the machine is in the correct state for the operation to be allowed. +As the given instances required by `turnedOn` and `turnedOff` are not used in the bodies of those functions we can mark them as `erased`. ```scala +import language.experimental.erasedDefinitions import scala.annotation.implicitNotFound sealed trait State @@ -128,104 +189,41 @@ final class Off extends State @implicitNotFound("State must be Off") class IsOff[S <: State] object IsOff: - // will not be called at runtime for turnedOn, the - // compiler will only require that this evidence exists - given IsOff[Off] = new IsOff[Off] + inline given IsOff[Off]() @implicitNotFound("State must be On") class IsOn[S <: State] object IsOn: - // will not exist at runtime, the compiler will only - // require that this evidence exists at compile time - erased given IsOn[On] = new IsOn[On] - -class Machine[S <: State] private (): - // ev will disappear from both functions - def turnedOn(using erased ev: IsOff[S]): Machine[On] = new Machine[On] - def turnedOff(using erased ev: IsOn[S]): Machine[Off] = new Machine[Off] - -object Machine: - def newMachine(): Machine[Off] = new Machine[Off] - -@main def test = - val m = Machine.newMachine() - m.turnedOn - m.turnedOn.turnedOff - - // m.turnedOff - // ^ - // State must be On - - // m.turnedOn.turnedOn - // ^ - // State must be Off -``` - -Note that in [Compile-time operations](../metaprogramming/compiletime-ops.md#erasedvalue) we discussed `erasedValue` and inline -matches. `erasedValue` is internally implemented with `erased` (and is not experimental), so the state machine above -can be encoded as follows: - -```scala -import scala.compiletime.* - -sealed trait State -final class On extends State -final class Off extends State + inline given IsOn[On]() class Machine[S <: State]: - transparent inline def turnOn(): Machine[On] = - inline erasedValue[S] match - case _: Off => new Machine[On] - case _: On => error("Turning on an already turned on machine") - - transparent inline def turnOff(): Machine[Off] = - inline erasedValue[S] match - case _: On => new Machine[Off] - case _: Off => error("Turning off an already turned off machine") - -object Machine: - def newMachine(): Machine[Off] = - println("newMachine") - new Machine[Off] -end Machine + // ev will disappear from both functions + def turnOn(using erased IsOff[S]): Machine[On] = new Machine[On] + def turnOff(using erased IsOn[S]): Machine[Off] = new Machine[Off] @main def test = - val m = Machine.newMachine() - m.turnOn() - m.turnOn().turnOff() - m.turnOn().turnOn() // error: Turning on an already turned on machine + val m = Machine[Off]() + val m1 = m.turnOn + val m2 = m1.turnOff + m2.turnOn + + // m1.turnOn + // ^ error: State must be Off + // m2.turnOff + // ^ error: State must be On ``` +The first four lines of method `test` are all valid. The commented-out operations are invalid. The operation `m1.turnOn` is invalid since `m1` is of type `Machine[On]` and `m1.turnOn` requires the given instance `IsOff[On]` which does not exist. `m2.turnOff` is invalid by analogous reasoning. -## Erased Classes +## ErasedValue -`erased` can also be used as a modifier for a class. An erased class is intended to be used only in erased definitions. If the type of a val definition or parameter is -a (possibly aliased, refined, or instantiated) erased class, the definition is assumed to be `erased` itself. Likewise, a method with an erased class return type is assumed to be `erased` itself. Since given instances expand to vals and defs, they are also assumed to be erased if the type they produce is an erased class. Finally -function types with erased classes as arguments turn into erased function types. +The `compiletime.erasedValue` method was discussed in +[Compile-time operations](../metaprogramming/compiletime-ops.md#erasedvalue). A call to `erasedValue[T]` counts as an erased reference, so it could only be +used in an erased context, i.e. as an argument to an erased parameter or on the right-hand side of an erased `val` definition. At the same time +`erasedValue` does _not_ count as a pure expression, and for that reason cannot be part of these expressions. The net effect is that any references +to `erasedValue` must be eliminated by inlining. This is intentional: +allowing `erasedValue[T]` as a legal erased expression would undermine the safety of erased capabilities, since evidence for _any_ value of an erased type can be made up by it. -Example: -```scala -erased class CanRead - -val x: CanRead = ... // `x` is turned into an erased val -val y: CanRead => Int = ... // the function is turned into an erased function -def f(x: CanRead) = ... // `f` takes an erased parameter -def g(): CanRead = ... // `g` is turned into an erased def -given CanRead = ... // the anonymous given is assumed to be erased -``` -The code above expands to -```scala -erased class CanRead - -erased val x: CanRead = ... -val y: (erased CanRead) => Int = ... -def f(erased x: CanRead) = ... -erased def g(): CanRead = ... -erased given CanRead = ... -``` -After erasure, it is checked that no references to values of erased classes remain and that no instances of erased classes are created. So the following would be an error: -```scala -val err: Any = CanRead() // error: illegal reference to erased class CanRead -``` -Here, the type of `err` is `Any`, so `err` is not considered erased. Yet its initializing value is a reference to the erased class `CanRead`. +As an escape hatch, there also a method `unsafeErasedValue` in the +`scala.caps.unsafe` object. `scala.caps.unsafe.unsafeErasedValue[T]` does count as a pure expression for every type `T`, so it can be used in an erased context. But it should be used only if we can prove by other means that the established erased evidence is valid. [More Details](./erased-defs-spec.md) diff --git a/docs/_docs/reference/experimental/explicit-nulls.md b/docs/_docs/reference/experimental/explicit-nulls.md index 50339c3fa1e4..1be4d7f4f450 100644 --- a/docs/_docs/reference/experimental/explicit-nulls.md +++ b/docs/_docs/reference/experimental/explicit-nulls.md @@ -90,26 +90,10 @@ More details can be found in [safe initialization](../other-new-features/safe-in ## Equality -We don't allow the double-equal (`==` and `!=`) and reference (`eq` and `ne`) comparison between -`AnyRef` and `Null` anymore, since a variable with a non-nullable type cannot have `null` as value. -`null` can only be compared with `Null`, nullable union (`T | Null`), or `Any` type. - -For some reason, if we really want to compare `null` with non-null values, we have to provide a type hint (e.g. `: Any`). - -```scala -val x: String = ??? -val y: String | Null = ??? - -x == null // error: Values of types String and Null cannot be compared with == or != -x eq null // error -"hello" == null // error - -y == null // ok -y == x // ok - -(x: String | Null) == null // ok -(x: Any) == null // ok -``` +We still allow the double-equal (`==` and `!=`), reference (`eq` and `ne`) comparison, +and pattern matching between `Null` and reference types. +Even if a type is non-nullable, we still need to consider the possibility of `null` value +caused by the Java methods or uninitialized values. ## Java Interoperability and Flexible Types diff --git a/docs/_docs/reference/experimental/into-modifier.md b/docs/_docs/reference/experimental/into-modifier.md deleted file mode 100644 index 54da5f976320..000000000000 --- a/docs/_docs/reference/experimental/into-modifier.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -layout: doc-page -title: "The `into` Type Modifier" -redirectFrom: /docs/reference/other-new-features/into-modifier.html -nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/into-modifier.html ---- - -Scala 3's implicit conversions of the `scala.Conversion` class require a language import -``` -import scala.language.implicitConversions -``` -in any code that uses them as implicit conversions (code that calls conversions explicitly is not affected). If the import is missing, a feature warning is currently issued, and this will become an error in a future version of Scala 3. The motivation for this restriction is that code with hidden implicit conversions is hard to understand and might have correctness or performance problems that go undetected. - -There is one broad use case, however, where implicit conversions are very hard to replace. This is the case where an implicit conversion is used to adapt a method argument to its formal parameter type. An example from the standard library: -```scala -scala> val xs = List(0, 1) -scala> val ys = Array(2, 3) -scala> xs ++ ys -val res0: List[Int] = List(0, 1, 2, 3) -``` -The last input made use of an implicit conversion from `Array[Int]` to `IterableOnce[Int]` which is defined as a Scala 2 style implicit conversion in the standard library. Once the standard library is rewritten with Scala 3 conversions, this will -require a language import at the use site, which is clearly unacceptable. It is possible to avoid the need for implicit conversions using method overloading or type classes, but this often leads to longer and more complicated code, and neither of these alternatives work for vararg parameters. - -This is where the `into` modifier on parameter types comes in. Here is a signature of the `++` method on `List[A]` that uses it: -```scala - def ++ (elems: into IterableOnce[A]): List[A] -``` -The `into` modifier on the type of `elems` means that implicit conversions can be applied to convert the actual argument to an `IterableOnce` value, and this without needing a language import. - -## Function arguments - -`into` also allows conversions on the results of function arguments. For instance, consider the new proposed signature of the `flatMap` method on `List[A]`: - -```scala - def flatMap[B](f: A => into IterableOnce[B]): List[B] -``` -This accepts all actual arguments `f` that, when applied to an `A`, give a result -that is convertible to `IterableOnce[B]`. So the following would work: -```scala -scala> val xs = List(1, 2, 3) -scala> xs.flatMap(x => x.toString * x) -val res2: List[Char] = List(1, 2, 2, 3, 3, 3) -``` -Here, the conversion from `String` to `Iterable[Char]` is applied on the results of `flatMap`'s function argument when it is applied to the elements of `xs`. - -## Vararg arguments - -When applied to a vararg parameter, `into` allows a conversion on each argument value individually. For example, consider a method `concatAll` that concatenates a variable -number of `IterableOnce[Char]` arguments, and also allows implicit conversions into `IterableOnce[Char]`: - -```scala -def concatAll(xss: (into IterableOnce[Char])*): List[Char] = - xss.foldLeft(List[Char]())(_ ++ _) -``` -Here, the call -```scala -concatAll(List('a'), "bc", Array('d', 'e')) -``` -would apply two _different_ implicit conversions: the conversion from `String` to `Iterable[Char]` gets applied to the second argument and the conversion from `Array[Char]` to `Iterable[Char]` gets applied to the third argument. - -Note that a vararg parameter type with into modifiers needs to be put in parentheses, as is shown in the example above. This is to make the precedence clear: each element of the argument sequence is converted by itself. - -## Retrofitting Scala 2 libraries - -There is also an annotation `@into` in the `scala.annotation` package that has -the same effect as an `into` modifier. It is intended to be used for retrofitting Scala 2 library code so that Scala 3 conversions can be applied to arguments without language imports. For instance, the definitions of -`++` and `flatMap` in the Scala 2.13 `List` class could be retrofitted as follows. -```scala - def ++ (elems: IterableOnce[A] @into): List[A] - def flatMap[B](f: A => IterableOnce[B] @into): List[B] -``` -For Scala 3 code, the `into` modifier is preferred, because it adheres to the principle that annotations should not influence typing and type inference in Scala. - -## Restrictions - -The `into` modifier is only allowed in the types of method parameters. It can be given either for the whole type, or some result type of a top-level function type, but not anywhere else. The `into` modifier does not propagate outside the method. In particular, a partially applied method does not propagate `into` modifiers to its result. - -**Example:** - -Say we have -```scala -def f(x: Int)(y: into Text): Unit -``` -then -```scala -f(3) : Text => Unit -``` -Note the `into` modifier is not longer present on the type of `f(3)`. Therefore, follow-on arguments to `f(3)` do not allow implicit conversions. Generally it is not possible to -define function types that allow implicit conversions on their arguments, but it is possible to define SAM types that allow conversions. E.g. -```scala -trait ConvArg: - def apply(x: into Text): Unit - -val x: ConvArg = f(3)(_) -``` - -Note this is similar to the way vararg parameters are handled in Scala. If we have -```scala -def g(x: Int)(y: Int*): Unit -``` -then -```scala -g(4) : Seq[Int] => Unit -``` -Observe that the vararg annotation also got dropped in the result type of `g(4)`. - -## Syntax changes - -The addition to the grammar is: -``` -ParamType ::= [‘=>’] ParamValueType -ParamValueType ::= Type [‘*’] - | IntoType - | ‘(’ IntoType ‘)’ ‘*’ -IntoType ::= [‘into’] IntoTargetType - | ‘(’ IntoType ‘)’ -IntoTargetType ::= Type - | FunTypeArgs (‘=>’ | ‘?=>’) IntoType -``` -As the grammar shows, `into` can only applied in the type of a parameter; it is illegal in other positions. Also, `into` modifiers in vararg types have to be enclosed in parentheses. diff --git a/docs/_docs/reference/experimental/into.md b/docs/_docs/reference/experimental/into.md new file mode 100644 index 000000000000..8b54865c2cba --- /dev/null +++ b/docs/_docs/reference/experimental/into.md @@ -0,0 +1,285 @@ +--- +layout: doc-page +title: The `into` Type and Modifier +redirectFrom: /docs/reference/other-new-features/into.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/into.html +--- + +This feature is not yet part of the Scala 3 language definition. It can be made available by a language import: + +```scala +import scala.language.experimental.into +``` + + +## Summary + +Scala 3 offers two alternative schemes to allow implicit conversions using Scala-3's `Conversion` +class without requiring a language import. + +The first scheme is +to have a special type `into[T]` which serves as a marker that conversions into that type are allowed. These types are typically used in parameters of methods that are designed to work with implicit conversions of their arguments. This allows fine-grained control over where implicit conversions should be allowed. We call this scheme "_into as a type constructor_". + +The second scheme allows `into` as a soft modifier on traits, classes, and opaque type aliases. If a type definition is declared with this modifier, conversions to that type are allowed. The second scheme requires that one has control over the conversion target types so that an `into` can be added to their declaration. It is appropriate where there are a few designated types that are meant to be conversion targets. If that's the case, migration from Scala 2 to Scala 3 +becomes easier since no function signatures need to be rewritten. We call this scheme "_into as a modifier_". + + +## Motivation + +Scala 3's implicit conversions of the `scala.Conversion` class require a language import +``` +import scala.language.implicitConversions +``` +in any code that uses them as implicit conversions (code that calls conversions explicitly is not affected). If the import is missing, a feature warning is currently issued, and this will become an error in future versions of Scala 3. The motivation for this restriction is two-fold: + + - Code with hidden implicit conversions is hard to understand and might have correctness or performance issues that go undetected. + - If we require explicit user opt-in for implicit conversions, we can significantly improve type inference by propagating expected type information more widely in those parts of the program where there is no opt-in. + +There is one broad use case, however, where implicit conversions are very hard to replace. This is the case where an implicit conversion is used to adapt a method argument to its formal parameter type. An example from the standard library: +```scala +scala> val xs = List(0, 1) +scala> val ys = Array(2, 3) +scala> xs ++ ys +val res0: List[Int] = List(0, 1, 2, 3) +``` +The input line `xs ++ ys` makes use of an implicit conversion from `Array[Int]` to `IterableOnce[Int]`. This conversion is defined in the standard library as an `implicit def`. Once the standard library is rewritten with Scala 3 conversions, this will require a language import at the use site, which is clearly unacceptable. It is possible to avoid the need for implicit conversions using method overloading or type classes, but this often leads to longer and more complicated code, and neither of these alternatives work for vararg parameters. + +## First Scheme: `into` as a Type Constructor + +This is where the `into` type constructor comes in. Here is a signature of a `++` method on `List[A]` that uses it: + +```scala + def ++ (elems: into[IterableOnce[A]]): List[A] +``` +The `into` wrapper on the type of `elems` means that implicit conversions can be applied to convert the actual argument to an `IterableOnce` value, and this without needing a language import. + +`into` is defined as follows in the companion object of the `scala.Conversion` class: +```scala +opaque type into[T] >: T = T +``` +Types of the form `into[T]` are treated specially during type checking. If the expected type of an expression is `into[T]` then an implicit conversion to that type can be inserted without the need for a language import. + +Note: Unlike other types, `into` starts with a lower-case letter. This emphasizes the fact that `into` is treated specially by the compiler, by making `into` look more like a keyword than a regular type. + +### Example 1 + +```scala +given Conversion[Array[Int], IterableOnce[Int]] = wrapIntArray +val xs: List[Int] = List(1) +val ys: Array[Int] = Array(2, 3) +xs ++ ys +``` +This inserts the given conversion on the `ys` argument in `xs ++ ys`. It typechecks without a feature warning since the formal parameter of `++` is of type `into[IterableOnce]`, which is also the expected type of `ys`. + +### Example 2 + +Consider a simple expression AST type: +```scala +enum Expr: + case Neg(e: Expr) + case Add(e1: Expr, e2: Expr) + case Const(n: Int) +import Expr.* +``` +Say we'd like to build `Expr` trees without explicit `Const` wrapping, as in `Add(1, Neg(2))`. The usual way to achieve this is with an implicit conversion from `Int` to `Const`: +```scala +given Conversion[Int, Const] = Const(_) +``` +Normally, that would require a language import in all source modules that construct `Expr` trees. We can avoid this requirement on user code by declaring `Neg` and `Add` with `into` parameters: +```scala +enum Expr: + case Neg(e: into[Expr]) + case Add(e1: into[Expr], e2: into[Expr]) + case Const(n: Int) +``` +This would allow conversions from `Int` to `Const` when constructing trees but not elsewhere. + +### `into` in Function Results + +`into` allows conversions everywhere it appears as expected type, including in the results of function arguments. For instance, consider the new proposed signature of the `flatMap` method on `List[A]`: + +```scala + def flatMap[B](f: A => into[IterableOnce[B]]): List[B] +``` +This accepts all actual arguments `f` that, when applied to an `A`, give a result +that is convertible to `IterableOnce[B]`. So the following would work: +```scala +scala> val xs = List(1, 2, 3) +scala> xs.flatMap(x => x.toString * x) +val res2: List[Char] = List(1, 2, 2, 3, 3, 3) +``` +Here, the conversion from `String` to `Iterable[Char]` is applied on the results of `flatMap`'s function argument when it is applied to the elements of `xs`. + +### Vararg arguments + +When applied to a vararg parameter, `into` allows a conversion on each argument value individually. For example, consider a method `concatAll` that concatenates a variable +number of `IterableOnce[Char]` arguments, and also allows implicit conversions into `IterableOnce[Char]`: + +```scala +def concatAll(xss: into[IterableOnce[Char]]*): List[Char] = + xss.foldRight(Nil)(_ ++: _) +``` +Here, the call +```scala +concatAll(List('a'), "bc", Array('d', 'e')) +``` +would apply two _different_ implicit conversions: the conversion from `String` to `Iterable[Char]` gets applied to the second argument and the conversion from `Array[Char]` to `Iterable[Char]` gets applied to the third argument. + + +### Unwrapping `into` + +Since `into[T]` is an opaque type, its run-time representation is just `T`. +At compile time, the type `into[T]` is a known supertype of the type `T`. So if `t: T`, then +```scala + val x: into[T] = t +``` +typechecks but +```scala +val y: T = x // error +``` +is ill-typed. We can recover the underlying type `T` using the `underlying` extension method which is also defined in object `Conversion`: +```scala +import Conversion.underlying + +val y: T = x.underlying // ok +``` +However, the next section shows that unwrapping with `.underlying` is not needed for parameters, which is the most common use case. So explicit unwrapping should be quite rare. + + + +### Dropping `into` for Parameters in Method Bodies + +The typical use cases for `into` wrappers are for parameters. Here, they specify that the +corresponding arguments can be converted to the formal parameter types. On the other hand, inside a method, a parameter type can be assumed to be of the underlying type since the conversion already took place when the enclosing method was called. This is reflected in the type system which erases `into` wrappers in the local types of parameters +as they are seen in a method body. Here is an example: +```scala + def ++ (elems: into[IterableOnce[A]]): List[A] = + val buf = ListBuffer[A]() + for elem <- elems.iterator do // no `.underlying` needed here + buf += elems + buf.toList +``` +Inside the `++` method, the `elems` parameter is of type `IterableOnce[A]`, not `into[IterableOne[A]]`. Hence, we can simply write `elems.iterator` to get at the `iterator` method of the `IterableOnce` class. + +Specifically, we erase all `into` wrappers in the local types of parameter types that appear in covariant or invariant position. Contravariant `into` wrappers are kept since these typically are on the parameters of function arguments. + +### Into Constructors in Type Aliases + +Since `into` is a regular type constructor, it can be used anywhere, including in type aliases and type parameters. For instance, in the Scala standard library we could define +```scala +type ToIterator[T] = into[IterableOnce[T]] +``` +and then `++`, `flatMap` and other functions could use this alias in their parameter types. The effect would be the same as when `into` is written out explicitly. + +## Second Scheme: `into` as a Modifier + +The `into` scheme discussed so far strikes a nice balance between explicitness and convenience. But migrating to it from Scala 2 implicits does require major changes since possibly a large number of function signatures has to be changed to allow conversions on the arguments. This might ultimately hold back migration to Scala 3 implicits. + +To facilitate migration, we also introduce an alternative way to specify target types of implicit conversions. We allow `into` as a soft modifier on +classes, traits, and opaque type aliases. If a type definition is declared with `into`, then implicit conversions into that type don't need a language import. + +For instance, the Laminar framework +defines a trait `Modifier` that is commonly used as a parameter type of user-defined methods and that should support implicit conversions into it. +`Modifier` is commonly used as a parameter type in both Laminar framework functions and in application-level functions that use Laminar. + +We can support implicit conversions to `Modifier`s simply by making `Modifier` an `into` trait: +```scala +into trait Modifier ... +``` +This means implicit `Conversion` instances with `Modifier` results can be inserted without requiring a language import. + +Here is a simplified example: +```scala +trait Modifier +given Conversion[Option[Node], Modifier] = ... +given Conversion[Seq[Node], Modifier] = ... + +def f(x: Source, m: Modifier) = ... +f(source, Some(node)) // inserts conversion +``` + +The `into`-as-a-modifier scheme is handy in codebases that have a small set of specific types that are intended as the targets of implicit conversions defined in the same codebase. Laminar's `Modifier` is a typical example. But the scheme can be easily abused by making the number of `into` types too large. One should restrict the number of `into`-declared types to the absolute minimum. In particular, never make a type `into` to just cater for the possibility that someone might want to later add an implicit conversion to it. + + +## Details: Conversion target types + +To make the preceding descriptions more precise: An implicit conversion is permitted without an `implicitConversions` language import if the target type is a valid conversion target type. A valid conversion target type is one of the following: + + - A type of the form `into[T]`. + - A reference `p.C` to a class, trait, or opaque type alias `C` that is declared with an `into` modifier. The reference can be followed by type arguments. + - A type alias of a valid conversion target type. + - A match type that reduces to a valid conversion target type. + - An annotated type `T @ann` where `T` is a valid conversion target type. + - A refined type `T {...}` where `T` is a valid conversion target type. + - A union `T | U` of two valid conversion target types `T` and `U`. + - An intersection `T & U` of two valid conversion target types `T` and `U`. + - An instance of a type parameter that is explicitly instantiated to a valid conversion target type. + +Type parameters that are not fully instantiated do not count as valid conversion target types. For instance, consider: + +```scala + trait Token + class Keyword(str: String) + given Conversion[String, Keyword] = KeyWord(_) + + List[into[Keyword]]("if", "then", "else") +``` +This type-checks since the target type of the list elements is the type parameter of the `List.apply` method which is explicitly instantiated to `into[Keyword]`. On the other hand, if we continue the example as follows we get an error: +```scala + val ifKW: into[Keyword] = "if" + val ys: List[into[Keyword]] = List(ifKW, "then", "else") +``` +Here, the type variable of `List.apply` is not explicitly instantiated +when we check the `List(...)` arguments (it is just upper-bounded by the target type `into[Keyword]`). This is not enough to allow +implicit conversions on the second and third arguments. + +Subclasses of `into` classes or traits do not count as valid conversion target types. For instance, consider: + +```scala +into trait T +class C(x: Int) extends T +given Conversion[Int, C] = C(_) + +def f(x: T) = () +def g(x: C) = () +f(1) // ok +g(1) // error +``` +The call `f("abc")` type-checks since `f`'s parameter type `T` is `into`. +But the call `g("abc")` does not type-check since `g`'s parameter type `C` is not `into`. It does not matter that `C` extends a trait `T` that is `into`. + + +## Why Two Different Schemes? + +Can we make do with just one scheme instead of two? In practice this would be difficult. + +Let's first take a look the `Expr` example, which uses into-as-a-constructor. Could it be rewritten to use into-as-a-modifier? +This would mean we have to add `into` to the whole `Expr` enum. Adding it to just `Const` is not enough, since `Add` and `Neg` take `Expr` arguments, not `Const` arguments. + +But we might not always have permission to change the `Expr` enum. For instance, `Expr` could be defined in a lower level library without implicit conversions, but later we want to make `Expr` construction convenient by eliding `Const` wrappers in some higher-level library or application. With `into` constructors, this is easy: Define the implicit conversion and facade methods that construct `Expr` trees while taking `into[Expr]` parameters. +With `into` modifiers there is no way to achieve the same. + +A possibly more important objection is that even if we could add the `into` modifier to `Expr`, it would be bad style to do so! We want to allow for implicit conversion in the very specific case where we build an `Expr` tree using the `Add` and `Neg` constructors. Our applications could have lots of other methods that take `Expr` trees, for instance to analyze them or evaluate them. +We probably do not want to allow implicit conversions for the arguments of all these other methods. The `into` modifier is too unspecific to distinguish the good use case from the problematic ones. + +On the other hand, there are also situations where into-as-a-modifier is the practical choice. To see this, consider again the `Modifier` use case in Laminar. +We could avoid the `into` modifier by wrapping all `Modifier` parameters +with the `into` constructor. This would be a lot more work than adding just the single `into` modifier. Worse, functions taking `Modifier` parameters are found both in the Laminar framework code and in many applications using it. The framework and the applications would have to be upgraded in lockstep. When Laminar upgrades to Scala 3 implicits, all applications would have to be rewritten, which would make such a migration very cumbersome. + +One can try to mitigate the effort by playing with type aliases. For instance, a hypothetical future Laminar using Scala 3 conversions could rename the +trait `Modifier` to `ModifierTrait` and define an alias +```scala +type Modifier = into[ModifierTrait] +``` +Then the source code of applications would not have to change (unless these applications define classes directly extending `Modifier`). But that future Laminar would not be binary compatible with the current one, since the name +of the original `Modifier` trait has changed. In summary, upgrading Laminar to use Scala 3 conversions could keep either source compatibility or binary compatibility but not both at the same time. + + +## Syntax Changes + +``` +LocalModifier ::= ... | ‘into’ +``` + +`into` is a soft modifier. It is only allowed classes, traits, and opaque type aliases. + diff --git a/docs/_docs/reference/experimental/modularity.md b/docs/_docs/reference/experimental/modularity.md index 1a3d47695861..580044ce4d66 100644 --- a/docs/_docs/reference/experimental/modularity.md +++ b/docs/_docs/reference/experimental/modularity.md @@ -196,6 +196,43 @@ LocalModifier ::= ‘tracked’ The (soft) `tracked` modifier is allowed as a local modifier. +## Applied constructor types + +A new syntax is also introduced, to make classes with `tracked` parameters +easier to use. The new syntax is essentially the ability to use an application +of a class constructor as a type, we call such types applied constructor types. + +With this new feature the following example compiles correctly and the type in +the comment is the resulting type of the applied constructor types. + +```scala +import scala.language.experimental.modularity + +class C(tracked val v: Any) + +val c: C(42) /* C { val v: 42 } */ = C(42) +``` + +### Syntax change + +``` +SimpleType ::= SimpleLiteral + | ‘?’ TypeBounds +--- | SimpleType1 ++++ | SimpleType1 {ParArgumentExprs} +``` + +A `SimpleType` can now optionally be followed by `ParArgumentExprs`. + +The arguments are used to typecheck the whole type, as if it was a normal +constructor application. For classes with `tracked` parameters this will mean +that the resulting type will have a refinement for each `tracked` parameter. + +For example, given the following class definition: +```scala +class Person(tracked val name: String, tracked val age: Int) +``` +**Type** `Person("Kasia", 27)` will be translated to `Person { val name: "Kasia"; val age: 27 }`. ## Allow Class Parents to be Refined Types diff --git a/docs/_docs/reference/experimental/typeclasses.md b/docs/_docs/reference/experimental/typeclasses.md index add5853e10ba..0fc4fa043940 100644 --- a/docs/_docs/reference/experimental/typeclasses.md +++ b/docs/_docs/reference/experimental/typeclasses.md @@ -25,7 +25,7 @@ in source version `future` if the additional experimental language import `modul ``` It is intended to turn features described here into proposals under the Scala improvement process. A first installment is SIP 64, which covers some syntactic changes, names for context bounds, multiple context bounds and deferred givens. This SIP has been accepted for inclusion in the language and will be released in Scala 3.6. The remaining elements -that concern type classes are described in the following. There is also a separate [page on modularity improvements](../modularity.md) that describes proposed additions not directly related to type classes. +that concern type classes are described in the following. There is also a separate [page on modularity improvements](modularity.md) that describes proposed additions not directly related to type classes. ## Generalizing Context Bounds diff --git a/docs/_docs/reference/features-classification.md b/docs/_docs/reference/features-classification.md index 550130780b44..ce5318859ce0 100644 --- a/docs/_docs/reference/features-classification.md +++ b/docs/_docs/reference/features-classification.md @@ -40,7 +40,7 @@ These constructs replace existing constructs with the aim of making the language - [Extension methods](contextual/extension-methods.md) replace implicit classes with a clearer and simpler mechanism. - [Opaque type aliases](other-new-features/opaques.md) replace most uses of value classes while guaranteeing absence of boxing. - - [Top-level definitions](dropped-features/package-objects.md) replace package objects, dropping syntactic boilerplate. + - [Top-level definitions](other-new-features/toplevel-definitions.md) replace package objects, dropping syntactic boilerplate. - [Export clauses](other-new-features/export.md) provide a simple and general way to express aggregation, which can replace the previous facade pattern of package objects inheriting from classes. diff --git a/docs/_docs/reference/metaprogramming/reflection.md b/docs/_docs/reference/metaprogramming/reflection.md index 65ae2f733b7a..71f4555e6418 100644 --- a/docs/_docs/reference/metaprogramming/reflection.md +++ b/docs/_docs/reference/metaprogramming/reflection.md @@ -5,15 +5,17 @@ nightlyOf: https://docs.scala-lang.org/scala3/reference/metaprogramming/reflecti --- Reflection enables inspection and construction of Typed Abstract Syntax Trees -(Typed-AST). It may be used on quoted expressions (`quoted.Expr`) and quoted -types (`quoted.Type`) from [Macros](./macros.md) or on full TASTy files. +(Typed-AST). +It may be used on quoted expressions (`quoted.Expr`) and quoted +types (`quoted.Type`) from [Macros](./macros.md) or [multi-staging-programming](./staging.md), +or on whole TASTy files (via [tasty-inspection](./tasty-inspect.md)). If you are writing macros, please first read [Macros](./macros.md). You may find all you need without using quote reflection. -## API: From quotes and splices to TASTy reflect trees and back +## Converting `Expr`s to TASTy reflect trees and back -With `quoted.Expr` and `quoted.Type` we can compute code but also analyze code +With `quoted.Expr` and `quoted.Type` we can not only compute code but also analyze code by inspecting the ASTs. [Macros](./macros.md) provide the guarantee that the generation of code will be type-correct. Using quote reflection will break these guarantees and may fail at macro expansion time, hence additional explicit @@ -33,10 +35,79 @@ def natConstImpl(x: Expr[Int])(using Quotes): Expr[Int] = ... ``` -### Extractors +We can access the underlying typed AST of an `Expr` using the `asTerm` extension method: -`import quotes.reflect.*` will provide all extractors and methods on `quotes.reflect.Tree`s. -For example the `Literal(_)` extractor used below. +```scala + val term: Term = x.asTerm +``` + +Similarly, you can change a `Term` back into an `Expr` with `.asExpr` (returning `Expr[Any]`) +or `.asExprOf[T]` (returning `Expr[T]`, with an exception being thrown at macro-expansion time if the type does not conform). + +## Constructing and Analysing trees + +Generally, there are 3 main types of constructs you need to know to properly construct and analyse Typed ASTs: +* Trees +* Symbols with Flags +* TypeReprs + +### Typed Abstract Syntax Trees +Typed AST is a tree-like representation of the code of a program achieved after typing. +It’s represented by the `Tree` type in the reflection API. + +`Terms` are subtypes of trees that represent an expression of certain value. Because of this, +they always have a type associated with them (accessible with `.tpe`). `Terms` can be transformed into `Exprs` with `.asExpr`. + +Let’s look at an example in how the `Trees` map into real scala code: + +```scala + val foo: Int = 0 +``` +The above is represented in the quotes reflect API by a `ValDef` (a subtype of `Tree`, but not `Term`!): +```scala + ValDef(foo,Ident(Int),Literal(Constant(0))) // ValDef is a subtype of Tree but not Term +``` + +```scala + val foo: Int = 0 + foo + 1 +``` +The above is represented in the quotes reflect API by a `Block` (a subtype of `Term`, itself a subtype of `Tree`) +```scala + Block( + List( + ValDef(foo,Ident(Int),Literal(Constant(0))) + ), + Apply( + Select(Ident(foo),+), + List(Literal(Constant(1))) + ) + ) +``` + +You can see the whole hierarchy between different types of Trees in +[`reflectModule` documentation](https://scala-lang.org/api/3.3_LTS/scala/quoted/Quotes$reflectModule.html#`). + +You can also check the shape of code by printing out quoted code transformed into a Term: +```scala + println( '{ scalaCode }.asTerm ) +``` +Bear in mind this will always produce a Term. E.g.: +```scala + '{ + val foo: Int = 0 + }.asTerm +``` +Is represented as `Block(List(ValDef(foo,Ident(Int),Literal(Constant(0)))),Literal(Constant(())))`, which is actually a `Block` of `Unit` type: +```scala + '{ + val foo: Int = 0 + () + } +``` +#### Tree Extractors and Constructors +`import quotes.reflect.*` provides all extractors, apply-based constructors and methods on `quotes.reflect.Tree`s. +For example, see the `Literal(_)` extractor used below. ```scala def natConstImpl(x: Expr[Int])(using Quotes): Expr[Int] = @@ -54,7 +125,7 @@ def natConstImpl(x: Expr[Int])(using Quotes): Expr[Int] = '{0} ``` -We can easily know which extractors are needed using `Printer.TreeStructure.show`, +We can easily know which extractors/constructors are needed using `Printer.TreeStructure.show`, which returns the string representation the structure of the tree. Other printers can also be found in the `Printer` module. @@ -64,14 +135,121 @@ tree.show(using Printer.TreeStructure) Printer.TreeStructure.show(tree) ``` -The methods `quotes.reflect.Term.{asExpr, asExprOf}` provide a way to go back to -a `quoted.Expr`. Note that `asExpr` returns a `Expr[Any]`. On the other hand -`asExprOf[T]` returns a `Expr[T]`, if the type does not conform to it an exception -will be thrown at runtime. +Bear in mind that extractors and constructors for the same trees might be comprised of different arguments, e.g. for `ValDef` the `apply` method +has `(Symbol, Option[Term])` arguments and `unapply` has `(String, TypeTree, Option[Term])` (if we want to obtain the symbol directly, we can call `.symbol` on the `ValDef`). + +### Symbols +To construct definition `Trees` we might have to create or use a `Symbol`. Symbols represent the "named" parts of the code, the declarations we can reference elsewhere later. Let’s try to create `val name: Int = 0` from scratch. +To create a val like this, we need to first create a `Symbol` that matches the intended `Tree` type, so for a `ValDef` we would use the `Symbol.newVal` method: +```scala + import quotes.reflect._ + val fooSym = Symbol.newVal( + parent = Symbol.spliceOwner, + name = "foo", + tpe = TypeRepr.of[Int], + flags = Flags.EmptyFlags, + privateWithin = Symbol.noSymbol + ) + val tree = ValDef(fooSym, Some(Literal(IntConstant(0)))) +``` +Generally, every `Symbol` needs to have an parent/owner `Symbol`, signifying where it is defined. +E.g if we want to define the val as part of a class, then naturally, we need that class' symbol to be the owner of the val symbol. +You may also notice the flags and privateWithin arguments, which are explained later in the `Flags` chapter. + +The created val can be later referenced in other parts of the generated code with the use of `Ref` (a subtype of `Term`): +```scala + Ref(fooSym) +``` +For referencing types (e.g. ones created with `Symbol.newType` or `Symbol.newClass`), use `TypeIdent` (a subtype of `TypeTree`) instead. + +#### Flags +`Flags` tell us about various attributes of `Symbols`. These can include access modifiers, +whether the symbol was defined in Scala 2 or Java, whether it's `inline` or `transparent`, whether it was generated by the compiler, etc. + +They are implemented as a bit set, with the `.is` method allowing to check if a given `Flags` is a subset, and `.|` with `.&` allowing to +get a union or intersection respectively. You can see the available individual `Flags` from which to create the sets in the +[api documentation](https://scala-lang.org/api/3.3_LTS/scala/quoted/Quotes$reflectModule$FlagsModule.html). + +It's worth thinking about individual `Flags` more in terms of explicitly stated modifiers, instead of general attributes. +For example, while we might say that every trait is `abstract`, a symbol of a trait will not have their `abstract` flag set +(just the `trait` flag instead), simply because it does not make sense to have an `abstract trait`. + +Different types of Symbols have different flags allowed to be set, as stated in the API docs for individual `Symbol` constructor methods. + +### TypeReprs and TypeTrees +When writing macros, we have access to `scala.quoted.Type`, which we can use to assign types in quoted code. +In the context of the reflection api however, it won't be of much use. We can convert it into a more useful +`TypeRepr` with `TypeRepr.of[T]` (when we have a given Type[T] in scope) which we can also convert back into a `Type`, with the simplest method being: +```scala +typeRepr.asType match + case '[t] => + // access to a given Type[t] in scope +``` + +`TypeRepr`s are a type representation used when assigning and reading types from `Symbols`. It can be constructed/read similarly to the Typed AST trees. E.g.: +```Scala + List[String] +``` +is represented as: +```scala + AppliedType( + TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class collection)),object immutable),List), + List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class java)),object lang),String)) + ) +``` +Similarly to [Typed ASTs](#typed-abstract-syntax-trees), you can find the `TypeRepr` type hierarchy in +[reflectModule](https://scala-lang.org/api/3.3_LTS/scala/quoted/Quotes$reflectModule.html) docs. +Most of the nodes like `AppliedType` `AndType`, `MethodType`, etc. should be self explanatory, +but `TypeRef` and `TermRef` might require some additional context: +* `TypeRef(prefix, typeSymbol)` - corresponds to a selection of a type. E.g.: if `SomeType` is a type located in `prefix`, +and `someTypeSymbol` is its `Symbol`, `TypeRef(prefix, someTypeSymbol)` will correspond to prefix.SomeType +* `TermRef(prefix, termSymbol)` - corresponds to a selection on a term, which can also be useful if we are trying †o get a path dependent type. +E.g.: if `someVal` is a val in `prefix`, and `someValSymbol` is its symbol, then `TermRef(prefix, someValSymbol)` will correspond +to `prefix.someVal.type`. TermRef can be widened into their underlying non-TermRef type with `.widenByTermRef`. + +Generally, if we need to insert a type directly as part of a tree (e.g. when passing it as a type parameter with a `TypeApply`), +we would use a `TypeTree` (subtype of `Tree`) instead. + +#### Extracting TypeReprs from Symbols + +Since `TypeReprs` allow us to create and analyse `Symbols`, we might expect there to be a method to obtain the type of a `Symbol`. +While there do exist `.typeRef` and `.termRef` methods, they can only generate TypeRefs or TermRefs that are usable only in +the scope of it's owner. E.g. for: +```scala + val value: List[String] = List("") +``` +If we were to call `.typeRef` on the symbol of value, we would get `TypeRef(This(...), valueSymbol)`, instead of `List[String]`. +This is because **Symbols hold incomplete type information**. +Let's look at the following: +```scala +class Outer[T]: + val inner: List[T] = ??? +``` +The type of `inner` depends on the type parameter of `Outer` - so just having the symbol of `inner` +(which has no information about its prefix, in fact the symbols of `new Outer[Int].inner` and `new Outer[String].inner` are equal) is not enough. +However, we can still read the type if we have the prefixing `TypeRepr` with `prefix.memberType(symbol)` or `prefix.select(symbol)`: +```scala +val prefix = TypeRepr.of[Outer[String]] +val innerSymbol = Symbol.classMember +prefix.memberType(innerSymbol) +// The above returns: +// +// AppliedType( +// TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class collection)),object immutable),List), +// List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class java)),object lang),String)) +// ) +``` + +### Navigating the API documentation +All Quotes reflection API documentation can be found inside of the +[reflectModule](https://scala-lang.org/api/3.3_LTS/scala/quoted/Quotes$reflectModule.html) trait in the scala library API docs. +Due to the implementation details, methods relevant to a certain type are split between `_Module` and `_Methods` traits. +For example, if we were to work on a `Select` node, the static methods like `apply` and `unapply` would be found in `SelectModule`, +and methods on instances of `Select` would be found in `SelectMethods`. ### Positions -The `Position` in the context provides an `ofMacroExpansion` value. It corresponds +The `Position` in the `quotes.reflect.*` provides an `ofMacroExpansion` value. It corresponds to the expansion site for macros. The macro authors can obtain various information about that expansion site. The example below shows how we can obtain position information such as the start line, the end line or even the source code at the @@ -94,7 +272,7 @@ def macroImpl()(quotes: Quotes): Expr[Unit] = ... ``` -### Tree Utilities +## Tree Utilities `quotes.reflect` contains three facilities for tree traversal and transformation. @@ -118,12 +296,12 @@ def collectPatternVariables(tree: Tree)(using ctx: Context): List[Symbol] = ``` A `TreeTraverser` extends a `TreeAccumulator[Unit]` and performs the same traversal -but without returning any value. +but without returning any value. `TreeMap` transforms trees along the traversal, through overloading its methods it is possible to transform only trees of specific types, for example `transformStatement` only transforms `Statement`s. -#### ValDef.let +### ValDef.let The object `quotes.reflect.ValDef` also offers a method `let` that allows us to bind the `rhs` (right-hand side) to a `val` and use it in `body`. Additionally, `lets` binds the given `terms` to names and allows to use them in the `body`. diff --git a/docs/_docs/reference/other-new-features/indentation.md b/docs/_docs/reference/other-new-features/indentation.md index 9963d1ee7577..103de5d6797a 100644 --- a/docs/_docs/reference/other-new-features/indentation.md +++ b/docs/_docs/reference/other-new-features/indentation.md @@ -189,7 +189,7 @@ Define for an arbitrary sequence of tokens or non-terminals `TS`: ```ebnf :<<< TS >>> ::= ‘{’ TS ‘}’ - | + | TS ``` Then the grammar changes as follows: ```ebnf diff --git a/docs/_docs/reference/other-new-features/toplevel-definitions.md b/docs/_docs/reference/other-new-features/toplevel-definitions.md index b1793bd1941c..29da270729cb 100644 --- a/docs/_docs/reference/other-new-features/toplevel-definitions.md +++ b/docs/_docs/reference/other-new-features/toplevel-definitions.md @@ -1,7 +1,7 @@ --- layout: doc-page title: "Toplevel Definitions" -nightlyOf: https://docs.scala-lang.org/scala3/reference/dropped-features/toplevel-definitions.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/other-new-features/toplevel-definitions.html --- All kind of definitions can now be written at the top-level. diff --git a/docs/_docs/reference/overview.md b/docs/_docs/reference/overview.md index bdb8aa74c1aa..35a3a58733f1 100644 --- a/docs/_docs/reference/overview.md +++ b/docs/_docs/reference/overview.md @@ -46,7 +46,7 @@ These constructs replace existing constructs with the aim of making the language replace implicit classes with a clearer and simpler mechanism. - [Opaque type aliases](other-new-features/opaques.md) replace most uses of value classes while guaranteeing the absence of boxing. -- [Top-level definitions](dropped-features/package-objects.md) +- [Top-level definitions](other-new-features/toplevel-definitions.md) replace package objects, dropping syntactic boilerplate. - [Export clauses](other-new-features/export.md) provide a simple and general way to express aggregation, which can replace diff --git a/docs/_docs/reference/preview/overview.md b/docs/_docs/reference/preview/overview.md index ec8d36bdfd25..62fc499487a1 100644 --- a/docs/_docs/reference/preview/overview.md +++ b/docs/_docs/reference/preview/overview.md @@ -2,6 +2,7 @@ layout: doc-page title: "Preview" nightlyOf: https://docs.scala-lang.org/scala3/reference/preview/overview.html +redirectFrom: overview.html --- ## Preview language features diff --git a/docs/_docs/reference/syntax.md b/docs/_docs/reference/syntax.md index ccba2ec9578a..188294d7a08a 100644 --- a/docs/_docs/reference/syntax.md +++ b/docs/_docs/reference/syntax.md @@ -199,6 +199,7 @@ SimpleType ::= SimpleLiteral | Singleton ‘.’ id | Singleton ‘.’ ‘type’ | ‘(’ [Types] ‘)’ + | ‘(’ NameAndType {‘,’ NameAndType} ‘)’ | Refinement | SimpleType TypeArgs | SimpleType ‘#’ id @@ -220,6 +221,7 @@ ContextBounds ::= ContextBound | '{' ContextBound {',' ContextBound} '}' ContextBound ::= Type ['as' id] Types ::= Type {‘,’ Type} +NameAndType ::= id ‘:’ Type ``` ### Expressions @@ -244,6 +246,7 @@ Expr1 ::= [‘inline’] ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[ | ForExpr | [SimpleExpr ‘.’] id ‘=’ Expr | PrefixOperator SimpleExpr ‘=’ Expr + | InfixExpr id [nl] `=' Expr -- only if language.postfixOps is enabled | SimpleExpr ArgumentExprs ‘=’ Expr | PostfixExpr [Ascription] | ‘inline’ InfixExpr MatchClause @@ -268,6 +271,7 @@ SimpleExpr ::= SimpleRef | ‘new’ ConstrApp {‘with’ ConstrApp} [TemplateBody] | ‘new’ TemplateBody | ‘(’ [ExprsInParens] ‘)’ + | ‘(’ NamedExprInParens {‘,’ NamedExprInParens} ‘)’ | SimpleExpr ‘.’ id | SimpleExpr ‘.’ MatchClause | SimpleExpr TypeArgs @@ -287,6 +291,7 @@ ExprInParens ::= PostfixExpr ‘:’ Type | Expr ParArgumentExprs ::= ‘(’ [ExprsInParens] ‘)’ | ‘(’ ‘using’ ExprsInParens ‘)’ | ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘*’ ‘)’ +NamedExprInParens ::= id ‘=’ ExprInParens ArgumentExprs ::= ParArgumentExprs | BlockExpr BlockExpr ::= <<< (CaseClauses | Block) >>> @@ -333,7 +338,9 @@ SimplePattern1 ::= SimpleRef | SimplePattern1 ‘.’ id PatVar ::= varid | ‘_’ +NamedPattern ::= id ‘=’ Pattern Patterns ::= Pattern {‘,’ Pattern} + | NamedPattern {‘,’ NamedPattern} ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ | ‘(’ [Patterns ‘,’] PatVar ‘*’ ‘)’ diff --git a/docs/_spec/08-pattern-matching.md b/docs/_spec/08-pattern-matching.md index 4a34ae8631c4..c13aa5d44b89 100644 --- a/docs/_spec/08-pattern-matching.md +++ b/docs/_spec/08-pattern-matching.md @@ -616,7 +616,7 @@ new scala.PartialFunction[´S´, ´T´] { def apply(´x´: ´S´): ´T´ = x match { case ´p_1´ => ´b_1´ ... case ´p_n´ => ´b_n´ } - def isDefinedAt(´x´: ´S´): Boolean = { + def isDefinedAt(´x´: ´S´): Boolean = x match { case ´p_1´ => true ... case ´p_n´ => true case _ => false } @@ -626,6 +626,22 @@ new scala.PartialFunction[´S´, ´T´] { Here, ´x´ is a fresh name and ´T´ is the least upper bound of the types of all ´b_i´. The final default case in the `isDefinedAt` method is omitted if one of the patterns ´p_1, ..., p_n´ is already a variable or wildcard pattern. +As a convenience, the partial function may be written using function literal notation: + +```scala +(´x: S´) => e(´x´) match { + case ´p_1´ => ´b_1´ ... case ´p_n´ => ´b_n´ +} +``` +where the selector expression is used for matches in the expansion. +The body of the function must consist solely of the match expression. + +This syntax permits annotating the selector: + +```scala +(´x: S´) => (e(´x´): @unchecked) match { ... } +``` + ###### Example Here's an example which uses `foldLeft` to compute the scalar product of two vectors: diff --git a/docs/_spec/TODOreference/experimental/cc.md b/docs/_spec/TODOreference/experimental/cc.md index 878bc0a64ed6..c2011fbcbc88 100644 --- a/docs/_spec/TODOreference/experimental/cc.md +++ b/docs/_spec/TODOreference/experimental/cc.md @@ -655,7 +655,7 @@ TBD The following options are relevant for capture checking. - **-Ycc** Enables capture checking. - - **-Xprint:cc** Prints the program with capturing types as inferred by capture checking. + - **-Vprint:cc** Prints the program with capturing types as inferred by capture checking. - **-Ycc-debug** Gives more detailed, implementation-oriented information about capture checking, as described in the next section. The implementation supporting capture checking with these options is currently in branch `cc-experiment` on dotty.epfl.ch. diff --git a/docs/sidebar.yml b/docs/sidebar.yml index aecd974326ab..e49bddfd2e7d 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -88,6 +88,7 @@ subsection: - page: reference/other-new-features/experimental-defs.md - page: reference/other-new-features/preview-defs.md - page: reference/other-new-features/binary-literals.md + - page: reference/other-new-features/toplevel-definitions.md - title: Other Changed Features directory: changed-features index: reference/changed-features/changed-features.md @@ -160,8 +161,10 @@ subsection: - page: reference/experimental/numeric-literals.md - page: reference/experimental/explicit-nulls.md - page: reference/experimental/main-annotation.md - - page: reference/experimental/into-modifier.md + - page: reference/experimental/into.md - page: reference/experimental/cc.md + - page: reference/experimental/cc-advanced.md + hidden: true - page: reference/experimental/purefuns.md - page: reference/experimental/tupled-function.md - page: reference/experimental/modularity.md @@ -169,6 +172,7 @@ subsection: - page: reference/experimental/runtimeChecked.md - page: reference/experimental/unrolled-defs.md - page: reference/experimental/package-object-values.md + - page: reference/experimental/quoted-patterns-with-polymorphic-functions.md - page: reference/syntax.md - title: Language Versions index: reference/language-versions/language-versions.md diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala index 8f05d6ad11da..528aa1055c6f 100644 --- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala +++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala @@ -32,7 +32,9 @@ class CompletionTest { @Test def completionFromScalaPackage: Unit = { code"class Foo { val foo: Conv${m1} }" - .completion(("Conversion", Class, "Conversion")) + .completion( + ("Conversion", Class, "Conversion"), + ("Conversion", Module, "Conversion")) } @Test def implicitSearchCrash: Unit = diff --git a/library/src/rootdoc.txt b/library/src/rootdoc.txt new file mode 100644 index 000000000000..458605b1bab6 --- /dev/null +++ b/library/src/rootdoc.txt @@ -0,0 +1,47 @@ +This is the documentation for the Scala standard library. + +== Package structure == + +The [[scala]] package contains core types like [[scala.Int `Int`]], [[scala.Float `Float`]], [[scala.Array `Array`]] +or [[scala.Option `Option`]] which are accessible in all Scala compilation units without explicit qualification or +imports. + +Notable packages include: + + - [[scala.collection `scala.collection`]] and its sub-packages contain Scala's collections framework + - [[scala.collection.immutable `scala.collection.immutable`]] - Immutable, sequential data-structures such as + [[scala.collection.immutable.Vector `Vector`]], [[scala.collection.immutable.List `List`]], + [[scala.collection.immutable.Range `Range`]], [[scala.collection.immutable.HashMap `HashMap`]] or + [[scala.collection.immutable.HashSet `HashSet`]] + - [[scala.collection.mutable `scala.collection.mutable`]] - Mutable, sequential data-structures such as + [[scala.collection.mutable.ArrayBuffer `ArrayBuffer`]], + [[scala.collection.mutable.StringBuilder `StringBuilder`]], + [[scala.collection.mutable.HashMap `HashMap`]] or [[scala.collection.mutable.HashSet `HashSet`]] + - [[scala.collection.concurrent `scala.collection.concurrent`]] - Mutable, concurrent data-structures such as + [[scala.collection.concurrent.TrieMap `TrieMap`]] + - [[scala.concurrent `scala.concurrent`]] - Primitives for concurrent programming such as + [[scala.concurrent.Future `Futures`]] and [[scala.concurrent.Promise `Promises`]] + - [[scala.io `scala.io`]] - Input and output operations + - [[scala.math `scala.math`]] - Basic math functions and additional numeric types like + [[scala.math.BigInt `BigInt`]] and [[scala.math.BigDecimal `BigDecimal`]] + - [[scala.sys `scala.sys`]] - Interaction with other processes and the operating system + - [[scala.util.matching `scala.util.matching`]] - [[scala.util.matching.Regex Regular expressions]] + +Other packages exist. See the complete list on the right. + +Additional parts of the standard library are shipped as separate libraries. These include: + + - [[https://www.scala-lang.org/api/current/scala-reflect/scala/reflect/index.html `scala.reflect`]] - Scala's reflection API (scala-reflect.jar) + - [[https://github.com/scala/scala-xml `scala.xml`]] - XML parsing, manipulation, and serialization (scala-xml.jar) + - [[https://github.com/scala/scala-parallel-collections `scala.collection.parallel`]] - Parallel collections (scala-parallel-collections.jar) + - [[https://github.com/scala/scala-parser-combinators `scala.util.parsing`]] - Parser combinators (scala-parser-combinators.jar) + - [[https://github.com/scala/scala-swing `scala.swing`]] - A convenient wrapper around Java's GUI framework called Swing (scala-swing.jar) + +== Automatic imports == + +Identifiers in the scala package and the [[scala.Predef `scala.Predef`]] object are always in scope by default. + +Some of these identifiers are type aliases provided as shortcuts to commonly used classes. For example, `List` is an alias for +[[scala.collection.immutable.List `scala.collection.immutable.List`]]. + +Other aliases refer to classes provided by the underlying platform. For example, on the JVM, `String` is an alias for `java.lang.String`. diff --git a/library/src/scala/AnyVal.scala.ignore b/library/src/scala/AnyVal.scala.ignore new file mode 100644 index 000000000000..6a3b449ab120 --- /dev/null +++ b/library/src/scala/AnyVal.scala.ignore @@ -0,0 +1,60 @@ +/* + * 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.language.`2.13` + +/** `AnyVal` is the root class of all ''value types'', which describe values + * not implemented as objects in the underlying host system. Value classes + * are specified in Scala Language Specification, section 12.2. + * + * The standard implementation includes nine `AnyVal` subtypes: + * + * [[scala.Double]], [[scala.Float]], [[scala.Long]], [[scala.Int]], [[scala.Char]], + * [[scala.Short]], and [[scala.Byte]] are the ''numeric value types''. + * + * [[scala.Unit]] and [[scala.Boolean]] are the ''non-numeric value types''. + * + * Other groupings: + * + * - The ''subrange types'' are [[scala.Byte]], [[scala.Short]], and [[scala.Char]]. + * - The ''integer types'' include the subrange types as well as [[scala.Int]] and [[scala.Long]]. + * - The ''floating point types'' are [[scala.Float]] and [[scala.Double]]. + * + * A subclass of `AnyVal` is called a ''user-defined value class'' + * and is treated specially by the compiler. Properly-defined user value classes provide a way + * to improve performance on user-defined types by avoiding object allocation at runtime, and by + * replacing virtual method invocations with static method invocations. + * + * User-defined value classes which avoid object allocation... + * + * - must have a single `val` parameter that is the underlying runtime representation. + * - can define `def`s, but no `val`s, `var`s, or nested `trait`s, `class`es or `object`s. + * - typically extend no other trait apart from `AnyVal`. + * - cannot be used in type tests or pattern matching. + * - may not override `equals` or `hashCode` methods. + * + * A minimal example: + * {{{ + * class Wrapper(val underlying: Int) extends AnyVal { + * def foo: Wrapper = new Wrapper(underlying * 19) + * } + * }}} + * + * It's important to note that user-defined value classes are limited, and in some circumstances, + * still must allocate a value class instance at runtime. These limitations and circumstances are + * explained in greater detail in the [[https://docs.scala-lang.org/overviews/core/value-classes.html Value Classes and Universal Traits]]. + */ +transparent abstract class AnyVal extends Any { + def getClass(): Class[_ <: AnyVal] = null +} diff --git a/library/src/scala/AnyValCompanion.scala b/library/src/scala/AnyValCompanion.scala new file mode 100644 index 000000000000..a2e4225208ae --- /dev/null +++ b/library/src/scala/AnyValCompanion.scala @@ -0,0 +1,27 @@ +/* + * 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.language.`2.13` + +/** A common supertype for companion classes of primitive types. + * + * A common trait for /companion/ objects of primitive types comes handy + * when parameterizing code on types. For instance, the specialized + * annotation is passed a sequence of types on which to specialize: + * {{{ + * class Tuple1[@specialized(Unit, Int, Double) T] + * }}} + * + */ +private[scala] trait AnyValCompanion extends Specializable { } diff --git a/library/src/scala/App.scala b/library/src/scala/App.scala new file mode 100644 index 000000000000..75b0a0c78adf --- /dev/null +++ b/library/src/scala/App.scala @@ -0,0 +1,106 @@ +/* + * 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 java.lang.System.{currentTimeMillis => currentTime} + +import scala.language.`2.13` + +import scala.annotation.nowarn +import scala.collection.mutable.ListBuffer + +/** The `App` trait can be used to quickly turn objects + * into executable programs. Here is an example: + * {{{ + * object Main extends App { + * Console.println("Hello World: " + (args mkString ", ")) + * } + * }}} + * + * No explicit `main` method is needed. Instead, + * the whole class body becomes the “main method”. + * + * `args` returns the current command line arguments as an array. + * + * ==Caveats== + * + * '''''It should be noted that this trait is implemented using the [[DelayedInit]] + * functionality, which means that fields of the object will not have been initialized + * before the main method has been executed.''''' + * + * Future versions of this trait will no longer extend `DelayedInit`. + * + * In Scala 3, the `DelayedInit` feature was dropped. `App` exists only in a limited form + * that also does not support command line arguments and will be deprecated in the future. + * + * [[https://docs.scala-lang.org/scala3/book/methods-main-methods.html @main]] methods are the + * recommended scheme to generate programs that can be invoked from the command line in Scala 3. + * + * {{{ + * @main def runMyProgram(args: String*): Unit = { + * // your program here + * } + * }}} + * + * If programs need to cross-build between Scala 2 and Scala 3, it is recommended to use an + * explicit `main` method: + * {{{ + * object Main { + * def main(args: Array[String]): Unit = { + * // your program here + * } + * } + * }}} + */ +@nowarn("""cat=deprecation&origin=scala\.DelayedInit""") +trait App extends DelayedInit { + + /** The time when the execution of this program started, in milliseconds since 1 + * January 1970 UTC. */ + final val executionStart: Long = currentTime + + /** The command line arguments passed to the application's `main` method. + */ + protected final def args: Array[String] = _args + + private[this] var _args: Array[String] = _ + + private[this] val initCode = new ListBuffer[() => Unit] + + /** The init hook. This saves all initialization code for execution within `main`. + * This method is normally never called directly from user code. + * Instead it is called as compiler-generated code for those classes and objects + * (but not traits) that inherit from the `DelayedInit` trait and that do not + * themselves define a `delayedInit` method. + * @param body the initialization code to be stored for later execution + */ + @deprecated("the delayedInit mechanism will disappear", "2.11.0") + override def delayedInit(body: => Unit): Unit = { + initCode += (() => body) + } + + /** The main method. + * This stores all arguments so that they can be retrieved with `args` + * and then executes all initialization code segments in the order in which + * they were passed to `delayedInit`. + * @param args the arguments passed to the main method + */ + final def main(args: Array[String]) = { + this._args = args + for (proc <- initCode) proc() + if (util.Properties.propIsSet("scala.time")) { + val total = currentTime - executionStart + Console.println("[total " + total + "ms]") + } + } +} diff --git a/library/src/scala/Array.scala b/library/src/scala/Array.scala new file mode 100644 index 000000000000..8daa1859cf44 --- /dev/null +++ b/library/src/scala/Array.scala @@ -0,0 +1,694 @@ +/* + * 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.language.`2.13` + +//import scala.collection.generic._ +import scala.collection.{Factory, immutable, mutable} +import mutable.ArrayBuilder +import immutable.ArraySeq +import scala.language.implicitConversions +import scala.reflect.{ClassTag, classTag} +import scala.runtime.BoxedUnit +import scala.runtime.ScalaRunTime +import scala.runtime.ScalaRunTime.{array_apply, array_update} + +/** Utility methods for operating on arrays. + * For example: + * {{{ + * val a = Array(1, 2) + * val b = Array.ofDim[Int](2) + * val c = Array.concat(a, b) + * }}} + * where the array objects `a`, `b` and `c` have respectively the values + * `Array(1, 2)`, `Array(0, 0)` and `Array(1, 2, 0, 0)`. + */ +object Array { + val emptyBooleanArray = new Array[Boolean](0) + val emptyByteArray = new Array[Byte](0) + val emptyCharArray = new Array[Char](0) + val emptyDoubleArray = new Array[Double](0) + val emptyFloatArray = new Array[Float](0) + val emptyIntArray = new Array[Int](0) + val emptyLongArray = new Array[Long](0) + val emptyShortArray = new Array[Short](0) + val emptyObjectArray = new Array[Object](0) + + /** Provides an implicit conversion from the Array object to a collection Factory */ + implicit def toFactory[A : ClassTag](dummy: Array.type): Factory[A, Array[A]] = new ArrayFactory(dummy) + @SerialVersionUID(3L) + private class ArrayFactory[A : ClassTag](dummy: Array.type) extends Factory[A, Array[A]] with Serializable { + def fromSpecific(it: IterableOnce[A]): Array[A] = Array.from[A](it) + def newBuilder: mutable.Builder[A, Array[A]] = Array.newBuilder[A] + } + + /** + * Returns a new [[scala.collection.mutable.ArrayBuilder]]. + */ + def newBuilder[T](implicit t: ClassTag[T]): ArrayBuilder[T] = ArrayBuilder.make[T](using t) + + /** Build an array from the iterable collection. + * + * {{{ + * scala> val a = Array.from(Seq(1, 5)) + * val a: Array[Int] = Array(1, 5) + * + * scala> val b = Array.from(Range(1, 5)) + * val b: Array[Int] = Array(1, 2, 3, 4) + * }}} + * + * @param it the iterable collection + * @return an array consisting of elements of the iterable collection + */ + def from[A : ClassTag](it: IterableOnce[A]): Array[A] = it match { + case it: Iterable[A] => it.toArray[A] + case _ => it.iterator.toArray[A] + } + + private def slowcopy(src : AnyRef, + srcPos : Int, + dest : AnyRef, + destPos : Int, + length : Int): Unit = { + var i = srcPos + var j = destPos + val srcUntil = srcPos + length + while (i < srcUntil) { + array_update(dest, j, array_apply(src, i)) + i += 1 + j += 1 + } + } + + /** Copy one array to another. + * Equivalent to Java's + * `System.arraycopy(src, srcPos, dest, destPos, length)`, + * except that this also works for polymorphic and boxed arrays. + * + * Note that the passed-in `dest` array will be modified by this call. + * + * @param src the source array. + * @param srcPos starting position in the source array. + * @param dest destination array. + * @param destPos starting position in the destination array. + * @param length the number of array elements to be copied. + * + * @see `java.lang.System#arraycopy` + */ + def copy(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int): Unit = { + val srcClass = src.getClass + val destClass = dest.getClass + if (srcClass.isArray && ((destClass eq srcClass) || + (destClass.isArray && !srcClass.getComponentType.isPrimitive && !destClass.getComponentType.isPrimitive))) + java.lang.System.arraycopy(src, srcPos, dest, destPos, length) + else + slowcopy(src, srcPos, dest, destPos, length) + } + + /** Copy one array to another, truncating or padding with default values (if + * necessary) so the copy has the specified length. + * + * Equivalent to Java's + * `java.util.Arrays.copyOf(original, newLength)`, + * except that this works for primitive and object arrays in a single method. + * + * @see `java.util.Arrays#copyOf` + */ + def copyOf[A](original: Array[A], newLength: Int): Array[A] = ((original: @unchecked) match { + case original: Array[BoxedUnit] => newUnitArray(newLength).asInstanceOf[Array[A]] + case original: Array[AnyRef] => java.util.Arrays.copyOf(original, newLength) + case original: Array[Int] => java.util.Arrays.copyOf(original, newLength) + case original: Array[Double] => java.util.Arrays.copyOf(original, newLength) + case original: Array[Long] => java.util.Arrays.copyOf(original, newLength) + case original: Array[Float] => java.util.Arrays.copyOf(original, newLength) + case original: Array[Char] => java.util.Arrays.copyOf(original, newLength) + case original: Array[Byte] => java.util.Arrays.copyOf(original, newLength) + case original: Array[Short] => java.util.Arrays.copyOf(original, newLength) + case original: Array[Boolean] => java.util.Arrays.copyOf(original, newLength) + }).asInstanceOf[Array[A]] + + /** Copy one array to another, truncating or padding with default values (if + * necessary) so the copy has the specified length. The new array can have + * a different type than the original one as long as the values are + * assignment-compatible. When copying between primitive and object arrays, + * boxing and unboxing are supported. + * + * Equivalent to Java's + * `java.util.Arrays.copyOf(original, newLength, newType)`, + * except that this works for all combinations of primitive and object arrays + * in a single method. + * + * @see `java.util.Arrays#copyOf` + */ + def copyAs[A](original: Array[_], newLength: Int)(implicit ct: ClassTag[A]): Array[A] = { + val runtimeClass = ct.runtimeClass + if (runtimeClass == Void.TYPE) newUnitArray(newLength).asInstanceOf[Array[A]] + else { + val destClass = runtimeClass.asInstanceOf[Class[A]] + if (destClass.isAssignableFrom(original.getClass.getComponentType)) { + if (destClass.isPrimitive) copyOf[A](original.asInstanceOf[Array[A]], newLength) + else { + val destArrayClass = java.lang.reflect.Array.newInstance(destClass, 0).getClass.asInstanceOf[Class[Array[AnyRef]]] + java.util.Arrays.copyOf(original.asInstanceOf[Array[AnyRef]], newLength, destArrayClass).asInstanceOf[Array[A]] + } + } else { + val dest = new Array[A](newLength) + Array.copy(original, 0, dest, 0, original.length) + dest + } + } + } + + private def newUnitArray(len: Int): Array[Unit] = { + val result = new Array[Unit](len) + java.util.Arrays.fill(result.asInstanceOf[Array[AnyRef]], ()) + result + } + + /** Returns an array of length 0 */ + def empty[T: ClassTag]: Array[T] = new Array[T](0) + + /** Creates an array with given elements. + * + * @param xs the elements to put in the array + * @return an array containing all elements from xs. + */ + // Subject to a compiler optimization in Cleanup. + // Array(e0, ..., en) is translated to { val a = new Array(3); a(i) = ei; a } + def apply[T: ClassTag](xs: T*): Array[T] = { + val len = xs.length + xs match { + case wa: immutable.ArraySeq[_] if wa.unsafeArray.getClass.getComponentType == classTag[T].runtimeClass => + // We get here in test/files/run/sd760a.scala, `Array[T](t)` for + // a specialized type parameter `T`. While we still pay for two + // copies of the array it is better than before when we also boxed + // each element when populating the result. + ScalaRunTime.array_clone(wa.unsafeArray).asInstanceOf[Array[T]] + case _ => + val array = new Array[T](len) + val iterator = xs.iterator + var i = 0 + while (iterator.hasNext) { + array(i) = iterator.next(); i += 1 + } + array + } + } + + /** Creates an array of `Boolean` objects */ + // Subject to a compiler optimization in Cleanup, see above. + def apply(x: Boolean, xs: Boolean*): Array[Boolean] = { + val array = new Array[Boolean](xs.length + 1) + array(0) = x + val iterator = xs.iterator + var i = 1 + while (iterator.hasNext) { + array(i) = iterator.next(); i += 1 + } + array + } + + /** Creates an array of `Byte` objects */ + // Subject to a compiler optimization in Cleanup, see above. + def apply(x: Byte, xs: Byte*): Array[Byte] = { + val array = new Array[Byte](xs.length + 1) + array(0) = x + val iterator = xs.iterator + var i = 1 + while (iterator.hasNext) { + array(i) = iterator.next(); i += 1 + } + array + } + + /** Creates an array of `Short` objects */ + // Subject to a compiler optimization in Cleanup, see above. + def apply(x: Short, xs: Short*): Array[Short] = { + val array = new Array[Short](xs.length + 1) + array(0) = x + val iterator = xs.iterator + var i = 1 + while (iterator.hasNext) { + array(i) = iterator.next(); i += 1 + } + array + } + + /** Creates an array of `Char` objects */ + // Subject to a compiler optimization in Cleanup, see above. + def apply(x: Char, xs: Char*): Array[Char] = { + val array = new Array[Char](xs.length + 1) + array(0) = x + val iterator = xs.iterator + var i = 1 + while (iterator.hasNext) { + array(i) = iterator.next(); i += 1 + } + array + } + + /** Creates an array of `Int` objects */ + // Subject to a compiler optimization in Cleanup, see above. + def apply(x: Int, xs: Int*): Array[Int] = { + val array = new Array[Int](xs.length + 1) + array(0) = x + val iterator = xs.iterator + var i = 1 + while (iterator.hasNext) { + array(i) = iterator.next(); i += 1 + } + array + } + + /** Creates an array of `Long` objects */ + // Subject to a compiler optimization in Cleanup, see above. + def apply(x: Long, xs: Long*): Array[Long] = { + val array = new Array[Long](xs.length + 1) + array(0) = x + val iterator = xs.iterator + var i = 1 + while (iterator.hasNext) { + array(i) = iterator.next(); i += 1 + } + array + } + + /** Creates an array of `Float` objects */ + // Subject to a compiler optimization in Cleanup, see above. + def apply(x: Float, xs: Float*): Array[Float] = { + val array = new Array[Float](xs.length + 1) + array(0) = x + val iterator = xs.iterator + var i = 1 + while (iterator.hasNext) { + array(i) = iterator.next(); i += 1 + } + array + } + + /** Creates an array of `Double` objects */ + // Subject to a compiler optimization in Cleanup, see above. + def apply(x: Double, xs: Double*): Array[Double] = { + val array = new Array[Double](xs.length + 1) + array(0) = x + val iterator = xs.iterator + var i = 1 + while (iterator.hasNext) { + array(i) = iterator.next(); i += 1 + } + array + } + + /** Creates an array of `Unit` objects */ + def apply(x: Unit, xs: Unit*): Array[Unit] = { + val array = new Array[Unit](xs.length + 1) + array(0) = x + val iterator = xs.iterator + var i = 1 + while (iterator.hasNext) { + array(i) = iterator.next(); i += 1 + } + array + } + + /** Creates array with given dimensions */ + def ofDim[T: ClassTag](n1: Int): Array[T] = + new Array[T](n1) + /** Creates a 2-dimensional array */ + def ofDim[T: ClassTag](n1: Int, n2: Int): Array[Array[T]] = { + val arr: Array[Array[T]] = (new Array[Array[T]](n1): Array[Array[T]]) + for (i <- 0 until n1) arr(i) = new Array[T](n2) + arr + // tabulate(n1)(_ => ofDim[T](n2)) + } + /** Creates a 3-dimensional array */ + def ofDim[T: ClassTag](n1: Int, n2: Int, n3: Int): Array[Array[Array[T]]] = + tabulate(n1)(_ => ofDim[T](n2, n3)) + /** Creates a 4-dimensional array */ + def ofDim[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int): Array[Array[Array[Array[T]]]] = + tabulate(n1)(_ => ofDim[T](n2, n3, n4)) + /** Creates a 5-dimensional array */ + def ofDim[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int): Array[Array[Array[Array[Array[T]]]]] = + tabulate(n1)(_ => ofDim[T](n2, n3, n4, n5)) + + /** Concatenates all arrays into a single array. + * + * @param xss the given arrays + * @return the array created from concatenating `xss` + */ + def concat[T: ClassTag](xss: Array[T]*): Array[T] = { + val b = newBuilder[T] + b.sizeHint(xss.map(_.length).sum) + for (xs <- xss) b ++= xs + b.result() + } + + /** Returns an array that contains the results of some element computation a number + * of times. + * + * Note that this means that `elem` is computed a total of n times: + * {{{ + * scala> Array.fill(3){ math.random } + * res3: Array[Double] = Array(0.365461167592537, 1.550395944913685E-4, 0.7907242137333306) + * }}} + * + * @param n the number of elements desired + * @param elem the element computation + * @return an Array of size n, where each element contains the result of computing + * `elem`. + */ + def fill[T: ClassTag](n: Int)(elem: => T): Array[T] = { + if (n <= 0) { + empty[T] + } else { + val array = new Array[T](n) + var i = 0 + while (i < n) { + array(i) = elem + i += 1 + } + array + } + } + + /** Returns a two-dimensional array that contains the results of some element + * computation a number of times. + * + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param elem the element computation + */ + def fill[T: ClassTag](n1: Int, n2: Int)(elem: => T): Array[Array[T]] = + tabulate(n1)(_ => fill(n2)(elem)) + + /** Returns a three-dimensional array that contains the results of some element + * computation a number of times. + * + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param elem the element computation + */ + def fill[T: ClassTag](n1: Int, n2: Int, n3: Int)(elem: => T): Array[Array[Array[T]]] = + tabulate(n1)(_ => fill(n2, n3)(elem)) + + /** Returns a four-dimensional array that contains the results of some element + * computation a number of times. + * + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param elem the element computation + */ + def fill[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => T): Array[Array[Array[Array[T]]]] = + tabulate(n1)(_ => fill(n2, n3, n4)(elem)) + + /** Returns a five-dimensional array that contains the results of some element + * computation a number of times. + * + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param n5 the number of elements in the 5th dimension + * @param elem the element computation + */ + def fill[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => T): Array[Array[Array[Array[Array[T]]]]] = + tabulate(n1)(_ => fill(n2, n3, n4, n5)(elem)) + + /** Returns an array containing values of a given function over a range of integer + * values starting from 0. + * + * @param n The number of elements in the array + * @param f The function computing element values + * @return An `Array` consisting of elements `f(0),f(1), ..., f(n - 1)` + */ + def tabulate[T: ClassTag](n: Int)(f: Int => T): Array[T] = { + if (n <= 0) { + empty[T] + } else { + val array = new Array[T](n) + var i = 0 + while (i < n) { + array(i) = f(i) + i += 1 + } + array + } + } + + /** Returns a two-dimensional array containing values of a given function + * over ranges of integer values starting from `0`. + * + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param f The function computing element values + */ + def tabulate[T: ClassTag](n1: Int, n2: Int)(f: (Int, Int) => T): Array[Array[T]] = + tabulate(n1)(i1 => tabulate(n2)(f(i1, _))) + + /** Returns a three-dimensional array containing values of a given function + * over ranges of integer values starting from `0`. + * + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param f The function computing element values + */ + def tabulate[T: ClassTag](n1: Int, n2: Int, n3: Int)(f: (Int, Int, Int) => T): Array[Array[Array[T]]] = + tabulate(n1)(i1 => tabulate(n2, n3)(f(i1, _, _))) + + /** Returns a four-dimensional array containing values of a given function + * over ranges of integer values starting from `0`. + * + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param f The function computing element values + */ + def tabulate[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int)(f: (Int, Int, Int, Int) => T): Array[Array[Array[Array[T]]]] = + tabulate(n1)(i1 => tabulate(n2, n3, n4)(f(i1, _, _, _))) + + /** Returns a five-dimensional array containing values of a given function + * over ranges of integer values starting from `0`. + * + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param n5 the number of elements in the 5th dimension + * @param f The function computing element values + */ + def tabulate[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(f: (Int, Int, Int, Int, Int) => T): Array[Array[Array[Array[Array[T]]]]] = + tabulate(n1)(i1 => tabulate(n2, n3, n4, n5)(f(i1, _, _, _, _))) + + /** Returns an array containing a sequence of increasing integers in a range. + * + * @param start the start value of the array + * @param end the end value of the array, exclusive (in other words, this is the first value '''not''' returned) + * @return the array with values in range `start, start + 1, ..., end - 1` + * up to, but excluding, `end`. + */ + def range(start: Int, end: Int): Array[Int] = range(start, end, 1) + + /** Returns an array containing equally spaced values in some integer interval. + * + * @param start the start value of the array + * @param end the end value of the array, exclusive (in other words, this is the first value '''not''' returned) + * @param step the increment value of the array (may not be zero) + * @return the array with values in `start, start + step, ...` up to, but excluding `end` + */ + def range(start: Int, end: Int, step: Int): Array[Int] = { + if (step == 0) throw new IllegalArgumentException("zero step") + val array = new Array[Int](immutable.Range.count(start, end, step, isInclusive = false)) + + var n = 0 + var i = start + while (if (step < 0) end < i else i < end) { + array(n) = i + i += step + n += 1 + } + array + } + + /** Returns an array containing repeated applications of a function to a start value. + * + * @param start the start value of the array + * @param len the number of elements returned by the array + * @param f the function that is repeatedly applied + * @return the array returning `len` values in the sequence `start, f(start), f(f(start)), ...` + */ + def iterate[T: ClassTag](start: T, len: Int)(f: T => T): Array[T] = { + if (len > 0) { + val array = new Array[T](len) + var acc = start + var i = 1 + array(0) = acc + + while (i < len) { + acc = f(acc) + array(i) = acc + i += 1 + } + array + } else { + empty[T] + } + } + + /** Compare two arrays per element. + * + * A more efficient version of `xs.sameElements(ys)`. + * + * Note that arrays are invariant in Scala, but it may + * be sound to cast an array of arbitrary reference type + * to `Array[AnyRef]`. Arrays on the JVM are covariant + * in their element type. + * + * `Array.equals(xs.asInstanceOf[Array[AnyRef]], ys.asInstanceOf[Array[AnyRef]])` + * + * @param xs an array of AnyRef + * @param ys an array of AnyRef + * @return true if corresponding elements are equal + */ + def equals(xs: Array[AnyRef], ys: Array[AnyRef]): Boolean = + (xs eq ys) || + (xs.length == ys.length) && { + var i = 0 + while (i < xs.length && xs(i) == ys(i)) i += 1 + i >= xs.length + } + + /** Called in a pattern match like `{ case Array(x,y,z) => println('3 elements')}`. + * + * @param x the selector value + * @return sequence wrapped in a [[scala.Some]], if `x` is an Array, otherwise `None` + */ + def unapplySeq[T](x: Array[T]): UnapplySeqWrapper[T] = new UnapplySeqWrapper(x) + + final class UnapplySeqWrapper[T](private val a: Array[T]) extends AnyVal { + def isEmpty: false = false + def get: UnapplySeqWrapper[T] = this + def lengthCompare(len: Int): Int = a.lengthCompare(len) + def apply(i: Int): T = a(i) + def drop(n: Int): scala.Seq[T] = ArraySeq.unsafeWrapArray(a.drop(n)) // clones the array, also if n == 0 + def toSeq: scala.Seq[T] = a.toSeq // clones the array + } +} + +/** Arrays are mutable, indexed collections of values. `Array[T]` is Scala's representation + * for Java's `T[]`. + * + * {{{ + * val numbers = Array(1, 2, 3, 4) + * val first = numbers(0) // read the first element + * numbers(3) = 100 // replace the 4th array element with 100 + * val biggerNumbers = numbers.map(_ * 2) // multiply all numbers by two + * }}} + * + * Arrays make use of two common pieces of Scala syntactic sugar, shown on lines 2 and 3 of the above + * example code. + * Line 2 is translated into a call to `apply(Int)`, while line 3 is translated into a call to + * `update(Int, T)`. + * + * Two implicit conversions exist in [[scala.Predef]] that are frequently applied to arrays: a conversion + * to [[scala.collection.ArrayOps]] (shown on line 4 of the example above) and a conversion + * to [[scala.collection.mutable.ArraySeq]] (a subtype of [[scala.collection.Seq]]). + * Both types make available many of the standard operations found in the Scala collections API. + * The conversion to `ArrayOps` is temporary, as all operations defined on `ArrayOps` return an `Array`, + * while the conversion to `ArraySeq` is permanent as all operations return a `ArraySeq`. + * + * The conversion to `ArrayOps` takes priority over the conversion to `ArraySeq`. For instance, + * consider the following code: + * + * {{{ + * val arr = Array(1, 2, 3) + * val arrReversed = arr.reverse + * val seqReversed : collection.Seq[Int] = arr.reverse + * }}} + * + * Value `arrReversed` will be of type `Array[Int]`, with an implicit conversion to `ArrayOps` occurring + * to perform the `reverse` operation. The value of `seqReversed`, on the other hand, will be computed + * by converting to `ArraySeq` first and invoking the variant of `reverse` that returns another + * `ArraySeq`. + * + * @see [[https://www.scala-lang.org/files/archive/spec/2.13/ Scala Language Specification]], for in-depth information on the transformations the Scala compiler makes on Arrays (Sections 6.6 and 6.15 respectively.) + * @see [[https://docs.scala-lang.org/sips/scala-2-8-arrays.html "Scala 2.8 Arrays"]] the Scala Improvement Document detailing arrays since Scala 2.8. + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/arrays.html "The Scala 2.8 Collections' API"]] section on `Array` by Martin Odersky for more information. + * @hideImplicitConversion scala.Predef.booleanArrayOps + * @hideImplicitConversion scala.Predef.byteArrayOps + * @hideImplicitConversion scala.Predef.charArrayOps + * @hideImplicitConversion scala.Predef.doubleArrayOps + * @hideImplicitConversion scala.Predef.floatArrayOps + * @hideImplicitConversion scala.Predef.intArrayOps + * @hideImplicitConversion scala.Predef.longArrayOps + * @hideImplicitConversion scala.Predef.refArrayOps + * @hideImplicitConversion scala.Predef.shortArrayOps + * @hideImplicitConversion scala.Predef.unitArrayOps + * @hideImplicitConversion scala.LowPriorityImplicits.wrapRefArray + * @hideImplicitConversion scala.LowPriorityImplicits.wrapIntArray + * @hideImplicitConversion scala.LowPriorityImplicits.wrapDoubleArray + * @hideImplicitConversion scala.LowPriorityImplicits.wrapLongArray + * @hideImplicitConversion scala.LowPriorityImplicits.wrapFloatArray + * @hideImplicitConversion scala.LowPriorityImplicits.wrapCharArray + * @hideImplicitConversion scala.LowPriorityImplicits.wrapByteArray + * @hideImplicitConversion scala.LowPriorityImplicits.wrapShortArray + * @hideImplicitConversion scala.LowPriorityImplicits.wrapBooleanArray + * @hideImplicitConversion scala.LowPriorityImplicits.wrapUnitArray + * @hideImplicitConversion scala.LowPriorityImplicits.genericWrapArray + * @define coll array + * @define Coll `Array` + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + * @define collectExample + * @define undefinedorder + */ +final class Array[T](_length: Int) extends java.io.Serializable with java.lang.Cloneable { + + /** The length of the array */ + def length: Int = throw new Error() + + /** The element at given index. + * + * Indices start at `0`; `xs.apply(0)` is the first element of array `xs`. + * Note the indexing syntax `xs(i)` is a shorthand for `xs.apply(i)`. + * + * @param i the index + * @return the element at the given index + * @throws ArrayIndexOutOfBoundsException if `i < 0` or `length <= i` + */ + def apply(i: Int): T = throw new Error() + + /** Update the element at given index. + * + * Indices start at `0`; `xs.update(i, x)` replaces the i^th^ element in the array. + * Note the syntax `xs(i) = x` is a shorthand for `xs.update(i, x)`. + * + * @param i the index + * @param x the value to be written at index `i` + * @throws ArrayIndexOutOfBoundsException if `i < 0` or `length <= i` + */ + def update(i: Int, x: T): Unit = { throw new Error() } + + /** Clone the Array. + * + * @return A clone of the Array. + */ + override def clone(): Array[T] = throw new Error() +} diff --git a/library/src/scala/Boolean.scala b/library/src/scala/Boolean.scala new file mode 100644 index 000000000000..5ff37694d133 --- /dev/null +++ b/library/src/scala/Boolean.scala @@ -0,0 +1,142 @@ +/* + * 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. + */ + +// DO NOT EDIT, CHANGES WILL BE LOST +// This auto-generated code can be modified in "project/GenerateAnyVals.scala". +// Afterwards, running "sbt generateSources" regenerates this source file. + +package scala + +import scala.language.`2.13` + +/** `Boolean` (equivalent to Java's `boolean` primitive type) is a + * subtype of [[scala.AnyVal]]. Instances of `Boolean` are not + * represented by an object in the underlying runtime system. + * + * There is an implicit conversion from [[scala.Boolean]] => [[scala.runtime.RichBoolean]] + * which provides useful non-primitive operations. + */ +final abstract class Boolean private extends AnyVal { + /** Negates a Boolean expression. + * + * - `!a` results in `false` if and only if `a` evaluates to `true` and + * - `!a` results in `true` if and only if `a` evaluates to `false`. + * + * @return the negated expression + */ + def unary_! : Boolean + + /** Compares two Boolean expressions and returns `true` if they evaluate to the same value. + * + * `a == b` returns `true` if and only if + * - `a` and `b` are `true` or + * - `a` and `b` are `false`. + */ + def ==(x: Boolean): Boolean + + /** + * Compares two Boolean expressions and returns `true` if they evaluate to a different value. + * + * `a != b` returns `true` if and only if + * - `a` is `true` and `b` is `false` or + * - `a` is `false` and `b` is `true`. + */ + def !=(x: Boolean): Boolean + + /** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true. + * + * `a || b` returns `true` if and only if + * - `a` is `true` or + * - `b` is `true` or + * - `a` and `b` are `true`. + * + * @note This method uses 'short-circuit' evaluation and + * behaves as if it was declared as `def ||(x: => Boolean): Boolean`. + * If `a` evaluates to `true`, `true` is returned without evaluating `b`. + */ + def ||(x: Boolean): Boolean + + /** Compares two Boolean expressions and returns `true` if both of them evaluate to true. + * + * `a && b` returns `true` if and only if + * - `a` and `b` are `true`. + * + * @note This method uses 'short-circuit' evaluation and + * behaves as if it was declared as `def &&(x: => Boolean): Boolean`. + * If `a` evaluates to `false`, `false` is returned without evaluating `b`. + */ + def &&(x: Boolean): Boolean + + // Compiler won't build with these seemingly more accurate signatures + // def ||(x: => Boolean): Boolean + // def &&(x: => Boolean): Boolean + + /** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true. + * + * `a | b` returns `true` if and only if + * - `a` is `true` or + * - `b` is `true` or + * - `a` and `b` are `true`. + * + * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`. + */ + def |(x: Boolean): Boolean + + /** Compares two Boolean expressions and returns `true` if both of them evaluate to true. + * + * `a & b` returns `true` if and only if + * - `a` and `b` are `true`. + * + * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`. + */ + def &(x: Boolean): Boolean + + /** Compares two Boolean expressions and returns `true` if they evaluate to a different value. + * + * `a ^ b` returns `true` if and only if + * - `a` is `true` and `b` is `false` or + * - `a` is `false` and `b` is `true`. + */ + def ^(x: Boolean): Boolean + + // Provide a more specific return type for Scaladoc + override def getClass(): Class[Boolean] = ??? +} + +object Boolean extends AnyValCompanion { + + /** Transform a value type into a boxed reference type. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToBoolean`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the Boolean to be boxed + * @return a java.lang.Boolean offering `x` as its underlying value. + */ + def box(x: Boolean): java.lang.Boolean = ??? + + /** Transform a boxed type into a value type. Note that this + * method is not typesafe: it accepts any Object, but will throw + * an exception if the argument is not a java.lang.Boolean. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxToBoolean`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the java.lang.Boolean to be unboxed. + * @throws ClassCastException if the argument is not a java.lang.Boolean + * @return the Boolean resulting from calling booleanValue() on `x` + */ + def unbox(x: java.lang.Object): Boolean = ??? + + /** The String representation of the scala.Boolean companion object. */ + override def toString = "object scala.Boolean" + +} + diff --git a/library/src/scala/Byte.scala b/library/src/scala/Byte.scala new file mode 100644 index 000000000000..c5891be37e36 --- /dev/null +++ b/library/src/scala/Byte.scala @@ -0,0 +1,489 @@ +/* + * 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. + */ + +// DO NOT EDIT, CHANGES WILL BE LOST +// This auto-generated code can be modified in "project/GenerateAnyVals.scala". +// Afterwards, running "sbt generateSources" regenerates this source file. + +package scala + +import scala.language.`2.13` + +/** `Byte`, a 8-bit signed integer (equivalent to Java's `byte` primitive type) is a + * subtype of [[scala.AnyVal]]. Instances of `Byte` are not + * represented by an object in the underlying runtime system. + * + * There is an implicit conversion from [[scala.Byte]] => [[scala.runtime.RichByte]] + * which provides useful non-primitive operations. + */ +final abstract class Byte private extends AnyVal { + def toByte: Byte + def toShort: Short + def toChar: Char + def toInt: Int + def toLong: Long + def toFloat: Float + def toDouble: Double + + /** + * Returns the bitwise negation of this value. + * @example {{{ + * ~5 == -6 + * // in binary: ~00000101 == + * // 11111010 + * }}} + */ + def unary_~ : Int + /** Returns this value, unmodified. */ + def unary_+ : Int + /** Returns the negation of this value. */ + def unary_- : Int + + @deprecated("Adding a number and a String is deprecated. Use the string interpolation `s\"$num$str\"`", "2.13.0") + def +(x: String): String + + /** + * Returns this value bit-shifted left by the specified number of bits, + * filling in the new right bits with zeroes. + * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}} + */ + def <<(x: Int): Int + /** + * Returns this value bit-shifted left by the specified number of bits, + * filling in the new right bits with zeroes. + * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}} + */ + @deprecated("shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.", "2.12.7") + def <<(x: Long): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling the new left bits with zeroes. + * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}} + * @example {{{ + * -21 >>> 3 == 536870909 + * // in binary: 11111111 11111111 11111111 11101011 >>> 3 == + * // 00011111 11111111 11111111 11111101 + * }}} + */ + def >>>(x: Int): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling the new left bits with zeroes. + * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}} + * @example {{{ + * -21 >>> 3 == 536870909 + * // in binary: 11111111 11111111 11111111 11101011 >>> 3 == + * // 00011111 11111111 11111111 11111101 + * }}} + */ + @deprecated("shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.", "2.12.7") + def >>>(x: Long): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling in the left bits with the same value as the left-most bit of this. + * The effect of this is to retain the sign of the value. + * @example {{{ + * -21 >> 3 == -3 + * // in binary: 11111111 11111111 11111111 11101011 >> 3 == + * // 11111111 11111111 11111111 11111101 + * }}} + */ + def >>(x: Int): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling in the left bits with the same value as the left-most bit of this. + * The effect of this is to retain the sign of the value. + * @example {{{ + * -21 >> 3 == -3 + * // in binary: 11111111 11111111 11111111 11101011 >> 3 == + * // 11111111 11111111 11111111 11111101 + * }}} + */ + @deprecated("shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.", "2.12.7") + def >>(x: Long): Int + + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Byte): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Short): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Char): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Int): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Long): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Float): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Double): Boolean + + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Byte): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Short): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Char): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Int): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Long): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Float): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Double): Boolean + + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Byte): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Short): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Char): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Int): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Long): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Float): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Double): Boolean + + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Byte): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Short): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Char): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Int): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Long): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Float): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Double): Boolean + + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Byte): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Short): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Char): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Int): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Long): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Float): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Double): Boolean + + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Byte): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Short): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Char): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Int): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Long): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Float): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Double): Boolean + + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Byte): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Short): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Char): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Int): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Long): Long + + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Byte): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Short): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Char): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Int): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Long): Long + + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Byte): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Short): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Char): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Int): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Long): Long + + /** Returns the sum of this value and `x`. */ + def +(x: Byte): Int + /** Returns the sum of this value and `x`. */ + def +(x: Short): Int + /** Returns the sum of this value and `x`. */ + def +(x: Char): Int + /** Returns the sum of this value and `x`. */ + def +(x: Int): Int + /** Returns the sum of this value and `x`. */ + def +(x: Long): Long + /** Returns the sum of this value and `x`. */ + def +(x: Float): Float + /** Returns the sum of this value and `x`. */ + def +(x: Double): Double + + /** Returns the difference of this value and `x`. */ + def -(x: Byte): Int + /** Returns the difference of this value and `x`. */ + def -(x: Short): Int + /** Returns the difference of this value and `x`. */ + def -(x: Char): Int + /** Returns the difference of this value and `x`. */ + def -(x: Int): Int + /** Returns the difference of this value and `x`. */ + def -(x: Long): Long + /** Returns the difference of this value and `x`. */ + def -(x: Float): Float + /** Returns the difference of this value and `x`. */ + def -(x: Double): Double + + /** Returns the product of this value and `x`. */ + def *(x: Byte): Int + /** Returns the product of this value and `x`. */ + def *(x: Short): Int + /** Returns the product of this value and `x`. */ + def *(x: Char): Int + /** Returns the product of this value and `x`. */ + def *(x: Int): Int + /** Returns the product of this value and `x`. */ + def *(x: Long): Long + /** Returns the product of this value and `x`. */ + def *(x: Float): Float + /** Returns the product of this value and `x`. */ + def *(x: Double): Double + + /** Returns the quotient of this value and `x`. */ + def /(x: Byte): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Short): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Char): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Int): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Long): Long + /** Returns the quotient of this value and `x`. */ + def /(x: Float): Float + /** Returns the quotient of this value and `x`. */ + def /(x: Double): Double + + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Byte): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Short): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Char): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Int): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Long): Long + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Float): Float + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Double): Double + + // Provide a more specific return type for Scaladoc + override def getClass(): Class[Byte] = ??? +} + +object Byte extends AnyValCompanion { + /** The smallest value representable as a Byte. */ + final val MinValue = java.lang.Byte.MIN_VALUE + + /** The largest value representable as a Byte. */ + final val MaxValue = java.lang.Byte.MAX_VALUE + + /** Transform a value type into a boxed reference type. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToByte`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the Byte to be boxed + * @return a java.lang.Byte offering `x` as its underlying value. + */ + def box(x: Byte): java.lang.Byte = ??? + + /** Transform a boxed type into a value type. Note that this + * method is not typesafe: it accepts any Object, but will throw + * an exception if the argument is not a java.lang.Byte. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxToByte`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the java.lang.Byte to be unboxed. + * @throws ClassCastException if the argument is not a java.lang.Byte + * @return the Byte resulting from calling byteValue() on `x` + */ + def unbox(x: java.lang.Object): Byte = ??? + + /** The String representation of the scala.Byte companion object. */ + override def toString = "object scala.Byte" + /** Language mandated coercions from Byte to "wider" types. */ + import scala.language.implicitConversions + implicit def byte2short(x: Byte): Short = x.toShort + implicit def byte2int(x: Byte): Int = x.toInt + implicit def byte2long(x: Byte): Long = x.toLong + implicit def byte2float(x: Byte): Float = x.toFloat + implicit def byte2double(x: Byte): Double = x.toDouble +} + diff --git a/library/src/scala/CanThrow.scala b/library/src/scala/CanThrow.scala index 485dcecb37df..d55d631235e7 100644 --- a/library/src/scala/CanThrow.scala +++ b/library/src/scala/CanThrow.scala @@ -8,9 +8,9 @@ import annotation.{implicitNotFound, experimental, capability} */ @experimental @implicitNotFound("The capability to throw exception ${E} is missing.\nThe capability can be provided by one of the following:\n - Adding a using clause `(using CanThrow[${E}])` to the definition of the enclosing method\n - Adding `throws ${E}` clause after the result type of the enclosing method\n - Wrapping this piece of code with a `try` block that catches ${E}") -erased class CanThrow[-E <: Exception] extends caps.SharedCapability +class CanThrow[-E <: Exception] extends caps.Control, compiletime.Erased @experimental object unsafeExceptions: - given canThrowAny: CanThrow[Exception] = compiletime.erasedValue + inline given canThrowAny: CanThrow[Exception] = caps.unsafe.unsafeErasedValue diff --git a/library/src/scala/Char.scala b/library/src/scala/Char.scala new file mode 100644 index 000000000000..500ffcb05412 --- /dev/null +++ b/library/src/scala/Char.scala @@ -0,0 +1,488 @@ +/* + * 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. + */ + +// DO NOT EDIT, CHANGES WILL BE LOST +// This auto-generated code can be modified in "project/GenerateAnyVals.scala". +// Afterwards, running "sbt generateSources" regenerates this source file. + +package scala + +import scala.language.`2.13` + +/** `Char`, a 16-bit unsigned integer (equivalent to Java's `char` primitive type) is a + * subtype of [[scala.AnyVal]]. Instances of `Char` are not + * represented by an object in the underlying runtime system. + * + * There is an implicit conversion from [[scala.Char]] => [[scala.runtime.RichChar]] + * which provides useful non-primitive operations. + */ +final abstract class Char private extends AnyVal { + def toByte: Byte + def toShort: Short + def toChar: Char + def toInt: Int + def toLong: Long + def toFloat: Float + def toDouble: Double + + /** + * Returns the bitwise negation of this value. + * @example {{{ + * ~5 == -6 + * // in binary: ~00000101 == + * // 11111010 + * }}} + */ + def unary_~ : Int + /** Returns this value, unmodified. */ + def unary_+ : Int + /** Returns the negation of this value. */ + def unary_- : Int + + @deprecated("Adding a number and a String is deprecated. Use the string interpolation `s\"$num$str\"`", "2.13.0") + def +(x: String): String + + /** + * Returns this value bit-shifted left by the specified number of bits, + * filling in the new right bits with zeroes. + * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}} + */ + def <<(x: Int): Int + /** + * Returns this value bit-shifted left by the specified number of bits, + * filling in the new right bits with zeroes. + * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}} + */ + @deprecated("shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.", "2.12.7") + def <<(x: Long): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling the new left bits with zeroes. + * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}} + * @example {{{ + * -21 >>> 3 == 536870909 + * // in binary: 11111111 11111111 11111111 11101011 >>> 3 == + * // 00011111 11111111 11111111 11111101 + * }}} + */ + def >>>(x: Int): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling the new left bits with zeroes. + * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}} + * @example {{{ + * -21 >>> 3 == 536870909 + * // in binary: 11111111 11111111 11111111 11101011 >>> 3 == + * // 00011111 11111111 11111111 11111101 + * }}} + */ + @deprecated("shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.", "2.12.7") + def >>>(x: Long): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling in the left bits with the same value as the left-most bit of this. + * The effect of this is to retain the sign of the value. + * @example {{{ + * -21 >> 3 == -3 + * // in binary: 11111111 11111111 11111111 11101011 >> 3 == + * // 11111111 11111111 11111111 11111101 + * }}} + */ + def >>(x: Int): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling in the left bits with the same value as the left-most bit of this. + * The effect of this is to retain the sign of the value. + * @example {{{ + * -21 >> 3 == -3 + * // in binary: 11111111 11111111 11111111 11101011 >> 3 == + * // 11111111 11111111 11111111 11111101 + * }}} + */ + @deprecated("shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.", "2.12.7") + def >>(x: Long): Int + + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Byte): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Short): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Char): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Int): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Long): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Float): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Double): Boolean + + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Byte): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Short): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Char): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Int): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Long): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Float): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Double): Boolean + + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Byte): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Short): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Char): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Int): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Long): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Float): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Double): Boolean + + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Byte): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Short): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Char): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Int): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Long): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Float): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Double): Boolean + + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Byte): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Short): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Char): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Int): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Long): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Float): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Double): Boolean + + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Byte): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Short): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Char): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Int): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Long): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Float): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Double): Boolean + + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Byte): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Short): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Char): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Int): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Long): Long + + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Byte): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Short): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Char): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Int): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Long): Long + + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Byte): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Short): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Char): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Int): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Long): Long + + /** Returns the sum of this value and `x`. */ + def +(x: Byte): Int + /** Returns the sum of this value and `x`. */ + def +(x: Short): Int + /** Returns the sum of this value and `x`. */ + def +(x: Char): Int + /** Returns the sum of this value and `x`. */ + def +(x: Int): Int + /** Returns the sum of this value and `x`. */ + def +(x: Long): Long + /** Returns the sum of this value and `x`. */ + def +(x: Float): Float + /** Returns the sum of this value and `x`. */ + def +(x: Double): Double + + /** Returns the difference of this value and `x`. */ + def -(x: Byte): Int + /** Returns the difference of this value and `x`. */ + def -(x: Short): Int + /** Returns the difference of this value and `x`. */ + def -(x: Char): Int + /** Returns the difference of this value and `x`. */ + def -(x: Int): Int + /** Returns the difference of this value and `x`. */ + def -(x: Long): Long + /** Returns the difference of this value and `x`. */ + def -(x: Float): Float + /** Returns the difference of this value and `x`. */ + def -(x: Double): Double + + /** Returns the product of this value and `x`. */ + def *(x: Byte): Int + /** Returns the product of this value and `x`. */ + def *(x: Short): Int + /** Returns the product of this value and `x`. */ + def *(x: Char): Int + /** Returns the product of this value and `x`. */ + def *(x: Int): Int + /** Returns the product of this value and `x`. */ + def *(x: Long): Long + /** Returns the product of this value and `x`. */ + def *(x: Float): Float + /** Returns the product of this value and `x`. */ + def *(x: Double): Double + + /** Returns the quotient of this value and `x`. */ + def /(x: Byte): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Short): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Char): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Int): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Long): Long + /** Returns the quotient of this value and `x`. */ + def /(x: Float): Float + /** Returns the quotient of this value and `x`. */ + def /(x: Double): Double + + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Byte): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Short): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Char): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Int): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Long): Long + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Float): Float + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Double): Double + + // Provide a more specific return type for Scaladoc + override def getClass(): Class[Char] = ??? +} + +object Char extends AnyValCompanion { + /** The smallest value representable as a Char. */ + final val MinValue = java.lang.Character.MIN_VALUE + + /** The largest value representable as a Char. */ + final val MaxValue = java.lang.Character.MAX_VALUE + + /** Transform a value type into a boxed reference type. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToCharacter`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the Char to be boxed + * @return a java.lang.Character offering `x` as its underlying value. + */ + def box(x: Char): java.lang.Character = ??? + + /** Transform a boxed type into a value type. Note that this + * method is not typesafe: it accepts any Object, but will throw + * an exception if the argument is not a java.lang.Character. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxToChar`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the java.lang.Character to be unboxed. + * @throws ClassCastException if the argument is not a java.lang.Character + * @return the Char resulting from calling charValue() on `x` + */ + def unbox(x: java.lang.Object): Char = ??? + + /** The String representation of the scala.Char companion object. */ + override def toString = "object scala.Char" + /** Language mandated coercions from Char to "wider" types. */ + import scala.language.implicitConversions + implicit def char2int(x: Char): Int = x.toInt + implicit def char2long(x: Char): Long = x.toLong + implicit def char2float(x: Char): Float = x.toFloat + implicit def char2double(x: Char): Double = x.toDouble +} + diff --git a/library/src/scala/Console.scala b/library/src/scala/Console.scala new file mode 100644 index 000000000000..0c89da155b65 --- /dev/null +++ b/library/src/scala/Console.scala @@ -0,0 +1,283 @@ +/* + * 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.language.`2.13` + +import java.io.{ BufferedReader, InputStream, InputStreamReader, OutputStream, PrintStream, Reader } +import scala.io.AnsiColor +import scala.util.DynamicVariable + +/** Implements functionality for printing Scala values on the terminal. For reading values + * use [[scala.io.StdIn$ StdIn]]. + * Also defines constants for marking up text on ANSI terminals. + * + * == Console Output == + * + * Use the print methods to output text. + * {{{ + * scala> Console.printf( + * "Today the outside temperature is a balmy %.1f°C. %<.1f°C beats the previous record of %.1f°C.\n", + * -137.0, + * -135.05) + * Today the outside temperature is a balmy -137.0°C. -137.0°C beats the previous record of -135.1°C. + * }}} + * + * == ANSI escape codes == + * Use the ANSI escape codes for colorizing console output either to STDOUT or STDERR. + * {{{ + * import Console.{GREEN, RED, RESET, YELLOW_B, UNDERLINED} + * + * object PrimeTest { + * + * def isPrime(): Unit = { + * + * val candidate = io.StdIn.readInt().ensuring(_ > 1) + * + * val prime = (2 to candidate - 1).forall(candidate % _ != 0) + * + * if (prime) + * Console.println(s"\${RESET}\${GREEN}yes\${RESET}") + * else + * Console.err.println(s"\${RESET}\${YELLOW_B}\${RED}\${UNDERLINED}NO!\${RESET}") + * } + * + * def main(args: Array[String]): Unit = isPrime() + * + * } + * }}} + * + * + * + * + * + * + * + * + *
\$ scala PrimeTest
1234567891
yes
\$ scala PrimeTest
56474
NO!
+ * + * == IO redefinition == + * + * Use IO redefinition to temporarily swap in a different set of input and/or output streams. In this example the stream based + * method above is wrapped into a function. + * + * {{{ + * import java.io.{ByteArrayOutputStream, StringReader} + * + * object FunctionalPrimeTest { + * + * def isPrime(candidate: Int): Boolean = { + * + * val input = new StringReader(s"\$candidate\n") + * val outCapture = new ByteArrayOutputStream + * val errCapture = new ByteArrayOutputStream + * + * Console.withIn(input) { + * Console.withOut(outCapture) { + * Console.withErr(errCapture) { + * PrimeTest.isPrime() + * } + * } + * } + * + * if (outCapture.toByteArray.nonEmpty) // "yes" + * true + * else if (errCapture.toByteArray.nonEmpty) // "NO!" + * false + * else throw new IllegalArgumentException(candidate.toString) + * } + * + * def main(args: Array[String]): Unit = { + * val primes = (2 to 50) filter (isPrime) + * println(s"First primes: \$primes") + * } + * + * } + * }}} + * + * + * + * + * + *
\$ scala FunctionalPrimeTest
First primes: Vector(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47)
+ * + * @groupname console-output Console Output + * @groupprio console-output 30 + * @groupdesc console-output These methods provide output via the console. + * + * @groupname io-default IO Defaults + * @groupprio io-default 50 + * @groupdesc io-default These values provide direct access to the standard IO channels + * + * @groupname io-redefinition IO Redefinition + * @groupprio io-redefinition 60 + * @groupdesc io-redefinition These methods allow substituting alternative streams for the duration of + * a body of code. Threadsafe by virtue of [[scala.util.DynamicVariable]]. + * + */ +object Console extends AnsiColor { + private[this] val outVar = new DynamicVariable[PrintStream](java.lang.System.out) + private[this] val errVar = new DynamicVariable[PrintStream](java.lang.System.err) + private[this] val inVar = new DynamicVariable[BufferedReader]( + new BufferedReader(new InputStreamReader(java.lang.System.in))) + + protected def setOutDirect(out: PrintStream): Unit = outVar.value = out + protected def setErrDirect(err: PrintStream): Unit = errVar.value = err + protected def setInDirect(in: BufferedReader): Unit = inVar.value = in + + /** The default output, can be overridden by `withOut` + * @group io-default + */ + def out: PrintStream = outVar.value + /** The default error, can be overridden by `withErr` + * @group io-default + */ + def err: PrintStream = errVar.value + /** The default input, can be overridden by `withIn` + * @group io-default + */ + def in: BufferedReader = inVar.value + + /** Sets the default output stream for the duration + * of execution of one thunk. + * + * @example {{{ + * withOut(Console.err) { println("This goes to default _error_") } + * }}} + * + * @param out the new output stream. + * @param thunk the code to execute with + * the new output stream active + * @return the results of `thunk` + * @see `withOut[T](out:OutputStream)(thunk: => T)` + * @group io-redefinition + */ + def withOut[T](out: PrintStream)(thunk: => T): T = + outVar.withValue(out)(thunk) + + /** Sets the default output stream for the duration + * of execution of one thunk. + * + * @param out the new output stream. + * @param thunk the code to execute with + * the new output stream active + * @return the results of `thunk` + * @see `withOut[T](out:PrintStream)(thunk: => T)` + * @group io-redefinition + */ + def withOut[T](out: OutputStream)(thunk: => T): T = + withOut(new PrintStream(out))(thunk) + + /** Set the default error stream for the duration + * of execution of one thunk. + * @example {{{ + * withErr(Console.out) { err.println("This goes to default _out_") } + * }}} + * + * @param err the new error stream. + * @param thunk the code to execute with + * the new error stream active + * @return the results of `thunk` + * @see `withErr[T](err:OutputStream)(thunk: => T)` + * @group io-redefinition + */ + def withErr[T](err: PrintStream)(thunk: => T): T = + errVar.withValue(err)(thunk) + + /** Sets the default error stream for the duration + * of execution of one thunk. + * + * @param err the new error stream. + * @param thunk the code to execute with + * the new error stream active + * @return the results of `thunk` + * @see `withErr[T](err:PrintStream)(thunk: => T)` + * @group io-redefinition + */ + def withErr[T](err: OutputStream)(thunk: => T): T = + withErr(new PrintStream(err))(thunk) + + /** Sets the default input stream for the duration + * of execution of one thunk. + * + * @example {{{ + * val someFile:Reader = openFile("file.txt") + * withIn(someFile) { + * // Reads a line from file.txt instead of default input + * println(readLine) + * } + * }}} + * + * @param thunk the code to execute with + * the new input stream active + * + * @return the results of `thunk` + * @see `withIn[T](in:InputStream)(thunk: => T)` + * @group io-redefinition + */ + def withIn[T](reader: Reader)(thunk: => T): T = + inVar.withValue(new BufferedReader(reader))(thunk) + + /** Sets the default input stream for the duration + * of execution of one thunk. + * + * @param in the new input stream. + * @param thunk the code to execute with + * the new input stream active + * @return the results of `thunk` + * @see `withIn[T](reader:Reader)(thunk: => T)` + * @group io-redefinition + */ + def withIn[T](in: InputStream)(thunk: => T): T = + withIn(new InputStreamReader(in))(thunk) + + /** Prints an object to `out` using its `toString` method. + * + * @param obj the object to print; may be null. + * @group console-output + */ + def print(obj: Any): Unit = { + out.print(if (null == obj) "null" else obj.toString()) + } + + /** Flushes the output stream. This function is required when partial + * output (i.e. output not terminated by a newline character) has + * to be made visible on the terminal. + * @group console-output + */ + def flush(): Unit = { out.flush() } + + /** Prints a newline character on the default output. + * @group console-output + */ + def println(): Unit = { out.println() } + + /** Prints out an object to the default output, followed by a newline character. + * + * @param x the object to print. + * @group console-output + */ + def println(x: Any): Unit = { out.println(x) } + + /** Prints its arguments as a formatted string to the default output, + * based on a string pattern (in a fashion similar to printf in C). + * + * The interpretation of the formatting patterns is described in [[java.util.Formatter]]. + * + * @param text the pattern for formatting the arguments. + * @param args the arguments used to instantiating the pattern. + * @throws java.lang.IllegalArgumentException if there was a problem with the format string or arguments + * @group console-output + */ + def printf(text: String, args: Any*): Unit = { out.print(text.format(args: _*)) } +} diff --git a/library/src/scala/Conversion.scala b/library/src/scala/Conversion.scala index f6267dc79fb2..cbae3448dab9 100644 --- a/library/src/scala/Conversion.scala +++ b/library/src/scala/Conversion.scala @@ -29,3 +29,20 @@ abstract class Conversion[-T, +U] extends Function1[T, U]: extension (x: T) /** `x.convert` converts a value `x` of type `T` to type `U` */ def convert = this(x) + +object Conversion: + import annotation.experimental + + /** An opaque type alias to declare "into" parameter types that allow implicit conversions + * on corresponding arguments. If the expected type of an expression t is into[T], implicit + * conversions are tried from the type of `t` to `T`. `into[T]` types are erased to `T` + * in all covariant positions of the types of parameter symbols. + */ + @experimental + opaque type into[+T] >: T = T + + /** Unwrap an `into` */ + extension [T](x: into[T]) + @experimental def underlying: T = x + +end Conversion \ No newline at end of file diff --git a/library/src/scala/DelayedInit.scala b/library/src/scala/DelayedInit.scala new file mode 100644 index 000000000000..abcb86274051 --- /dev/null +++ b/library/src/scala/DelayedInit.scala @@ -0,0 +1,54 @@ +/* + * 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.language.`2.13` + +/** Classes and objects (but note, not traits) inheriting the `DelayedInit` + * marker trait will have their initialization code rewritten as follows: + * `code` becomes `delayedInit(code)`. + * + * Initialization code comprises all statements and all value definitions + * that are executed during initialization. + * + * Example: + * {{{ + * trait Helper extends DelayedInit { + * def delayedInit(body: => Unit) = { + * println("dummy text, printed before initialization of C") + * body // evaluates the initialization code of C + * } + * } + * + * class C extends Helper { + * println("this is the initialization code of C") + * } + * + * object Test extends App { + * val c = new C + * } + * }}} + * + * Should result in the following being printed: + * {{{ + * dummy text, printed before initialization of C + * this is the initialization code of C + * }}} + * + * @see "Delayed Initialization" subsection of the Scala Language Specification (section 5.1) + * + */ +@deprecated("DelayedInit semantics can be surprising. Support for `App` will continue. See the release notes for more details: https://github.com/scala/scala/releases/tag/v2.11.0", "2.11.0") +trait DelayedInit { + def delayedInit(x: => Unit): Unit +} diff --git a/library/src/scala/Double.scala b/library/src/scala/Double.scala new file mode 100644 index 000000000000..08a91bf8c603 --- /dev/null +++ b/library/src/scala/Double.scala @@ -0,0 +1,257 @@ +/* + * 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. + */ + +// DO NOT EDIT, CHANGES WILL BE LOST +// This auto-generated code can be modified in "project/GenerateAnyVals.scala". +// Afterwards, running "sbt generateSources" regenerates this source file. + +package scala + +import scala.language.`2.13` + +/** `Double`, a 64-bit IEEE-754 floating point number (equivalent to Java's `double` primitive type) is a + * subtype of [[scala.AnyVal]]. Instances of `Double` are not + * represented by an object in the underlying runtime system. + * + * There is an implicit conversion from [[scala.Double]] => [[scala.runtime.RichDouble]] + * which provides useful non-primitive operations. + */ +final abstract class Double private extends AnyVal { + def toByte: Byte + def toShort: Short + def toChar: Char + def toInt: Int + def toLong: Long + def toFloat: Float + def toDouble: Double + + /** Returns this value, unmodified. */ + def unary_+ : Double + /** Returns the negation of this value. */ + def unary_- : Double + + @deprecated("Adding a number and a String is deprecated. Use the string interpolation `s\"$num$str\"`", "2.13.0") + def +(x: String): String + + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Byte): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Short): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Char): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Int): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Long): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Float): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Double): Boolean + + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Byte): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Short): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Char): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Int): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Long): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Float): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Double): Boolean + + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Byte): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Short): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Char): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Int): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Long): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Float): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Double): Boolean + + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Byte): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Short): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Char): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Int): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Long): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Float): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Double): Boolean + + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Byte): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Short): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Char): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Int): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Long): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Float): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Double): Boolean + + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Byte): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Short): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Char): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Int): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Long): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Float): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Double): Boolean + + /** Returns the sum of this value and `x`. */ + def +(x: Byte): Double + /** Returns the sum of this value and `x`. */ + def +(x: Short): Double + /** Returns the sum of this value and `x`. */ + def +(x: Char): Double + /** Returns the sum of this value and `x`. */ + def +(x: Int): Double + /** Returns the sum of this value and `x`. */ + def +(x: Long): Double + /** Returns the sum of this value and `x`. */ + def +(x: Float): Double + /** Returns the sum of this value and `x`. */ + def +(x: Double): Double + + /** Returns the difference of this value and `x`. */ + def -(x: Byte): Double + /** Returns the difference of this value and `x`. */ + def -(x: Short): Double + /** Returns the difference of this value and `x`. */ + def -(x: Char): Double + /** Returns the difference of this value and `x`. */ + def -(x: Int): Double + /** Returns the difference of this value and `x`. */ + def -(x: Long): Double + /** Returns the difference of this value and `x`. */ + def -(x: Float): Double + /** Returns the difference of this value and `x`. */ + def -(x: Double): Double + + /** Returns the product of this value and `x`. */ + def *(x: Byte): Double + /** Returns the product of this value and `x`. */ + def *(x: Short): Double + /** Returns the product of this value and `x`. */ + def *(x: Char): Double + /** Returns the product of this value and `x`. */ + def *(x: Int): Double + /** Returns the product of this value and `x`. */ + def *(x: Long): Double + /** Returns the product of this value and `x`. */ + def *(x: Float): Double + /** Returns the product of this value and `x`. */ + def *(x: Double): Double + + /** Returns the quotient of this value and `x`. */ + def /(x: Byte): Double + /** Returns the quotient of this value and `x`. */ + def /(x: Short): Double + /** Returns the quotient of this value and `x`. */ + def /(x: Char): Double + /** Returns the quotient of this value and `x`. */ + def /(x: Int): Double + /** Returns the quotient of this value and `x`. */ + def /(x: Long): Double + /** Returns the quotient of this value and `x`. */ + def /(x: Float): Double + /** Returns the quotient of this value and `x`. */ + def /(x: Double): Double + + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Byte): Double + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Short): Double + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Char): Double + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Int): Double + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Long): Double + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Float): Double + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Double): Double + + // Provide a more specific return type for Scaladoc + override def getClass(): Class[Double] = ??? +} + +object Double extends AnyValCompanion { + /** The smallest positive value greater than 0.0d which is + * representable as a Double. + */ + final val MinPositiveValue = java.lang.Double.MIN_VALUE + final val NaN = java.lang.Double.NaN + final val PositiveInfinity = java.lang.Double.POSITIVE_INFINITY + final val NegativeInfinity = java.lang.Double.NEGATIVE_INFINITY + + /** The negative number with the greatest (finite) absolute value which is representable + * by a Double. Note that it differs from [[java.lang.Double.MIN_VALUE]], which + * is the smallest positive value representable by a Double. In Scala that number + * is called Double.MinPositiveValue. + */ + final val MinValue = -java.lang.Double.MAX_VALUE + + /** The largest finite positive number representable as a Double. */ + final val MaxValue = java.lang.Double.MAX_VALUE + + /** Transform a value type into a boxed reference type. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToDouble`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the Double to be boxed + * @return a java.lang.Double offering `x` as its underlying value. + */ + def box(x: Double): java.lang.Double = ??? + + /** Transform a boxed type into a value type. Note that this + * method is not typesafe: it accepts any Object, but will throw + * an exception if the argument is not a java.lang.Double. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxToDouble`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the java.lang.Double to be unboxed. + * @throws ClassCastException if the argument is not a java.lang.Double + * @return the Double resulting from calling doubleValue() on `x` + */ + def unbox(x: java.lang.Object): Double = ??? + + /** The String representation of the scala.Double companion object. */ + override def toString = "object scala.Double" +} + diff --git a/library/src/scala/DummyImplicit.scala b/library/src/scala/DummyImplicit.scala new file mode 100644 index 000000000000..9e542ccb1d7b --- /dev/null +++ b/library/src/scala/DummyImplicit.scala @@ -0,0 +1,23 @@ +/* + * 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.language.`2.13` + +/** A type for which there is always an implicit value. */ +final class DummyImplicit private () + +object DummyImplicit { + /** An implicit value yielding a `DummyImplicit`. */ + implicit val dummyImplicit: DummyImplicit = new DummyImplicit +} diff --git a/library/src/scala/Dynamic.scala b/library/src/scala/Dynamic.scala new file mode 100644 index 000000000000..bb4e44f78912 --- /dev/null +++ b/library/src/scala/Dynamic.scala @@ -0,0 +1,40 @@ +/* + * 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.language.`2.13` + +/** A marker trait that enables dynamic invocations. Instances `x` of this + * trait allow method invocations `x.meth(args)` for arbitrary method + * names `meth` and argument lists `args` as well as field accesses + * `x.field` for arbitrary field names `field`. + * + * If a call is not natively supported by `x` (i.e. if type checking + * fails), it is rewritten according to the following rules: + * + * {{{ + * foo.method("blah") ~~> foo.applyDynamic("method")("blah") + * foo.method(x = "blah") ~~> foo.applyDynamicNamed("method")(("x", "blah")) + * foo.method(x = 1, 2) ~~> foo.applyDynamicNamed("method")(("x", 1), ("", 2)) + * foo.field ~~> foo.selectDynamic("field") + * foo.varia = 10 ~~> foo.updateDynamic("varia")(10) + * foo.arr(10) = 13 ~~> foo.selectDynamic("arr").update(10, 13) + * foo.arr(10) ~~> foo.applyDynamic("arr")(10) + * }}} + * + * Defining direct or indirect subclasses of this trait + * is only possible if the language feature `dynamics` is enabled. + */ +trait Dynamic extends Any + + diff --git a/library/src/scala/Enumeration.scala b/library/src/scala/Enumeration.scala new file mode 100644 index 000000000000..b527fd3fc2fb --- /dev/null +++ b/library/src/scala/Enumeration.scala @@ -0,0 +1,352 @@ +/* + * 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.language.`2.13` + +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] { + 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/library/src/scala/Equals.scala b/library/src/scala/Equals.scala new file mode 100644 index 000000000000..04dc3db6b7d9 --- /dev/null +++ b/library/src/scala/Equals.scala @@ -0,0 +1,37 @@ +/* + * 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.language.`2.13` + +/** An interface containing operations for equality. + * The only method not already present in class `AnyRef` is `canEqual`. + */ +trait Equals extends Any { + /** Checks whether this instance can possibly equal `that`. + * + * A method that should be called from every well-designed equals method + * that is open to be overridden in a subclass. See + * [[https://www.artima.com/pins1ed/object-equality.html Programming in Scala, + * Chapter 28]] for discussion and design. + * + * @param that the value being probed for possible equality + * @return true if this instance can possibly equal `that`, otherwise false + */ + def canEqual(that: Any): Boolean + + /** Checks whether this instance is equal to `that`. + * This universal equality method is defined in `AnyRef`. + */ + def equals(that: Any): Boolean +} diff --git a/library/src/scala/Float.scala b/library/src/scala/Float.scala new file mode 100644 index 000000000000..fae92d99e882 --- /dev/null +++ b/library/src/scala/Float.scala @@ -0,0 +1,260 @@ +/* + * 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. + */ + +// DO NOT EDIT, CHANGES WILL BE LOST +// This auto-generated code can be modified in "project/GenerateAnyVals.scala". +// Afterwards, running "sbt generateSources" regenerates this source file. + +package scala + +import scala.language.`2.13` + +/** `Float`, a 32-bit IEEE-754 floating point number (equivalent to Java's `float` primitive type) is a + * subtype of [[scala.AnyVal]]. Instances of `Float` are not + * represented by an object in the underlying runtime system. + * + * There is an implicit conversion from [[scala.Float]] => [[scala.runtime.RichFloat]] + * which provides useful non-primitive operations. + */ +final abstract class Float private extends AnyVal { + def toByte: Byte + def toShort: Short + def toChar: Char + def toInt: Int + def toLong: Long + def toFloat: Float + def toDouble: Double + + /** Returns this value, unmodified. */ + def unary_+ : Float + /** Returns the negation of this value. */ + def unary_- : Float + + @deprecated("Adding a number and a String is deprecated. Use the string interpolation `s\"$num$str\"`", "2.13.0") + def +(x: String): String + + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Byte): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Short): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Char): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Int): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Long): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Float): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Double): Boolean + + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Byte): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Short): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Char): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Int): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Long): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Float): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Double): Boolean + + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Byte): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Short): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Char): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Int): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Long): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Float): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Double): Boolean + + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Byte): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Short): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Char): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Int): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Long): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Float): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Double): Boolean + + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Byte): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Short): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Char): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Int): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Long): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Float): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Double): Boolean + + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Byte): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Short): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Char): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Int): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Long): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Float): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Double): Boolean + + /** Returns the sum of this value and `x`. */ + def +(x: Byte): Float + /** Returns the sum of this value and `x`. */ + def +(x: Short): Float + /** Returns the sum of this value and `x`. */ + def +(x: Char): Float + /** Returns the sum of this value and `x`. */ + def +(x: Int): Float + /** Returns the sum of this value and `x`. */ + def +(x: Long): Float + /** Returns the sum of this value and `x`. */ + def +(x: Float): Float + /** Returns the sum of this value and `x`. */ + def +(x: Double): Double + + /** Returns the difference of this value and `x`. */ + def -(x: Byte): Float + /** Returns the difference of this value and `x`. */ + def -(x: Short): Float + /** Returns the difference of this value and `x`. */ + def -(x: Char): Float + /** Returns the difference of this value and `x`. */ + def -(x: Int): Float + /** Returns the difference of this value and `x`. */ + def -(x: Long): Float + /** Returns the difference of this value and `x`. */ + def -(x: Float): Float + /** Returns the difference of this value and `x`. */ + def -(x: Double): Double + + /** Returns the product of this value and `x`. */ + def *(x: Byte): Float + /** Returns the product of this value and `x`. */ + def *(x: Short): Float + /** Returns the product of this value and `x`. */ + def *(x: Char): Float + /** Returns the product of this value and `x`. */ + def *(x: Int): Float + /** Returns the product of this value and `x`. */ + def *(x: Long): Float + /** Returns the product of this value and `x`. */ + def *(x: Float): Float + /** Returns the product of this value and `x`. */ + def *(x: Double): Double + + /** Returns the quotient of this value and `x`. */ + def /(x: Byte): Float + /** Returns the quotient of this value and `x`. */ + def /(x: Short): Float + /** Returns the quotient of this value and `x`. */ + def /(x: Char): Float + /** Returns the quotient of this value and `x`. */ + def /(x: Int): Float + /** Returns the quotient of this value and `x`. */ + def /(x: Long): Float + /** Returns the quotient of this value and `x`. */ + def /(x: Float): Float + /** Returns the quotient of this value and `x`. */ + def /(x: Double): Double + + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Byte): Float + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Short): Float + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Char): Float + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Int): Float + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Long): Float + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Float): Float + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Double): Double + + // Provide a more specific return type for Scaladoc + override def getClass(): Class[Float] = ??? +} + +object Float extends AnyValCompanion { + /** The smallest positive value greater than 0.0f which is + * representable as a Float. + */ + final val MinPositiveValue = java.lang.Float.MIN_VALUE + final val NaN = java.lang.Float.NaN + final val PositiveInfinity = java.lang.Float.POSITIVE_INFINITY + final val NegativeInfinity = java.lang.Float.NEGATIVE_INFINITY + + /** The negative number with the greatest (finite) absolute value which is representable + * by a Float. Note that it differs from [[java.lang.Float.MIN_VALUE]], which + * is the smallest positive value representable by a Float. In Scala that number + * is called Float.MinPositiveValue. + */ + final val MinValue = -java.lang.Float.MAX_VALUE + + /** The largest finite positive number representable as a Float. */ + final val MaxValue = java.lang.Float.MAX_VALUE + + /** Transform a value type into a boxed reference type. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToFloat`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the Float to be boxed + * @return a java.lang.Float offering `x` as its underlying value. + */ + def box(x: Float): java.lang.Float = ??? + + /** Transform a boxed type into a value type. Note that this + * method is not typesafe: it accepts any Object, but will throw + * an exception if the argument is not a java.lang.Float. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxToFloat`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the java.lang.Float to be unboxed. + * @throws ClassCastException if the argument is not a java.lang.Float + * @return the Float resulting from calling floatValue() on `x` + */ + def unbox(x: java.lang.Object): Float = ??? + + /** The String representation of the scala.Float companion object. */ + override def toString = "object scala.Float" + /** Language mandated coercions from Float to "wider" types. */ + import scala.language.implicitConversions + implicit def float2double(x: Float): Double = x.toDouble +} + diff --git a/library/src/scala/Function.scala b/library/src/scala/Function.scala new file mode 100644 index 000000000000..28061026c000 --- /dev/null +++ b/library/src/scala/Function.scala @@ -0,0 +1,132 @@ +/* + * 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.language.`2.13` + +/** A module defining utility methods for higher-order functional programming. + */ +object Function { + /** Given a sequence of functions `f,,1,,`, ..., `f,,n,,`, return the + * function `f,,1,, andThen ... andThen f,,n,,`. + * + * @param fs The given sequence of functions + */ + def chain[T](fs: scala.collection.Seq[T => T]): T => T = { x => fs.foldLeft(x)((x, f) => f(x)) } + + /** The constant function */ + def const[T, U](x: T)(y: U): T = x + + /** Turns a function `A => Option[B]` into a `PartialFunction[A, B]`. + * + * '''Important note''': this transformation implies the original function + * may be called 2 or more times on each logical invocation, because the + * only way to supply an implementation of `isDefinedAt` is to call the + * function and examine the return value. + * See also [[scala.PartialFunction]], method `applyOrElse`. + * + * @param f a function `T => Option[R]` + * @return a partial function defined for those inputs where + * f returns `Some(_)` and undefined where `f` returns `None`. + * @see [[scala.PartialFunction]], method `lift`. + */ + def unlift[T, R](f: T => Option[R]): PartialFunction[T, R] = PartialFunction.unlifted(f) + + /** Uncurrying for functions of arity 2. This transforms a unary function + * returning another unary function into a function of arity 2. + */ + def uncurried[T1, T2, R](f: T1 => T2 => R): (T1, T2) => R = { + (x1, x2) => f(x1)(x2) + } + + /** Uncurrying for functions of arity 3. + */ + def uncurried[T1, T2, T3, R](f: T1 => T2 => T3 => R): (T1, T2, T3) => R = { + (x1, x2, x3) => f(x1)(x2)(x3) + } + + /** Uncurrying for functions of arity 4. + */ + def uncurried[T1, T2, T3, T4, R](f: T1 => T2 => T3 => T4 => R): (T1, T2, T3, T4) => R = { + (x1, x2, x3, x4) => f(x1)(x2)(x3)(x4) + } + + /** Uncurrying for functions of arity 5. + */ + def uncurried[T1, T2, T3, T4, T5, R](f: T1 => T2 => T3 => T4 => T5 => R): (T1, T2, T3, T4, T5) => R = { + (x1, x2, x3, x4, x5) => f(x1)(x2)(x3)(x4)(x5) + } + + /** Tupling for functions of arity 2. This transforms a function + * of arity 2 into a unary function that takes a pair of arguments. + * + * @note These functions are slotted for deprecation, but it is on + * hold pending superior type inference for tupling anonymous functions. + */ + // @deprecated("use `f.tupled` instead") + def tupled[T1, T2, R](f: (T1, T2) => R): ((T1, T2)) => R = { + case ((x1, x2)) => f(x1, x2) + } + + /** Tupling for functions of arity 3. This transforms a function + * of arity 3 into a unary function that takes a triple of arguments. + */ + // @deprecated("use `f.tupled` instead") + def tupled[T1, T2, T3, R](f: (T1, T2, T3) => R): ((T1, T2, T3)) => R = { + case ((x1, x2, x3)) => f(x1, x2, x3) + } + + /** Tupling for functions of arity 4. This transforms a function + * of arity 4 into a unary function that takes a 4-tuple of arguments. + */ + // @deprecated("use `f.tupled` instead") + def tupled[T1, T2, T3, T4, R](f: (T1, T2, T3, T4) => R): ((T1, T2, T3, T4)) => R = { + case ((x1, x2, x3, x4)) => f(x1, x2, x3, x4) + } + + /** Tupling for functions of arity 5. This transforms a function + * of arity 5 into a unary function that takes a 5-tuple of arguments. + */ + // @deprecated("use `f.tupled` instead") + def tupled[T1, T2, T3, T4, T5, R](f: (T1, T2, T3, T4, T5) => R): ((T1, T2, T3, T4, T5)) => R = { + case ((x1, x2, x3, x4, x5)) => f(x1, x2, x3, x4, x5) + } + + /** Un-tupling for functions of arity 2. This transforms a function taking + * a pair of arguments into a binary function which takes each argument separately. + */ + def untupled[T1, T2, R](f: ((T1, T2)) => R): (T1, T2) => R = { + (x1, x2) => f((x1, x2)) + } + + /** Un-tupling for functions of arity 3. This transforms a function taking + * a triple of arguments into a ternary function which takes each argument separately. + */ + def untupled[T1, T2, T3, R](f: ((T1, T2, T3)) => R): (T1, T2, T3) => R = { + (x1, x2, x3) => f((x1, x2, x3)) + } + + /** Un-tupling for functions of arity 4. This transforms a function taking + * a 4-tuple of arguments into a function of arity 4 which takes each argument separately. + */ + def untupled[T1, T2, T3, T4, R](f: ((T1, T2, T3, T4)) => R): (T1, T2, T3, T4) => R = { + (x1, x2, x3, x4) => f((x1, x2, x3, x4)) + } + + /** Un-tupling for functions of arity 5. This transforms a function taking + * a 5-tuple of arguments into a function of arity 5 which takes each argument separately. + */ + def untupled[T1, T2, T3, T4, T5, R](f: ((T1, T2, T3, T4, T5)) => R): (T1, T2, T3, T4, T5) => R = { + (x1, x2, x3, x4, x5) => f((x1, x2, x3, x4, x5)) + } +} diff --git a/library/src/scala/Function0.scala b/library/src/scala/Function0.scala new file mode 100644 index 000000000000..af9327e6e88c --- /dev/null +++ b/library/src/scala/Function0.scala @@ -0,0 +1,46 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. +// genprod generated these sources at: 2022-01-17T20:47:12.170348200Z + +package scala + +import scala.language.`2.13` + +/** A function of 0 parameters. + * + * In the following example, the definition of `greeting` is + * shorthand, conceptually, for the anonymous class definition + * `anonfun0`, although the implementation details of how the + * function value is constructed may differ: + * + * {{{ + * object Main extends App { + * val name = "world" + * val greeting = () => s"hello, $name" + * + * val anonfun0 = new Function0[String] { + * def apply(): String = s"hello, $name" + * } + * assert(greeting() == anonfun0()) + * } + * }}} + */ +trait Function0[@specialized(Specializable.Primitives) +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(): R + + override def toString(): String = "" +} diff --git a/library/src/scala/Function1.scala b/library/src/scala/Function1.scala new file mode 100644 index 000000000000..b31ed60766d5 --- /dev/null +++ b/library/src/scala/Function1.scala @@ -0,0 +1,91 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Function1 { + + implicit final class UnliftOps[A, B] private[Function1](private val f: A => Option[B]) extends AnyVal { + /** Converts an optional function to a partial function. + * + * @example Unlike [[Function.unlift]], this [[UnliftOps.unlift]] method can be used in extractors. + * {{{ + * val of: Int => Option[String] = { i => + * if (i == 2) { + * Some("matched by an optional function") + * } else { + * None + * } + * } + * + * util.Random.nextInt(4) match { + * case of.unlift(m) => // Convert an optional function to a pattern + * println(m) + * case _ => + * println("Not matched") + * } + * }}} + */ + def unlift: PartialFunction[A, B] = Function.unlift(f) + } + +} + +/** A function of 1 parameter. + * + * In the following example, the definition of `succ` is + * shorthand, conceptually, for the anonymous class definition + * `anonfun1`, although the implementation details of how the + * function value is constructed may differ: + * + * {{{ + * object Main extends App { + * val succ = (x: Int) => x + 1 + * val anonfun1 = new Function1[Int, Int] { + * def apply(x: Int): Int = x + 1 + * } + * assert(succ(0) == anonfun1(0)) + * } + * }}} + * + * Note that the difference between `Function1` and [[scala.PartialFunction]] + * is that the latter can specify inputs which it will not handle. + */ +@annotation.implicitNotFound(msg = "No implicit view available from ${T1} => ${R}.") +trait Function1[@specialized(Specializable.Arg) -T1, @specialized(Specializable.Return) +R] extends AnyRef { self => + /** Apply the body of this function to the argument. + * @return the result of function application. + */ + def apply(v1: T1): R + + /** Composes two instances of `Function1` in a new `Function1`, with this function applied last. + * + * @tparam A the type to which function `g` can be applied + * @param g a function A => T1 + * @return a new function `f` such that `f(x) == apply(g(x))` + */ + @annotation.unspecialized def compose[A](g: A => T1): A => R = { x => apply(g(x)) } + + /** Composes two instances of `Function1` in a new `Function1`, with this function applied first. + * + * @tparam A the result type of function `g` + * @param g a function R => A + * @return a new function `f` such that `f(x) == g(apply(x))` + */ + @annotation.unspecialized def andThen[A](g: R => A): T1 => A = { x => g(apply(x)) } + + override def toString(): String = "" +} diff --git a/library/src/scala/Function10.scala b/library/src/scala/Function10.scala new file mode 100644 index 000000000000..c362ea877732 --- /dev/null +++ b/library/src/scala/Function10.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 10 parameters. + * + */ +trait Function10[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)).curried + } + /** Creates a tupled version of this function: instead of 10 arguments, + * it accepts a single [[scala.Tuple10]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)) == f(Tuple10(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)) => apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function11.scala b/library/src/scala/Function11.scala new file mode 100644 index 000000000000..1706470355c3 --- /dev/null +++ b/library/src/scala/Function11.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 11 parameters. + * + */ +trait Function11[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10, v11: T11): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11)).curried + } + /** Creates a tupled version of this function: instead of 11 arguments, + * it accepts a single [[scala.Tuple11]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11)) == f(Tuple11(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11)) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11)) => apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function12.scala b/library/src/scala/Function12.scala new file mode 100644 index 000000000000..5ccaa7722095 --- /dev/null +++ b/library/src/scala/Function12.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 12 parameters. + * + */ +trait Function12[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10, v11: T11, v12: T12): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12)).curried + } + /** Creates a tupled version of this function: instead of 12 arguments, + * it accepts a single [[scala.Tuple12]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12)) == f(Tuple12(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12)) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12)) => apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function13.scala b/library/src/scala/Function13.scala new file mode 100644 index 000000000000..a92b6710bbfb --- /dev/null +++ b/library/src/scala/Function13.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 13 parameters. + * + */ +trait Function13[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10, v11: T11, v12: T12, v13: T13): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13)).curried + } + /** Creates a tupled version of this function: instead of 13 arguments, + * it accepts a single [[scala.Tuple13]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13)) == f(Tuple13(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13)) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13)) => apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function14.scala b/library/src/scala/Function14.scala new file mode 100644 index 000000000000..687a3e693766 --- /dev/null +++ b/library/src/scala/Function14.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 14 parameters. + * + */ +trait Function14[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10, v11: T11, v12: T12, v13: T13, v14: T14): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14)).curried + } + /** Creates a tupled version of this function: instead of 14 arguments, + * it accepts a single [[scala.Tuple14]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14)) == f(Tuple14(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14)) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14)) => apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function15.scala b/library/src/scala/Function15.scala new file mode 100644 index 000000000000..c45cae4e1a97 --- /dev/null +++ b/library/src/scala/Function15.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 15 parameters. + * + */ +trait Function15[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10, v11: T11, v12: T12, v13: T13, v14: T14, v15: T15): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15)).curried + } + /** Creates a tupled version of this function: instead of 15 arguments, + * it accepts a single [[scala.Tuple15]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15)) == f(Tuple15(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15)) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15)) => apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function16.scala b/library/src/scala/Function16.scala new file mode 100644 index 000000000000..8795ccfbd546 --- /dev/null +++ b/library/src/scala/Function16.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 16 parameters. + * + */ +trait Function16[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10, v11: T11, v12: T12, v13: T13, v14: T14, v15: T15, v16: T16): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15)(x16) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => T16 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16)).curried + } + /** Creates a tupled version of this function: instead of 16 arguments, + * it accepts a single [[scala.Tuple16]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16)) == f(Tuple16(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16)) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16)) => apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function17.scala b/library/src/scala/Function17.scala new file mode 100644 index 000000000000..874ea3bcd9fc --- /dev/null +++ b/library/src/scala/Function17.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 17 parameters. + * + */ +trait Function17[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10, v11: T11, v12: T12, v13: T13, v14: T14, v15: T15, v16: T16, v17: T17): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15)(x16)(x17) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => T16 => T17 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17)).curried + } + /** Creates a tupled version of this function: instead of 17 arguments, + * it accepts a single [[scala.Tuple17]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17)) == f(Tuple17(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17)) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17)) => apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function18.scala b/library/src/scala/Function18.scala new file mode 100644 index 000000000000..4fa20a649416 --- /dev/null +++ b/library/src/scala/Function18.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 18 parameters. + * + */ +trait Function18[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10, v11: T11, v12: T12, v13: T13, v14: T14, v15: T15, v16: T16, v17: T17, v18: T18): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15)(x16)(x17)(x18) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => T16 => T17 => T18 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18)).curried + } + /** Creates a tupled version of this function: instead of 18 arguments, + * it accepts a single [[scala.Tuple18]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18)) == f(Tuple18(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18)) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18)) => apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function19.scala b/library/src/scala/Function19.scala new file mode 100644 index 000000000000..c59e32b4ae40 --- /dev/null +++ b/library/src/scala/Function19.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 19 parameters. + * + */ +trait Function19[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10, v11: T11, v12: T12, v13: T13, v14: T14, v15: T15, v16: T16, v17: T17, v18: T18, v19: T19): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15)(x16)(x17)(x18)(x19) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => T16 => T17 => T18 => T19 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18, x19: T19) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19)).curried + } + /** Creates a tupled version of this function: instead of 19 arguments, + * it accepts a single [[scala.Tuple19]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19)) == f(Tuple19(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19)) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19)) => apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function2.scala b/library/src/scala/Function2.scala new file mode 100644 index 000000000000..ccb066fb9f3a --- /dev/null +++ b/library/src/scala/Function2.scala @@ -0,0 +1,59 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 2 parameters. + * + * In the following example, the definition of `max` is + * shorthand, conceptually, for the anonymous class definition + * `anonfun2`, although the implementation details of how the + * function value is constructed may differ: + * + * {{{ + * object Main extends App { + * val max = (x: Int, y: Int) => if (x < y) y else x + * + * val anonfun2 = new Function2[Int, Int, Int] { + * def apply(x: Int, y: Int): Int = if (x < y) y else x + * } + * assert(max(0, 1) == anonfun2(0, 1)) + * } + * }}} + */ +trait Function2[@specialized(Specializable.Args) -T1, @specialized(Specializable.Args) -T2, @specialized(Specializable.Return) +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2) == apply(x1, x2)` + */ + @annotation.unspecialized def curried: T1 => T2 => R = { + (x1: T1) => (x2: T2) => apply(x1, x2) + } + /** Creates a tupled version of this function: instead of 2 arguments, + * it accepts a single [[scala.Tuple2]] argument. + * + * @return a function `f` such that `f((x1, x2)) == f(Tuple2(x1, x2)) == apply(x1, x2)` + */ + + @annotation.unspecialized def tupled: ((T1, T2)) => R = { + case ((x1, x2)) => apply(x1, x2) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function20.scala b/library/src/scala/Function20.scala new file mode 100644 index 000000000000..1b445f783310 --- /dev/null +++ b/library/src/scala/Function20.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 20 parameters. + * + */ +trait Function20[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10, v11: T11, v12: T12, v13: T13, v14: T14, v15: T15, v16: T16, v17: T17, v18: T18, v19: T19, v20: T20): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15)(x16)(x17)(x18)(x19)(x20) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => T16 => T17 => T18 => T19 => T20 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18, x19: T19, x20: T20) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20)).curried + } + /** Creates a tupled version of this function: instead of 20 arguments, + * it accepts a single [[scala.Tuple20]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20)) == f(Tuple20(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20)) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20)) => apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function21.scala b/library/src/scala/Function21.scala new file mode 100644 index 000000000000..e5e3047da3b3 --- /dev/null +++ b/library/src/scala/Function21.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 21 parameters. + * + */ +trait Function21[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, -T21, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10, v11: T11, v12: T12, v13: T13, v14: T14, v15: T15, v16: T16, v17: T17, v18: T18, v19: T19, v20: T20, v21: T21): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15)(x16)(x17)(x18)(x19)(x20)(x21) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => T16 => T17 => T18 => T19 => T20 => T21 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18, x19: T19, x20: T20, x21: T21) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21)).curried + } + /** Creates a tupled version of this function: instead of 21 arguments, + * it accepts a single [[scala.Tuple21]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21)) == f(Tuple21(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21)) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21)) => apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function22.scala b/library/src/scala/Function22.scala new file mode 100644 index 000000000000..9eae1d43ce26 --- /dev/null +++ b/library/src/scala/Function22.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 22 parameters. + * + */ +trait Function22[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, -T21, -T22, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10, v11: T11, v12: T12, v13: T13, v14: T14, v15: T15, v16: T16, v17: T17, v18: T18, v19: T19, v20: T20, v21: T21, v22: T22): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15)(x16)(x17)(x18)(x19)(x20)(x21)(x22) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => T16 => T17 => T18 => T19 => T20 => T21 => T22 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18, x19: T19, x20: T20, x21: T21, x22: T22) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22)).curried + } + /** Creates a tupled version of this function: instead of 22 arguments, + * it accepts a single [[scala.Tuple22]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22)) == f(Tuple22(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22)) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22)) => apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function3.scala b/library/src/scala/Function3.scala new file mode 100644 index 000000000000..5c29f6e4fbbd --- /dev/null +++ b/library/src/scala/Function3.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 3 parameters. + * + */ +trait Function3[-T1, -T2, -T3, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3) == apply(x1, x2, x3)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => R = { + (x1: T1) => (x2: T2) => (x3: T3) => apply(x1, x2, x3) + } + /** Creates a tupled version of this function: instead of 3 arguments, + * it accepts a single [[scala.Tuple3]] argument. + * + * @return a function `f` such that `f((x1, x2, x3)) == f(Tuple3(x1, x2, x3)) == apply(x1, x2, x3)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3)) => R = { + case ((x1, x2, x3)) => apply(x1, x2, x3) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function4.scala b/library/src/scala/Function4.scala new file mode 100644 index 000000000000..efc3c56909eb --- /dev/null +++ b/library/src/scala/Function4.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 4 parameters. + * + */ +trait Function4[-T1, -T2, -T3, -T4, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4) == apply(x1, x2, x3, x4)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => R = { + (x1: T1) => (x2: T2) => (x3: T3) => (x4: T4) => apply(x1, x2, x3, x4) + } + /** Creates a tupled version of this function: instead of 4 arguments, + * it accepts a single [[scala.Tuple4]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4)) == f(Tuple4(x1, x2, x3, x4)) == apply(x1, x2, x3, x4)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4)) => R = { + case ((x1, x2, x3, x4)) => apply(x1, x2, x3, x4) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function5.scala b/library/src/scala/Function5.scala new file mode 100644 index 000000000000..a0e4b082c728 --- /dev/null +++ b/library/src/scala/Function5.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 5 parameters. + * + */ +trait Function5[-T1, -T2, -T3, -T4, -T5, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5) == apply(x1, x2, x3, x4, x5)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5) => self.apply(x1, x2, x3, x4, x5)).curried + } + /** Creates a tupled version of this function: instead of 5 arguments, + * it accepts a single [[scala.Tuple5]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5)) == f(Tuple5(x1, x2, x3, x4, x5)) == apply(x1, x2, x3, x4, x5)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5)) => R = { + case ((x1, x2, x3, x4, x5)) => apply(x1, x2, x3, x4, x5) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function6.scala b/library/src/scala/Function6.scala new file mode 100644 index 000000000000..58d428c8e888 --- /dev/null +++ b/library/src/scala/Function6.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 6 parameters. + * + */ +trait Function6[-T1, -T2, -T3, -T4, -T5, -T6, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6) == apply(x1, x2, x3, x4, x5, x6)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6) => self.apply(x1, x2, x3, x4, x5, x6)).curried + } + /** Creates a tupled version of this function: instead of 6 arguments, + * it accepts a single [[scala.Tuple6]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6)) == f(Tuple6(x1, x2, x3, x4, x5, x6)) == apply(x1, x2, x3, x4, x5, x6)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6)) => R = { + case ((x1, x2, x3, x4, x5, x6)) => apply(x1, x2, x3, x4, x5, x6) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function7.scala b/library/src/scala/Function7.scala new file mode 100644 index 000000000000..8f4bfa19aa9d --- /dev/null +++ b/library/src/scala/Function7.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 7 parameters. + * + */ +trait Function7[-T1, -T2, -T3, -T4, -T5, -T6, -T7, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7) == apply(x1, x2, x3, x4, x5, x6, x7)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7) => self.apply(x1, x2, x3, x4, x5, x6, x7)).curried + } + /** Creates a tupled version of this function: instead of 7 arguments, + * it accepts a single [[scala.Tuple7]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7)) == f(Tuple7(x1, x2, x3, x4, x5, x6, x7)) == apply(x1, x2, x3, x4, x5, x6, x7)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7)) => apply(x1, x2, x3, x4, x5, x6, x7) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function8.scala b/library/src/scala/Function8.scala new file mode 100644 index 000000000000..384f6132d1a5 --- /dev/null +++ b/library/src/scala/Function8.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 8 parameters. + * + */ +trait Function8[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8) == apply(x1, x2, x3, x4, x5, x6, x7, x8)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8)).curried + } + /** Creates a tupled version of this function: instead of 8 arguments, + * it accepts a single [[scala.Tuple8]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8)) == f(Tuple8(x1, x2, x3, x4, x5, x6, x7, x8)) == apply(x1, x2, x3, x4, x5, x6, x7, x8)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8)) => apply(x1, x2, x3, x4, x5, x6, x7, x8) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Function9.scala b/library/src/scala/Function9.scala new file mode 100644 index 000000000000..a60f59f0f25d --- /dev/null +++ b/library/src/scala/Function9.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A function of 9 parameters. + * + */ +trait Function9[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, +R] extends AnyRef { self => + /** Apply the body of this function to the arguments. + * @return the result of function application. + */ + def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9): R + /** Creates a curried version of this function. + * + * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9)` + */ + @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => R = { + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9)).curried + } + /** Creates a tupled version of this function: instead of 9 arguments, + * it accepts a single [[scala.Tuple9]] argument. + * + * @return a function `f` such that `f((x1, x2, x3, x4, x5, x6, x7, x8, x9)) == f(Tuple9(x1, x2, x3, x4, x5, x6, x7, x8, x9)) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9)` + */ + + @annotation.unspecialized def tupled: ((T1, T2, T3, T4, T5, T6, T7, T8, T9)) => R = { + case ((x1, x2, x3, x4, x5, x6, x7, x8, x9)) => apply(x1, x2, x3, x4, x5, x6, x7, x8, x9) + } + override def toString(): String = "" +} diff --git a/library/src/scala/Int.scala b/library/src/scala/Int.scala new file mode 100644 index 000000000000..335e33233541 --- /dev/null +++ b/library/src/scala/Int.scala @@ -0,0 +1,488 @@ +/* + * 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. + */ + +// DO NOT EDIT, CHANGES WILL BE LOST +// This auto-generated code can be modified in "project/GenerateAnyVals.scala". +// Afterwards, running "sbt generateSources" regenerates this source file. + +package scala + +import scala.language.`2.13` + +/** `Int`, a 32-bit signed integer (equivalent to Java's `int` primitive type) is a + * subtype of [[scala.AnyVal]]. Instances of `Int` are not + * represented by an object in the underlying runtime system. + * + * There is an implicit conversion from [[scala.Int]] => [[scala.runtime.RichInt]] + * which provides useful non-primitive operations. + */ +final abstract class Int private extends AnyVal { + def toByte: Byte + def toShort: Short + def toChar: Char + def toInt: Int + def toLong: Long + def toFloat: Float + def toDouble: Double + + /** + * Returns the bitwise negation of this value. + * @example {{{ + * ~5 == -6 + * // in binary: ~00000101 == + * // 11111010 + * }}} + */ + def unary_~ : Int + /** Returns this value, unmodified. */ + def unary_+ : Int + /** Returns the negation of this value. */ + def unary_- : Int + + @deprecated("Adding a number and a String is deprecated. Use the string interpolation `s\"$num$str\"`", "2.13.0") + def +(x: String): String + + /** + * Returns this value bit-shifted left by the specified number of bits, + * filling in the new right bits with zeroes. + * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}} + */ + def <<(x: Int): Int + /** + * Returns this value bit-shifted left by the specified number of bits, + * filling in the new right bits with zeroes. + * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}} + */ + @deprecated("shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.", "2.12.7") + def <<(x: Long): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling the new left bits with zeroes. + * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}} + * @example {{{ + * -21 >>> 3 == 536870909 + * // in binary: 11111111 11111111 11111111 11101011 >>> 3 == + * // 00011111 11111111 11111111 11111101 + * }}} + */ + def >>>(x: Int): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling the new left bits with zeroes. + * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}} + * @example {{{ + * -21 >>> 3 == 536870909 + * // in binary: 11111111 11111111 11111111 11101011 >>> 3 == + * // 00011111 11111111 11111111 11111101 + * }}} + */ + @deprecated("shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.", "2.12.7") + def >>>(x: Long): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling in the left bits with the same value as the left-most bit of this. + * The effect of this is to retain the sign of the value. + * @example {{{ + * -21 >> 3 == -3 + * // in binary: 11111111 11111111 11111111 11101011 >> 3 == + * // 11111111 11111111 11111111 11111101 + * }}} + */ + def >>(x: Int): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling in the left bits with the same value as the left-most bit of this. + * The effect of this is to retain the sign of the value. + * @example {{{ + * -21 >> 3 == -3 + * // in binary: 11111111 11111111 11111111 11101011 >> 3 == + * // 11111111 11111111 11111111 11111101 + * }}} + */ + @deprecated("shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.", "2.12.7") + def >>(x: Long): Int + + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Byte): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Short): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Char): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Int): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Long): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Float): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Double): Boolean + + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Byte): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Short): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Char): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Int): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Long): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Float): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Double): Boolean + + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Byte): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Short): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Char): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Int): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Long): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Float): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Double): Boolean + + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Byte): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Short): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Char): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Int): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Long): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Float): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Double): Boolean + + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Byte): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Short): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Char): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Int): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Long): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Float): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Double): Boolean + + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Byte): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Short): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Char): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Int): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Long): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Float): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Double): Boolean + + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Byte): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Short): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Char): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Int): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Long): Long + + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Byte): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Short): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Char): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Int): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Long): Long + + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Byte): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Short): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Char): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Int): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Long): Long + + /** Returns the sum of this value and `x`. */ + def +(x: Byte): Int + /** Returns the sum of this value and `x`. */ + def +(x: Short): Int + /** Returns the sum of this value and `x`. */ + def +(x: Char): Int + /** Returns the sum of this value and `x`. */ + def +(x: Int): Int + /** Returns the sum of this value and `x`. */ + def +(x: Long): Long + /** Returns the sum of this value and `x`. */ + def +(x: Float): Float + /** Returns the sum of this value and `x`. */ + def +(x: Double): Double + + /** Returns the difference of this value and `x`. */ + def -(x: Byte): Int + /** Returns the difference of this value and `x`. */ + def -(x: Short): Int + /** Returns the difference of this value and `x`. */ + def -(x: Char): Int + /** Returns the difference of this value and `x`. */ + def -(x: Int): Int + /** Returns the difference of this value and `x`. */ + def -(x: Long): Long + /** Returns the difference of this value and `x`. */ + def -(x: Float): Float + /** Returns the difference of this value and `x`. */ + def -(x: Double): Double + + /** Returns the product of this value and `x`. */ + def *(x: Byte): Int + /** Returns the product of this value and `x`. */ + def *(x: Short): Int + /** Returns the product of this value and `x`. */ + def *(x: Char): Int + /** Returns the product of this value and `x`. */ + def *(x: Int): Int + /** Returns the product of this value and `x`. */ + def *(x: Long): Long + /** Returns the product of this value and `x`. */ + def *(x: Float): Float + /** Returns the product of this value and `x`. */ + def *(x: Double): Double + + /** Returns the quotient of this value and `x`. */ + def /(x: Byte): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Short): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Char): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Int): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Long): Long + /** Returns the quotient of this value and `x`. */ + def /(x: Float): Float + /** Returns the quotient of this value and `x`. */ + def /(x: Double): Double + + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Byte): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Short): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Char): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Int): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Long): Long + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Float): Float + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Double): Double + + // Provide a more specific return type for Scaladoc + override def getClass(): Class[Int] = ??? +} + +object Int extends AnyValCompanion { + /** The smallest value representable as an Int. */ + final val MinValue = java.lang.Integer.MIN_VALUE + + /** The largest value representable as an Int. */ + final val MaxValue = java.lang.Integer.MAX_VALUE + + /** Transform a value type into a boxed reference type. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToInteger`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the Int to be boxed + * @return a java.lang.Integer offering `x` as its underlying value. + */ + def box(x: Int): java.lang.Integer = ??? + + /** Transform a boxed type into a value type. Note that this + * method is not typesafe: it accepts any Object, but will throw + * an exception if the argument is not a java.lang.Integer. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxToInt`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the java.lang.Integer to be unboxed. + * @throws ClassCastException if the argument is not a java.lang.Integer + * @return the Int resulting from calling intValue() on `x` + */ + def unbox(x: java.lang.Object): Int = ??? + + /** The String representation of the scala.Int companion object. */ + override def toString = "object scala.Int" + /** Language mandated coercions from Int to "wider" types. */ + import scala.language.implicitConversions + @deprecated("Implicit conversion from Int to Float is dangerous because it loses precision. Write `.toFloat` instead.", "2.13.1") + implicit def int2float(x: Int): Float = x.toFloat + implicit def int2long(x: Int): Long = x.toLong + implicit def int2double(x: Int): Double = x.toDouble +} + diff --git a/library/src/scala/Long.scala b/library/src/scala/Long.scala new file mode 100644 index 000000000000..e19b7e706f96 --- /dev/null +++ b/library/src/scala/Long.scala @@ -0,0 +1,485 @@ +/* + * 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. + */ + +// DO NOT EDIT, CHANGES WILL BE LOST +// This auto-generated code can be modified in "project/GenerateAnyVals.scala". +// Afterwards, running "sbt generateSources" regenerates this source file. + +package scala + +import scala.language.`2.13` + +/** `Long`, a 64-bit signed integer (equivalent to Java's `long` primitive type) is a + * subtype of [[scala.AnyVal]]. Instances of `Long` are not + * represented by an object in the underlying runtime system. + * + * There is an implicit conversion from [[scala.Long]] => [[scala.runtime.RichLong]] + * which provides useful non-primitive operations. + */ +final abstract class Long private extends AnyVal { + def toByte: Byte + def toShort: Short + def toChar: Char + def toInt: Int + def toLong: Long + def toFloat: Float + def toDouble: Double + + /** + * Returns the bitwise negation of this value. + * @example {{{ + * ~5 == -6 + * // in binary: ~00000101 == + * // 11111010 + * }}} + */ + def unary_~ : Long + /** Returns this value, unmodified. */ + def unary_+ : Long + /** Returns the negation of this value. */ + def unary_- : Long + + @deprecated("Adding a number and a String is deprecated. Use the string interpolation `s\"$num$str\"`", "2.13.0") + def +(x: String): String + + /** + * Returns this value bit-shifted left by the specified number of bits, + * filling in the new right bits with zeroes. + * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}} + */ + def <<(x: Int): Long + /** + * Returns this value bit-shifted left by the specified number of bits, + * filling in the new right bits with zeroes. + * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}} + */ + def <<(x: Long): Long + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling the new left bits with zeroes. + * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}} + * @example {{{ + * -21 >>> 3 == 536870909 + * // in binary: 11111111 11111111 11111111 11101011 >>> 3 == + * // 00011111 11111111 11111111 11111101 + * }}} + */ + def >>>(x: Int): Long + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling the new left bits with zeroes. + * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}} + * @example {{{ + * -21 >>> 3 == 536870909 + * // in binary: 11111111 11111111 11111111 11101011 >>> 3 == + * // 00011111 11111111 11111111 11111101 + * }}} + */ + def >>>(x: Long): Long + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling in the left bits with the same value as the left-most bit of this. + * The effect of this is to retain the sign of the value. + * @example {{{ + * -21 >> 3 == -3 + * // in binary: 11111111 11111111 11111111 11101011 >> 3 == + * // 11111111 11111111 11111111 11111101 + * }}} + */ + def >>(x: Int): Long + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling in the left bits with the same value as the left-most bit of this. + * The effect of this is to retain the sign of the value. + * @example {{{ + * -21 >> 3 == -3 + * // in binary: 11111111 11111111 11111111 11101011 >> 3 == + * // 11111111 11111111 11111111 11111101 + * }}} + */ + def >>(x: Long): Long + + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Byte): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Short): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Char): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Int): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Long): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Float): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Double): Boolean + + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Byte): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Short): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Char): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Int): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Long): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Float): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Double): Boolean + + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Byte): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Short): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Char): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Int): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Long): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Float): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Double): Boolean + + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Byte): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Short): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Char): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Int): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Long): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Float): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Double): Boolean + + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Byte): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Short): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Char): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Int): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Long): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Float): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Double): Boolean + + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Byte): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Short): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Char): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Int): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Long): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Float): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Double): Boolean + + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Byte): Long + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Short): Long + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Char): Long + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Int): Long + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Long): Long + + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Byte): Long + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Short): Long + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Char): Long + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Int): Long + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Long): Long + + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Byte): Long + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Short): Long + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Char): Long + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Int): Long + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Long): Long + + /** Returns the sum of this value and `x`. */ + def +(x: Byte): Long + /** Returns the sum of this value and `x`. */ + def +(x: Short): Long + /** Returns the sum of this value and `x`. */ + def +(x: Char): Long + /** Returns the sum of this value and `x`. */ + def +(x: Int): Long + /** Returns the sum of this value and `x`. */ + def +(x: Long): Long + /** Returns the sum of this value and `x`. */ + def +(x: Float): Float + /** Returns the sum of this value and `x`. */ + def +(x: Double): Double + + /** Returns the difference of this value and `x`. */ + def -(x: Byte): Long + /** Returns the difference of this value and `x`. */ + def -(x: Short): Long + /** Returns the difference of this value and `x`. */ + def -(x: Char): Long + /** Returns the difference of this value and `x`. */ + def -(x: Int): Long + /** Returns the difference of this value and `x`. */ + def -(x: Long): Long + /** Returns the difference of this value and `x`. */ + def -(x: Float): Float + /** Returns the difference of this value and `x`. */ + def -(x: Double): Double + + /** Returns the product of this value and `x`. */ + def *(x: Byte): Long + /** Returns the product of this value and `x`. */ + def *(x: Short): Long + /** Returns the product of this value and `x`. */ + def *(x: Char): Long + /** Returns the product of this value and `x`. */ + def *(x: Int): Long + /** Returns the product of this value and `x`. */ + def *(x: Long): Long + /** Returns the product of this value and `x`. */ + def *(x: Float): Float + /** Returns the product of this value and `x`. */ + def *(x: Double): Double + + /** Returns the quotient of this value and `x`. */ + def /(x: Byte): Long + /** Returns the quotient of this value and `x`. */ + def /(x: Short): Long + /** Returns the quotient of this value and `x`. */ + def /(x: Char): Long + /** Returns the quotient of this value and `x`. */ + def /(x: Int): Long + /** Returns the quotient of this value and `x`. */ + def /(x: Long): Long + /** Returns the quotient of this value and `x`. */ + def /(x: Float): Float + /** Returns the quotient of this value and `x`. */ + def /(x: Double): Double + + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Byte): Long + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Short): Long + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Char): Long + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Int): Long + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Long): Long + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Float): Float + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Double): Double + + // Provide a more specific return type for Scaladoc + override def getClass(): Class[Long] = ??? +} + +object Long extends AnyValCompanion { + /** The smallest value representable as a Long. */ + final val MinValue = java.lang.Long.MIN_VALUE + + /** The largest value representable as a Long. */ + final val MaxValue = java.lang.Long.MAX_VALUE + + /** Transform a value type into a boxed reference type. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToLong`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the Long to be boxed + * @return a java.lang.Long offering `x` as its underlying value. + */ + def box(x: Long): java.lang.Long = ??? + + /** Transform a boxed type into a value type. Note that this + * method is not typesafe: it accepts any Object, but will throw + * an exception if the argument is not a java.lang.Long. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxToLong`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the java.lang.Long to be unboxed. + * @throws ClassCastException if the argument is not a java.lang.Long + * @return the Long resulting from calling longValue() on `x` + */ + def unbox(x: java.lang.Object): Long = ??? + + /** The String representation of the scala.Long companion object. */ + override def toString = "object scala.Long" + /** Language mandated coercions from Long to "wider" types. */ + import scala.language.implicitConversions + @deprecated("Implicit conversion from Long to Float is dangerous because it loses precision. Write `.toFloat` instead.", "2.13.1") + implicit def long2float(x: Long): Float = x.toFloat + @deprecated("Implicit conversion from Long to Double is dangerous because it loses precision. Write `.toDouble` instead.", "2.13.1") + implicit def long2double(x: Long): Double = x.toDouble +} + diff --git a/library/src/scala/MatchError.scala b/library/src/scala/MatchError.scala new file mode 100644 index 000000000000..7e6bcc480d8c --- /dev/null +++ b/library/src/scala/MatchError.scala @@ -0,0 +1,42 @@ +/* + * 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.language.`2.13` + +/** This class implements errors which are thrown whenever an + * object doesn't match any pattern of a pattern matching + * expression. + */ +final class MatchError(@transient obj: Any) extends RuntimeException { + /** There's no reason we need to call toString eagerly, + * so defer it until getMessage is called or object is serialized + */ + private[this] lazy val objString = { + def ofClass = "of class " + obj.getClass.getName + if (obj == null) "null" + else + try s"$obj ($ofClass)" + catch { + case _: Throwable => "an instance " + ofClass + } + } + + @throws[java.io.ObjectStreamException] + private def writeReplace(): Object = { + objString + this + } + + override def getMessage() = objString +} diff --git a/library/src/scala/NotImplementedError.scala b/library/src/scala/NotImplementedError.scala new file mode 100644 index 000000000000..b77d08b22bee --- /dev/null +++ b/library/src/scala/NotImplementedError.scala @@ -0,0 +1,23 @@ +/* + * 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.language.`2.13` + +/** Throwing this exception can be a temporary replacement for a method + * body that remains to be implemented. For instance, the exception is thrown by + * `Predef.???`. + */ +final class NotImplementedError(msg: String) extends Error(msg) { + def this() = this("an implementation is missing") +} diff --git a/library/src/scala/Option.scala b/library/src/scala/Option.scala new file mode 100644 index 000000000000..894eade2445a --- /dev/null +++ b/library/src/scala/Option.scala @@ -0,0 +1,630 @@ +/* + * 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.language.`2.13` + +object Option { + + import scala.language.implicitConversions + + /** An implicit conversion that converts an option to an iterable value */ + implicit def option2Iterable[A](xo: Option[A]): Iterable[A] = + if (xo.isEmpty) Iterable.empty else Iterable.single(xo.get) + + /** An Option factory which creates Some(x) if the argument is not null, + * and None if it is null. + * + * @param x the value + * @return Some(value) if value != null, None if value == null + */ + def apply[A](x: A): Option[A] = if (x == null) None else Some(x) + + /** An Option factory which returns `None` in a manner consistent with + * the collections hierarchy. + */ + def empty[A] : Option[A] = None + + /** When a given condition is true, evaluates the `a` argument and returns + * Some(a). When the condition is false, `a` is not evaluated and None is + * returned. + */ + def when[A](cond: Boolean)(a: => A): Option[A] = + if (cond) Some(a) else None + + /** Unless a given condition is true, this will evaluate the `a` argument and + * return Some(a). Otherwise, `a` is not evaluated and None is returned. + */ + @inline def unless[A](cond: Boolean)(a: => A): Option[A] = + when(!cond)(a) +} + +/** Represents optional values. Instances of `Option` + * are either an instance of $some or the object $none. + * + * The most idiomatic way to use an $option instance is to treat it + * as a collection or monad and use `map`,`flatMap`, `filter`, or + * `foreach`: + * + * {{{ + * val name: Option[String] = request.getParameter("name") + * val upper = name.map(_.trim).filter(_.length != 0).map(_.toUpperCase) + * println(upper.getOrElse("")) + * }}} + * + * Note that this is equivalent to {{{ + * val upper = for { + * name <- request.getParameter("name") + * trimmed <- Some(name.trim) + * upper <- Some(trimmed.toUpperCase) if trimmed.length != 0 + * } yield upper + * println(upper.getOrElse("")) + * }}} + * + * Because of how for comprehension works, if $none is returned + * from `request.getParameter`, the entire expression results in + * $none + * + * This allows for sophisticated chaining of $option values without + * having to check for the existence of a value. + * + * These are useful methods that exist for both $some and $none. + * - [[isDefined]] — True if not empty + * - [[isEmpty]] — True if empty + * - [[nonEmpty]] — True if not empty + * - [[orElse]] — Evaluate and return alternate optional value if empty + * - [[getOrElse]] — Evaluate and return alternate value if empty + * - [[get]] — Return value, throw exception if empty + * - [[fold]] — Apply function on optional value, return default if empty + * - [[map]] — Apply a function on the optional value + * - [[flatMap]] — Same as map but function must return an optional value + * - [[foreach]] — Apply a procedure on option value + * - [[collect]] — Apply partial pattern match on optional value + * - [[filter]] — An optional value satisfies predicate + * - [[filterNot]] — An optional value doesn't satisfy predicate + * - [[exists]] — Apply predicate on optional value, or false if empty + * - [[forall]] — Apply predicate on optional value, or true if empty + * - [[contains]] — Checks if value equals optional value, or false if empty + * - [[zip]] — Combine two optional values to make a paired optional value + * - [[unzip]] — Split an optional pair to two optional values + * - [[unzip3]] — Split an optional triple to three optional values + * - [[toList]] — Unary list of optional value, otherwise the empty list + * + * A less-idiomatic way to use $option values is via pattern matching: {{{ + * val nameMaybe = request.getParameter("name") + * nameMaybe match { + * case Some(name) => + * println(name.trim.toUppercase) + * case None => + * println("No name value") + * } + * }}} + * + * Interacting with code that can occasionally return null can be + * safely wrapped in $option to become $none and $some otherwise. {{{ + * val abc = new java.util.HashMap[Int, String] + * abc.put(1, "A") + * bMaybe = Option(abc.get(2)) + * bMaybe match { + * case Some(b) => + * println(s"Found \$b") + * case None => + * println("Not found") + * } + * }}} + * + * @note Many of the methods in here are duplicative with those + * in the Iterable hierarchy, but they are duplicated for a reason: + * the implicit conversion tends to leave one with an Iterable in + * situations where one could have retained an Option. + * + * @define none `None` + * @define some [[scala.Some]] + * @define option [[scala.Option]] + * @define p `p` + * @define f `f` + * @define coll option + * @define Coll `Option` + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + * @define collectExample + * @define undefinedorder + */ +@SerialVersionUID(-114498752079829388L) // value computed by serialver for 2.11.2, annotation added in 2.11.4 +sealed abstract class Option[+A] extends IterableOnce[A] with Product with Serializable { + self => + + /** Returns true if the option is $none, false otherwise. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(_) => false + * case None => true + * } + * }}} + */ + final def isEmpty: Boolean = this eq None + + /** Returns true if the option is an instance of $some, false otherwise. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(_) => true + * case None => false + * } + * }}} + */ + final def isDefined: Boolean = !isEmpty + + override final def knownSize: Int = if (isEmpty) 0 else 1 + + /** Returns the option's value. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) => x + * case None => throw new Exception + * } + * }}} + * @note The option must be nonempty. + * @throws NoSuchElementException if the option is empty. + */ + def get: A + + /** Returns the option's value if the option is nonempty, otherwise + * return the result of evaluating `default`. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) => x + * case None => default + * } + * }}} + * + * @param default the default expression. + */ + @inline final def getOrElse[B >: A](default: => B): B = + if (isEmpty) default else this.get + + /** Returns the option's value if it is nonempty, + * or `null` if it is empty. + * + * Although the use of null is discouraged, code written to use + * $option must often interface with code that expects and returns nulls. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) => x + * case None => null + * } + * }}} + * @example {{{ + * val initialText: Option[String] = getInitialText + * val textField = new JComponent(initialText.orNull,20) + * }}} + */ + @inline final def orNull[A1 >: A](implicit ev: Null <:< A1): A1 = this getOrElse ev(null) + + /** Returns a $some containing the result of applying $f to this $option's + * value if this $option is nonempty. + * Otherwise return $none. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) => Some(f(x)) + * case None => None + * } + * }}} + * @note This is similar to `flatMap` except here, + * $f does not need to wrap its result in an $option. + * + * @param f the function to apply + * @see flatMap + * @see foreach + */ + @inline final def map[B](f: A => B): Option[B] = + if (isEmpty) None else Some(f(this.get)) + + /** Returns the result of applying $f to this $option's + * value if the $option is nonempty. Otherwise, evaluates + * expression `ifEmpty`. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) => f(x) + * case None => ifEmpty + * } + * }}} + * This is also equivalent to: + * {{{ + * option.map(f).getOrElse(ifEmpty) + * }}} + * @param ifEmpty the expression to evaluate if empty. + * @param f the function to apply if nonempty. + */ + @inline final def fold[B](ifEmpty: => B)(f: A => B): B = + if (isEmpty) ifEmpty else f(this.get) + + /** Returns the result of applying $f to this $option's value if + * this $option is nonempty. + * Returns $none if this $option is empty. + * Slightly different from `map` in that $f is expected to + * return an $option (which could be $none). + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) => f(x) + * case None => None + * } + * }}} + * @param f the function to apply + * @see map + * @see foreach + */ + @inline final def flatMap[B](f: A => Option[B]): Option[B] = + if (isEmpty) None else f(this.get) + + /** Returns the nested $option value if it is nonempty. Otherwise, + * return $none. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(Some(b)) => Some(b) + * case _ => None + * } + * }}} + * @example {{{ + * Some(Some("something")).flatten + * }}} + * + * @param ev an implicit conversion that asserts that the value is + * also an $option. + * @see flatMap + */ + def flatten[B](implicit ev: A <:< Option[B]): Option[B] = + if (isEmpty) None else ev(this.get) + + /** Returns this $option if it is nonempty '''and''' applying the predicate $p to + * this $option's value returns true. Otherwise, return $none. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) if p(x) => Some(x) + * case _ => None + * } + * }}} + * @param p the predicate used for testing. + */ + @inline final def filter(p: A => Boolean): Option[A] = + if (isEmpty || p(this.get)) this else None + + /** Returns this $option if it is nonempty '''and''' applying the predicate $p to + * this $option's value returns false. Otherwise, return $none. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) if !p(x) => Some(x) + * case _ => None + * } + * }}} + * @param p the predicate used for testing. + */ + @inline final def filterNot(p: A => Boolean): Option[A] = + if (isEmpty || !p(this.get)) this else None + + /** Returns false if the option is $none, true otherwise. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(_) => true + * case None => false + * } + * }}} + * @note Implemented here to avoid the implicit conversion to Iterable. + */ + final def nonEmpty: Boolean = isDefined + + /** Necessary to keep $option from being implicitly converted to + * [[scala.collection.Iterable]] in `for` comprehensions. + */ + @inline final def withFilter(p: A => Boolean): WithFilter = new WithFilter(p) + + /** We need a whole WithFilter class to honor the "doesn't create a new + * collection" contract even though it seems unlikely to matter much in a + * collection with max size 1. + */ + class WithFilter(p: A => Boolean) { + def map[B](f: A => B): Option[B] = self filter p map f + def flatMap[B](f: A => Option[B]): Option[B] = self filter p flatMap f + def foreach[U](f: A => U): Unit = self filter p foreach f + def withFilter(q: A => Boolean): WithFilter = new WithFilter(x => p(x) && q(x)) + } + + /** Tests whether the option contains a given value as an element. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) => x == elem + * case None => false + * } + * }}} + * @example {{{ + * // Returns true because Some instance contains string "something" which equals "something". + * Some("something") contains "something" + * + * // Returns false because "something" != "anything". + * Some("something") contains "anything" + * + * // Returns false when method called on None. + * None contains "anything" + * }}} + * + * @param elem the element to test. + * @return `true` if the option has an element that is equal (as + * determined by `==`) to `elem`, `false` otherwise. + */ + final def contains[A1 >: A](elem: A1): Boolean = + !isEmpty && this.get == elem + + /** Returns true if this option is nonempty '''and''' the predicate + * $p returns true when applied to this $option's value. + * Otherwise, returns false. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) => p(x) + * case None => false + * } + * }}} + * @param p the predicate to test + */ + @inline final def exists(p: A => Boolean): Boolean = + !isEmpty && p(this.get) + + /** Returns true if this option is empty '''or''' the predicate + * $p returns true when applied to this $option's value. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) => p(x) + * case None => true + * } + * }}} + * @param p the predicate to test + */ + @inline final def forall(p: A => Boolean): Boolean = isEmpty || p(this.get) + + /** Apply the given procedure $f to the option's value, + * if it is nonempty. Otherwise, do nothing. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) => f(x) + * case None => () + * } + * }}} + * @param f the procedure to apply. + * @see map + * @see flatMap + */ + @inline final def foreach[U](f: A => U): Unit = { + if (!isEmpty) f(this.get) + } + + /** Returns a $some containing the result of + * applying `pf` to this $option's contained + * value, '''if''' this option is + * nonempty '''and''' `pf` is defined for that value. + * Returns $none otherwise. + * + * @example {{{ + * // Returns Some(HTTP) because the partial function covers the case. + * Some("http") collect {case "http" => "HTTP"} + * + * // Returns None because the partial function doesn't cover the case. + * Some("ftp") collect {case "http" => "HTTP"} + * + * // Returns None because the option is empty. There is no value to pass to the partial function. + * None collect {case value => value} + * }}} + * + * @param pf the partial function. + * @return the result of applying `pf` to this $option's + * value (if possible), or $none. + */ + @inline final def collect[B](pf: PartialFunction[A, B]): Option[B] = + if (!isEmpty) pf.lift(this.get) else None + + /** Returns this $option if it is nonempty, + * otherwise return the result of evaluating `alternative`. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) => Some(x) + * case None => alternative + * } + * }}} + * @param alternative the alternative expression. + */ + @inline final def orElse[B >: A](alternative: => Option[B]): Option[B] = + if (isEmpty) alternative else this + + /** Returns a $some formed from this option and another option + * by combining the corresponding elements in a pair. + * If either of the two options is empty, $none is returned. + * + * This is equivalent to: + * {{{ + * (option1, option2) match { + * case (Some(x), Some(y)) => Some((x, y)) + * case _ => None + * } + * }}} + * @example {{{ + * // Returns Some(("foo", "bar")) because both options are nonempty. + * Some("foo") zip Some("bar") + * + * // Returns None because `that` option is empty. + * Some("foo") zip None + * + * // Returns None because `this` option is empty. + * None zip Some("bar") + * }}} + * + * @param that the options which is going to be zipped + */ + final def zip[A1 >: A, B](that: Option[B]): Option[(A1, B)] = + if (isEmpty || that.isEmpty) None else Some((this.get, that.get)) + + /** Converts an Option of a pair into an Option of the first element and an Option of the second element. + * + * This is equivalent to: + * {{{ + * option match { + * case Some((x, y)) => (Some(x), Some(y)) + * case _ => (None, None) + * } + * }}} + * @tparam A1 the type of the first half of the element pair + * @tparam A2 the type of the second half of the element pair + * @param asPair an implicit conversion which asserts that the element type + * of this Option is a pair. + * @return a pair of Options, containing, respectively, the first and second half + * of the element pair of this Option. + */ + final def unzip[A1, A2](implicit asPair: A <:< (A1, A2)): (Option[A1], Option[A2]) = { + if (isEmpty) + (None, None) + else { + val e = asPair(this.get) + (Some(e._1), Some(e._2)) + } + } + + /** Converts an Option of a triple into three Options, one containing the element from each position of the triple. + * + * This is equivalent to: + * {{{ + * option match { + * case Some((x, y, z)) => (Some(x), Some(y), Some(z)) + * case _ => (None, None, None) + * } + * }}} + * @tparam A1 the type of the first of three elements in the triple + * @tparam A2 the type of the second of three elements in the triple + * @tparam A3 the type of the third of three elements in the triple + * @param asTriple an implicit conversion which asserts that the element type + * of this Option is a triple. + * @return a triple of Options, containing, respectively, the first, second, and third + * elements from the element triple of this Option. + */ + final def unzip3[A1, A2, A3](implicit asTriple: A <:< (A1, A2, A3)): (Option[A1], Option[A2], Option[A3]) = { + if (isEmpty) + (None, None, None) + else { + val e = asTriple(this.get) + (Some(e._1), Some(e._2), Some(e._3)) + } + } + + /** Returns a singleton iterator returning the $option's value + * if it is nonempty, or an empty iterator if the option is empty. + */ + def iterator: Iterator[A] = + if (isEmpty) collection.Iterator.empty else collection.Iterator.single(this.get) + + /** Returns a singleton list containing the $option's value + * if it is nonempty, or the empty list if the $option is empty. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) => List(x) + * case None => Nil + * } + * }}} + */ + def toList: List[A] = + if (isEmpty) List() else new ::(this.get, Nil) + + /** Returns a [[scala.util.Left]] containing the given + * argument `left` if this $option is empty, or + * a [[scala.util.Right]] containing this $option's value if + * this is nonempty. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) => Right(x) + * case None => Left(left) + * } + * }}} + * @param left the expression to evaluate and return if this is empty + * @see toLeft + */ + @inline final def toRight[X](left: => X): Either[X, A] = + if (isEmpty) Left(left) else Right(this.get) + + /** Returns a [[scala.util.Right]] containing the given + * argument `right` if this is empty, or + * a [[scala.util.Left]] containing this $option's value + * if this $option is nonempty. + * + * This is equivalent to: + * {{{ + * option match { + * case Some(x) => Left(x) + * case None => Right(right) + * } + * }}} + * @param right the expression to evaluate and return if this is empty + * @see toRight + */ + @inline final def toLeft[X](right: => X): Either[A, X] = + if (isEmpty) Right(right) else Left(this.get) +} + +/** Class `Some[A]` represents existing values of type + * `A`. + */ +@SerialVersionUID(1234815782226070388L) // value computed by serialver for 2.11.2, annotation added in 2.11.4 +final case class Some[+A](value: A) extends Option[A] { + def get: A = value +} + + +/** This case object represents non-existent values. + */ +@SerialVersionUID(5066590221178148012L) // value computed by serialver for 2.11.2, annotation added in 2.11.4 +case object None extends Option[Nothing] { + def get: Nothing = throw new NoSuchElementException("None.get") +} diff --git a/library/src/scala/PartialFunction.scala b/library/src/scala/PartialFunction.scala new file mode 100644 index 000000000000..af8199d6209e --- /dev/null +++ b/library/src/scala/PartialFunction.scala @@ -0,0 +1,415 @@ +/* + * 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.language.`2.13` +import scala.annotation.nowarn + +/** A partial function of type `PartialFunction[A, B]` is a unary function + * where the domain does not necessarily include all values of type `A`. + * The function [[isDefinedAt]] allows to test dynamically if a value is in + * the domain of the function. + * + * Even if `isDefinedAt` returns true for an `a: A`, calling `apply(a)` may + * still throw an exception, so the following code is legal: + * + * {{{ + * val f: PartialFunction[Int, Any] = { case x => x / 0 } // ArithmeticException: / by zero + * }}} + * + * It is the responsibility of the caller to call `isDefinedAt` before + * calling `apply`, because if `isDefinedAt` is false, it is not guaranteed + * `apply` will throw an exception to indicate an error condition. If an + * exception is not thrown, evaluation may result in an arbitrary value. + * + * The usual way to respect this contract is to call [[applyOrElse]], + * which is expected to be more efficient than calling both `isDefinedAt` + * and `apply`. + * + * Note that `isDefinedAt` may itself throw an exception while evaluating pattern guards + * or other parts of the `PartialFunction`. The same caveat holds for `applyOrElse`. + * + * {{{ + * val sample = 1 to 10 + * def isEven(n: Int) = n % 2 == 0 + * + * val eveningNews: PartialFunction[Int, String] = { + * case x if isEven(x) => s"\$x is even" + * } + * + * // The method "collect" is described as "filter + map" + * // because it uses a PartialFunction to select elements + * // to which the function is applied. + * val evenNumbers = sample.collect(eveningNews) + * + * // It's more usual to write the PartialFunction as a block of case clauses + * // called an "anonymous pattern-matching function". Since the collect method + * // expects a PartialFunction, one is synthesized from the case clauses. + * def evenly = sample.collect { case x if isEven(x) => s"\$x is even" } + * + * // A method that takes a Function will get one, using the same syntax. + * // Note that all cases are supplied since Function has no `isDefinedAt`. + * def evened = sample.map { case odd if !isEven(odd) => odd + 1 case even => even } + * }}} + * + * The main distinction between `PartialFunction` and [[scala.Function1]] is + * that the client of a `PartialFunction` can perform an alternative computation + * with input that is reported to be outside the domain of the function. + * + * For example: + * + * {{{ + * val oddlyEnough: PartialFunction[Int, String] = { + * case x if !isEven(x) => s"\$x is odd" + * } + * + * // The method orElse allows chaining another PartialFunction + * // to handle input outside the declared domain. + * val numbers = sample.map(eveningNews.orElse(oddlyEnough)) + * + * // The same computation but with a function literal that calls applyOrElse + * // with oddlyEnough as fallback, which it can do because a PartialFunction is a Function. + * val numbers = sample.map(n => eveningNews.applyOrElse(n, oddlyEnough)) + * }}} + * + * As a convenience, function literals can also be adapted into partial functions + * when needed. If the body of the function is a match expression, then the cases + * are used to synthesize the PartialFunction as already shown. + * + * {{{ + * // The partial function isDefinedAt inputs resulting in the Success case. + * val inputs = List("1", "two", "3").collect(x => Try(x.toInt) match { case Success(i) => i }) + * }}} + * + * @note Optional [[Function]]s, [[PartialFunction]]s and extractor objects + * can be converted to each other as shown in the following table. + *   + * | How to convert ... | to a [[PartialFunction]] | to an optional [[Function]] | to an extractor | + * | :---: | --- | --- | --- | + * | from a [[PartialFunction]] | [[Predef.identity]] | [[lift]] | [[Predef.identity]] | + * | from optional [[Function]] | [[Function1.UnliftOps#unlift]] or [[Function.unlift]] | [[Predef.identity]] | [[Function1.UnliftOps#unlift]] | + * | from an extractor | `{ case extractor(x) => x }` | `extractor.unapply(_)` | [[Predef.identity]] | + *   + * + * @define applyOrElseOrElse Note that calling [[isDefinedAt]] on the resulting partial function + * may apply the first partial function and execute its side effect. + * For efficiency, it is recommended to call [[applyOrElse]] instead of [[isDefinedAt]] or [[apply]]. + */ +trait PartialFunction[-A, +B] extends (A => B) { self => + import PartialFunction._ + + /** Tries to extract a `B` from an `A` in a pattern matching expression. */ + def unapply(a: A): Option[B] = lift(a) + + /** Returns an extractor object with a `unapplySeq` method, which extracts each element of a sequence data. + * + * @example {{{ + * val firstChar: String => Option[Char] = _.headOption + * + * Seq("foo", "bar", "baz") match { + * case firstChar.unlift.elementWise(c0, c1, c2) => + * println(s"\$c0, \$c1, \$c2") // Output: f, b, b + * } + * }}} + */ + def elementWise: ElementWiseExtractor[A, B] = new ElementWiseExtractor[A, B](this) + + /** Checks if a value is contained in the function's domain. + * + * @param x the value to test + * @return `'''true'''`, iff `x` is in the domain of this function, `'''false'''` otherwise. + */ + def isDefinedAt(x: A): Boolean + + /** Composes this partial function with a fallback partial function which + * gets applied where this partial function is not defined. + * + * @param that the fallback function + * @tparam A1 the argument type of the fallback function + * @tparam B1 the result type of the fallback function + * @return a partial function which has as domain the union of the domains + * of this partial function and `that`. The resulting partial function + * takes `x` to `this(x)` where `this` is defined, and to `that(x)` where it is not. + */ + def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1]): PartialFunction[A1, B1] = + new OrElse[A1, B1] (this, that) + //TODO: why not overload it with orElse(that: F1): F1? + + /** Composes this partial function with a transformation function that + * gets applied to results of this partial function. + * + * If the runtime type of the function is a `PartialFunction` then the + * other `andThen` method is used (note its cautions). + * + * @param k the transformation function + * @tparam C the result type of the transformation function. + * @return a partial function with the domain of this partial function, + * possibly narrowed by the specified function, which maps + * arguments `x` to `k(this(x))`. + */ + override def andThen[C](k: B => C): PartialFunction[A, C] = k match { + case pf: PartialFunction[B, C] => andThen(pf) + case _ => new AndThen[A, B, C](this, k) + } + + /** + * Composes this partial function with another partial function that + * gets applied to results of this partial function. + * + * $applyOrElseOrElse + * + * @param k the transformation function + * @tparam C the result type of the transformation function. + * @return a partial function with the domain of this partial function narrowed by + * other partial function, which maps arguments `x` to `k(this(x))`. + */ + def andThen[C](k: PartialFunction[B, C]): PartialFunction[A, C] = + new Combined[A, B, C](this, k) + + /** + * Composes another partial function `k` with this partial function so that this + * partial function gets applied to results of `k`. + * + * $applyOrElseOrElse + * + * @param k the transformation function + * @tparam R the parameter type of the transformation function. + * @return a partial function with the domain of other partial function narrowed by + * this partial function, which maps arguments `x` to `this(k(x))`. + */ + def compose[R](k: PartialFunction[R, A]): PartialFunction[R, B] = + new Combined[R, A, B](k, this) + + /** Turns this partial function into a plain function returning an `Option` result. + * @see Function.unlift + * @return a function that takes an argument `x` to `Some(this(x))` if `this` + * is defined for `x`, and to `None` otherwise. + */ + def lift: A => Option[B] = new Lifted(this) + + /** Applies this partial function to the given argument when it is contained in the function domain. + * Applies fallback function where this partial function is not defined. + * + * Note that expression `pf.applyOrElse(x, default)` is equivalent to + * {{{ if(pf isDefinedAt x) pf(x) else default(x) }}} + * except that `applyOrElse` method can be implemented more efficiently. + * For all partial function literals the compiler generates an `applyOrElse` implementation which + * avoids double evaluation of pattern matchers and guards. + * This makes `applyOrElse` the basis for the efficient implementation for many operations and scenarios, such as: + * + * - combining partial functions into `orElse`/`andThen` chains does not lead to + * excessive `apply`/`isDefinedAt` evaluation + * - `lift` and `unlift` do not evaluate source functions twice on each invocation + * - `runWith` allows efficient imperative-style combining of partial functions + * with conditionally applied actions + * + * For non-literal partial function classes with nontrivial `isDefinedAt` method + * it is recommended to override `applyOrElse` with custom implementation that avoids + * double `isDefinedAt` evaluation. This may result in better performance + * and more predictable behavior w.r.t. side effects. + * + * @param x the function argument + * @param default the fallback function + * @return the result of this function or fallback function application. + */ + def applyOrElse[A1 <: A, B1 >: B](x: A1, default: A1 => B1): B1 = + if (isDefinedAt(x)) apply(x) else default(x) + + /** Composes this partial function with an action function which + * gets applied to results of this partial function. + * The action function is invoked only for its side effects; its result is ignored. + * + * Note that expression `pf.runWith(action)(x)` is equivalent to + * {{{ if(pf isDefinedAt x) { action(pf(x)); true } else false }}} + * except that `runWith` is implemented via `applyOrElse` and thus potentially more efficient. + * Using `runWith` avoids double evaluation of pattern matchers and guards for partial function literals. + * @see `applyOrElse`. + * + * @param action the action function + * @return a function which maps arguments `x` to `isDefinedAt(x)`. The resulting function + * runs `action(this(x))` where `this` is defined. + */ + def runWith[U](action: B => U): A => Boolean = { x => + val z = applyOrElse(x, checkFallback[B]) + if (!fallbackOccurred(z)) { action(z); true } else false + } +} + +/** A few handy operations which leverage the extra bit of information + * available in partial functions. Examples: + * {{{ + * import PartialFunction._ + * + * def strangeConditional(other: Any): Boolean = cond(other) { + * case x: String if x == "abc" || x == "def" => true + * case x: Int => true + * } + * def onlyInt(v: Any): Option[Int] = condOpt(v) { case x: Int => x } + * }}} + */ +object PartialFunction { + + final class ElementWiseExtractor[-A, +B] private[PartialFunction] (private val pf: PartialFunction[A, B]) extends AnyVal { + @nowarn("cat=lint-nonlocal-return") + def unapplySeq(seq: Seq[A]): Option[Seq[B]] = { + Some(seq.map { + case pf(b) => b + case _ => return None + }) + } + } + + /** Composite function produced by `PartialFunction#orElse` method + */ + private class OrElse[-A, +B] (f1: PartialFunction[A, B], f2: PartialFunction[A, B]) + extends scala.runtime.AbstractPartialFunction[A, B] with Serializable { + def isDefinedAt(x: A) = f1.isDefinedAt(x) || f2.isDefinedAt(x) + + override def apply(x: A): B = f1.applyOrElse(x, f2) + + override def applyOrElse[A1 <: A, B1 >: B](x: A1, default: A1 => B1): B1 = { + val z = f1.applyOrElse(x, checkFallback[B]) + if (!fallbackOccurred(z)) z else f2.applyOrElse(x, default) + } + + override def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1]): OrElse[A1, B1] = + new OrElse[A1, B1] (f1, f2 orElse that) + + override def andThen[C](k: B => C): OrElse[A, C] = + new OrElse[A, C] (f1 andThen k, f2 andThen k) + } + + /** Composite function produced by `PartialFunction#andThen` method + */ + private class AndThen[-A, B, +C] (pf: PartialFunction[A, B], k: B => C) extends PartialFunction[A, C] with Serializable { + def isDefinedAt(x: A) = pf.isDefinedAt(x) + + def apply(x: A): C = k(pf(x)) + + override def applyOrElse[A1 <: A, C1 >: C](x: A1, default: A1 => C1): C1 = { + val z = pf.applyOrElse(x, checkFallback[B]) + if (!fallbackOccurred(z)) k(z) else default(x) + } + } + + /** Composite function produced by `PartialFunction#andThen` method + */ + private class Combined[-A, B, +C] (pf: PartialFunction[A, B], k: PartialFunction[B, C]) extends PartialFunction[A, C] with Serializable { + def isDefinedAt(x: A): Boolean = { + val b: B = pf.applyOrElse(x, checkFallback[B]) + if (!fallbackOccurred(b)) k.isDefinedAt(b) else false + } + + def apply(x: A): C = k(pf(x)) + + override def applyOrElse[A1 <: A, C1 >: C](x: A1, default: A1 => C1): C1 = { + val pfv = pf.applyOrElse(x, checkFallback[B]) + if (!fallbackOccurred(pfv)) k.applyOrElse(pfv, (_: B) => default(x)) else default(x) + } + } + + /** To implement patterns like {{{ if(pf isDefinedAt x) f1(pf(x)) else f2(x) }}} efficiently + * the following trick is used: + * + * To avoid double evaluation of pattern matchers & guards `applyOrElse` method is used here + * instead of `isDefinedAt`/`apply` pair. + * + * After call to `applyOrElse` we need both the function result it returned and + * the fact if the function's argument was contained in its domain. The only degree of freedom we have here + * to achieve this goal is tweaking with the continuation argument (`default`) of `applyOrElse` method. + * The obvious way is to throw an exception from `default` function and to catch it after + * calling `applyOrElse` but I consider this somewhat inefficient. + * + * I know only one way how you can do this task efficiently: `default` function should return unique marker object + * which never may be returned by any other (regular/partial) function. This way after calling `applyOrElse` you need + * just one reference comparison to distinguish if `pf isDefined x` or not. + * + * This correctly interacts with specialization as return type of `applyOrElse` + * (which is parameterized upper bound) can never be specialized. + * + * Here `fallback_fn` is used as both unique marker object and special fallback function that returns it. + */ + private[this] val fallback_fn: Any => Any = _ => fallback_fn + private def checkFallback[B] = fallback_fn.asInstanceOf[Any => B] + private def fallbackOccurred[B](x: B) = fallback_fn eq x.asInstanceOf[AnyRef] + + private class Lifted[-A, +B] (val pf: PartialFunction[A, B]) + extends scala.runtime.AbstractFunction1[A, Option[B]] with Serializable { + + def apply(x: A): Option[B] = { + val z = pf.applyOrElse(x, checkFallback[B]) + if (!fallbackOccurred(z)) Some(z) else None + } + } + + private class Unlifted[A, B] (f: A => Option[B]) extends scala.runtime.AbstractPartialFunction[A, B] with Serializable { + def isDefinedAt(x: A): Boolean = f(x).isDefined + + override def applyOrElse[A1 <: A, B1 >: B](x: A1, default: A1 => B1): B1 = { + f(x).getOrElse(default(x)) + } + + override def lift = f + } + + private[scala] def unlifted[A, B](f: A => Option[B]): PartialFunction[A, B] = f match { + case lf: Lifted[A, B] => lf.pf + case ff => new Unlifted(ff) + } + + /** Converts an ordinary function to a partial function. Note that calling `isDefinedAt(x)` on + * this partial function will return `true` for every `x`. + * @param f an ordinary function + * @return a partial function which delegates to the ordinary function `f` + */ + def fromFunction[A, B](f: A => B): PartialFunction[A, B] = { case x => f(x) } + + private[this] val constFalse: Any => Boolean = { _ => false} + + private[this] val empty_pf: PartialFunction[Any, Nothing] = new PartialFunction[Any, Nothing] with Serializable { + def isDefinedAt(x: Any) = false + def apply(x: Any) = throw new MatchError(x) + override def orElse[A1, B1](that: PartialFunction[A1, B1]) = that + override def andThen[C](k: Nothing => C): PartialFunction[Any, Nothing] = this + override val lift: Any => None.type = (x: Any) => None + override def runWith[U](action: Nothing => U) = constFalse + } + + /** The partial function with empty domain. + * Any attempt to invoke empty partial function leads to throwing [[scala.MatchError]] exception. + */ + def empty[A, B] : PartialFunction[A, B] = empty_pf + + /** A Boolean test that is the result of the given function where defined, + * and false otherwise. + * + * It behaves like a `case _ => false` were added to the partial function. + * + * @param x the value to test + * @param pf the partial function + * @return true, iff `x` is in the domain of `pf` and `pf(x) == true`. + */ + def cond[A](x: A)(pf: PartialFunction[A, Boolean]): Boolean = pf.applyOrElse(x, constFalse) + + /** Apply the function to the given value if defined, and return the result + * in a `Some`; otherwise, return `None`. + * + * @param x the value to test + * @param pf the PartialFunction[T, U] + * @return `Some(pf(x))` if `pf isDefinedAt x`, `None` otherwise. + */ + def condOpt[A, B](x: A)(pf: PartialFunction[A, B]): Option[B] = { + val z = pf.applyOrElse(x, checkFallback[B]) + if (!fallbackOccurred(z)) Some(z) else None + } +} diff --git a/library/src/scala/Precise.scala b/library/src/scala/Precise.scala index aad42ca8950f..f8a8dd6b47f4 100644 --- a/library/src/scala/Precise.scala +++ b/library/src/scala/Precise.scala @@ -7,5 +7,5 @@ import language.experimental.erasedDefinitions * in precise mode. This means that singleton types and union types are not * widened. */ -@experimental erased trait Precise: +@experimental trait Precise extends compiletime.Erased: type Self diff --git a/library/src/scala/Predef.scala b/library/src/scala/Predef.scala new file mode 100644 index 000000000000..7e2c2f09666b --- /dev/null +++ b/library/src/scala/Predef.scala @@ -0,0 +1,703 @@ +/* + * 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.language.`2.13` +import scala.language.implicitConversions + +import scala.collection.{mutable, immutable, ArrayOps, StringOps}, immutable.WrappedString +import scala.annotation.{elidable, experimental, implicitNotFound, publicInBinary, targetName }, elidable.ASSERTION +import scala.annotation.meta.{ companionClass, companionMethod } +import scala.annotation.internal.{ RuntimeChecked } +import scala.compiletime.summonFrom + +/** The `Predef` object provides definitions that are accessible in all Scala + * compilation units without explicit qualification. + * + * === Commonly Used Types === + * Predef provides type aliases for types which are commonly used, such as + * the immutable collection types [[scala.collection.immutable.Map]] and + * [[scala.collection.immutable.Set]]. + * + * === Console Output === + * For basic console output, `Predef` provides convenience methods [[print(x:Any* print]] and [[println(x:Any* println]], + * which are aliases of the methods in the object [[scala.Console]]. + * + * === Assertions === + * A set of `assert` functions are provided for use as a way to document + * and dynamically check invariants in code. Invocations of `assert` can be elided + * at compile time by providing the command line option `-Xdisable-assertions`, + * which raises `-Xelide-below` above `elidable.ASSERTION`, to the `scalac` command. + * + * Variants of `assert` intended for use with static analysis tools are also + * provided: `assume`, `require` and `ensuring`. `require` and `ensuring` are + * intended for use as a means of design-by-contract style specification + * of pre- and post-conditions on functions, with the intention that these + * specifications could be consumed by a static analysis tool. For instance, + * + * {{{ + * def addNaturals(nats: List[Int]): Int = { + * require(nats forall (_ >= 0), "List contains negative numbers") + * nats.foldLeft(0)(_ + _) + * } ensuring(_ >= 0) + * }}} + * + * The declaration of `addNaturals` states that the list of integers passed should + * only contain natural numbers (i.e. non-negative), and that the result returned + * will also be natural. `require` is distinct from `assert` in that if the + * condition fails, then the caller of the function is to blame rather than a + * logical error having been made within `addNaturals` itself. `ensuring` is a + * form of `assert` that declares the guarantee the function is providing with + * regards to its return value. + * + * === Implicit Conversions === + * A number of commonly applied implicit conversions are also defined here, and + * in the parent type [[scala.LowPriorityImplicits]]. Implicit conversions + * are provided for the "widening" of numeric values, for instance, converting a + * Short value to a Long value as required, and to add additional higher-order + * functions to Array values. These are described in more detail in the documentation of [[scala.Array]]. + * + * @groupname utilities Utility Methods + * @groupprio utilities 10 + * + * @groupname assertions Assertions + * @groupprio assertions 20 + * @groupdesc assertions These methods support program verification and runtime correctness. + * + * @groupname console-output Console Output + * @groupprio console-output 30 + * @groupdesc console-output These methods provide output via the console. + * + * @groupname aliases Aliases + * @groupprio aliases 50 + * @groupdesc aliases These aliases bring selected immutable types into scope without any imports. + * + * @groupname conversions-string String Conversions + * @groupprio conversions-string 60 + * @groupdesc conversions-string Conversions from String to StringOps or WrappedString. + * + * @groupname implicit-classes-any Implicit Classes + * @groupprio implicit-classes-any 70 + * @groupdesc implicit-classes-any These implicit classes add useful extension methods to every type. + * + * @groupname char-sequence-wrappers CharSequence Wrappers + * @groupprio char-sequence-wrappers 80 + * @groupdesc char-sequence-wrappers Wrappers that implements CharSequence and were implicit classes. + * + * @groupname conversions-java-to-anyval Java to Scala + * @groupprio conversions-java-to-anyval 90 + * @groupdesc conversions-java-to-anyval Implicit conversion from Java primitive wrapper types to Scala equivalents. + * + * @groupname conversions-anyval-to-java Scala to Java + * @groupprio conversions-anyval-to-java 100 + * @groupdesc conversions-anyval-to-java Implicit conversion from Scala AnyVals to Java primitive wrapper types equivalents. + * + * @groupname conversions-array-to-wrapped-array Array to ArraySeq + * @groupprio conversions-array-to-wrapped-array 110 + * @groupdesc conversions-array-to-wrapped-array Conversions from Arrays to ArraySeqs. + */ +object Predef extends LowPriorityImplicits { + /** + * Retrieve the runtime representation of a class type. `classOf[T]` is equivalent to + * the class literal `T.class` in Java. + * + * @example {{{ + * val listClass = classOf[List[_]] + * // listClass is java.lang.Class[List[_]] = class scala.collection.immutable.List + * + * val mapIntString = classOf[Map[Int,String]] + * // mapIntString is java.lang.Class[Map[Int,String]] = interface scala.collection.immutable.Map + * }}} + * + * @return The runtime [[Class]] representation of type `T`. + * @group utilities + */ + def classOf[T]: Class[T] = null // This is a stub method. The actual implementation is filled in by the compiler. + + /** + * Retrieve the single value of a type with a unique inhabitant. + * + * @example {{{ + * object Foo + * val foo = valueOf[Foo.type] + * // foo is Foo.type = Foo + * + * val bar = valueOf[23] + * // bar is 23.type = 23 + * }}} + * @group utilities + */ + @inline def valueOf[T](implicit vt: ValueOf[T]): T = vt.value + + /** + * Retrieve the single value of a type with a unique inhabitant. + * + * @example {{{ + * object Foo + * val foo = valueOf[Foo.type] + * // foo is Foo.type = Foo + * + * val bar = valueOf[23] + * // bar is 23.type = 23 + * }}} + * @group utilities + */ + inline def valueOf[T]: T = summonFrom { + case ev: ValueOf[T] => ev.value + } + + /** The `String` type in Scala has all the methods of the underlying + * [[java.lang.String]], of which it is just an alias. + * + * In addition, extension methods in [[scala.collection.StringOps]] + * are added implicitly through the conversion [[augmentString]]. + * @group aliases + */ + type String = java.lang.String + /** @group aliases */ + type Class[T] = java.lang.Class[T] + + // miscellaneous ----------------------------------------------------- + scala.`package` // to force scala package object to be seen. + scala.collection.immutable.List // to force Nil, :: to be seen. + + /** @group aliases */ + type Function[-A, +B] = Function1[A, B] + + /** @group aliases */ + type Map[K, +V] = immutable.Map[K, V] + /** @group aliases */ + type Set[A] = immutable.Set[A] + /** @group aliases */ + val Map = immutable.Map + /** @group aliases */ + val Set = immutable.Set + + /** + * Allows destructuring tuples with the same syntax as constructing them. + * + * @example {{{ + * val tup = "foobar" -> 3 + * + * val c = tup match { + * case str -> i => str.charAt(i) + * } + * }}} + * @group aliases + */ + val -> = Tuple2 + + // Manifest types, companions, and incantations for summoning + // TODO undeprecated until Scala reflection becomes non-experimental + // @deprecated("this notion doesn't have a corresponding concept in 2.10, because scala.reflect.runtime.universe.TypeTag can capture arbitrary types. Use type tags instead of manifests, and there will be no need in opt manifests.", "2.10.0") + type OptManifest[T] = scala.reflect.OptManifest[T] + @implicitNotFound(msg = "No Manifest available for ${T}.") + // TODO undeprecated until Scala reflection becomes non-experimental + // @deprecated("use `scala.reflect.ClassTag` (to capture erasures) or scala.reflect.runtime.universe.TypeTag (to capture types) or both instead", "2.10.0") + type Manifest[T] = scala.reflect.Manifest[T] + // TODO undeprecated until Scala reflection becomes non-experimental + // @deprecated("use `scala.reflect.ClassTag` (to capture erasures) or scala.reflect.runtime.universe.TypeTag (to capture types) or both instead", "2.10.0") + val Manifest = scala.reflect.Manifest + // TODO undeprecated until Scala reflection becomes non-experimental + // @deprecated("this notion doesn't have a corresponding concept in 2.10, because scala.reflect.runtime.universe.TypeTag can capture arbitrary types. Use type tags instead of manifests, and there will be no need in opt manifests.", "2.10.0") + val NoManifest = scala.reflect.NoManifest + + // TODO undeprecated until Scala reflection becomes non-experimental + // @deprecated("use scala.reflect.classTag[T] and scala.reflect.runtime.universe.typeTag[T] instead", "2.10.0") + def manifest[T](implicit m: Manifest[T]): Manifest[T] = m + // TODO undeprecated until Scala reflection becomes non-experimental + // @deprecated("this notion doesn't have a corresponding concept in 2.10, because scala.reflect.runtime.universe.TypeTag can capture arbitrary types. Use type tags instead of manifests, and there will be no need in opt manifests.", "2.10.0") + def optManifest[T](implicit m: OptManifest[T]): OptManifest[T] = m + + // Minor variations on identity functions + + /** + * A method that returns its input value. + * @tparam A type of the input value x. + * @param x the value of type `A` to be returned. + * @return the value `x`. + * @group utilities */ + @inline def identity[A](x: A): A = x // see `$conforms` for the implicit version + + /** Summon an implicit value of type `T`. Usually, the argument is not passed explicitly. + * + * @tparam T the type of the value to be summoned + * @return the implicit value of type `T` + * @group utilities + */ + @inline def implicitly[T](implicit e: T): T = e // TODO: when dependent method types are on by default, give this result type `e.type`, so that inliner has better chance of knowing which method to inline in calls like `implicitly[MatchingStrategy[Option]].zero` + + /** Summon a given value of type `T`. Usually, the argument is not passed explicitly. + * + * @tparam T the type of the value to be summoned + * @return the given value typed: the provided type parameter + */ + transparent inline def summon[T](using x: T): x.type = x + + /** Used to mark code blocks as being expressions, instead of being taken as part of anonymous classes and the like. + * This is just a different name for [[identity]]. + * + * @example Separating code blocks from `new`: + * {{{ + * val x = new AnyRef + * { + * val y = ... + * println(y) + * } + * // the { ... } block is seen as the body of an anonymous class + * + * val x = new AnyRef + * + * { + * val y = ... + * println(y) + * } + * // an empty line is a brittle "fix" + * + * val x = new AnyRef + * locally { + * val y = ... + * println(y) + * } + * // locally guards the block and helps communicate intent + * }}} + * @group utilities + */ + @inline def locally[T](@deprecatedName("x") x: T): T = x + + // ============================================================================================== + // ========================================= ASSERTIONS ========================================= + // ============================================================================================== + + /* In Scala 3, `assert` are methods that are `transparent` and `inline`. + In Scala 2, `assert` are methods that are elidable, inlinable by the optimizer + For scala 2 code to be able to run with the scala 3 library in the classpath + (following our own compatibility policies), we will need the `assert` methods + to be available at runtime. + To achieve this, we keep the Scala 3 signature publicly available. + We rely on the fact that it is `inline` and will not be visible in the bytecode. + To add the required Scala 2 ones, we define the `scala2Assert`, we use: + - `@targetName` to swap the name in the generated code to `assert` + - `@publicInBinary` to make it available during runtime. + As such, we would successfully hijack the definitions of `assert` such as: + - At compile time, we would have the definitions of `assert` + - At runtime, the definitions of `scala2Assert` as `assert` + NOTE: Tasty-Reader in Scala 2 will have to learn about this swapping if we are to + allow loading the full Scala 3 library by it. + */ + + /** Tests an expression, throwing an `AssertionError` if false. + * Calls to this method will not be generated if `-Xelide-below` + * is greater than `ASSERTION`. + * + * @see [[scala.annotation.elidable elidable]] + * @param assertion the expression to test + * @group assertions + */ + @elidable(ASSERTION) @publicInBinary + @targetName("assert") private[scala] def scala2Assert(assertion: Boolean): Unit = { + if (!assertion) + throw new java.lang.AssertionError("assertion failed") + } + + /** Tests an expression, throwing an `AssertionError` if false. + * Calls to this method will not be generated if `-Xelide-below` + * is greater than `ASSERTION`. + * + * @see [[scala.annotation.elidable elidable]] + * @param assertion the expression to test + * @param message a String to include in the failure message + * @group assertions + */ + @elidable(ASSERTION) @inline @publicInBinary + @targetName("assert") private[scala] final def scala2Assert(assertion: Boolean, message: => Any): Unit = { + if (!assertion) + throw new java.lang.AssertionError("assertion failed: "+ message) + } + + transparent inline def assert(inline assertion: Boolean, inline message: => Any): Unit = + if !assertion then scala.runtime.Scala3RunTime.assertFailed(message) + + transparent inline def assert(inline assertion: Boolean): Unit = + if !assertion then scala.runtime.Scala3RunTime.assertFailed() + + // ============================================================================================== + // ======================================== ASSUMPTIONS ========================================= + // ============================================================================================== + + /** Tests an expression, throwing an `AssertionError` if false. + * This method differs from assert only in the intent expressed: + * assert contains a predicate which needs to be proven, while + * assume contains an axiom for a static checker. Calls to this method + * will not be generated if `-Xelide-below` is greater than `ASSERTION`. + * + * @see [[scala.annotation.elidable elidable]] + * @param assumption the expression to test + * @group assertions + */ + @elidable(ASSERTION) + def assume(assumption: Boolean): Unit = { + if (!assumption) + throw new java.lang.AssertionError("assumption failed") + } + + /** Tests an expression, throwing an `AssertionError` if false. + * This method differs from assert only in the intent expressed: + * assert contains a predicate which needs to be proven, while + * assume contains an axiom for a static checker. Calls to this method + * will not be generated if `-Xelide-below` is greater than `ASSERTION`. + * + * @see [[scala.annotation.elidable elidable]] + * @param assumption the expression to test + * @param message a String to include in the failure message + * @group assertions + */ + @elidable(ASSERTION) @inline + final def assume(assumption: Boolean, message: => Any): Unit = { + if (!assumption) + throw new java.lang.AssertionError("assumption failed: "+ message) + } + + /** Tests an expression, throwing an `IllegalArgumentException` if false. + * This method is similar to `assert`, but blames the caller of the method + * for violating the condition. + * + * @param requirement the expression to test + * @group assertions + */ + def require(requirement: Boolean): Unit = { + if (!requirement) + throw new IllegalArgumentException("requirement failed") + } + + /** Tests an expression, throwing an `IllegalArgumentException` if false. + * This method is similar to `assert`, but blames the caller of the method + * for violating the condition. + * + * @param requirement the expression to test + * @param message a String to include in the failure message + * @group assertions + */ + @inline final def require(requirement: Boolean, message: => Any): Unit = { + if (!requirement) + throw new IllegalArgumentException("requirement failed: "+ message) + } + + /** `???` can be used for marking methods that remain to be implemented. + * @throws NotImplementedError when `???` is invoked. + * @group utilities + */ + def ??? : Nothing = throw new NotImplementedError + + // implicit classes ----------------------------------------------------- + + /** @group implicit-classes-any */ + implicit final class ArrowAssoc[A](private val self: A) extends AnyVal { + @inline def -> [B](y: B): (A, B) = (self, y) + @deprecated("Use `->` instead. If you still wish to display it as one character, consider using a font with programming ligatures such as Fira Code.", "2.13.0") + def →[B](y: B): (A, B) = ->(y) + } + + /** @group implicit-classes-any */ + implicit final class Ensuring[A](private val self: A) extends AnyVal { + def ensuring(cond: Boolean): A = { assert(cond); self } + def ensuring(cond: Boolean, msg: => Any): A = { assert(cond, msg); self } + def ensuring(cond: A => Boolean): A = { assert(cond(self)); self } + def ensuring(cond: A => Boolean, msg: => Any): A = { assert(cond(self), msg); self } + } + + /** @group implicit-classes-any */ + implicit final class StringFormat[A](private val self: A) extends AnyVal { + /** Returns string formatted according to given `format` string. + * Format strings are as for `String.format` + * (@see java.lang.String.format). + */ + @deprecated("Use `formatString.format(value)` instead of `value.formatted(formatString)`,\nor use the `f\"\"` string interpolator. In Java 15 and later, `formatted` resolves to the new method in String which has reversed parameters.", "2.12.16") + @inline def formatted(fmtstr: String): String = fmtstr format self + } + + /** Injects String concatenation operator `+` to any classes. + * @group implicit-classes-any + */ + @(deprecated @companionMethod)("Implicit injection of + is deprecated. Convert to String to call +", "2.13.0") + @(deprecated @companionClass)("Implicit injection of + is deprecated. Convert to String to call +", "2.13.0") // for Scaladoc + // scala/bug#8229 retaining the pre 2.11 name for source compatibility in shadowing this implicit + implicit final class any2stringadd[A](private val self: A) extends AnyVal { + def +(other: String): String = String.valueOf(self) + other + } + + /** @group char-sequence-wrappers */ + final class SeqCharSequence(sequenceOfChars: scala.collection.IndexedSeq[Char]) extends CharSequence { + def length: Int = sequenceOfChars.length + def charAt(index: Int): Char = sequenceOfChars(index) + def subSequence(start: Int, end: Int): CharSequence = new SeqCharSequence(sequenceOfChars.slice(start, end)) + override def toString = sequenceOfChars.mkString + } + + /** @group char-sequence-wrappers */ + def SeqCharSequence(sequenceOfChars: scala.collection.IndexedSeq[Char]): SeqCharSequence = new SeqCharSequence(sequenceOfChars) + + /** @group char-sequence-wrappers */ + final class ArrayCharSequence(arrayOfChars: Array[Char]) extends CharSequence { + def length: Int = arrayOfChars.length + def charAt(index: Int): Char = arrayOfChars(index) + def subSequence(start: Int, end: Int): CharSequence = new runtime.ArrayCharSequence(arrayOfChars, start, end) + override def toString = arrayOfChars.mkString + } + + /** @group char-sequence-wrappers */ + def ArrayCharSequence(arrayOfChars: Array[Char]): ArrayCharSequence = new ArrayCharSequence(arrayOfChars) + + /** @group conversions-string */ + @inline implicit def augmentString(x: String): StringOps = new StringOps(x) + + // printing ----------------------------------------------------------- + + /** Prints an object to `out` using its `toString` method. + * + * @param x the object to print; may be null. + * @group console-output + */ + def print(x: Any): Unit = Console.print(x) + + /** Prints a newline character on the default output. + * @group console-output + */ + def println(): Unit = Console.println() + + /** Prints out an object to the default output, followed by a newline character. + * + * @param x the object to print. + * @group console-output + */ + def println(x: Any): Unit = Console.println(x) + + /** Prints its arguments as a formatted string to the default output, + * based on a string pattern (in a fashion similar to printf in C). + * + * The interpretation of the formatting patterns is described in + * [[java.util.Formatter]]. + * + * Consider using the [[scala.StringContext.f f interpolator]] as more type safe and idiomatic. + * + * @param text the pattern for formatting the arguments. + * @param xs the arguments used to instantiate the pattern. + * @throws java.lang.IllegalArgumentException if there was a problem with the format string or arguments + * + * @see [[scala.StringContext.f StringContext.f]] + * @group console-output + */ + def printf(text: String, xs: Any*): Unit = Console.print(text.format(xs: _*)) + + // views -------------------------------------------------------------- + + // these two are morally deprecated but the @deprecated annotation has been moved to the extension method themselves, + // in order to provide a more specific deprecation method. + implicit def tuple2ToZippedOps[T1, T2](x: (T1, T2)): runtime.Tuple2Zipped.Ops[T1, T2] = new runtime.Tuple2Zipped.Ops(x) + implicit def tuple3ToZippedOps[T1, T2, T3](x: (T1, T2, T3)): runtime.Tuple3Zipped.Ops[T1, T2, T3] = new runtime.Tuple3Zipped.Ops(x) + + // Not specialized anymore since 2.13 but we still need separate methods + // to avoid https://github.com/scala/bug/issues/10746 + // TODO: should not need @inline. add heuristic to inline factories for value classes. + @inline implicit def genericArrayOps[T](xs: Array[T]): ArrayOps[T] = new ArrayOps(xs) + @inline implicit def booleanArrayOps(xs: Array[Boolean]): ArrayOps[Boolean] = new ArrayOps(xs) + @inline implicit def byteArrayOps(xs: Array[Byte]): ArrayOps[Byte] = new ArrayOps(xs) + @inline implicit def charArrayOps(xs: Array[Char]): ArrayOps[Char] = new ArrayOps(xs) + @inline implicit def doubleArrayOps(xs: Array[Double]): ArrayOps[Double] = new ArrayOps(xs) + @inline implicit def floatArrayOps(xs: Array[Float]): ArrayOps[Float] = new ArrayOps(xs) + @inline implicit def intArrayOps(xs: Array[Int]): ArrayOps[Int] = new ArrayOps(xs) + @inline implicit def longArrayOps(xs: Array[Long]): ArrayOps[Long] = new ArrayOps(xs) + @inline implicit def refArrayOps[T <: AnyRef](xs: Array[T]): ArrayOps[T] = new ArrayOps(xs) + @inline implicit def shortArrayOps(xs: Array[Short]): ArrayOps[Short] = new ArrayOps(xs) + @inline implicit def unitArrayOps(xs: Array[Unit]): ArrayOps[Unit] = new ArrayOps(xs) + + // "Autoboxing" and "Autounboxing" --------------------------------------------------- + + /** @group conversions-anyval-to-java */ + implicit def byte2Byte(x: Byte): java.lang.Byte = x.asInstanceOf[java.lang.Byte] + /** @group conversions-anyval-to-java */ + implicit def short2Short(x: Short): java.lang.Short = x.asInstanceOf[java.lang.Short] + /** @group conversions-anyval-to-java */ + implicit def char2Character(x: Char): java.lang.Character = x.asInstanceOf[java.lang.Character] + /** @group conversions-anyval-to-java */ + implicit def int2Integer(x: Int): java.lang.Integer = x.asInstanceOf[java.lang.Integer] + /** @group conversions-anyval-to-java */ + implicit def long2Long(x: Long): java.lang.Long = x.asInstanceOf[java.lang.Long] + /** @group conversions-anyval-to-java */ + implicit def float2Float(x: Float): java.lang.Float = x.asInstanceOf[java.lang.Float] + /** @group conversions-anyval-to-java */ + implicit def double2Double(x: Double): java.lang.Double = x.asInstanceOf[java.lang.Double] + /** @group conversions-anyval-to-java */ + implicit def boolean2Boolean(x: Boolean): java.lang.Boolean = x.asInstanceOf[java.lang.Boolean] + + /** @group conversions-java-to-anyval */ + implicit def Byte2byte(x: java.lang.Byte): Byte = x.asInstanceOf[Byte] + /** @group conversions-java-to-anyval */ + implicit def Short2short(x: java.lang.Short): Short = x.asInstanceOf[Short] + /** @group conversions-java-to-anyval */ + implicit def Character2char(x: java.lang.Character): Char = x.asInstanceOf[Char] + /** @group conversions-java-to-anyval */ + implicit def Integer2int(x: java.lang.Integer): Int = x.asInstanceOf[Int] + /** @group conversions-java-to-anyval */ + implicit def Long2long(x: java.lang.Long): Long = x.asInstanceOf[Long] + /** @group conversions-java-to-anyval */ + implicit def Float2float(x: java.lang.Float): Float = x.asInstanceOf[Float] + /** @group conversions-java-to-anyval */ + implicit def Double2double(x: java.lang.Double): Double = x.asInstanceOf[Double] + /** @group conversions-java-to-anyval */ + implicit def Boolean2boolean(x: java.lang.Boolean): Boolean = x.asInstanceOf[Boolean] + + /** An implicit of type `A => A` is available for all `A` because it can always + * be implemented using the identity function. This also means that an + * implicit of type `A => B` is always available when `A <: B`, because + * `(A => A) <: (A => B)`. + */ + // $ to avoid accidental shadowing (e.g. scala/bug#7788) + implicit def $conforms[A]: A => A = <:<.refl + + // Extension methods for working with explicit nulls + + /** Strips away the nullability from a value. Note that `.nn` performs a checked cast, + * so if invoked on a `null` value it will throw an `NullPointerException`. + * @example {{{ + * val s1: String | Null = "hello" + * val s2: String = s1.nn + * + * val s3: String | Null = null + * val s4: String = s3.nn // throw NullPointerException + * }}} + */ + extension [T](x: T | Null) inline def nn: x.type & T = + if x.asInstanceOf[Any] == null then scala.runtime.Scala3RunTime.nnFail() + x.asInstanceOf[x.type & T] + + extension (inline x: AnyRef | Null) + /** Enables an expression of type `T|Null`, where `T` is a subtype of `AnyRef`, to be checked for `null` + * using `eq` rather than only `==`. This is needed because `Null` no longer has + * `eq` or `ne` methods, only `==` and `!=` inherited from `Any`. */ + inline infix def eq(inline y: AnyRef | Null): Boolean = + x.asInstanceOf[AnyRef] eq y.asInstanceOf[AnyRef] + /** Enables an expression of type `T|Null`, where `T` is a subtype of `AnyRef`, to be checked for `null` + * using `ne` rather than only `!=`. This is needed because `Null` no longer has + * `eq` or `ne` methods, only `==` and `!=` inherited from `Any`. */ + inline infix def ne(inline y: AnyRef | Null): Boolean = + !(x eq y) + + extension (opt: Option.type) + @experimental + inline def fromNullable[T](t: T | Null): Option[T] = Option(t).asInstanceOf[Option[T]] + + /** A type supporting Self-based type classes. + * + * A is TC + * + * expands to + * + * TC { type Self = A } + * + * which is what is needed for a context bound `[A: TC]`. + */ + @experimental + infix type is[A <: AnyKind, B <: Any{type Self <: AnyKind}] = B { type Self = A } + + extension [T](x: T) + /**Asserts that a term should be exempt from static checks that can be reliably checked at runtime. + * @example {{{ + * val xs: Option[Int] = Option(1) + * xs.runtimeChecked match + * case Some(x) => x // `Some(_)` can be checked at runtime, so no warning + * }}} + * @example {{{ + * val xs: List[Int] = List(1,2,3) + * val y :: ys = xs.runtimeChecked // `_ :: _` can be checked at runtime, so no warning + * }}} + */ + @experimental + inline def runtimeChecked: x.type @RuntimeChecked = x: @RuntimeChecked + +} + +/** The `LowPriorityImplicits` class provides implicit values that +* are valid in all Scala compilation units without explicit qualification, +* but that are partially overridden by higher-priority conversions in object +* `Predef`. +*/ +// scala/bug#7335 Parents of Predef are defined in the same compilation unit to avoid +// cyclic reference errors compiling the standard library *without* a previously +// compiled copy on the classpath. +private[scala] abstract class LowPriorityImplicits extends LowPriorityImplicits2 { + import mutable.ArraySeq + + /** We prefer the java.lang.* boxed types to these wrappers in + * any potential conflicts. Conflicts do exist because the wrappers + * need to implement ScalaNumber in order to have a symmetric equals + * method, but that implies implementing java.lang.Number as well. + * + * Note - these are inlined because they are value classes, but + * the call to xxxWrapper is not eliminated even though it does nothing. + * Even inlined, every call site does a no-op retrieval of Predef's MODULE$ + * because maybe loading Predef has side effects! + */ + @inline implicit def byteWrapper(x: Byte): runtime.RichByte = new runtime.RichByte(x) + @inline implicit def shortWrapper(x: Short): runtime.RichShort = new runtime.RichShort(x) + @inline implicit def intWrapper(x: Int): runtime.RichInt = new runtime.RichInt(x) + @inline implicit def charWrapper(c: Char): runtime.RichChar = new runtime.RichChar(c) + @inline implicit def longWrapper(x: Long): runtime.RichLong = new runtime.RichLong(x) + @inline implicit def floatWrapper(x: Float): runtime.RichFloat = new runtime.RichFloat(x) + @inline implicit def doubleWrapper(x: Double): runtime.RichDouble = new runtime.RichDouble(x) + @inline implicit def booleanWrapper(x: Boolean): runtime.RichBoolean = new runtime.RichBoolean(x) + + /** @group conversions-array-to-wrapped-array */ + implicit def genericWrapArray[T](xs: Array[T]): ArraySeq[T] = + if (xs eq null) null + else ArraySeq.make(xs) + + // Since the JVM thinks arrays are covariant, one 0-length Array[AnyRef] + // is as good as another for all T <: AnyRef. Instead of creating 100,000,000 + // unique ones by way of this implicit, let's share one. + /** @group conversions-array-to-wrapped-array */ + implicit def wrapRefArray[T <: AnyRef](xs: Array[T]): ArraySeq.ofRef[T] = { + if (xs eq null) null + else if (xs.length == 0) ArraySeq.empty[AnyRef].asInstanceOf[ArraySeq.ofRef[T]] + else new ArraySeq.ofRef[T](xs) + } + + /** @group conversions-array-to-wrapped-array */ + implicit def wrapIntArray(xs: Array[Int]): ArraySeq.ofInt = if (xs ne null) new ArraySeq.ofInt(xs) else null + /** @group conversions-array-to-wrapped-array */ + implicit def wrapDoubleArray(xs: Array[Double]): ArraySeq.ofDouble = if (xs ne null) new ArraySeq.ofDouble(xs) else null + /** @group conversions-array-to-wrapped-array */ + implicit def wrapLongArray(xs: Array[Long]): ArraySeq.ofLong = if (xs ne null) new ArraySeq.ofLong(xs) else null + /** @group conversions-array-to-wrapped-array */ + implicit def wrapFloatArray(xs: Array[Float]): ArraySeq.ofFloat = if (xs ne null) new ArraySeq.ofFloat(xs) else null + /** @group conversions-array-to-wrapped-array */ + implicit def wrapCharArray(xs: Array[Char]): ArraySeq.ofChar = if (xs ne null) new ArraySeq.ofChar(xs) else null + /** @group conversions-array-to-wrapped-array */ + implicit def wrapByteArray(xs: Array[Byte]): ArraySeq.ofByte = if (xs ne null) new ArraySeq.ofByte(xs) else null + /** @group conversions-array-to-wrapped-array */ + implicit def wrapShortArray(xs: Array[Short]): ArraySeq.ofShort = if (xs ne null) new ArraySeq.ofShort(xs) else null + /** @group conversions-array-to-wrapped-array */ + implicit def wrapBooleanArray(xs: Array[Boolean]): ArraySeq.ofBoolean = if (xs ne null) new ArraySeq.ofBoolean(xs) else null + /** @group conversions-array-to-wrapped-array */ + implicit def wrapUnitArray(xs: Array[Unit]): ArraySeq.ofUnit = if (xs ne null) new ArraySeq.ofUnit(xs) else null + + /** @group conversions-string */ + implicit def wrapString(s: String): WrappedString = if (s ne null) new WrappedString(s) else null +} + +private[scala] abstract class LowPriorityImplicits2 { + @deprecated("implicit conversions from Array to immutable.IndexedSeq are implemented by copying; use `toIndexedSeq` explicitly if you want to copy, or use the more efficient non-copying ArraySeq.unsafeWrapArray", since="2.13.0") + implicit def copyArrayToImmutableIndexedSeq[T](xs: Array[T]): IndexedSeq[T] = + if (xs eq null) null + else new ArrayOps(xs).toIndexedSeq +} diff --git a/library/src/scala/Product.scala b/library/src/scala/Product.scala new file mode 100644 index 000000000000..3ef4e497932a --- /dev/null +++ b/library/src/scala/Product.scala @@ -0,0 +1,74 @@ +/* + * 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.language.`2.13` + +/** Base trait for all products, which in the standard library include at + * least [[scala.Product1]] through [[scala.Product22]] and therefore also + * their subclasses [[scala.Tuple1]] through [[scala.Tuple22]]. In addition, + * all case classes implement `Product` with synthetically generated methods. + */ +transparent trait Product extends Any with Equals { + /** The size of this product. + * @return for a product `A(x,,1,,, ..., x,,k,,)`, returns `k` + */ + def productArity: Int + + /** The n^th^ element of this product, 0-based. In other words, for a + * product `A(x,,1,,, ..., x,,k,,)`, returns `x,,(n+1),,` where `0 <= n < k`. + * + * @param n the index of the element to return + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= productArity). + * @return the element `n` elements after the first element + */ + def productElement(n: Int): Any + + /** An iterator over all the elements of this product. + * @return in the default implementation, an `Iterator[Any]` + */ + def productIterator: Iterator[Any] = new scala.collection.AbstractIterator[Any] { + private[this] var c: Int = 0 + private[this] val cmax = productArity + def hasNext: Boolean = c < cmax + def next(): Any = { val result = productElement(c); c += 1; result } + } + + /** A string used in the `toString` methods of derived classes. + * Implementations may override this method to prepend a string prefix + * to the result of `toString` methods. + * + * @return in the default implementation, the empty string + */ + def productPrefix: String = "" + + /** The name of the n^th^ element of this product, 0-based. + * In the default implementation, an empty string. + * + * @param n the index of the element name to return + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= productArity). + * @return the name of the specified element + */ + def productElementName(n: Int): String = + if (n >= 0 && n < productArity) "" + else throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max ${productArity-1})") + + /** An iterator over the names of all the elements of this product. + */ + def productElementNames: Iterator[String] = new scala.collection.AbstractIterator[String] { + private[this] var c: Int = 0 + private[this] val cmax = productArity + def hasNext: Boolean = c < cmax + def next(): String = { val result = productElementName(c); c += 1; result } + } +} diff --git a/library/src/scala/Product1.scala b/library/src/scala/Product1.scala new file mode 100644 index 000000000000..d0e6066babfa --- /dev/null +++ b/library/src/scala/Product1.scala @@ -0,0 +1,53 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product1 { + def unapply[T1](x: Product1[T1]): Option[Product1[T1]] = + Some(x) +} + +/** Product1 is a Cartesian product of 1 component. + */ +trait Product1[@specialized(Int, Long, Double) +T1] extends Any with Product { + /** The arity of this product. + * @return 1 + */ + override def productArity: Int = 1 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 1). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 0)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + + +} diff --git a/library/src/scala/Product10.scala b/library/src/scala/Product10.scala new file mode 100644 index 000000000000..ed2b7f748dab --- /dev/null +++ b/library/src/scala/Product10.scala @@ -0,0 +1,98 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product10 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](x: Product10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]): Option[Product10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] = + Some(x) +} + +/** Product10 is a Cartesian product of 10 components. + */ +trait Product10[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10] extends Any with Product { + /** The arity of this product. + * @return 10 + */ + override def productArity: Int = 10 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 10). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case 8 => _9 + case 9 => _10 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 9)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + /** A projection of element 9 of this Product. + * @return A projection of element 9. + */ + def _9: T9 + /** A projection of element 10 of this Product. + * @return A projection of element 10. + */ + def _10: T10 + + +} diff --git a/library/src/scala/Product11.scala b/library/src/scala/Product11.scala new file mode 100644 index 000000000000..ba469e72998f --- /dev/null +++ b/library/src/scala/Product11.scala @@ -0,0 +1,103 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product11 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11](x: Product11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]): Option[Product11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] = + Some(x) +} + +/** Product11 is a Cartesian product of 11 components. + */ +trait Product11[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11] extends Any with Product { + /** The arity of this product. + * @return 11 + */ + override def productArity: Int = 11 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 11). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case 8 => _9 + case 9 => _10 + case 10 => _11 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 10)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + /** A projection of element 9 of this Product. + * @return A projection of element 9. + */ + def _9: T9 + /** A projection of element 10 of this Product. + * @return A projection of element 10. + */ + def _10: T10 + /** A projection of element 11 of this Product. + * @return A projection of element 11. + */ + def _11: T11 + + +} diff --git a/library/src/scala/Product12.scala b/library/src/scala/Product12.scala new file mode 100644 index 000000000000..ef75989d561b --- /dev/null +++ b/library/src/scala/Product12.scala @@ -0,0 +1,108 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product12 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12](x: Product12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]): Option[Product12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] = + Some(x) +} + +/** Product12 is a Cartesian product of 12 components. + */ +trait Product12[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12] extends Any with Product { + /** The arity of this product. + * @return 12 + */ + override def productArity: Int = 12 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 12). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case 8 => _9 + case 9 => _10 + case 10 => _11 + case 11 => _12 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 11)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + /** A projection of element 9 of this Product. + * @return A projection of element 9. + */ + def _9: T9 + /** A projection of element 10 of this Product. + * @return A projection of element 10. + */ + def _10: T10 + /** A projection of element 11 of this Product. + * @return A projection of element 11. + */ + def _11: T11 + /** A projection of element 12 of this Product. + * @return A projection of element 12. + */ + def _12: T12 + + +} diff --git a/library/src/scala/Product13.scala b/library/src/scala/Product13.scala new file mode 100644 index 000000000000..86f0ad54b253 --- /dev/null +++ b/library/src/scala/Product13.scala @@ -0,0 +1,113 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product13 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13](x: Product13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]): Option[Product13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] = + Some(x) +} + +/** Product13 is a Cartesian product of 13 components. + */ +trait Product13[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13] extends Any with Product { + /** The arity of this product. + * @return 13 + */ + override def productArity: Int = 13 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 13). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case 8 => _9 + case 9 => _10 + case 10 => _11 + case 11 => _12 + case 12 => _13 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 12)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + /** A projection of element 9 of this Product. + * @return A projection of element 9. + */ + def _9: T9 + /** A projection of element 10 of this Product. + * @return A projection of element 10. + */ + def _10: T10 + /** A projection of element 11 of this Product. + * @return A projection of element 11. + */ + def _11: T11 + /** A projection of element 12 of this Product. + * @return A projection of element 12. + */ + def _12: T12 + /** A projection of element 13 of this Product. + * @return A projection of element 13. + */ + def _13: T13 + + +} diff --git a/library/src/scala/Product14.scala b/library/src/scala/Product14.scala new file mode 100644 index 000000000000..5f24459a775b --- /dev/null +++ b/library/src/scala/Product14.scala @@ -0,0 +1,118 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product14 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14](x: Product14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]): Option[Product14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] = + Some(x) +} + +/** Product14 is a Cartesian product of 14 components. + */ +trait Product14[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14] extends Any with Product { + /** The arity of this product. + * @return 14 + */ + override def productArity: Int = 14 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 14). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case 8 => _9 + case 9 => _10 + case 10 => _11 + case 11 => _12 + case 12 => _13 + case 13 => _14 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 13)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + /** A projection of element 9 of this Product. + * @return A projection of element 9. + */ + def _9: T9 + /** A projection of element 10 of this Product. + * @return A projection of element 10. + */ + def _10: T10 + /** A projection of element 11 of this Product. + * @return A projection of element 11. + */ + def _11: T11 + /** A projection of element 12 of this Product. + * @return A projection of element 12. + */ + def _12: T12 + /** A projection of element 13 of this Product. + * @return A projection of element 13. + */ + def _13: T13 + /** A projection of element 14 of this Product. + * @return A projection of element 14. + */ + def _14: T14 + + +} diff --git a/library/src/scala/Product15.scala b/library/src/scala/Product15.scala new file mode 100644 index 000000000000..b02d7fdde0aa --- /dev/null +++ b/library/src/scala/Product15.scala @@ -0,0 +1,123 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product15 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15](x: Product15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]): Option[Product15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] = + Some(x) +} + +/** Product15 is a Cartesian product of 15 components. + */ +trait Product15[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15] extends Any with Product { + /** The arity of this product. + * @return 15 + */ + override def productArity: Int = 15 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 15). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case 8 => _9 + case 9 => _10 + case 10 => _11 + case 11 => _12 + case 12 => _13 + case 13 => _14 + case 14 => _15 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 14)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + /** A projection of element 9 of this Product. + * @return A projection of element 9. + */ + def _9: T9 + /** A projection of element 10 of this Product. + * @return A projection of element 10. + */ + def _10: T10 + /** A projection of element 11 of this Product. + * @return A projection of element 11. + */ + def _11: T11 + /** A projection of element 12 of this Product. + * @return A projection of element 12. + */ + def _12: T12 + /** A projection of element 13 of this Product. + * @return A projection of element 13. + */ + def _13: T13 + /** A projection of element 14 of this Product. + * @return A projection of element 14. + */ + def _14: T14 + /** A projection of element 15 of this Product. + * @return A projection of element 15. + */ + def _15: T15 + + +} diff --git a/library/src/scala/Product16.scala b/library/src/scala/Product16.scala new file mode 100644 index 000000000000..b07918060c03 --- /dev/null +++ b/library/src/scala/Product16.scala @@ -0,0 +1,128 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product16 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16](x: Product16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]): Option[Product16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]] = + Some(x) +} + +/** Product16 is a Cartesian product of 16 components. + */ +trait Product16[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16] extends Any with Product { + /** The arity of this product. + * @return 16 + */ + override def productArity: Int = 16 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 16). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case 8 => _9 + case 9 => _10 + case 10 => _11 + case 11 => _12 + case 12 => _13 + case 13 => _14 + case 14 => _15 + case 15 => _16 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 15)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + /** A projection of element 9 of this Product. + * @return A projection of element 9. + */ + def _9: T9 + /** A projection of element 10 of this Product. + * @return A projection of element 10. + */ + def _10: T10 + /** A projection of element 11 of this Product. + * @return A projection of element 11. + */ + def _11: T11 + /** A projection of element 12 of this Product. + * @return A projection of element 12. + */ + def _12: T12 + /** A projection of element 13 of this Product. + * @return A projection of element 13. + */ + def _13: T13 + /** A projection of element 14 of this Product. + * @return A projection of element 14. + */ + def _14: T14 + /** A projection of element 15 of this Product. + * @return A projection of element 15. + */ + def _15: T15 + /** A projection of element 16 of this Product. + * @return A projection of element 16. + */ + def _16: T16 + + +} diff --git a/library/src/scala/Product17.scala b/library/src/scala/Product17.scala new file mode 100644 index 000000000000..969171c3c3fc --- /dev/null +++ b/library/src/scala/Product17.scala @@ -0,0 +1,133 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product17 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17](x: Product17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]): Option[Product17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]] = + Some(x) +} + +/** Product17 is a Cartesian product of 17 components. + */ +trait Product17[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16, +T17] extends Any with Product { + /** The arity of this product. + * @return 17 + */ + override def productArity: Int = 17 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 17). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case 8 => _9 + case 9 => _10 + case 10 => _11 + case 11 => _12 + case 12 => _13 + case 13 => _14 + case 14 => _15 + case 15 => _16 + case 16 => _17 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 16)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + /** A projection of element 9 of this Product. + * @return A projection of element 9. + */ + def _9: T9 + /** A projection of element 10 of this Product. + * @return A projection of element 10. + */ + def _10: T10 + /** A projection of element 11 of this Product. + * @return A projection of element 11. + */ + def _11: T11 + /** A projection of element 12 of this Product. + * @return A projection of element 12. + */ + def _12: T12 + /** A projection of element 13 of this Product. + * @return A projection of element 13. + */ + def _13: T13 + /** A projection of element 14 of this Product. + * @return A projection of element 14. + */ + def _14: T14 + /** A projection of element 15 of this Product. + * @return A projection of element 15. + */ + def _15: T15 + /** A projection of element 16 of this Product. + * @return A projection of element 16. + */ + def _16: T16 + /** A projection of element 17 of this Product. + * @return A projection of element 17. + */ + def _17: T17 + + +} diff --git a/library/src/scala/Product18.scala b/library/src/scala/Product18.scala new file mode 100644 index 000000000000..cc6891dae9ee --- /dev/null +++ b/library/src/scala/Product18.scala @@ -0,0 +1,138 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product18 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18](x: Product18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]): Option[Product18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]] = + Some(x) +} + +/** Product18 is a Cartesian product of 18 components. + */ +trait Product18[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16, +T17, +T18] extends Any with Product { + /** The arity of this product. + * @return 18 + */ + override def productArity: Int = 18 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 18). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case 8 => _9 + case 9 => _10 + case 10 => _11 + case 11 => _12 + case 12 => _13 + case 13 => _14 + case 14 => _15 + case 15 => _16 + case 16 => _17 + case 17 => _18 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 17)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + /** A projection of element 9 of this Product. + * @return A projection of element 9. + */ + def _9: T9 + /** A projection of element 10 of this Product. + * @return A projection of element 10. + */ + def _10: T10 + /** A projection of element 11 of this Product. + * @return A projection of element 11. + */ + def _11: T11 + /** A projection of element 12 of this Product. + * @return A projection of element 12. + */ + def _12: T12 + /** A projection of element 13 of this Product. + * @return A projection of element 13. + */ + def _13: T13 + /** A projection of element 14 of this Product. + * @return A projection of element 14. + */ + def _14: T14 + /** A projection of element 15 of this Product. + * @return A projection of element 15. + */ + def _15: T15 + /** A projection of element 16 of this Product. + * @return A projection of element 16. + */ + def _16: T16 + /** A projection of element 17 of this Product. + * @return A projection of element 17. + */ + def _17: T17 + /** A projection of element 18 of this Product. + * @return A projection of element 18. + */ + def _18: T18 + + +} diff --git a/library/src/scala/Product19.scala b/library/src/scala/Product19.scala new file mode 100644 index 000000000000..44bdd2dd91d2 --- /dev/null +++ b/library/src/scala/Product19.scala @@ -0,0 +1,143 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product19 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19](x: Product19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]): Option[Product19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]] = + Some(x) +} + +/** Product19 is a Cartesian product of 19 components. + */ +trait Product19[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16, +T17, +T18, +T19] extends Any with Product { + /** The arity of this product. + * @return 19 + */ + override def productArity: Int = 19 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 19). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case 8 => _9 + case 9 => _10 + case 10 => _11 + case 11 => _12 + case 12 => _13 + case 13 => _14 + case 14 => _15 + case 15 => _16 + case 16 => _17 + case 17 => _18 + case 18 => _19 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 18)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + /** A projection of element 9 of this Product. + * @return A projection of element 9. + */ + def _9: T9 + /** A projection of element 10 of this Product. + * @return A projection of element 10. + */ + def _10: T10 + /** A projection of element 11 of this Product. + * @return A projection of element 11. + */ + def _11: T11 + /** A projection of element 12 of this Product. + * @return A projection of element 12. + */ + def _12: T12 + /** A projection of element 13 of this Product. + * @return A projection of element 13. + */ + def _13: T13 + /** A projection of element 14 of this Product. + * @return A projection of element 14. + */ + def _14: T14 + /** A projection of element 15 of this Product. + * @return A projection of element 15. + */ + def _15: T15 + /** A projection of element 16 of this Product. + * @return A projection of element 16. + */ + def _16: T16 + /** A projection of element 17 of this Product. + * @return A projection of element 17. + */ + def _17: T17 + /** A projection of element 18 of this Product. + * @return A projection of element 18. + */ + def _18: T18 + /** A projection of element 19 of this Product. + * @return A projection of element 19. + */ + def _19: T19 + + +} diff --git a/library/src/scala/Product2.scala b/library/src/scala/Product2.scala new file mode 100644 index 000000000000..e7ab6be6e677 --- /dev/null +++ b/library/src/scala/Product2.scala @@ -0,0 +1,58 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product2 { + def unapply[T1, T2](x: Product2[T1, T2]): Option[Product2[T1, T2]] = + Some(x) +} + +/** Product2 is a Cartesian product of 2 components. + */ +trait Product2[@specialized(Int, Long, Double) +T1, @specialized(Int, Long, Double) +T2] extends Any with Product { + /** The arity of this product. + * @return 2 + */ + override def productArity: Int = 2 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 2). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 1)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + + +} diff --git a/library/src/scala/Product20.scala b/library/src/scala/Product20.scala new file mode 100644 index 000000000000..148d936bde7e --- /dev/null +++ b/library/src/scala/Product20.scala @@ -0,0 +1,148 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product20 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20](x: Product20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20]): Option[Product20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20]] = + Some(x) +} + +/** Product20 is a Cartesian product of 20 components. + */ +trait Product20[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16, +T17, +T18, +T19, +T20] extends Any with Product { + /** The arity of this product. + * @return 20 + */ + override def productArity: Int = 20 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 20). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case 8 => _9 + case 9 => _10 + case 10 => _11 + case 11 => _12 + case 12 => _13 + case 13 => _14 + case 14 => _15 + case 15 => _16 + case 16 => _17 + case 17 => _18 + case 18 => _19 + case 19 => _20 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 19)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + /** A projection of element 9 of this Product. + * @return A projection of element 9. + */ + def _9: T9 + /** A projection of element 10 of this Product. + * @return A projection of element 10. + */ + def _10: T10 + /** A projection of element 11 of this Product. + * @return A projection of element 11. + */ + def _11: T11 + /** A projection of element 12 of this Product. + * @return A projection of element 12. + */ + def _12: T12 + /** A projection of element 13 of this Product. + * @return A projection of element 13. + */ + def _13: T13 + /** A projection of element 14 of this Product. + * @return A projection of element 14. + */ + def _14: T14 + /** A projection of element 15 of this Product. + * @return A projection of element 15. + */ + def _15: T15 + /** A projection of element 16 of this Product. + * @return A projection of element 16. + */ + def _16: T16 + /** A projection of element 17 of this Product. + * @return A projection of element 17. + */ + def _17: T17 + /** A projection of element 18 of this Product. + * @return A projection of element 18. + */ + def _18: T18 + /** A projection of element 19 of this Product. + * @return A projection of element 19. + */ + def _19: T19 + /** A projection of element 20 of this Product. + * @return A projection of element 20. + */ + def _20: T20 + + +} diff --git a/library/src/scala/Product21.scala b/library/src/scala/Product21.scala new file mode 100644 index 000000000000..3000c47f6f1e --- /dev/null +++ b/library/src/scala/Product21.scala @@ -0,0 +1,153 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product21 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21](x: Product21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]): Option[Product21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]] = + Some(x) +} + +/** Product21 is a Cartesian product of 21 components. + */ +trait Product21[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16, +T17, +T18, +T19, +T20, +T21] extends Any with Product { + /** The arity of this product. + * @return 21 + */ + override def productArity: Int = 21 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 21). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case 8 => _9 + case 9 => _10 + case 10 => _11 + case 11 => _12 + case 12 => _13 + case 13 => _14 + case 14 => _15 + case 15 => _16 + case 16 => _17 + case 17 => _18 + case 18 => _19 + case 19 => _20 + case 20 => _21 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 20)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + /** A projection of element 9 of this Product. + * @return A projection of element 9. + */ + def _9: T9 + /** A projection of element 10 of this Product. + * @return A projection of element 10. + */ + def _10: T10 + /** A projection of element 11 of this Product. + * @return A projection of element 11. + */ + def _11: T11 + /** A projection of element 12 of this Product. + * @return A projection of element 12. + */ + def _12: T12 + /** A projection of element 13 of this Product. + * @return A projection of element 13. + */ + def _13: T13 + /** A projection of element 14 of this Product. + * @return A projection of element 14. + */ + def _14: T14 + /** A projection of element 15 of this Product. + * @return A projection of element 15. + */ + def _15: T15 + /** A projection of element 16 of this Product. + * @return A projection of element 16. + */ + def _16: T16 + /** A projection of element 17 of this Product. + * @return A projection of element 17. + */ + def _17: T17 + /** A projection of element 18 of this Product. + * @return A projection of element 18. + */ + def _18: T18 + /** A projection of element 19 of this Product. + * @return A projection of element 19. + */ + def _19: T19 + /** A projection of element 20 of this Product. + * @return A projection of element 20. + */ + def _20: T20 + /** A projection of element 21 of this Product. + * @return A projection of element 21. + */ + def _21: T21 + + +} diff --git a/library/src/scala/Product22.scala b/library/src/scala/Product22.scala new file mode 100644 index 000000000000..a89de1d525fc --- /dev/null +++ b/library/src/scala/Product22.scala @@ -0,0 +1,158 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product22 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22](x: Product22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22]): Option[Product22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22]] = + Some(x) +} + +/** Product22 is a Cartesian product of 22 components. + */ +trait Product22[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16, +T17, +T18, +T19, +T20, +T21, +T22] extends Any with Product { + /** The arity of this product. + * @return 22 + */ + override def productArity: Int = 22 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 22). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case 8 => _9 + case 9 => _10 + case 10 => _11 + case 11 => _12 + case 12 => _13 + case 13 => _14 + case 14 => _15 + case 15 => _16 + case 16 => _17 + case 17 => _18 + case 18 => _19 + case 19 => _20 + case 20 => _21 + case 21 => _22 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 21)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + /** A projection of element 9 of this Product. + * @return A projection of element 9. + */ + def _9: T9 + /** A projection of element 10 of this Product. + * @return A projection of element 10. + */ + def _10: T10 + /** A projection of element 11 of this Product. + * @return A projection of element 11. + */ + def _11: T11 + /** A projection of element 12 of this Product. + * @return A projection of element 12. + */ + def _12: T12 + /** A projection of element 13 of this Product. + * @return A projection of element 13. + */ + def _13: T13 + /** A projection of element 14 of this Product. + * @return A projection of element 14. + */ + def _14: T14 + /** A projection of element 15 of this Product. + * @return A projection of element 15. + */ + def _15: T15 + /** A projection of element 16 of this Product. + * @return A projection of element 16. + */ + def _16: T16 + /** A projection of element 17 of this Product. + * @return A projection of element 17. + */ + def _17: T17 + /** A projection of element 18 of this Product. + * @return A projection of element 18. + */ + def _18: T18 + /** A projection of element 19 of this Product. + * @return A projection of element 19. + */ + def _19: T19 + /** A projection of element 20 of this Product. + * @return A projection of element 20. + */ + def _20: T20 + /** A projection of element 21 of this Product. + * @return A projection of element 21. + */ + def _21: T21 + /** A projection of element 22 of this Product. + * @return A projection of element 22. + */ + def _22: T22 + + +} diff --git a/library/src/scala/Product3.scala b/library/src/scala/Product3.scala new file mode 100644 index 000000000000..96ef7f5cdab7 --- /dev/null +++ b/library/src/scala/Product3.scala @@ -0,0 +1,63 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product3 { + def unapply[T1, T2, T3](x: Product3[T1, T2, T3]): Option[Product3[T1, T2, T3]] = + Some(x) +} + +/** Product3 is a Cartesian product of 3 components. + */ +trait Product3[+T1, +T2, +T3] extends Any with Product { + /** The arity of this product. + * @return 3 + */ + override def productArity: Int = 3 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 3). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 2)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + + +} diff --git a/library/src/scala/Product4.scala b/library/src/scala/Product4.scala new file mode 100644 index 000000000000..7afb3e102a6c --- /dev/null +++ b/library/src/scala/Product4.scala @@ -0,0 +1,68 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product4 { + def unapply[T1, T2, T3, T4](x: Product4[T1, T2, T3, T4]): Option[Product4[T1, T2, T3, T4]] = + Some(x) +} + +/** Product4 is a Cartesian product of 4 components. + */ +trait Product4[+T1, +T2, +T3, +T4] extends Any with Product { + /** The arity of this product. + * @return 4 + */ + override def productArity: Int = 4 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 4). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 3)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + + +} diff --git a/library/src/scala/Product5.scala b/library/src/scala/Product5.scala new file mode 100644 index 000000000000..1758cd4e5e29 --- /dev/null +++ b/library/src/scala/Product5.scala @@ -0,0 +1,73 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product5 { + def unapply[T1, T2, T3, T4, T5](x: Product5[T1, T2, T3, T4, T5]): Option[Product5[T1, T2, T3, T4, T5]] = + Some(x) +} + +/** Product5 is a Cartesian product of 5 components. + */ +trait Product5[+T1, +T2, +T3, +T4, +T5] extends Any with Product { + /** The arity of this product. + * @return 5 + */ + override def productArity: Int = 5 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 5). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 4)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + + +} diff --git a/library/src/scala/Product6.scala b/library/src/scala/Product6.scala new file mode 100644 index 000000000000..d4d22928c83c --- /dev/null +++ b/library/src/scala/Product6.scala @@ -0,0 +1,78 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product6 { + def unapply[T1, T2, T3, T4, T5, T6](x: Product6[T1, T2, T3, T4, T5, T6]): Option[Product6[T1, T2, T3, T4, T5, T6]] = + Some(x) +} + +/** Product6 is a Cartesian product of 6 components. + */ +trait Product6[+T1, +T2, +T3, +T4, +T5, +T6] extends Any with Product { + /** The arity of this product. + * @return 6 + */ + override def productArity: Int = 6 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 6). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 5)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + + +} diff --git a/library/src/scala/Product7.scala b/library/src/scala/Product7.scala new file mode 100644 index 000000000000..a88e3b5a1a09 --- /dev/null +++ b/library/src/scala/Product7.scala @@ -0,0 +1,83 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product7 { + def unapply[T1, T2, T3, T4, T5, T6, T7](x: Product7[T1, T2, T3, T4, T5, T6, T7]): Option[Product7[T1, T2, T3, T4, T5, T6, T7]] = + Some(x) +} + +/** Product7 is a Cartesian product of 7 components. + */ +trait Product7[+T1, +T2, +T3, +T4, +T5, +T6, +T7] extends Any with Product { + /** The arity of this product. + * @return 7 + */ + override def productArity: Int = 7 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 7). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 6)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + + +} diff --git a/library/src/scala/Product8.scala b/library/src/scala/Product8.scala new file mode 100644 index 000000000000..4763ae36b8b8 --- /dev/null +++ b/library/src/scala/Product8.scala @@ -0,0 +1,88 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product8 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8](x: Product8[T1, T2, T3, T4, T5, T6, T7, T8]): Option[Product8[T1, T2, T3, T4, T5, T6, T7, T8]] = + Some(x) +} + +/** Product8 is a Cartesian product of 8 components. + */ +trait Product8[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8] extends Any with Product { + /** The arity of this product. + * @return 8 + */ + override def productArity: Int = 8 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 8). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 7)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + + +} diff --git a/library/src/scala/Product9.scala b/library/src/scala/Product9.scala new file mode 100644 index 000000000000..06a24b57cf16 --- /dev/null +++ b/library/src/scala/Product9.scala @@ -0,0 +1,93 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +object Product9 { + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9](x: Product9[T1, T2, T3, T4, T5, T6, T7, T8, T9]): Option[Product9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] = + Some(x) +} + +/** Product9 is a Cartesian product of 9 components. + */ +trait Product9[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9] extends Any with Product { + /** The arity of this product. + * @return 9 + */ + override def productArity: Int = 9 + + + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= 9). + */ + + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match { + case 0 => _1 + case 1 => _2 + case 2 => _3 + case 3 => _4 + case 4 => _5 + case 5 => _6 + case 6 => _7 + case 7 => _8 + case 8 => _9 + case _ => throw new IndexOutOfBoundsException(s"$n is out of bounds (min 0, max 8)") + } + + /** A projection of element 1 of this Product. + * @return A projection of element 1. + */ + def _1: T1 + /** A projection of element 2 of this Product. + * @return A projection of element 2. + */ + def _2: T2 + /** A projection of element 3 of this Product. + * @return A projection of element 3. + */ + def _3: T3 + /** A projection of element 4 of this Product. + * @return A projection of element 4. + */ + def _4: T4 + /** A projection of element 5 of this Product. + * @return A projection of element 5. + */ + def _5: T5 + /** A projection of element 6 of this Product. + * @return A projection of element 6. + */ + def _6: T6 + /** A projection of element 7 of this Product. + * @return A projection of element 7. + */ + def _7: T7 + /** A projection of element 8 of this Product. + * @return A projection of element 8. + */ + def _8: T8 + /** A projection of element 9 of this Product. + * @return A projection of element 9. + */ + def _9: T9 + + +} diff --git a/library/src/scala/Proxy.scala b/library/src/scala/Proxy.scala new file mode 100644 index 000000000000..d92f10b50c25 --- /dev/null +++ b/library/src/scala/Proxy.scala @@ -0,0 +1,50 @@ +/* + * 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.language.`2.13` + +/** This class implements a simple proxy that forwards all calls to + * the public, non-final methods defined in class `Any` to another + * object self. Those methods are: + * {{{ + * def hashCode(): Int + * def equals(other: Any): Boolean + * def toString(): String + * }}} + * '''Note:''' forwarding methods in this way will most likely create + * an asymmetric equals method, which is not generally recommended. + */ +@deprecated("Explicitly override hashCode, equals and toString instead.", "2.13.0") +trait Proxy extends Any { + def self: Any + + override def hashCode: Int = self.hashCode + override def equals(that: Any): Boolean = that match { + case null => false + case _ => + val x = that.asInstanceOf[AnyRef] + (x eq this.asInstanceOf[AnyRef]) || (x eq self.asInstanceOf[AnyRef]) || (x equals self) + } + override def toString = "" + self +} + +@deprecated("All members of this object are deprecated.", "2.13.0") +object Proxy { + /** A proxy which exposes the type it is proxying for via a type parameter. + */ + @deprecated("Explicitly override hashCode, equals and toString instead.", "2.13.0") + trait Typed[T] extends Any with Proxy { + def self: T + } +} diff --git a/library/src/scala/SerialVersionUID.scala b/library/src/scala/SerialVersionUID.scala new file mode 100644 index 000000000000..62becf8daf79 --- /dev/null +++ b/library/src/scala/SerialVersionUID.scala @@ -0,0 +1,29 @@ +/* + * 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.language.`2.13` + +/** + * Annotation for specifying the `serialVersionUID` field of a (serializable) class. + * + * On the JVM, a class with this annotation will receive a `private`, `static`, + * and `final` field called `serialVersionUID` with the provided `value`, + * which the JVM's serialization mechanism uses to determine serialization + * compatibility between different versions of a class. + * + * @see [[java.io.Serializable]] + * @see [[Serializable]] + */ +@deprecatedInheritance("Scheduled for being final in the future", "2.13.0") +class SerialVersionUID(value: Long) extends scala.annotation.ConstantAnnotation diff --git a/library/src/scala/Short.scala b/library/src/scala/Short.scala new file mode 100644 index 000000000000..fc58ad8640e7 --- /dev/null +++ b/library/src/scala/Short.scala @@ -0,0 +1,488 @@ +/* + * 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. + */ + +// DO NOT EDIT, CHANGES WILL BE LOST +// This auto-generated code can be modified in "project/GenerateAnyVals.scala". +// Afterwards, running "sbt generateSources" regenerates this source file. + +package scala + +import scala.language.`2.13` + +/** `Short`, a 16-bit signed integer (equivalent to Java's `short` primitive type) is a + * subtype of [[scala.AnyVal]]. Instances of `Short` are not + * represented by an object in the underlying runtime system. + * + * There is an implicit conversion from [[scala.Short]] => [[scala.runtime.RichShort]] + * which provides useful non-primitive operations. + */ +final abstract class Short private extends AnyVal { + def toByte: Byte + def toShort: Short + def toChar: Char + def toInt: Int + def toLong: Long + def toFloat: Float + def toDouble: Double + + /** + * Returns the bitwise negation of this value. + * @example {{{ + * ~5 == -6 + * // in binary: ~00000101 == + * // 11111010 + * }}} + */ + def unary_~ : Int + /** Returns this value, unmodified. */ + def unary_+ : Int + /** Returns the negation of this value. */ + def unary_- : Int + + @deprecated("Adding a number and a String is deprecated. Use the string interpolation `s\"$num$str\"`", "2.13.0") + def +(x: String): String + + /** + * Returns this value bit-shifted left by the specified number of bits, + * filling in the new right bits with zeroes. + * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}} + */ + def <<(x: Int): Int + /** + * Returns this value bit-shifted left by the specified number of bits, + * filling in the new right bits with zeroes. + * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}} + */ + @deprecated("shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.", "2.12.7") + def <<(x: Long): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling the new left bits with zeroes. + * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}} + * @example {{{ + * -21 >>> 3 == 536870909 + * // in binary: 11111111 11111111 11111111 11101011 >>> 3 == + * // 00011111 11111111 11111111 11111101 + * }}} + */ + def >>>(x: Int): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling the new left bits with zeroes. + * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}} + * @example {{{ + * -21 >>> 3 == 536870909 + * // in binary: 11111111 11111111 11111111 11101011 >>> 3 == + * // 00011111 11111111 11111111 11111101 + * }}} + */ + @deprecated("shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.", "2.12.7") + def >>>(x: Long): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling in the left bits with the same value as the left-most bit of this. + * The effect of this is to retain the sign of the value. + * @example {{{ + * -21 >> 3 == -3 + * // in binary: 11111111 11111111 11111111 11101011 >> 3 == + * // 11111111 11111111 11111111 11111101 + * }}} + */ + def >>(x: Int): Int + /** + * Returns this value bit-shifted right by the specified number of bits, + * filling in the left bits with the same value as the left-most bit of this. + * The effect of this is to retain the sign of the value. + * @example {{{ + * -21 >> 3 == -3 + * // in binary: 11111111 11111111 11111111 11101011 >> 3 == + * // 11111111 11111111 11111111 11111101 + * }}} + */ + @deprecated("shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.", "2.12.7") + def >>(x: Long): Int + + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Byte): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Short): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Char): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Int): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Long): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Float): Boolean + /** Returns `true` if this value is equal to x, `false` otherwise. */ + def ==(x: Double): Boolean + + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Byte): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Short): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Char): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Int): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Long): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Float): Boolean + /** Returns `true` if this value is not equal to x, `false` otherwise. */ + def !=(x: Double): Boolean + + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Byte): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Short): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Char): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Int): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Long): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Float): Boolean + /** Returns `true` if this value is less than x, `false` otherwise. */ + def <(x: Double): Boolean + + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Byte): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Short): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Char): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Int): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Long): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Float): Boolean + /** Returns `true` if this value is less than or equal to x, `false` otherwise. */ + def <=(x: Double): Boolean + + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Byte): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Short): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Char): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Int): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Long): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Float): Boolean + /** Returns `true` if this value is greater than x, `false` otherwise. */ + def >(x: Double): Boolean + + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Byte): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Short): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Char): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Int): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Long): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Float): Boolean + /** Returns `true` if this value is greater than or equal to x, `false` otherwise. */ + def >=(x: Double): Boolean + + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Byte): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Short): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Char): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Int): Int + /** + * Returns the bitwise OR of this value and `x`. + * @example {{{ + * (0xf0 | 0xaa) == 0xfa + * // in binary: 11110000 + * // | 10101010 + * // -------- + * // 11111010 + * }}} + */ + def |(x: Long): Long + + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Byte): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Short): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Char): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Int): Int + /** + * Returns the bitwise AND of this value and `x`. + * @example {{{ + * (0xf0 & 0xaa) == 0xa0 + * // in binary: 11110000 + * // & 10101010 + * // -------- + * // 10100000 + * }}} + */ + def &(x: Long): Long + + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Byte): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Short): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Char): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Int): Int + /** + * Returns the bitwise XOR of this value and `x`. + * @example {{{ + * (0xf0 ^ 0xaa) == 0x5a + * // in binary: 11110000 + * // ^ 10101010 + * // -------- + * // 01011010 + * }}} + */ + def ^(x: Long): Long + + /** Returns the sum of this value and `x`. */ + def +(x: Byte): Int + /** Returns the sum of this value and `x`. */ + def +(x: Short): Int + /** Returns the sum of this value and `x`. */ + def +(x: Char): Int + /** Returns the sum of this value and `x`. */ + def +(x: Int): Int + /** Returns the sum of this value and `x`. */ + def +(x: Long): Long + /** Returns the sum of this value and `x`. */ + def +(x: Float): Float + /** Returns the sum of this value and `x`. */ + def +(x: Double): Double + + /** Returns the difference of this value and `x`. */ + def -(x: Byte): Int + /** Returns the difference of this value and `x`. */ + def -(x: Short): Int + /** Returns the difference of this value and `x`. */ + def -(x: Char): Int + /** Returns the difference of this value and `x`. */ + def -(x: Int): Int + /** Returns the difference of this value and `x`. */ + def -(x: Long): Long + /** Returns the difference of this value and `x`. */ + def -(x: Float): Float + /** Returns the difference of this value and `x`. */ + def -(x: Double): Double + + /** Returns the product of this value and `x`. */ + def *(x: Byte): Int + /** Returns the product of this value and `x`. */ + def *(x: Short): Int + /** Returns the product of this value and `x`. */ + def *(x: Char): Int + /** Returns the product of this value and `x`. */ + def *(x: Int): Int + /** Returns the product of this value and `x`. */ + def *(x: Long): Long + /** Returns the product of this value and `x`. */ + def *(x: Float): Float + /** Returns the product of this value and `x`. */ + def *(x: Double): Double + + /** Returns the quotient of this value and `x`. */ + def /(x: Byte): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Short): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Char): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Int): Int + /** Returns the quotient of this value and `x`. */ + def /(x: Long): Long + /** Returns the quotient of this value and `x`. */ + def /(x: Float): Float + /** Returns the quotient of this value and `x`. */ + def /(x: Double): Double + + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Byte): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Short): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Char): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Int): Int + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Long): Long + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Float): Float + /** Returns the remainder of the division of this value by `x`. */ + def %(x: Double): Double + + // Provide a more specific return type for Scaladoc + override def getClass(): Class[Short] = ??? +} + +object Short extends AnyValCompanion { + /** The smallest value representable as a Short. */ + final val MinValue = java.lang.Short.MIN_VALUE + + /** The largest value representable as a Short. */ + final val MaxValue = java.lang.Short.MAX_VALUE + + /** Transform a value type into a boxed reference type. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToShort`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the Short to be boxed + * @return a java.lang.Short offering `x` as its underlying value. + */ + def box(x: Short): java.lang.Short = ??? + + /** Transform a boxed type into a value type. Note that this + * method is not typesafe: it accepts any Object, but will throw + * an exception if the argument is not a java.lang.Short. + * + * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxToShort`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + * + * @param x the java.lang.Short to be unboxed. + * @throws ClassCastException if the argument is not a java.lang.Short + * @return the Short resulting from calling shortValue() on `x` + */ + def unbox(x: java.lang.Object): Short = ??? + + /** The String representation of the scala.Short companion object. */ + override def toString = "object scala.Short" + /** Language mandated coercions from Short to "wider" types. */ + import scala.language.implicitConversions + implicit def short2int(x: Short): Int = x.toInt + implicit def short2long(x: Short): Long = x.toLong + implicit def short2float(x: Short): Float = x.toFloat + implicit def short2double(x: Short): Double = x.toDouble +} + diff --git a/library/src/scala/Specializable.scala b/library/src/scala/Specializable.scala new file mode 100644 index 000000000000..51822f9f6446 --- /dev/null +++ b/library/src/scala/Specializable.scala @@ -0,0 +1,40 @@ +/* + * 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.language.`2.13` + +/** A common supertype for companions of specializable types. + * Should not be extended in user code. + */ +trait Specializable + +object Specializable { + // No type parameter in @specialized annotation. + trait SpecializedGroup + + // Smuggle a list of types by way of a tuple upon which Group is parameterized. + class Group[T >: Null](value: T) extends SpecializedGroup + + final val Primitives: Group[(Byte, Short, Int, Long, Char, Float, Double, Boolean, Unit)] = null + final val Everything: Group[(Byte, Short, Int, Long, Char, Float, Double, Boolean, Unit, AnyRef)] = null + final val Bits32AndUp: Group[(Int, Long, Float, Double)] = null + final val Integral: Group[(Byte, Short, Int, Long, Char)] = null + final val AllNumeric: Group[(Byte, Short, Int, Long, Char, Float, Double)] = null + final val BestOfBreed: Group[(Int, Double, Boolean, Unit, AnyRef)] = null + final val Unit: Group[Tuple1[Unit]] = null + + final val Arg: Group[(Int, Long, Float, Double)] = null + final val Args: Group[(Int, Long, Double)] = null + final val Return: Group[(Int, Long, Float, Double, Boolean, Unit)] = null +} diff --git a/library/src/scala/StringContext.scala b/library/src/scala/StringContext.scala new file mode 100644 index 000000000000..ab07a50d6e01 --- /dev/null +++ b/library/src/scala/StringContext.scala @@ -0,0 +1,477 @@ +/* + * 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.language.`2.13` +import java.lang.{ StringBuilder => JLSBuilder } +import scala.annotation.tailrec +import scala.collection.mutable.ArrayBuilder + +/** This class provides the basic mechanism to do String Interpolation. + * String Interpolation allows users + * to embed variable references directly in *processed* string literals. + * Here's an example: + * {{{ + * val name = "James" + * println(s"Hello, \$name") // Hello, James + * }}} + * + * Any processed string literal is rewritten as an instantiation and + * method call against this class. For example: + * {{{ + * s"Hello, \$name" + * }}} + * + * is rewritten to be: + * + * {{{ + * StringContext("Hello, ", "").s(name) + * }}} + * + * By default, this class provides the `raw`, `s` and `f` methods as + * available interpolators. + * + * To provide your own string interpolator, create an implicit class + * which adds a method to `StringContext`. Here's an example: + * {{{ + * implicit class JsonHelper(private val sc: StringContext) extends AnyVal { + * def json(args: Any*): JSONObject = ... + * } + * val x: JSONObject = json"{ a: \$a }" + * }}} + * + * Here the `JsonHelper` extension class implicitly adds the `json` method to + * `StringContext` which can be used for `json` string literals. + * + * @param parts The parts that make up the interpolated string, + * without the expressions that get inserted by interpolation. + */ +case class StringContext(parts: String*) { + + import StringContext.{checkLengths => scCheckLengths, glob, processEscapes, standardInterpolator => scStandardInterpolator} + + @deprecated("use same-named method on StringContext companion object", "2.13.0") + def checkLengths(args: scala.collection.Seq[Any]): Unit = scCheckLengths(args, parts) + + /** The simple string interpolator. + * + * It inserts its arguments between corresponding parts of the string context. + * It also treats standard escape sequences as defined in the Scala specification. + * Here's an example of usage: + * {{{ + * val name = "James" + * println(s"Hello, \$name") // Hello, James + * }}} + * In this example, the expression \$name is replaced with the `toString` of the + * variable `name`. + * The `s` interpolator can take the `toString` of any arbitrary expression within + * a `\${}` block, for example: + * {{{ + * println(s"1 + 1 = \${1 + 1}") + * }}} + * will print the string `1 + 1 = 2`. + * + * @param `args` The arguments to be inserted into the resulting string. + * @throws IllegalArgumentException + * if the number of `parts` in the enclosing `StringContext` does not exceed + * the number of arguments `arg` by exactly 1. + * @throws StringContext.InvalidEscapeException + * if a `parts` string contains a backslash (`\`) character + * that does not start a valid escape sequence. + * @note The Scala compiler may replace a call to this method with an equivalent, but more efficient, + * use of a StringBuilder. + */ + def s(args: Any*): String = macro ??? // fasttracked to scala.tools.reflect.FastStringInterpolator::interpolateS + object s { + /** The simple string matcher. + * + * Attempts to match the input string to the given interpolated patterns via + * a naive globbing, that is the reverse of the simple interpolator. + * + * Here is an example usage: + * + * {{{ + * val s"Hello, \$name" = "Hello, James" + * println(name) // "James" + * }}} + * + * In this example, the string "James" ends up matching the location where the pattern + * `\$name` is positioned, and thus ends up bound to that variable. + * + * Multiple matches are supported: + * + * {{{ + * val s"\$greeting, \$name" = "Hello, James" + * println(greeting) // "Hello" + * println(name) // "James" + * }}} + * + * And the `s` matcher can match an arbitrary pattern within the `\${}` block, for example: + * + * {{{ + * val TimeSplitter = "([0-9]+)[.:]([0-9]+)".r + * val s"The time is \${TimeSplitter(hours, mins)}" = "The time is 10.50" + * println(hours) // 10 + * println(mins) // 50 + * }}} + * + * Here, we use the `TimeSplitter` regex within the `s` matcher, further splitting the + * matched string "10.50" into its constituent parts + */ + def unapplySeq(s: String): Option[Seq[String]] = glob(parts.map(processEscapes), s) + } + /** The raw string interpolator. + * + * It inserts its arguments between corresponding parts of the string context. + * As opposed to the simple string interpolator `s`, this one does not treat + * standard escape sequences as defined in the Scala specification. + * + * For example, the raw processed string `raw"a\nb"` is equal to the scala string `"a\\nb"`. + * + * ''Note:'' Even when using the raw interpolator, Scala will process Unicode escapes. + * Unicode processing in the raw interpolator is deprecated as of scala 2.13.2 and + * will be removed in the future + * For example: + * {{{ + * scala> raw"\u005cu0023" + * res0: String = # + * }}} + * + * @param `args` The arguments to be inserted into the resulting string. + * @throws IllegalArgumentException + * if the number of `parts` in the enclosing `StringContext` does not exceed + * the number of arguments `arg` by exactly 1. + * @note The Scala compiler may replace a call to this method with an equivalent, but more efficient, + * use of a StringBuilder. + */ + def raw(args: Any*): String = macro ??? // fasttracked to scala.tools.reflect.FastStringInterpolator::interpolateRaw + + @deprecated("Use the static method StringContext.standardInterpolator instead of the instance method", "2.13.0") + def standardInterpolator(process: String => String, args: Seq[Any]): String = scStandardInterpolator(process, args, parts) + + /** The formatted string interpolator. + * + * It inserts its arguments between corresponding parts of the string context. + * It also treats standard escape sequences as defined in the Scala specification. + * Finally, if an interpolated expression is followed by a `parts` string + * that starts with a formatting specifier, the expression is formatted according to that + * specifier. All specifiers allowed in Java format strings are handled, and in the same + * way they are treated in Java. + * + * For example: + * {{{ + * val height = 1.9d + * val name = "James" + * println(f"\$name%s is \$height%2.2f meters tall") // James is 1.90 meters tall + * }}} + * + * @param `args` The arguments to be inserted into the resulting string. + * @throws IllegalArgumentException + * if the number of `parts` in the enclosing `StringContext` does not exceed + * the number of arguments `arg` by exactly 1. + * @throws StringContext.InvalidEscapeException + * if a `parts` string contains a backslash (`\`) character + * that does not start a valid escape sequence. + * + * Note: The `f` method works by assembling a format string from all the `parts` strings and using + * `java.lang.String.format` to format all arguments with that format string. The format string is + * obtained by concatenating all `parts` strings, and performing two transformations: + * + * 1. Let a _formatting position_ be a start of any `parts` string except the first one. + * If a formatting position does not refer to a `%` character (which is assumed to + * start a format specifier), then the string format specifier `%s` is inserted. + * + * 2. Any `%` characters not in formatting positions must begin one of the conversions + * `%%` (the literal percent) or `%n` (the platform-specific line separator). + */ + def f[A >: Any](args: A*): String = macro ??? // fasttracked to scala.tools.reflect.FormatInterpolator::interpolateF +} + +object StringContext { + /** + * Linear time glob-matching implementation. + * Adapted from https://research.swtch.com/glob + * + * @param patternChunks The non-wildcard portions of the input pattern, + * separated by wildcards + * @param input The input you wish to match against + * @return None if there is no match, Some containing the sequence of matched + * wildcard strings if there is a match + */ + def glob(patternChunks: Seq[String], input: String): Option[Seq[String]] = { + var patternIndex = 0 + var inputIndex = 0 + var nextPatternIndex = 0 + var nextInputIndex = 0 + + val numWildcards = patternChunks.length - 1 + val matchStarts = Array.fill(numWildcards)(-1) + val matchEnds = Array.fill(numWildcards)(-1) + + val nameLength = input.length + // The final pattern is as long as all the chunks, separated by 1-character + // glob-wildcard placeholders + val patternLength = patternChunks.iterator.map(_.length).sum + numWildcards + + // Convert the input pattern chunks into a single sequence of shorts; each + // non-negative short represents a character, while -1 represents a glob wildcard + val pattern = { + val b = new ArrayBuilder.ofShort ; b.sizeHint(patternLength) + patternChunks.head.foreach(c => b.addOne(c.toShort)) + patternChunks.tail.foreach { s => b.addOne(-1) ; s.foreach(c => b.addOne(c.toShort)) } + b.result() + } + + // Lookup table for each character in the pattern to check whether or not + // it refers to a glob wildcard; a non-negative integer indicates which + // glob wildcard it represents, while -1 means it doesn't represent any + val matchIndices = { + val arr = Array.fill(patternLength + 1)(-1) + patternChunks.init.zipWithIndex.foldLeft(0) { case (ttl, (chunk, i)) => + val sum = ttl + chunk.length + arr(sum) = i + sum + 1 + } + arr + } + + while (patternIndex < patternLength || inputIndex < nameLength) { + matchIndices(patternIndex) match { + case -1 => // do nothing + case n => + matchStarts(n) = matchStarts(n) match { + case -1 => inputIndex + case s => math.min(s, inputIndex) + } + matchEnds(n) = matchEnds(n) match { + case -1 => inputIndex + case s => math.max(s, inputIndex) + } + } + + val continue = if (patternIndex < patternLength) { + val c = pattern(patternIndex) + c match { + case -1 => // zero-or-more-character wildcard + // Try to match at nx. If that doesn't work out, restart at nx+1 next. + nextPatternIndex = patternIndex + nextInputIndex = inputIndex + 1 + patternIndex += 1 + true + case _ => // ordinary character + if (inputIndex < nameLength && input(inputIndex) == c) { + patternIndex += 1 + inputIndex += 1 + true + } else { + false + } + } + } else false + + // Mismatch. Maybe restart. + if (!continue) { + if (0 < nextInputIndex && nextInputIndex <= nameLength) { + patternIndex = nextPatternIndex + inputIndex = nextInputIndex + } else { + return None + } + } + } + + // Matched all of pattern to all of name. Success. + Some(collection.immutable.ArraySeq.unsafeWrapArray( + Array.tabulate(patternChunks.length - 1)(n => input.slice(matchStarts(n), matchEnds(n))) + )) + } + + /** An exception that is thrown if a string contains a backslash (`\`) character + * that does not start a valid escape sequence. + * @param str The offending string + * @param index The index of the offending backslash character in `str`. + */ + class InvalidEscapeException(str: String, val index: Int) extends IllegalArgumentException( + s"""invalid escape ${ + require(index >= 0 && index < str.length) + val ok = s"""[\\b, \\t, \\n, \\f, \\r, \\\\, \\", \\', \\uxxxx]""" + if (index == str.length - 1) "at terminal" else s"'\\${str(index + 1)}' not one of $ok at" + } index $index in "$str". Use \\\\ for literal \\.""" + ) + + protected[scala] class InvalidUnicodeEscapeException(str: String, val escapeStart: Int, val index: Int) extends IllegalArgumentException( + s"""invalid unicode escape at index $index of $str""" + ) + + private[this] def readUEscape(src: String, startindex: Int): (Char, Int) = { + val len = src.length() + def loop(uindex: Int): (Char, Int) = { + def loopCP(dindex: Int, codepoint: Int): (Char, Int) = { + //supports BMP + surrogate escapes + //but only in four hex-digit code units (uxxxx) + if(dindex >= 4) { + val usRead = uindex - startindex + val digitsRead = dindex + (codepoint.asInstanceOf[Char], usRead + digitsRead) + } + else if (dindex + uindex >= len) + throw new InvalidUnicodeEscapeException(src, startindex, uindex + dindex) + else { + val ch = src(dindex + uindex) + val e = ch.asDigit + if(e >= 0 && e <= 15) loopCP(dindex + 1, (codepoint << 4) + e) + else throw new InvalidUnicodeEscapeException(src, startindex, uindex + dindex) + } + } + if(uindex >= len) throw new InvalidUnicodeEscapeException(src, startindex, uindex - 1) + //allow one or more `u` characters between the + //backslash and the code unit + else if(src(uindex) == 'u') loop(uindex + 1) + else loopCP(0, 0) + } + loop(startindex) + } + + /** Expands standard Scala escape sequences in a string. + * Escape sequences are: + * control: `\b`, `\t`, `\n`, `\f`, `\r` + * escape: `\\`, `\"`, `\'` + * + * @param str A string that may contain escape sequences + * @return The string with all escape sequences expanded. + */ + @deprecated("use processEscapes", "2.13.0") + def treatEscapes(str: String): String = processEscapes(str) + + /** Expands standard Scala escape sequences in a string. + * Escape sequences are: + * control: `\b`, `\t`, `\n`, `\f`, `\r` + * escape: `\\`, `\"`, `\'` + * + * @param str A string that may contain escape sequences + * @return The string with all escape sequences expanded. + */ + def processEscapes(str: String): String = + str indexOf '\\' match { + case -1 => str + case i => replace(str, i) + } + + protected[scala] def processUnicode(str: String): String = + str indexOf "\\u" match { + case -1 => str + case i => replaceU(str, i) + } + + //replace escapes with given first escape + private[this] def replace(str: String, first: Int): String = { + val len = str.length() + val b = new JLSBuilder + // append replacement starting at index `i`, with `next` backslash + @tailrec def loop(i: Int, next: Int): String = { + if (next >= 0) { + //require(str(next) == '\\') + if (next > i) b.append(str, i, next) + var idx = next + 1 + if (idx >= len) throw new InvalidEscapeException(str, next) + val c = str(idx) match { + case 'u' => 'u' + case 'b' => '\b' + case 't' => '\t' + case 'n' => '\n' + case 'f' => '\f' + case 'r' => '\r' + case '"' => '"' + case '\'' => '\'' + case '\\' => '\\' + case _ => throw new InvalidEscapeException(str, next) + } + val (ch, advance) = if (c == 'u') readUEscape(str, idx) + else (c, 1) + idx += advance + b append ch + loop(idx, str.indexOf('\\', idx)) + } else { + if (i < len) b.append(str, i, len) + b.toString + } + } + loop(0, first) + } + + /** replace Unicode escapes starting at index `backslash` which must be the + * index of the first index of a backslash character followed by a `u` + * character + * + * If a backslash is followed by one or more `u` characters and there is + * an odd number of backslashes immediately preceding the `u`, processing + * the escape is attempted and an invalid escape is an error. + * The odd backslashes rule is, well, odd, but is grandfathered in from + * pre-2.13.2 times, when this same rule existed in the scanner, and was also + * odd. Since escape handling here is for backwards compatibility only, that + * backwards compatibility is also retained. + * Otherwise, the backslash is not taken to introduce an escape and the + * backslash is taken to be literal + */ + private[this] def replaceU(str: String, backslash: Int): String = { + val len = str.length() + val b = new JLSBuilder + + @tailrec def loop(i: Int, next: Int): String = { + if (next >= 0) { + //require(str(next) == '\\' && str(next + 1) == 'u') + def oddBackslashes(ibackslash: Int): Boolean = + if (ibackslash > 0 && str(ibackslash - 1) == '\\') oddBackslashes(ibackslash - 1) + else ((next - ibackslash) % 2) == 0 + + if(oddBackslashes(next)) { + if (next > i) b.append(str, i, next) + val idx = next + 1 + val (ch, advance) = readUEscape(str, idx) + val nextCharIndex = idx + advance + b.append(ch) + loop(nextCharIndex, str.indexOf("\\u", nextCharIndex)) + } + else loop(i, str.indexOf("\\u", next + 1)) + } + else { + if (i < len) b.append(str, i, len) + b.toString() + } + } + loop(0, backslash) + } + + def standardInterpolator(process: String => String, args: scala.collection.Seq[Any], parts: Seq[String]): String = { + StringContext.checkLengths(args, parts) + val pi = parts.iterator + val ai = args.iterator + val bldr = new JLSBuilder(process(pi.next())) + while (ai.hasNext) { + bldr append ai.next() + bldr append process(pi.next()) + } + bldr.toString + } + + /** Checks that the length of the given argument `args` is one less than the number + * of `parts` supplied to the `StringContext`. + * + * @throws IllegalArgumentException if this is not the case. + */ + def checkLengths(args: scala.collection.Seq[Any], parts: Seq[String]): Unit = + if (parts.length != args.length + 1) + throw new IllegalArgumentException("wrong number of arguments ("+ args.length + +") for interpolated string with "+ parts.length +" parts") + +} diff --git a/library/src/scala/Symbol.scala b/library/src/scala/Symbol.scala new file mode 100644 index 000000000000..f865f2ce4262 --- /dev/null +++ b/library/src/scala/Symbol.scala @@ -0,0 +1,86 @@ +/* + * 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.language.`2.13` + +/** This class provides a simple way to get unique objects for equal strings. + * Since symbols are interned, they can be compared using reference equality. + */ +final class Symbol private (val name: String) extends Serializable { + /** A string representation of this symbol. + */ + override def toString(): String = s"Symbol($name)" + + @throws(classOf[java.io.ObjectStreamException]) + private def readResolve(): Any = Symbol.apply(name) + override def hashCode = name.hashCode() + override def equals(other: Any) = this eq other.asInstanceOf[AnyRef] +} + +object Symbol extends UniquenessCache[String, Symbol] { + override def apply(name: String): Symbol = super.apply(name) + protected def valueFromKey(name: String): Symbol = new Symbol(name) + protected def keyFromValue(sym: Symbol): Option[String] = Some(sym.name) +} + +/** This is private so it won't appear in the library API, but + * abstracted to offer some hope of reusability. */ +private[scala] abstract class UniquenessCache[K, V >: Null] { + import java.lang.ref.WeakReference + import java.util.WeakHashMap + import java.util.concurrent.locks.ReentrantReadWriteLock + + private[this] val rwl = new ReentrantReadWriteLock() + private[this] val rlock = rwl.readLock + private[this] val wlock = rwl.writeLock + private[this] val map = new WeakHashMap[K, WeakReference[V]] + + protected def valueFromKey(k: K): V + protected def keyFromValue(v: V): Option[K] + + def apply(name: K): V = { + def cached(): V = { + rlock.lock + try { + val reference = map get name + if (reference == null) null + else reference.get // will be null if we were gc-ed + } + finally rlock.unlock + } + def updateCache(): V = { + wlock.lock + try { + val res = cached() + if (res != null) res + else { + // If we don't remove the old String key from the map, we can + // wind up with one String as the key and a different String as + // the name field in the Symbol, which can lead to surprising GC + // behavior and duplicate Symbols. See scala/bug#6706. + map remove name + val sym = valueFromKey(name) + map.put(name, new WeakReference(sym)) + sym + } + } + finally wlock.unlock + } + cached() match { + case null => updateCache() + case res => res + } + } + def unapply(other: V): Option[K] = keyFromValue(other) +} diff --git a/library/src/scala/Tuple1.scala b/library/src/scala/Tuple1.scala new file mode 100644 index 000000000000..b172fc372d62 --- /dev/null +++ b/library/src/scala/Tuple1.scala @@ -0,0 +1,29 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 1 elements; the canonical representation of a [[scala.Product1]]. + * + * @constructor Create a new tuple with 1 elements. + * @param _1 Element 1 of this Tuple1 + */ +final case class Tuple1[@specialized(Int, Long, Double) +T1](_1: T1) + extends Product1[T1] +{ + override def toString(): String = "(" + _1 + ")" + +} diff --git a/library/src/scala/Tuple10.scala b/library/src/scala/Tuple10.scala new file mode 100644 index 000000000000..38a8aed871c5 --- /dev/null +++ b/library/src/scala/Tuple10.scala @@ -0,0 +1,38 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 10 elements; the canonical representation of a [[scala.Product10]]. + * + * @constructor Create a new tuple with 10 elements. Note that it is more idiomatic to create a Tuple10 via `(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10)` + * @param _1 Element 1 of this Tuple10 + * @param _2 Element 2 of this Tuple10 + * @param _3 Element 3 of this Tuple10 + * @param _4 Element 4 of this Tuple10 + * @param _5 Element 5 of this Tuple10 + * @param _6 Element 6 of this Tuple10 + * @param _7 Element 7 of this Tuple10 + * @param _8 Element 8 of this Tuple10 + * @param _9 Element 9 of this Tuple10 + * @param _10 Element 10 of this Tuple10 + */ +final case class Tuple10[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10) + extends Product10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + "," + _7 + "," + _8 + "," + _9 + "," + _10 + ")" + +} diff --git a/library/src/scala/Tuple11.scala b/library/src/scala/Tuple11.scala new file mode 100644 index 000000000000..516dc8a0d8b4 --- /dev/null +++ b/library/src/scala/Tuple11.scala @@ -0,0 +1,39 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 11 elements; the canonical representation of a [[scala.Product11]]. + * + * @constructor Create a new tuple with 11 elements. Note that it is more idiomatic to create a Tuple11 via `(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11)` + * @param _1 Element 1 of this Tuple11 + * @param _2 Element 2 of this Tuple11 + * @param _3 Element 3 of this Tuple11 + * @param _4 Element 4 of this Tuple11 + * @param _5 Element 5 of this Tuple11 + * @param _6 Element 6 of this Tuple11 + * @param _7 Element 7 of this Tuple11 + * @param _8 Element 8 of this Tuple11 + * @param _9 Element 9 of this Tuple11 + * @param _10 Element 10 of this Tuple11 + * @param _11 Element 11 of this Tuple11 + */ +final case class Tuple11[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11) + extends Product11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + "," + _7 + "," + _8 + "," + _9 + "," + _10 + "," + _11 + ")" + +} diff --git a/library/src/scala/Tuple12.scala b/library/src/scala/Tuple12.scala new file mode 100644 index 000000000000..f3a0d3ff1192 --- /dev/null +++ b/library/src/scala/Tuple12.scala @@ -0,0 +1,41 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 12 elements; the canonical representation of a [[scala.Product12]]. + * + * @constructor Create a new tuple with 12 elements. Note that it is more idiomatic to create a Tuple12 via `(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12)` + * @param _1 Element 1 of this Tuple12 + * @param _2 Element 2 of this Tuple12 + * @param _3 Element 3 of this Tuple12 + * @param _4 Element 4 of this Tuple12 + * @param _5 Element 5 of this Tuple12 + * @param _6 Element 6 of this Tuple12 + * @param _7 Element 7 of this Tuple12 + * @param _8 Element 8 of this Tuple12 + * @param _9 Element 9 of this Tuple12 + * @param _10 Element 10 of this Tuple12 + * @param _11 Element 11 of this Tuple12 + * @param _12 Element 12 of this Tuple12 + */ +final case class Tuple12[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12) + extends Product12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + + "," + _7 + "," + _8 + "," + _9 + "," + _10 + "," + _11 + "," + _12 + ")" + +} diff --git a/library/src/scala/Tuple13.scala b/library/src/scala/Tuple13.scala new file mode 100644 index 000000000000..29b016d8fe0f --- /dev/null +++ b/library/src/scala/Tuple13.scala @@ -0,0 +1,42 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 13 elements; the canonical representation of a [[scala.Product13]]. + * + * @constructor Create a new tuple with 13 elements. Note that it is more idiomatic to create a Tuple13 via `(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13)` + * @param _1 Element 1 of this Tuple13 + * @param _2 Element 2 of this Tuple13 + * @param _3 Element 3 of this Tuple13 + * @param _4 Element 4 of this Tuple13 + * @param _5 Element 5 of this Tuple13 + * @param _6 Element 6 of this Tuple13 + * @param _7 Element 7 of this Tuple13 + * @param _8 Element 8 of this Tuple13 + * @param _9 Element 9 of this Tuple13 + * @param _10 Element 10 of this Tuple13 + * @param _11 Element 11 of this Tuple13 + * @param _12 Element 12 of this Tuple13 + * @param _13 Element 13 of this Tuple13 + */ +final case class Tuple13[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13) + extends Product13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + + "," + _7 + "," + _8 + "," + _9 + "," + _10 + "," + _11 + "," + _12 + "," + _13 + ")" + +} diff --git a/library/src/scala/Tuple14.scala b/library/src/scala/Tuple14.scala new file mode 100644 index 000000000000..1cc257ba384d --- /dev/null +++ b/library/src/scala/Tuple14.scala @@ -0,0 +1,43 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 14 elements; the canonical representation of a [[scala.Product14]]. + * + * @constructor Create a new tuple with 14 elements. Note that it is more idiomatic to create a Tuple14 via `(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14)` + * @param _1 Element 1 of this Tuple14 + * @param _2 Element 2 of this Tuple14 + * @param _3 Element 3 of this Tuple14 + * @param _4 Element 4 of this Tuple14 + * @param _5 Element 5 of this Tuple14 + * @param _6 Element 6 of this Tuple14 + * @param _7 Element 7 of this Tuple14 + * @param _8 Element 8 of this Tuple14 + * @param _9 Element 9 of this Tuple14 + * @param _10 Element 10 of this Tuple14 + * @param _11 Element 11 of this Tuple14 + * @param _12 Element 12 of this Tuple14 + * @param _13 Element 13 of this Tuple14 + * @param _14 Element 14 of this Tuple14 + */ +final case class Tuple14[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14) + extends Product14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + "," + _7 + + "," + _8 + "," + _9 + "," + _10 + "," + _11 + "," + _12 + "," + _13 + "," + _14 + ")" + +} diff --git a/library/src/scala/Tuple15.scala b/library/src/scala/Tuple15.scala new file mode 100644 index 000000000000..e7dcd5cedb81 --- /dev/null +++ b/library/src/scala/Tuple15.scala @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 15 elements; the canonical representation of a [[scala.Product15]]. + * + * @constructor Create a new tuple with 15 elements. Note that it is more idiomatic to create a Tuple15 via `(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15)` + * @param _1 Element 1 of this Tuple15 + * @param _2 Element 2 of this Tuple15 + * @param _3 Element 3 of this Tuple15 + * @param _4 Element 4 of this Tuple15 + * @param _5 Element 5 of this Tuple15 + * @param _6 Element 6 of this Tuple15 + * @param _7 Element 7 of this Tuple15 + * @param _8 Element 8 of this Tuple15 + * @param _9 Element 9 of this Tuple15 + * @param _10 Element 10 of this Tuple15 + * @param _11 Element 11 of this Tuple15 + * @param _12 Element 12 of this Tuple15 + * @param _13 Element 13 of this Tuple15 + * @param _14 Element 14 of this Tuple15 + * @param _15 Element 15 of this Tuple15 + */ +final case class Tuple15[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15) + extends Product15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + "," + _7 + + "," + _8 + "," + _9 + "," + _10 + "," + _11 + "," + _12 + "," + _13 + "," + _14 + "," + _15 + ")" + +} diff --git a/library/src/scala/Tuple16.scala b/library/src/scala/Tuple16.scala new file mode 100644 index 000000000000..f585f8e8a54c --- /dev/null +++ b/library/src/scala/Tuple16.scala @@ -0,0 +1,45 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 16 elements; the canonical representation of a [[scala.Product16]]. + * + * @constructor Create a new tuple with 16 elements. Note that it is more idiomatic to create a Tuple16 via `(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16)` + * @param _1 Element 1 of this Tuple16 + * @param _2 Element 2 of this Tuple16 + * @param _3 Element 3 of this Tuple16 + * @param _4 Element 4 of this Tuple16 + * @param _5 Element 5 of this Tuple16 + * @param _6 Element 6 of this Tuple16 + * @param _7 Element 7 of this Tuple16 + * @param _8 Element 8 of this Tuple16 + * @param _9 Element 9 of this Tuple16 + * @param _10 Element 10 of this Tuple16 + * @param _11 Element 11 of this Tuple16 + * @param _12 Element 12 of this Tuple16 + * @param _13 Element 13 of this Tuple16 + * @param _14 Element 14 of this Tuple16 + * @param _15 Element 15 of this Tuple16 + * @param _16 Element 16 of this Tuple16 + */ +final case class Tuple16[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16) + extends Product16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + "," + _7 + "," + _8 + + "," + _9 + "," + _10 + "," + _11 + "," + _12 + "," + _13 + "," + _14 + "," + _15 + "," + _16 + ")" + +} diff --git a/library/src/scala/Tuple17.scala b/library/src/scala/Tuple17.scala new file mode 100644 index 000000000000..d2fbbc2bc961 --- /dev/null +++ b/library/src/scala/Tuple17.scala @@ -0,0 +1,46 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 17 elements; the canonical representation of a [[scala.Product17]]. + * + * @constructor Create a new tuple with 17 elements. Note that it is more idiomatic to create a Tuple17 via `(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17)` + * @param _1 Element 1 of this Tuple17 + * @param _2 Element 2 of this Tuple17 + * @param _3 Element 3 of this Tuple17 + * @param _4 Element 4 of this Tuple17 + * @param _5 Element 5 of this Tuple17 + * @param _6 Element 6 of this Tuple17 + * @param _7 Element 7 of this Tuple17 + * @param _8 Element 8 of this Tuple17 + * @param _9 Element 9 of this Tuple17 + * @param _10 Element 10 of this Tuple17 + * @param _11 Element 11 of this Tuple17 + * @param _12 Element 12 of this Tuple17 + * @param _13 Element 13 of this Tuple17 + * @param _14 Element 14 of this Tuple17 + * @param _15 Element 15 of this Tuple17 + * @param _16 Element 16 of this Tuple17 + * @param _17 Element 17 of this Tuple17 + */ +final case class Tuple17[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16, +T17](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17) + extends Product17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + "," + _7 + "," + _8 + + "," + _9 + "," + _10 + "," + _11 + "," + _12 + "," + _13 + "," + _14 + "," + _15 + "," + _16 + "," + _17 + ")" + +} diff --git a/library/src/scala/Tuple18.scala b/library/src/scala/Tuple18.scala new file mode 100644 index 000000000000..8928c483d09e --- /dev/null +++ b/library/src/scala/Tuple18.scala @@ -0,0 +1,47 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 18 elements; the canonical representation of a [[scala.Product18]]. + * + * @constructor Create a new tuple with 18 elements. Note that it is more idiomatic to create a Tuple18 via `(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18)` + * @param _1 Element 1 of this Tuple18 + * @param _2 Element 2 of this Tuple18 + * @param _3 Element 3 of this Tuple18 + * @param _4 Element 4 of this Tuple18 + * @param _5 Element 5 of this Tuple18 + * @param _6 Element 6 of this Tuple18 + * @param _7 Element 7 of this Tuple18 + * @param _8 Element 8 of this Tuple18 + * @param _9 Element 9 of this Tuple18 + * @param _10 Element 10 of this Tuple18 + * @param _11 Element 11 of this Tuple18 + * @param _12 Element 12 of this Tuple18 + * @param _13 Element 13 of this Tuple18 + * @param _14 Element 14 of this Tuple18 + * @param _15 Element 15 of this Tuple18 + * @param _16 Element 16 of this Tuple18 + * @param _17 Element 17 of this Tuple18 + * @param _18 Element 18 of this Tuple18 + */ +final case class Tuple18[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16, +T17, +T18](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18) + extends Product18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + "," + _7 + "," + _8 + "," + _9 + + "," + _10 + "," + _11 + "," + _12 + "," + _13 + "," + _14 + "," + _15 + "," + _16 + "," + _17 + "," + _18 + ")" + +} diff --git a/library/src/scala/Tuple19.scala b/library/src/scala/Tuple19.scala new file mode 100644 index 000000000000..a95ab333d64c --- /dev/null +++ b/library/src/scala/Tuple19.scala @@ -0,0 +1,48 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 19 elements; the canonical representation of a [[scala.Product19]]. + * + * @constructor Create a new tuple with 19 elements. Note that it is more idiomatic to create a Tuple19 via `(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19)` + * @param _1 Element 1 of this Tuple19 + * @param _2 Element 2 of this Tuple19 + * @param _3 Element 3 of this Tuple19 + * @param _4 Element 4 of this Tuple19 + * @param _5 Element 5 of this Tuple19 + * @param _6 Element 6 of this Tuple19 + * @param _7 Element 7 of this Tuple19 + * @param _8 Element 8 of this Tuple19 + * @param _9 Element 9 of this Tuple19 + * @param _10 Element 10 of this Tuple19 + * @param _11 Element 11 of this Tuple19 + * @param _12 Element 12 of this Tuple19 + * @param _13 Element 13 of this Tuple19 + * @param _14 Element 14 of this Tuple19 + * @param _15 Element 15 of this Tuple19 + * @param _16 Element 16 of this Tuple19 + * @param _17 Element 17 of this Tuple19 + * @param _18 Element 18 of this Tuple19 + * @param _19 Element 19 of this Tuple19 + */ +final case class Tuple19[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16, +T17, +T18, +T19](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18, _19: T19) + extends Product19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + "," + _7 + "," + _8 + "," + _9 + + "," + _10 + "," + _11 + "," + _12 + "," + _13 + "," + _14 + "," + _15 + "," + _16 + "," + _17 + "," + _18 + "," + _19 + ")" + +} diff --git a/library/src/scala/Tuple2.scala b/library/src/scala/Tuple2.scala new file mode 100644 index 000000000000..7f7b1d8d0866 --- /dev/null +++ b/library/src/scala/Tuple2.scala @@ -0,0 +1,36 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 2 elements; the canonical representation of a [[scala.Product2]]. + * + * @constructor Create a new tuple with 2 elements. Note that it is more idiomatic to create a Tuple2 via `(t1, t2)` + * @param _1 Element 1 of this Tuple2 + * @param _2 Element 2 of this Tuple2 + */ +final case class Tuple2[@specialized(Int, Long, Double, Char, Boolean/*, AnyRef*/) +T1, @specialized(Int, Long, Double, Char, Boolean/*, AnyRef*/) +T2](_1: T1, _2: T2) + extends Product2[T1, T2] +{ + override def toString(): String = "(" + _1 + "," + _2 + ")" + + /** Swaps the elements of this `Tuple`. + * @return a new Tuple where the first element is the second element of this Tuple and the + * second element is the first element of this Tuple. + */ + def swap: Tuple2[T2,T1] = Tuple2(_2, _1) + +} diff --git a/library/src/scala/Tuple20.scala b/library/src/scala/Tuple20.scala new file mode 100644 index 000000000000..79217269fae7 --- /dev/null +++ b/library/src/scala/Tuple20.scala @@ -0,0 +1,49 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 20 elements; the canonical representation of a [[scala.Product20]]. + * + * @constructor Create a new tuple with 20 elements. Note that it is more idiomatic to create a Tuple20 via `(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, t20)` + * @param _1 Element 1 of this Tuple20 + * @param _2 Element 2 of this Tuple20 + * @param _3 Element 3 of this Tuple20 + * @param _4 Element 4 of this Tuple20 + * @param _5 Element 5 of this Tuple20 + * @param _6 Element 6 of this Tuple20 + * @param _7 Element 7 of this Tuple20 + * @param _8 Element 8 of this Tuple20 + * @param _9 Element 9 of this Tuple20 + * @param _10 Element 10 of this Tuple20 + * @param _11 Element 11 of this Tuple20 + * @param _12 Element 12 of this Tuple20 + * @param _13 Element 13 of this Tuple20 + * @param _14 Element 14 of this Tuple20 + * @param _15 Element 15 of this Tuple20 + * @param _16 Element 16 of this Tuple20 + * @param _17 Element 17 of this Tuple20 + * @param _18 Element 18 of this Tuple20 + * @param _19 Element 19 of this Tuple20 + * @param _20 Element 20 of this Tuple20 + */ +final case class Tuple20[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16, +T17, +T18, +T19, +T20](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18, _19: T19, _20: T20) + extends Product20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + "," + _7 + "," + _8 + "," + _9 + "," + _10 + + "," + _11 + "," + _12 + "," + _13 + "," + _14 + "," + _15 + "," + _16 + "," + _17 + "," + _18 + "," + _19 + "," + _20 + ")" + +} diff --git a/library/src/scala/Tuple21.scala b/library/src/scala/Tuple21.scala new file mode 100644 index 000000000000..951bccf2bc97 --- /dev/null +++ b/library/src/scala/Tuple21.scala @@ -0,0 +1,50 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 21 elements; the canonical representation of a [[scala.Product21]]. + * + * @constructor Create a new tuple with 21 elements. Note that it is more idiomatic to create a Tuple21 via `(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, t20, t21)` + * @param _1 Element 1 of this Tuple21 + * @param _2 Element 2 of this Tuple21 + * @param _3 Element 3 of this Tuple21 + * @param _4 Element 4 of this Tuple21 + * @param _5 Element 5 of this Tuple21 + * @param _6 Element 6 of this Tuple21 + * @param _7 Element 7 of this Tuple21 + * @param _8 Element 8 of this Tuple21 + * @param _9 Element 9 of this Tuple21 + * @param _10 Element 10 of this Tuple21 + * @param _11 Element 11 of this Tuple21 + * @param _12 Element 12 of this Tuple21 + * @param _13 Element 13 of this Tuple21 + * @param _14 Element 14 of this Tuple21 + * @param _15 Element 15 of this Tuple21 + * @param _16 Element 16 of this Tuple21 + * @param _17 Element 17 of this Tuple21 + * @param _18 Element 18 of this Tuple21 + * @param _19 Element 19 of this Tuple21 + * @param _20 Element 20 of this Tuple21 + * @param _21 Element 21 of this Tuple21 + */ +final case class Tuple21[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16, +T17, +T18, +T19, +T20, +T21](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18, _19: T19, _20: T20, _21: T21) + extends Product21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + "," + _7 + "," + _8 + "," + _9 + "," + _10 + + "," + _11 + "," + _12 + "," + _13 + "," + _14 + "," + _15 + "," + _16 + "," + _17 + "," + _18 + "," + _19 + "," + _20 + "," + _21 + ")" + +} diff --git a/library/src/scala/Tuple22.scala b/library/src/scala/Tuple22.scala new file mode 100644 index 000000000000..f0f3533cd4bd --- /dev/null +++ b/library/src/scala/Tuple22.scala @@ -0,0 +1,51 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 22 elements; the canonical representation of a [[scala.Product22]]. + * + * @constructor Create a new tuple with 22 elements. Note that it is more idiomatic to create a Tuple22 via `(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, t20, t21, t22)` + * @param _1 Element 1 of this Tuple22 + * @param _2 Element 2 of this Tuple22 + * @param _3 Element 3 of this Tuple22 + * @param _4 Element 4 of this Tuple22 + * @param _5 Element 5 of this Tuple22 + * @param _6 Element 6 of this Tuple22 + * @param _7 Element 7 of this Tuple22 + * @param _8 Element 8 of this Tuple22 + * @param _9 Element 9 of this Tuple22 + * @param _10 Element 10 of this Tuple22 + * @param _11 Element 11 of this Tuple22 + * @param _12 Element 12 of this Tuple22 + * @param _13 Element 13 of this Tuple22 + * @param _14 Element 14 of this Tuple22 + * @param _15 Element 15 of this Tuple22 + * @param _16 Element 16 of this Tuple22 + * @param _17 Element 17 of this Tuple22 + * @param _18 Element 18 of this Tuple22 + * @param _19 Element 19 of this Tuple22 + * @param _20 Element 20 of this Tuple22 + * @param _21 Element 21 of this Tuple22 + * @param _22 Element 22 of this Tuple22 + */ +final case class Tuple22[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9, +T10, +T11, +T12, +T13, +T14, +T15, +T16, +T17, +T18, +T19, +T20, +T21, +T22](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18, _19: T19, _20: T20, _21: T21, _22: T22) + extends Product22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + "," + _7 + "," + _8 + "," + _9 + "," + _10 + "," + _11 + + "," + _12 + "," + _13 + "," + _14 + "," + _15 + "," + _16 + "," + _17 + "," + _18 + "," + _19 + "," + _20 + "," + _21 + "," + _22 + ")" + +} diff --git a/library/src/scala/Tuple3.scala b/library/src/scala/Tuple3.scala new file mode 100644 index 000000000000..c63abe5786a0 --- /dev/null +++ b/library/src/scala/Tuple3.scala @@ -0,0 +1,31 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 3 elements; the canonical representation of a [[scala.Product3]]. + * + * @constructor Create a new tuple with 3 elements. Note that it is more idiomatic to create a Tuple3 via `(t1, t2, t3)` + * @param _1 Element 1 of this Tuple3 + * @param _2 Element 2 of this Tuple3 + * @param _3 Element 3 of this Tuple3 + */ +final case class Tuple3[+T1, +T2, +T3](_1: T1, _2: T2, _3: T3) + extends Product3[T1, T2, T3] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + ")" + +} diff --git a/library/src/scala/Tuple4.scala b/library/src/scala/Tuple4.scala new file mode 100644 index 000000000000..e48cb2043d4c --- /dev/null +++ b/library/src/scala/Tuple4.scala @@ -0,0 +1,32 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 4 elements; the canonical representation of a [[scala.Product4]]. + * + * @constructor Create a new tuple with 4 elements. Note that it is more idiomatic to create a Tuple4 via `(t1, t2, t3, t4)` + * @param _1 Element 1 of this Tuple4 + * @param _2 Element 2 of this Tuple4 + * @param _3 Element 3 of this Tuple4 + * @param _4 Element 4 of this Tuple4 + */ +final case class Tuple4[+T1, +T2, +T3, +T4](_1: T1, _2: T2, _3: T3, _4: T4) + extends Product4[T1, T2, T3, T4] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + ")" + +} diff --git a/library/src/scala/Tuple5.scala b/library/src/scala/Tuple5.scala new file mode 100644 index 000000000000..c4a4fc6cf415 --- /dev/null +++ b/library/src/scala/Tuple5.scala @@ -0,0 +1,33 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 5 elements; the canonical representation of a [[scala.Product5]]. + * + * @constructor Create a new tuple with 5 elements. Note that it is more idiomatic to create a Tuple5 via `(t1, t2, t3, t4, t5)` + * @param _1 Element 1 of this Tuple5 + * @param _2 Element 2 of this Tuple5 + * @param _3 Element 3 of this Tuple5 + * @param _4 Element 4 of this Tuple5 + * @param _5 Element 5 of this Tuple5 + */ +final case class Tuple5[+T1, +T2, +T3, +T4, +T5](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5) + extends Product5[T1, T2, T3, T4, T5] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + ")" + +} diff --git a/library/src/scala/Tuple6.scala b/library/src/scala/Tuple6.scala new file mode 100644 index 000000000000..1e28feb1661b --- /dev/null +++ b/library/src/scala/Tuple6.scala @@ -0,0 +1,34 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 6 elements; the canonical representation of a [[scala.Product6]]. + * + * @constructor Create a new tuple with 6 elements. Note that it is more idiomatic to create a Tuple6 via `(t1, t2, t3, t4, t5, t6)` + * @param _1 Element 1 of this Tuple6 + * @param _2 Element 2 of this Tuple6 + * @param _3 Element 3 of this Tuple6 + * @param _4 Element 4 of this Tuple6 + * @param _5 Element 5 of this Tuple6 + * @param _6 Element 6 of this Tuple6 + */ +final case class Tuple6[+T1, +T2, +T3, +T4, +T5, +T6](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6) + extends Product6[T1, T2, T3, T4, T5, T6] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + ")" + +} diff --git a/library/src/scala/Tuple7.scala b/library/src/scala/Tuple7.scala new file mode 100644 index 000000000000..64e261eb7f32 --- /dev/null +++ b/library/src/scala/Tuple7.scala @@ -0,0 +1,35 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 7 elements; the canonical representation of a [[scala.Product7]]. + * + * @constructor Create a new tuple with 7 elements. Note that it is more idiomatic to create a Tuple7 via `(t1, t2, t3, t4, t5, t6, t7)` + * @param _1 Element 1 of this Tuple7 + * @param _2 Element 2 of this Tuple7 + * @param _3 Element 3 of this Tuple7 + * @param _4 Element 4 of this Tuple7 + * @param _5 Element 5 of this Tuple7 + * @param _6 Element 6 of this Tuple7 + * @param _7 Element 7 of this Tuple7 + */ +final case class Tuple7[+T1, +T2, +T3, +T4, +T5, +T6, +T7](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7) + extends Product7[T1, T2, T3, T4, T5, T6, T7] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + "," + _7 + ")" + +} diff --git a/library/src/scala/Tuple8.scala b/library/src/scala/Tuple8.scala new file mode 100644 index 000000000000..5c5adfd5a5b4 --- /dev/null +++ b/library/src/scala/Tuple8.scala @@ -0,0 +1,36 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 8 elements; the canonical representation of a [[scala.Product8]]. + * + * @constructor Create a new tuple with 8 elements. Note that it is more idiomatic to create a Tuple8 via `(t1, t2, t3, t4, t5, t6, t7, t8)` + * @param _1 Element 1 of this Tuple8 + * @param _2 Element 2 of this Tuple8 + * @param _3 Element 3 of this Tuple8 + * @param _4 Element 4 of this Tuple8 + * @param _5 Element 5 of this Tuple8 + * @param _6 Element 6 of this Tuple8 + * @param _7 Element 7 of this Tuple8 + * @param _8 Element 8 of this Tuple8 + */ +final case class Tuple8[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8) + extends Product8[T1, T2, T3, T4, T5, T6, T7, T8] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + "," + _7 + "," + _8 + ")" + +} diff --git a/library/src/scala/Tuple9.scala b/library/src/scala/Tuple9.scala new file mode 100644 index 000000000000..3a69c9ae8b25 --- /dev/null +++ b/library/src/scala/Tuple9.scala @@ -0,0 +1,37 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala + +import scala.language.`2.13` + +/** A tuple of 9 elements; the canonical representation of a [[scala.Product9]]. + * + * @constructor Create a new tuple with 9 elements. Note that it is more idiomatic to create a Tuple9 via `(t1, t2, t3, t4, t5, t6, t7, t8, t9)` + * @param _1 Element 1 of this Tuple9 + * @param _2 Element 2 of this Tuple9 + * @param _3 Element 3 of this Tuple9 + * @param _4 Element 4 of this Tuple9 + * @param _5 Element 5 of this Tuple9 + * @param _6 Element 6 of this Tuple9 + * @param _7 Element 7 of this Tuple9 + * @param _8 Element 8 of this Tuple9 + * @param _9 Element 9 of this Tuple9 + */ +final case class Tuple9[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8, +T9](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9) + extends Product9[T1, T2, T3, T4, T5, T6, T7, T8, T9] +{ + override def toString(): String = "(" + _1 + "," + _2 + "," + _3 + "," + _4 + "," + _5 + "," + _6 + "," + _7 + "," + _8 + "," + _9 + ")" + +} diff --git a/library/src/scala/UninitializedError.scala b/library/src/scala/UninitializedError.scala new file mode 100644 index 000000000000..e53c2698ef68 --- /dev/null +++ b/library/src/scala/UninitializedError.scala @@ -0,0 +1,21 @@ +/* + * 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.language.`2.13` + +/** This class represents uninitialized variable/value errors. + */ +// TODO: remove +@deprecated("will be removed in a future release", since = "2.12.7") +final class UninitializedError extends RuntimeException("uninitialized value") diff --git a/library/src/scala/UninitializedFieldError.scala b/library/src/scala/UninitializedFieldError.scala new file mode 100644 index 000000000000..77476a69b8af --- /dev/null +++ b/library/src/scala/UninitializedFieldError.scala @@ -0,0 +1,25 @@ +/* + * 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.language.`2.13` + +/** This class implements errors which are thrown whenever a + * field is used before it has been initialized. + * + * Such runtime checks are not emitted by default. + * They can be enabled by the `-Xcheckinit` compiler option. + */ +final case class UninitializedFieldError(msg: String) extends RuntimeException(msg) { + def this(obj: Any) = this("" + obj) +} diff --git a/library/src/scala/Unit.scala b/library/src/scala/Unit.scala new file mode 100644 index 000000000000..1799f678e3fa --- /dev/null +++ b/library/src/scala/Unit.scala @@ -0,0 +1,60 @@ +/* + * 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. + */ + +// DO NOT EDIT, CHANGES WILL BE LOST +// This auto-generated code can be modified in "project/GenerateAnyVals.scala". +// Afterwards, running "sbt generateSources" regenerates this source file. + +package scala + +import scala.language.`2.13` + +/** `Unit` is a subtype of [[scala.AnyVal]]. There is only one value of type + * `Unit`, `()`, and it is not represented by any object in the underlying + * runtime system. A method with return type `Unit` is analogous to a Java + * method which is declared `void`. + */ +final abstract class Unit private extends AnyVal { + // Provide a more specific return type for Scaladoc + override def getClass(): Class[Unit] = ??? +} + +@scala.annotation.compileTimeOnly("`Unit` companion object is not allowed in source; instead, use `()` for the unit value") +object Unit extends AnyValCompanion { + + /** Transform a value type into a boxed reference type. + * + * This method is not intended for use in source code. + * The runtime representation of this value is platform specific. + * + * @param x the Unit to be boxed + * @return a scala.runtime.BoxedUnit offering `x` as its underlying value. + */ + def box(x: Unit): scala.runtime.BoxedUnit = scala.runtime.BoxedUnit.UNIT + + /** Transform a boxed type into a value type. Note that this + * method is not typesafe: it accepts any Object, but will throw + * an exception if the argument is not a scala.runtime.BoxedUnit. + * + * This method is not intended for use in source code. + * The result of successfully unboxing a value is `()`. + * + * @param x the scala.runtime.BoxedUnit to be unboxed. + * @throws ClassCastException if the argument is not a scala.runtime.BoxedUnit + * @return the Unit value () + */ + def unbox(x: java.lang.Object): Unit = x.asInstanceOf[scala.runtime.BoxedUnit] + + /** The String representation of the scala.Unit companion object. */ + override def toString = "object scala.Unit" +} + diff --git a/library/src/scala/ValueOf.scala b/library/src/scala/ValueOf.scala new file mode 100644 index 000000000000..eea40ca5e0c3 --- /dev/null +++ b/library/src/scala/ValueOf.scala @@ -0,0 +1,57 @@ +/* + * 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.language.`2.13` + +/** + * `ValueOf[T]` provides the unique value of the type `T` where `T` is a type which has a + * single inhabitant. Eligible types are singleton types of the form `stablePath.type`, + * Unit and singleton types corresponding to value literals. + * + * The value itself can conveniently be retrieved with [[Predef#valueOf]], which requires + * a `ValueOf` to be available in implicit scope. + * + * The compiler provides instances of `ValueOf[T]` for all eligible types. Typically + * an instance would be required where a runtime value corresponding to a type level + * computation is needed. + + * For example, we might define a type `Residue[M <: Int]` corresponding to the group of + * integers modulo `M`. We could then mandate that residues can be summed only when they + * are parameterized by the same modulus, + * + * {{{ + * case class Residue[M <: Int](n: Int) extends AnyVal { + * def +(rhs: Residue[M])(implicit m: ValueOf[M]): Residue[M] = + * Residue((this.n + rhs.n) % valueOf[M]) + * } + * + * val fiveModTen = Residue[10](5) + * val nineModTen = Residue[10](9) + * + * fiveModTen + nineModTen // OK == Residue[10](4) + * + * val fourModEleven = Residue[11](4) + * + * fiveModTen + fourModEleven // compiler error: type mismatch; + * // found : Residue[11] + * // required: Residue[10] + * }}} + * + * Notice that here the modulus is encoded in the type of the values and so does not + * incur any additional per-value storage cost. When a runtime value of the modulus + * is required in the implementation of `+` it is provided at the call site via the + * implicit argument `m` of type `ValueOf[M]`. + */ +@scala.annotation.implicitNotFound(msg = "No singleton value available for ${T}; eligible singleton types for `ValueOf` synthesis include literals and stable paths.") +final class ValueOf[T](val value: T) extends AnyVal diff --git a/library/src/scala/annotation/Annotation.scala b/library/src/scala/annotation/Annotation.scala new file mode 100644 index 000000000000..8997071d9321 --- /dev/null +++ b/library/src/scala/annotation/Annotation.scala @@ -0,0 +1,28 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** + * A base class for annotations. + * + * Annotations extending this class directly are not preserved in the classfile. To enable storing + * annotations in the classfile's Scala signature and make it available to Scala reflection and + * other tools, the annotation needs to inherit from [[scala.annotation.StaticAnnotation]]. + * + * Annotation classes defined in Scala are not stored in classfiles in a Java-compatible manner + * and therefore not visible in Java reflection. In order to achieve this, the annotation has to + * be written in Java. + */ +abstract class Annotation diff --git a/library/src/scala/annotation/ClassfileAnnotation.scala b/library/src/scala/annotation/ClassfileAnnotation.scala new file mode 100644 index 000000000000..c9e9cd63614a --- /dev/null +++ b/library/src/scala/annotation/ClassfileAnnotation.scala @@ -0,0 +1,22 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** A base class for classfile annotations. These are stored as + * [[https://docs.oracle.com/javase/8/docs/technotes/guides/language/annotations.html Java annotations]] + * in classfiles. + */ +@deprecated("Annotation classes need to be written in Java in order to be stored in classfiles in a Java-compatible manner", "2.13.0") +trait ClassfileAnnotation extends ConstantAnnotation diff --git a/library/src/scala/annotation/ConstantAnnotation.scala b/library/src/scala/annotation/ConstantAnnotation.scala new file mode 100644 index 000000000000..248d18545842 --- /dev/null +++ b/library/src/scala/annotation/ConstantAnnotation.scala @@ -0,0 +1,46 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** + * Annotation classes extending this trait only accept constant values as arguments. + * + * Note that this trait extends [[StaticAnnotation]], so constant annotations are persisted in the + * classfile. + * + * The implementation requires arguments of constant annotations to be passed as named arguments, + * except if there is a single argument, which then defines the annotation's parameter named + * `value`. + * + * Constant annotations may use default arguments. Note that the internal representation of an + * annotation usage (which is visible for compiler plugins, for example) only contains arguments + * that are explicitly provided. + * + * Constant annotations are not allowed to define auxiliary constructors, and the primary + * constructor is required to have a single parameter list. + * + * Example: + * + * {{{ + * class Ann(value: Int, x: Int = 0) extends scala.annotation.ConstantAnnotation + * class Test { + * def someInt = 0 + * @Ann(value = 0, x = 1) def g = 0 + * @Ann(0) def f = 0 // Internal representation contains `@Ann(value = 0)` + * @Ann(someInt) // error: argument needs to be a compile-time constant + * } + * }}} + */ +trait ConstantAnnotation extends StaticAnnotation diff --git a/library/src/scala/annotation/StaticAnnotation.scala b/library/src/scala/annotation/StaticAnnotation.scala new file mode 100644 index 000000000000..0d0e2159731e --- /dev/null +++ b/library/src/scala/annotation/StaticAnnotation.scala @@ -0,0 +1,25 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** + * A base class for static annotations. These are available to the Scala type checker or Scala + * reflection, even across different compilation units. + * + * Annotation classes defined in Scala are not stored in classfiles in a Java-compatible manner + * and therefore not visible in Java reflection. In order to achieve this, the annotation has to + * be written in Java. + */ +trait StaticAnnotation extends Annotation diff --git a/library/src/scala/annotation/TypeConstraint.scala b/library/src/scala/annotation/TypeConstraint.scala new file mode 100644 index 000000000000..4cfd24a1bc9a --- /dev/null +++ b/library/src/scala/annotation/TypeConstraint.scala @@ -0,0 +1,28 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** A marker for annotations that, when applied to a type, should be treated + * as a constraint on the annotated type. + * + * A proper constraint should restrict the type based only on information + * mentioned within the type. A Scala compiler can use this assumption to + * rewrite the contents of the constraint as necessary. To contrast, a type + * annotation whose meaning depends on the context where it is written + * down is not a proper constrained type, and this marker should not be + * applied. A Scala compiler will drop such annotations in cases where it + * would rewrite a type constraint. + */ +trait TypeConstraint extends Annotation diff --git a/library/src/scala/annotation/compileTimeOnly.scala b/library/src/scala/annotation/compileTimeOnly.scala new file mode 100644 index 000000000000..731e03a0bbc6 --- /dev/null +++ b/library/src/scala/annotation/compileTimeOnly.scala @@ -0,0 +1,34 @@ +/* + * 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.annotation + +import scala.language.`2.13` +import scala.annotation.meta._ + +/** + * An annotation that designates that an annottee should not be referred to after + * type checking (which includes macro expansion). + * + * Examples of potential use: + * 1) The annottee can only appear in the arguments of some other macro + * that will eliminate it from the AST during expansion. + * 2) The annottee is a macro and should have been expanded away, + * so if hasn't, something wrong has happened. + * (Comes in handy to provide better support for new macro flavors, + * e.g. macro annotations, that can't be expanded by the vanilla compiler). + * + * @param message the error message to print during compilation if a reference remains + * after type checking + */ +@getter @setter @beanGetter @beanSetter @companionClass @companionMethod +final class compileTimeOnly(message: String) extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/elidable.scala b/library/src/scala/annotation/elidable.scala new file mode 100644 index 000000000000..db2aa024a071 --- /dev/null +++ b/library/src/scala/annotation/elidable.scala @@ -0,0 +1,158 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** An annotation for methods whose bodies may be excluded + * from compiler-generated bytecode. + * + * Behavior is influenced by passing `-Xelide-below ` to `scalac`. + * Calls to methods marked elidable (as well as the method body) will + * be omitted from generated code if the priority given the annotation + * is lower than that given on the command line. + * + * {{{ + * @elidable(123) // annotation priority + * scalac -Xelide-below 456 // command line priority + * }}} + * + * The method call will be replaced with an expression which depends on + * the type of the elided expression. In decreasing order of precedence: + * + * {{{ + * Unit () + * Boolean false + * T <: AnyVal 0 + * T >: Null null + * T >: Nothing Predef.??? + * }}} + * + * Complete example: + * {{{ + * import scala.annotation._, elidable._ + * object Test extends App { + * def expensiveComputation(): Int = { Thread.sleep(1000) ; 172 } + * + * @elidable(WARNING) def warning(msg: String) = println(msg) + * @elidable(FINE) def debug(msg: String) = println(msg) + * @elidable(FINE) def computedValue = expensiveComputation() + * + * warning("Warning! Danger! Warning!") + * debug("Debug! Danger! Debug!") + * println("I computed a value: " + computedValue) + * } + * % scalac example.scala && scala Test + * Warning! Danger! Warning! + * Debug! Danger! Debug! + * I computed a value: 172 + * + * // INFO lies between WARNING and FINE + * % scalac -Xelide-below INFO example.scala && scala Test + * Warning! Danger! Warning! + * I computed a value: 0 + * }}} + * + * Note that only concrete methods can be marked `@elidable`. A non-annotated method + * is not elided, even if it overrides / implements a method that has the annotation. + * + * Also note that the static type determines which annotations are considered: + * + * {{{ + * import scala.annotation._, elidable._ + * class C { @elidable(0) def f(): Unit = ??? } + * object O extends C { override def f(): Unit = println("O.f") } + * object Test extends App { + * O.f() // not elided + * (O: C).f() // elided if compiled with `-Xelide-below 1` + * } + * }}} + * + * Note for Scala 3 users: + * If you're using Scala 3, the annotation exists since Scala 3 uses the Scala 2 + * standard library, but it's unsupported by the Scala 3 compiler. Instead, to + * achieve the same result you'd want to utilize the `inline if` feature to + * introduce behavior that makes a method de facto elided at compile-time. + * {{{ + * type LogLevel = Int + * + * object LogLevel: + * inline val Info = 0 + * inline val Warn = 1 + * inline val Debug = 2 + * + * inline val appLogLevel = LogLevel.Warn + * + * inline def log(msg: String, inline level: LogLevel): Unit = + * inline if (level <= appLogLevel) then println(msg) + * + * log("Warn log", LogLevel.Warn) + * + * log("Debug log", LogLevel. Debug) + * }}} + */ +final class elidable(final val level: Int) extends scala.annotation.ConstantAnnotation + +/** This useless appearing code was necessary to allow people to use + * named constants for the elidable annotation. This is what it takes + * to convince the compiler to fold the constants: otherwise when it's + * time to check an elision level it's staring at a tree like + * {{{ + * (Select(Level, Select(FINEST, Apply(intValue, Nil)))) + * }}} + * instead of the number `300`. + */ +object elidable { + /** The levels `ALL` and `OFF` are confusing in this context because + * the sentiment being expressed when using the annotation is at cross + * purposes with the one being expressed via `-Xelide-below`. This + * confusion reaches its zenith at level `OFF`, where the annotation means + * ''never elide this method'' but `-Xelide-below OFF` is how you would + * say ''elide everything possible''. + * + * With no simple remedy at hand, the issue is now at least documented, + * and aliases `MAXIMUM` and `MINIMUM` are offered. + */ + final val ALL = Int.MinValue // Level.ALL.intValue() + final val FINEST = 300 // Level.FINEST.intValue() + final val FINER = 400 // Level.FINER.intValue() + final val FINE = 500 // Level.FINE.intValue() + final val CONFIG = 700 // Level.CONFIG.intValue() + final val INFO = 800 // Level.INFO.intValue() + final val WARNING = 900 // Level.WARNING.intValue() + final val SEVERE = 1000 // Level.SEVERE.intValue() + final val OFF = Int.MaxValue // Level.OFF.intValue() + + // a couple aliases for the confusing ALL and OFF + final val MAXIMUM = OFF + final val MINIMUM = ALL + + // and we can add a few of our own + final val ASSERTION = 2000 // we should make this more granular + + // for command line parsing so we can use names or ints + val byName: Map[String, Int] = Map( + "FINEST" -> FINEST, + "FINER" -> FINER, + "FINE" -> FINE, + "CONFIG" -> CONFIG, + "INFO" -> INFO, + "WARNING" -> WARNING, + "SEVERE" -> SEVERE, + "ASSERTION" -> ASSERTION, + "ALL" -> ALL, + "OFF" -> OFF, + "MAXIMUM" -> MAXIMUM, + "MINIMUM" -> MINIMUM + ) +} diff --git a/library/src/scala/annotation/implicitAmbiguous.scala b/library/src/scala/annotation/implicitAmbiguous.scala new file mode 100644 index 000000000000..1d33d329272d --- /dev/null +++ b/library/src/scala/annotation/implicitAmbiguous.scala @@ -0,0 +1,44 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** + * To customize the error message that's emitted when an implicit search finds + * multiple ambiguous values, annotate at least one of the implicit values + * `@implicitAmbiguous`. Assuming the implicit value is a method with type + * parameters `X1,..., XN`, the error message will be the result of replacing + * all occurrences of `\${Xi}` in the string `msg` with the string representation + * of the corresponding type argument `Ti`. + * + * If more than one `@implicitAmbiguous` annotation is collected, the compiler is + * free to pick any of them to display. + * + * Nice errors can direct users to fix imports or even tell them why code + * intentionally doesn't compile. + * + * {{{ + * trait =!=[C, D] + * + * implicit def neq[E, F] : E =!= F = null + * + * @annotation.implicitAmbiguous("Could not prove \${J} =!= \${J}") + * implicit def neqAmbig1[G, H, J] : J =!= J = null + * implicit def neqAmbig2[I] : I =!= I = null + * + * implicitly[Int =!= Int] + * }}} + */ +@meta.getter +final class implicitAmbiguous(msg: String) extends scala.annotation.ConstantAnnotation diff --git a/library/src/scala/annotation/implicitNotFound.scala b/library/src/scala/annotation/implicitNotFound.scala new file mode 100644 index 000000000000..cc12beb7d701 --- /dev/null +++ b/library/src/scala/annotation/implicitNotFound.scala @@ -0,0 +1,58 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** + * To customize the error message that's emitted when an implicit of type + * `C[T1,..., TN]` cannot be found, annotate the class `C` with `@implicitNotFound`. + * Assuming `C` has type parameters `X1, ..., XN`, the error message will be the + * result of replacing all occurrences of `\${Xi}` in the string `msg` with the + * string representation of the corresponding type argument `Ti`. + * The annotation is effectively inherited by subtypes if they are not annotated. + * + * The annotation can also be attached to implicit parameters. In this case, `\${Xi}` + * can refer to type parameters in the current scope. The `@implicitNotFound` message + * on the parameter takes precedence over the one on the parameter's type. + * + * {{{ + * import scala.annotation.implicitNotFound + * + * @implicitNotFound("Could not find an implicit C[\${T}, \${U}]") + * class C[T, U] + * + * class K[A] { + * def m[B](implicit c: C[List[A], B]) = 0 + * def n[B](implicit @implicitNotFound("Specific message for C of list of \${A} and \${B}") c: C[List[A], B]) = 1 + * } + * + * object Test { + * val k = new K[Int] + * k.m[String] + * k.n[String] + * } + * }}} + * + * The compiler issues the following error messages: + * + *
+ * Test.scala:13: error: Could not find an implicit C[List[Int], String]
+ *   k.m[String]
+ *      ^
+ * Test.scala:14: error: Specific message for C of list of Int and String
+ *   k.n[String]
+ *      ^
+ * 
+ */ +final class implicitNotFound(msg: String) extends scala.annotation.ConstantAnnotation diff --git a/library/src/scala/annotation/internal/$into.scala b/library/src/scala/annotation/internal/$into.scala index 4d8788724e25..bad0e399d389 100644 --- a/library/src/scala/annotation/internal/$into.scala +++ b/library/src/scala/annotation/internal/$into.scala @@ -1,15 +1,12 @@ package scala.annotation.internal import annotation.experimental -/** An internal annotation on (part of) a parameter type that allows implicit conversions - * for its arguments. The publicly visible `into` annotation in the parent package - * `annotation` gets mapped to `$into` by the compiler in all places where - * conversions should be allowed. The reason for the split into two annotations - * is that `annotation.into` is given in source code and may propagate in unspecified - * ways through type inference. By contrast `$into` is constrained to occur only - * on parameters of method types. This makes implicit conversion insertion - * predictable and independent of the un-specified aspects of type inference. +/** An internal annotation on (part of) a parameter type that serves as a marker where + * the original type was of the form `into[T]`. These annotated types are mapped back + * to `into[T]` types when forming a method types from the parameter types. The idea is + * that `T @$into` is equivalent to `T`, whereas `into[T]` is only a known supertype of + * `T`. Hence, we don't need to use `.underlying` to go from an into type to its + * underlying type in the types of local parameters. */ @experimental -class $into() extends annotation.StaticAnnotation - +class $into extends annotation.StaticAnnotation \ No newline at end of file diff --git a/library/src/scala/annotation/internal/onlyCapability.scala b/library/src/scala/annotation/internal/onlyCapability.scala new file mode 100644 index 000000000000..6eaa72d45dcc --- /dev/null +++ b/library/src/scala/annotation/internal/onlyCapability.scala @@ -0,0 +1,8 @@ +package scala.annotation +package internal + +/** An annotation that represents a capability `c.only[T]`, + * encoded as `x.type @onlyCapability[T]` + */ +class onlyCapability[T] extends StaticAnnotation + diff --git a/library/src/scala/annotation/into.scala b/library/src/scala/annotation/into.scala deleted file mode 100644 index 70a53ff9478d..000000000000 --- a/library/src/scala/annotation/into.scala +++ /dev/null @@ -1,10 +0,0 @@ -package scala.annotation -import annotation.experimental - -/** An annotation on (part of) a parameter type that allows implicit conversions - * for its arguments. The `into` modifier on parameter types in Scala 3 is - * mapped to this annotation. The annotation is intended to be used directly in - * Scala 2 sources only. For Scala 3, the `into` modifier should be preferred. - */ -@experimental -class into() extends annotation.StaticAnnotation diff --git a/library/src/scala/annotation/meta/beanGetter.scala b/library/src/scala/annotation/meta/beanGetter.scala new file mode 100644 index 000000000000..fcd12d2f679f --- /dev/null +++ b/library/src/scala/annotation/meta/beanGetter.scala @@ -0,0 +1,20 @@ +/* + * 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.annotation.meta + +import scala.language.`2.13` + +/** + * Consult the documentation in package [[scala.annotation.meta]]. + */ +final class beanGetter extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/meta/beanSetter.scala b/library/src/scala/annotation/meta/beanSetter.scala new file mode 100644 index 000000000000..3951ccf0da90 --- /dev/null +++ b/library/src/scala/annotation/meta/beanSetter.scala @@ -0,0 +1,20 @@ +/* + * 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.annotation.meta + +import scala.language.`2.13` + +/** + * Consult the documentation in package [[scala.annotation.meta]]. + */ +final class beanSetter extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/meta/companionClass.scala b/library/src/scala/annotation/meta/companionClass.scala new file mode 100644 index 000000000000..a1417e3808d9 --- /dev/null +++ b/library/src/scala/annotation/meta/companionClass.scala @@ -0,0 +1,24 @@ +/* + * 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.annotation.meta + +import scala.language.`2.13` + +/** + * When defining an implicit class, the Scala compiler creates an implicit + * conversion method for it. Annotations `@companionClass` and `@companionMethod` + * control where an annotation on the implicit class will go. By default, annotations + * on an implicit class end up only on the class. + * + */ +final class companionClass extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/meta/companionMethod.scala b/library/src/scala/annotation/meta/companionMethod.scala new file mode 100644 index 000000000000..746c8bc73b60 --- /dev/null +++ b/library/src/scala/annotation/meta/companionMethod.scala @@ -0,0 +1,24 @@ +/* + * 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.annotation.meta + +import scala.language.`2.13` + +/** + * When defining an implicit class, the Scala compiler creates an implicit + * conversion method for it. Annotations `@companionClass` and `@companionMethod` + * control where an annotation on the implicit class will go. By default, annotations + * on an implicit class end up only on the class. + * + */ +final class companionMethod extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/meta/companionObject.scala b/library/src/scala/annotation/meta/companionObject.scala new file mode 100644 index 000000000000..91bc7c300ac1 --- /dev/null +++ b/library/src/scala/annotation/meta/companionObject.scala @@ -0,0 +1,21 @@ +/* + * 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.annotation.meta + +import scala.language.`2.13` + +/** + * Currently unused; intended as an annotation target for classes such as case classes + * that automatically generate a companion object + */ +final class companionObject extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/meta/defaultArg.scala b/library/src/scala/annotation/meta/defaultArg.scala new file mode 100644 index 000000000000..2cbc3aa2af67 --- /dev/null +++ b/library/src/scala/annotation/meta/defaultArg.scala @@ -0,0 +1,32 @@ +/* + * 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.annotation +package meta + +import scala.language.`2.13` + +/** + * This internal meta annotation is used by the compiler to support default annotation arguments. + * + * For an annotation definition `class ann(x: Int = defaultExpr) extends Annotation`, the compiler adds + * `@defaultArg(defaultExpr)` to the parameter `x`. This causes the syntax tree of `defaultExpr` to be + * stored in the classfile. + * + * When using a default annotation argument, the compiler can recover the syntax tree and insert it in the + * `AnnotationInfo`. + * + * For details, see `scala.reflect.internal.AnnotationInfos.AnnotationInfo`. + */ +@meta.param class defaultArg(arg: Any) extends StaticAnnotation { + def this() = this(null) +} diff --git a/library/src/scala/annotation/meta/field.scala b/library/src/scala/annotation/meta/field.scala new file mode 100644 index 000000000000..43c70f93e255 --- /dev/null +++ b/library/src/scala/annotation/meta/field.scala @@ -0,0 +1,20 @@ +/* + * 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.annotation.meta + +import scala.language.`2.13` + +/** + * Consult the documentation in package [[scala.annotation.meta]]. + */ +final class field extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/meta/getter.scala b/library/src/scala/annotation/meta/getter.scala new file mode 100644 index 000000000000..1d7e0f5db5b5 --- /dev/null +++ b/library/src/scala/annotation/meta/getter.scala @@ -0,0 +1,20 @@ +/* + * 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.annotation.meta + +import scala.language.`2.13` + +/** + * Consult the documentation in package [[scala.annotation.meta]]. + */ +final class getter extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/meta/languageFeature.scala b/library/src/scala/annotation/meta/languageFeature.scala new file mode 100644 index 000000000000..83a96c65b940 --- /dev/null +++ b/library/src/scala/annotation/meta/languageFeature.scala @@ -0,0 +1,20 @@ +/* + * 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.annotation.meta + +import scala.language.`2.13` + +/** + * An annotation giving particulars for a language feature in object `scala.language`. + */ +final class languageFeature(feature: String, enableRequired: Boolean) extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/meta/package.scala b/library/src/scala/annotation/meta/package.scala new file mode 100644 index 000000000000..ddb21d208d53 --- /dev/null +++ b/library/src/scala/annotation/meta/package.scala @@ -0,0 +1,82 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** + * When defining a field, the Scala compiler creates up to four accessors + * for it: a getter, a setter, and if the field is annotated with + * `@BeanProperty`, a bean getter and a bean setter. + * + * For instance in the following class definition + * + * {{{ + * class C(@myAnnot @BeanProperty var c: Int) + * }}} + * + * there are six entities which can carry the annotation `@myAnnot`: the + * constructor parameter, the generated field and the four accessors. + * + * By default, annotations on (`val`-, `var`- or plain) constructor parameters + * end up on the parameter, not on any other entity. Annotations on fields + * by default only end up on the field. + * + * The meta-annotations in package `scala.annotation.meta` are used + * to control where annotations on fields and class parameters are copied. + * This is done by annotating either the annotation type or the annotation + * class with one or several of the meta-annotations in this package. + * + * ==Annotating the annotation type== + * + * The target meta-annotations can be put on the annotation type when + * instantiating the annotation. In the following example, the annotation + * `@Id` will be added only to the bean getter `getX`. + * + * {{{ + * import javax.persistence.Id + * class A { + * @(Id @beanGetter) @BeanProperty val x = 0 + * } + * }}} + * + * In order to annotate the field as well, the meta-annotation `@field` + * would need to be added. + * + * The syntax can be improved using a type alias: + * + * {{{ + * object ScalaJPA { + * type Id = javax.persistence.Id @beanGetter + * } + * import ScalaJPA.Id + * class A { + * @Id @BeanProperty val x = 0 + * } + * }}} + * + * ==Annotating the annotation class== + * + * For annotations defined in Scala, a default target can be specified + * in the annotation class itself, for example + * + * {{{ + * @getter + * class myAnnotation extends Annotation + * }}} + * + * This only changes the default target for the annotation `myAnnotation`. + * When instantiating the annotation, the target can still be specified + * as described in the last section. + */ +package object meta diff --git a/library/src/scala/annotation/meta/param.scala b/library/src/scala/annotation/meta/param.scala new file mode 100644 index 000000000000..725d89ade8f5 --- /dev/null +++ b/library/src/scala/annotation/meta/param.scala @@ -0,0 +1,20 @@ +/* + * 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.annotation.meta + +import scala.language.`2.13` + +/** + * Consult the documentation in package [[scala.annotation.meta]]. + */ +final class param extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/meta/setter.scala b/library/src/scala/annotation/meta/setter.scala new file mode 100644 index 000000000000..5db7cae2da72 --- /dev/null +++ b/library/src/scala/annotation/meta/setter.scala @@ -0,0 +1,20 @@ +/* + * 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.annotation.meta + +import scala.language.`2.13` + +/** + * Consult the documentation in package [[scala.annotation.meta]]. + */ +final class setter extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/meta/superArg.scala b/library/src/scala/annotation/meta/superArg.scala new file mode 100644 index 000000000000..6dd22135990a --- /dev/null +++ b/library/src/scala/annotation/meta/superArg.scala @@ -0,0 +1,36 @@ +/* + * 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.annotation +package meta + +import scala.language.`2.13` + +/** + * This internal annotation encodes arguments passed to annotation superclasses. Example: + * + * {{{ + * class a(x: Int) extends Annotation + * class b extends a(42) // the compiler adds `@superArg("x", 42)` to class b + * }}} + */ +class superArg(p: String, v: Any) extends StaticAnnotation + +/** + * This internal annotation encodes arguments passed to annotation superclasses. Example: + * + * {{{ + * class a(x: Int) extends Annotation + * class b(y: Int) extends a(y) // the compiler adds `@superFwdArg("x", "y")` to class b + * }}} + */ +class superFwdArg(p: String, n: String) extends StaticAnnotation diff --git a/library/src/scala/annotation/migration.scala b/library/src/scala/annotation/migration.scala new file mode 100644 index 000000000000..8daa003eba96 --- /dev/null +++ b/library/src/scala/annotation/migration.scala @@ -0,0 +1,32 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** + * An annotation that marks a member as having changed semantics + * between versions. This is intended for methods which for one + * reason or another retain the same name and type signature, + * but some aspect of their behavior is different. An illustrative + * examples is Stack.iterator, which reversed from LIFO to FIFO + * order between Scala 2.7 and 2.8. + * + * @param message A message describing the change, which is emitted + * by the compiler if the flag `-Xmigration` indicates a version + * prior to the changedIn version. + * + * @param changedIn The version, in which the behaviour change was + * introduced. + */ +private[scala] final class migration(message: String, changedIn: String) extends scala.annotation.ConstantAnnotation diff --git a/library/src/scala/annotation/nowarn.scala b/library/src/scala/annotation/nowarn.scala new file mode 100644 index 000000000000..af1cebb42b0a --- /dev/null +++ b/library/src/scala/annotation/nowarn.scala @@ -0,0 +1,44 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** An annotation for local warning suppression. + * + * The optional `value` parameter allows selectively silencing messages. See `-Wconf:help` for help + * writing a message filter expression, or use `@nowarn("verbose")` / `@nowarn("v")` to display message + * filters applicable to a specific warning. + * + * Examples: + * + * {{{ + * def f = { + * 1: @nowarn // don't warn "a pure expression does nothing in statement position" + * 2 + * } + * + * // show the warning, plus the applicable @nowarn / Wconf filters ("cat=other-pure-statement", ...) + * @nowarn("v") def f = { 1; 2 } + * + * @nowarn def f = { 1; deprecated() } // don't warn + * + * @nowarn("msg=pure expression does nothing") + * def f = { 1; deprecated() } // show deprecation warning + * }}} + * + * To ensure that a `@nowarn` annotation actually suppresses a warning, enable `-Xlint:unused` or `-Wunused:nowarn`. + * The unused annotation warning is emitted in category `unused-nowarn` and can be selectively managed + * using `-Wconf:cat=unused-nowarn:s`. + */ +class nowarn(value: String = "") extends ConstantAnnotation diff --git a/library/src/scala/annotation/retains.scala b/library/src/scala/annotation/retains.scala index 9c4af7f2336d..05896b117796 100644 --- a/library/src/scala/annotation/retains.scala +++ b/library/src/scala/annotation/retains.scala @@ -2,7 +2,7 @@ package scala.annotation /** An annotation that indicates capture of a set of references under capture checking. * - * T @retains(x, y, z) + * T @retains[x.type | y.type | z.type] * * is the internal representation used for the capturing type * @@ -12,20 +12,11 @@ package scala.annotation * non-standard capturing type syntax. */ @experimental -class retains(xs: (Any@retainsArg)*) extends annotation.StaticAnnotation +class retains[Elems] extends annotation.StaticAnnotation -/** Equivalent in meaning to `@retains(cap)`, but consumes less bytecode. +/** Equivalent in meaning to `@retains[cap.type]`, but consumes less bytecode. */ @experimental -class retainsCap() extends annotation.StaticAnnotation +class retainsCap extends annotation.StaticAnnotation // This special case is needed to be able to load standard library modules without // cyclic reference errors. Specifically, load sequences involving IterableOnce. - -/** Internal use, only for parameters of `retains` and `retainsByName`. - */ -@experimental -class retainsArg extends annotation.StaticAnnotation - // This annotation prevents argument references to retains and retainsByName from being - // augmented with explicit arguments. That's unsound in general, but necessary - // since a captureRef could have an impure context function type, A ?=> B, but - // we still need to have the unapplied captureRef in the annotation. diff --git a/library/src/scala/annotation/retainsByName.scala b/library/src/scala/annotation/retainsByName.scala index e6e3dafcb752..acc8ec664af3 100644 --- a/library/src/scala/annotation/retainsByName.scala +++ b/library/src/scala/annotation/retainsByName.scala @@ -2,5 +2,5 @@ package scala.annotation /** An annotation that indicates capture of an enclosing by-name type */ -@experimental class retainsByName(xs: (Any@retainsArg)*) extends annotation.StaticAnnotation +@experimental class retainsByName[Elems] extends annotation.StaticAnnotation diff --git a/library/src/scala/annotation/showAsInfix.scala b/library/src/scala/annotation/showAsInfix.scala new file mode 100644 index 000000000000..89cbd623e76c --- /dev/null +++ b/library/src/scala/annotation/showAsInfix.scala @@ -0,0 +1,41 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** + * This annotation configures how Scala prints two-parameter generic types. + * + * By default, types with symbolic names are printed infix; while types without + * them are printed using the regular generic type syntax. + * + * Example of usage: + {{{ + scala> class Map[T, U] + defined class Map + + scala> def foo: Int Map Int = ??? + foo: Map[Int,Int] + + scala> @showAsInfix class Map[T, U] + defined class Map + + scala> def foo: Int Map Int = ??? + foo: Int Map Int + }}} + * + * @param enabled whether to show this type as an infix type operator. + */ +@deprecatedInheritance("Scheduled for being final in the future", "2.13.0") +class showAsInfix(enabled: Boolean = true) extends annotation.StaticAnnotation diff --git a/library/src/scala/annotation/stableNull.scala b/library/src/scala/annotation/stableNull.scala new file mode 100644 index 000000000000..e2ebac72fce5 --- /dev/null +++ b/library/src/scala/annotation/stableNull.scala @@ -0,0 +1,10 @@ +package scala.annotation + +/** An annotation that can be used to mark a mutable field as trackable for nullability. + * With explicit nulls, a normal mutable field cannot be tracked for nullability by flow typing, + * since it can be updated to a null value at the same time. + * This annotation will force the compiler to track the field for nullability, as long as the + * prefix is a stable path. + * See `tests/explicit-nulls/pos/force-track-var-fields.scala` for an example. + */ +private[scala] final class stableNull extends StaticAnnotation diff --git a/library/src/scala/annotation/strictfp.scala b/library/src/scala/annotation/strictfp.scala new file mode 100644 index 000000000000..fd558390abfb --- /dev/null +++ b/library/src/scala/annotation/strictfp.scala @@ -0,0 +1,21 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** If this annotation is present on a method or its enclosing class, + * the strictfp flag will be emitted. + */ +@deprecatedInheritance("Scheduled for being final in the future", "2.13.0") +class strictfp extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/switch.scala b/library/src/scala/annotation/switch.scala new file mode 100644 index 000000000000..590c07bc94ba --- /dev/null +++ b/library/src/scala/annotation/switch.scala @@ -0,0 +1,35 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** An annotation to be applied to a match expression. If present, + * the compiler will verify that the match has been compiled to a + * [[https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.10 tableswitch or lookupswitch]] + * and issue a warning if it instead compiles into a series of conditional expressions. + * Example usage: +{{{ + val Constant = 'Q' + def tokenMe(ch: Char) = (ch: @switch) match { + case ' ' | '\t' | '\n' => 1 + case 'A' | 'Z' | '$' => 2 + case '5' | Constant => 3 // a non-literal may prevent switch generation: this would not compile + case _ => 4 + } +}}} + * + * Note: for pattern matches with one or two cases, the compiler generates jump instructions. + * Annotating such a match with `@switch` does not issue any warning. + */ +final class switch extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/tailrec.scala b/library/src/scala/annotation/tailrec.scala new file mode 100644 index 000000000000..7d5fa19ef043 --- /dev/null +++ b/library/src/scala/annotation/tailrec.scala @@ -0,0 +1,23 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** A method annotation which verifies that the method will be compiled + * with tail call optimization. + * + * If it is present, the compiler will issue an error if the method cannot + * be optimized into a loop. + */ +final class tailrec extends StaticAnnotation diff --git a/library/src/scala/annotation/unchecked/uncheckedStable.scala b/library/src/scala/annotation/unchecked/uncheckedStable.scala new file mode 100644 index 000000000000..585c5a13ef37 --- /dev/null +++ b/library/src/scala/annotation/unchecked/uncheckedStable.scala @@ -0,0 +1,22 @@ +/* + * 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.annotation.unchecked + +import scala.language.`2.13` +import scala.annotation.meta.{field, getter} + +/** An annotation for values that are assumed to be stable even though their + * types are volatile. + */ +@getter @field +final class uncheckedStable extends scala.annotation.StaticAnnotation {} diff --git a/library/src/scala/annotation/unchecked/uncheckedVariance.scala b/library/src/scala/annotation/unchecked/uncheckedVariance.scala new file mode 100644 index 000000000000..fb426dbf877c --- /dev/null +++ b/library/src/scala/annotation/unchecked/uncheckedVariance.scala @@ -0,0 +1,19 @@ +/* + * 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.annotation.unchecked + +import scala.language.`2.13` + +/** An annotation for type arguments for which one wants to suppress variance checking. + */ +final class uncheckedVariance extends scala.annotation.StaticAnnotation {} diff --git a/library/src/scala/annotation/unspecialized.scala b/library/src/scala/annotation/unspecialized.scala new file mode 100644 index 000000000000..616542305fbc --- /dev/null +++ b/library/src/scala/annotation/unspecialized.scala @@ -0,0 +1,22 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** A method annotation which suppresses the creation of + * additional specialized forms based on enclosing specialized + * type parameters. + */ +@deprecatedInheritance("Scheduled for being final in the future", "2.13.0") +class unspecialized extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/unused.scala b/library/src/scala/annotation/unused.scala new file mode 100644 index 000000000000..b2fdd2034fef --- /dev/null +++ b/library/src/scala/annotation/unused.scala @@ -0,0 +1,28 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** Mark an element unused for a given context. + * + * Unused warnings are suppressed for elements known to be unused. + * + * For example, a method parameter may be marked `@unused` + * because the method is designed to be overridden by + * an implementation that does use the parameter. + */ +@meta.getter @meta.setter +class unused(message: String) extends StaticAnnotation { + def this() = this("") +} diff --git a/library/src/scala/annotation/varargs.scala b/library/src/scala/annotation/varargs.scala new file mode 100644 index 000000000000..967e2df00f6f --- /dev/null +++ b/library/src/scala/annotation/varargs.scala @@ -0,0 +1,21 @@ +/* + * 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.annotation + +import scala.language.`2.13` + +/** A method annotation which instructs the compiler to generate a + * Java varargs-style forwarder method for interop. This annotation can + * only be applied to methods with repeated parameters. + */ +final class varargs extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/beans/BeanProperty.scala b/library/src/scala/beans/BeanProperty.scala new file mode 100644 index 000000000000..37c6ecf3676b --- /dev/null +++ b/library/src/scala/beans/BeanProperty.scala @@ -0,0 +1,40 @@ +/* + * 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.beans + +import scala.language.`2.13` + +import scala.annotation.meta.{beanGetter, beanSetter, field} + +/** When attached to a field, this annotation adds a setter and a getter + * method following the Java Bean convention. For example: + * {{{ + * @BeanProperty + * var status = "" + * }}} + * adds the following methods to the class: + * {{{ + * def setStatus(s: String): Unit = { this.status = s } + * def getStatus(): String = this.status + * }}} + * For fields of type `Boolean`, if you need a getter named `isStatus`, + * use the `scala.beans.BooleanBeanProperty` annotation instead. + * + * In Scala 2, the added methods are visible from both Scala and Java. + * + * In Scala 3, that has changed. The added methods are only visible from + * Java (including via Java reflection). + */ +@field @beanGetter @beanSetter +@deprecatedInheritance("Scheduled for being final in the future", "2.13.0") +class BeanProperty extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/beans/BooleanBeanProperty.scala b/library/src/scala/beans/BooleanBeanProperty.scala new file mode 100644 index 000000000000..657b17d564bb --- /dev/null +++ b/library/src/scala/beans/BooleanBeanProperty.scala @@ -0,0 +1,24 @@ +/* + * 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.beans + +import scala.language.`2.13` +import scala.annotation.meta.{beanGetter, beanSetter, field} + +/** This annotation has the same functionality as + * `scala.beans.BeanProperty`, but the generated Bean getter will be + * named `isFieldName` instead of `getFieldName`. + */ +@field @beanGetter @beanSetter +@deprecatedInheritance("Scheduled for being final in the future", "2.13.0") +class BooleanBeanProperty extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/caps/package.scala b/library/src/scala/caps/package.scala index 7f8b184c1c95..2f466af166e3 100644 --- a/library/src/scala/caps/package.scala +++ b/library/src/scala/caps/package.scala @@ -25,19 +25,32 @@ import annotation.{experimental, compileTimeOnly, retainsCap} @experimental trait Capability extends Any +/** A marker trait for classifier capabilities that can appear in `.only` + * qualifiers. Capability classes directly extending `Classifier` are treated + * as classifier capbilities + */ +@experimental +trait Classifier + /** The universal capture reference. */ @experimental object cap extends Capability /** Marker trait for classes with methods that requires an exclusive reference. */ @experimental -trait Mutable extends Capability +trait Mutable extends Capability, Classifier /** Marker trait for capabilities that can be safely shared in a concurrent context. * During separation checking, shared capabilities are not taken into account. */ @experimental -trait SharedCapability extends Capability +trait Sharable extends Capability, Classifier + +/** Base trait for capabilities that capture some continuation or return point in + * the stack. Examples are exceptions, labels, Async, CanThrow. + */ +@experimental +trait Control extends Sharable, Classifier /** Carrier trait for capture set type parameters */ @experimental @@ -88,24 +101,6 @@ 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 - - /** Read-only capabilities x.rd which appear as terms in @retains annotations are encoded - * as `caps.readOnlyCapability(x)`. When converted to CaptureRef types in capture sets - * they are represented as `x.type @annotation.internal.readOnlyCapability`. - */ - extension (x: Any) def readOnlyCapability: Any = x - /** An internal annotation placed on a refinement created by capture checking. * Refinements with this annotation unconditionally override any * info from the parent type, so no intersection needs to be formed. @@ -122,6 +117,21 @@ object internal: */ final class rootCapability extends annotation.StaticAnnotation + /** An annotation used internally to mark a function type that was + * converted to a dependent function type during setup of inferred types. + * Such function types should not map roots to result variables. + */ + final class inferredDepFun extends annotation.StaticAnnotation + + /** An erasedValue issued internally by the compiler. Unlike the + * user-accessible compiletime.erasedValue, this version is assumed + * to be a pure expression, hence capability safe. The compiler generates this + * version only where it is known that a value can be generated. + */ + def erasedValue[T]: T = ??? + +end internal + @experimental object unsafe: /** @@ -145,4 +155,11 @@ object unsafe: */ def unsafeAssumeSeparate(op: Any): op.type = op + /** An unsafe variant of erasedValue that can be used as an escape hatch. Unlike the + * user-accessible compiletime.erasedValue, this version is assumed + * to be a pure expression, hence capability safe. But there is no proof + * of realizability, hence it is unsafe. + */ + def unsafeErasedValue[T]: T = ??? + end unsafe diff --git a/library/src/scala/collection/ArrayOps.scala b/library/src/scala/collection/ArrayOps.scala new file mode 100644 index 000000000000..c7f5ff67bd73 --- /dev/null +++ b/library/src/scala/collection/ArrayOps.scala @@ -0,0 +1,1667 @@ +/* + * 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 collection + +import scala.language.`2.13` +import java.lang.Math.{max, min} +import java.util.Arrays + +import scala.Predef.{ // unimport all array-related implicit conversions to avoid triggering them accidentally + genericArrayOps => _, + booleanArrayOps => _, + byteArrayOps => _, + charArrayOps => _, + doubleArrayOps => _, + floatArrayOps => _, + intArrayOps => _, + longArrayOps => _, + refArrayOps => _, + shortArrayOps => _, + unitArrayOps => _, + genericWrapArray => _, + wrapRefArray => _, + wrapIntArray => _, + wrapDoubleArray => _, + wrapLongArray => _, + wrapFloatArray => _, + wrapCharArray => _, + wrapByteArray => _, + wrapShortArray => _, + wrapBooleanArray => _, + wrapUnitArray => _, + wrapString => _, + copyArrayToImmutableIndexedSeq => _, + _ +} +import scala.collection.Stepper.EfficientSplit +import scala.collection.generic.CommonErrors +import scala.collection.immutable.Range +import scala.collection.mutable.ArrayBuilder +import scala.math.Ordering +import scala.reflect.ClassTag +import scala.util.Sorting + +object ArrayOps { + + @SerialVersionUID(3L) + private class ArrayView[A](xs: Array[A]) extends AbstractIndexedSeqView[A] { + def length = xs.length + def apply(n: Int) = xs(n) + override def toString: String = immutable.ArraySeq.unsafeWrapArray(xs).mkString("ArrayView(", ", ", ")") + } + + /** A lazy filtered array. No filtering is applied until one of `foreach`, `map` or `flatMap` is called. */ + class WithFilter[A](p: A => Boolean, xs: Array[A]) { + + /** Apply `f` to each element for its side effects. + * Note: [U] parameter needed to help scalac's type inference. + */ + def foreach[U](f: A => U): Unit = { + val len = xs.length + var i = 0 + while(i < len) { + val x = xs(i) + if(p(x)) f(x) + i += 1 + } + } + + /** Builds a new array by applying a function to all elements of this array. + * + * @param f the function to apply to each element. + * @tparam B the element type of the returned array. + * @return a new array resulting from applying the given function + * `f` to each element of this array and collecting the results. + */ + def map[B: ClassTag](f: A => B): Array[B] = { + val b = ArrayBuilder.make[B] + var i = 0 + while (i < xs.length) { + val x = xs(i) + if(p(x)) b += f(x) + i = i + 1 + } + b.result() + } + + /** Builds a new array by applying a function to all elements of this array + * and using the elements of the resulting collections. + * + * @param f the function to apply to each element. + * @tparam B the element type of the returned array. + * @return a new array resulting from applying the given collection-valued function + * `f` to each element of this array and concatenating the results. + */ + def flatMap[B: ClassTag](f: A => IterableOnce[B]): Array[B] = { + val b = ArrayBuilder.make[B] + var i = 0 + while(i < xs.length) { + val x = xs(i) + if(p(x)) b ++= f(xs(i)) + i += 1 + } + b.result() + } + + def flatMap[BS, B](f: A => BS)(implicit asIterable: BS => Iterable[B], m: ClassTag[B]): Array[B] = + flatMap[B](x => asIterable(f(x))) + + /** Creates a new non-strict filter which combines this filter with the given predicate. */ + def withFilter(q: A => Boolean): WithFilter[A] = new WithFilter[A](a => p(a) && q(a), xs) + } + + @SerialVersionUID(3L) + private[collection] final class ArrayIterator[@specialized(Specializable.Everything) A](xs: Array[A]) extends AbstractIterator[A] with Serializable { + private[this] var pos = 0 + private[this] val len = xs.length + override def knownSize: Int = len - pos + def hasNext: Boolean = pos < len + def next(): A = { + if (pos >= xs.length) Iterator.empty.next() + val r = xs(pos) + pos += 1 + r + } + override def drop(n: Int): Iterator[A] = { + if (n > 0) { + val newPos = pos + n + pos = + if (newPos < 0 /* overflow */) len + else Math.min(len, newPos) + } + this + } + } + + @SerialVersionUID(3L) + private final class ReverseIterator[@specialized(Specializable.Everything) A](xs: Array[A]) extends AbstractIterator[A] with Serializable { + private[this] var pos = xs.length-1 + def hasNext: Boolean = pos >= 0 + def next(): A = { + if (pos < 0) Iterator.empty.next() + val r = xs(pos) + pos -= 1 + r + } + + override def drop(n: Int): Iterator[A] = { + if (n > 0) pos = Math.max( -1, pos - n) + this + } + } + + @SerialVersionUID(3L) + private final class GroupedIterator[A](xs: Array[A], groupSize: Int) extends AbstractIterator[Array[A]] with Serializable { + private[this] var pos = 0 + def hasNext: Boolean = pos < xs.length + def next(): Array[A] = { + if(pos >= xs.length) throw new NoSuchElementException + val r = new ArrayOps(xs).slice(pos, pos+groupSize) + pos += groupSize + r + } + } + + /** The cut-off point for the array size after which we switch from `Sorting.stableSort` to + * an implementation that copies the data to a boxed representation for use with `Arrays.sort`. + */ + private final val MaxStableSortLength = 300 + + /** Avoid an allocation in [[collect]]. */ + private val fallback: Any => Any = _ => fallback +} + +/** This class serves as a wrapper for `Array`s with many of the operations found in + * indexed sequences. Where needed, instances of arrays are implicitly converted + * into this class. There is generally no reason to create an instance explicitly or use + * an `ArrayOps` type. It is better to work with plain `Array` types instead and rely on + * the implicit conversion to `ArrayOps` when calling a method (which does not actually + * allocate an instance of `ArrayOps` because it is a value class). + * + * Neither `Array` nor `ArrayOps` are proper collection types + * (i.e. they do not extend `Iterable` or even `IterableOnce`). `mutable.ArraySeq` and + * `immutable.ArraySeq` serve this purpose. + * + * The difference between this class and `ArraySeq`s is that calling transformer methods such as + * `filter` and `map` will yield an array, whereas an `ArraySeq` will remain an `ArraySeq`. + * + * @tparam A type of the elements contained in this array. + */ +final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { + + @`inline` private[this] implicit def elemTag: ClassTag[A] = ClassTag(xs.getClass.getComponentType) + + /** The size of this array. + * + * @return the number of elements in this array. + */ + @`inline` def size: Int = xs.length + + /** The size of this array. + * + * @return the number of elements in this array. + */ + @`inline` def knownSize: Int = xs.length + + /** Tests whether the array is empty. + * + * @return `true` if the array contains no elements, `false` otherwise. + */ + @`inline` def isEmpty: Boolean = xs.length == 0 + + /** Tests whether the array is not empty. + * + * @return `true` if the array contains at least one element, `false` otherwise. + */ + @`inline` def nonEmpty: Boolean = xs.length != 0 + + /** Selects the first element of this array. + * + * @return the first element of this array. + * @throws NoSuchElementException if the array is empty. + */ + def head: A = if (nonEmpty) xs.apply(0) else throw new NoSuchElementException("head of empty array") + + /** Selects the last element. + * + * @return The last element of this array. + * @throws NoSuchElementException If the array is empty. + */ + def last: A = if (nonEmpty) xs.apply(xs.length-1) else throw new NoSuchElementException("last of empty array") + + /** Optionally selects the first element. + * + * @return the first element of this array if it is nonempty, + * `None` if it is empty. + */ + def headOption: Option[A] = if(isEmpty) None else Some(head) + + /** Optionally selects the last element. + * + * @return the last element of this array$ if it is nonempty, + * `None` if it is empty. + */ + def lastOption: Option[A] = if(isEmpty) None else Some(last) + + /** Compares the size of this array to a test value. + * + * @param otherSize the test value that gets compared with the size. + * @return A value `x` where + * {{{ + * x < 0 if this.size < otherSize + * x == 0 if this.size == otherSize + * x > 0 if this.size > otherSize + * }}} + */ + def sizeCompare(otherSize: Int): Int = Integer.compare(xs.length, otherSize) + + /** Compares the length of this array to a test value. + * + * @param len the test value that gets compared with the length. + * @return A value `x` where + * {{{ + * x < 0 if this.length < len + * x == 0 if this.length == len + * x > 0 if this.length > len + * }}} + */ + def lengthCompare(len: Int): Int = Integer.compare(xs.length, len) + + /** Method mirroring [[SeqOps.sizeIs]] for consistency, except it returns an `Int` + * because `size` is known and comparison is constant-time. + * + * These operations are equivalent to [[sizeCompare(Int) `sizeCompare(Int)`]], and + * allow the following more readable usages: + * + * {{{ + * this.sizeIs < size // this.sizeCompare(size) < 0 + * this.sizeIs <= size // this.sizeCompare(size) <= 0 + * this.sizeIs == size // this.sizeCompare(size) == 0 + * this.sizeIs != size // this.sizeCompare(size) != 0 + * this.sizeIs >= size // this.sizeCompare(size) >= 0 + * this.sizeIs > size // this.sizeCompare(size) > 0 + * }}} + */ + def sizeIs: Int = xs.length + + /** Method mirroring [[SeqOps.lengthIs]] for consistency, except it returns an `Int` + * because `length` is known and comparison is constant-time. + * + * These operations are equivalent to [[lengthCompare(Int) `lengthCompare(Int)`]], and + * allow the following more readable usages: + * + * {{{ + * this.lengthIs < len // this.lengthCompare(len) < 0 + * this.lengthIs <= len // this.lengthCompare(len) <= 0 + * this.lengthIs == len // this.lengthCompare(len) == 0 + * this.lengthIs != len // this.lengthCompare(len) != 0 + * this.lengthIs >= len // this.lengthCompare(len) >= 0 + * this.lengthIs > len // this.lengthCompare(len) > 0 + * }}} + */ + def lengthIs: Int = xs.length + + /** Selects an interval of elements. The returned array is made up + * of all elements `x` which satisfy the invariant: + * {{{ + * from <= indexOf(x) < until + * }}} + * + * @param from the lowest index to include from this array. + * @param until the lowest index to EXCLUDE from this array. + * @return an array containing the elements greater than or equal to + * index `from` extending up to (but not including) index `until` + * of this array. + */ + def slice(from: Int, until: Int): Array[A] = { + import java.util.Arrays.copyOfRange + val lo = max(from, 0) + val hi = min(until, xs.length) + if (hi > lo) { + (((xs: Array[_]): @unchecked) match { + case x: Array[AnyRef] => copyOfRange(x, lo, hi) + case x: Array[Int] => copyOfRange(x, lo, hi) + case x: Array[Double] => copyOfRange(x, lo, hi) + case x: Array[Long] => copyOfRange(x, lo, hi) + case x: Array[Float] => copyOfRange(x, lo, hi) + case x: Array[Char] => copyOfRange(x, lo, hi) + case x: Array[Byte] => copyOfRange(x, lo, hi) + case x: Array[Short] => copyOfRange(x, lo, hi) + case x: Array[Boolean] => copyOfRange(x, lo, hi) + }).asInstanceOf[Array[A]] + } else new Array[A](0) + } + + /** The rest of the array without its first element. */ + def tail: Array[A] = + if(xs.length == 0) throw new UnsupportedOperationException("tail of empty array") else slice(1, xs.length) + + /** The initial part of the array without its last element. */ + def init: Array[A] = + if(xs.length == 0) throw new UnsupportedOperationException("init of empty array") else slice(0, xs.length-1) + + /** Iterates over the tails of this array. The first value will be this + * array and the final one will be an empty array, with the intervening + * values the results of successive applications of `tail`. + * + * @return an iterator over all the tails of this array + */ + def tails: Iterator[Array[A]] = iterateUntilEmpty(xs => new ArrayOps(xs).tail) + + /** Iterates over the inits of this array. The first value will be this + * array and the final one will be an empty array, with the intervening + * values the results of successive applications of `init`. + * + * @return an iterator over all the inits of this array + */ + def inits: Iterator[Array[A]] = iterateUntilEmpty(xs => new ArrayOps(xs).init) + + // A helper for tails and inits. + private[this] def iterateUntilEmpty(f: Array[A] => Array[A]): Iterator[Array[A]] = + Iterator.iterate(xs)(f).takeWhile(x => x.length != 0) ++ Iterator.single(Array.empty[A]) + + /** An array containing the first `n` elements of this array. */ + def take(n: Int): Array[A] = slice(0, n) + + /** The rest of the array without its `n` first elements. */ + def drop(n: Int): Array[A] = slice(n, xs.length) + + /** An array containing the last `n` elements of this array. */ + def takeRight(n: Int): Array[A] = drop(xs.length - max(n, 0)) + + /** The rest of the array without its `n` last elements. */ + def dropRight(n: Int): Array[A] = take(xs.length - max(n, 0)) + + /** Takes longest prefix of elements that satisfy a predicate. + * + * @param p The predicate used to test elements. + * @return the longest prefix of this array whose elements all satisfy + * the predicate `p`. + */ + def takeWhile(p: A => Boolean): Array[A] = { + val i = indexWhere(x => !p(x)) + val hi = if(i < 0) xs.length else i + slice(0, hi) + } + + /** Drops longest prefix of elements that satisfy a predicate. + * + * @param p The predicate used to test elements. + * @return the longest suffix of this array whose first element + * does not satisfy the predicate `p`. + */ + def dropWhile(p: A => Boolean): Array[A] = { + val i = indexWhere(x => !p(x)) + val lo = if(i < 0) xs.length else i + slice(lo, xs.length) + } + + def iterator: Iterator[A] = + ((xs: Any @unchecked) match { + case xs: Array[AnyRef] => new ArrayOps.ArrayIterator(xs) + case xs: Array[Int] => new ArrayOps.ArrayIterator(xs) + case xs: Array[Double] => new ArrayOps.ArrayIterator(xs) + case xs: Array[Long] => new ArrayOps.ArrayIterator(xs) + case xs: Array[Float] => new ArrayOps.ArrayIterator(xs) + case xs: Array[Char] => new ArrayOps.ArrayIterator(xs) + case xs: Array[Byte] => new ArrayOps.ArrayIterator(xs) + case xs: Array[Short] => new ArrayOps.ArrayIterator(xs) + case xs: Array[Boolean] => new ArrayOps.ArrayIterator(xs) + case xs: Array[Unit] => new ArrayOps.ArrayIterator(xs) + case null => throw new NullPointerException + }).asInstanceOf[Iterator[A]] + + def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit = { + import convert.impl._ + val s = (shape.shape: @unchecked) match { + case StepperShape.ReferenceShape => (xs: Any) match { + case bs: Array[Boolean] => new BoxedBooleanArrayStepper(bs, 0, xs.length) + case _ => new ObjectArrayStepper[AnyRef](xs.asInstanceOf[Array[AnyRef ]], 0, xs.length) + } + case StepperShape.IntShape => new IntArrayStepper (xs.asInstanceOf[Array[Int ]], 0, xs.length) + case StepperShape.LongShape => new LongArrayStepper (xs.asInstanceOf[Array[Long ]], 0, xs.length) + case StepperShape.DoubleShape => new DoubleArrayStepper (xs.asInstanceOf[Array[Double ]], 0, xs.length) + case StepperShape.ByteShape => new WidenedByteArrayStepper (xs.asInstanceOf[Array[Byte ]], 0, xs.length) + case StepperShape.ShortShape => new WidenedShortArrayStepper (xs.asInstanceOf[Array[Short ]], 0, xs.length) + case StepperShape.CharShape => new WidenedCharArrayStepper (xs.asInstanceOf[Array[Char ]], 0, xs.length) + case StepperShape.FloatShape => new WidenedFloatArrayStepper (xs.asInstanceOf[Array[Float ]], 0, xs.length) + } + s.asInstanceOf[S with EfficientSplit] + } + + /** Partitions elements in fixed size arrays. + * @see [[scala.collection.Iterator]], method `grouped` + * + * @param size the number of elements per group + * @return An iterator producing arrays of size `size`, except the + * last will be less than size `size` if the elements don't divide evenly. + */ + def grouped(size: Int): Iterator[Array[A]] = new ArrayOps.GroupedIterator[A](xs, size) + + /** Splits this array into a prefix/suffix pair according to a predicate. + * + * Note: `c span p` is equivalent to (but more efficient than) + * `(c takeWhile p, c dropWhile p)`, provided the evaluation of the + * predicate `p` does not cause any side-effects. + * + * @param p the test predicate + * @return a pair consisting of the longest prefix of this array whose + * elements all satisfy `p`, and the rest of this array. + */ + def span(p: A => Boolean): (Array[A], Array[A]) = { + val i = indexWhere(x => !p(x)) + val idx = if(i < 0) xs.length else i + (slice(0, idx), slice(idx, xs.length)) + } + + /** Splits this array into two at a given position. + * Note: `c splitAt n` is equivalent to `(c take n, c drop n)`. + * + * @param n the position at which to split. + * @return a pair of arrays consisting of the first `n` + * elements of this array, and the other elements. + */ + def splitAt(n: Int): (Array[A], Array[A]) = (take(n), drop(n)) + + /** A pair of, first, all elements that satisfy predicate `p` and, second, all elements that do not. */ + def partition(p: A => Boolean): (Array[A], Array[A]) = { + val res1, res2 = ArrayBuilder.make[A] + var i = 0 + while(i < xs.length) { + val x = xs(i) + (if(p(x)) res1 else res2) += x + i += 1 + } + (res1.result(), res2.result()) + } + + /** Applies a function `f` to each element of the array and returns a pair of arrays: the first one + * made of those values returned by `f` that were wrapped in [[scala.util.Left]], and the second + * one made of those wrapped in [[scala.util.Right]]. + * + * Example: + * {{{ + * val xs = Array(1, "one", 2, "two", 3, "three") partitionMap { + * case i: Int => Left(i) + * case s: String => Right(s) + * } + * // xs == (Array(1, 2, 3), + * // Array(one, two, three)) + * }}} + * + * @tparam A1 the element type of the first resulting collection + * @tparam A2 the element type of the second resulting collection + * @param f the 'split function' mapping the elements of this array to an [[scala.util.Either]] + * + * @return a pair of arrays: the first one made of those values returned by `f` that were wrapped in [[scala.util.Left]], + * and the second one made of those wrapped in [[scala.util.Right]]. */ + def partitionMap[A1: ClassTag, A2: ClassTag](f: A => Either[A1, A2]): (Array[A1], Array[A2]) = { + val res1 = ArrayBuilder.make[A1] + val res2 = ArrayBuilder.make[A2] + var i = 0 + while(i < xs.length) { + f(xs(i)) match { + case Left(x) => res1 += x + case Right(x) => res2 += x + } + i += 1 + } + (res1.result(), res2.result()) + } + + /** Returns a new array with the elements in reversed order. */ + @inline def reverse: Array[A] = { + val len = xs.length + val res = new Array[A](len) + var i = 0 + while(i < len) { + res(len-i-1) = xs(i) + i += 1 + } + res + } + + /** An iterator yielding elements in reversed order. + * + * Note: `xs.reverseIterator` is the same as `xs.reverse.iterator` but implemented more efficiently. + * + * @return an iterator yielding the elements of this array in reversed order + */ + def reverseIterator: Iterator[A] = + ((xs: Any @unchecked) match { + case xs: Array[AnyRef] => new ArrayOps.ReverseIterator(xs) + case xs: Array[Int] => new ArrayOps.ReverseIterator(xs) + case xs: Array[Double] => new ArrayOps.ReverseIterator(xs) + case xs: Array[Long] => new ArrayOps.ReverseIterator(xs) + case xs: Array[Float] => new ArrayOps.ReverseIterator(xs) + case xs: Array[Char] => new ArrayOps.ReverseIterator(xs) + case xs: Array[Byte] => new ArrayOps.ReverseIterator(xs) + case xs: Array[Short] => new ArrayOps.ReverseIterator(xs) + case xs: Array[Boolean] => new ArrayOps.ReverseIterator(xs) + case xs: Array[Unit] => new ArrayOps.ReverseIterator(xs) + case null => throw new NullPointerException + }).asInstanceOf[Iterator[A]] + + /** Selects all elements of this array which satisfy a predicate. + * + * @param p the predicate used to test elements. + * @return a new array consisting of all elements of this array that satisfy the given predicate `p`. + */ + def filter(p: A => Boolean): Array[A] = { + val res = ArrayBuilder.make[A] + var i = 0 + while(i < xs.length) { + val x = xs(i) + if(p(x)) res += x + i += 1 + } + res.result() + } + + /** Selects all elements of this array which do not satisfy a predicate. + * + * @param p the predicate used to test elements. + * @return a new array consisting of all elements of this array that do not satisfy the given predicate `p`. + */ + def filterNot(p: A => Boolean): Array[A] = filter(x => !p(x)) + + /** Sorts this array according to an Ordering. + * + * The sort is stable. That is, elements that are equal (as determined by + * `lt`) appear in the same order in the sorted sequence as in the original. + * + * @see [[scala.math.Ordering]] + * + * @param ord the ordering to be used to compare elements. + * @return an array consisting of the elements of this array + * sorted according to the ordering `ord`. + */ + def sorted[B >: A](implicit ord: Ordering[B]): Array[A] = { + val len = xs.length + def boxed = if(len < ArrayOps.MaxStableSortLength) { + val a = xs.clone() + Sorting.stableSort(a)(using ord.asInstanceOf[Ordering[A]]) + a + } else { + val a = Array.copyAs[AnyRef](xs, len)(ClassTag.AnyRef) + Arrays.sort(a, ord.asInstanceOf[Ordering[AnyRef]]) + Array.copyAs[A](a, len) + } + if(len <= 1) xs.clone() + else ((xs: Array[_]) match { + case xs: Array[AnyRef] => + val a = Arrays.copyOf(xs, len); Arrays.sort(a, ord.asInstanceOf[Ordering[AnyRef]]); a + case xs: Array[Int] => + if(ord eq Ordering.Int) { val a = Arrays.copyOf(xs, len); Arrays.sort(a); a } + else boxed + case xs: Array[Long] => + if(ord eq Ordering.Long) { val a = Arrays.copyOf(xs, len); Arrays.sort(a); a } + else boxed + case xs: Array[Char] => + if(ord eq Ordering.Char) { val a = Arrays.copyOf(xs, len); Arrays.sort(a); a } + else boxed + case xs: Array[Byte] => + if(ord eq Ordering.Byte) { val a = Arrays.copyOf(xs, len); Arrays.sort(a); a } + else boxed + case xs: Array[Short] => + if(ord eq Ordering.Short) { val a = Arrays.copyOf(xs, len); Arrays.sort(a); a } + else boxed + case xs: Array[Boolean] => + if(ord eq Ordering.Boolean) { val a = Arrays.copyOf(xs, len); Sorting.stableSort(a); a } + else boxed + case xs => boxed + }).asInstanceOf[Array[A]] + } + + /** Sorts this array according to a comparison function. + * + * The sort is stable. That is, elements that are equal (as determined by + * `lt`) appear in the same order in the sorted sequence as in the original. + * + * @param lt the comparison function which tests whether + * its first argument precedes its second argument in + * the desired ordering. + * @return an array consisting of the elements of this array + * sorted according to the comparison function `lt`. + */ + def sortWith(lt: (A, A) => Boolean): Array[A] = sorted(Ordering.fromLessThan(lt)) + + /** Sorts this array according to the Ordering which results from transforming + * an implicitly given Ordering with a transformation function. + * + * @see [[scala.math.Ordering]] + * @param f the transformation function mapping elements + * to some other domain `B`. + * @param ord the ordering assumed on domain `B`. + * @tparam B the target type of the transformation `f`, and the type where + * the ordering `ord` is defined. + * @return an array consisting of the elements of this array + * sorted according to the ordering where `x < y` if + * `ord.lt(f(x), f(y))`. + */ + def sortBy[B](f: A => B)(implicit ord: Ordering[B]): Array[A] = sorted(ord on f) + + /** Creates a non-strict filter of this array. + * + * Note: the difference between `c filter p` and `c withFilter p` is that + * the former creates a new array, whereas the latter only + * restricts the domain of subsequent `map`, `flatMap`, `foreach`, + * and `withFilter` operations. + * + * @param p the predicate used to test elements. + * @return an object of class `ArrayOps.WithFilter`, which supports + * `map`, `flatMap`, `foreach`, and `withFilter` operations. + * All these operations apply to those elements of this array + * which satisfy the predicate `p`. + */ + def withFilter(p: A => Boolean): ArrayOps.WithFilter[A] = new ArrayOps.WithFilter[A](p, xs) + + /** Finds index of first occurrence of some value in this array after or at some start index. + * + * @param elem the element value to search for. + * @param from the start index + * @return the index `>= from` of the first element of this array that is equal (as determined by `==`) + * to `elem`, or `-1`, if none exists. + */ + def indexOf(elem: A, from: Int = 0): Int = { + var i = from + while(i < xs.length) { + if(elem == xs(i)) return i + i += 1 + } + -1 + } + + /** Finds index of the first element satisfying some predicate after or at some start index. + * + * @param p the predicate used to test elements. + * @param from the start index + * @return the index `>= from` of the first element of this array that satisfies the predicate `p`, + * or `-1`, if none exists. + */ + def indexWhere(@deprecatedName("f", "2.13.3") p: A => Boolean, from: Int = 0): Int = { + var i = from + while(i < xs.length) { + if(p(xs(i))) return i + i += 1 + } + -1 + } + + /** Finds index of last occurrence of some value in this array before or at a given end index. + * + * @param elem the element value to search for. + * @param end the end index. + * @return the index `<= end` of the last element of this array that is equal (as determined by `==`) + * to `elem`, or `-1`, if none exists. + */ + def lastIndexOf(elem: A, end: Int = xs.length - 1): Int = { + var i = min(end, xs.length-1) + while(i >= 0) { + if(elem == xs(i)) return i + i -= 1 + } + -1 + } + + /** Finds index of last element satisfying some predicate before or at given end index. + * + * @param p the predicate used to test elements. + * @return the index `<= end` of the last element of this array that satisfies the predicate `p`, + * or `-1`, if none exists. + */ + def lastIndexWhere(p: A => Boolean, end: Int = xs.length - 1): Int = { + var i = min(end, xs.length-1) + while(i >= 0) { + if(p(xs(i))) return i + i -= 1 + } + -1 + } + + /** Finds the first element of the array satisfying a predicate, if any. + * + * @param p the predicate used to test elements. + * @return an option value containing the first element in the array + * that satisfies `p`, or `None` if none exists. + */ + def find(@deprecatedName("f", "2.13.3") p: A => Boolean): Option[A] = { + val idx = indexWhere(p) + if(idx == -1) None else Some(xs(idx)) + } + + /** Tests whether a predicate holds for at least one element of this array. + * + * @param p the predicate used to test elements. + * @return `true` if the given predicate `p` is satisfied by at least one element of this array, otherwise `false` + */ + def exists(@deprecatedName("f", "2.13.3") p: A => Boolean): Boolean = indexWhere(p) >= 0 + + /** Tests whether a predicate holds for all elements of this array. + * + * @param p the predicate used to test elements. + * @return `true` if this array is empty or the given predicate `p` + * holds for all elements of this array, otherwise `false`. + */ + def forall(@deprecatedName("f", "2.13.3") p: A => Boolean): Boolean = { + var i = 0 + while(i < xs.length) { + if(!p(xs(i))) return false + i += 1 + } + true + } + + /** Applies the given binary operator `op` to the given initial value `z` and + * all elements of this array, going left to right. Returns the initial value + * if this array is empty. + * + * If `x,,1,,`, `x,,2,,`, ..., `x,,n,,` are the elements of this array, the + * result is `op( op( ... op( op(z, x,,1,,), x,,2,,) ... ), x,,n,,)`. + * + * @param z An initial value. + * @param op A binary operator. + * @tparam B The result type of the binary operator. + * @return The result of applying `op` to `z` and all elements of this array, + * going left to right. Returns `z` if this array is empty. + */ + def foldLeft[B](z: B)(op: (B, A) => B): B = { + def f[@specialized(Specializable.Everything) T](xs: Array[T], op: (Any, Any) => Any, z: Any): Any = { + val length = xs.length + var v: Any = z + var i = 0 + while(i < length) { + v = op(v, xs(i)) + i += 1 + } + v + } + ((xs: Any @unchecked) match { + case null => throw new NullPointerException // null-check first helps static analysis of instanceOf + case xs: Array[AnyRef] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Int] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Double] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Long] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Float] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Char] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Byte] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Short] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Boolean] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Unit] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + }).asInstanceOf[B] + } + + /** Produces an array containing cumulative results of applying the binary + * operator going left to right. + * + * @param z the start value. + * @param op the binary operator. + * @tparam B the result type of the binary operator. + * @return array with intermediate values. + * + * Example: + * {{{ + * Array(1, 2, 3, 4).scanLeft(0)(_ + _) == Array(0, 1, 3, 6, 10) + * }}} + * + */ + def scanLeft[ B : ClassTag ](z: B)(op: (B, A) => B): Array[B] = { + var v = z + var i = 0 + val res = new Array[B](xs.length + 1) + while(i < xs.length) { + res(i) = v + v = op(v, xs(i)) + i += 1 + } + res(i) = v + res + } + + /** Computes a prefix scan of the elements of the array. + * + * Note: The neutral element `z` may be applied more than once. + * + * @tparam B element type of the resulting array + * @param z neutral element for the operator `op` + * @param op the associative operator for the scan + * + * @return a new array containing the prefix scan of the elements in this array + */ + def scan[B >: A : ClassTag](z: B)(op: (B, B) => B): Array[B] = scanLeft(z)(op) + + /** Produces an array containing cumulative results of applying the binary + * operator going right to left. + * + * @param z the start value. + * @param op the binary operator. + * @tparam B the result type of the binary operator. + * @return array with intermediate values. + * + * Example: + * {{{ + * Array(4, 3, 2, 1).scanRight(0)(_ + _) == Array(10, 6, 3, 1, 0) + * }}} + * + */ + def scanRight[ B : ClassTag ](z: B)(op: (A, B) => B): Array[B] = { + var v = z + var i = xs.length - 1 + val res = new Array[B](xs.length + 1) + res(xs.length) = z + while(i >= 0) { + v = op(xs(i), v) + res(i) = v + i -= 1 + } + res + } + + /** Applies the given binary operator `op` to all elements of this array and + * the given initial value `z`, going right to left. Returns the initial + * value if this array is empty. + * + * If `x,,1,,`, `x,,2,,`, ..., `x,,n,,` are the elements of this array, the + * result is `op(x,,1,,, op(x,,2,,, op( ... op(x,,n,,, z) ... )))`. + * + * @param z An initial value. + * @param op A binary operator. + * @tparam B The result type of the binary operator. + * @return The result of applying `op` to all elements of this array + * and `z`, going right to left. Returns `z` if this array + * is empty. + */ + def foldRight[B](z: B)(op: (A, B) => B): B = { + def f[@specialized(Specializable.Everything) T](xs: Array[T], op: (Any, Any) => Any, z: Any): Any = { + var v = z + var i = xs.length - 1 + while(i >= 0) { + v = op(xs(i), v) + i -= 1 + } + v + } + ((xs: Any @unchecked) match { + case null => throw new NullPointerException + case xs: Array[AnyRef] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Int] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Double] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Long] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Float] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Char] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Byte] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Short] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Boolean] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + case xs: Array[Unit] => f(xs, op.asInstanceOf[(Any, Any) => Any], z) + }).asInstanceOf[B] + + } + + /** Alias for [[foldLeft]]. + * + * The type parameter is more restrictive than for `foldLeft` to be + * consistent with [[IterableOnceOps.fold]]. + * + * @tparam A1 The type parameter for the binary operator, a supertype of `A`. + * @param z An initial value. + * @param op A binary operator. + * @return The result of applying `op` to `z` and all elements of this array, + * going left to right. Returns `z` if this string is empty. + */ + def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1 = foldLeft(z)(op) + + /** Builds a new array by applying a function to all elements of this array. + * + * @param f the function to apply to each element. + * @tparam B the element type of the returned array. + * @return a new array resulting from applying the given function + * `f` to each element of this array and collecting the results. + */ + def map[B](f: A => B)(implicit ct: ClassTag[B]): Array[B] = { + val len = xs.length + val ys = new Array[B](len) + if(len > 0) { + var i = 0 + (xs: Any @unchecked) match { + case xs: Array[AnyRef] => while (i < len) { ys(i) = f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Int] => while (i < len) { ys(i) = f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Double] => while (i < len) { ys(i) = f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Long] => while (i < len) { ys(i) = f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Float] => while (i < len) { ys(i) = f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Char] => while (i < len) { ys(i) = f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Byte] => while (i < len) { ys(i) = f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Short] => while (i < len) { ys(i) = f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Boolean] => while (i < len) { ys(i) = f(xs(i).asInstanceOf[A]); i = i+1 } + } + } + ys + } + + def mapInPlace(f: A => A): Array[A] = { + var i = 0 + while (i < xs.length) { + xs.update(i, f(xs(i))) + i = i + 1 + } + xs + } + + /** Builds a new array by applying a function to all elements of this array + * and using the elements of the resulting collections. + * + * @param f the function to apply to each element. + * @tparam B the element type of the returned array. + * @return a new array resulting from applying the given collection-valued function + * `f` to each element of this array and concatenating the results. + */ + def flatMap[B : ClassTag](f: A => IterableOnce[B]): Array[B] = { + val b = ArrayBuilder.make[B] + var i = 0 + while(i < xs.length) { + b ++= f(xs(i)) + i += 1 + } + b.result() + } + + def flatMap[BS, B](f: A => BS)(implicit asIterable: BS => Iterable[B], m: ClassTag[B]): Array[B] = + flatMap[B](x => asIterable(f(x))) + + /** Flattens a two-dimensional array by concatenating all its rows + * into a single array. + * + * @tparam B Type of row elements. + * @param asIterable A function that converts elements of this array to rows - Iterables of type `B`. + * @return An array obtained by concatenating rows of this array. + */ + def flatten[B](implicit asIterable: A => IterableOnce[B], m: ClassTag[B]): Array[B] = { + val b = ArrayBuilder.make[B] + val len = xs.length + var size = 0 + var i = 0 + while(i < len) { + xs(i) match { + case it: IterableOnce[_] => + val k = it.knownSize + if(k > 0) size += k + case a: Array[_] => size += a.length + case _ => + } + i += 1 + } + if(size > 0) b.sizeHint(size) + i = 0 + while(i < len) { + b ++= asIterable(xs(i)) + i += 1 + } + b.result() + } + + /** Builds a new array by applying a partial function to all elements of this array + * on which the function is defined. + * + * @param pf the partial function which filters and maps the array. + * @tparam B the element type of the returned array. + * @return a new array resulting from applying the given partial function + * `pf` to each element on which it is defined and collecting the results. + * The order of the elements is preserved. + */ + def collect[B: ClassTag](pf: PartialFunction[A, B]): Array[B] = { + val fallback: Any => Any = ArrayOps.fallback + val b = ArrayBuilder.make[B] + var i = 0 + while (i < xs.length) { + val v = pf.applyOrElse(xs(i), fallback) + if (v.asInstanceOf[AnyRef] ne fallback) b.addOne(v.asInstanceOf[B]) + i += 1 + } + b.result() + } + + /** Finds the first element of the array for which the given partial function is defined, and applies the + * partial function to it. */ + def collectFirst[B](@deprecatedName("f","2.13.9") pf: PartialFunction[A, B]): Option[B] = { + val fallback: Any => Any = ArrayOps.fallback + var i = 0 + while (i < xs.length) { + val v = pf.applyOrElse(xs(i), fallback) + if (v.asInstanceOf[AnyRef] ne fallback) return Some(v.asInstanceOf[B]) + i += 1 + } + None + } + + /** Returns an array formed from this array and another iterable collection + * by combining corresponding elements in pairs. + * If one of the two collections is longer than the other, its remaining elements are ignored. + * + * @param that The iterable providing the second half of each result pair + * @tparam B the type of the second half of the returned pairs + * @return a new array containing pairs consisting of corresponding elements of this array and `that`. + * The length of the returned array is the minimum of the lengths of this array and `that`. + */ + def zip[B](that: IterableOnce[B]): Array[(A, B)] = { + val b = new ArrayBuilder.ofRef[(A, B)]() + val k = that.knownSize + b.sizeHint(if(k >= 0) min(k, xs.length) else xs.length) + var i = 0 + val it = that.iterator + while(i < xs.length && it.hasNext) { + b += ((xs(i), it.next())) + i += 1 + } + b.result() + } + + /** Analogous to `zip` except that the elements in each collection are not consumed until a strict operation is + * invoked on the returned `LazyZip2` decorator. + * + * Calls to `lazyZip` can be chained to support higher arities (up to 4) without incurring the expense of + * constructing and deconstructing intermediary tuples. + * + * {{{ + * val xs = List(1, 2, 3) + * val res = (xs lazyZip xs lazyZip xs lazyZip xs).map((a, b, c, d) => a + b + c + d) + * // res == List(4, 8, 12) + * }}} + * + * @param that the iterable providing the second element of each eventual pair + * @tparam B the type of the second element in each eventual pair + * @return a decorator `LazyZip2` that allows strict operations to be performed on the lazily evaluated pairs + * or chained calls to `lazyZip`. Implicit conversion to `Iterable[(A, B)]` is also supported. + */ + def lazyZip[B](that: Iterable[B]): LazyZip2[A, B, Array[A]] = new LazyZip2(xs, immutable.ArraySeq.unsafeWrapArray(xs), that) + + /** Returns an array formed from this array and another iterable collection + * by combining corresponding elements in pairs. + * If one of the two collections is shorter than the other, + * placeholder elements are used to extend the shorter collection to the length of the longer. + * + * @param that the iterable providing the second half of each result pair + * @param thisElem the element to be used to fill up the result if this array is shorter than `that`. + * @param thatElem the element to be used to fill up the result if `that` is shorter than this array. + * @return a new array containing pairs consisting of corresponding elements of this array and `that`. + * The length of the returned array is the maximum of the lengths of this array and `that`. + * If this array is shorter than `that`, `thisElem` values are used to pad the result. + * If `that` is shorter than this array, `thatElem` values are used to pad the result. + */ + def zipAll[A1 >: A, B](that: Iterable[B], thisElem: A1, thatElem: B): Array[(A1, B)] = { + val b = new ArrayBuilder.ofRef[(A1, B)]() + val k = that.knownSize + b.sizeHint(max(k, xs.length)) + var i = 0 + val it = that.iterator + while(i < xs.length && it.hasNext) { + b += ((xs(i), it.next())) + i += 1 + } + while(it.hasNext) { + b += ((thisElem, it.next())) + i += 1 + } + while(i < xs.length) { + b += ((xs(i), thatElem)) + i += 1 + } + b.result() + } + + /** Zips this array with its indices. + * + * @return A new array containing pairs consisting of all elements of this array paired with their index. + * Indices start at `0`. + */ + def zipWithIndex: Array[(A, Int)] = { + val b = new Array[(A, Int)](xs.length) + var i = 0 + while(i < xs.length) { + b(i) = ((xs(i), i)) + i += 1 + } + b + } + + /** A copy of this array with an element appended. */ + def appended[B >: A : ClassTag](x: B): Array[B] = { + val dest = Array.copyAs[B](xs, xs.length+1) + dest(xs.length) = x + dest + } + + @`inline` final def :+ [B >: A : ClassTag](x: B): Array[B] = appended(x) + + /** A copy of this array with an element prepended. */ + def prepended[B >: A : ClassTag](x: B): Array[B] = { + val dest = new Array[B](xs.length + 1) + dest(0) = x + Array.copy(xs, 0, dest, 1, xs.length) + dest + } + + @`inline` final def +: [B >: A : ClassTag](x: B): Array[B] = prepended(x) + + /** A copy of this array with all elements of a collection prepended. */ + def prependedAll[B >: A : ClassTag](prefix: IterableOnce[B]): Array[B] = { + val b = ArrayBuilder.make[B] + val k = prefix.knownSize + if(k >= 0) b.sizeHint(k + xs.length) + b.addAll(prefix) + if(k < 0) b.sizeHint(b.length + xs.length) + b.addAll(xs) + b.result() + } + + /** A copy of this array with all elements of an array prepended. */ + def prependedAll[B >: A : ClassTag](prefix: Array[_ <: B]): Array[B] = { + val dest = Array.copyAs[B](prefix, prefix.length+xs.length) + Array.copy(xs, 0, dest, prefix.length, xs.length) + dest + } + + @`inline` final def ++: [B >: A : ClassTag](prefix: IterableOnce[B]): Array[B] = prependedAll(prefix) + + @`inline` final def ++: [B >: A : ClassTag](prefix: Array[_ <: B]): Array[B] = prependedAll(prefix) + + /** A copy of this array with all elements of a collection appended. */ + def appendedAll[B >: A : ClassTag](suffix: IterableOnce[B]): Array[B] = { + val b = ArrayBuilder.make[B] + b.sizeHint(suffix, delta = xs.length) + b.addAll(xs) + b.addAll(suffix) + b.result() + } + + /** A copy of this array with all elements of an array appended. */ + def appendedAll[B >: A : ClassTag](suffix: Array[_ <: B]): Array[B] = { + val dest = Array.copyAs[B](xs, xs.length+suffix.length) + Array.copy(suffix, 0, dest, xs.length, suffix.length) + dest + } + + @`inline` final def :++ [B >: A : ClassTag](suffix: IterableOnce[B]): Array[B] = appendedAll(suffix) + + @`inline` final def :++ [B >: A : ClassTag](suffix: Array[_ <: B]): Array[B] = appendedAll(suffix) + + @`inline` final def concat[B >: A : ClassTag](suffix: IterableOnce[B]): Array[B] = appendedAll(suffix) + + @`inline` final def concat[B >: A : ClassTag](suffix: Array[_ <: B]): Array[B] = appendedAll(suffix) + + @`inline` final def ++[B >: A : ClassTag](xs: IterableOnce[B]): Array[B] = appendedAll(xs) + + @`inline` final def ++[B >: A : ClassTag](xs: Array[_ <: B]): Array[B] = appendedAll(xs) + + /** Tests whether this array contains a given value as an element. + * + * @param elem the element to test. + * @return `true` if this array has an element that is equal (as + * determined by `==`) to `elem`, `false` otherwise. + */ + def contains(elem: A): Boolean = exists (_ == elem) + + /** Returns a copy of this array with patched values. + * Patching at negative indices is the same as patching starting at 0. + * Patching at indices at or larger than the length of the original array appends the patch to the end. + * If more values are replaced than actually exist, the excess is ignored. + * + * @param from The start index from which to patch + * @param other The patch values + * @param replaced The number of values in the original array that are replaced by the patch. + */ + def patch[B >: A : ClassTag](from: Int, other: IterableOnce[B], replaced: Int): Array[B] = { + val b = ArrayBuilder.make[B] + val k = other.knownSize + val r = if(replaced < 0) 0 else replaced + if(k >= 0) b.sizeHint(xs.length + k - r) + val chunk1 = if(from > 0) min(from, xs.length) else 0 + if(chunk1 > 0) b.addAll(xs, 0, chunk1) + b ++= other + val remaining = xs.length - chunk1 - r + if(remaining > 0) b.addAll(xs, xs.length - remaining, remaining) + b.result() + } + + /** Converts an array of pairs into an array of first elements and an array of second elements. + * + * @tparam A1 the type of the first half of the element pairs + * @tparam A2 the type of the second half of the element pairs + * @param asPair an implicit conversion which asserts that the element type + * of this Array is a pair. + * @param ct1 a class tag for `A1` type parameter that is required to create an instance + * of `Array[A1]` + * @param ct2 a class tag for `A2` type parameter that is required to create an instance + * of `Array[A2]` + * @return a pair of Arrays, containing, respectively, the first and second half + * of each element pair of this Array. + */ + def unzip[A1, A2](implicit asPair: A => (A1, A2), ct1: ClassTag[A1], ct2: ClassTag[A2]): (Array[A1], Array[A2]) = { + val a1 = new Array[A1](xs.length) + val a2 = new Array[A2](xs.length) + var i = 0 + while (i < xs.length) { + val e = asPair(xs(i)) + a1(i) = e._1 + a2(i) = e._2 + i += 1 + } + (a1, a2) + } + + /** Converts an array of triples into three arrays, one containing the elements from each position of the triple. + * + * @tparam A1 the type of the first of three elements in the triple + * @tparam A2 the type of the second of three elements in the triple + * @tparam A3 the type of the third of three elements in the triple + * @param asTriple an implicit conversion which asserts that the element type + * of this Array is a triple. + * @param ct1 a class tag for T1 type parameter that is required to create an instance + * of Array[T1] + * @param ct2 a class tag for T2 type parameter that is required to create an instance + * of Array[T2] + * @param ct3 a class tag for T3 type parameter that is required to create an instance + * of Array[T3] + * @return a triple of Arrays, containing, respectively, the first, second, and third + * elements from each element triple of this Array. + */ + def unzip3[A1, A2, A3](implicit asTriple: A => (A1, A2, A3), ct1: ClassTag[A1], ct2: ClassTag[A2], + ct3: ClassTag[A3]): (Array[A1], Array[A2], Array[A3]) = { + val a1 = new Array[A1](xs.length) + val a2 = new Array[A2](xs.length) + val a3 = new Array[A3](xs.length) + var i = 0 + while (i < xs.length) { + val e = asTriple(xs(i)) + a1(i) = e._1 + a2(i) = e._2 + a3(i) = e._3 + i += 1 + } + (a1, a2, a3) + } + + /** Transposes a two dimensional array. + * + * @tparam B Type of row elements. + * @param asArray A function that converts elements of this array to rows - arrays of type `B`. + * @return An array obtained by replacing elements of this arrays with rows the represent. + */ + def transpose[B](implicit asArray: A => Array[B]): Array[Array[B]] = { + val aClass = xs.getClass.getComponentType + val bb = new ArrayBuilder.ofRef[Array[B]]()(ClassTag[Array[B]](aClass)) + if (xs.length == 0) bb.result() + else { + def mkRowBuilder() = ArrayBuilder.make[B](using ClassTag[B](aClass.getComponentType)) + val bs = new ArrayOps(asArray(xs(0))).map((x: B) => mkRowBuilder()) + for (xs <- this) { + var i = 0 + for (x <- new ArrayOps(asArray(xs))) { + bs(i) += x + i += 1 + } + } + for (b <- new ArrayOps(bs)) bb += b.result() + bb.result() + } + } + + /** Apply `f` to each element for its side effects. + * Note: [U] parameter needed to help scalac's type inference. + */ + def foreach[U](f: A => U): Unit = { + val len = xs.length + var i = 0 + (xs: Any @unchecked) match { + case xs: Array[AnyRef] => while (i < len) { f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Int] => while (i < len) { f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Double] => while (i < len) { f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Long] => while (i < len) { f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Float] => while (i < len) { f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Char] => while (i < len) { f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Byte] => while (i < len) { f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Short] => while (i < len) { f(xs(i).asInstanceOf[A]); i = i+1 } + case xs: Array[Boolean] => while (i < len) { f(xs(i).asInstanceOf[A]); i = i+1 } + } + } + + /** Selects all the elements of this array ignoring the duplicates. + * + * @return a new array consisting of all the elements of this array without duplicates. + */ + def distinct: Array[A] = distinctBy(identity) + + /** Selects all the elements of this array ignoring the duplicates as determined by `==` after applying + * the transforming function `f`. + * + * @param f The transforming function whose result is used to determine the uniqueness of each element + * @tparam B the type of the elements after being transformed by `f` + * @return a new array consisting of all the elements of this array without duplicates. + */ + def distinctBy[B](f: A => B): Array[A] = + ArrayBuilder.make[A].addAll(iterator.distinctBy(f)).result() + + /** A copy of this array with an element value appended until a given target length is reached. + * + * @param len the target length + * @param elem the padding value + * @tparam B the element type of the returned array. + * @return a new array consisting of + * all elements of this array followed by the minimal number of occurrences of `elem` so + * that the resulting collection has a length of at least `len`. + */ + def padTo[B >: A : ClassTag](len: Int, elem: B): Array[B] = { + var i = xs.length + val newlen = max(i, len) + val dest = Array.copyAs[B](xs, newlen) + while(i < newlen) { + dest(i) = elem + i += 1 + } + dest + } + + /** Produces the range of all indices of this sequence. + * + * @return a `Range` value from `0` to one less than the length of this array. + */ + def indices: Range = Range(0, xs.length) + + /** Partitions this array into a map of arrays according to some discriminator function. + * + * @param f the discriminator function. + * @tparam K the type of keys returned by the discriminator function. + * @return A map from keys to arrays such that the following invariant holds: + * {{{ + * (xs groupBy f)(k) = xs filter (x => f(x) == k) + * }}} + * That is, every key `k` is bound to an array of those elements `x` + * for which `f(x)` equals `k`. + */ + def groupBy[K](f: A => K): immutable.Map[K, Array[A]] = { + val m = mutable.Map.empty[K, ArrayBuilder[A]] + val len = xs.length + var i = 0 + while(i < len) { + val elem = xs(i) + val key = f(elem) + val bldr = m.getOrElseUpdate(key, ArrayBuilder.make[A]) + bldr += elem + i += 1 + } + m.view.mapValues(_.result()).toMap + } + + /** + * Partitions this array into a map of arrays according to a discriminator function `key`. + * Each element in a group is transformed into a value of type `B` using the `value` function. + * + * It is equivalent to `groupBy(key).mapValues(_.map(f))`, but more efficient. + * + * {{{ + * case class User(name: String, age: Int) + * + * def namesByAge(users: Array[User]): Map[Int, Array[String]] = + * users.groupMap(_.age)(_.name) + * }}} + * + * @param key the discriminator function + * @param f the element transformation function + * @tparam K the type of keys returned by the discriminator function + * @tparam B the type of values returned by the transformation function + */ + def groupMap[K, B : ClassTag](key: A => K)(f: A => B): immutable.Map[K, Array[B]] = { + val m = mutable.Map.empty[K, ArrayBuilder[B]] + val len = xs.length + var i = 0 + while(i < len) { + val elem = xs(i) + val k = key(elem) + val bldr = m.getOrElseUpdate(k, ArrayBuilder.make[B]) + bldr += f(elem) + i += 1 + } + m.view.mapValues(_.result()).toMap + } + + @`inline` final def toSeq: immutable.Seq[A] = toIndexedSeq + + def toIndexedSeq: immutable.IndexedSeq[A] = + immutable.ArraySeq.unsafeWrapArray(Array.copyOf(xs, xs.length)) + + /** Copy elements of this array to another array. + * Fills the given array `xs` starting at index 0. + * Copying will stop once either all the elements of this array have been copied, + * or the end of the array is reached. + * + * @param xs the array to fill. + * @tparam B the type of the elements of the array. + */ + def copyToArray[B >: A](xs: Array[B]): Int = copyToArray(xs, 0) + + /** Copy elements of this array to another array. + * Fills the given array `xs` starting at index `start`. + * Copying will stop once either all the elements of this array have been copied, + * or the end of the array is reached. + * + * @param xs the array to fill. + * @param start the starting index within the destination array. + * @tparam B the type of the elements of the array. + */ + def copyToArray[B >: A](xs: Array[B], start: Int): Int = copyToArray(xs, start, Int.MaxValue) + + /** Copy elements of this array to another array. + * Fills the given array `xs` starting at index `start` with at most `len` values. + * Copying will stop once either all the elements of this array have been copied, + * or the end of the array is reached, or `len` elements have been copied. + * + * @param xs the array to fill. + * @param start the starting index within the destination array. + * @param len the maximal number of elements to copy. + * @tparam B the type of the elements of the array. + */ + def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): Int = { + val copied = IterableOnce.elemsToCopyToArray(this.xs.length, xs.length, start, len) + if (copied > 0) { + Array.copy(this.xs, 0, xs, start, copied) + } + copied + } + + /** Create a copy of this array with the specified element type. */ + def toArray[B >: A: ClassTag]: Array[B] = { + val destination = new Array[B](xs.length) + @annotation.unused val copied = copyToArray(destination, 0) + //assert(copied == xs.length) + destination + } + + /** Counts the number of elements in this array which satisfy a predicate */ + def count(p: A => Boolean): Int = { + var i, res = 0 + val len = xs.length + while(i < len) { + if(p(xs(i))) res += 1 + i += 1 + } + res + } + + // can't use a default arg because we already have another overload with a default arg + /** Tests whether this array starts with the given array. */ + @`inline` def startsWith[B >: A](that: Array[B]): Boolean = startsWith(that, 0) + + /** Tests whether this array contains the given array at a given index. + * + * @param that the array to test + * @param offset the index where the array is searched. + * @return `true` if the array `that` is contained in this array at + * index `offset`, otherwise `false`. + */ + def startsWith[B >: A](that: Array[B], offset: Int): Boolean = { + val safeOffset = offset.max(0) + val thatl = that.length + if(thatl > xs.length-safeOffset) thatl == 0 + else { + var i = 0 + while(i < thatl) { + if(xs(i+safeOffset) != that(i)) return false + i += 1 + } + true + } + } + + /** Tests whether this array ends with the given array. + * + * @param that the array to test + * @return `true` if this array has `that` as a suffix, `false` otherwise. + */ + def endsWith[B >: A](that: Array[B]): Boolean = { + val thatl = that.length + val off = xs.length - thatl + if(off < 0) false + else { + var i = 0 + while(i < thatl) { + if(xs(i+off) != that(i)) return false + i += 1 + } + true + } + } + + /** A copy of this array with one single replaced element. + * @param index the position of the replacement + * @param elem the replacing element + * @return a new array which is a copy of this array with the element at position `index` replaced by `elem`. + * @throws IndexOutOfBoundsException if `index` does not satisfy `0 <= index < length`. + */ + def updated[B >: A : ClassTag](index: Int, elem: B): Array[B] = { + if(index < 0 || index >= xs.length) + throw CommonErrors.indexOutOfBounds(index = index, max = xs.length-1) + val dest = toArray[B] + dest(index) = elem + dest + } + + @`inline` def view: IndexedSeqView[A] = new ArrayOps.ArrayView[A](xs) + + + /* ************************************************************************************************************ + The remaining methods are provided for completeness but they delegate to mutable.ArraySeq implementations which + may not provide the best possible performance. We need them in `ArrayOps` because their return type + mentions `C` (which is `Array[A]` in `StringOps` and `mutable.ArraySeq[A]` in `mutable.ArraySeq`). + ************************************************************************************************************ */ + + + /** Computes the multiset difference between this array and another sequence. + * + * @param that the sequence of elements to remove + * @return a new array which contains all elements of this array + * except some of occurrences of elements that also appear in `that`. + * If an element value `x` appears + * ''n'' times in `that`, then the first ''n'' occurrences of `x` will not form + * part of the result, but any following occurrences will. + */ + def diff[B >: A](that: Seq[B]): Array[A] = mutable.ArraySeq.make(xs).diff(that).toArray[A] + + /** Computes the multiset intersection between this array and another sequence. + * + * @param that the sequence of elements to intersect with. + * @return a new array which contains all elements of this array + * which also appear in `that`. + * If an element value `x` appears + * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained + * in the result, but any following occurrences will be omitted. + */ + def intersect[B >: A](that: Seq[B]): Array[A] = mutable.ArraySeq.make(xs).intersect(that).toArray[A] + + /** Groups elements in fixed size blocks by passing a "sliding window" + * over them (as opposed to partitioning them, as is done in grouped.) + * @see [[scala.collection.Iterator]], method `sliding` + * + * @param size the number of elements per group + * @param step the distance between the first elements of successive groups + * @return An iterator producing arrays of size `size`, except the + * last element (which may be the only element) will be truncated + * if there are fewer than `size` elements remaining to be grouped. + */ + def sliding(size: Int, step: Int = 1): Iterator[Array[A]] = mutable.ArraySeq.make(xs).sliding(size, step).map(_.toArray[A]) + + /** Iterates over combinations of elements. + * + * A '''combination''' of length `n` is a sequence of `n` elements selected in order of their first index in this sequence. + * + * For example, `"xyx"` has two combinations of length 2. The `x` is selected first: `"xx"`, `"xy"`. + * The sequence `"yx"` is not returned as a combination because it is subsumed by `"xy"`. + * + * If there is more than one way to generate the same combination, only one will be returned. + * + * For example, the result `"xy"` arbitrarily selected one of the `x` elements. + * + * As a further illustration, `"xyxx"` has three different ways to generate `"xy"` because there are three elements `x` + * to choose from. Moreover, there are three unordered pairs `"xx"` but only one is returned. + * + * It is not specified which of these equal combinations is returned. It is an implementation detail + * that should not be relied on. For example, the combination `"xx"` does not necessarily contain + * the first `x` in this sequence. This behavior is observable if the elements compare equal + * but are not identical. + * + * As a consequence, `"xyx".combinations(3).next()` is `"xxy"`: the combination does not reflect the order + * of the original sequence, but the order in which elements were selected, by "first index"; + * the order of each `x` element is also arbitrary. + * + * @return An Iterator which traverses the n-element combinations of this array + * @example {{{ + * Array('a', 'b', 'b', 'b', 'c').combinations(2).map(runtime.ScalaRunTime.stringOf).foreach(println) + * // Array(a, b) + * // Array(a, c) + * // Array(b, b) + * // Array(b, c) + * Array('b', 'a', 'b').combinations(2).map(runtime.ScalaRunTime.stringOf).foreach(println) + * // Array(b, b) + * // Array(b, a) + * }}} + */ + def combinations(n: Int): Iterator[Array[A]] = mutable.ArraySeq.make(xs).combinations(n).map(_.toArray[A]) + + /** Iterates over distinct permutations of elements. + * + * @return An Iterator which traverses the distinct permutations of this array. + * @example {{{ + * Array('a', 'b', 'b').permutations.map(runtime.ScalaRunTime.stringOf).foreach(println) + * // Array(a, b, b) + * // Array(b, a, b) + * // Array(b, b, a) + * }}} + */ + def permutations: Iterator[Array[A]] = mutable.ArraySeq.make(xs).permutations.map(_.toArray[A]) + + // we have another overload here, so we need to duplicate this method + /** Tests whether this array contains the given sequence at a given index. + * + * @param that the sequence to test + * @param offset the index where the sequence is searched. + * @return `true` if the sequence `that` is contained in this array at + * index `offset`, otherwise `false`. + */ + def startsWith[B >: A](that: IterableOnce[B], offset: Int = 0): Boolean = mutable.ArraySeq.make(xs).startsWith(that, offset) + + // we have another overload here, so we need to duplicate this method + /** Tests whether this array ends with the given sequence. + * + * @param that the sequence to test + * @return `true` if this array has `that` as a suffix, `false` otherwise. + */ + def endsWith[B >: A](that: Iterable[B]): Boolean = mutable.ArraySeq.make(xs).endsWith(that) +} diff --git a/library/src/scala/collection/BitSet.scala b/library/src/scala/collection/BitSet.scala new file mode 100644 index 000000000000..98beab6ae521 --- /dev/null +++ b/library/src/scala/collection/BitSet.scala @@ -0,0 +1,349 @@ +/* + * 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 collection + +import scala.language.`2.13` +import java.io.{ObjectInputStream, ObjectOutputStream} + +import scala.annotation.nowarn +import scala.collection.Stepper.EfficientSplit +import scala.collection.mutable.Builder + + +/** Base type of bitsets. + * + * This trait provides most of the operations of a `BitSet` independently of its representation. + * It is inherited by all concrete implementations of bitsets. + * + * @define bitsetinfo + * Bitsets are sets of non-negative integers which are represented as + * variable-size arrays of bits packed into 64-bit words. The lower bound of memory footprint of a bitset is + * determined by the largest number stored in it. + * @define coll bitset + * @define Coll `BitSet` + */ +trait BitSet extends SortedSet[Int] with BitSetOps[BitSet] { + override protected def fromSpecific(coll: IterableOnce[Int]): BitSet = bitSetFactory.fromSpecific(coll) + override protected def newSpecificBuilder: Builder[Int, BitSet] = bitSetFactory.newBuilder + override def empty: BitSet = bitSetFactory.empty + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix = "BitSet" + override def unsorted: Set[Int] = this +} + +@SerialVersionUID(3L) +object BitSet extends SpecificIterableFactory[Int, BitSet] { + private[collection] final val ordMsg = "No implicit Ordering[${B}] found to build a SortedSet[${B}]. You may want to upcast to a Set[Int] first by calling `unsorted`." + private[collection] final val zipOrdMsg = "No implicit Ordering[${B}] found to build a SortedSet[(Int, ${B})]. You may want to upcast to a Set[Int] first by calling `unsorted`." + + def empty: BitSet = immutable.BitSet.empty + def newBuilder: Builder[Int, BitSet] = immutable.BitSet.newBuilder + def fromSpecific(it: IterableOnce[Int]): BitSet = immutable.BitSet.fromSpecific(it) + + @SerialVersionUID(3L) + private[collection] abstract class SerializationProxy(@transient protected val coll: BitSet) extends Serializable { + + @transient protected var elems: Array[Long] = _ + + private[this] def writeObject(out: ObjectOutputStream): Unit = { + out.defaultWriteObject() + val nwords = coll.nwords + out.writeInt(nwords) + var i = 0 + while(i < nwords) { + out.writeLong(coll.word(i)) + i += 1 + } + } + + private[this] def readObject(in: ObjectInputStream): Unit = { + in.defaultReadObject() + val nwords = in.readInt() + elems = new Array[Long](nwords) + var i = 0 + while(i < nwords) { + elems(i) = in.readLong() + i += 1 + } + } + + protected[this] def readResolve(): Any + } +} + +/** Base implementation type of bitsets */ +transparent trait BitSetOps[+C <: BitSet with BitSetOps[C]] + extends SortedSetOps[Int, SortedSet, C] { self => + import BitSetOps._ + + def bitSetFactory: SpecificIterableFactory[Int, C] + + def unsorted: Set[Int] + + final def ordering: Ordering[Int] = Ordering.Int + + /** The number of words (each with 64 bits) making up the set */ + protected[collection] def nwords: Int + + /** The words at index `idx`, or 0L if outside the range of the set + * '''Note:''' requires `idx >= 0` + */ + protected[collection] def word(idx: Int): Long + + /** Creates a new set of this kind from an array of longs + */ + protected[collection] def fromBitMaskNoCopy(elems: Array[Long]): C + + def contains(elem: Int): Boolean = + 0 <= elem && (word(elem >> LogWL) & (1L << elem)) != 0L + + def iterator: Iterator[Int] = iteratorFrom(0) + + def iteratorFrom(start: Int): Iterator[Int] = new AbstractIterator[Int] { + private[this] var currentPos = if (start > 0) start >> LogWL else 0 + private[this] var currentWord = if (start > 0) word(currentPos) & (-1L << (start & (WordLength - 1))) else word(0) + final override def hasNext: Boolean = { + while (currentWord == 0) { + if (currentPos + 1 >= nwords) return false + currentPos += 1 + currentWord = word(currentPos) + } + true + } + final override def next(): Int = { + if (hasNext) { + val bitPos = java.lang.Long.numberOfTrailingZeros(currentWord) + currentWord &= currentWord - 1 + (currentPos << LogWL) + bitPos + } else Iterator.empty.next() + } + } + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Int, S]): S with EfficientSplit = { + val st = scala.collection.convert.impl.BitSetStepper.from(this) + val r = + if (shape.shape == StepperShape.IntShape) st + else { + assert(shape.shape == StepperShape.ReferenceShape, s"unexpected StepperShape: $shape") + AnyStepper.ofParIntStepper(st) + } + r.asInstanceOf[S with EfficientSplit] + } + + override def size: Int = { + var s = 0 + var i = nwords + while (i > 0) { + i -= 1 + s += java.lang.Long.bitCount(word(i)) + } + s + } + + override def isEmpty: Boolean = 0 until nwords forall (i => word(i) == 0) + + @inline private[this] def smallestInt: Int = { + val thisnwords = nwords + var i = 0 + while(i < thisnwords) { + val currentWord = word(i) + if (currentWord != 0L) { + return java.lang.Long.numberOfTrailingZeros(currentWord) + (i * WordLength) + } + i += 1 + } + throw new UnsupportedOperationException("empty.smallestInt") + } + + @inline private[this] def largestInt: Int = { + var i = nwords - 1 + while(i >= 0) { + val currentWord = word(i) + if (currentWord != 0L) { + return ((i + 1) * WordLength) - java.lang.Long.numberOfLeadingZeros(currentWord) - 1 + } + i -= 1 + } + throw new UnsupportedOperationException("empty.largestInt") + } + + override def max[B >: Int](implicit ord: Ordering[B]): Int = + if (Ordering.Int eq ord) largestInt + else if (Ordering.Int isReverseOf ord) smallestInt + else super.max(ord) + + + override def min[B >: Int](implicit ord: Ordering[B]): Int = + if (Ordering.Int eq ord) smallestInt + else if (Ordering.Int isReverseOf ord) largestInt + else super.min(ord) + + override def foreach[U](f: Int => U): Unit = { + /* NOTE: while loops are significantly faster as of 2.11 and + one major use case of bitsets is performance. Also, there + is nothing to do when all bits are clear, so use that as + the inner loop condition. */ + var i = 0 + while (i < nwords) { + var w = word(i) + var j = i * WordLength + while (w != 0L) { + if ((w&1L) == 1L) f(j) + w = w >>> 1 + j += 1 + } + i += 1 + } + } + + /** Creates a bit mask for this set as a new array of longs + */ + def toBitMask: Array[Long] = { + val a = new Array[Long](nwords) + var i = a.length + while(i > 0) { + i -= 1 + a(i) = word(i) + } + a + } + + def rangeImpl(from: Option[Int], until: Option[Int]): C = { + val a = coll.toBitMask + val len = a.length + if (from.isDefined) { + val f = from.get + val w = f >> LogWL + val b = f & (WordLength - 1) + if (w >= 0) { + java.util.Arrays.fill(a, 0, math.min(w, len), 0) + if (b > 0 && w < len) a(w) &= ~((1L << b) - 1) + } + } + if (until.isDefined) { + val u = until.get + val w = u >> LogWL + val b = u & (WordLength - 1) + if (w < len) { + java.util.Arrays.fill(a, math.max(w + 1, 0), len, 0) + if (w >= 0) a(w) &= (1L << b) - 1 + } + } + coll.fromBitMaskNoCopy(a) + } + + override def concat(other: collection.IterableOnce[Int]): C = other match { + case otherBitset: BitSet => + val len = coll.nwords max otherBitset.nwords + val words = new Array[Long](len) + for (idx <- 0 until len) + words(idx) = this.word(idx) | otherBitset.word(idx) + fromBitMaskNoCopy(words) + case _ => super.concat(other) + } + + override def intersect(other: Set[Int]): C = other match { + case otherBitset: BitSet => + val len = coll.nwords min otherBitset.nwords + val words = new Array[Long](len) + for (idx <- 0 until len) + words(idx) = this.word(idx) & otherBitset.word(idx) + fromBitMaskNoCopy(words) + case _ => super.intersect(other) + } + + abstract override def diff(other: Set[Int]): C = other match { + case otherBitset: BitSet => + val len = coll.nwords + val words = new Array[Long](len) + for (idx <- 0 until len) + words(idx) = this.word(idx) & ~otherBitset.word(idx) + fromBitMaskNoCopy(words) + case _ => super.diff(other) + } + + /** Computes the symmetric difference of this bitset and another bitset by performing + * a bitwise "exclusive-or". + * + * @param other the other bitset to take part in the symmetric difference. + * @return a bitset containing those bits of this + * bitset or the other bitset that are not contained in both bitsets. + */ + def xor(other: BitSet): C = { + val len = coll.nwords max other.nwords + val words = new Array[Long](len) + for (idx <- 0 until len) + words(idx) = coll.word(idx) ^ other.word(idx) + coll.fromBitMaskNoCopy(words) + } + + @`inline` final def ^ (other: BitSet): C = xor(other) + + /** + * Builds a new bitset by applying a function to all elements of this bitset + * @param f the function to apply to each element. + * @return a new bitset resulting from applying the given function ''f'' to + * each element of this bitset and collecting the results + */ + def map(f: Int => Int): C = fromSpecific(new View.Map(this, f)) + + def flatMap(f: Int => IterableOnce[Int]): C = fromSpecific(new View.FlatMap(this, f)) + + def collect(pf: PartialFunction[Int, Int]): C = fromSpecific(super[SortedSetOps].collect(pf)) + + override def partition(p: Int => Boolean): (C, C) = { + val left = filter(p) + (left, this &~ left) + } +} + +object BitSetOps { + + /* Final vals can sometimes be inlined as constants (faster) */ + private[collection] final val LogWL = 6 + private[collection] final val WordLength = 64 + private[collection] final val MaxSize = (Int.MaxValue >> LogWL) + 1 + + private[collection] def updateArray(elems: Array[Long], idx: Int, w: Long): Array[Long] = { + var len = elems.length + while (len > 0 && (elems(len - 1) == 0L || w == 0L && idx == len - 1)) len -= 1 + var newlen = len + if (idx >= newlen && w != 0L) newlen = idx + 1 + val newelems = new Array[Long](newlen) + Array.copy(elems, 0, newelems, 0, len) + if (idx < newlen) newelems(idx) = w + else assert(w == 0L) + newelems + } + + private[collection] def computeWordForFilter(pred: Int => Boolean, isFlipped: Boolean, oldWord: Long, wordIndex: Int): Long = + if (oldWord == 0L) 0L else { + var w = oldWord + val trailingZeroes = java.lang.Long.numberOfTrailingZeros(w) + var jmask = 1L << trailingZeroes + var j = wordIndex * BitSetOps.WordLength + trailingZeroes + val maxJ = (wordIndex + 1) * BitSetOps.WordLength - java.lang.Long.numberOfLeadingZeros(w) + while (j != maxJ) { + if ((w & jmask) != 0L) { + if (pred(j) == isFlipped) { + // j did not pass the filter here + w = w & ~jmask + } + } + jmask = jmask << 1 + j += 1 + } + w + } +} diff --git a/library/src/scala/collection/BufferedIterator.scala b/library/src/scala/collection/BufferedIterator.scala new file mode 100644 index 000000000000..e933ffcd4c16 --- /dev/null +++ b/library/src/scala/collection/BufferedIterator.scala @@ -0,0 +1,33 @@ +/* + * 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.collection + +import scala.language.`2.13` + +/** Buffered iterators are iterators which provide a method `head` + * that inspects the next element without discarding it. + */ +trait BufferedIterator[+A] extends Iterator[A] { + + /** Returns next element of iterator without advancing beyond it. + */ + def head: A + + /** Returns an option of the next element of an iterator without advancing beyond it. + * @return the next element of this iterator if it has a next element + * `None` if it does not + */ + def headOption : Option[A] = if (hasNext) Some(head) else None + + override def buffered: this.type = this +} diff --git a/library/src/scala/collection/BuildFrom.scala b/library/src/scala/collection/BuildFrom.scala new file mode 100644 index 000000000000..1a623228db55 --- /dev/null +++ b/library/src/scala/collection/BuildFrom.scala @@ -0,0 +1,123 @@ +/* + * 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.collection + +import scala.language.`2.13` +import scala.annotation.implicitNotFound +import scala.collection.mutable.Builder +import scala.collection.immutable.WrappedString +import scala.reflect.ClassTag + +/** Builds a collection of type `C` from elements of type `A` when a source collection of type `From` is available. + * Implicit instances of `BuildFrom` are available for all collection types. + * + * @tparam From Type of source collection + * @tparam A Type of elements (e.g. `Int`, `Boolean`, etc.) + * @tparam C Type of collection (e.g. `List[Int]`, `TreeMap[Int, String]`, etc.) + */ +@implicitNotFound(msg = "Cannot construct a collection of type ${C} with elements of type ${A} based on a collection of type ${From}.") +trait BuildFrom[-From, -A, +C] extends Any { self => + def fromSpecific(from: From)(it: IterableOnce[A]): C + + /** Get a Builder for the collection. For non-strict collection types this will use an intermediate buffer. + * Building collections with `fromSpecific` is preferred because it can be lazy for lazy collections. */ + def newBuilder(from: From): Builder[A, C] + + @deprecated("Use newBuilder() instead of apply()", "2.13.0") + @`inline` def apply(from: From): Builder[A, C] = newBuilder(from) + + /** Partially apply a BuildFrom to a Factory */ + def toFactory(from: From): Factory[A, C] = new Factory[A, C] { + def fromSpecific(it: IterableOnce[A]): C = self.fromSpecific(from)(it) + def newBuilder: Builder[A, C] = self.newBuilder(from) + } +} + +object BuildFrom extends BuildFromLowPriority1 { + + /** Build the source collection type from a MapOps */ + implicit def buildFromMapOps[CC[X, Y] <: Map[X, Y] with MapOps[X, Y, CC, _], K0, V0, K, V]: BuildFrom[CC[K0, V0] with Map[K0, V0], (K, V), CC[K, V] with Map[K, V]] = new BuildFrom[CC[K0, V0], (K, V), CC[K, V]] { + //TODO: Reuse a prototype instance + def newBuilder(from: CC[K0, V0]): Builder[(K, V), CC[K, V]] = (from: MapOps[K0, V0, CC, _]).mapFactory.newBuilder[K, V] + def fromSpecific(from: CC[K0, V0])(it: IterableOnce[(K, V)]): CC[K, V] = (from: MapOps[K0, V0, CC, _]).mapFactory.from(it) + } + + /** Build the source collection type from a SortedMapOps */ + implicit def buildFromSortedMapOps[CC[X, Y] <: SortedMap[X, Y] with SortedMapOps[X, Y, CC, _], K0, V0, K : Ordering, V]: BuildFrom[CC[K0, V0] with SortedMap[K0, V0], (K, V), CC[K, V] with SortedMap[K, V]] = new BuildFrom[CC[K0, V0], (K, V), CC[K, V]] { + def newBuilder(from: CC[K0, V0]): Builder[(K, V), CC[K, V]] = (from: SortedMapOps[K0, V0, CC, _]).sortedMapFactory.newBuilder[K, V] + def fromSpecific(from: CC[K0, V0])(it: IterableOnce[(K, V)]): CC[K, V] = (from: SortedMapOps[K0, V0, CC, _]).sortedMapFactory.from(it) + } + + implicit def buildFromBitSet[C <: BitSet with BitSetOps[C]]: BuildFrom[C, Int, C] = + new BuildFrom[C, Int, C] { + def fromSpecific(from: C)(it: IterableOnce[Int]): C = from.bitSetFactory.fromSpecific(it) + def newBuilder(from: C): Builder[Int, C] = from.bitSetFactory.newBuilder + } + + implicit val buildFromString: BuildFrom[String, Char, String] = + new BuildFrom[String, Char, String] { + def fromSpecific(from: String)(it: IterableOnce[Char]): String = Factory.stringFactory.fromSpecific(it) + def newBuilder(from: String): Builder[Char, String] = Factory.stringFactory.newBuilder + } + + implicit val buildFromWrappedString: BuildFrom[WrappedString, Char, WrappedString] = + new BuildFrom[WrappedString, Char, WrappedString] { + def fromSpecific(from: WrappedString)(it: IterableOnce[Char]): WrappedString = WrappedString.fromSpecific(it) + def newBuilder(from: WrappedString): mutable.Builder[Char, WrappedString] = WrappedString.newBuilder + } + + implicit def buildFromArray[A : ClassTag]: BuildFrom[Array[_], A, Array[A]] = + new BuildFrom[Array[_], A, Array[A]] { + def fromSpecific(from: Array[_])(it: IterableOnce[A]): Array[A] = Factory.arrayFactory[A].fromSpecific(it) + def newBuilder(from: Array[_]): Builder[A, Array[A]] = Factory.arrayFactory[A].newBuilder + } + + implicit def buildFromView[A, B]: BuildFrom[View[A], B, View[B]] = + new BuildFrom[View[A], B, View[B]] { + def fromSpecific(from: View[A])(it: IterableOnce[B]): View[B] = View.from(it) + def newBuilder(from: View[A]): Builder[B, View[B]] = View.newBuilder + } + +} + +trait BuildFromLowPriority1 extends BuildFromLowPriority2 { + + /** Build the source collection type from an Iterable with SortedOps */ + // Restating the upper bound of CC in the result type seems redundant, but it serves to prune the + // implicit search space for faster compilation and reduced change of divergence. See the compilation + // test in test/junit/scala/collection/BuildFromTest.scala and discussion in https://github.com/scala/scala/pull/10209 + implicit def buildFromSortedSetOps[CC[X] <: SortedSet[X] with SortedSetOps[X, CC, _], A0, A : Ordering]: BuildFrom[CC[A0] with SortedSet[A0], A, CC[A] with SortedSet[A]] = new BuildFrom[CC[A0], A, CC[A]] { + def newBuilder(from: CC[A0]): Builder[A, CC[A]] = (from: SortedSetOps[A0, CC, _]).sortedIterableFactory.newBuilder[A] + def fromSpecific(from: CC[A0])(it: IterableOnce[A]): CC[A] = (from: SortedSetOps[A0, CC, _]).sortedIterableFactory.from(it) + } + + implicit def fallbackStringCanBuildFrom[A]: BuildFrom[String, A, immutable.IndexedSeq[A]] = + new BuildFrom[String, A, immutable.IndexedSeq[A]] { + def fromSpecific(from: String)(it: IterableOnce[A]): immutable.IndexedSeq[A] = immutable.IndexedSeq.from(it) + def newBuilder(from: String): Builder[A, immutable.IndexedSeq[A]] = immutable.IndexedSeq.newBuilder[A] + } +} + +trait BuildFromLowPriority2 { + /** Build the source collection type from an IterableOps */ + implicit def buildFromIterableOps[CC[X] <: Iterable[X] with IterableOps[X, CC, _], A0, A]: BuildFrom[CC[A0], A, CC[A]] = new BuildFrom[CC[A0], A, CC[A]] { + //TODO: Reuse a prototype instance + def newBuilder(from: CC[A0]): Builder[A, CC[A]] = (from: IterableOps[A0, CC, _]).iterableFactory.newBuilder[A] + def fromSpecific(from: CC[A0])(it: IterableOnce[A]): CC[A] = (from: IterableOps[A0, CC, _]).iterableFactory.from(it) + } + + implicit def buildFromIterator[A]: BuildFrom[Iterator[_], A, Iterator[A]] = new BuildFrom[Iterator[_], A, Iterator[A]] { + def newBuilder(from: Iterator[_]): mutable.Builder[A, Iterator[A]] = Iterator.newBuilder + def fromSpecific(from: Iterator[_])(it: IterableOnce[A]): Iterator[A] = Iterator.from(it) + } +} diff --git a/library/src/scala/collection/DefaultMap.scala b/library/src/scala/collection/DefaultMap.scala new file mode 100644 index 000000000000..d957a4ef245e --- /dev/null +++ b/library/src/scala/collection/DefaultMap.scala @@ -0,0 +1,22 @@ +/* + * 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 collection + +import scala.language.`2.13` + +/** A default map which builds a default `immutable.Map` implementation for all + * transformations. + */ +@deprecated("DefaultMap is no longer necessary; extend Map directly", "2.13.0") +trait DefaultMap[K, +V] extends Map[K, V] diff --git a/library/src/scala/collection/Factory.scala b/library/src/scala/collection/Factory.scala new file mode 100644 index 000000000000..8e40f2955853 --- /dev/null +++ b/library/src/scala/collection/Factory.scala @@ -0,0 +1,785 @@ +/* + * 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 collection + +import scala.language.`2.13` +import scala.collection.immutable.NumericRange +import scala.language.implicitConversions +import scala.collection.mutable.Builder +import scala.annotation.unchecked.uncheckedVariance +import scala.reflect.ClassTag + +/** + * A factory that builds a collection of type `C` with elements of type `A`. + * + * This is a general form of any factory ([[IterableFactory]], + * [[SortedIterableFactory]], [[MapFactory]] and [[SortedMapFactory]]) whose + * element type is fixed. + * + * @tparam A Type of elements (e.g. `Int`, `Boolean`, etc.) + * @tparam C Type of collection (e.g. `List[Int]`, `TreeMap[Int, String]`, etc.) + */ +trait Factory[-A, +C] extends Any { + + /** + * @return A collection of type `C` containing the same elements + * as the source collection `it`. + * @param it Source collection + */ + def fromSpecific(it: IterableOnce[A]): C + + /** Get a Builder for the collection. For non-strict collection types this will use an intermediate buffer. + * Building collections with `fromSpecific` is preferred because it can be lazy for lazy collections. */ + def newBuilder: Builder[A, C] +} + +object Factory { + + implicit val stringFactory: Factory[Char, String] = new StringFactory + @SerialVersionUID(3L) + private class StringFactory extends Factory[Char, String] with Serializable { + def fromSpecific(it: IterableOnce[Char]): String = { + val b = new mutable.StringBuilder(scala.math.max(0, it.knownSize)) + b ++= it + b.result() + } + def newBuilder: Builder[Char, String] = new mutable.StringBuilder() + } + + implicit def arrayFactory[A: ClassTag]: Factory[A, Array[A]] = new ArrayFactory[A] + @SerialVersionUID(3L) + private class ArrayFactory[A: ClassTag] extends Factory[A, Array[A]] with Serializable { + def fromSpecific(it: IterableOnce[A]): Array[A] = { + val b = newBuilder + b.sizeHint(it, delta = 0) + b ++= it + b.result() + } + def newBuilder: Builder[A, Array[A]] = mutable.ArrayBuilder.make[A] + } + +} + +/** Base trait for companion objects of unconstrained collection types that may require + * multiple traversals of a source collection to build a target collection `CC`. + * + * @tparam CC Collection type constructor (e.g. `List`) + * @define factoryInfo + * This object provides a set of operations to create $Coll values. + * + * @define coll collection + * @define Coll `Iterable` + */ +trait IterableFactory[+CC[_]] extends Serializable { + + /** Creates a target $coll from an existing source collection + * + * @param source Source collection + * @tparam A the type of the collection’s elements + * @return a new $coll with the elements of `source` + */ + def from[A](source: IterableOnce[A]): CC[A] + + /** An empty $coll + * @tparam A the type of the ${coll}'s elements + */ + def empty[A]: CC[A] + + /** Creates a $coll with the specified elements. + * @tparam A the type of the ${coll}'s elements + * @param elems the elements of the created $coll + * @return a new $coll with elements `elems` + */ + def apply[A](elems: A*): CC[A] = from(elems) + + /** Produces a $coll containing repeated applications of a function to a start value. + * + * @param start the start value of the $coll + * @param len the number of elements contained in the $coll + * @param f the function that's repeatedly applied + * @return a $coll with `len` values in the sequence `start, f(start), f(f(start)), ...` + */ + def iterate[A](start: A, len: Int)(f: A => A): CC[A] = from(new View.Iterate(start, len)(f)) + + /** Produces a $coll that uses a function `f` to produce elements of type `A` + * and update an internal state of type `S`. + * + * @param init State initial value + * @param f Computes the next element (or returns `None` to signal + * the end of the collection) + * @tparam A Type of the elements + * @tparam S Type of the internal state + * @return a $coll that produces elements using `f` until `f` returns `None` + */ + def unfold[A, S](init: S)(f: S => Option[(A, S)]): CC[A] = from(new View.Unfold(init)(f)) + + /** Produces a $coll containing a sequence of increasing of integers. + * + * @param start the first element of the $coll + * @param end the end value of the $coll (the first value NOT contained) + * @return a $coll with values `start, start + 1, ..., end - 1` + */ + def range[A : Integral](start: A, end: A): CC[A] = from(NumericRange(start, end, implicitly[Integral[A]].one)) + + /** Produces a $coll containing equally spaced values in some integer interval. + * @param start the start value of the $coll + * @param end the end value of the $coll (the first value NOT contained) + * @param step the difference between successive elements of the $coll (must be positive or negative) + * @return a $coll with values `start, start + step, ...` up to, but excluding `end` + */ + def range[A : Integral](start: A, end: A, step: A): CC[A] = from(NumericRange(start, end, step)) + + /** + * @return A builder for $Coll objects. + * @tparam A the type of the ${coll}’s elements + */ + def newBuilder[A]: Builder[A, CC[A]] + + /** Produces a $coll containing the results of some element computation a number of times. + * @param n the number of elements contained in the $coll. + * @param elem the element computation + * @return A $coll that contains the results of `n` evaluations of `elem`. + */ + def fill[A](n: Int)(elem: => A): CC[A] = from(new View.Fill(n)(elem)) + + /** Produces a two-dimensional $coll containing the results of some element computation a number of times. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param elem the element computation + * @return A $coll that contains the results of `n1 x n2` evaluations of `elem`. + */ + def fill[A](n1: Int, n2: Int)(elem: => A): CC[CC[A] @uncheckedVariance] = fill(n1)(fill(n2)(elem)) + + /** Produces a three-dimensional $coll containing the results of some element computation a number of times. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param elem the element computation + * @return A $coll that contains the results of `n1 x n2 x n3` evaluations of `elem`. + */ + def fill[A](n1: Int, n2: Int, n3: Int)(elem: => A): CC[CC[CC[A]] @uncheckedVariance] = fill(n1)(fill(n2, n3)(elem)) + + /** Produces a four-dimensional $coll containing the results of some element computation a number of times. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param elem the element computation + * @return A $coll that contains the results of `n1 x n2 x n3 x n4` evaluations of `elem`. + */ + def fill[A](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => A): CC[CC[CC[CC[A]]] @uncheckedVariance] = + fill(n1)(fill(n2, n3, n4)(elem)) + + /** Produces a five-dimensional $coll containing the results of some element computation a number of times. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param n5 the number of elements in the 5th dimension + * @param elem the element computation + * @return A $coll that contains the results of `n1 x n2 x n3 x n4 x n5` evaluations of `elem`. + */ + def fill[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => A): CC[CC[CC[CC[CC[A]]]] @uncheckedVariance] = + fill(n1)(fill(n2, n3, n4, n5)(elem)) + + /** Produces a $coll containing values of a given function over a range of integer values starting from 0. + * @param n The number of elements in the $coll + * @param f The function computing element values + * @return A $coll consisting of elements `f(0), ..., f(n -1)` + */ + def tabulate[A](n: Int)(f: Int => A): CC[A] = from(new View.Tabulate(n)(f)) + + /** Produces a two-dimensional $coll containing values of a given function over ranges of integer values starting from 0. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param f The function computing element values + * @return A $coll consisting of elements `f(i1, i2)` + * for `0 <= i1 < n1` and `0 <= i2 < n2`. + */ + def tabulate[A](n1: Int, n2: Int)(f: (Int, Int) => A): CC[CC[A] @uncheckedVariance] = + tabulate(n1)(i1 => tabulate(n2)(f(i1, _))) + + /** Produces a three-dimensional $coll containing values of a given function over ranges of integer values starting from 0. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param f The function computing element values + * @return A $coll consisting of elements `f(i1, i2, i3)` + * for `0 <= i1 < n1`, `0 <= i2 < n2`, and `0 <= i3 < n3`. + */ + def tabulate[A](n1: Int, n2: Int, n3: Int)(f: (Int, Int, Int) => A): CC[CC[CC[A]] @uncheckedVariance] = + tabulate(n1)(i1 => tabulate(n2, n3)(f(i1, _, _))) + + /** Produces a four-dimensional $coll containing values of a given function over ranges of integer values starting from 0. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param f The function computing element values + * @return A $coll consisting of elements `f(i1, i2, i3, i4)` + * for `0 <= i1 < n1`, `0 <= i2 < n2`, `0 <= i3 < n3`, and `0 <= i4 < n4`. + */ + def tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int)(f: (Int, Int, Int, Int) => A): CC[CC[CC[CC[A]]] @uncheckedVariance] = + tabulate(n1)(i1 => tabulate(n2, n3, n4)(f(i1, _, _, _))) + + /** Produces a five-dimensional $coll containing values of a given function over ranges of integer values starting from 0. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param n5 the number of elements in the 5th dimension + * @param f The function computing element values + * @return A $coll consisting of elements `f(i1, i2, i3, i4, i5)` + * for `0 <= i1 < n1`, `0 <= i2 < n2`, `0 <= i3 < n3`, `0 <= i4 < n4`, and `0 <= i5 < n5`. + */ + def tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(f: (Int, Int, Int, Int, Int) => A): CC[CC[CC[CC[CC[A]]]] @uncheckedVariance] = + tabulate(n1)(i1 => tabulate(n2, n3, n4, n5)(f(i1, _, _, _, _))) + + /** Concatenates all argument collections into a single $coll. + * + * @param xss the collections that are to be concatenated. + * @return the concatenation of all the collections. + */ + def concat[A](xss: Iterable[A]*): CC[A] = { + from(xss.foldLeft(View.empty[A])(_ ++ _)) + } + + implicit def iterableFactory[A]: Factory[A, CC[A]] = IterableFactory.toFactory(this) +} + +object IterableFactory { + + /** + * Fixes the element type of `factory` to `A` + * @param factory The factory to fix the element type + * @tparam A Type of elements + * @tparam CC Collection type constructor of the factory (e.g. `Seq`, `List`) + * @return A [[Factory]] that uses the given `factory` to build a collection of elements + * of type `A` + */ + implicit def toFactory[A, CC[_]](factory: IterableFactory[CC]): Factory[A, CC[A]] = new ToFactory[A, CC](factory) + + @SerialVersionUID(3L) + private[this] class ToFactory[A, CC[_]](factory: IterableFactory[CC]) extends Factory[A, CC[A]] with Serializable { + def fromSpecific(it: IterableOnce[A]): CC[A] = factory.from[A](it) + def newBuilder: Builder[A, CC[A]] = factory.newBuilder[A] + } + + implicit def toBuildFrom[A, CC[_]](factory: IterableFactory[CC]): BuildFrom[Any, A, CC[A]] = + new BuildFrom[Any, A, CC[A]] { + def fromSpecific(from: Any)(it: IterableOnce[A]) = factory.from(it) + def newBuilder(from: Any) = factory.newBuilder + } + + @SerialVersionUID(3L) + class Delegate[CC[_]](delegate: IterableFactory[CC]) extends IterableFactory[CC] { + override def apply[A](elems: A*): CC[A] = delegate.apply(elems: _*) + def empty[A]: CC[A] = delegate.empty + def from[E](it: IterableOnce[E]): CC[E] = delegate.from(it) + def newBuilder[A]: Builder[A, CC[A]] = delegate.newBuilder[A] + } +} + +/** + * @tparam CC Collection type constructor (e.g. `List`) + */ +trait SeqFactory[+CC[A] <: SeqOps[A, Seq, Seq[A]]] extends IterableFactory[CC] { + import SeqFactory.UnapplySeqWrapper + final def unapplySeq[A](x: CC[A] @uncheckedVariance): UnapplySeqWrapper[A] = new UnapplySeqWrapper(x) // TODO is uncheckedVariance sound here? +} + +object SeqFactory { + @SerialVersionUID(3L) + class Delegate[CC[A] <: SeqOps[A, Seq, Seq[A]]](delegate: SeqFactory[CC]) extends SeqFactory[CC] { + override def apply[A](elems: A*): CC[A] = delegate.apply(elems: _*) + def empty[A]: CC[A] = delegate.empty + def from[E](it: IterableOnce[E]): CC[E] = delegate.from(it) + def newBuilder[A]: Builder[A, CC[A]] = delegate.newBuilder[A] + } + + final class UnapplySeqWrapper[A](private val c: SeqOps[A, Seq, Seq[A]]) extends AnyVal { + def isEmpty: false = false + def get: UnapplySeqWrapper[A] = this + def lengthCompare(len: Int): Int = c.lengthCompare(len) + def apply(i: Int): A = c(i) + def drop(n: Int): scala.Seq[A] = c match { + case seq: scala.Seq[A] => seq.drop(n) + case _ => c.view.drop(n).toSeq + } + def toSeq: scala.Seq[A] = c.toSeq + } +} + +trait StrictOptimizedSeqFactory[+CC[A] <: SeqOps[A, Seq, Seq[A]]] extends SeqFactory[CC] { + + override def fill[A](n: Int)(elem: => A): CC[A] = { + val b = newBuilder[A] + b.sizeHint(n) + var i = 0 + while (i < n) { + b += elem + i += 1 + } + b.result() + } + + override def tabulate[A](n: Int)(f: Int => A): CC[A] = { + val b = newBuilder[A] + b.sizeHint(n) + var i = 0 + while (i < n) { + b += f(i) + i += 1 + } + b.result() + } + + override def concat[A](xss: Iterable[A]*): CC[A] = { + val b = newBuilder[A] + val knownSizes = xss.view.map(_.knownSize) + if (knownSizes forall (_ >= 0)) { + b.sizeHint(knownSizes.sum) + } + for (xs <- xss) b ++= xs + b.result() + } + +} + +/** + * @tparam A Type of elements (e.g. `Int`, `Boolean`, etc.) + * @tparam C Type of collection (e.g. `List[Int]`, `TreeMap[Int, String]`, etc.) + * @define factoryInfo + * This object provides a set of operations to create $Coll values. + * + * @define coll collection + * @define Coll `Iterable` + */ +trait SpecificIterableFactory[-A, +C] extends Factory[A, C] { + def empty: C + def apply(xs: A*): C = fromSpecific(xs) + def fill(n: Int)(elem: => A): C = fromSpecific(new View.Fill(n)(elem)) + def newBuilder: Builder[A, C] + + implicit def specificIterableFactory: Factory[A, C] = this +} + +/** + * @define factoryInfo + * This object provides a set of operations to create $Coll values. + * + * @define coll collection + * @define Coll `Iterable` + */ +trait MapFactory[+CC[_, _]] extends Serializable { + + /** + * An empty Map + */ + def empty[K, V]: CC[K, V] + + /** + * A collection of type Map generated from given iterable object. + */ + def from[K, V](it: IterableOnce[(K, V)]): CC[K, V] + + /** + * A collection of type Map that contains given key/value bindings. + */ + def apply[K, V](elems: (K, V)*): CC[K, V] = from(elems) + + /** + * The default builder for Map objects. + */ + def newBuilder[K, V]: Builder[(K, V), CC[K, V]] + + /** + * The default Factory instance for maps. + */ + implicit def mapFactory[K, V]: Factory[(K, V), CC[K, V]] = MapFactory.toFactory(this) +} + +object MapFactory { + + /** + * Fixes the key and value types of `factory` to `K` and `V`, respectively + * @param factory The factory to fix the key and value types + * @tparam K Type of keys + * @tparam V Type of values + * @tparam CC Collection type constructor of the factory (e.g. `Map`, `HashMap`, etc.) + * @return A [[Factory]] that uses the given `factory` to build a map with keys of type `K` + * and values of type `V` + */ + implicit def toFactory[K, V, CC[_, _]](factory: MapFactory[CC]): Factory[(K, V), CC[K, V]] = new ToFactory[K, V, CC](factory) + + @SerialVersionUID(3L) + private[this] class ToFactory[K, V, CC[_, _]](factory: MapFactory[CC]) extends Factory[(K, V), CC[K, V]] with Serializable { + def fromSpecific(it: IterableOnce[(K, V)]): CC[K, V] = factory.from[K, V](it) + def newBuilder: Builder[(K, V), CC[K, V]] = factory.newBuilder[K, V] + } + + implicit def toBuildFrom[K, V, CC[_, _]](factory: MapFactory[CC]): BuildFrom[Any, (K, V), CC[K, V]] = + new BuildFrom[Any, (K, V), CC[K, V]] { + def fromSpecific(from: Any)(it: IterableOnce[(K, V)]) = factory.from(it) + def newBuilder(from: Any) = factory.newBuilder[K, V] + } + + @SerialVersionUID(3L) + class Delegate[C[_, _]](delegate: MapFactory[C]) extends MapFactory[C] { + override def apply[K, V](elems: (K, V)*): C[K, V] = delegate.apply(elems: _*) + def from[K, V](it: IterableOnce[(K, V)]): C[K, V] = delegate.from(it) + def empty[K, V]: C[K, V] = delegate.empty + def newBuilder[K, V]: Builder[(K, V), C[K, V]] = delegate.newBuilder + } +} + +/** Base trait for companion objects of collections that require an implicit evidence. + * @tparam CC Collection type constructor (e.g. `ArraySeq`) + * @tparam Ev Unary type constructor for the implicit evidence required for an element type + * (typically `Ordering` or `ClassTag`) + * + * @define factoryInfo + * This object provides a set of operations to create $Coll values. + * + * @define coll collection + * @define Coll `Iterable` + */ +trait EvidenceIterableFactory[+CC[_], Ev[_]] extends Serializable { + + def from[E : Ev](it: IterableOnce[E]): CC[E] + + def empty[A : Ev]: CC[A] + + def apply[A : Ev](xs: A*): CC[A] = from(xs) + + /** Produces a $coll containing the results of some element computation a number of times. + * @param n the number of elements contained in the $coll. + * @param elem the element computation + * @return A $coll that contains the results of `n` evaluations of `elem`. + */ + def fill[A : Ev](n: Int)(elem: => A): CC[A] = from(new View.Fill(n)(elem)) + + /** Produces a $coll containing values of a given function over a range of integer values starting from 0. + * @param n The number of elements in the $coll + * @param f The function computing element values + * @return A $coll consisting of elements `f(0), ..., f(n -1)` + */ + def tabulate[A : Ev](n: Int)(f: Int => A): CC[A] = from(new View.Tabulate(n)(f)) + + /** Produces a $coll containing repeated applications of a function to a start value. + * + * @param start the start value of the $coll + * @param len the number of elements contained in the $coll + * @param f the function that's repeatedly applied + * @return a $coll with `len` values in the sequence `start, f(start), f(f(start)), ...` + */ + def iterate[A : Ev](start: A, len: Int)(f: A => A): CC[A] = from(new View.Iterate(start, len)(f)) + + /** Produces a $coll that uses a function `f` to produce elements of type `A` + * and update an internal state of type `S`. + * + * @param init State initial value + * @param f Computes the next element (or returns `None` to signal + * the end of the collection) + * @tparam A Type of the elements + * @tparam S Type of the internal state + * @return a $coll that produces elements using `f` until `f` returns `None` + */ + def unfold[A : Ev, S](init: S)(f: S => Option[(A, S)]): CC[A] = from(new View.Unfold(init)(f)) + + def newBuilder[A : Ev]: Builder[A, CC[A]] + + implicit def evidenceIterableFactory[A : Ev]: Factory[A, CC[A]] = EvidenceIterableFactory.toFactory(this) +} + +object EvidenceIterableFactory { + + /** + * Fixes the element type of `factory` to `A` + * @param factory The factory to fix the element type + * @tparam A Type of elements + * @tparam CC Collection type constructor of the factory (e.g. `TreeSet`) + * @tparam Ev Type constructor of the evidence (usually `Ordering` or `ClassTag`) + * @return A [[Factory]] that uses the given `factory` to build a collection of elements + * of type `A` + */ + implicit def toFactory[Ev[_], A: Ev, CC[_]](factory: EvidenceIterableFactory[CC, Ev]): Factory[A, CC[A]] = new ToFactory[Ev, A, CC](factory) + + @SerialVersionUID(3L) + private[this] class ToFactory[Ev[_], A: Ev, CC[_]](factory: EvidenceIterableFactory[CC, Ev]) extends Factory[A, CC[A]] with Serializable { + def fromSpecific(it: IterableOnce[A]): CC[A] = factory.from[A](it) + def newBuilder: Builder[A, CC[A]] = factory.newBuilder[A] + } + + implicit def toBuildFrom[Ev[_], A: Ev, CC[_]](factory: EvidenceIterableFactory[CC, Ev]): BuildFrom[Any, A, CC[A]] = new EvidenceIterableFactoryToBuildFrom(factory) + private class EvidenceIterableFactoryToBuildFrom[Ev[_], A: Ev, CC[_]](factory: EvidenceIterableFactory[CC, Ev]) extends BuildFrom[Any, A, CC[A]] { + def fromSpecific(from: Any)(it: IterableOnce[A]): CC[A] = factory.from[A](it) + def newBuilder(from: Any): Builder[A, CC[A]] = factory.newBuilder[A] + } + + @SerialVersionUID(3L) + class Delegate[CC[_], Ev[_]](delegate: EvidenceIterableFactory[CC, Ev]) extends EvidenceIterableFactory[CC, Ev] { + override def apply[A: Ev](xs: A*): CC[A] = delegate.apply(xs: _*) + def empty[A : Ev]: CC[A] = delegate.empty + def from[E : Ev](it: IterableOnce[E]): CC[E] = delegate.from(it) + def newBuilder[A : Ev]: Builder[A, CC[A]] = delegate.newBuilder[A] + } +} + +/** Base trait for companion objects of collections that require an implicit `Ordering`. + * @tparam CC Collection type constructor (e.g. `SortedSet`) + */ +trait SortedIterableFactory[+CC[_]] extends EvidenceIterableFactory[CC, Ordering] + +object SortedIterableFactory { + @SerialVersionUID(3L) + class Delegate[CC[_]](delegate: EvidenceIterableFactory[CC, Ordering]) + extends EvidenceIterableFactory.Delegate[CC, Ordering](delegate) with SortedIterableFactory[CC] +} + +/** Base trait for companion objects of collections that require an implicit `ClassTag`. + * @tparam CC Collection type constructor (e.g. `ArraySeq`) + */ +trait ClassTagIterableFactory[+CC[_]] extends EvidenceIterableFactory[CC, ClassTag] { + + @`inline` private[this] implicit def ccClassTag[X]: ClassTag[CC[X]] = + ClassTag.AnyRef.asInstanceOf[ClassTag[CC[X]]] // Good enough for boxed vs primitive arrays + + /** Produces a $coll containing a sequence of increasing of integers. + * + * @param start the first element of the $coll + * @param end the end value of the $coll (the first value NOT contained) + * @return a $coll with values `start, start + 1, ..., end - 1` + */ + def range[A : Integral : ClassTag](start: A, end: A): CC[A] = from(NumericRange(start, end, implicitly[Integral[A]].one)) + + /** Produces a $coll containing equally spaced values in some integer interval. + * @param start the start value of the $coll + * @param end the end value of the $coll (the first value NOT contained) + * @param step the difference between successive elements of the $coll (must be positive or negative) + * @return a $coll with values `start, start + step, ...` up to, but excluding `end` + */ + def range[A : Integral : ClassTag](start: A, end: A, step: A): CC[A] = from(NumericRange(start, end, step)) + + /** Produces a two-dimensional $coll containing the results of some element computation a number of times. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param elem the element computation + * @return A $coll that contains the results of `n1 x n2` evaluations of `elem`. + */ + def fill[A : ClassTag](n1: Int, n2: Int)(elem: => A): CC[CC[A] @uncheckedVariance] = fill(n1)(fill(n2)(elem)) + + /** Produces a three-dimensional $coll containing the results of some element computation a number of times. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param elem the element computation + * @return A $coll that contains the results of `n1 x n2 x n3` evaluations of `elem`. + */ + def fill[A : ClassTag](n1: Int, n2: Int, n3: Int)(elem: => A): CC[CC[CC[A]] @uncheckedVariance] = fill(n1)(fill(n2, n3)(elem)) + + /** Produces a four-dimensional $coll containing the results of some element computation a number of times. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param elem the element computation + * @return A $coll that contains the results of `n1 x n2 x n3 x n4` evaluations of `elem`. + */ + def fill[A : ClassTag](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => A): CC[CC[CC[CC[A]]] @uncheckedVariance] = + fill(n1)(fill(n2, n3, n4)(elem)) + + /** Produces a five-dimensional $coll containing the results of some element computation a number of times. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param n5 the number of elements in the 5th dimension + * @param elem the element computation + * @return A $coll that contains the results of `n1 x n2 x n3 x n4 x n5` evaluations of `elem`. + */ + def fill[A : ClassTag](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => A): CC[CC[CC[CC[CC[A]]]] @uncheckedVariance] = + fill(n1)(fill(n2, n3, n4, n5)(elem)) + + /** Produces a two-dimensional $coll containing values of a given function over ranges of integer values starting from 0. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param f The function computing element values + * @return A $coll consisting of elements `f(i1, i2)` + * for `0 <= i1 < n1` and `0 <= i2 < n2`. + */ + def tabulate[A : ClassTag](n1: Int, n2: Int)(f: (Int, Int) => A): CC[CC[A] @uncheckedVariance] = + tabulate(n1)(i1 => tabulate(n2)(f(i1, _))) + + /** Produces a three-dimensional $coll containing values of a given function over ranges of integer values starting from 0. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param f The function computing element values + * @return A $coll consisting of elements `f(i1, i2, i3)` + * for `0 <= i1 < n1`, `0 <= i2 < n2`, and `0 <= i3 < n3`. + */ + def tabulate[A : ClassTag](n1: Int, n2: Int, n3: Int)(f: (Int, Int, Int) => A): CC[CC[CC[A]] @uncheckedVariance] = + tabulate(n1)(i1 => tabulate(n2, n3)(f(i1, _, _))) + + /** Produces a four-dimensional $coll containing values of a given function over ranges of integer values starting from 0. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param f The function computing element values + * @return A $coll consisting of elements `f(i1, i2, i3, i4)` + * for `0 <= i1 < n1`, `0 <= i2 < n2`, `0 <= i3 < n3`, and `0 <= i4 < n4`. + */ + def tabulate[A : ClassTag](n1: Int, n2: Int, n3: Int, n4: Int)(f: (Int, Int, Int, Int) => A): CC[CC[CC[CC[A]]] @uncheckedVariance] = + tabulate(n1)(i1 => tabulate(n2, n3, n4)(f(i1, _, _, _))) + + /** Produces a five-dimensional $coll containing values of a given function over ranges of integer values starting from 0. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param n5 the number of elements in the 5th dimension + * @param f The function computing element values + * @return A $coll consisting of elements `f(i1, i2, i3, i4, i5)` + * for `0 <= i1 < n1`, `0 <= i2 < n2`, `0 <= i3 < n3`, `0 <= i4 < n4`, and `0 <= i5 < n5`. + */ + def tabulate[A : ClassTag](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(f: (Int, Int, Int, Int, Int) => A): CC[CC[CC[CC[CC[A]]]] @uncheckedVariance] = + tabulate(n1)(i1 => tabulate(n2, n3, n4, n5)(f(i1, _, _, _, _))) +} + +object ClassTagIterableFactory { + @SerialVersionUID(3L) + class Delegate[CC[_]](delegate: EvidenceIterableFactory[CC, ClassTag]) + extends EvidenceIterableFactory.Delegate[CC, ClassTag](delegate) with ClassTagIterableFactory[CC] + + /** An IterableFactory that uses ClassTag.Any as the evidence for every element type. This may or may not be + * sound depending on the use of the `ClassTag` by the collection implementation. */ + @SerialVersionUID(3L) + class AnyIterableDelegate[CC[_]](delegate: ClassTagIterableFactory[CC]) extends IterableFactory[CC] { + def empty[A]: CC[A] = delegate.empty(using ClassTag.Any).asInstanceOf[CC[A]] + def from[A](it: IterableOnce[A]): CC[A] = delegate.from[Any](it)(using ClassTag.Any).asInstanceOf[CC[A]] + def newBuilder[A]: Builder[A, CC[A]] = delegate.newBuilder(using ClassTag.Any).asInstanceOf[Builder[A, CC[A]]] + override def apply[A](elems: A*): CC[A] = delegate.apply[Any](elems: _*)(using ClassTag.Any).asInstanceOf[CC[A]] + override def iterate[A](start: A, len: Int)(f: A => A): CC[A] = delegate.iterate[A](start, len)(f)(using ClassTag.Any.asInstanceOf[ClassTag[A]]) + override def unfold[A, S](init: S)(f: S => Option[(A, S)]): CC[A] = delegate.unfold[A, S](init)(f)(using ClassTag.Any.asInstanceOf[ClassTag[A]]) + override def range[A](start: A, end: A)(implicit i: Integral[A]): CC[A] = delegate.range[A](start, end)(using i, ClassTag.Any.asInstanceOf[ClassTag[A]]) + override def range[A](start: A, end: A, step: A)(implicit i: Integral[A]): CC[A] = delegate.range[A](start, end, step)(using i, ClassTag.Any.asInstanceOf[ClassTag[A]]) + override def fill[A](n: Int)(elem: => A): CC[A] = delegate.fill[Any](n)(elem)(using ClassTag.Any).asInstanceOf[CC[A]] + override def tabulate[A](n: Int)(f: Int => A): CC[A] = delegate.tabulate[Any](n)(f)(using ClassTag.Any).asInstanceOf[CC[A]] + } +} + +/** + * @tparam CC Collection type constructor (e.g. `ArraySeq`) + */ +trait ClassTagSeqFactory[+CC[A] <: SeqOps[A, Seq, Seq[A]]] extends ClassTagIterableFactory[CC] { + import SeqFactory.UnapplySeqWrapper + final def unapplySeq[A](x: CC[A] @uncheckedVariance): UnapplySeqWrapper[A] = new UnapplySeqWrapper(x) // TODO is uncheckedVariance sound here? +} + +object ClassTagSeqFactory { + @SerialVersionUID(3L) + class Delegate[CC[A] <: SeqOps[A, Seq, Seq[A]]](delegate: ClassTagSeqFactory[CC]) + extends ClassTagIterableFactory.Delegate[CC](delegate) with ClassTagSeqFactory[CC] + + /** A SeqFactory that uses ClassTag.Any as the evidence for every element type. This may or may not be + * sound depending on the use of the `ClassTag` by the collection implementation. */ + @SerialVersionUID(3L) + class AnySeqDelegate[CC[A] <: SeqOps[A, Seq, Seq[A]]](delegate: ClassTagSeqFactory[CC]) + extends ClassTagIterableFactory.AnyIterableDelegate[CC](delegate) with SeqFactory[CC] +} + +trait StrictOptimizedClassTagSeqFactory[+CC[A] <: SeqOps[A, Seq, Seq[A]]] extends ClassTagSeqFactory[CC] { + + override def fill[A : ClassTag](n: Int)(elem: => A): CC[A] = { + val b = newBuilder[A] + b.sizeHint(n) + var i = 0 + while (i < n) { + b += elem + i += 1 + } + b.result() + } + + override def tabulate[A : ClassTag](n: Int)(f: Int => A): CC[A] = { + val b = newBuilder[A] + b.sizeHint(n) + var i = 0 + while (i < n) { + b += f(i) + i += 1 + } + b.result() + } + +} + +/** + * @define factoryInfo + * This object provides a set of operations to create $Coll values. + * + * @define coll collection + * @define Coll `Iterable` + */ +trait SortedMapFactory[+CC[_, _]] extends Serializable { + + def empty[K : Ordering, V]: CC[K, V] + + def from[K : Ordering, V](it: IterableOnce[(K, V)]): CC[K, V] + + def apply[K : Ordering, V](elems: (K, V)*): CC[K, V] = from(elems) + + def newBuilder[K : Ordering, V]: Builder[(K, V), CC[K, V]] + + implicit def sortedMapFactory[K : Ordering, V]: Factory[(K, V), CC[K, V]] = SortedMapFactory.toFactory(this) + +} + +object SortedMapFactory { + + /** + * Implicit conversion that fixes the key and value types of `factory` to `K` and `V`, + * respectively. + * + * @param factory The factory to fix the key and value types + * @tparam K Type of keys + * @tparam V Type of values + * @tparam CC Collection type constructor of the factory (e.g. `TreeMap`) + * @return A [[Factory]] that uses the given `factory` to build a map with keys of + * type `K` and values of type `V` + */ + implicit def toFactory[K : Ordering, V, CC[_, _]](factory: SortedMapFactory[CC]): Factory[(K, V), CC[K, V]] = new ToFactory[K, V, CC](factory) + + @SerialVersionUID(3L) + private[this] class ToFactory[K : Ordering, V, CC[_, _]](factory: SortedMapFactory[CC]) extends Factory[(K, V), CC[K, V]] with Serializable { + def fromSpecific(it: IterableOnce[(K, V)]): CC[K, V] = factory.from[K, V](it) + def newBuilder: Builder[(K, V), CC[K, V]] = factory.newBuilder[K, V] + } + + implicit def toBuildFrom[K : Ordering, V, CC[_, _]](factory: SortedMapFactory[CC]): BuildFrom[Any, (K, V), CC[K, V]] = new SortedMapFactoryToBuildFrom(factory) + private class SortedMapFactoryToBuildFrom[K : Ordering, V, CC[_, _]](factory: SortedMapFactory[CC]) extends BuildFrom[Any, (K, V), CC[K, V]] { + def fromSpecific(from: Any)(it: IterableOnce[(K, V)]) = factory.from(it) + def newBuilder(from: Any) = factory.newBuilder[K, V] + } + + @SerialVersionUID(3L) + class Delegate[CC[_, _]](delegate: SortedMapFactory[CC]) extends SortedMapFactory[CC] { + override def apply[K: Ordering, V](elems: (K, V)*): CC[K, V] = delegate.apply(elems: _*) + def from[K : Ordering, V](it: IterableOnce[(K, V)]): CC[K, V] = delegate.from(it) + def empty[K : Ordering, V]: CC[K, V] = delegate.empty + def newBuilder[K : Ordering, V]: Builder[(K, V), CC[K, V]] = delegate.newBuilder + } +} diff --git a/library/src/scala/collection/Hashing.scala b/library/src/scala/collection/Hashing.scala new file mode 100644 index 000000000000..5ba8ad24948f --- /dev/null +++ b/library/src/scala/collection/Hashing.scala @@ -0,0 +1,63 @@ +/* + * 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 collection + +import scala.language.`2.13` + +protected[collection] object Hashing { + + def elemHashCode(key: Any): Int = key.## + + def improve(hcode: Int): Int = { + var h: Int = hcode + ~(hcode << 9) + h = h ^ (h >>> 14) + h = h + (h << 4) + h ^ (h >>> 10) + } + + def computeHash(key: Any): Int = + improve(elemHashCode(key)) + + /** + * Utility method to keep a subset of all bits in a given bitmap + * + * Example + * bitmap (binary): 00000001000000010000000100000001 + * keep (binary): 1010 + * result (binary): 00000001000000000000000100000000 + * + * @param bitmap the bitmap + * @param keep a bitmask containing which bits to keep + * @return the original bitmap with all bits where keep is not 1 set to 0 + */ + def keepBits(bitmap: Int, keep: Int): Int = { + var result = 0 + var current = bitmap + var kept = keep + while (kept != 0) { + // lowest remaining bit in current + val lsb = current ^ (current & (current - 1)) + if ((kept & 1) != 0) { + // mark bit in result bitmap + result |= lsb + } + // clear lowest remaining one bit in abm + current &= ~lsb + // look at the next kept bit + kept >>>= 1 + } + result + } + +} diff --git a/library/src/scala/collection/IndexedSeq.scala b/library/src/scala/collection/IndexedSeq.scala new file mode 100644 index 000000000000..366fd526a022 --- /dev/null +++ b/library/src/scala/collection/IndexedSeq.scala @@ -0,0 +1,176 @@ +/* + * 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 collection + +import scala.language.`2.13` +import scala.annotation.{nowarn, tailrec} +import scala.collection.Searching.{Found, InsertionPoint, SearchResult} +import scala.collection.Stepper.EfficientSplit +import scala.math.Ordering + +/** Base trait for indexed sequences that have efficient `apply` and `length` */ +trait IndexedSeq[+A] extends Seq[A] + with IndexedSeqOps[A, IndexedSeq, IndexedSeq[A]] + with IterableFactoryDefaults[A, IndexedSeq] { + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "IndexedSeq" + + override def iterableFactory: SeqFactory[IndexedSeq] = IndexedSeq +} + +@SerialVersionUID(3L) +object IndexedSeq extends SeqFactory.Delegate[IndexedSeq](immutable.IndexedSeq) + +/** Base trait for indexed Seq operations */ +transparent trait IndexedSeqOps[+A, +CC[_], +C] extends Any with SeqOps[A, CC, C] { self => + + def iterator: Iterator[A] = view.iterator + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit = { + import convert.impl._ + val s = shape.shape match { + case StepperShape.IntShape => new IntIndexedSeqStepper (this.asInstanceOf[IndexedSeqOps[Int, AnyConstr, _]], 0, length) + case StepperShape.LongShape => new LongIndexedSeqStepper (this.asInstanceOf[IndexedSeqOps[Long, AnyConstr, _]], 0, length) + case StepperShape.DoubleShape => new DoubleIndexedSeqStepper(this.asInstanceOf[IndexedSeqOps[Double, AnyConstr, _]], 0, length) + case _ => shape.parUnbox(new AnyIndexedSeqStepper[A](this, 0, length)) + } + s.asInstanceOf[S with EfficientSplit] + } + + override def reverseIterator: Iterator[A] = view.reverseIterator + + /* TODO 2.14+ uncomment and delete related code in IterableOnce + @tailrec private def foldl[B](start: Int, end: Int, z: B, op: (B, A) => B): B = + if (start == end) z + else foldl(start + 1, end, op(z, apply(start)), op) + */ + + @tailrec private def foldr[B](start: Int, end: Int, z: B, op: (A, B) => B): B = + if (start == end) z + else foldr(start, end - 1, op(apply(end - 1), z), op) + + //override def foldLeft[B](z: B)(op: (B, A) => B): B = foldl(0, length, z, op) + + override def foldRight[B](z: B)(op: (A, B) => B): B = foldr(0, length, z, op) + + //override def reduceLeft[B >: A](op: (B, A) => B): B = if (length > 0) foldl(1, length, apply(0), op) else super.reduceLeft(op) + + //override def reduceRight[B >: A](op: (A, B) => B): B = if (length > 0) foldr(0, length - 1, apply(length - 1), op) else super.reduceRight(op) + + override def view: IndexedSeqView[A] = new IndexedSeqView.Id[A](this) + + @deprecated("Use .view.slice(from, until) instead of .view(from, until)", "2.13.0") + override def view(from: Int, until: Int): IndexedSeqView[A] = view.slice(from, until) + + override protected def reversed: Iterable[A] = new IndexedSeqView.Reverse(this) + + // Override transformation operations to use more efficient views than the default ones + override def prepended[B >: A](elem: B): CC[B] = iterableFactory.from(new IndexedSeqView.Prepended(elem, this)) + + override def take(n: Int): C = fromSpecific(new IndexedSeqView.Take(this, n)) + + override def takeRight(n: Int): C = fromSpecific(new IndexedSeqView.TakeRight(this, n)) + + override def drop(n: Int): C = fromSpecific(new IndexedSeqView.Drop(this, n)) + + override def dropRight(n: Int): C = fromSpecific(new IndexedSeqView.DropRight(this, n)) + + override def map[B](f: A => B): CC[B] = iterableFactory.from(new IndexedSeqView.Map(this, f)) + + override def reverse: C = fromSpecific(new IndexedSeqView.Reverse(this)) + + override def slice(from: Int, until: Int): C = fromSpecific(new IndexedSeqView.Slice(this, from, until)) + + override def sliding(size: Int, step: Int): Iterator[C] = { + require(size >= 1 && step >= 1, f"size=$size%d and step=$step%d, but both must be positive") + new IndexedSeqSlidingIterator[A, CC, C](this, size, step) + } + + override def head: A = + if (!isEmpty) apply(0) + else throw new NoSuchElementException(s"head of empty ${ + self match { + case self: IndexedSeq[_] => self.collectionClassName + case _ => toString + } + }") + + override def headOption: Option[A] = if (isEmpty) None else Some(head) + + override def last: A = + if (!isEmpty) apply(length - 1) + else throw new NoSuchElementException(s"last of empty ${ + self match { + case self: IndexedSeq[_] => self.collectionClassName + case _ => toString + } + }") + + // We already inherit an efficient `lastOption = if (isEmpty) None else Some(last)` + + override final def lengthCompare(len: Int): Int = Integer.compare(length, len) + + override def knownSize: Int = length + + override final def lengthCompare(that: Iterable[_]): Int = { + val res = that.sizeCompare(length) + // can't just invert the result, because `-Int.MinValue == Int.MinValue` + if (res == Int.MinValue) 1 else -res + } + + override def search[B >: A](elem: B)(implicit ord: Ordering[B]): SearchResult = + binarySearch(elem, 0, length)(ord) + + override def search[B >: A](elem: B, from: Int, to: Int)(implicit ord: Ordering[B]): SearchResult = + binarySearch(elem, from, to)(ord) + + @tailrec + private[this] def binarySearch[B >: A](elem: B, from: Int, to: Int) + (implicit ord: Ordering[B]): SearchResult = { + if (from < 0) binarySearch(elem, 0, to) + else if (to > length) binarySearch(elem, from, length) + else if (to <= from) InsertionPoint(from) + else { + val idx = from + (to - from - 1) / 2 + math.signum(ord.compare(elem, apply(idx))) match { + case -1 => binarySearch(elem, from, idx)(ord) + case 1 => binarySearch(elem, idx + 1, to)(ord) + case _ => Found(idx) + } + } + } +} + +/** A fast sliding iterator for IndexedSeqs which uses the underlying `slice` operation. */ +private final class IndexedSeqSlidingIterator[A, CC[_], C](s: IndexedSeqOps[A, CC, C], size: Int, step: Int) + extends AbstractIterator[C] { + + private[this] val len = s.length + private[this] var pos = 0 + private def chklen: Boolean = len == s.length || { + throw new java.util.ConcurrentModificationException("collection size changed during iteration") + false + } + + def hasNext: Boolean = chklen && pos < len + + def next(): C = if (!chklen || !hasNext) Iterator.empty.next() else { + val end = { val x = pos + size; if (x < 0 || x > len) len else x } // (pos.toLong + size).min(len).toInt + val slice = s.slice(pos, end) + pos = + if (end >= len) len + else { val x = pos + step; if (x < 0 || x > len) len else x } // (pos.toLong + step).min(len).toInt + slice + } +} diff --git a/library/src/scala/collection/IndexedSeqView.scala b/library/src/scala/collection/IndexedSeqView.scala new file mode 100644 index 000000000000..d03ff1b1c689 --- /dev/null +++ b/library/src/scala/collection/IndexedSeqView.scala @@ -0,0 +1,181 @@ +/* + * 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 collection + +import scala.language.`2.13` +import scala.annotation.nowarn + + +/** View defined in terms of indexing a range */ +trait IndexedSeqView[+A] extends IndexedSeqOps[A, View, View[A]] with SeqView[A] { self => + + override def view: IndexedSeqView[A] = this + + @deprecated("Use .view.slice(from, until) instead of .view(from, until)", "2.13.0") + override def view(from: Int, until: Int): IndexedSeqView[A] = view.slice(from, until) + + override def iterator: Iterator[A] = new IndexedSeqView.IndexedSeqViewIterator(this) + override def reverseIterator: Iterator[A] = new IndexedSeqView.IndexedSeqViewReverseIterator(this) + + override def appended[B >: A](elem: B): IndexedSeqView[B] = new IndexedSeqView.Appended(this, elem) + override def prepended[B >: A](elem: B): IndexedSeqView[B] = new IndexedSeqView.Prepended(elem, this) + override def take(n: Int): IndexedSeqView[A] = new IndexedSeqView.Take(this, n) + override def takeRight(n: Int): IndexedSeqView[A] = new IndexedSeqView.TakeRight(this, n) + override def drop(n: Int): IndexedSeqView[A] = new IndexedSeqView.Drop(this, n) + override def dropRight(n: Int): IndexedSeqView[A] = new IndexedSeqView.DropRight(this, n) + override def map[B](f: A => B): IndexedSeqView[B] = new IndexedSeqView.Map(this, f) + override def reverse: IndexedSeqView[A] = new IndexedSeqView.Reverse(this) + override def slice(from: Int, until: Int): IndexedSeqView[A] = new IndexedSeqView.Slice(this, from, until) + override def tapEach[U](f: A => U): IndexedSeqView[A] = new IndexedSeqView.Map(this, { (a: A) => f(a); a}) + + def concat[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new IndexedSeqView.Concat(this, suffix) + def appendedAll[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new IndexedSeqView.Concat(this, suffix) + def prependedAll[B >: A](prefix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new IndexedSeqView.Concat(prefix, this) + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "IndexedSeqView" +} + +object IndexedSeqView { + + @SerialVersionUID(3L) + private[collection] class IndexedSeqViewIterator[A](self: IndexedSeqView[A]) extends AbstractIterator[A] with Serializable { + private[this] var current = 0 + private[this] var remainder = self.length + override def knownSize: Int = remainder + @inline private[this] def _hasNext: Boolean = remainder > 0 + def hasNext: Boolean = _hasNext + def next(): A = + if (_hasNext) { + val r = self(current) + current += 1 + remainder -= 1 + r + } else Iterator.empty.next() + + override def drop(n: Int): Iterator[A] = { + if (n > 0) { + current += n + remainder = Math.max(0, remainder - n) + } + this + } + + override protected def sliceIterator(from: Int, until: Int): Iterator[A] = { + + def formatRange(value : Int) : Int = if (value < 0) 0 else if (value > remainder) remainder else value + + val formatFrom = formatRange(from) + val formatUntil = formatRange(until) + remainder = Math.max(0, formatUntil - formatFrom) + current = current + formatFrom + this + } + } + @SerialVersionUID(3L) + private[collection] class IndexedSeqViewReverseIterator[A](self: IndexedSeqView[A]) extends AbstractIterator[A] with Serializable { + private[this] var remainder = self.length + private[this] var pos = remainder - 1 + @inline private[this] def _hasNext: Boolean = remainder > 0 + def hasNext: Boolean = _hasNext + def next(): A = + if (_hasNext) { + val r = self(pos) + pos -= 1 + remainder -= 1 + r + } else Iterator.empty.next() + + // from < 0 means don't move pos, until < 0 means don't limit remainder + // + override protected def sliceIterator(from: Int, until: Int): Iterator[A] = { + if (_hasNext) { + if (remainder <= from) remainder = 0 // exhausted by big skip + else if (from <= 0) { // no skip, pos is same + if (until >= 0 && until < remainder) remainder = until // ...limited by until + } + else { + pos -= from // skip ahead + if (until >= 0 && until < remainder) { // ...limited by until + if (until <= from) remainder = 0 // ...exhausted if limit is smaller than skip + else remainder = until - from // ...limited by until, less the skip + } + else remainder -= from // ...otherwise just less the skip + } + } + this + } + } + + /** An `IndexedSeqOps` whose collection type and collection type constructor are unknown */ + type SomeIndexedSeqOps[A] = IndexedSeqOps[A, AnyConstr, _] + + @SerialVersionUID(3L) + class Id[+A](underlying: SomeIndexedSeqOps[A]) + extends SeqView.Id(underlying) with IndexedSeqView[A] + + @SerialVersionUID(3L) + class Appended[+A](underlying: SomeIndexedSeqOps[A], elem: A) + extends SeqView.Appended(underlying, elem) with IndexedSeqView[A] + + @SerialVersionUID(3L) + class Prepended[+A](elem: A, underlying: SomeIndexedSeqOps[A]) + extends SeqView.Prepended(elem, underlying) with IndexedSeqView[A] + + @SerialVersionUID(3L) + class Concat[A](prefix: SomeIndexedSeqOps[A], suffix: SomeIndexedSeqOps[A]) + extends SeqView.Concat[A](prefix, suffix) with IndexedSeqView[A] + + @SerialVersionUID(3L) + class Take[A](underlying: SomeIndexedSeqOps[A], n: Int) + extends SeqView.Take(underlying, n) with IndexedSeqView[A] + + @SerialVersionUID(3L) + class TakeRight[A](underlying: SomeIndexedSeqOps[A], n: Int) + extends SeqView.TakeRight(underlying, n) with IndexedSeqView[A] + + @SerialVersionUID(3L) + class Drop[A](underlying: SomeIndexedSeqOps[A], n: Int) + extends SeqView.Drop[A](underlying, n) with IndexedSeqView[A] + + @SerialVersionUID(3L) + class DropRight[A](underlying: SomeIndexedSeqOps[A], n: Int) + extends SeqView.DropRight[A](underlying, n) with IndexedSeqView[A] + + @SerialVersionUID(3L) + class Map[A, B](underlying: SomeIndexedSeqOps[A], f: A => B) + extends SeqView.Map(underlying, f) with IndexedSeqView[B] + + @SerialVersionUID(3L) + class Reverse[A](underlying: SomeIndexedSeqOps[A]) extends SeqView.Reverse[A](underlying) with IndexedSeqView[A] { + override def reverse: IndexedSeqView[A] = underlying match { + case x: IndexedSeqView[A] => x + case _ => super.reverse + } + } + + @SerialVersionUID(3L) + class Slice[A](underlying: SomeIndexedSeqOps[A], from: Int, until: Int) extends AbstractIndexedSeqView[A] { + protected val lo = from max 0 + protected val hi = (until max 0) min underlying.length + protected val len = (hi - lo) max 0 + @throws[IndexOutOfBoundsException] + def apply(i: Int): A = underlying(lo + i) + def length: Int = len + } +} + +/** Explicit instantiation of the `IndexedSeqView` trait to reduce class file size in subclasses. */ +@SerialVersionUID(3L) +abstract class AbstractIndexedSeqView[+A] extends AbstractSeqView[A] with IndexedSeqView[A] diff --git a/library/src/scala/collection/Iterable.scala b/library/src/scala/collection/Iterable.scala new file mode 100644 index 000000000000..641b4bb4745e --- /dev/null +++ b/library/src/scala/collection/Iterable.scala @@ -0,0 +1,1047 @@ +/* + * 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 collection + +import scala.language.`2.13` +import scala.annotation.nowarn +import scala.annotation.unchecked.uncheckedVariance +import scala.collection.mutable.Builder +import scala.collection.View.{LeftPartitionMapped, RightPartitionMapped} + +/** Base trait for generic collections. + * + * @tparam A the element type of the collection + * + * @define Coll `Iterable` + * @define coll iterable collection + */ +trait Iterable[+A] extends IterableOnce[A] + with IterableOps[A, Iterable, Iterable[A]] + with IterableFactoryDefaults[A, Iterable] { + + // The collection itself + @deprecated("toIterable is internal and will be made protected; its name is similar to `toList` or `toSeq`, but it doesn't copy non-immutable collections", "2.13.7") + final def toIterable: this.type = this + + final protected def coll: this.type = this + + def iterableFactory: IterableFactory[Iterable] = Iterable + + @deprecated("Iterable.seq always returns the iterable itself", "2.13.0") + def seq: this.type = this + + /** Defines the prefix of this object's `toString` representation. + * + * It is recommended to return the name of the concrete collection type, but + * not implementation subclasses. For example, for `ListMap` this method should + * return `"ListMap"`, not `"Map"` (the supertype) or `"Node"` (an implementation + * subclass). + * + * The default implementation returns "Iterable". It is overridden for the basic + * collection kinds "Seq", "IndexedSeq", "LinearSeq", "Buffer", "Set", "Map", + * "SortedSet", "SortedMap" and "View". + * + * @return a string representation which starts the result of `toString` + * applied to this $coll. By default the string prefix is the + * simple name of the collection class $coll. + */ + protected[this] def className: String = stringPrefix + + /** Forwarder to `className` for use in `scala.runtime.ScalaRunTime`. + * + * This allows the proper visibility for `className` to be + * published, but provides the exclusive access needed by + * `scala.runtime.ScalaRunTime.stringOf` (and a few tests in + * the test suite). + */ + private[scala] final def collectionClassName: String = className + + @deprecatedOverriding("Override className instead", "2.13.0") + protected[this] def stringPrefix: String = "Iterable" + + /** Converts this $coll to a string. + * + * @return a string representation of this collection. By default this + * string consists of the `className` of this $coll, followed + * by all elements separated by commas and enclosed in parentheses. + */ + override def toString = mkString(className + "(", ", ", ")") + + /** Analogous to `zip` except that the elements in each collection are not consumed until a strict operation is + * invoked on the returned `LazyZip2` decorator. + * + * Calls to `lazyZip` can be chained to support higher arities (up to 4) without incurring the expense of + * constructing and deconstructing intermediary tuples. + * + * {{{ + * val xs = List(1, 2, 3) + * val res = (xs lazyZip xs lazyZip xs lazyZip xs).map((a, b, c, d) => a + b + c + d) + * // res == List(4, 8, 12) + * }}} + * + * @param that the iterable providing the second element of each eventual pair + * @tparam B the type of the second element in each eventual pair + * @return a decorator `LazyZip2` that allows strict operations to be performed on the lazily evaluated pairs + * or chained calls to `lazyZip`. Implicit conversion to `Iterable[(A, B)]` is also supported. + */ + def lazyZip[B](that: Iterable[B]): LazyZip2[A, B, this.type] = new LazyZip2(this, this, that) +} + +/** Base trait for Iterable operations + * + * =VarianceNote= + * + * We require that for all child classes of Iterable the variance of + * the child class and the variance of the `C` parameter passed to `IterableOps` + * are the same. We cannot express this since we lack variance polymorphism. That's + * why we have to resort at some places to write `C[A @uncheckedVariance]`. + * + * @tparam CC type constructor of the collection (e.g. `List`, `Set`). Operations returning a collection + * with a different type of element `B` (e.g. `map`) return a `CC[B]`. + * @tparam C type of the collection (e.g. `List[Int]`, `String`, `BitSet`). Operations returning a collection + * with the same type of element (e.g. `drop`, `filter`) return a `C`. + * + * @define Coll Iterable + * @define coll iterable collection + * @define orderDependent + * + * Note: might return different results for different runs, unless the underlying collection type is ordered. + * @define orderDependentFold + * + * Note: might return different results for different runs, unless the + * underlying collection type is ordered or the operator is associative + * and commutative. + * @define mayNotTerminateInf + * + * Note: may not terminate for infinite-sized collections. + * @define willNotTerminateInf + * + * Note: will not terminate for infinite-sized collections. + * @define undefinedorder + * The order in which operations are performed on elements is unspecified + * and may be nondeterministic. + */ +transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with IterableOnceOps[A, CC, C] { + /** + * @return This collection as an `Iterable[A]`. No new collection will be built if `this` is already an `Iterable[A]`. + */ + // Should be `protected def asIterable`, or maybe removed altogether if it's not needed + @deprecated("toIterable is internal and will be made protected; its name is similar to `toList` or `toSeq`, but it doesn't copy non-immutable collections", "2.13.7") + def toIterable: Iterable[A] + + /** Converts this $coll to an unspecified Iterable. Will return + * the same collection if this instance is already Iterable. + * @return An Iterable containing all elements of this $coll. + */ + @deprecated("toTraversable is internal and will be made protected; its name is similar to `toList` or `toSeq`, but it doesn't copy non-immutable collections", "2.13.0") + final def toTraversable: Traversable[A] = toIterable + + override def isTraversableAgain: Boolean = true + + /** + * @return This collection as a `C`. + */ + protected def coll: C + + @deprecated("Use coll instead of repr in a collection implementation, use the collection value itself from the outside", "2.13.0") + final def repr: C = coll + + /** + * Defines how to turn a given `Iterable[A]` into a collection of type `C`. + * + * This process can be done in a strict way or a non-strict way (ie. without evaluating + * the elements of the resulting collections). In other words, this methods defines + * the evaluation model of the collection. + * + * @note When implementing a custom collection type and refining `C` to the new type, this + * method needs to be overridden (the compiler will issue an error otherwise). In the + * common case where `C =:= CC[A]`, this can be done by mixing in the + * [[scala.collection.IterableFactoryDefaults]] trait, which implements the method using + * [[iterableFactory]]. + * + * @note As witnessed by the `@uncheckedVariance` annotation, using this method + * might be unsound. However, as long as it is called with an + * `Iterable[A]` obtained from `this` collection (as it is the case in the + * implementations of operations where we use a `View[A]`), it is safe. + */ + protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]): C + + /** The companion object of this ${coll}, providing various factory methods. + * + * @note When implementing a custom collection type and refining `CC` to the new type, this + * method needs to be overridden to return a factory for the new type (the compiler will + * issue an error otherwise). + */ + def iterableFactory: IterableFactory[CC] + + @deprecated("Use iterableFactory instead", "2.13.0") + @deprecatedOverriding("Use iterableFactory instead", "2.13.0") + @`inline` def companion: IterableFactory[CC] = iterableFactory + + /** + * @return a strict builder for the same collection type. + * + * Note that in the case of lazy collections (e.g. [[scala.collection.View]] or [[scala.collection.immutable.LazyList]]), + * it is possible to implement this method but the resulting `Builder` will break laziness. + * As a consequence, operations should preferably be implemented with `fromSpecific` + * instead of this method. + * + * @note When implementing a custom collection type and refining `C` to the new type, this + * method needs to be overridden (the compiler will issue an error otherwise). In the + * common case where `C =:= CC[A]`, this can be done by mixing in the + * [[scala.collection.IterableFactoryDefaults]] trait, which implements the method using + * [[iterableFactory]]. + * + * @note As witnessed by the `@uncheckedVariance` annotation, using this method might + * be unsound. However, as long as the returned builder is only fed + * with `A` values taken from `this` instance, it is safe. + */ + protected def newSpecificBuilder: Builder[A @uncheckedVariance, C] + + /** The empty $coll. + * + * @return an empty iterable of type $Coll. + */ + def empty: C = fromSpecific(Nil) + + /** Selects the first element of this $coll. + * $orderDependent + * @return the first element of this $coll. + * @throws NoSuchElementException if the $coll is empty. + */ + def head: A = iterator.next() + + /** Optionally selects the first element. + * $orderDependent + * @return the first element of this $coll if it is nonempty, + * `None` if it is empty. + */ + def headOption: Option[A] = { + val it = iterator + if (it.hasNext) Some(it.next()) else None + } + + /** Selects the last element. + * $orderDependent + * @return The last element of this $coll. + * @throws NoSuchElementException If the $coll is empty. + */ + def last: A = { + val it = iterator + var lst = it.next() + while (it.hasNext) lst = it.next() + lst + } + + /** Optionally selects the last element. + * $orderDependent + * @return the last element of this $coll if it is nonempty, + * `None` if it is empty. + */ + def lastOption: Option[A] = if (isEmpty) None else Some(last) + + /** A view over the elements of this collection. */ + def view: View[A] = View.fromIteratorProvider(() => iterator) + + /** Compares the size of this $coll to a test value. + * + * @param otherSize the test value that gets compared with the size. + * @return A value `x` where + * {{{ + * x < 0 if this.size < otherSize + * x == 0 if this.size == otherSize + * x > 0 if this.size > otherSize + * }}} + * + * The method as implemented here does not call `size` directly; its running time + * is `O(size min otherSize)` instead of `O(size)`. The method should be overridden + * if computing `size` is cheap and `knownSize` returns `-1`. + * + * @see [[sizeIs]] + */ + def sizeCompare(otherSize: Int): Int = + if (otherSize < 0) 1 + else { + val known = knownSize + if (known >= 0) Integer.compare(known, otherSize) + else { + var i = 0 + val it = iterator + while (it.hasNext) { + if (i == otherSize) return 1 + it.next() + i += 1 + } + i - otherSize + } + } + + /** Returns a value class containing operations for comparing the size of this $coll to a test value. + * + * These operations are implemented in terms of [[sizeCompare(Int) `sizeCompare(Int)`]], and + * allow the following more readable usages: + * + * {{{ + * this.sizeIs < size // this.sizeCompare(size) < 0 + * this.sizeIs <= size // this.sizeCompare(size) <= 0 + * this.sizeIs == size // this.sizeCompare(size) == 0 + * this.sizeIs != size // this.sizeCompare(size) != 0 + * this.sizeIs >= size // this.sizeCompare(size) >= 0 + * this.sizeIs > size // this.sizeCompare(size) > 0 + * }}} + */ + @inline final def sizeIs: IterableOps.SizeCompareOps = new IterableOps.SizeCompareOps(this) + + /** Compares the size of this $coll to the size of another `Iterable`. + * + * @param that the `Iterable` whose size is compared with this $coll's size. + * @return A value `x` where + * {{{ + * x < 0 if this.size < that.size + * x == 0 if this.size == that.size + * x > 0 if this.size > that.size + * }}} + * + * The method as implemented here does not call `size` directly; its running time + * is `O(this.size min that.size)` instead of `O(this.size + that.size)`. + * The method should be overridden if computing `size` is cheap and `knownSize` returns `-1`. + */ + def sizeCompare(that: Iterable[_]): Int = { + val thatKnownSize = that.knownSize + + if (thatKnownSize >= 0) this sizeCompare thatKnownSize + else { + val thisKnownSize = this.knownSize + + if (thisKnownSize >= 0) { + val res = that sizeCompare thisKnownSize + // can't just invert the result, because `-Int.MinValue == Int.MinValue` + if (res == Int.MinValue) 1 else -res + } else { + val thisIt = this.iterator + val thatIt = that.iterator + while (thisIt.hasNext && thatIt.hasNext) { + thisIt.next() + thatIt.next() + } + java.lang.Boolean.compare(thisIt.hasNext, thatIt.hasNext) + } + } + } + + /** A view over a slice of the elements of this collection. */ + @deprecated("Use .view.slice(from, until) instead of .view(from, until)", "2.13.0") + def view(from: Int, until: Int): View[A] = view.slice(from, until) + + /** Transposes this $coll of iterable collections into + * a $coll of ${coll}s. + * + * The resulting collection's type will be guided by the + * static type of $coll. For example: + * + * {{{ + * val xs = List( + * Set(1, 2, 3), + * Set(4, 5, 6)).transpose + * // xs == List( + * // List(1, 4), + * // List(2, 5), + * // List(3, 6)) + * + * val ys = Vector( + * List(1, 2, 3), + * List(4, 5, 6)).transpose + * // ys == Vector( + * // Vector(1, 4), + * // Vector(2, 5), + * // Vector(3, 6)) + * }}} + * + * $willForceEvaluation + * + * @tparam B the type of the elements of each iterable collection. + * @param asIterable an implicit conversion which asserts that the + * element type of this $coll is an `Iterable`. + * @return a two-dimensional $coll of ${coll}s which has as ''n''th row + * the ''n''th column of this $coll. + * @throws IllegalArgumentException if all collections in this $coll + * are not of the same size. + */ + def transpose[B](implicit asIterable: A => /*<:= headSize) fail + bs(i) += x + i += 1 + } + if (i != headSize) + fail + } + iterableFactory.from(bs.map(_.result())) + } + + def filter(pred: A => Boolean): C = fromSpecific(new View.Filter(this, pred, isFlipped = false)) + + def filterNot(pred: A => Boolean): C = fromSpecific(new View.Filter(this, pred, isFlipped = true)) + + /** Creates a non-strict filter of this $coll. + * + * Note: the difference between `c filter p` and `c withFilter p` is that + * the former creates a new collection, whereas the latter only + * restricts the domain of subsequent `map`, `flatMap`, `foreach`, + * and `withFilter` operations. + * $orderDependent + * + * @param p the predicate used to test elements. + * @return an object of class `WithFilter`, which supports + * `map`, `flatMap`, `foreach`, and `withFilter` operations. + * All these operations apply to those elements of this $coll + * which satisfy the predicate `p`. + */ + def withFilter(p: A => Boolean): collection.WithFilter[A, CC] = new IterableOps.WithFilter(this, p) + + /** A pair of, first, all elements that satisfy predicate `p` and, second, + * all elements that do not. + * + * The two $coll correspond to the result of [[filter]] and [[filterNot]], respectively. + * + * The default implementation provided here needs to traverse the collection twice. + * Strict collections have an overridden version of `partition` in `StrictOptimizedIterableOps`, + * which requires only a single traversal. + */ + def partition(p: A => Boolean): (C, C) = { + val first = new View.Filter(this, p, isFlipped = false) + val second = new View.Filter(this, p, isFlipped = true) + (fromSpecific(first), fromSpecific(second)) + } + + override def splitAt(n: Int): (C, C) = (take(n), drop(n)) + + def take(n: Int): C = fromSpecific(new View.Take(this, n)) + + /** Selects the last ''n'' elements. + * $orderDependent + * @param n the number of elements to take from this $coll. + * @return a $coll consisting only of the last `n` elements of this $coll, + * or else the whole $coll, if it has less than `n` elements. + * If `n` is negative, returns an empty $coll. + */ + def takeRight(n: Int): C = fromSpecific(new View.TakeRight(this, n)) + + /** Takes longest prefix of elements that satisfy a predicate. + * $orderDependent + * @param p The predicate used to test elements. + * @return the longest prefix of this $coll whose elements all satisfy + * the predicate `p`. + */ + def takeWhile(p: A => Boolean): C = fromSpecific(new View.TakeWhile(this, p)) + + def span(p: A => Boolean): (C, C) = (takeWhile(p), dropWhile(p)) + + def drop(n: Int): C = fromSpecific(new View.Drop(this, n)) + + /** Selects all elements except last ''n'' ones. + * $orderDependent + * @param n the number of elements to drop from this $coll. + * @return a $coll consisting of all elements of this $coll except the last `n` ones, or else the + * empty $coll, if this $coll has less than `n` elements. + * If `n` is negative, don't drop any elements. + */ + def dropRight(n: Int): C = fromSpecific(new View.DropRight(this, n)) + + def dropWhile(p: A => Boolean): C = fromSpecific(new View.DropWhile(this, p)) + + /** Partitions elements in fixed size ${coll}s. + * @see [[scala.collection.Iterator]], method `grouped` + * + * @param size the number of elements per group + * @return An iterator producing ${coll}s of size `size`, except the + * last will be less than size `size` if the elements don't divide evenly. + */ + def grouped(size: Int): Iterator[C] = + iterator.grouped(size).map(fromSpecific) + + /** Groups elements in fixed size blocks by passing a "sliding window" + * over them (as opposed to partitioning them, as is done in `grouped`). + * + * An empty collection returns an empty iterator, and a non-empty + * collection containing fewer elements than the window size returns + * an iterator that will produce the original collection as its only + * element. + * @see [[scala.collection.Iterator]], method `sliding` + * + * @param size the number of elements per group + * @return An iterator producing ${coll}s of size `size`, except for a + * non-empty collection with less than `size` elements, which + * returns an iterator that produces the source collection itself + * as its only element. + * @example `List().sliding(2) = empty iterator` + * @example `List(1).sliding(2) = Iterator(List(1))` + * @example `List(1, 2).sliding(2) = Iterator(List(1, 2))` + * @example `List(1, 2, 3).sliding(2) = Iterator(List(1, 2), List(2, 3))` + */ + def sliding(size: Int): Iterator[C] = sliding(size, 1) + + /** Groups elements in fixed size blocks by passing a "sliding window" + * over them (as opposed to partitioning them, as is done in `grouped`). + * + * The returned iterator will be empty when called on an empty collection. + * The last element the iterator produces may be smaller than the window + * size when the original collection isn't exhausted by the window before + * it and its last element isn't skipped by the step before it. + * + * @see [[scala.collection.Iterator]], method `sliding` + * + * @param size the number of elements per group + * @param step the distance between the first elements of successive + * groups + * @return An iterator producing ${coll}s of size `size`, except the last + * element (which may be the only element) will be smaller + * if there are fewer than `size` elements remaining to be grouped. + * @example `List(1, 2, 3, 4, 5).sliding(2, 2) = Iterator(List(1, 2), List(3, 4), List(5))` + * @example `List(1, 2, 3, 4, 5, 6).sliding(2, 3) = Iterator(List(1, 2), List(4, 5))` + */ + def sliding(size: Int, step: Int): Iterator[C] = + iterator.sliding(size, step).map(fromSpecific) + + /** The rest of the collection without its first element. */ + def tail: C = { + if (isEmpty) throw new UnsupportedOperationException + drop(1) + } + + /** The initial part of the collection without its last element. + * $willForceEvaluation + */ + def init: C = { + if (isEmpty) throw new UnsupportedOperationException + dropRight(1) + } + + def slice(from: Int, until: Int): C = + fromSpecific(new View.Drop(new View.Take(this, until), from)) + + /** Partitions this $coll into a map of ${coll}s according to some discriminator function. + * + * $willForceEvaluation + * + * @param f the discriminator function. + * @tparam K the type of keys returned by the discriminator function. + * @return A map from keys to ${coll}s such that the following invariant holds: + * {{{ + * (xs groupBy f)(k) = xs filter (x => f(x) == k) + * }}} + * That is, every key `k` is bound to a $coll of those elements `x` + * for which `f(x)` equals `k`. + * + */ + def groupBy[K](f: A => K): immutable.Map[K, C] = { + val m = mutable.Map.empty[K, Builder[A, C]] + val it = iterator + while (it.hasNext) { + val elem = it.next() + val key = f(elem) + val bldr = m.getOrElseUpdate(key, newSpecificBuilder) + bldr += elem + } + var result = immutable.HashMap.empty[K, C] + val mapIt = m.iterator + while (mapIt.hasNext) { + val (k, v) = mapIt.next() + result = result.updated(k, v.result()) + } + result + } + + /** + * Partitions this $coll into a map of ${coll}s according to a discriminator function `key`. + * Each element in a group is transformed into a value of type `B` using the `value` function. + * + * It is equivalent to `groupBy(key).mapValues(_.map(f))`, but more efficient. + * + * {{{ + * case class User(name: String, age: Int) + * + * def namesByAge(users: Seq[User]): Map[Int, Seq[String]] = + * users.groupMap(_.age)(_.name) + * }}} + * + * $willForceEvaluation + * + * @param key the discriminator function + * @param f the element transformation function + * @tparam K the type of keys returned by the discriminator function + * @tparam B the type of values returned by the transformation function + */ + def groupMap[K, B](key: A => K)(f: A => B): immutable.Map[K, CC[B]] = { + val m = mutable.Map.empty[K, Builder[B, CC[B]]] + for (elem <- this) { + val k = key(elem) + val bldr = m.getOrElseUpdate(k, iterableFactory.newBuilder[B]) + bldr += f(elem) + } + class Result extends runtime.AbstractFunction1[(K, Builder[B, CC[B]]), Unit] { + var built = immutable.Map.empty[K, CC[B]] + def apply(kv: (K, Builder[B, CC[B]])) = + built = built.updated(kv._1, kv._2.result()) + } + val result = new Result + m.foreach(result) + result.built + } + + /** + * Partitions this $coll into a map according to a discriminator function `key`. All the values that + * have the same discriminator are then transformed by the `f` function and then reduced into a + * single value with the `reduce` function. + * + * It is equivalent to `groupBy(key).mapValues(_.map(f).reduce(reduce))`, but more efficient. + * + * {{{ + * def occurrences[A](as: Seq[A]): Map[A, Int] = + * as.groupMapReduce(identity)(_ => 1)(_ + _) + * }}} + * + * $willForceEvaluation + */ + def groupMapReduce[K, B](key: A => K)(f: A => B)(reduce: (B, B) => B): immutable.Map[K, B] = { + val m = mutable.Map.empty[K, B] + for (elem <- this) { + val k = key(elem) + val v = + m.get(k) match { + case Some(b) => reduce(b, f(elem)) + case None => f(elem) + } + m.put(k, v) + } + m.to(immutable.Map) + } + + /** Computes a prefix scan of the elements of the collection. + * + * Note: The neutral element `z` may be applied more than once. + * + * @tparam B element type of the resulting collection + * @param z neutral element for the operator `op` + * @param op the associative operator for the scan + * + * @return a new $coll containing the prefix scan of the elements in this $coll + */ + def scan[B >: A](z: B)(op: (B, B) => B): CC[B] = scanLeft(z)(op) + + def scanLeft[B](z: B)(op: (B, A) => B): CC[B] = iterableFactory.from(new View.ScanLeft(this, z, op)) + + /** Produces a collection containing cumulative results of applying the operator going right to left. + * The head of the collection is the last cumulative result. + * $willNotTerminateInf + * $orderDependent + * $willForceEvaluation + * + * Example: + * {{{ + * List(1, 2, 3, 4).scanRight(0)(_ + _) == List(10, 9, 7, 4, 0) + * }}} + * + * @tparam B the type of the elements in the resulting collection + * @param z the initial value + * @param op the binary operator applied to the intermediate result and the element + * @return collection with intermediate results + */ + def scanRight[B](z: B)(op: (A, B) => B): CC[B] = { + class Scanner extends runtime.AbstractFunction1[A, Unit] { + var acc = z + var scanned = acc :: immutable.Nil + def apply(x: A) = { + acc = op(x, acc) + scanned ::= acc + } + } + val scanner = new Scanner + reversed.foreach(scanner) + iterableFactory.from(scanner.scanned) + } + + def map[B](f: A => B): CC[B] = iterableFactory.from(new View.Map(this, f)) + + def flatMap[B](f: A => IterableOnce[B]): CC[B] = iterableFactory.from(new View.FlatMap(this, f)) + + def flatten[B](implicit asIterable: A => IterableOnce[B]): CC[B] = flatMap(asIterable) + + def collect[B](pf: PartialFunction[A, B]): CC[B] = + iterableFactory.from(new View.Collect(this, pf)) + + /** Applies a function `f` to each element of the $coll and returns a pair of ${coll}s: the first one + * made of those values returned by `f` that were wrapped in [[scala.util.Left]], and the second + * one made of those wrapped in [[scala.util.Right]]. + * + * Example: + * {{{ + * val xs = $Coll(1, "one", 2, "two", 3, "three") partitionMap { + * case i: Int => Left(i) + * case s: String => Right(s) + * } + * // xs == ($Coll(1, 2, 3), + * // $Coll(one, two, three)) + * }}} + * + * @tparam A1 the element type of the first resulting collection + * @tparam A2 the element type of the second resulting collection + * @param f the 'split function' mapping the elements of this $coll to an [[scala.util.Either]] + * + * @return a pair of ${coll}s: the first one made of those values returned by `f` that were wrapped in [[scala.util.Left]], + * and the second one made of those wrapped in [[scala.util.Right]]. + */ + def partitionMap[A1, A2](f: A => Either[A1, A2]): (CC[A1], CC[A2]) = { + val left: View[A1] = new LeftPartitionMapped(this, f) + val right: View[A2] = new RightPartitionMapped(this, f) + (iterableFactory.from(left), iterableFactory.from(right)) + } + + /** Returns a new $ccoll containing the elements from the left hand operand followed by the elements from the + * right hand operand. The element type of the $ccoll is the most specific superclass encompassing + * the element types of the two operands. + * + * @param suffix the iterable to append. + * @tparam B the element type of the returned collection. + * @return a new $coll which contains all elements + * of this $coll followed by all elements of `suffix`. + */ + def concat[B >: A](suffix: IterableOnce[B]): CC[B] = iterableFactory.from { + suffix match { + case suffix: Iterable[B] => new View.Concat(this, suffix) + case suffix => iterator ++ suffix.iterator + } + } + + /** Alias for `concat` */ + @inline final def ++ [B >: A](suffix: IterableOnce[B]): CC[B] = concat(suffix) + + /** Returns a $ccoll formed from this $coll and another iterable collection + * by combining corresponding elements in pairs. + * If one of the two collections is longer than the other, its remaining elements are ignored. + * + * @param that The iterable providing the second half of each result pair + * @tparam B the type of the second half of the returned pairs + * @return a new $ccoll containing pairs consisting of corresponding elements of this $coll and `that`. + * The length of the returned collection is the minimum of the lengths of this $coll and `that`. + */ + def zip[B](that: IterableOnce[B]): CC[(A @uncheckedVariance, B)] = iterableFactory.from(that match { // sound bcs of VarianceNote + case that: Iterable[B] => new View.Zip(this, that) + case _ => iterator.zip(that) + }) + + def zipWithIndex: CC[(A @uncheckedVariance, Int)] = iterableFactory.from(new View.ZipWithIndex(this)) + + /** Returns a $coll formed from this $coll and another iterable collection + * by combining corresponding elements in pairs. + * If one of the two collections is shorter than the other, + * placeholder elements are used to extend the shorter collection to the length of the longer. + * + * @param that the iterable providing the second half of each result pair + * @param thisElem the element to be used to fill up the result if this $coll is shorter than `that`. + * @param thatElem the element to be used to fill up the result if `that` is shorter than this $coll. + * @return a new $coll containing pairs consisting of + * corresponding elements of this $coll and `that`. The length + * of the returned collection is the maximum of the lengths of this $coll and `that`. + * If this $coll is shorter than `that`, `thisElem` values are used to pad the result. + * If `that` is shorter than this $coll, `thatElem` values are used to pad the result. + */ + def zipAll[A1 >: A, B](that: Iterable[B], thisElem: A1, thatElem: B): CC[(A1, B)] = iterableFactory.from(new View.ZipAll(this, that, thisElem, thatElem)) + + /** Converts this $coll of pairs into two collections of the first and second + * half of each pair. + * + * {{{ + * val xs = $Coll( + * (1, "one"), + * (2, "two"), + * (3, "three")).unzip + * // xs == ($Coll(1, 2, 3), + * // $Coll(one, two, three)) + * }}} + * + * @tparam A1 the type of the first half of the element pairs + * @tparam A2 the type of the second half of the element pairs + * @param asPair an implicit conversion which asserts that the element type + * of this $coll is a pair. + * @return a pair of ${coll}s, containing the first, respectively second + * half of each element pair of this $coll. + */ + def unzip[A1, A2](implicit asPair: A => (A1, A2)): (CC[A1], CC[A2]) = { + val first: View[A1] = new View.Map[A, A1](this, asPair(_)._1) + val second: View[A2] = new View.Map[A, A2](this, asPair(_)._2) + (iterableFactory.from(first), iterableFactory.from(second)) + } + + /** Converts this $coll of triples into three collections of the first, second, + * and third element of each triple. + * + * {{{ + * val xs = $Coll( + * (1, "one", '1'), + * (2, "two", '2'), + * (3, "three", '3')).unzip3 + * // xs == ($Coll(1, 2, 3), + * // $Coll(one, two, three), + * // $Coll(1, 2, 3)) + * }}} + * + * @tparam A1 the type of the first member of the element triples + * @tparam A2 the type of the second member of the element triples + * @tparam A3 the type of the third member of the element triples + * @param asTriple an implicit conversion which asserts that the element type + * of this $coll is a triple. + * @return a triple of ${coll}s, containing the first, second, respectively + * third member of each element triple of this $coll. + */ + def unzip3[A1, A2, A3](implicit asTriple: A => (A1, A2, A3)): (CC[A1], CC[A2], CC[A3]) = { + val first: View[A1] = new View.Map[A, A1](this, asTriple(_)._1) + val second: View[A2] = new View.Map[A, A2](this, asTriple(_)._2) + val third: View[A3] = new View.Map[A, A3](this, asTriple(_)._3) + (iterableFactory.from(first), iterableFactory.from(second), iterableFactory.from(third)) + } + + /** Iterates over the tails of this $coll. The first value will be this + * $coll and the final one will be an empty $coll, with the intervening + * values the results of successive applications of `tail`. + * + * @return an iterator over all the tails of this $coll + * @example `List(1,2,3).tails = Iterator(List(1,2,3), List(2,3), List(3), Nil)` + */ + def tails: Iterator[C] = iterateUntilEmpty(_.tail) + + /** Iterates over the inits of this $coll. The first value will be this + * $coll and the final one will be an empty $coll, with the intervening + * values the results of successive applications of `init`. + * + * $willForceEvaluation + * + * @return an iterator over all the inits of this $coll + * @example `List(1,2,3).inits = Iterator(List(1,2,3), List(1,2), List(1), Nil)` + */ + def inits: Iterator[C] = iterateUntilEmpty(_.init) + + override def tapEach[U](f: A => U): C = fromSpecific(new View.Map(this, { (a: A) => f(a); a })) + + // A helper for tails and inits. + private[this] def iterateUntilEmpty(f: Iterable[A] => Iterable[A]): Iterator[C] = { + // toIterable ties the knot between `this: IterableOnceOps[A, CC, C]` and `this.tail: C` + // `this.tail.tail` doesn't compile as `C` is unbounded + // `Iterable.from(this)` would eagerly copy non-immutable collections + val it = Iterator.iterate(toIterable: @nowarn("cat=deprecation"))(f).takeWhile(_.nonEmpty) + (it ++ Iterator.single(Iterable.empty)).map(fromSpecific) + } + + @deprecated("Use ++ instead of ++: for collections of type Iterable", "2.13.0") + def ++:[B >: A](that: IterableOnce[B]): CC[B] = iterableFactory.from(that match { + case xs: Iterable[B] => new View.Concat(xs, this) + case _ => that.iterator ++ iterator + }) +} + +object IterableOps { + + /** Operations for comparing the size of a collection to a test value. + * + * These operations are implemented in terms of + * [[scala.collection.IterableOps!.sizeCompare(Int):Int* `sizeCompare(Int)`]] + */ + final class SizeCompareOps private[collection](val it: IterableOps[_, AnyConstr, _]) extends AnyVal { + /** Tests if the size of the collection is less than some value. */ + @inline def <(size: Int): Boolean = it.sizeCompare(size) < 0 + /** Tests if the size of the collection is less than or equal to some value. */ + @inline def <=(size: Int): Boolean = it.sizeCompare(size) <= 0 + /** Tests if the size of the collection is equal to some value. */ + @inline def ==(size: Int): Boolean = it.sizeCompare(size) == 0 + /** Tests if the size of the collection is not equal to some value. */ + @inline def !=(size: Int): Boolean = it.sizeCompare(size) != 0 + /** Tests if the size of the collection is greater than or equal to some value. */ + @inline def >=(size: Int): Boolean = it.sizeCompare(size) >= 0 + /** Tests if the size of the collection is greater than some value. */ + @inline def >(size: Int): Boolean = it.sizeCompare(size) > 0 + } + + /** A trait that contains just the `map`, `flatMap`, `foreach` and `withFilter` methods + * of trait `Iterable`. + * + * @tparam A Element type (e.g. `Int`) + * @tparam CC Collection type constructor (e.g. `List`) + * + * @define coll collection + */ + @SerialVersionUID(3L) + class WithFilter[+A, +CC[_]]( + self: IterableOps[A, CC, _], + p: A => Boolean + ) extends collection.WithFilter[A, CC] with Serializable { + + protected def filtered: Iterable[A] = + new View.Filter(self, p, isFlipped = false) + + def map[B](f: A => B): CC[B] = + self.iterableFactory.from(new View.Map(filtered, f)) + + def flatMap[B](f: A => IterableOnce[B]): CC[B] = + self.iterableFactory.from(new View.FlatMap(filtered, f)) + + def foreach[U](f: A => U): Unit = filtered.foreach(f) + + def withFilter(q: A => Boolean): WithFilter[A, CC] = + new WithFilter(self, (a: A) => p(a) && q(a)) + + } + +} + +@SerialVersionUID(3L) +object Iterable extends IterableFactory.Delegate[Iterable](immutable.Iterable) { + + def single[A](a: A): Iterable[A] = new AbstractIterable[A] { + override def iterator = Iterator.single(a) + override def knownSize = 1 + override def head = a + override def headOption: Some[A] = Some(a) + override def last = a + override def lastOption: Some[A] = Some(a) + override def view: View.Single[A] = new View.Single(a) + override def take(n: Int) = if (n > 0) this else Iterable.empty + override def takeRight(n: Int) = if (n > 0) this else Iterable.empty + override def drop(n: Int) = if (n > 0) Iterable.empty else this + override def dropRight(n: Int) = if (n > 0) Iterable.empty else this + override def tail: Iterable[Nothing] = Iterable.empty + override def init: Iterable[Nothing] = Iterable.empty + } +} + +/** Explicit instantiation of the `Iterable` trait to reduce class file size in subclasses. */ +abstract class AbstractIterable[+A] extends Iterable[A] + +/** This trait provides default implementations for the factory methods `fromSpecific` and + * `newSpecificBuilder` that need to be refined when implementing a collection type that refines + * the `CC` and `C` type parameters. + * + * The default implementations in this trait can be used in the common case when `CC[A]` is the + * same as `C`. + */ +trait IterableFactoryDefaults[+A, +CC[x] <: IterableOps[x, CC, CC[x]]] extends IterableOps[A, CC, CC[A @uncheckedVariance]] { + protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]): CC[A @uncheckedVariance] = iterableFactory.from(coll) + protected def newSpecificBuilder: Builder[A @uncheckedVariance, CC[A @uncheckedVariance]] = iterableFactory.newBuilder[A] + + // overridden for efficiency, since we know CC[A] =:= C + override def empty: CC[A @uncheckedVariance] = iterableFactory.empty +} + +/** This trait provides default implementations for the factory methods `fromSpecific` and + * `newSpecificBuilder` that need to be refined when implementing a collection type that refines + * the `CC` and `C` type parameters. It is used for collections that have an additional constraint, + * expressed by the `evidenceIterableFactory` method. + * + * The default implementations in this trait can be used in the common case when `CC[A]` is the + * same as `C`. + */ +trait EvidenceIterableFactoryDefaults[+A, +CC[x] <: IterableOps[x, CC, CC[x]], Ev[_]] extends IterableOps[A, CC, CC[A @uncheckedVariance]] { + protected def evidenceIterableFactory: EvidenceIterableFactory[CC, Ev] + implicit protected def iterableEvidence: Ev[A @uncheckedVariance] + override protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]): CC[A @uncheckedVariance] = evidenceIterableFactory.from(coll) + override protected def newSpecificBuilder: Builder[A @uncheckedVariance, CC[A @uncheckedVariance]] = evidenceIterableFactory.newBuilder[A] + override def empty: CC[A @uncheckedVariance] = evidenceIterableFactory.empty +} + +/** This trait provides default implementations for the factory methods `fromSpecific` and + * `newSpecificBuilder` that need to be refined when implementing a collection type that refines + * the `CC` and `C` type parameters. It is used for sorted sets. + * + * Note that in sorted sets, the `CC` type of the set is not the same as the `CC` type for the + * underlying iterable (which is fixed to `Set` in [[SortedSetOps]]). This trait has therefore + * two type parameters `CC` and `WithFilterCC`. The `withFilter` method inherited from + * `IterableOps` is overridden with a compatible default implementation. + * + * The default implementations in this trait can be used in the common case when `CC[A]` is the + * same as `C`. + */ +trait SortedSetFactoryDefaults[+A, + +CC[X] <: SortedSet[X] with SortedSetOps[X, CC, CC[X]], + +WithFilterCC[x] <: IterableOps[x, WithFilterCC, WithFilterCC[x]] with Set[x]] extends SortedSetOps[A @uncheckedVariance, CC, CC[A @uncheckedVariance]] { + self: IterableOps[A, WithFilterCC, _] => + + override protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]): CC[A @uncheckedVariance] = sortedIterableFactory.from(coll)(using ordering) + override protected def newSpecificBuilder: mutable.Builder[A @uncheckedVariance, CC[A @uncheckedVariance]] = sortedIterableFactory.newBuilder[A](using ordering) + override def empty: CC[A @uncheckedVariance] = sortedIterableFactory.empty(using ordering) + + override def withFilter(p: A => Boolean): SortedSetOps.WithFilter[A, WithFilterCC, CC] = + new SortedSetOps.WithFilter[A, WithFilterCC, CC](this, p) +} + + +/** This trait provides default implementations for the factory methods `fromSpecific` and + * `newSpecificBuilder` that need to be refined when implementing a collection type that refines + * the `CC` and `C` type parameters. It is used for maps. + * + * Note that in maps, the `CC` type of the map is not the same as the `CC` type for the + * underlying iterable (which is fixed to `Map` in [[MapOps]]). This trait has therefore + * two type parameters `CC` and `WithFilterCC`. The `withFilter` method inherited from + * `IterableOps` is overridden with a compatible default implementation. + * + * The default implementations in this trait can be used in the common case when `CC[A]` is the + * same as `C`. + */ +trait MapFactoryDefaults[K, +V, + +CC[x, y] <: IterableOps[(x, y), Iterable, Iterable[(x, y)]], + +WithFilterCC[x] <: IterableOps[x, WithFilterCC, WithFilterCC[x]] with Iterable[x]] extends MapOps[K, V, CC, CC[K, V @uncheckedVariance]] with IterableOps[(K, V), WithFilterCC, CC[K, V @uncheckedVariance]] { + override protected def fromSpecific(coll: IterableOnce[(K, V @uncheckedVariance)]): CC[K, V @uncheckedVariance] = mapFactory.from(coll) + override protected def newSpecificBuilder: mutable.Builder[(K, V @uncheckedVariance), CC[K, V @uncheckedVariance]] = mapFactory.newBuilder[K, V] + override def empty: CC[K, V @uncheckedVariance] = (this: AnyRef) match { + // Implemented here instead of in TreeSeqMap since overriding empty in TreeSeqMap is not forwards compatible (should be moved) + case self: immutable.TreeSeqMap[_, _] => immutable.TreeSeqMap.empty(self.orderedBy).asInstanceOf[CC[K, V]] + case _ => mapFactory.empty + } + + override def withFilter(p: ((K, V)) => Boolean): MapOps.WithFilter[K, V, WithFilterCC, CC] = + new MapOps.WithFilter[K, V, WithFilterCC, CC](this, p) +} + +/** This trait provides default implementations for the factory methods `fromSpecific` and + * `newSpecificBuilder` that need to be refined when implementing a collection type that refines + * the `CC` and `C` type parameters. It is used for sorted maps. + * + * Note that in sorted maps, the `CC` type of the map is not the same as the `CC` type for the + * underlying map (which is fixed to `Map` in [[SortedMapOps]]). This trait has therefore + * three type parameters `CC`, `WithFilterCC` and `UnsortedCC`. The `withFilter` method inherited + * from `IterableOps` is overridden with a compatible default implementation. + * + * The default implementations in this trait can be used in the common case when `CC[A]` is the + * same as `C`. + */ +trait SortedMapFactoryDefaults[K, +V, + +CC[x, y] <: Map[x, y] with SortedMapOps[x, y, CC, CC[x, y]] with UnsortedCC[x, y], + +WithFilterCC[x] <: IterableOps[x, WithFilterCC, WithFilterCC[x]] with Iterable[x], + +UnsortedCC[x, y] <: Map[x, y]] extends SortedMapOps[K, V, CC, CC[K, V @uncheckedVariance]] with MapOps[K, V, UnsortedCC, CC[K, V @uncheckedVariance]] { + self: IterableOps[(K, V), WithFilterCC, _] => + + override def empty: CC[K, V @uncheckedVariance] = sortedMapFactory.empty(using ordering) + override protected def fromSpecific(coll: IterableOnce[(K, V @uncheckedVariance)]): CC[K, V @uncheckedVariance] = sortedMapFactory.from(coll)(using ordering) + override protected def newSpecificBuilder: mutable.Builder[(K, V @uncheckedVariance), CC[K, V @uncheckedVariance]] = sortedMapFactory.newBuilder[K, V](using ordering) + + override def withFilter(p: ((K, V)) => Boolean): collection.SortedMapOps.WithFilter[K, V, WithFilterCC, UnsortedCC, CC] = + new collection.SortedMapOps.WithFilter[K, V, WithFilterCC, UnsortedCC, CC](this, p) +} diff --git a/library/src/scala/collection/IterableOnce.scala b/library/src/scala/collection/IterableOnce.scala new file mode 100644 index 000000000000..44a5eaa1ca20 --- /dev/null +++ b/library/src/scala/collection/IterableOnce.scala @@ -0,0 +1,1513 @@ +/* + * 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 collection + +import scala.language.`2.13` +import scala.annotation.tailrec +import scala.annotation.unchecked.uncheckedVariance +import scala.collection.mutable.StringBuilder +import scala.language.implicitConversions +import scala.math.{Numeric, Ordering} +import scala.reflect.ClassTag +import scala.runtime.{AbstractFunction1, AbstractFunction2} + +/** + * A template trait for collections which can be traversed either once only + * or one or more times. + * + * Note: `IterableOnce` does not extend [[IterableOnceOps]]. This is different than the general + * design of the collections library, which uses the following pattern: + * {{{ + * trait Seq extends Iterable with SeqOps + * trait SeqOps extends IterableOps + * + * trait IndexedSeq extends Seq with IndexedSeqOps + * trait IndexedSeqOps extends SeqOps + * }}} + * + * The goal is to provide a minimal interface without any sequential operations. This allows + * third-party extension like Scala parallel collections to integrate at the level of IterableOnce + * without inheriting unwanted implementations. + * + * @define coll collection + * @define ccoll $coll + */ +trait IterableOnce[+A] extends Any { + + /** An [[scala.collection.Iterator]] over the elements of this $coll. + * + * If an `IterableOnce` object is in fact an [[scala.collection.Iterator]], this method always returns itself, + * in its current state, but if it is an [[scala.collection.Iterable]], this method always returns a new + * [[scala.collection.Iterator]]. + */ + def iterator: Iterator[A] + + /** Returns a [[scala.collection.Stepper]] for the elements of this collection. + * + * The Stepper enables creating a Java stream to operate on the collection, see + * [[scala.jdk.StreamConverters]]. For collections holding primitive values, the Stepper can be + * used as an iterator which doesn't box the elements. + * + * The implicit [[scala.collection.StepperShape]] parameter defines the resulting Stepper type according to the + * element type of this collection. + * + * - For collections of `Int`, `Short`, `Byte` or `Char`, an [[scala.collection.IntStepper]] is returned + * - For collections of `Double` or `Float`, a [[scala.collection.DoubleStepper]] is returned + * - For collections of `Long` a [[scala.collection.LongStepper]] is returned + * - For any other element type, an [[scala.collection.AnyStepper]] is returned + * + * Note that this method is overridden in subclasses and the return type is refined to + * `S with EfficientSplit`, for example [[scala.collection.IndexedSeqOps.stepper]]. For Steppers marked with + * [[scala.collection.Stepper.EfficientSplit]], the converters in [[scala.jdk.StreamConverters]] + * allow creating parallel streams, whereas bare Steppers can be converted only to sequential + * streams. + */ + def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S = { + import convert.impl._ + val s = shape.shape match { + case StepperShape.IntShape => new IntIteratorStepper (iterator.asInstanceOf[Iterator[Int]]) + case StepperShape.LongShape => new LongIteratorStepper (iterator.asInstanceOf[Iterator[Long]]) + case StepperShape.DoubleShape => new DoubleIteratorStepper(iterator.asInstanceOf[Iterator[Double]]) + case _ => shape.seqUnbox(new AnyIteratorStepper[A](iterator)) + } + s.asInstanceOf[S] + } + + /** The number of elements in this $coll, if it can be cheaply computed, + * -1 otherwise. Cheaply usually means: Not requiring a collection traversal. + */ + def knownSize: Int = -1 +} + +final class IterableOnceExtensionMethods[A](private val it: IterableOnce[A]) extends AnyVal { + @deprecated("Use .iterator.withFilter(...) instead", "2.13.0") + def withFilter(f: A => Boolean): Iterator[A] = it.iterator.withFilter(f) + + @deprecated("Use .iterator.reduceLeftOption(...) instead", "2.13.0") + def reduceLeftOption(f: (A, A) => A): Option[A] = it.iterator.reduceLeftOption(f) + + @deprecated("Use .iterator.min instead", "2.13.0") + def min(implicit ord: Ordering[A]): A = it.iterator.min + + @deprecated("Use .iterator.nonEmpty instead", "2.13.0") + def nonEmpty: Boolean = it.iterator.nonEmpty + + @deprecated("Use .iterator.max instead", "2.13.0") + def max(implicit ord: Ordering[A]): A = it.iterator.max + + @deprecated("Use .iterator.reduceRight(...) instead", "2.13.0") + def reduceRight(f: (A, A) => A): A = it.iterator.reduceRight(f) + + @deprecated("Use .iterator.maxBy(...) instead", "2.13.0") + def maxBy[B](f: A => B)(implicit cmp: Ordering[B]): A = it.iterator.maxBy(f) + + @deprecated("Use .iterator.reduceLeft(...) instead", "2.13.0") + def reduceLeft(f: (A, A) => A): A = it.iterator.reduceLeft(f) + + @deprecated("Use .iterator.sum instead", "2.13.0") + def sum(implicit num: Numeric[A]): A = it.iterator.sum + + @deprecated("Use .iterator.product instead", "2.13.0") + def product(implicit num: Numeric[A]): A = it.iterator.product + + @deprecated("Use .iterator.count(...) instead", "2.13.0") + def count(f: A => Boolean): Int = it.iterator.count(f) + + @deprecated("Use .iterator.reduceOption(...) instead", "2.13.0") + def reduceOption(f: (A, A) => A): Option[A] = it.iterator.reduceOption(f) + + @deprecated("Use .iterator.minBy(...) instead", "2.13.0") + def minBy[B](f: A => B)(implicit cmp: Ordering[B]): A = it.iterator.minBy(f) + + @deprecated("Use .iterator.size instead", "2.13.0") + def size: Int = it.iterator.size + + @deprecated("Use .iterator.forall(...) instead", "2.13.0") + def forall(f: A => Boolean): Boolean = it.iterator.forall(f) + + @deprecated("Use .iterator.collectFirst(...) instead", "2.13.0") + def collectFirst[B](f: PartialFunction[A, B]): Option[B] = it.iterator.collectFirst(f) + + @deprecated("Use .iterator.filter(...) instead", "2.13.0") + def filter(f: A => Boolean): Iterator[A] = it.iterator.filter(f) + + @deprecated("Use .iterator.exists(...) instead", "2.13.0") + def exists(f: A => Boolean): Boolean = it.iterator.exists(f) + + @deprecated("Use .iterator.copyToBuffer(...) instead", "2.13.0") + def copyToBuffer(dest: mutable.Buffer[A]): Unit = it.iterator.copyToBuffer(dest) + + @deprecated("Use .iterator.reduce(...) instead", "2.13.0") + def reduce(f: (A, A) => A): A = it.iterator.reduce(f) + + @deprecated("Use .iterator.reduceRightOption(...) instead", "2.13.0") + def reduceRightOption(f: (A, A) => A): Option[A] = it.iterator.reduceRightOption(f) + + @deprecated("Use .iterator.toIndexedSeq instead", "2.13.0") + def toIndexedSeq: IndexedSeq[A] = it.iterator.toIndexedSeq + + @deprecated("Use .iterator.foreach(...) instead", "2.13.0") + @`inline` def foreach[U](f: A => U): Unit = it match { + case it: Iterable[A] => it.foreach(f) + case _ => it.iterator.foreach(f) + } + + @deprecated("Use .iterator.to(factory) instead", "2.13.0") + def to[C1](factory: Factory[A, C1]): C1 = factory.fromSpecific(it) + + @deprecated("Use .iterator.to(ArrayBuffer) instead", "2.13.0") + def toBuffer[B >: A]: mutable.Buffer[B] = mutable.ArrayBuffer.from(it) + + @deprecated("Use .iterator.toArray", "2.13.0") + def toArray[B >: A: ClassTag]: Array[B] = it match { + case it: Iterable[B] => it.toArray[B] + case _ => it.iterator.toArray[B] + } + + @deprecated("Use .iterator.to(List) instead", "2.13.0") + def toList: immutable.List[A] = immutable.List.from(it) + + @deprecated("Use .iterator.to(Set) instead", "2.13.0") + @`inline` def toSet[B >: A]: immutable.Set[B] = immutable.Set.from(it) + + @deprecated("Use .iterator.to(Iterable) instead", "2.13.0") + @`inline` final def toTraversable: Traversable[A] = toIterable + + @deprecated("Use .iterator.to(Iterable) instead", "2.13.0") + @`inline` final def toIterable: Iterable[A] = Iterable.from(it) + + @deprecated("Use .iterator.to(Seq) instead", "2.13.0") + @`inline` def toSeq: immutable.Seq[A] = immutable.Seq.from(it) + + @deprecated("Use .iterator.to(LazyList) instead", "2.13.0") + @`inline` def toStream: immutable.Stream[A] = immutable.Stream.from(it) + + @deprecated("Use .iterator.to(Vector) instead", "2.13.0") + @`inline` def toVector: immutable.Vector[A] = immutable.Vector.from(it) + + @deprecated("Use .iterator.to(Map) instead", "2.13.0") + def toMap[K, V](implicit ev: A <:< (K, V)): immutable.Map[K, V] = + immutable.Map.from(it.asInstanceOf[IterableOnce[(K, V)]]) + + @deprecated("Use .iterator instead", "2.13.0") + @`inline` def toIterator: Iterator[A] = it.iterator + + @deprecated("Use .iterator.isEmpty instead", "2.13.0") + def isEmpty: Boolean = it match { + case it: Iterable[A] => it.isEmpty + case _ => it.iterator.isEmpty + } + + @deprecated("Use .iterator.mkString instead", "2.13.0") + def mkString(start: String, sep: String, end: String): String = it match { + case it: Iterable[A] => it.mkString(start, sep, end) + case _ => it.iterator.mkString(start, sep, end) + } + + @deprecated("Use .iterator.mkString instead", "2.13.0") + def mkString(sep: String): String = it match { + case it: Iterable[A] => it.mkString(sep) + case _ => it.iterator.mkString(sep) + } + + @deprecated("Use .iterator.mkString instead", "2.13.0") + def mkString: String = it match { + case it: Iterable[A] => it.mkString + case _ => it.iterator.mkString + } + + @deprecated("Use .iterator.find instead", "2.13.0") + def find(p: A => Boolean): Option[A] = it.iterator.find(p) + + @deprecated("Use .iterator.foldLeft instead", "2.13.0") + @`inline` def foldLeft[B](z: B)(op: (B, A) => B): B = it.iterator.foldLeft(z)(op) + + @deprecated("Use .iterator.foldRight instead", "2.13.0") + @`inline` def foldRight[B](z: B)(op: (A, B) => B): B = it.iterator.foldRight(z)(op) + + @deprecated("Use .iterator.fold instead", "2.13.0") + def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1 = it.iterator.fold(z)(op) + + @deprecated("Use .iterator.foldLeft instead", "2.13.0") + @`inline` def /: [B](z: B)(op: (B, A) => B): B = foldLeft[B](z)(op) + + @deprecated("Use .iterator.foldRight instead", "2.13.0") + @`inline` def :\ [B](z: B)(op: (A, B) => B): B = foldRight[B](z)(op) + + @deprecated("Use .iterator.map instead or consider requiring an Iterable", "2.13.0") + def map[B](f: A => B): IterableOnce[B] = it match { + case it: Iterable[A] => it.map(f) + case _ => it.iterator.map(f) + } + + @deprecated("Use .iterator.flatMap instead or consider requiring an Iterable", "2.13.0") + def flatMap[B](f: A => IterableOnce[B]): IterableOnce[B] = it match { + case it: Iterable[A] => it.flatMap(f) + case _ => it.iterator.flatMap(f) + } + + @deprecated("Use .iterator.sameElements instead", "2.13.0") + def sameElements[B >: A](that: IterableOnce[B]): Boolean = it.iterator.sameElements(that) +} + +object IterableOnce { + @inline implicit def iterableOnceExtensionMethods[A](it: IterableOnce[A]): IterableOnceExtensionMethods[A] = + new IterableOnceExtensionMethods[A](it) + + /** Computes the number of elements to copy to an array from a source IterableOnce + * + * @param srcLen the length of the source collection + * @param destLen the length of the destination array + * @param start the index in the destination array at which to start copying elements to + * @param len the requested number of elements to copy (we may only be able to copy less than this) + * @return the number of elements that will be copied to the destination array + */ + @inline private[collection] def elemsToCopyToArray(srcLen: Int, destLen: Int, start: Int, len: Int): Int = + math.max(math.min(math.min(len, srcLen), destLen - start), 0) + + /** Calls `copyToArray` on the given collection, regardless of whether or not it is an `Iterable`. */ + @inline private[collection] def copyElemsToArray[A, B >: A](elems: IterableOnce[A], + xs: Array[B], + start: Int = 0, + len: Int = Int.MaxValue): Int = + elems match { + case src: Iterable[A] => src.copyToArray[B](xs, start, len) + case src => src.iterator.copyToArray[B](xs, start, len) + } +} + +/** This implementation trait can be mixed into an `IterableOnce` to get the basic methods that are shared between + * `Iterator` and `Iterable`. The `IterableOnce` must support multiple calls to `iterator` but may or may not + * return the same `Iterator` every time. + * + * @define orderDependent + * + * Note: might return different results for different runs, unless the underlying collection type is ordered. + * @define orderDependentReduce + * + * Note: might return different results for different runs, unless the + * underlying collection type is ordered or the operator is associative + * and commutative. + * @define orderIndependentReduce + * + * Note: might return different results for different runs, unless either + * of the following conditions is met: (1) the operator is associative, + * and the underlying collection type is ordered; or (2) the operator is + * associative and commutative. + * @define mayNotTerminateInf + * + * Note: may not terminate for infinite-sized collections. + * @define willNotTerminateInf + * + * Note: will not terminate for infinite-sized collections. + * @define willForceEvaluation + * Note: Even when applied to a view or a lazy collection it will always force the elements. + * @define consumesIterator + * After calling this method, one should discard the iterator it was called + * on. Using it is undefined and subject to change. + * @define undefinedOrder + * The order of applications of the operator is unspecified and may be nondeterministic. + * @define exactlyOnce + * Each element appears exactly once in the computation. + */ +transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => + /////////////////////////////////////////////////////////////// Abstract methods that must be implemented + + /** Produces a $coll containing cumulative results of applying the + * operator going left to right, including the initial value. + * + * $willNotTerminateInf + * $orderDependent + * + * @tparam B the type of the elements in the resulting collection + * @param z the initial value + * @param op the binary operator applied to the intermediate result and the element + * @return collection with intermediate results + */ + def scanLeft[B](z: B)(op: (B, A) => B): CC[B] + + /** Selects all elements of this $coll which satisfy a predicate. + * + * @param p the predicate used to test elements. + * @return a new $coll consisting of all elements of this $coll that satisfy the given + * predicate `p`. The order of the elements is preserved. + */ + def filter(p: A => Boolean): C + + /** Selects all elements of this $coll which do not satisfy a predicate. + * + * @param pred the predicate used to test elements. + * @return a new $coll consisting of all elements of this $coll that do not satisfy the given + * predicate `pred`. Their order may not be preserved. + */ + def filterNot(pred: A => Boolean): C + + /** Selects the first `n` elements. + * $orderDependent + * @param n the number of elements to take from this $coll. + * @return a $coll consisting only of the first `n` elements of this $coll, + * or else the whole $coll, if it has less than `n` elements. + * If `n` is negative, returns an empty $coll. + */ + def take(n: Int): C + + /** Selects the longest prefix of elements that satisfy a predicate. + * + * The matching prefix starts with the first element of this $coll, + * and the element following the prefix is the first element that + * does not satisfy the predicate. The matching prefix may empty, + * so that this method returns an empty $coll. + * + * Example: + * + * {{{ + * scala> List(1, 2, 3, 100, 4).takeWhile(n => n < 10) + * val res0: List[Int] = List(1, 2, 3) + * + * scala> List(1, 2, 3, 100, 4).takeWhile(n => n == 0) + * val res1: List[Int] = List() + * }}} + * + * Use [[span]] to obtain both the prefix and suffix. + * Use [[filter]] to retain only those elements from the entire $coll that satisfy the predicate. + * $orderDependent + * @param p The predicate used to test elements. + * @return the longest prefix of this $coll whose elements all satisfy + * the predicate `p`. + */ + def takeWhile(p: A => Boolean): C + + /** Selects all elements except the first `n` ones. + * $orderDependent + * @param n the number of elements to drop from this $coll. + * @return a $coll consisting of all elements of this $coll except the first `n` ones, or else the + * empty $coll, if this $coll has less than `n` elements. + * If `n` is negative, don't drop any elements. + */ + def drop(n: Int): C + + /** Selects all elements except the longest prefix that satisfies a predicate. + * + * The matching prefix starts with the first element of this $coll, + * and the element following the prefix is the first element that + * does not satisfy the predicate. The matching prefix may be empty, + * so that this method returns the entire $coll. + * + * Example: + * + * {{{ + * scala> List(1, 2, 3, 100, 4).dropWhile(n => n < 10) + * val res0: List[Int] = List(100, 4) + * + * scala> List(1, 2, 3, 100, 4).dropWhile(n => n == 0) + * val res1: List[Int] = List(1, 2, 3, 100, 4) + * }}} + * + * Use [[span]] to obtain both the prefix and suffix. + * Use [[filterNot]] to drop all elements that satisfy the predicate. + * + * $orderDependent + * @param p The predicate used to test elements. + * @return the longest suffix of this $coll whose first element + * does not satisfy the predicate `p`. + */ + def dropWhile(p: A => Boolean): C + + /** Selects an interval of elements. The returned $coll is made up + * of all elements `x` which satisfy the invariant: + * {{{ + * from <= indexOf(x) < until + * }}} + * $orderDependent + * + * @param from the lowest index to include from this $coll. + * @param until the lowest index to EXCLUDE from this $coll. + * @return a $coll containing the elements greater than or equal to + * index `from` extending up to (but not including) index `until` + * of this $coll. + */ + def slice(from: Int, until: Int): C + + /** Builds a new $ccoll by applying a function to all elements of this $coll. + * + * @param f the function to apply to each element. + * @tparam B the element type of the returned $ccoll. + * @return a new $ccoll resulting from applying the given function + * `f` to each element of this $coll and collecting the results. + */ + def map[B](f: A => B): CC[B] + + /** Builds a new $ccoll by applying a function to all elements of this $coll + * and using the elements of the resulting collections. + * + * For example: + * + * {{{ + * def getWords(lines: Seq[String]): Seq[String] = lines.flatMap(line => line.split("\\W+")) + * }}} + * + * The type of the resulting collection is guided by the static type of this $coll. This might + * cause unexpected results sometimes. For example: + * + * {{{ + * // lettersOf will return a Seq[Char] of likely repeated letters, instead of a Set + * def lettersOf(words: Seq[String]) = words.flatMap(word => word.toSet) + * + * // lettersOf will return a Set[Char], not a Seq + * def lettersOf(words: Seq[String]) = words.toSet.flatMap(word => word.toSeq) + * + * // xs will be an Iterable[Int] + * val xs = Map("a" -> List(11, 111), "b" -> List(22, 222)).flatMap(_._2) + * + * // ys will be a Map[Int, Int] + * val ys = Map("a" -> List(1 -> 11, 1 -> 111), "b" -> List(2 -> 22, 2 -> 222)).flatMap(_._2) + * }}} + * + * @param f the function to apply to each element. + * @tparam B the element type of the returned collection. + * @return a new $ccoll resulting from applying the given collection-valued function + * `f` to each element of this $coll and concatenating the results. + */ + def flatMap[B](f: A => IterableOnce[B]): CC[B] + + /** Given that the elements of this collection are themselves iterable collections, + * converts this $coll into a $ccoll comprising the elements of these iterable collections. + * + * The resulting collection's type will be guided by the + * type of $coll. For example: + * + * {{{ + * val xs = List( + * Set(1, 2, 3), + * Set(1, 2, 3) + * ).flatten + * // xs == List(1, 2, 3, 1, 2, 3) + * + * val ys = Set( + * List(1, 2, 3), + * List(3, 2, 1) + * ).flatten + * // ys == Set(1, 2, 3) + * }}} + * + * @tparam B the type of the elements of each iterable collection. + * @param asIterable an implicit conversion which asserts that the element + * type of this $coll is an `Iterable`. + * @return a new $ccoll resulting from concatenating all element collections. + */ + def flatten[B](implicit asIterable: A => IterableOnce[B]): CC[B] + + /** Builds a new $ccoll by applying a partial function to all elements of this $coll + * on which the function is defined. + * + * @param pf the partial function which filters and maps the $coll. + * @tparam B the element type of the returned $coll. + * @return a new $ccoll resulting from applying the given partial function + * `pf` to each element on which it is defined and collecting the results. + * The order of the elements is preserved. + */ + def collect[B](pf: PartialFunction[A, B]): CC[B] + + /** Zips this $coll with its indices. + * + * @return A new $ccoll containing pairs consisting of all elements of this $coll paired with their index. + * Indices start at `0`. + * @example + * `List("a", "b", "c").zipWithIndex == List(("a", 0), ("b", 1), ("c", 2))` + */ + def zipWithIndex: CC[(A @uncheckedVariance, Int)] + + /** Splits this $coll into a prefix/suffix pair according to a predicate. + * + * Note: `c span p` is equivalent to (but possibly more efficient than) + * `(c takeWhile p, c dropWhile p)`, provided the evaluation of the + * predicate `p` does not cause any side effects. + * $orderDependent + * + * @param p the test predicate + * @return a pair consisting of the longest prefix of this $coll whose + * elements all satisfy `p`, and the rest of this $coll. + */ + def span(p: A => Boolean): (C, C) + + /** Splits this $coll into a prefix/suffix pair at a given position. + * + * Note: `c splitAt n` is equivalent to (but possibly more efficient than) + * `(c take n, c drop n)`. + * $orderDependent + * + * @param n the position at which to split. + * @return a pair of ${coll}s consisting of the first `n` + * elements of this $coll, and the other elements. + */ + def splitAt(n: Int): (C, C) = { + class Spanner extends runtime.AbstractFunction1[A, Boolean] { + var i = 0 + def apply(a: A) = i < n && { i += 1 ; true } + } + val spanner = new Spanner + span(spanner) + } + + /** Applies a side-effecting function to each element in this collection. + * Strict collections will apply `f` to their elements immediately, while lazy collections + * like Views and LazyLists will only apply `f` on each element if and when that element + * is evaluated, and each time that element is evaluated. + * + * @param f a function to apply to each element in this $coll + * @tparam U the return type of f + * @return The same logical collection as this + */ + def tapEach[U](f: A => U): C + + /////////////////////////////////////////////////////////////// Concrete methods based on iterator + + /** Tests whether this $coll is known to have a finite size. + * All strict collections are known to have finite size. For a non-strict + * collection such as `Stream`, the predicate returns `'''true'''` if all + * elements have been computed. It returns `'''false'''` if the stream is + * not yet evaluated to the end. Non-empty Iterators usually return + * `'''false'''` even if they were created from a collection with a known + * finite size. + * + * Note: many collection methods will not work on collections of infinite sizes. + * The typical failure mode is an infinite loop. These methods always attempt a + * traversal without checking first that `hasDefiniteSize` returns `'''true'''`. + * However, checking `hasDefiniteSize` can provide an assurance that size is + * well-defined and non-termination is not a concern. + * + * @deprecated This method is deprecated in 2.13 because it does not provide any + * actionable information. As noted above, even the collection library itself + * does not use it. When there is no guarantee that a collection is finite, it + * is generally best to attempt a computation anyway and document that it will + * not terminate for infinite collections rather than backing out because this + * would prevent performing the computation on collections that are in fact + * finite even though `hasDefiniteSize` returns `false`. + * + * @see method `knownSize` for a more useful alternative + * + * @return `'''true'''` if this collection is known to have finite size, + * `'''false'''` otherwise. + */ + @deprecated("Check .knownSize instead of .hasDefiniteSize for more actionable information (see scaladoc for details)", "2.13.0") + def hasDefiniteSize: Boolean = true + + /** Tests whether this $coll can be repeatedly traversed. Always + * true for Iterables and false for Iterators unless overridden. + * + * @return `true` if it is repeatedly traversable, `false` otherwise. + */ + def isTraversableAgain: Boolean = false + + /** Applies `f` to each element for its side effects. + * Note: `U` parameter needed to help scalac's type inference. + */ + def foreach[U](f: A => U): Unit = { + val it = iterator + while(it.hasNext) f(it.next()) + } + + /** Tests whether a predicate holds for all elements of this $coll. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @return `true` if this $coll is empty or the given predicate `p` + * holds for all elements of this $coll, otherwise `false`. + */ + def forall(p: A => Boolean): Boolean = { + var res = true + val it = iterator + while (res && it.hasNext) res = p(it.next()) + res + } + + /** Tests whether a predicate holds for at least one element of this $coll. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @return `true` if the given predicate `p` is satisfied by at least one element of this $coll, otherwise `false` + */ + def exists(p: A => Boolean): Boolean = { + var res = false + val it = iterator + while (!res && it.hasNext) res = p(it.next()) + res + } + + /** Counts the number of elements in the $coll which satisfy a predicate. + * + * $willNotTerminateInf + * + * @param p the predicate used to test elements. + * @return the number of elements satisfying the predicate `p`. + */ + def count(p: A => Boolean): Int = { + var res = 0 + val it = iterator + while (it.hasNext) if (p(it.next())) res += 1 + res + } + + /** Finds the first element of the $coll satisfying a predicate, if any. + * + * $mayNotTerminateInf + * $orderDependent + * + * @param p the predicate used to test elements. + * @return an option value containing the first element in the $coll + * that satisfies `p`, or `None` if none exists. + */ + def find(p: A => Boolean): Option[A] = { + val it = iterator + while (it.hasNext) { + val a = it.next() + if (p(a)) return Some(a) + } + None + } + + // in future, move to IndexedSeqOps + private def foldl[X >: A, B](seq: IndexedSeq[X], start: Int, z: B, op: (B, X) => B): B = { + @tailrec def loop(at: Int, end: Int, acc: B): B = + if (at == end) acc + else loop(at + 1, end, op(acc, seq(at))) + loop(start, seq.length, z) + } + + private def foldr[X >: A, B >: X](seq: IndexedSeq[X], op: (X, B) => B): B = { + @tailrec def loop(at: Int, acc: B): B = + if (at == 0) acc + else loop(at - 1, op(seq(at - 1), acc)) + loop(seq.length - 1, seq(seq.length - 1)) + } + + /** Applies the given binary operator `op` to the given initial value `z` and all + * elements of this $coll, going left to right. Returns the initial value if this $coll + * is empty. + * + * "Going left to right" only makes sense if this collection is ordered: then if + * `x,,1,,`, `x,,2,,`, ..., `x,,n,,` are the elements of this $coll, the result is + * `op( op( ... op( op(z, x,,1,,), x,,2,,) ... ), x,,n,,)`. + * + * If this collection is not ordered, then for each application of the operator, each + * right operand is an element. In addition, the leftmost operand is the initial + * value, and each other left operand is itself an application of the operator. The + * elements of this $coll and the initial value all appear exactly once in the + * computation. + * + * $orderDependent + * $willNotTerminateInf + * + * @param z An initial value. + * @param op A binary operator. + * @tparam B The result type of the binary operator. + * @return The result of applying `op` to `z` and all elements of this $coll, + * going left to right. Returns `z` if this $coll is empty. + */ + def foldLeft[B](z: B)(op: (B, A) => B): B = this match { + case seq: IndexedSeq[A @unchecked] => foldl[A, B](seq, 0, z, op) + case _ => + var result = z + val it = iterator + while (it.hasNext) { + result = op(result, it.next()) + } + result + } + + /** Applies the given binary operator `op` to all elements of this $coll and the given + * initial value `z`, going right to left. Returns the initial value if this $coll is + * empty. + * + * "Going right to left" only makes sense if this collection is ordered: then if + * `x,,1,,`, `x,,2,,`, ..., `x,,n,,` are the elements of this $coll, the result is + * `op(x,,1,,, op(x,,2,,, op( ... op(x,,n,,, z) ... )))`. + * + * If this collection is not ordered, then for each application of the operator, each + * left operand is an element. In addition, the rightmost operand is the initial + * value, and each other right operand is itself an application of the operator. The + * elements of this $coll and the initial value all appear exactly once in the + * computation. + * + * $orderDependent + * $willNotTerminateInf + * + * @param z An initial value. + * @param op A binary operator. + * @tparam B The result type of the binary operator. + * @return The result of applying `op` to all elements of this $coll and `z`, + * going right to left. Returns `z` if this $coll is empty. + */ + def foldRight[B](z: B)(op: (A, B) => B): B = reversed.foldLeft(z)((b, a) => op(a, b)) + + @deprecated("Use foldLeft instead of /:", "2.13.0") + @`inline` final def /: [B](z: B)(op: (B, A) => B): B = foldLeft[B](z)(op) + + @deprecated("Use foldRight instead of :\\", "2.13.0") + @`inline` final def :\ [B](z: B)(op: (A, B) => B): B = foldRight[B](z)(op) + + /** Applies the given binary operator `op` to the given initial value `z` and all + * elements of this $coll. + * + * For each application of the operator, each operand is either an element of this + * $coll, the initial value, or another such application of the operator. + * $undefinedOrder $exactlyOnce The initial value may be used an arbitrary number of + * times, but at least once. + * + * If this collection is ordered, then for any application of the operator, the + * element(s) appearing in the left operand will precede those in the right. + * + * $orderIndependentReduce In either case, it is also necessary that the initial value + * be a neutral value for the operator, e.g. `Nil` for `List` concatenation or `1` for + * multiplication. + * + * The default implementation in `IterableOnce` is equivalent to `foldLeft` but may be + * overridden for more efficient traversal orders. + * + * $willNotTerminateInf + * + * @tparam A1 The type parameter for the binary operator, a supertype of `A`. + * @param z An initial value; may be used an arbitrary number of times in the + * computation of the result; must be a neutral value for `op` for the + * result to always be the same across runs. + * @param op A binary operator; must be associative for the result to always be the + * same across runs. + * @return The result of applying `op` between all the elements and `z`, or `z` + * if this $coll is empty. + */ + def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1 = foldLeft(z)(op) + + /** Applies the given binary operator `op` to all elements of this $coll. + * + * For each application of the operator, each operand is either an element of this + * $coll or another such application of the operator. $undefinedOrder $exactlyOnce + * + * If this collection is ordered, then for any application of the operator, the + * element(s) appearing in the left operand will precede those in the right. + * + * $orderIndependentReduce + * $willNotTerminateInf + * + * @tparam B The type parameter for the binary operator, a supertype of `A`. + * @param op A binary operator; must be associative for the result to always be the + * same across runs. + * @return The result of applying `op` between all the elements if the $coll is + * nonempty. + * @throws UnsupportedOperationException if this $coll is empty. + */ + def reduce[B >: A](op: (B, B) => B): B = reduceLeft(op) + + /** If this $coll is nonempty, reduces it with the given binary operator `op`. + * + * The behavior is the same as [[reduce]] except that the value is `None` if the $coll + * is empty. $undefinedOrder $exactlyOnce + * + * $orderIndependentReduce + * $willNotTerminateInf + * + * @tparam B A type parameter for the binary operator, a supertype of `A`. + * @param op A binary operator; must be associative for the result to always be the + * same across runs. + * @return The result of reducing this $coll with `op` if the $coll is nonempty, + * inside a `Some`, and `None` otherwise. + */ + def reduceOption[B >: A](op: (B, B) => B): Option[B] = reduceLeftOption(op) + + /** Applies the given binary operator `op` to all elements of this $coll, going left to + * right. + * + * "Going left to right" only makes sense if this collection is ordered: then if + * `x,,1,,`, `x,,2,,`, ..., `x,,n,,` are the elements of this $coll, the result is + * `op( op( op( ... op(x,,1,,, x,,2,,) ... ), x,,n-1,,), x,,n,,)`. + * + * If this collection is not ordered, then for each application of the operator, each + * right operand is an element. In addition, the leftmost operand is the first element + * of this $coll and each other left operand is itself an application of the + * operator. $exactlyOnce + * + * $orderDependentReduce + * $willNotTerminateInf + * + * @param op A binary operator. + * @tparam B The result type of the binary operator, a supertype of `A`. + * @return The result of applying `op` to all elements of this $coll, going + * left to right. + * @throws UnsupportedOperationException if this $coll is empty. + */ + def reduceLeft[B >: A](op: (B, A) => B): B = this match { + case seq: IndexedSeq[A @unchecked] if seq.length > 0 => foldl(seq, 1, seq(0), op) + case _ if knownSize == 0 => throw new UnsupportedOperationException("empty.reduceLeft") + case _ => reduceLeftIterator[B](throw new UnsupportedOperationException("empty.reduceLeft"))(op) + } + private final def reduceLeftIterator[B >: A](onEmpty: => B)(op: (B, A) => B): B = { + val it = iterator + if (it.hasNext) { + var acc: B = it.next() + while (it.hasNext) + acc = op(acc, it.next()) + acc + } + else onEmpty + } + + /** Applies the given binary operator `op` to all elements of this $coll, going right to + * left. + * + * "Going right to left" only makes sense if this collection is ordered: then if + * `x,,1,,`, `x,,2,,`, ..., `x,,n,,` are the elements of this $coll, the result is + * `op(x,,1,,, op(x,,2,,, op( ... op(x,,n-1,,, x,,n,,) ... )))`. + * + * If this collection is not ordered, then for each application of the operator, each + * left operand is an element. In addition, the rightmost operand is the last element + * of this $coll and each other right operand is itself an application of the + * operator. $exactlyOnce + * + * $orderDependentReduce + * $willNotTerminateInf + * + * @param op A binary operator. + * @tparam B The result type of the binary operator, a supertype of `A`. + * @return The result of applying `op` to all elements of this $coll, going + * right to left. + * @throws UnsupportedOperationException if this $coll is empty. + */ + def reduceRight[B >: A](op: (A, B) => B): B = this match { + case seq: IndexedSeq[A @unchecked] if seq.length > 0 => foldr[A, B](seq, op) + case _ if knownSize == 0 => throw new UnsupportedOperationException("empty.reduceRight") + case _ => reversed.reduceLeft[B]((x, y) => op(y, x)) // reduceLeftIterator + } + + /** If this $coll is nonempty, reduces it with the given binary operator `op`, going + * left to right. + * + * The behavior is the same as [[reduceLeft]] except that the value is `None` if the + * $coll is empty. $exactlyOnce + * + * $orderDependentReduce + * $willNotTerminateInf + * + * @param op A binary operator. + * @tparam B The result type of the binary operator, a supertype of `A`. + * @return The result of reducing this $coll with `op` going left to right if + * the $coll is nonempty, inside a `Some`, and `None` otherwise. + */ + def reduceLeftOption[B >: A](op: (B, A) => B): Option[B] = + knownSize match { + case -1 => reduceLeftOptionIterator[B](op) + case 0 => None + case _ => Some(reduceLeft(op)) + } + private final def reduceLeftOptionIterator[B >: A](op: (B, A) => B): Option[B] = reduceOptionIterator[A, B](iterator)(op) + private final def reduceOptionIterator[X >: A, B >: X](it: Iterator[X])(op: (B, X) => B): Option[B] = { + if (it.hasNext) { + var acc: B = it.next() + while (it.hasNext) + acc = op(acc, it.next()) + Some(acc) + } + else None + } + + /** If this $coll is nonempty, reduces it with the given binary operator `op`, going + * right to left. + * + * The behavior is the same as [[reduceRight]] except that the value is `None` if the + * $coll is empty. $exactlyOnce + * + * $orderDependentReduce + * $willNotTerminateInf + * + * @param op A binary operator. + * @tparam B The result type of the binary operator, a supertype of `A`. + * @return The result of reducing this $coll with `op` going right to left if + * the $coll is nonempty, inside a `Some`, and `None` otherwise. + */ + def reduceRightOption[B >: A](op: (A, B) => B): Option[B] = + knownSize match { + case -1 => reduceOptionIterator[A, B](reversed.iterator)((x, y) => op(y, x)) + case 0 => None + case _ => Some(reduceRight(op)) + } + + /** Tests whether the $coll is empty. + * + * Note: The default implementation creates and discards an iterator. + * + * Note: Implementations in subclasses that are not repeatedly iterable must take + * care not to consume any elements when `isEmpty` is called. + * + * @return `true` if the $coll contains no elements, `false` otherwise. + */ + def isEmpty: Boolean = + knownSize match { + case -1 => !iterator.hasNext + case 0 => true + case _ => false + } + + /** Tests whether the $coll is not empty. + * + * @return `true` if the $coll contains at least one element, `false` otherwise. + */ + @deprecatedOverriding("nonEmpty is defined as !isEmpty; override isEmpty instead", "2.13.0") + def nonEmpty: Boolean = !isEmpty + + /** The size of this $coll. + * + * $willNotTerminateInf + * + * @return the number of elements in this $coll. + */ + def size: Int = + if (knownSize >= 0) knownSize + else { + val it = iterator + var len = 0 + while (it.hasNext) { len += 1; it.next() } + len + } + + @deprecated("Use `dest ++= coll` instead", "2.13.0") + @inline final def copyToBuffer[B >: A](dest: mutable.Buffer[B]): Unit = dest ++= this + + /** Copies elements to an array, returning the number of elements written. + * + * Fills the given array `xs` starting at index `start` with values of this $coll. + * + * Copying will stop once either all the elements of this $coll have been copied, + * or the end of the array is reached. + * + * @param xs the array to fill. + * @tparam B the type of the elements of the array. + * @return the number of elements written to the array + * + * @note Reuse: $consumesIterator + */ + @deprecatedOverriding("This should always forward to the 3-arg version of this method", since = "2.13.4") + def copyToArray[B >: A](xs: Array[B]): Int = copyToArray(xs, 0, Int.MaxValue) + + /** Copies elements to an array, returning the number of elements written. + * + * Fills the given array `xs` starting at index `start` with values of this $coll. + * + * Copying will stop once either all the elements of this $coll have been copied, + * or the end of the array is reached. + * + * @param xs the array to fill. + * @param start the starting index of xs. + * @tparam B the type of the elements of the array. + * @return the number of elements written to the array + * + * @note Reuse: $consumesIterator + */ + @deprecatedOverriding("This should always forward to the 3-arg version of this method", since = "2.13.4") + def copyToArray[B >: A](xs: Array[B], start: Int): Int = copyToArray(xs, start, Int.MaxValue) + + /** Copy elements to an array, returning the number of elements written. + * + * Fills the given array `xs` starting at index `start` with at most `len` elements of this $coll. + * + * Copying will stop once either all the elements of this $coll have been copied, + * or the end of the array is reached, or `len` elements have been copied. + * + * @param xs the array to fill. + * @param start the starting index of xs. + * @param len the maximal number of elements to copy. + * @tparam B the type of the elements of the array. + * @return the number of elements written to the array + * + * @note Reuse: $consumesIterator + */ + def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): Int = { + val it = iterator + var i = start + val end = start + math.min(len, xs.length - start) + while (i < end && it.hasNext) { + xs(i) = it.next() + i += 1 + } + i - start + } + + /** Sums the elements of this collection. + * + * The default implementation uses `reduce` for a known non-empty collection, `foldLeft` otherwise. + * + * $willNotTerminateInf + * + * @param num an implicit parameter defining a set of numeric operations + * which includes the `+` operator to be used in forming the sum. + * @tparam B the result type of the `+` operator. + * @return the sum of all elements of this $coll with respect to the `+` operator in `num`. + */ + def sum[B >: A](implicit num: Numeric[B]): B = + knownSize match { + case -1 => foldLeft(num.zero)(num.plus) + case 0 => num.zero + case _ => reduce(num.plus) + } + + /** Multiplies together the elements of this collection. + * + * The default implementation uses `reduce` for a known non-empty collection, `foldLeft` otherwise. + * + * $willNotTerminateInf + * + * @param num an implicit parameter defining a set of numeric operations + * which includes the `*` operator to be used in forming the product. + * @tparam B the result type of the `*` operator. + * @return the product of all elements of this $coll with respect to the `*` operator in `num`. + */ + def product[B >: A](implicit num: Numeric[B]): B = + knownSize match { + case -1 => foldLeft(num.one)(num.times) + case 0 => num.one + case _ => reduce(num.times) + } + + /** Finds the smallest element. + * + * $willNotTerminateInf + * + * @param ord An ordering to be used for comparing elements. + * @tparam B The type over which the ordering is defined. + * @throws UnsupportedOperationException if this $coll is empty. + * @return the smallest element of this $coll with respect to the ordering `ord`. + * + */ + def min[B >: A](implicit ord: Ordering[B]): A = + knownSize match { + case -1 => reduceLeftIterator[A](throw new UnsupportedOperationException("empty.min"))(ord.min) + case 0 => throw new UnsupportedOperationException("empty.min") + case _ => reduceLeft(ord.min) + } + + /** Finds the smallest element. + * + * $willNotTerminateInf + * + * @param ord An ordering to be used for comparing elements. + * @tparam B The type over which the ordering is defined. + * @return an option value containing the smallest element of this $coll + * with respect to the ordering `ord`. + */ + def minOption[B >: A](implicit ord: Ordering[B]): Option[A] = + knownSize match { + case -1 => reduceLeftOptionIterator[A](ord.min) + case 0 => None + case _ => Some(reduceLeft(ord.min)) + } + + /** Finds the largest element. + * + * $willNotTerminateInf + * + * @param ord An ordering to be used for comparing elements. + * @tparam B The type over which the ordering is defined. + * @throws UnsupportedOperationException if this $coll is empty. + * @return the largest element of this $coll with respect to the ordering `ord`. + */ + def max[B >: A](implicit ord: Ordering[B]): A = + knownSize match { + case -1 => reduceLeftIterator[A](throw new UnsupportedOperationException("empty.max"))(ord.max) + case 0 => throw new UnsupportedOperationException("empty.max") + case _ => reduceLeft(ord.max) + } + + /** Finds the largest element. + * + * $willNotTerminateInf + * + * @param ord An ordering to be used for comparing elements. + * @tparam B The type over which the ordering is defined. + * @return an option value containing the largest element of this $coll with + * respect to the ordering `ord`. + */ + def maxOption[B >: A](implicit ord: Ordering[B]): Option[A] = + knownSize match { + case -1 => reduceLeftOptionIterator[A](ord.max) + case 0 => None + case _ => Some(reduceLeft(ord.max)) + } + + /** Finds the first element which yields the largest value measured by function `f`. + * + * $willNotTerminateInf + * + * @param cmp An ordering to be used for comparing elements. + * @tparam B The result type of the function `f`. + * @param f The measuring function. + * @throws UnsupportedOperationException if this $coll is empty. + * @return the first element of this $coll with the largest value measured by function `f` + * with respect to the ordering `cmp`. + */ + def maxBy[B](f: A => B)(implicit ord: Ordering[B]): A = + knownSize match { + case 0 => throw new UnsupportedOperationException("empty.maxBy") + case _ => foldLeft(new Maximized[A, B]("maxBy")(f)(ord.gt))((m, a) => m(m, a)).result + } + + private class Maximized[X, B](descriptor: String)(f: X => B)(cmp: (B, B) => Boolean) extends AbstractFunction2[Maximized[X, B], X, Maximized[X, B]] { + var maxElem: X = null.asInstanceOf[X] + var maxF: B = null.asInstanceOf[B] + var nonEmpty = false + def toOption: Option[X] = if (nonEmpty) Some(maxElem) else None + def result: X = if (nonEmpty) maxElem else throw new UnsupportedOperationException(s"empty.$descriptor") + def apply(m: Maximized[X, B], a: X): Maximized[X, B] = + if (m.nonEmpty) { + val fa = f(a) + if (cmp(fa, maxF)) { + maxF = fa + maxElem = a + } + m + } + else { + m.nonEmpty = true + m.maxElem = a + m.maxF = f(a) + m + } + } + + /** Finds the first element which yields the largest value measured by function `f`. + * + * $willNotTerminateInf + * + * @param cmp An ordering to be used for comparing elements. + * @tparam B The result type of the function `f`. + * @param f The measuring function. + * @return an option value containing the first element of this $coll with the + * largest value measured by function `f` with respect to the ordering `cmp`. + */ + def maxByOption[B](f: A => B)(implicit ord: Ordering[B]): Option[A] = + knownSize match { + case 0 => None + case _ => foldLeft(new Maximized[A, B]("maxBy")(f)(ord.gt))((m, a) => m(m, a)).toOption + } + + /** Finds the first element which yields the smallest value measured by function `f`. + * + * $willNotTerminateInf + * + * @param cmp An ordering to be used for comparing elements. + * @tparam B The result type of the function `f`. + * @param f The measuring function. + * @throws UnsupportedOperationException if this $coll is empty. + * @return the first element of this $coll with the smallest value measured by function `f` + * with respect to the ordering `cmp`. + */ + def minBy[B](f: A => B)(implicit ord: Ordering[B]): A = + knownSize match { + case 0 => throw new UnsupportedOperationException("empty.minBy") + case _ => foldLeft(new Maximized[A, B]("minBy")(f)(ord.lt))((m, a) => m(m, a)).result + } + + /** Finds the first element which yields the smallest value measured by function `f`. + * + * $willNotTerminateInf + * + * @param cmp An ordering to be used for comparing elements. + * @tparam B The result type of the function `f`. + * @param f The measuring function. + * @return an option value containing the first element of this $coll + * with the smallest value measured by function `f` + * with respect to the ordering `cmp`. + */ + def minByOption[B](f: A => B)(implicit ord: Ordering[B]): Option[A] = + knownSize match { + case 0 => None + case _ => foldLeft(new Maximized[A, B]("minBy")(f)(ord.lt))((m, a) => m(m, a)).toOption + } + + /** Finds the first element of the $coll for which the given partial + * function is defined, and applies the partial function to it. + * + * $mayNotTerminateInf + * $orderDependent + * + * @param pf the partial function + * @return an option value containing pf applied to the first + * value for which it is defined, or `None` if none exists. + * @example `Seq("a", 1, 5L).collectFirst({ case x: Int => x*10 }) = Some(10)` + */ + def collectFirst[B](pf: PartialFunction[A, B]): Option[B] = { + // Presumably the fastest way to get in and out of a partial function is for a sentinel function to return itself + // (Tested to be lower-overhead than runWith. Would be better yet to not need to (formally) allocate it) + val sentinel: scala.Function1[A, Any] = new AbstractFunction1[A, Any] { + def apply(a: A): AbstractFunction1[A, Any] = this + } + val it = iterator + while (it.hasNext) { + val x = pf.applyOrElse(it.next(), sentinel) + if (x.asInstanceOf[AnyRef] ne sentinel) return Some(x.asInstanceOf[B]) + } + None + } + + /** Aggregates the results of applying an operator to subsequent elements. + * + * Since this method degenerates to `foldLeft` for sequential (non-parallel) collections, + * where the combining operation is ignored, it is advisable to prefer `foldLeft` for that case. + * + * For [[https://github.com/scala/scala-parallel-collections parallel collections]], + * use the `aggregate` method specified by `scala.collection.parallel.ParIterableLike`. + * + * @param z the start value, a neutral element for `seqop`. + * @param seqop the binary operator used to accumulate the result. + * @param combop an associative operator for combining sequential results, unused for sequential collections. + * @tparam B the result type, produced by `seqop`, `combop`, and by this function as a final result. + */ + @deprecated("For sequential collections, prefer `foldLeft(z)(seqop)`. For parallel collections, use `ParIterableLike#aggregate`.", "2.13.0") + def aggregate[B](z: => B)(seqop: (B, A) => B, combop: (B, B) => B): B = foldLeft(z)(seqop) + + /** Tests whether every element of this collection's iterator relates to the + * corresponding element of another collection by satisfying a test predicate. + * + * $willNotTerminateInf + * + * @param that the other collection + * @param p the test predicate, which relates elements from both collections + * @tparam B the type of the elements of `that` + * @return `true` if both collections have the same length and + * `p(x, y)` is `true` for all corresponding elements `x` of this iterator + * and `y` of `that`, otherwise `false` + */ + def corresponds[B](that: IterableOnce[B])(p: (A, B) => Boolean): Boolean = { + val a = iterator + val b = that.iterator + + while (a.hasNext && b.hasNext) { + if (!p(a.next(), b.next())) return false + } + + a.hasNext == b.hasNext + } + + /** Displays all elements of this $coll in a string using start, end, and separator strings. + * + * Delegates to addString, which can be overridden. + * + * @param start the starting string. + * @param sep the separator string. + * @param end the ending string. + * @return a string representation of this $coll. The resulting string + * begins with the string `start` and ends with the string + * `end`. Inside, the string representations (w.r.t. the method + * `toString`) of all elements of this $coll are separated by + * the string `sep`. + * + * @example `List(1, 2, 3).mkString("(", "; ", ")") = "(1; 2; 3)"` + */ + final def mkString(start: String, sep: String, end: String): String = + if (knownSize == 0) start + end + else addString(new StringBuilder(), start, sep, end).result() + + /** Displays all elements of this $coll in a string using a separator string. + * + * Delegates to addString, which can be overridden. + * + * @param sep the separator string. + * @return a string representation of this $coll. In the resulting string + * the string representations (w.r.t. the method `toString`) + * of all elements of this $coll are separated by the string `sep`. + * + * @example `List(1, 2, 3).mkString("|") = "1|2|3"` + */ + @inline final def mkString(sep: String): String = mkString("", sep, "") + + /** Displays all elements of this $coll in a string. + * + * Delegates to addString, which can be overridden. + * + * @return a string representation of this $coll. In the resulting string + * the string representations (w.r.t. the method `toString`) + * of all elements of this $coll follow each other without any + * separator string. + */ + @inline final def mkString: String = mkString("") + + /** Appends all elements of this $coll to a string builder using start, end, and separator strings. + * The written text begins with the string `start` and ends with the string `end`. + * Inside, the string representations (w.r.t. the method `toString`) + * of all elements of this $coll are separated by the string `sep`. + * + * Example: + * + * {{{ + * scala> val a = List(1,2,3,4) + * a: List[Int] = List(1, 2, 3, 4) + * + * scala> val b = new StringBuilder() + * b: StringBuilder = + * + * scala> a.addString(b , "List(" , ", " , ")") + * res5: StringBuilder = List(1, 2, 3, 4) + * }}} + * + * @param b the string builder to which elements are appended. + * @param start the starting string. + * @param sep the separator string. + * @param end the ending string. + * @return the string builder `b` to which elements were appended. + */ + def addString(b: StringBuilder, start: String, sep: String, end: String): b.type = { + val jsb = b.underlying + if (start.length != 0) jsb.append(start) + val it = iterator + if (it.hasNext) { + jsb.append(it.next()) + while (it.hasNext) { + if (sep.length != 0) jsb.append(sep) + jsb.append(it.next()) + } + } + if (end.length != 0) jsb.append(end) + b + } + + /** Appends all elements of this $coll to a string builder using a separator string. + * The written text consists of the string representations (w.r.t. the method `toString`) + * of all elements of this $coll, separated by the string `sep`. + * + * Example: + * + * {{{ + * scala> val a = List(1,2,3,4) + * a: List[Int] = List(1, 2, 3, 4) + * + * scala> val b = new StringBuilder() + * b: StringBuilder = + * + * scala> a.addString(b, ", ") + * res0: StringBuilder = 1, 2, 3, 4 + * }}} + * + * @param b the string builder to which elements are appended. + * @param sep the separator string. + * @return the string builder `b` to which elements were appended. + */ + @inline final def addString(b: StringBuilder, sep: String): b.type = addString(b, "", sep, "") + + /** Appends all elements of this $coll to a string builder. + * The written text consists of the string representations (w.r.t. the method + * `toString`) of all elements of this $coll without any separator string. + * + * Example: + * + * {{{ + * scala> val a = List(1,2,3,4) + * a: List[Int] = List(1, 2, 3, 4) + * + * scala> val b = new StringBuilder() + * b: StringBuilder = + * + * scala> val h = a.addString(b) + * h: StringBuilder = 1234 + * }}} + * + * @param b the string builder to which elements are appended. + * @return the string builder `b` to which elements were appended. + */ + @inline final def addString(b: StringBuilder): b.type = addString(b, "") + + /** Given a collection factory `factory`, converts this $coll to the appropriate + * representation for the current element type `A`. Example uses: + * + * {{{ + * xs.to(List) + * xs.to(ArrayBuffer) + * xs.to(BitSet) // for xs: Iterable[Int] + * }}} + */ + def to[C1](factory: Factory[A, C1]): C1 = factory.fromSpecific(this) + + @deprecated("Use .iterator instead of .toIterator", "2.13.0") + @`inline` final def toIterator: Iterator[A] = iterator + + /** Converts this $coll to a `List`. + * + * @return This $coll as a `List[A]`. + */ + def toList: immutable.List[A] = immutable.List.from(this) + + /** Converts this $coll to a `Vector`. + * + * @return This $coll as a `Vector[A]`. + */ + def toVector: immutable.Vector[A] = immutable.Vector.from(this) + + /** Converts this $coll to a `Map`, given an implicit coercion from the $coll's type to a key-value tuple. + * + * @tparam K The key type for the resulting map. + * @tparam V The value type for the resulting map. + * @param ev An implicit coercion from `A` to `[K, V]`. + * @return This $coll as a `Map[K, V]`. + */ + def toMap[K, V](implicit ev: A <:< (K, V)): immutable.Map[K, V] = + immutable.Map.from(this.asInstanceOf[IterableOnce[(K, V)]]) + + /** Converts this $coll to a `Set`. + * + * @tparam B The type of elements of the result, a supertype of `A`. + * @return This $coll as a `Set[B]`. + */ + def toSet[B >: A]: immutable.Set[B] = immutable.Set.from(this) + + /** @return This $coll as a `Seq[A]`. This is equivalent to `to(Seq)` but might be faster. + */ + def toSeq: immutable.Seq[A] = immutable.Seq.from(this) + + /** Converts this $coll to an `IndexedSeq`. + * + * @return This $coll as an `IndexedSeq[A]`. + */ + def toIndexedSeq: immutable.IndexedSeq[A] = immutable.IndexedSeq.from(this) + + @deprecated("Use .to(LazyList) instead of .toStream", "2.13.0") + @inline final def toStream: immutable.Stream[A] = to(immutable.Stream) + + /** Converts this $coll to a `Buffer`. + * + * @tparam B The type of elements of the result, a supertype of `A`. + * @return This $coll as a `Buffer[B]`. + */ + @inline final def toBuffer[B >: A]: mutable.Buffer[B] = mutable.Buffer.from(this) + + /** Converts this $coll to an `Array`. + * + * Implementation note: DO NOT call [[Array.from]] from this method. + * + * @tparam B The type of elements of the result, a supertype of `A`. + * @return This $coll as an `Array[B]`. + */ + def toArray[B >: A: ClassTag]: Array[B] = + if (knownSize >= 0) { + val destination = new Array[B](knownSize) + @annotation.unused val copied = copyToArray(destination, 0) + //assert(copied == destination.length) + destination + } + else mutable.ArrayBuilder.make[B].addAll(this).result() + + // For internal use + protected def reversed: Iterable[A] = { + var xs: immutable.List[A] = immutable.Nil + val it = iterator + while (it.hasNext) xs = it.next() :: xs + xs + } +} diff --git a/library/src/scala/collection/Iterator.scala b/library/src/scala/collection/Iterator.scala new file mode 100644 index 000000000000..4fb1cc6d362b --- /dev/null +++ b/library/src/scala/collection/Iterator.scala @@ -0,0 +1,1314 @@ +/* + * 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.collection + +import scala.language.`2.13` +import scala.collection.mutable.{ArrayBuffer, ArrayBuilder, Builder, ImmutableBuilder} +import scala.annotation.tailrec +import scala.annotation.unchecked.uncheckedVariance +import scala.runtime.Statics + +/** Iterators are data structures that allow to iterate over a sequence + * of elements. They have a `hasNext` method for checking + * if there is a next element available, and a `next` method + * which returns the next element and advances the iterator. + * + * An iterator is mutable: most operations on it change its state. While it is often used + * to iterate through the elements of a collection, it can also be used without + * being backed by any collection (see constructors on the companion object). + * + * It is of particular importance to note that, unless stated otherwise, ''one should never + * use an iterator after calling a method on it''. The two most important exceptions + * are also the sole abstract methods: `next` and `hasNext`. + * + * Both these methods can be called any number of times without having to discard the + * iterator. Note that even `hasNext` may cause mutation -- such as when iterating + * from an input stream, where it will block until the stream is closed or some + * input becomes available. + * + * Consider this example for safe and unsafe use: + * + * {{{ + * def f[A](it: Iterator[A]) = { + * if (it.hasNext) { // Safe to reuse "it" after "hasNext" + * it.next() // Safe to reuse "it" after "next" + * val remainder = it.drop(2) // it is *not* safe to use "it" again after this line! + * remainder.take(2) // it is *not* safe to use "remainder" after this line! + * } else it + * } + * }}} + * + * @define mayNotTerminateInf + * Note: may not terminate for infinite iterators. + * @define preservesIterator + * The iterator remains valid for further use whatever result is returned. + * @define consumesIterator + * After calling this method, one should discard the iterator it was called + * on. Using it is undefined and subject to change. + * @define consumesAndProducesIterator + * After calling this method, one should discard the iterator it was called + * on, and use only the iterator that was returned. Using the old iterator + * is undefined, subject to change, and may result in changes to the new + * iterator as well. + * @define consumesTwoAndProducesOneIterator + * After calling this method, one should discard the iterator it was called + * on, as well as the one passed as a parameter, and use only the iterator + * that was returned. Using the old iterators is undefined, subject to change, + * and may result in changes to the new iterator as well. + * @define consumesOneAndProducesTwoIterators + * After calling this method, one should discard the iterator it was called + * on, and use only the iterators that were returned. Using the old iterator + * is undefined, subject to change, and may result in changes to the new + * iterators as well. + * @define coll iterator + */ +trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Iterator[A]] { self => + + /** Check if there is a next element available. + * + * @return `true` if there is a next element, `false` otherwise + * @note Reuse: $preservesIterator + */ + def hasNext: Boolean + + @deprecated("hasDefiniteSize on Iterator is the same as isEmpty", "2.13.0") + @`inline` override final def hasDefiniteSize = isEmpty + + /** Return the next element and advance the iterator. + * + * @throws NoSuchElementException if there is no next element. + * @return the next element. + * @note Reuse: Advances the iterator, which may exhaust the elements. It is valid to + * make additional calls on the iterator. + */ + @throws[NoSuchElementException] + def next(): A + + @inline final def iterator = this + + /** Wraps the value of `next()` in an option. + * + * @return `Some(next)` if a next element exists, `None` otherwise. + */ + def nextOption(): Option[A] = if (hasNext) Some(next()) else None + + /** Tests whether this iterator contains a given value as an element. + * $mayNotTerminateInf + * + * @param elem the element to test. + * @return `true` if this iterator produces some value that is + * is equal (as determined by `==`) to `elem`, `false` otherwise. + * @note Reuse: $consumesIterator + */ + def contains(elem: Any): Boolean = exists(_ == elem) // Note--this seems faster than manual inlining! + + /** Creates a buffered iterator from this iterator. + * + * @see [[scala.collection.BufferedIterator]] + * @return a buffered iterator producing the same values as this iterator. + * @note Reuse: $consumesAndProducesIterator + */ + def buffered: BufferedIterator[A] = new AbstractIterator[A] with BufferedIterator[A] { + private[this] var hd: A = _ + private[this] var hdDefined: Boolean = false + + def head: A = { + if (!hdDefined) { + hd = next() + hdDefined = true + } + hd + } + + override def knownSize = { + val thisSize = self.knownSize + if (thisSize >= 0 && hdDefined) thisSize + 1 + else thisSize + } + + def hasNext = + hdDefined || self.hasNext + + def next() = + if (hdDefined) { + hdDefined = false + hd + } else self.next() + } + + /** A flexible iterator for transforming an `Iterator[A]` into an + * `Iterator[Seq[A]]`, with configurable sequence size, step, and + * strategy for dealing with remainder elements which don't fit evenly + * into the last group. + * + * A `GroupedIterator` is yielded by `grouped` and by `sliding`, + * where the `step` may differ from the group `size`. + */ + class GroupedIterator[B >: A](self: Iterator[B], size: Int, step: Int) extends AbstractIterator[immutable.Seq[B]] { + + require(size >= 1 && step >= 1, f"size=$size%d and step=$step%d, but both must be positive") + + private[this] var buffer: Array[B] = null // current result + private[this] var prev: Array[B] = null // if sliding, overlap from previous result + private[this] var first = true // if !first, advancing may skip ahead + private[this] var filled = false // whether the buffer is "hot" + private[this] var partial = true // whether to emit partial sequence + private[this] var padding: () => B = null // what to pad short sequences with + private[this] def pad = padding != null // irrespective of partial flag + private[this] def newBuilder = { + val b = ArrayBuilder.make[Any] + val k = self.knownSize + if (k > 0) b.sizeHint(k min size) // if k < size && !partial, buffer will grow on padding + b + } + + /** Specifies a fill element used to pad a partial segment + * so that all segments have the same size. + * + * Any previous setting of `withPartial` is ignored, + * as the last group will always be padded to `size` elements. + * + * The by-name argument is evaluated for each fill element. + * + * @param x The element that will be appended to the last segment, if necessary. + * @return The same iterator, and ''not'' a new iterator. + * @note This method mutates the iterator it is called on, which can be safely used afterwards. + * @note This method is mutually exclusive with `withPartial`. + * @group Configuration + */ + def withPadding(x: => B): this.type = { + padding = () => x + partial = true // redundant, as padding always results in complete segment + this + } + /** Specify whether to drop the last segment if it has less than `size` elements. + * + * If this flag is `false`, elements of a partial segment at the end of the iterator + * are not returned. + * + * The flag defaults to `true`. + * + * Any previous setting of `withPadding` is ignored, + * as the last group will never be padded. + * A partial segment is either retained or dropped, per the flag. + * + * @param x `true` if partial segments may be returned, `false` otherwise. + * @return The same iterator, and ''not'' a new iterator. + * @note This method mutates the iterator it is called on, which can be safely used afterwards. + * @note This method is mutually exclusive with `withPadding`. + * @group Configuration + */ + def withPartial(x: Boolean): this.type = { + partial = x + padding = null + this + } + + /** Eagerly fetch `size` elements to buffer. + * + * If buffer is dirty and stepping, copy prefix. + * If skipping, skip ahead. + * Fetch remaining elements. + * If unable to deliver size, then pad if padding enabled, otherwise drop segment. + * Returns true if successful in delivering `count` elements, + * or padded segment, or partial segment. + */ + private def fulfill(): Boolean = { + val builder = newBuilder + var done = false + // keep prefix of previous buffer if stepping + if (prev != null) builder.addAll(prev) + // skip ahead + if (!first && step > size) { + var dropping = step - size + while (dropping > 0 && self.hasNext) { + self.next(): Unit + dropping -= 1 + } + done = dropping > 0 // skip failed + } + var index = builder.length + if (!done) { + // advance to rest of segment if possible + while (index < size && self.hasNext) { + builder.addOne(self.next()) + index += 1 + } + // if unable to complete segment, pad if possible + if (index < size && pad) { + builder.sizeHint(size) + while (index < size) { + builder.addOne(padding()) + index += 1 + } + } + } + // segment must have data, and must be complete unless they allow partial + val ok = index > 0 && (partial || index == size) + if (ok) buffer = builder.result().asInstanceOf[Array[B]] + else prev = null + ok + } + + // fill() returns false if no more sequences can be produced + private def fill(): Boolean = filled || { filled = self.hasNext && fulfill() ; filled } + + def hasNext = fill() + + @throws[NoSuchElementException] + def next(): immutable.Seq[B] = + if (!fill()) Iterator.empty.next() + else { + filled = false + // if stepping, retain overlap in prev + if (step < size) { + if (first) prev = buffer.drop(step) + else if (buffer.length == size) Array.copy(src = buffer, srcPos = step, dest = prev, destPos = 0, length = size - step) + else prev = null + } + val res = immutable.ArraySeq.unsafeWrapArray(buffer).asInstanceOf[immutable.ArraySeq[B]] + buffer = null + first = false + res + } + } + + /** A copy of this $coll with an element value appended until a given target length is reached. + * + * @param len the target length + * @param elem the padding value + * @tparam B the element type of the returned $coll. + * @return a new $coll consisting of + * all elements of this $coll followed by the minimal number of occurrences of `elem` so + * that the resulting collection has a length of at least `len`. + */ + def padTo[B >: A](len: Int, elem: B): Iterator[B] = new AbstractIterator[B] { + private[this] var i = 0 + + override def knownSize: Int = { + val thisSize = self.knownSize + if (thisSize < 0) -1 + else thisSize max (len - i) + } + + def next(): B = { + val b = + if (self.hasNext) self.next() + else if (i < len) elem + else Iterator.empty.next() + i += 1 + b + } + + def hasNext: Boolean = self.hasNext || i < len + } + + /** Partitions this iterator in two iterators according to a predicate. + * + * @param p the predicate on which to partition + * @return a pair of iterators: the iterator that satisfies the predicate + * `p` and the iterator that does not. + * The relative order of the elements in the resulting iterators + * is the same as in the original iterator. + * @note Reuse: $consumesOneAndProducesTwoIterators + */ + def partition(p: A => Boolean): (Iterator[A], Iterator[A]) = { + val (a, b) = duplicate + (a filter p, b filterNot p) + } + + /** Returns an iterator which groups this iterator into fixed size + * blocks. Example usages: + * {{{ + * // Returns List(List(1, 2, 3), List(4, 5, 6), List(7))) + * (1 to 7).iterator.grouped(3).toList + * // Returns List(List(1, 2, 3), List(4, 5, 6)) + * (1 to 7).iterator.grouped(3).withPartial(false).toList + * // Returns List(List(1, 2, 3), List(4, 5, 6), List(7, 20, 25) + * // Illustrating that withPadding's argument is by-name. + * val it2 = Iterator.iterate(20)(_ + 5) + * (1 to 7).iterator.grouped(3).withPadding(it2.next).toList + * }}} + * + * @note Reuse: $consumesAndProducesIterator + */ + def grouped[B >: A](size: Int): GroupedIterator[B] = + new GroupedIterator[B](self, size, size) + + /** Returns an iterator which presents a "sliding window" view of + * this iterator. The first argument is the window size, and + * the second argument `step` is how far to advance the window + * on each iteration. The `step` defaults to `1`. + * + * The returned `GroupedIterator` can be configured to either + * pad a partial result to size `size` or suppress the partial + * result entirely. + * + * Example usages: + * {{{ + * // Returns List(ArraySeq(1, 2, 3), ArraySeq(2, 3, 4), ArraySeq(3, 4, 5)) + * (1 to 5).iterator.sliding(3).toList + * // Returns List(ArraySeq(1, 2, 3, 4), ArraySeq(4, 5)) + * (1 to 5).iterator.sliding(4, 3).toList + * // Returns List(ArraySeq(1, 2, 3, 4)) + * (1 to 5).iterator.sliding(4, 3).withPartial(false).toList + * // Returns List(ArraySeq(1, 2, 3, 4), ArraySeq(4, 5, 20, 25)) + * // Illustrating that withPadding's argument is by-name. + * val it2 = Iterator.iterate(20)(_ + 5) + * (1 to 5).iterator.sliding(4, 3).withPadding(it2.next).toList + * }}} + * + * @param size the number of elements per group + * @param step the distance between the first elements of successive + * groups + * @return A `GroupedIterator` producing `Seq[B]`s of size `size`, except the + * last element (which may be the only element) will be truncated + * if there are fewer than `size` elements remaining to be grouped. + * This behavior can be configured. + * + * @note Reuse: $consumesAndProducesIterator + */ + def sliding[B >: A](size: Int, step: Int = 1): GroupedIterator[B] = + new GroupedIterator[B](self, size, step) + + def scanLeft[B](z: B)(op: (B, A) => B): Iterator[B] = new AbstractIterator[B] { + // We use an intermediate iterator that iterates through the first element `z` + // and then that will be modified to iterate through the collection + private[this] var current: Iterator[B] = + new AbstractIterator[B] { + override def knownSize = { + val thisSize = self.knownSize + + if (thisSize < 0) -1 + else thisSize + 1 + } + def hasNext: Boolean = true + def next(): B = { + // Here we change our self-reference to a new iterator that iterates through `self` + current = new AbstractIterator[B] { + private[this] var acc = z + def next(): B = { + acc = op(acc, self.next()) + acc + } + def hasNext: Boolean = self.hasNext + override def knownSize = self.knownSize + } + z + } + } + override def knownSize = current.knownSize + def next(): B = current.next() + def hasNext: Boolean = current.hasNext + } + + @deprecated("Call scanRight on an Iterable instead.", "2.13.0") + def scanRight[B](z: B)(op: (A, B) => B): Iterator[B] = ArrayBuffer.from(this).scanRight(z)(op).iterator + + /** Finds index of the first element satisfying some predicate after or at some start index. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @param from the start index + * @return the index `>= from` of the first element of this $coll that satisfies the predicate `p`, + * or `-1`, if none exists. + * @note Reuse: $consumesIterator + */ + def indexWhere(p: A => Boolean, from: Int = 0): Int = { + var i = math.max(from, 0) + val dropped = drop(from) + while (dropped.hasNext) { + if (p(dropped.next())) return i + i += 1 + } + -1 + } + + /** Returns the index of the first occurrence of the specified + * object in this iterable object. + * $mayNotTerminateInf + * + * @param elem element to search for. + * @return the index of the first occurrence of `elem` in the values produced by this iterator, + * or -1 if such an element does not exist until the end of the iterator is reached. + * @note Reuse: $consumesIterator + */ + def indexOf[B >: A](elem: B): Int = indexOf(elem, 0) + + /** Returns the index of the first occurrence of the specified object in this iterable object + * after or at some start index. + * $mayNotTerminateInf + * + * @param elem element to search for. + * @param from the start index + * @return the index `>= from` of the first occurrence of `elem` in the values produced by this + * iterator, or -1 if such an element does not exist until the end of the iterator is + * reached. + * @note Reuse: $consumesIterator + */ + def indexOf[B >: A](elem: B, from: Int): Int = { + var i = 0 + while (i < from && hasNext) { + next() + i += 1 + } + + while (hasNext) { + if (next() == elem) return i + i += 1 + } + -1 + } + + @inline final def length: Int = size + + @deprecatedOverriding("isEmpty is defined as !hasNext; override hasNext instead", "2.13.0") + override def isEmpty: Boolean = !hasNext + + def filter(p: A => Boolean): Iterator[A] = filterImpl(p, isFlipped = false) + + def filterNot(p: A => Boolean): Iterator[A] = filterImpl(p, isFlipped = true) + + private[collection] def filterImpl(p: A => Boolean, isFlipped: Boolean): Iterator[A] = new AbstractIterator[A] { + private[this] var hd: A = _ + private[this] var hdDefined: Boolean = false + + def hasNext: Boolean = hdDefined || { + if (!self.hasNext) return false + hd = self.next() + while (p(hd) == isFlipped) { + if (!self.hasNext) return false + hd = self.next() + } + hdDefined = true + true + } + + def next() = + if (hasNext) { + hdDefined = false + hd + } + else Iterator.empty.next() + } + + /** Creates an iterator over all the elements of this iterator that + * satisfy the predicate `p`. The order of the elements + * is preserved. + * + * '''Note:''' `withFilter` is the same as `filter` on iterators. It exists so that + * for-expressions with filters work over iterators. + * + * @param p the predicate used to test values. + * @return an iterator which produces those values of this iterator which satisfy the predicate `p`. + * @note Reuse: $consumesAndProducesIterator + */ + def withFilter(p: A => Boolean): Iterator[A] = filter(p) + + def collect[B](pf: PartialFunction[A, B]): Iterator[B] = new AbstractIterator[B] with (A => B) { + // Manually buffer to avoid extra layer of wrapping with buffered + private[this] var hd: B = _ + + // Little state machine to keep track of where we are + // Seek = 0; Found = 1; Empty = -1 + // Not in vals because scalac won't make them static (@inline def only works with -optimize) + // BE REALLY CAREFUL TO KEEP COMMENTS AND NUMBERS IN SYNC! + private[this] var status = 0/*Seek*/ + + def apply(value: A): B = Statics.pfMarker.asInstanceOf[B] + + def hasNext = { + val marker = Statics.pfMarker + while (status == 0/*Seek*/) { + if (self.hasNext) { + val x = self.next() + val v = pf.applyOrElse(x, this) + if (marker ne v.asInstanceOf[AnyRef]) { + hd = v + status = 1/*Found*/ + } + } + else status = -1/*Empty*/ + } + status == 1/*Found*/ + } + def next() = if (hasNext) { status = 0/*Seek*/; hd } else Iterator.empty.next() + } + + /** + * Builds a new iterator from this one without any duplicated elements on it. + * @return iterator with distinct elements + * + * @note Reuse: $consumesIterator + */ + def distinct: Iterator[A] = distinctBy(identity) + + /** + * Builds a new iterator from this one without any duplicated elements as determined by `==` after applying + * the transforming function `f`. + * + * @param f The transforming function whose result is used to determine the uniqueness of each element + * @tparam B the type of the elements after being transformed by `f` + * @return iterator with distinct elements + * + * @note Reuse: $consumesIterator + */ + def distinctBy[B](f: A => B): Iterator[A] = new AbstractIterator[A] { + + private[this] val traversedValues = mutable.HashSet.empty[B] + private[this] var nextElementDefined: Boolean = false + private[this] var nextElement: A = _ + + def hasNext: Boolean = nextElementDefined || (self.hasNext && { + val a = self.next() + if (traversedValues.add(f(a))) { + nextElement = a + nextElementDefined = true + true + } + else hasNext + }) + + def next(): A = + if (hasNext) { + nextElementDefined = false + nextElement + } else { + Iterator.empty.next() + } + } + + def map[B](f: A => B): Iterator[B] = new AbstractIterator[B] { + override def knownSize = self.knownSize + def hasNext = self.hasNext + def next() = f(self.next()) + } + + def flatMap[B](f: A => IterableOnce[B]): Iterator[B] = new AbstractIterator[B] { + private[this] var cur: Iterator[B] = Iterator.empty + /** Trillium logic boolean: -1 = unknown, 0 = false, 1 = true */ + private[this] var _hasNext: Int = -1 + + def nextCur(): Unit = { + cur = Iterator.empty + cur = f(self.next()).iterator + _hasNext = -1 + } + + def hasNext: Boolean = { + if (_hasNext == -1) { + while (!cur.hasNext) { + if (!self.hasNext) { + _hasNext = 0 + // since we know we are exhausted, we can release cur for gc, and as well replace with + // static Iterator.empty which will support efficient subsequent `hasNext`/`next` calls + cur = Iterator.empty + return false + } + nextCur() + } + _hasNext = 1 + true + } else _hasNext == 1 + } + def next(): B = { + if (hasNext) { + _hasNext = -1 + } + cur.next() + } + } + + def flatten[B](implicit ev: A => IterableOnce[B]): Iterator[B] = + flatMap[B](ev) + + def concat[B >: A](xs: => IterableOnce[B]): Iterator[B] = new Iterator.ConcatIterator[B](self).concat(xs) + + @`inline` final def ++ [B >: A](xs: => IterableOnce[B]): Iterator[B] = concat(xs) + + def take(n: Int): Iterator[A] = sliceIterator(0, n max 0) + + def takeWhile(p: A => Boolean): Iterator[A] = new AbstractIterator[A] { + private[this] var hd: A = _ + private[this] var hdDefined: Boolean = false + private[this] var tail: Iterator[A] = self + + def hasNext = hdDefined || tail.hasNext && { + hd = tail.next() + if (p(hd)) hdDefined = true + else tail = Iterator.empty + hdDefined + } + def next() = if (hasNext) { hdDefined = false; hd } else Iterator.empty.next() + } + + def drop(n: Int): Iterator[A] = sliceIterator(n, -1) + + def dropWhile(p: A => Boolean): Iterator[A] = new AbstractIterator[A] { + // Magic value: -1 = hasn't dropped, 0 = found first, 1 = defer to parent iterator + private[this] var status = -1 + // Local buffering to avoid double-wrap with .buffered + private[this] var fst: A = _ + def hasNext: Boolean = + if (status == 1) self.hasNext + else if (status == 0) true + else { + while (self.hasNext) { + val a = self.next() + if (!p(a)) { + fst = a + status = 0 + return true + } + } + status = 1 + false + } + def next() = + if (hasNext) { + if (status == 1) self.next() + else { + status = 1 + fst + } + } + else Iterator.empty.next() + } + + /** + * @inheritdoc + * + * @note Reuse: $consumesOneAndProducesTwoIterators + */ + def span(p: A => Boolean): (Iterator[A], Iterator[A]) = { + /* + * Giving a name to following iterator (as opposed to trailing) because + * anonymous class is represented as a structural type that trailing + * iterator is referring (the finish() method) and thus triggering + * handling of structural calls. It's not what's intended here. + */ + final class Leading extends AbstractIterator[A] { + private[this] var lookahead: mutable.Queue[A] = null + private[this] var hd: A = _ + /* Status is kept with magic numbers + * 1 means next element is in hd and we're still reading into this iterator + * 0 means we're still reading but haven't found a next element + * -1 means we are done reading into the iterator, so we must rely on lookahead + * -2 means we are done but have saved hd for the other iterator to use as its first element + */ + private[this] var status = 0 + private def store(a: A): Unit = { + if (lookahead == null) lookahead = new mutable.Queue[A] + lookahead += a + } + def hasNext = { + if (status < 0) (lookahead ne null) && lookahead.nonEmpty + else if (status > 0) true + else { + if (self.hasNext) { + hd = self.next() + status = if (p(hd)) 1 else -2 + } + else status = -1 + status > 0 + } + } + def next() = { + if (hasNext) { + if (status == 1) { status = 0; hd } + else lookahead.dequeue() + } + else Iterator.empty.next() + } + @tailrec + def finish(): Boolean = status match { + case -2 => status = -1 ; true + case -1 => false + case 1 => store(hd) ; status = 0 ; finish() + case 0 => + status = -1 + while (self.hasNext) { + val a = self.next() + if (p(a)) store(a) + else { + hd = a + return true + } + } + false + } + def trailer: A = hd + } + + val leading = new Leading + + val trailing = new AbstractIterator[A] { + private[this] var myLeading = leading + /* Status flag meanings: + * -1 not yet accessed + * 0 single element waiting in leading + * 1 defer to self + * 2 self.hasNext already + * 3 exhausted + */ + private[this] var status = -1 + def hasNext = status match { + case 3 => false + case 2 => true + case 1 => if (self.hasNext) { status = 2 ; true } else { status = 3 ; false } + case 0 => true + case _ => + if (myLeading.finish()) { status = 0 ; true } else { status = 1 ; myLeading = null ; hasNext } + } + def next() = { + if (hasNext) { + if (status == 0) { + status = 1 + val res = myLeading.trailer + myLeading = null + res + } else { + status = 1 + self.next() + } + } + else Iterator.empty.next() + } + } + + (leading, trailing) + } + + def slice(from: Int, until: Int): Iterator[A] = sliceIterator(from, until max 0) + + /** Creates an optionally bounded slice, unbounded if `until` is negative. */ + protected def sliceIterator(from: Int, until: Int): Iterator[A] = { + val lo = from max 0 + val rest = + if (until < 0) -1 // unbounded + else if (until <= lo) 0 // empty + else until - lo // finite + + if (rest == 0) Iterator.empty + else new Iterator.SliceIterator(this, lo, rest) + } + + def zip[B](that: IterableOnce[B]): Iterator[(A, B)] = new AbstractIterator[(A, B)] { + val thatIterator = that.iterator + override def knownSize = self.knownSize min thatIterator.knownSize + def hasNext = self.hasNext && thatIterator.hasNext + def next() = (self.next(), thatIterator.next()) + } + + def zipAll[A1 >: A, B](that: IterableOnce[B], thisElem: A1, thatElem: B): Iterator[(A1, B)] = new AbstractIterator[(A1, B)] { + val thatIterator = that.iterator + override def knownSize = { + val thisSize = self.knownSize + val thatSize = thatIterator.knownSize + if (thisSize < 0 || thatSize < 0) -1 + else thisSize max thatSize + } + def hasNext = self.hasNext || thatIterator.hasNext + def next(): (A1, B) = { + val next1 = self.hasNext + val next2 = thatIterator.hasNext + if(!(next1 || next2)) throw new NoSuchElementException + (if(next1) self.next() else thisElem, if(next2) thatIterator.next() else thatElem) + } + } + + def zipWithIndex: Iterator[(A, Int)] = new AbstractIterator[(A, Int)] { + var idx = 0 + override def knownSize = self.knownSize + def hasNext = self.hasNext + def next() = { + val ret = (self.next(), idx) + idx += 1 + ret + } + } + + /** Checks whether corresponding elements of the given iterable collection + * compare equal (with respect to `==`) to elements of this $coll. + * + * @param that the collection to compare + * @tparam B the type of the elements of collection `that`. + * @return `true` if both collections contain equal elements in the same order, `false` otherwise. + */ + def sameElements[B >: A](that: IterableOnce[B]): Boolean = { + val those = that.iterator + while (hasNext) { + if (!those.hasNext) return false + if (next() != those.next()) return false + } + !those.hasNext + } + + /** Creates two new iterators that both iterate over the same elements + * as this iterator (in the same order). The duplicate iterators are + * considered equal if they are positioned at the same element. + * + * Given that most methods on iterators will make the original iterator + * unfit for further use, this methods provides a reliable way of calling + * multiple such methods on an iterator. + * + * @return a pair of iterators + * @note The implementation may allocate temporary storage for elements + * iterated by one iterator but not yet by the other. + * @note Reuse: $consumesOneAndProducesTwoIterators + */ + def duplicate: (Iterator[A], Iterator[A]) = { + val gap = new scala.collection.mutable.Queue[A] + var ahead: Iterator[A] = null + class Partner extends AbstractIterator[A] { + override def knownSize: Int = self.synchronized { + val thisSize = self.knownSize + + if (this eq ahead) thisSize + else if (thisSize < 0 || gap.knownSize < 0) -1 + else thisSize + gap.knownSize + } + def hasNext: Boolean = self.synchronized { + (this ne ahead) && !gap.isEmpty || self.hasNext + } + def next(): A = self.synchronized { + if (gap.isEmpty) ahead = this + if (this eq ahead) { + val e = self.next() + gap enqueue e + e + } else gap.dequeue() + } + // to verify partnerhood we use reference equality on gap because + // type testing does not discriminate based on origin. + private def compareGap(queue: scala.collection.mutable.Queue[A]) = gap eq queue + override def hashCode = gap.hashCode() + override def equals(other: Any) = other match { + case x: Partner => x.compareGap(gap) && gap.isEmpty + case _ => super.equals(other) + } + } + (new Partner, new Partner) + } + + /** Returns this iterator with patched values. + * Patching at negative indices is the same as patching starting at 0. + * Patching at indices at or larger than the length of the original iterator appends the patch to the end. + * If more values are replaced than actually exist, the excess is ignored. + * + * @param from The start index from which to patch + * @param patchElems The iterator of patch values + * @param replaced The number of values in the original iterator that are replaced by the patch. + * @note Reuse: $consumesTwoAndProducesOneIterator + */ + def patch[B >: A](from: Int, patchElems: Iterator[B], replaced: Int): Iterator[B] = + new AbstractIterator[B] { + private[this] var origElems = self + // > 0 => that many more elems from `origElems` before switching to `patchElems` + // 0 => need to drop elems from `origElems` and start using `patchElems` + // -1 => have dropped elems from `origElems`, will be using `patchElems` until it's empty + // and then using what's left of `origElems` after the drop + private[this] var state = if (from > 0) from else 0 + + // checks state and handles 0 => -1 + @inline private[this] def switchToPatchIfNeeded(): Unit = + if (state == 0) { + origElems = origElems drop replaced + state = -1 + } + + def hasNext: Boolean = { + switchToPatchIfNeeded() + origElems.hasNext || patchElems.hasNext + } + + def next(): B = { + switchToPatchIfNeeded() + if (state < 0 /* == -1 */) { + if (patchElems.hasNext) patchElems.next() + else origElems.next() + } + else { + if (origElems.hasNext) { + state -= 1 + origElems.next() + } + else { + state = -1 + patchElems.next() + } + } + } + } + + override def tapEach[U](f: A => U): Iterator[A] = new AbstractIterator[A] { + override def knownSize = self.knownSize + override def hasNext = self.hasNext + override def next() = { + val _next = self.next() + f(_next) + _next + } + } + + /** Converts this iterator to a string. + * + * @return `""` + * @note Reuse: $preservesIterator + */ + override def toString = "" + + @deprecated("Iterator.seq always returns the iterator itself", "2.13.0") + def seq: this.type = this +} + +@SerialVersionUID(3L) +object Iterator extends IterableFactory[Iterator] { + + private[this] val _empty: Iterator[Nothing] = new AbstractIterator[Nothing] { + def hasNext = false + def next() = throw new NoSuchElementException("next on empty iterator") + override def knownSize: Int = 0 + override protected def sliceIterator(from: Int, until: Int): AbstractIterator[Nothing] = this + } + + /** Creates a target $coll from an existing source collection + * + * @param source Source collection + * @tparam A the type of the collection’s elements + * @return a new $coll with the elements of `source` + */ + override def from[A](source: IterableOnce[A]): Iterator[A] = source.iterator + + /** The iterator which produces no values. */ + @`inline` final def empty[T]: Iterator[T] = _empty + + def single[A](a: A): Iterator[A] = new AbstractIterator[A] { + private[this] var consumed: Boolean = false + def hasNext = !consumed + def next() = if (consumed) empty.next() else { consumed = true; a } + override protected def sliceIterator(from: Int, until: Int) = + if (consumed || from > 0 || until == 0) empty + else this + } + + override def apply[A](xs: A*): Iterator[A] = xs.iterator + + /** + * @return A builder for $Coll objects. + * @tparam A the type of the ${coll}’s elements + */ + def newBuilder[A]: Builder[A, Iterator[A]] = + new ImmutableBuilder[A, Iterator[A]](empty[A]) { + override def addOne(elem: A): this.type = { elems = elems ++ single(elem); this } + } + + /** Creates iterator that produces the results of some element computation a number of times. + * + * @param len the number of elements returned by the iterator. + * @param elem the element computation + * @return An iterator that produces the results of `n` evaluations of `elem`. + */ + override def fill[A](len: Int)(elem: => A): Iterator[A] = new AbstractIterator[A] { + private[this] var i = 0 + override def knownSize: Int = (len - i) max 0 + def hasNext: Boolean = i < len + def next(): A = + if (hasNext) { i += 1; elem } + else empty.next() + } + + /** Creates an iterator producing the values of a given function over a range of integer values starting from 0. + * + * @param end The number of elements returned by the iterator + * @param f The function computing element values + * @return An iterator that produces the values `f(0), ..., f(n -1)`. + */ + override def tabulate[A](end: Int)(f: Int => A): Iterator[A] = new AbstractIterator[A] { + private[this] var i = 0 + override def knownSize: Int = (end - i) max 0 + def hasNext: Boolean = i < end + def next(): A = + if (hasNext) { val result = f(i); i += 1; result } + else empty.next() + } + + /** Creates an infinite-length iterator which returns successive values from some start value. + + * @param start the start value of the iterator + * @return the iterator producing the infinite sequence of values `start, start + 1, start + 2, ...` + */ + def from(start: Int): Iterator[Int] = from(start, 1) + + /** Creates an infinite-length iterator returning values equally spaced apart. + * + * @param start the start value of the iterator + * @param step the increment between successive values + * @return the iterator producing the infinite sequence of values `start, start + 1 * step, start + 2 * step, ...` + */ + def from(start: Int, step: Int): Iterator[Int] = new AbstractIterator[Int] { + private[this] var i = start + def hasNext: Boolean = true + def next(): Int = { val result = i; i += step; result } + } + + /** Creates nn iterator returning successive values in some integer interval. + * + * @param start the start value of the iterator + * @param end the end value of the iterator (the first value NOT returned) + * @return the iterator producing values `start, start + 1, ..., end - 1` + */ + def range(start: Int, end: Int): Iterator[Int] = range(start, end, 1) + + /** An iterator producing equally spaced values in some integer interval. + * + * @param start the start value of the iterator + * @param end the end value of the iterator (the first value NOT returned) + * @param step the increment value of the iterator (must be positive or negative) + * @return the iterator producing values `start, start + step, ...` up to, but excluding `end` + */ + def range(start: Int, end: Int, step: Int): Iterator[Int] = new AbstractIterator[Int] { + if (step == 0) throw new IllegalArgumentException("zero step") + private[this] var i = start + private[this] var hasOverflowed = false + override def knownSize: Int = { + val size = math.ceil((end.toLong - i.toLong) / step.toDouble) + if (size < 0) 0 + else if (size > Int.MaxValue) -1 + else size.toInt + } + def hasNext: Boolean = { + (step <= 0 || i < end) && (step >= 0 || i > end) && !hasOverflowed + } + def next(): Int = + if (hasNext) { + val result = i + val nextValue = i + step + hasOverflowed = (step > 0) == nextValue < i + i = nextValue + result + } + else empty.next() + } + + /** Creates an infinite iterator that repeatedly applies a given function to the previous result. + * + * @param start the start value of the iterator + * @param f the function that's repeatedly applied + * @return the iterator producing the infinite sequence of values `start, f(start), f(f(start)), ...` + */ + def iterate[T](start: T)(f: T => T): Iterator[T] = new AbstractIterator[T] { + private[this] var first = true + private[this] var acc = start + def hasNext: Boolean = true + def next(): T = { + if (first) first = false + else acc = f(acc) + + acc + } + } + + /** Creates an Iterator that uses a function `f` to produce elements of type `A` + * and update an internal state of type `S`. + * + * @param init State initial value + * @param f Computes the next element (or returns `None` to signal + * the end of the collection) + * @tparam A Type of the elements + * @tparam S Type of the internal state + * @return an Iterator that produces elements using `f` until `f` returns `None` + */ + override def unfold[A, S](init: S)(f: S => Option[(A, S)]): Iterator[A] = new UnfoldIterator(init)(f) + + /** Creates an infinite-length iterator returning the results of evaluating an expression. + * The expression is recomputed for every element. + * + * @param elem the element computation. + * @return the iterator containing an infinite number of results of evaluating `elem`. + */ + def continually[A](elem: => A): Iterator[A] = new AbstractIterator[A] { + def hasNext = true + def next() = elem + } + + /** Creates an iterator to which other iterators can be appended efficiently. + * Nested ConcatIterators are merged to avoid blowing the stack. + */ + private final class ConcatIterator[+A](private var current: Iterator[A @uncheckedVariance]) extends AbstractIterator[A] { + private var tail: ConcatIteratorCell[A @uncheckedVariance] = null + private var last: ConcatIteratorCell[A @uncheckedVariance] = null + private var currentHasNextChecked = false + + def hasNext = + if (currentHasNextChecked) true + else if (current == null) false + else if (current.hasNext) { + currentHasNextChecked = true + true + } + else { + // If we advanced the current iterator to a ConcatIterator, merge it into this one + @tailrec def merge(): Unit = + if (current.isInstanceOf[ConcatIterator[_]]) { + val c = current.asInstanceOf[ConcatIterator[A]] + current = c.current + currentHasNextChecked = c.currentHasNextChecked + if (c.tail != null) { + if (last == null) last = c.last + c.last.tail = tail + tail = c.tail + } + merge() + } + + // Advance current to the next non-empty iterator + // current is set to null when all iterators are exhausted + @tailrec def advance(): Boolean = + if (tail == null) { + current = null + last = null + false + } + else { + current = tail.headIterator + if (last eq tail) last = last.tail + tail = tail.tail + merge() + if (currentHasNextChecked) true + else if (current != null && current.hasNext) { + currentHasNextChecked = true + true + } else advance() + } + + advance() + } + + def next() = + if (hasNext) { + currentHasNextChecked = false + current.next() + } else Iterator.empty.next() + + override def concat[B >: A](that: => IterableOnce[B]): Iterator[B] = { + val c = new ConcatIteratorCell[B](that, null).asInstanceOf[ConcatIteratorCell[A]] + if (tail == null) { + tail = c + last = c + } + else { + last.tail = c + last = c + } + if (current == null) current = Iterator.empty + this + } + } + + private[this] final class ConcatIteratorCell[A](head: => IterableOnce[A], var tail: ConcatIteratorCell[A]) { + def headIterator: Iterator[A] = head.iterator + } + + /** Creates a delegating iterator capped by a limit count. Negative limit means unbounded. + * Lazily skip to start on first evaluation. Avoids daisy-chained iterators due to slicing. + */ + private[scala] final class SliceIterator[A](val underlying: Iterator[A], start: Int, limit: Int) extends AbstractIterator[A] { + private[this] var remaining = limit + private[this] var dropping = start + @inline private def unbounded = remaining < 0 + private def skip(): Unit = + while (dropping > 0) { + if (underlying.hasNext) { + underlying.next() + dropping -= 1 + } else + dropping = 0 + } + override def knownSize: Int = { + val size = underlying.knownSize + if (size < 0) -1 + else { + val dropSize = 0 max (size - dropping) + if (unbounded) dropSize + else remaining min dropSize + } + } + def hasNext = { skip(); remaining != 0 && underlying.hasNext } + def next() = { + skip() + if (remaining > 0) { + remaining -= 1 + underlying.next() + } + else if (unbounded) underlying.next() + else empty.next() + } + override protected def sliceIterator(from: Int, until: Int): Iterator[A] = { + val lo = from max 0 + def adjustedBound = + if (unbounded) -1 + else 0 max (remaining - lo) + val rest = + if (until < 0) adjustedBound // respect current bound, if any + else if (until <= lo) 0 // empty + else if (unbounded) until - lo // now finite + else adjustedBound min (until - lo) // keep lesser bound + val sum = dropping + lo + if (rest == 0) empty + else if (sum < 0) { + dropping = Int.MaxValue + remaining = 0 + this.concat(new SliceIterator(underlying, start = sum - Int.MaxValue, limit = rest)) + } + else { + dropping = sum + remaining = rest + this + } + } + } + + /** Creates an iterator that uses a function `f` to produce elements of + * type `A` and update an internal state of type `S`. + */ + private final class UnfoldIterator[A, S](init: S)(f: S => Option[(A, S)]) extends AbstractIterator[A] { + private[this] var state: S = init + private[this] var nextResult: Option[(A, S)] = null + + override def hasNext: Boolean = { + if (nextResult eq null) { + nextResult = { + val res = f(state) + if (res eq null) throw new NullPointerException("null during unfold") + res + } + state = null.asInstanceOf[S] // allow GC + } + nextResult.isDefined + } + + override def next(): A = { + if (hasNext) { + val (value, newState) = nextResult.get + state = newState + nextResult = null + value + } else Iterator.empty.next() + } + } +} + +/** Explicit instantiation of the `Iterator` trait to reduce class file size in subclasses. */ +abstract class AbstractIterator[+A] extends Iterator[A] diff --git a/library/src/scala/collection/JavaConverters.scala b/library/src/scala/collection/JavaConverters.scala new file mode 100644 index 000000000000..b49e472c04b2 --- /dev/null +++ b/library/src/scala/collection/JavaConverters.scala @@ -0,0 +1,336 @@ +/* + * 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.collection + +import scala.language.`2.13` +import java.util.{concurrent => juc} +import java.{lang => jl, util => ju} + +import scala.collection.convert._ +import scala.language.implicitConversions + +/** A variety of decorators that enable converting between + * Scala and Java collections using extension methods, `asScala` and `asJava`. + * + * The extension methods return adapters for the corresponding API. + * + * The following conversions are supported via `asScala` and `asJava`: + *{{{ + * scala.collection.Iterable <=> java.lang.Iterable + * scala.collection.Iterator <=> java.util.Iterator + * scala.collection.mutable.Buffer <=> java.util.List + * scala.collection.mutable.Set <=> java.util.Set + * scala.collection.mutable.Map <=> java.util.Map + * scala.collection.concurrent.Map <=> java.util.concurrent.ConcurrentMap + *}}} + * The following conversions are supported via `asScala` and through + * specially-named extension methods to convert to Java collections, as shown: + *{{{ + * scala.collection.Iterable <=> java.util.Collection (via asJavaCollection) + * scala.collection.Iterator <=> java.util.Enumeration (via asJavaEnumeration) + * scala.collection.mutable.Map <=> java.util.Dictionary (via asJavaDictionary) + *}}} + * In addition, the following one-way conversions are provided via `asJava`: + *{{{ + * scala.collection.Seq => java.util.List + * scala.collection.mutable.Seq => java.util.List + * scala.collection.Set => java.util.Set + * scala.collection.Map => java.util.Map + *}}} + * The following one way conversion is provided via `asScala`: + *{{{ + * java.util.Properties => scala.collection.mutable.Map + *}}} + * In all cases, converting from a source type to a target type and back + * again will return the original source object. For example: + * {{{ + * import scala.collection.JavaConverters._ + * + * val source = new scala.collection.mutable.ListBuffer[Int] + * val target: java.util.List[Int] = source.asJava + * val other: scala.collection.mutable.Buffer[Int] = target.asScala + * assert(source eq other) + * }}} + * Alternatively, the conversion methods have descriptive names and can be invoked explicitly. + * {{{ + * scala> val vs = java.util.Arrays.asList("hi", "bye") + * vs: java.util.List[String] = [hi, bye] + * + * scala> val ss = asScalaIterator(vs.iterator) + * ss: Iterator[String] = + * + * scala> .toList + * res0: List[String] = List(hi, bye) + * + * scala> val ss = asScalaBuffer(vs) + * ss: scala.collection.mutable.Buffer[String] = Buffer(hi, bye) + * }}} + */ +@deprecated("Use `scala.jdk.CollectionConverters` instead", "2.13.0") +object JavaConverters extends AsJavaConverters with AsScalaConverters { + @deprecated("Use `asJava` instead", "2.13.0") + def asJavaIterator[A](i: Iterator[A]): ju.Iterator[A] = asJava(i) + + @deprecated("Use `asJava` instead", "2.13.0") + def asJavaIterable[A](i: Iterable[A]): jl.Iterable[A] = asJava(i) + + @deprecated("Use `asJava` instead", "2.13.0") + def bufferAsJavaList[A](b: mutable.Buffer[A]): ju.List[A] = asJava(b) + + @deprecated("Use `asJava` instead", "2.13.0") + def mutableSeqAsJavaList[A](s: mutable.Seq[A]): ju.List[A] = asJava(s) + + @deprecated("Use `asJava` instead", "2.13.0") + def seqAsJavaList[A](s: Seq[A]): ju.List[A] = asJava(s) + + @deprecated("Use `asJava` instead", "2.13.0") + def mutableSetAsJavaSet[A](s: mutable.Set[A]): ju.Set[A] = asJava(s) + + @deprecated("Use `asJava` instead", "2.13.0") + def setAsJavaSet[A](s: Set[A]): ju.Set[A] = asJava(s) + + @deprecated("Use `asJava` instead", "2.13.0") + def mutableMapAsJavaMap[K, V](m: mutable.Map[K, V]): ju.Map[K, V] = asJava(m) + + @deprecated("Use `asJava` instead", "2.13.0") + def mapAsJavaMap[K, V](m: Map[K, V]): ju.Map[K, V] = asJava(m) + + @deprecated("Use `asJava` instead", "2.13.0") + def mapAsJavaConcurrentMap[K, V](m: concurrent.Map[K, V]): juc.ConcurrentMap[K, V] = asJava(m) + + + @deprecated("Use `asScala` instead", "2.13.0") + def asScalaIterator[A](i: ju.Iterator[A]): Iterator[A] = asScala(i) + + @deprecated("Use `asScala` instead", "2.13.0") + def enumerationAsScalaIterator[A](i: ju.Enumeration[A]): Iterator[A] = asScala(i) + + @deprecated("Use `asScala` instead", "2.13.0") + def iterableAsScalaIterable[A](i: jl.Iterable[A]): Iterable[A] = asScala(i) + + @deprecated("Use `asScala` instead", "2.13.0") + def collectionAsScalaIterable[A](i: ju.Collection[A]): Iterable[A] = asScala(i) + + @deprecated("Use `asScala` instead", "2.13.0") + def asScalaBuffer[A](l: ju.List[A]): mutable.Buffer[A] = asScala(l) + + @deprecated("Use `asScala` instead", "2.13.0") + def asScalaSet[A](s: ju.Set[A]): mutable.Set[A] = asScala(s) + + @deprecated("Use `asScala` instead", "2.13.0") + def mapAsScalaMap[A, B](m: ju.Map[A, B]): mutable.Map[A, B] = asScala(m) + + @deprecated("Use `asScala` instead", "2.13.0") + def mapAsScalaConcurrentMap[A, B](m: juc.ConcurrentMap[A, B]): concurrent.Map[A, B] = asScala(m) + + @deprecated("Use `asScala` instead", "2.13.0") + def dictionaryAsScalaMap[A, B](p: ju.Dictionary[A, B]): mutable.Map[A, B] = asScala(p) + + @deprecated("Use `asScala` instead", "2.13.0") + def propertiesAsScalaMap(p: ju.Properties): mutable.Map[String, String] = asScala(p) + + // Deprecated implicit conversions for code that directly imports them + + /** + * Adds an `asJava` method that implicitly converts a Scala `Iterator` to a Java `Iterator`. + * @see [[asJavaIterator]] + */ + implicit def asJavaIteratorConverter[A](i : Iterator[A]): AsJava[ju.Iterator[A]] = + new AsJava(asJavaIterator(i)) + + /** + * Adds an `asJavaEnumeration` method that implicitly converts a Scala `Iterator` to a Java `Enumeration`. + * @see [[asJavaEnumeration]] + */ + implicit def asJavaEnumerationConverter[A](i : Iterator[A]): AsJavaEnumeration[A] = + new AsJavaEnumeration(i) + + /** + * Adds an `asJava` method that implicitly converts a Scala `Iterable` to a Java `Iterable`. + * @see [[asJavaIterable]] + */ + implicit def asJavaIterableConverter[A](i : Iterable[A]): AsJava[jl.Iterable[A]] = + new AsJava(asJavaIterable(i)) + + /** + * Adds an `asJavaCollection` method that implicitly converts a Scala `Iterable` to an immutable Java `Collection`. + * @see [[asJavaCollection]] + */ + implicit def asJavaCollectionConverter[A](i : Iterable[A]): AsJavaCollection[A] = + new AsJavaCollection(i) + + /** + * Adds an `asJava` method that implicitly converts a Scala mutable `Buffer` to a Java `List`. + * @see [[bufferAsJavaList]] + */ + implicit def bufferAsJavaListConverter[A](b : mutable.Buffer[A]): AsJava[ju.List[A]] = + new AsJava(bufferAsJavaList(b)) + + /** + * Adds an `asJava` method that implicitly converts a Scala mutable `Seq` to a Java `List`. + * @see [[mutableSeqAsJavaList]] + */ + implicit def mutableSeqAsJavaListConverter[A](b : mutable.Seq[A]): AsJava[ju.List[A]] = + new AsJava(mutableSeqAsJavaList(b)) + + /** + * Adds an `asJava` method that implicitly converts a Scala `Seq` to a Java `List`. + * @see [[seqAsJavaList]] + */ + implicit def seqAsJavaListConverter[A](b : Seq[A]): AsJava[ju.List[A]] = + new AsJava(seqAsJavaList(b)) + + /** + * Adds an `asJava` method that implicitly converts a Scala mutable `Set` to a Java `Set`. + * @see [[mutableSetAsJavaSet]] + */ + implicit def mutableSetAsJavaSetConverter[A](s : mutable.Set[A]): AsJava[ju.Set[A]] = + new AsJava(mutableSetAsJavaSet(s)) + + /** + * Adds an `asJava` method that implicitly converts a Scala `Set` to a Java `Set`. + * @see [[setAsJavaSet]] + */ + implicit def setAsJavaSetConverter[A](s : Set[A]): AsJava[ju.Set[A]] = + new AsJava(setAsJavaSet(s)) + + /** + * Adds an `asJava` method that implicitly converts a Scala mutable `Map` to a Java `Map`. + * @see [[mutableMapAsJavaMap]] + */ + implicit def mutableMapAsJavaMapConverter[K, V](m : mutable.Map[K, V]): AsJava[ju.Map[K, V]] = + new AsJava(mutableMapAsJavaMap(m)) + + /** + * Adds an `asJavaDictionary` method that implicitly converts a Scala mutable `Map` to a Java `Dictionary`. + * @see [[asJavaDictionary]] + */ + implicit def asJavaDictionaryConverter[K, V](m : mutable.Map[K, V]): AsJavaDictionary[K, V] = + new AsJavaDictionary(m) + + /** + * Adds an `asJava` method that implicitly converts a Scala `Map` to a Java `Map`. + * @see [[mapAsJavaMap]] + */ + implicit def mapAsJavaMapConverter[K, V](m : Map[K, V]): AsJava[ju.Map[K, V]] = + new AsJava(mapAsJavaMap(m)) + + /** + * Adds an `asJava` method that implicitly converts a Scala mutable `concurrent.Map` to a Java `ConcurrentMap`. + * @see [[mapAsJavaConcurrentMap]]. + */ + implicit def mapAsJavaConcurrentMapConverter[K, V](m: concurrent.Map[K, V]): AsJava[juc.ConcurrentMap[K, V]] = + new AsJava(mapAsJavaConcurrentMap(m)) + + + /** + * Adds an `asScala` method that implicitly converts a Java `Iterator` to a Scala `Iterator`. + * @see [[asScalaIterator]] + */ + implicit def asScalaIteratorConverter[A](i : ju.Iterator[A]): AsScala[Iterator[A]] = + new AsScala(asScalaIterator(i)) + + /** + * Adds an `asScala` method that implicitly converts a Java `Enumeration` to a Scala `Iterator`. + * @see [[enumerationAsScalaIterator]] + */ + implicit def enumerationAsScalaIteratorConverter[A](i : ju.Enumeration[A]): AsScala[Iterator[A]] = + new AsScala(enumerationAsScalaIterator(i)) + + /** + * Adds an `asScala` method that implicitly converts a Java `Iterable` to a Scala `Iterable`. + * @see [[iterableAsScalaIterable]] + */ + implicit def iterableAsScalaIterableConverter[A](i : jl.Iterable[A]): AsScala[Iterable[A]] = + new AsScala(iterableAsScalaIterable(i)) + + /** + * Adds an `asScala` method that implicitly converts a Java `Collection` to an Scala `Iterable`. + * @see [[collectionAsScalaIterable]] + */ + implicit def collectionAsScalaIterableConverter[A](i : ju.Collection[A]): AsScala[Iterable[A]] = + new AsScala(collectionAsScalaIterable(i)) + + /** + * Adds an `asScala` method that implicitly converts a Java `List` to a Scala mutable `Buffer`. + * @see [[asScalaBuffer]] + */ + implicit def asScalaBufferConverter[A](l : ju.List[A]): AsScala[mutable.Buffer[A]] = + new AsScala(asScalaBuffer(l)) + + /** + * Adds an `asScala` method that implicitly converts a Java `Set` to a Scala mutable `Set`. + * @see [[asScalaSet]] + */ + implicit def asScalaSetConverter[A](s : ju.Set[A]): AsScala[mutable.Set[A]] = + new AsScala(asScalaSet(s)) + + /** + * Adds an `asScala` method that implicitly converts a Java `Map` to a Scala mutable `Map`. + * @see [[mapAsScalaMap]] + */ + implicit def mapAsScalaMapConverter[K, V](m : ju.Map[K, V]): AsScala[mutable.Map[K, V]] = + new AsScala(mapAsScalaMap(m)) + + /** + * Adds an `asScala` method that implicitly converts a Java `ConcurrentMap` to a Scala mutable `concurrent.Map`. + * @see [[mapAsScalaConcurrentMap]] + */ + implicit def mapAsScalaConcurrentMapConverter[K, V](m: juc.ConcurrentMap[K, V]): AsScala[concurrent.Map[K, V]] = + new AsScala(mapAsScalaConcurrentMap(m)) + + /** + * Adds an `asScala` method that implicitly converts a Java `Dictionary` to a Scala mutable `Map`. + * @see [[dictionaryAsScalaMap]] + */ + implicit def dictionaryAsScalaMapConverter[K, V](p: ju.Dictionary[K, V]): AsScala[mutable.Map[K, V]] = + new AsScala(dictionaryAsScalaMap(p)) + + /** + * Adds an `asScala` method that implicitly converts a Java `Properties` to a Scala mutable `Map[String, String]`. + * @see [[propertiesAsScalaMap]] + */ + implicit def propertiesAsScalaMapConverter(p: ju.Properties): AsScala[mutable.Map[String, String]] = + new AsScala(propertiesAsScalaMap(p)) + + + /** Generic class containing the `asJava` converter method */ + class AsJava[A](op: => A) { + /** Converts a Scala collection to the corresponding Java collection */ + def asJava: A = op + } + + /** Generic class containing the `asScala` converter method */ + class AsScala[A](op: => A) { + /** Converts a Java collection to the corresponding Scala collection */ + def asScala: A = op + } + + /** Generic class containing the `asJavaCollection` converter method */ + class AsJavaCollection[A](i: Iterable[A]) { + /** Converts a Scala `Iterable` to a Java `Collection` */ + def asJavaCollection: ju.Collection[A] = JavaConverters.asJavaCollection(i) + } + + /** Generic class containing the `asJavaEnumeration` converter method */ + class AsJavaEnumeration[A](i: Iterator[A]) { + /** Converts a Scala `Iterator` to a Java `Enumeration` */ + def asJavaEnumeration: ju.Enumeration[A] = JavaConverters.asJavaEnumeration(i) + } + + /** Generic class containing the `asJavaDictionary` converter method */ + class AsJavaDictionary[K, V](m : mutable.Map[K, V]) { + /** Converts a Scala `Map` to a Java `Dictionary` */ + def asJavaDictionary: ju.Dictionary[K, V] = JavaConverters.asJavaDictionary(m) + } +} diff --git a/library/src/scala/collection/LazyZipOps.scala b/library/src/scala/collection/LazyZipOps.scala new file mode 100644 index 000000000000..dba5b6432def --- /dev/null +++ b/library/src/scala/collection/LazyZipOps.scala @@ -0,0 +1,423 @@ +/* + * 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.collection + +import scala.language.`2.13` +import scala.language.implicitConversions + +/** Decorator representing lazily zipped pairs. + * + * @define coll pair + * @define willNotTerminateInf + * + * Note: will not terminate for infinite-sized collections. + */ +final class LazyZip2[+El1, +El2, C1] private[collection](src: C1, coll1: Iterable[El1], coll2: Iterable[El2]) { + + /** Zips `that` iterable collection with an existing `LazyZip2`. The elements in each collection are + * not consumed until a strict operation is invoked on the returned `LazyZip3` decorator. + * + * @param that the iterable providing the third element of each eventual triple + * @tparam B the type of the third element in each eventual triple + * @return a decorator `LazyZip3` that allows strict operations to be performed on the lazily evaluated tuples or + * chained calls to `lazyZip`. Implicit conversion to `Iterable[(El1, El2, B)]` is also supported. + */ + def lazyZip[B](that: Iterable[B]): LazyZip3[El1, El2, B, C1] = new LazyZip3(src, coll1, coll2, that) + + def map[B, C](f: (El1, El2) => B)(implicit bf: BuildFrom[C1, B, C]): C = { + bf.fromSpecific(src)(new AbstractView[B] { + def iterator: AbstractIterator[B] = new AbstractIterator[B] { + private[this] val elems1 = coll1.iterator + private[this] val elems2 = coll2.iterator + def hasNext = elems1.hasNext && elems2.hasNext + def next() = f(elems1.next(), elems2.next()) + } + override def knownSize: Int = zipKnownSize + override def isEmpty: Boolean = coll1.isEmpty || coll2.isEmpty + }) + } + + def flatMap[B, C](f: (El1, El2) => Iterable[B])(implicit bf: BuildFrom[C1, B, C]): C = { + bf.fromSpecific(src)(new AbstractView[B] { + def iterator: AbstractIterator[B] = new AbstractIterator[B] { + private[this] val elems1 = coll1.iterator + private[this] val elems2 = coll2.iterator + private[this] var _current: Iterator[B] = Iterator.empty + private def current = { + while (!_current.hasNext && elems1.hasNext && elems2.hasNext) + _current = f(elems1.next(), elems2.next()).iterator + _current + } + def hasNext = current.hasNext + def next() = current.next() + } + override def knownSize: Int = if (coll1.knownSize == 0 || coll2.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = coll1.isEmpty || coll2.isEmpty + }) + } + + def filter[C](p: (El1, El2) => Boolean)(implicit bf: BuildFrom[C1, (El1, El2), C]): C = { + bf.fromSpecific(src)(new AbstractView[(El1, El2)] { + def iterator: AbstractIterator[(El1, El2)] = new AbstractIterator[(El1, El2)] { + private[this] val elems1 = coll1.iterator + private[this] val elems2 = coll2.iterator + private[this] var _current: (El1, El2) = _ + private def current = { + while ((_current eq null) && elems1.hasNext && elems2.hasNext) { + val e1 = elems1.next() + val e2 = elems2.next() + if (p(e1, e2)) _current = (e1, e2) + } + _current + } + def hasNext = current ne null + def next() = { + val c = current + if (c ne null) { + _current = null + c + } else Iterator.empty.next() + } + } + override def knownSize: Int = if (coll1.knownSize == 0 || coll2.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = iterator.hasNext + }) + } + + def exists(p: (El1, El2) => Boolean): Boolean = { + val elems1 = coll1.iterator + val elems2 = coll2.iterator + var res = false + + while (!res && elems1.hasNext && elems2.hasNext) res = p(elems1.next(), elems2.next()) + + res + } + + def forall(p: (El1, El2) => Boolean): Boolean = !exists((el1, el2) => !p(el1, el2)) + + def foreach[U](f: (El1, El2) => U): Unit = { + val elems1 = coll1.iterator + val elems2 = coll2.iterator + + while (elems1.hasNext && elems2.hasNext) f(elems1.next(), elems2.next()) + } + + private def toIterable: View[(El1, El2)] = new AbstractView[(El1, El2)] { + def iterator: AbstractIterator[(El1, El2)] = new AbstractIterator[(El1, El2)] { + private[this] val elems1 = coll1.iterator + private[this] val elems2 = coll2.iterator + def hasNext = elems1.hasNext && elems2.hasNext + def next() = (elems1.next(), elems2.next()) + } + override def knownSize: Int = zipKnownSize + override def isEmpty: Boolean = coll1.isEmpty || coll2.isEmpty + } + + private def zipKnownSize: Int = { + val s1 = coll1.knownSize + if (s1 == 0) 0 else { + val s2 = coll2.knownSize + if (s2 == 0) 0 else s1 min s2 + } + } + + override def toString = s"$coll1.lazyZip($coll2)" +} + +object LazyZip2 { + implicit def lazyZip2ToIterable[El1, El2](zipped2: LazyZip2[El1, El2, _]): View[(El1, El2)] = zipped2.toIterable +} + + +/** Decorator representing lazily zipped triples. + * + * @define coll triple + * @define willNotTerminateInf + * + * Note: will not terminate for infinite-sized collections. + */ +final class LazyZip3[+El1, +El2, +El3, C1] private[collection](src: C1, + coll1: Iterable[El1], + coll2: Iterable[El2], + coll3: Iterable[El3]) { + + /** Zips `that` iterable collection with an existing `LazyZip3`. The elements in each collection are + * not consumed until a strict operation is invoked on the returned `LazyZip4` decorator. + * + * @param that the iterable providing the fourth element of each eventual 4-tuple + * @tparam B the type of the fourth element in each eventual 4-tuple + * @return a decorator `LazyZip4` that allows strict operations to be performed on the lazily evaluated tuples. + * Implicit conversion to `Iterable[(El1, El2, El3, B)]` is also supported. + */ + def lazyZip[B](that: Iterable[B]): LazyZip4[El1, El2, El3, B, C1] = new LazyZip4(src, coll1, coll2, coll3, that) + + def map[B, C](f: (El1, El2, El3) => B)(implicit bf: BuildFrom[C1, B, C]): C = { + bf.fromSpecific(src)(new AbstractView[B] { + def iterator: AbstractIterator[B] = new AbstractIterator[B] { + private[this] val elems1 = coll1.iterator + private[this] val elems2 = coll2.iterator + private[this] val elems3 = coll3.iterator + def hasNext = elems1.hasNext && elems2.hasNext && elems3.hasNext + def next() = f(elems1.next(), elems2.next(), elems3.next()) + } + override def knownSize: Int = zipKnownSize + override def isEmpty: Boolean = coll1.isEmpty || coll2.isEmpty || coll3.isEmpty + }) + } + + def flatMap[B, C](f: (El1, El2, El3) => Iterable[B])(implicit bf: BuildFrom[C1, B, C]): C = { + bf.fromSpecific(src)(new AbstractView[B] { + def iterator: AbstractIterator[B] = new AbstractIterator[B] { + private[this] val elems1 = coll1.iterator + private[this] val elems2 = coll2.iterator + private[this] val elems3 = coll3.iterator + private[this] var _current: Iterator[B] = Iterator.empty + private def current = { + while (!_current.hasNext && elems1.hasNext && elems2.hasNext && elems3.hasNext) + _current = f(elems1.next(), elems2.next(), elems3.next()).iterator + _current + } + def hasNext = current.hasNext + def next() = current.next() + } + override def knownSize: Int = if (coll1.knownSize == 0 || coll2.knownSize == 0 || coll3.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = iterator.isEmpty + }) + } + + def filter[C](p: (El1, El2, El3) => Boolean)(implicit bf: BuildFrom[C1, (El1, El2, El3), C]): C = { + bf.fromSpecific(src)(new AbstractView[(El1, El2, El3)] { + def iterator: AbstractIterator[(El1, El2, El3)] = new AbstractIterator[(El1, El2, El3)] { + private[this] val elems1 = coll1.iterator + private[this] val elems2 = coll2.iterator + private[this] val elems3 = coll3.iterator + private[this] var _current: (El1, El2, El3) = _ + private def current = { + while ((_current eq null) && elems1.hasNext && elems2.hasNext && elems3.hasNext) { + val e1 = elems1.next() + val e2 = elems2.next() + val e3 = elems3.next() + if (p(e1, e2, e3)) _current = (e1, e2, e3) + } + _current + } + def hasNext = current ne null + def next() = { + val c = current + if (c ne null) { + _current = null + c + } else Iterator.empty.next() + } + } + override def knownSize: Int = if (coll1.knownSize == 0 || coll2.knownSize == 0 || coll3.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = iterator.isEmpty + }) + } + + def exists(p: (El1, El2, El3) => Boolean): Boolean = { + val elems1 = coll1.iterator + val elems2 = coll2.iterator + val elems3 = coll3.iterator + var res = false + + while (!res && elems1.hasNext && elems2.hasNext && elems3.hasNext) + res = p(elems1.next(), elems2.next(), elems3.next()) + + res + } + + def forall(p: (El1, El2, El3) => Boolean): Boolean = !exists((el1, el2, el3) => !p(el1, el2, el3)) + + def foreach[U](f: (El1, El2, El3) => U): Unit = { + val elems1 = coll1.iterator + val elems2 = coll2.iterator + val elems3 = coll3.iterator + + while (elems1.hasNext && elems2.hasNext && elems3.hasNext) + f(elems1.next(), elems2.next(), elems3.next()) + } + + private def toIterable: View[(El1, El2, El3)] = new AbstractView[(El1, El2, El3)] { + def iterator: AbstractIterator[(El1, El2, El3)] = new AbstractIterator[(El1, El2, El3)] { + private[this] val elems1 = coll1.iterator + private[this] val elems2 = coll2.iterator + private[this] val elems3 = coll3.iterator + def hasNext = elems1.hasNext && elems2.hasNext && elems3.hasNext + def next() = (elems1.next(), elems2.next(), elems3.next()) + } + override def knownSize: Int = zipKnownSize + override def isEmpty: Boolean = coll1.isEmpty || coll2.isEmpty || coll3.isEmpty + } + + private def zipKnownSize: Int = { + val s1 = coll1.knownSize + if (s1 == 0) 0 else { + val s2 = coll2.knownSize + if (s2 == 0) 0 else { + val s3 = coll3.knownSize + if (s3 == 0) 0 else s1 min s2 min s3 + } + } + } + + override def toString = s"$coll1.lazyZip($coll2).lazyZip($coll3)" +} + +object LazyZip3 { + implicit def lazyZip3ToIterable[El1, El2, El3](zipped3: LazyZip3[El1, El2, El3, _]): View[(El1, El2, El3)] = zipped3.toIterable +} + + + +/** Decorator representing lazily zipped 4-tuples. + * + * @define coll tuple + * @define willNotTerminateInf + * + * Note: will not terminate for infinite-sized collections. + */ +final class LazyZip4[+El1, +El2, +El3, +El4, C1] private[collection](src: C1, + coll1: Iterable[El1], + coll2: Iterable[El2], + coll3: Iterable[El3], + coll4: Iterable[El4]) { + + def map[B, C](f: (El1, El2, El3, El4) => B)(implicit bf: BuildFrom[C1, B, C]): C = { + bf.fromSpecific(src)(new AbstractView[B] { + def iterator: AbstractIterator[B] = new AbstractIterator[B] { + private[this] val elems1 = coll1.iterator + private[this] val elems2 = coll2.iterator + private[this] val elems3 = coll3.iterator + private[this] val elems4 = coll4.iterator + def hasNext = elems1.hasNext && elems2.hasNext && elems3.hasNext && elems4.hasNext + def next() = f(elems1.next(), elems2.next(), elems3.next(), elems4.next()) + } + override def knownSize: Int = zipKnownSize + override def isEmpty: Boolean = coll1.isEmpty || coll2.isEmpty || coll3.isEmpty || coll4.isEmpty + }) + } + + def flatMap[B, C](f: (El1, El2, El3, El4) => Iterable[B])(implicit bf: BuildFrom[C1, B, C]): C = { + bf.fromSpecific(src)(new AbstractView[B] { + def iterator: AbstractIterator[B] = new AbstractIterator[B] { + private[this] val elems1 = coll1.iterator + private[this] val elems2 = coll2.iterator + private[this] val elems3 = coll3.iterator + private[this] val elems4 = coll4.iterator + private[this] var _current: Iterator[B] = Iterator.empty + private def current = { + while (!_current.hasNext && elems1.hasNext && elems2.hasNext && elems3.hasNext && elems4.hasNext) + _current = f(elems1.next(), elems2.next(), elems3.next(), elems4.next()).iterator + _current + } + def hasNext = current.hasNext + def next() = current.next() + } + override def knownSize: Int = if (coll1.knownSize == 0 || coll2.knownSize == 0 || coll3.knownSize == 0 || coll4.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = iterator.isEmpty + }) + } + + def filter[C](p: (El1, El2, El3, El4) => Boolean)(implicit bf: BuildFrom[C1, (El1, El2, El3, El4), C]): C = { + bf.fromSpecific(src)(new AbstractView[(El1, El2, El3, El4)] { + def iterator: AbstractIterator[(El1, El2, El3, El4)] = new AbstractIterator[(El1, El2, El3, El4)] { + private[this] val elems1 = coll1.iterator + private[this] val elems2 = coll2.iterator + private[this] val elems3 = coll3.iterator + private[this] val elems4 = coll4.iterator + private[this] var _current: (El1, El2, El3, El4) = _ + private def current = { + while ((_current eq null) && elems1.hasNext && elems2.hasNext && elems3.hasNext && elems4.hasNext) { + val e1 = elems1.next() + val e2 = elems2.next() + val e3 = elems3.next() + val e4 = elems4.next() + if (p(e1, e2, e3, e4)) _current = (e1, e2, e3, e4) + } + _current + } + def hasNext = current ne null + def next() = { + val c = current + if (c ne null) { + _current = null + c + } else Iterator.empty.next() + } + } + override def knownSize: Int = if (coll1.knownSize == 0 || coll2.knownSize == 0 || coll3.knownSize == 0 || coll4.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = iterator.isEmpty + }) + } + + def exists(p: (El1, El2, El3, El4) => Boolean): Boolean = { + val elems1 = coll1.iterator + val elems2 = coll2.iterator + val elems3 = coll3.iterator + val elems4 = coll4.iterator + var res = false + + while (!res && elems1.hasNext && elems2.hasNext && elems3.hasNext && elems4.hasNext) + res = p(elems1.next(), elems2.next(), elems3.next(), elems4.next()) + + res + } + + def forall(p: (El1, El2, El3, El4) => Boolean): Boolean = !exists((el1, el2, el3, el4) => !p(el1, el2, el3, el4)) + + def foreach[U](f: (El1, El2, El3, El4) => U): Unit = { + val elems1 = coll1.iterator + val elems2 = coll2.iterator + val elems3 = coll3.iterator + val elems4 = coll4.iterator + + while (elems1.hasNext && elems2.hasNext && elems3.hasNext && elems4.hasNext) + f(elems1.next(), elems2.next(), elems3.next(), elems4.next()) + } + + private def toIterable: View[(El1, El2, El3, El4)] = new AbstractView[(El1, El2, El3, El4)] { + def iterator: AbstractIterator[(El1, El2, El3, El4)] = new AbstractIterator[(El1, El2, El3, El4)] { + private[this] val elems1 = coll1.iterator + private[this] val elems2 = coll2.iterator + private[this] val elems3 = coll3.iterator + private[this] val elems4 = coll4.iterator + def hasNext = elems1.hasNext && elems2.hasNext && elems3.hasNext && elems4.hasNext + def next() = (elems1.next(), elems2.next(), elems3.next(), elems4.next()) + } + override def knownSize: Int = zipKnownSize + override def isEmpty: Boolean = coll1.isEmpty || coll2.isEmpty || coll3.isEmpty || coll4.isEmpty + } + + private def zipKnownSize: Int = { + val s1 = coll1.knownSize + if (s1 == 0) 0 else { + val s2 = coll2.knownSize + if (s2 == 0) 0 else { + val s3 = coll3.knownSize + if (s3 == 0) 0 else { + val s4 = coll4.knownSize + if (s4 == 0) 0 else s1 min s2 min s3 min s4 + } + } + } + } + + override def toString = s"$coll1.lazyZip($coll2).lazyZip($coll3).lazyZip($coll4)" +} + +object LazyZip4 { + implicit def lazyZip4ToIterable[El1, El2, El3, El4](zipped4: LazyZip4[El1, El2, El3, El4, _]): View[(El1, El2, El3, El4)] = + zipped4.toIterable +} diff --git a/library/src/scala/collection/LinearSeq.scala b/library/src/scala/collection/LinearSeq.scala new file mode 100644 index 000000000000..f6ae57168fcd --- /dev/null +++ b/library/src/scala/collection/LinearSeq.scala @@ -0,0 +1,311 @@ +/* + * 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 collection + +import scala.language.`2.13` +import scala.annotation.{nowarn, tailrec} + +/** Base trait for linearly accessed sequences that have efficient `head` and + * `tail` operations. + * Known subclasses: List, LazyList + */ +trait LinearSeq[+A] extends Seq[A] + with LinearSeqOps[A, LinearSeq, LinearSeq[A]] + with IterableFactoryDefaults[A, LinearSeq] { + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "LinearSeq" + + override def iterableFactory: SeqFactory[LinearSeq] = LinearSeq +} + +@SerialVersionUID(3L) +object LinearSeq extends SeqFactory.Delegate[LinearSeq](immutable.LinearSeq) + +/** Base trait for linear Seq operations */ +transparent trait LinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] with LinearSeqOps[A, CC, C]] extends Any with SeqOps[A, CC, C] { + + /** @inheritdoc + * + * Note: *Must* be overridden in subclasses. The default implementation that is inherited from [[SeqOps]] + * uses `lengthCompare`, which is defined here to use `isEmpty`. + */ + override def isEmpty: Boolean + + /** @inheritdoc + * + * Note: *Must* be overridden in subclasses. The default implementation is inherited from [[IterableOps]]. + */ + def head: A + + /** @inheritdoc + * + * Note: *Must* be overridden in subclasses. The default implementation is inherited from [[IterableOps]]. + */ + def tail: C + + override def headOption: Option[A] = + if (isEmpty) None else Some(head) + + def iterator: Iterator[A] = + if (knownSize == 0) Iterator.empty + else new LinearSeqIterator[A](this) + + def length: Int = { + var these = coll + var len = 0 + while (these.nonEmpty) { + len += 1 + these = these.tail + } + len + } + + override def last: A = { + if (isEmpty) throw new NoSuchElementException("LinearSeq.last") + else { + var these = coll + var scout = tail + while (scout.nonEmpty) { + these = scout + scout = scout.tail + } + these.head + } + } + + override def lengthCompare(len: Int): Int = { + @tailrec def loop(i: Int, xs: LinearSeq[A]): Int = { + if (i == len) + if (xs.isEmpty) 0 else 1 + else if (xs.isEmpty) + -1 + else + loop(i + 1, xs.tail) + } + if (len < 0) 1 + else loop(0, coll) + } + + override def lengthCompare(that: Iterable[_]): Int = { + val thatKnownSize = that.knownSize + + if (thatKnownSize >= 0) this lengthCompare thatKnownSize + else that match { + case that: LinearSeq[_] => + var thisSeq = this + var thatSeq = that + while (thisSeq.nonEmpty && thatSeq.nonEmpty) { + thisSeq = thisSeq.tail + thatSeq = thatSeq.tail + } + java.lang.Boolean.compare(thisSeq.nonEmpty, thatSeq.nonEmpty) + case _ => + var thisSeq = this + val thatIt = that.iterator + while (thisSeq.nonEmpty && thatIt.hasNext) { + thisSeq = thisSeq.tail + thatIt.next() + } + java.lang.Boolean.compare(thisSeq.nonEmpty, thatIt.hasNext) + } + } + + override def isDefinedAt(x: Int): Boolean = x >= 0 && lengthCompare(x) > 0 + + // `apply` is defined in terms of `drop`, which is in turn defined in + // terms of `tail`. + @throws[IndexOutOfBoundsException] + override def apply(n: Int): A = { + if (n < 0) throw new IndexOutOfBoundsException(n.toString) + val skipped = drop(n) + if (skipped.isEmpty) throw new IndexOutOfBoundsException(n.toString) + skipped.head + } + + override def foreach[U](f: A => U): Unit = { + var these: LinearSeq[A] = coll + while (!these.isEmpty) { + f(these.head) + these = these.tail + } + } + + override def forall(p: A => Boolean): Boolean = { + var these: LinearSeq[A] = coll + while (!these.isEmpty) { + if (!p(these.head)) return false + these = these.tail + } + true + } + + override def exists(p: A => Boolean): Boolean = { + var these: LinearSeq[A] = coll + while (!these.isEmpty) { + if (p(these.head)) return true + these = these.tail + } + false + } + + override def contains[A1 >: A](elem: A1): Boolean = { + var these: LinearSeq[A] = coll + while (!these.isEmpty) { + if (these.head == elem) return true + these = these.tail + } + false + } + + override def find(p: A => Boolean): Option[A] = { + var these: LinearSeq[A] = coll + while (!these.isEmpty) { + if (p(these.head)) return Some(these.head) + these = these.tail + } + None + } + + override def foldLeft[B](z: B)(op: (B, A) => B): B = { + var acc = z + var these: LinearSeq[A] = coll + while (!these.isEmpty) { + acc = op(acc, these.head) + these = these.tail + } + acc + } + + override def sameElements[B >: A](that: IterableOnce[B]): Boolean = { + @tailrec def linearSeqEq(a: LinearSeq[B], b: LinearSeq[B]): Boolean = + (a eq b) || { + if (a.nonEmpty && b.nonEmpty && a.head == b.head) { + linearSeqEq(a.tail, b.tail) + } + else { + a.isEmpty && b.isEmpty + } + } + + that match { + case that: LinearSeq[B] => linearSeqEq(coll, that) + case _ => super.sameElements(that) + } + } + + override def segmentLength(p: A => Boolean, from: Int): Int = { + var i = 0 + var seq = drop(from) + while (seq.nonEmpty && p(seq.head)) { + i += 1 + seq = seq.tail + } + i + } + + override def indexWhere(p: A => Boolean, from: Int): Int = { + var i = math.max(from, 0) + var these: LinearSeq[A] = this drop from + while (these.nonEmpty) { + if (p(these.head)) + return i + + i += 1 + these = these.tail + } + -1 + } + + override def lastIndexWhere(p: A => Boolean, end: Int): Int = { + var i = 0 + var these: LinearSeq[A] = coll + var last = -1 + while (!these.isEmpty && i <= end) { + if (p(these.head)) last = i + these = these.tail + i += 1 + } + last + } + + override def findLast(p: A => Boolean): Option[A] = { + var these: LinearSeq[A] = coll + var found = false + var last: A = null.asInstanceOf[A] // don't use `Option`, to prevent excessive `Some` allocation + while (these.nonEmpty) { + val elem = these.head + if (p(elem)) { + found = true + last = elem + } + these = these.tail + } + if (found) Some(last) else None + } + + override def tails: Iterator[C] = { + val end = Iterator.single(empty) + Iterator.iterate(coll)(_.tail).takeWhile(_.nonEmpty) ++ end + } +} + +transparent trait StrictOptimizedLinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] with StrictOptimizedLinearSeqOps[A, CC, C]] extends Any with LinearSeqOps[A, CC, C] with StrictOptimizedSeqOps[A, CC, C] { + // A more efficient iterator implementation than the default LinearSeqIterator + override def iterator: Iterator[A] = new AbstractIterator[A] { + private[this] var current = StrictOptimizedLinearSeqOps.this + def hasNext = !current.isEmpty + def next() = { val r = current.head; current = current.tail; r } + } + + // Optimized version of `drop` that avoids copying + override def drop(n: Int): C = { + @tailrec def loop(n: Int, s: C): C = + if (n <= 0 || s.isEmpty) s + else loop(n - 1, s.tail) + loop(n, coll) + } + + override def dropWhile(p: A => Boolean): C = { + @tailrec def loop(s: C): C = + if (s.nonEmpty && p(s.head)) loop(s.tail) + else s + loop(coll) + } +} + +/** A specialized Iterator for LinearSeqs that is lazy enough for Stream and LazyList. This is accomplished by not + * evaluating the tail after returning the current head. + */ +private[collection] final class LinearSeqIterator[A](coll: LinearSeqOps[A, LinearSeq, LinearSeq[A]]) extends AbstractIterator[A] { + // A call-by-need cell + private[this] final class LazyCell(st: => LinearSeqOps[A, LinearSeq, LinearSeq[A]]) { lazy val v = st } + + private[this] var these: LazyCell = { + // Reassign reference to avoid creating a private class field and holding a reference to the head. + // LazyCell would otherwise close over `coll`. + val initialHead = coll + new LazyCell(initialHead) + } + + def hasNext: Boolean = these.v.nonEmpty + + def next(): A = + if (isEmpty) Iterator.empty.next() + else { + val cur = these.v + val result = cur.head + these = new LazyCell(cur.tail) + result + } +} diff --git a/library/src/scala/collection/Map.scala b/library/src/scala/collection/Map.scala new file mode 100644 index 000000000000..4d448f96c7cb --- /dev/null +++ b/library/src/scala/collection/Map.scala @@ -0,0 +1,421 @@ +/* + * 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 collection + +import scala.language.`2.13` +import scala.annotation.nowarn +import scala.collection.generic.DefaultSerializable +import scala.collection.mutable.StringBuilder +import scala.util.hashing.MurmurHash3 + +/** Base Map type */ +trait Map[K, +V] + extends Iterable[(K, V)] + with MapOps[K, V, Map, Map[K, V]] + with MapFactoryDefaults[K, V, Map, Iterable] + with Equals { + + def mapFactory: scala.collection.MapFactory[Map] = Map + + def canEqual(that: Any): Boolean = true + + /** + * Equality of maps is implemented using the lookup method [[get]]. This method returns `true` if + * - the argument `o` is a `Map`, + * - the two maps have the same [[size]], and + * - for every `(key, value)` pair in this map, `other.get(key) == Some(value)`. + * + * The implementation of `equals` checks the [[canEqual]] method, so subclasses of `Map` can narrow down the equality + * to specific map types. The `Map` implementations in the standard library can all be compared, their `canEqual` + * methods return `true`. + * + * Note: The `equals` method only respects the equality laws (symmetry, transitivity) if the two maps use the same + * key equivalence function in their lookup operation. For example, the key equivalence operation in a + * [[scala.collection.immutable.TreeMap]] is defined by its ordering. Comparing a `TreeMap` with a `HashMap` leads + * to unexpected results if `ordering.equiv(k1, k2)` (used for lookup in `TreeMap`) is different from `k1 == k2` + * (used for lookup in `HashMap`). + * + * {{{ + * scala> import scala.collection.immutable._ + * scala> val ord: Ordering[String] = _ compareToIgnoreCase _ + * + * scala> TreeMap("A" -> 1)(ord) == HashMap("a" -> 1) + * val res0: Boolean = false + * + * scala> HashMap("a" -> 1) == TreeMap("A" -> 1)(ord) + * val res1: Boolean = true + * }}} + * + * + * @param o The map to which this map is compared + * @return `true` if the two maps are equal according to the description + */ + override def equals(o: Any): Boolean = + (this eq o.asInstanceOf[AnyRef]) || (o match { + case map: Map[K @unchecked, _] if map.canEqual(this) => + (this.size == map.size) && { + try this.forall(kv => map.getOrElse(kv._1, Map.DefaultSentinelFn()) == kv._2) + catch { case _: ClassCastException => false } // PR #9565 / scala/bug#12228 + } + case _ => + false + }) + + override def hashCode(): Int = MurmurHash3.mapHash(this) + + // These two methods are not in MapOps so that MapView is not forced to implement them + @deprecated("Use - or removed on an immutable Map", "2.13.0") + def - (key: K): Map[K, V] + @deprecated("Use -- or removedAll on an immutable Map", "2.13.0") + def - (key1: K, key2: K, keys: K*): Map[K, V] + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "Map" + + override def toString(): String = super[Iterable].toString() // Because `Function1` overrides `toString` too +} + +/** Base Map implementation type + * + * @tparam K Type of keys + * @tparam V Type of values + * @tparam CC type constructor of the map (e.g. `HashMap`). Operations returning a collection + * with a different type of entries `(L, W)` (e.g. `map`) return a `CC[L, W]`. + * @tparam C type of the map (e.g. `HashMap[Int, String]`). Operations returning a collection + * with the same type of element (e.g. `drop`, `filter`) return a `C`. + * @define coll map + * @define Coll `Map` + */ +// Note: the upper bound constraint on CC is useful only to +// erase CC to IterableOps instead of Object +transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] + extends IterableOps[(K, V), Iterable, C] + with PartialFunction[K, V] { + + override def view: MapView[K, V] = new MapView.Id(this) + + /** Returns a [[Stepper]] for the keys of this map. See method [[stepper]]. */ + def keyStepper[S <: Stepper[_]](implicit shape: StepperShape[K, S]): S = { + import convert.impl._ + val s = shape.shape match { + case StepperShape.IntShape => new IntIteratorStepper (keysIterator.asInstanceOf[Iterator[Int]]) + case StepperShape.LongShape => new LongIteratorStepper (keysIterator.asInstanceOf[Iterator[Long]]) + case StepperShape.DoubleShape => new DoubleIteratorStepper(keysIterator.asInstanceOf[Iterator[Double]]) + case _ => shape.seqUnbox(new AnyIteratorStepper(keysIterator)) + } + s.asInstanceOf[S] + } + + /** Returns a [[Stepper]] for the values of this map. See method [[stepper]]. */ + def valueStepper[S <: Stepper[_]](implicit shape: StepperShape[V, S]): S = { + import convert.impl._ + val s = shape.shape match { + case StepperShape.IntShape => new IntIteratorStepper (valuesIterator.asInstanceOf[Iterator[Int]]) + case StepperShape.LongShape => new LongIteratorStepper (valuesIterator.asInstanceOf[Iterator[Long]]) + case StepperShape.DoubleShape => new DoubleIteratorStepper(valuesIterator.asInstanceOf[Iterator[Double]]) + case _ => shape.seqUnbox(new AnyIteratorStepper(valuesIterator)) + } + s.asInstanceOf[S] + } + + /** Similar to `fromIterable`, but returns a Map collection type. + * Note that the return type is now `CC[K2, V2]`. + */ + @`inline` protected final def mapFromIterable[K2, V2](it: Iterable[(K2, V2)]): CC[K2, V2] = mapFactory.from(it) + + /** The companion object of this map, providing various factory methods. + * + * @note When implementing a custom collection type and refining `CC` to the new type, this + * method needs to be overridden to return a factory for the new type (the compiler will + * issue an error otherwise). + */ + def mapFactory: MapFactory[CC] + + /** Optionally returns the value associated with a key. + * + * @param key the key value + * @return an option value containing the value associated with `key` in this map, + * or `None` if none exists. + */ + def get(key: K): Option[V] + + /** Returns the value associated with a key, or a default value if the key is not contained in the map. + * @param key the key. + * @param default a computation that yields a default value in case no binding for `key` is + * found in the map. + * @tparam V1 the result type of the default computation. + * @return the value associated with `key` if it exists, + * otherwise the result of the `default` computation. + */ + def getOrElse[V1 >: V](key: K, default: => V1): V1 = get(key) match { + case Some(v) => v + case None => default + } + + /** Retrieves the value which is associated with the given key. This + * method invokes the `default` method of the map if there is no mapping + * from the given key to a value. Unless overridden, the `default` method throws a + * `NoSuchElementException`. + * + * @param key the key + * @return the value associated with the given key, or the result of the + * map's `default` method, if none exists. + */ + @throws[NoSuchElementException] + def apply(key: K): V = get(key) match { + case None => default(key) + case Some(value) => value + } + + override /*PartialFunction*/ def applyOrElse[K1 <: K, V1 >: V](x: K1, default: K1 => V1): V1 = getOrElse(x, default(x)) + + /** A set representing the keys contained by this map. + * + * For efficiency the resulting set may be a view (maintaining a reference to the map and reflecting modifications + * to the map), but it may also be a strict collection without reference to the map. + * + * - To ensure an independent strict collection, use `m.keysIterator.toSet` + * - To obtain a view on the keys, use `scala.collection.View.fromIteratorProvider(m.keysIterator)` + * + * @return a set representing the keys contained by this map + */ + def keySet: Set[K] = new KeySet + + /** The implementation class of the set returned by `keySet`. + */ + protected class KeySet extends AbstractSet[K] with GenKeySet with DefaultSerializable { + def diff(that: Set[K]): Set[K] = fromSpecific(this.view.filterNot(that)) + } + + /** A generic trait that is reused by keyset implementations */ + protected trait GenKeySet { this: Set[K] => + def iterator: Iterator[K] = MapOps.this.keysIterator + def contains(key: K): Boolean = MapOps.this.contains(key) + override def size: Int = MapOps.this.size + override def knownSize: Int = MapOps.this.knownSize + override def isEmpty: Boolean = MapOps.this.isEmpty + } + + /** An [[Iterable]] collection of the keys contained by this map. + * + * For efficiency the resulting collection may be a view (maintaining a reference to the map and reflecting + * modifications to the map), but it may also be a strict collection without reference to the map. + * + * - To ensure an independent strict collection, use `m.keysIterator.toSet` + * - To obtain a view on the keys, use `scala.collection.View.fromIteratorProvider(m.keysIterator)` + * + * @return an [[Iterable]] collection of the keys contained by this map + */ + @deprecatedOverriding("This method should be an alias for keySet", since="2.13.13") + def keys: Iterable[K] = keySet + + /** Collects all values of this map in an iterable collection. + * + * @return the values of this map as an iterable. + */ + def values: Iterable[V] = new AbstractIterable[V] with DefaultSerializable { + override def knownSize: Int = MapOps.this.knownSize + override def iterator: Iterator[V] = valuesIterator + } + + /** An [[Iterator]] of the keys contained by this map. + * + * @return an [[Iterator]] of the keys contained by this map + */ + def keysIterator: Iterator[K] = new AbstractIterator[K] { + val iter = MapOps.this.iterator + def hasNext = iter.hasNext + def next() = iter.next()._1 + } + + /** Creates an iterator for all values in this map. + * + * @return an iterator over all values that are associated with some key in this map. + */ + def valuesIterator: Iterator[V] = new AbstractIterator[V] { + val iter = MapOps.this.iterator + def hasNext = iter.hasNext + def next() = iter.next()._2 + } + + /** Apply `f` to each key/value pair for its side effects + * Note: [U] parameter needed to help scalac's type inference. + */ + def foreachEntry[U](f: (K, V) => U): Unit = { + val it = iterator + while (it.hasNext) { + val next = it.next() + f(next._1, next._2) + } + } + + /** Filters this map by retaining only keys satisfying a predicate. + * @param p the predicate used to test keys + * @return an immutable map consisting only of those key value pairs of this map where the key satisfies + * the predicate `p`. The resulting map wraps the original map without copying any elements. + */ + @deprecated("Use .view.filterKeys(f). A future version will include a strict version of this method (for now, .view.filterKeys(p).toMap).", "2.13.0") + def filterKeys(p: K => Boolean): MapView[K, V] = new MapView.FilterKeys(this, p) + + /** Transforms this map by applying a function to every retrieved value. + * @param f the function used to transform values of this map. + * @return a map view which maps every key of this map + * to `f(this(key))`. The resulting map wraps the original map without copying any elements. + */ + @deprecated("Use .view.mapValues(f). A future version will include a strict version of this method (for now, .view.mapValues(f).toMap).", "2.13.0") + def mapValues[W](f: V => W): MapView[K, W] = new MapView.MapValues(this, f) + + /** Defines the default value computation for the map, + * returned when a key is not found. + * + * The method implemented here throws an exception, + * but it may be overridden by subclasses. + * + * @param key the given key value for which a binding is missing. + * @throws NoSuchElementException if no default value is defined + */ + @throws[NoSuchElementException] + def default(key: K): V = + throw new NoSuchElementException("key not found: " + key) + + /** Tests whether this map contains a binding for a key. + * + * @param key the key + * @return `true` if there is a binding for `key` in this map, `false` otherwise. + */ + def contains(key: K): Boolean = get(key).isDefined + + + /** Tests whether this map contains a binding for a key. This method, + * which implements an abstract method of trait `PartialFunction`, + * is equivalent to `contains`. + * + * @param key the key + * @return `true` if there is a binding for `key` in this map, `false` otherwise. + */ + def isDefinedAt(key: K): Boolean = contains(key) + + /** Builds a new map by applying a function to all elements of this $coll. + * + * @param f the function to apply to each element. + * @return a new $coll resulting from applying the given function + * `f` to each element of this $coll and collecting the results. + */ + def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = mapFactory.from(new View.Map(this, f)) + + /** Builds a new collection by applying a partial function to all elements of this $coll + * on which the function is defined. + * + * @param pf the partial function which filters and maps the $coll. + * @tparam K2 the key type of the returned $coll. + * @tparam V2 the value type of the returned $coll. + * @return a new $coll resulting from applying the given partial function + * `pf` to each element on which it is defined and collecting the results. + * The order of the elements is preserved. + */ + def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]): CC[K2, V2] = + mapFactory.from(new View.Collect(this, pf)) + + /** Builds a new map by applying a function to all elements of this $coll + * and using the elements of the resulting collections. + * + * @param f the function to apply to each element. + * @return a new $coll resulting from applying the given collection-valued function + * `f` to each element of this $coll and concatenating the results. + */ + def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]): CC[K2, V2] = mapFactory.from(new View.FlatMap(this, f)) + + /** Returns a new $coll containing the elements from the left hand operand followed by the elements from the + * right hand operand. The element type of the $coll is the most specific superclass encompassing + * the element types of the two operands. + * + * @param suffix the iterable to append. + * @return a new $coll which contains all elements + * of this $coll followed by all elements of `suffix`. + */ + def concat[V2 >: V](suffix: collection.IterableOnce[(K, V2)]): CC[K, V2] = mapFactory.from(suffix match { + case it: Iterable[(K, V2)] => new View.Concat(this, it) + case _ => iterator.concat(suffix.iterator) + }) + + // Not final because subclasses refine the result type, e.g. in SortedMap, the result type is + // SortedMap's CC, while Map's CC is fixed to Map + /** Alias for `concat` */ + /*@`inline` final*/ def ++ [V2 >: V](xs: collection.IterableOnce[(K, V2)]): CC[K, V2] = concat(xs) + + override def addString(sb: StringBuilder, start: String, sep: String, end: String): sb.type = + iterator.map { case (k, v) => s"$k -> $v" }.addString(sb, start, sep, end) + + @deprecated("Consider requiring an immutable Map or fall back to Map.concat.", "2.13.0") + def + [V1 >: V](kv: (K, V1)): CC[K, V1] = + mapFactory.from(new View.Appended(this, kv)) + + @deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0") + def + [V1 >: V](elem1: (K, V1), elem2: (K, V1), elems: (K, V1)*): CC[K, V1] = + mapFactory.from(new View.Concat(new View.Appended(new View.Appended(this, elem1), elem2), elems)) + + @deprecated("Consider requiring an immutable Map.", "2.13.0") + @`inline` def -- (keys: IterableOnce[K]): C = { + lazy val keysSet = keys.iterator.to(immutable.Set) + fromSpecific(this.view.filterKeys(k => !keysSet.contains(k))) + } + + @deprecated("Use ++ instead of ++: for collections of type Iterable", "2.13.0") + def ++: [V1 >: V](that: IterableOnce[(K,V1)]): CC[K,V1] = { + val thatIterable: Iterable[(K, V1)] = that match { + case that: Iterable[(K, V1)] => that + case that => View.from(that) + } + mapFactory.from(new View.Concat(thatIterable, this)) + } +} + +object MapOps { + /** Specializes `WithFilter` for Map collection types by adding overloads to transformation + * operations that can return a Map. + * + * @define coll map collection + */ + @SerialVersionUID(3L) + class WithFilter[K, +V, +IterableCC[_], +CC[_, _] <: IterableOps[_, AnyConstr, _]]( + self: MapOps[K, V, CC, _] with IterableOps[(K, V), IterableCC, _], + p: ((K, V)) => Boolean + ) extends IterableOps.WithFilter[(K, V), IterableCC](self, p) with Serializable { + + def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = + self.mapFactory.from(new View.Map(filtered, f)) + + def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]): CC[K2, V2] = + self.mapFactory.from(new View.FlatMap(filtered, f)) + + override def withFilter(q: ((K, V)) => Boolean): WithFilter[K, V, IterableCC, CC] = + new WithFilter[K, V, IterableCC, CC](self, (kv: (K, V)) => p(kv) && q(kv)) + + } + +} + +/** + * $factoryInfo + * @define coll map + * @define Coll `Map` + */ +@SerialVersionUID(3L) +object Map extends MapFactory.Delegate[Map](immutable.Map) { + private val DefaultSentinel: AnyRef = new AnyRef + private val DefaultSentinelFn: () => AnyRef = () => DefaultSentinel +} + +/** Explicit instantiation of the `Map` trait to reduce class file size in subclasses. */ +abstract class AbstractMap[K, +V] extends AbstractIterable[(K, V)] with Map[K, V] diff --git a/library/src/scala/collection/MapView.scala b/library/src/scala/collection/MapView.scala new file mode 100644 index 000000000000..a058f3f79514 --- /dev/null +++ b/library/src/scala/collection/MapView.scala @@ -0,0 +1,189 @@ +/* + * 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.collection + +import scala.language.`2.13` +import scala.annotation.nowarn +import scala.collection.MapView.SomeMapOps +import scala.collection.mutable.Builder + +trait MapView[K, +V] + extends MapOps[K, V, ({ type l[X, Y] = View[(X, Y)] })#l, View[(K, V)]] + with View[(K, V)] { + + override def view: MapView[K, V] = this + + // Ideally this returns a `View`, but bincompat + /** Creates a view over all keys of this map. + * + * @return the keys of this map as a view. + */ + @nowarn("msg=overriding method keys") + override def keys: Iterable[K] = new MapView.Keys(this) + + // Ideally this returns a `View`, but bincompat + /** Creates a view over all values of this map. + * + * @return the values of this map as a view. + */ + override def values: Iterable[V] = new MapView.Values(this) + + /** Filters this map by retaining only keys satisfying a predicate. + * @param p the predicate used to test keys + * @return an immutable map consisting only of those key value pairs of this map where the key satisfies + * the predicate `p`. The resulting map wraps the original map without copying any elements. + */ + override def filterKeys(p: K => Boolean): MapView[K, V] = new MapView.FilterKeys(this, p) + + /** Transforms this map by applying a function to every retrieved value. + * @param f the function used to transform values of this map. + * @return a map view which maps every key of this map + * to `f(this(key))`. The resulting map wraps the original map without copying any elements. + */ + override def mapValues[W](f: V => W): MapView[K, W] = new MapView.MapValues(this, f) + + override def filter(pred: ((K, V)) => Boolean): MapView[K, V] = new MapView.Filter(this, isFlipped = false, pred) + + override def filterNot(pred: ((K, V)) => Boolean): MapView[K, V] = new MapView.Filter(this, isFlipped = true, pred) + + override def partition(p: ((K, V)) => Boolean): (MapView[K, V], MapView[K, V]) = (filter(p), filterNot(p)) + + override def tapEach[U](f: ((K, V)) => U): MapView[K, V] = new MapView.TapEach(this, f) + + def mapFactory: MapViewFactory = MapView + + override def empty: MapView[K, V] = mapFactory.empty + + override def withFilter(p: ((K, V)) => Boolean): MapOps.WithFilter[K, V, View, ({ type l[X, Y] = View[(X, Y)] })#l] = new MapOps.WithFilter(this, p) + + override def toString: String = super[View].toString + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "MapView" +} + +object MapView extends MapViewFactory { + + /** An `IterableOps` whose collection type and collection type constructor are unknown */ + type SomeIterableConstr[X, Y] = IterableOps[_, AnyConstr, _] + /** A `MapOps` whose collection type and collection type constructor are (mostly) unknown */ + type SomeMapOps[K, +V] = MapOps[K, V, SomeIterableConstr, _] + + @SerialVersionUID(3L) + private val EmptyMapView: MapView[Any, Nothing] = new AbstractMapView[Any, Nothing] { + override def get(key: Any): Option[Nothing] = None + override def iterator: Iterator[Nothing] = Iterator.empty[Nothing] + override def knownSize: Int = 0 + override def isEmpty: Boolean = true + override def filterKeys(p: Any => Boolean): MapView[Any, Nothing] = this + override def mapValues[W](f: Nothing => W): MapView[Any, Nothing] = this + override def filter(pred: ((Any, Nothing)) => Boolean): MapView[Any, Nothing] = this + override def filterNot(pred: ((Any, Nothing)) => Boolean): MapView[Any, Nothing] = this + override def partition(p: ((Any, Nothing)) => Boolean): (MapView[Any, Nothing], MapView[Any, Nothing]) = (this, this) + } + + @SerialVersionUID(3L) + class Id[K, +V](underlying: SomeMapOps[K, V]) extends AbstractMapView[K, V] { + def get(key: K): Option[V] = underlying.get(key) + def iterator: Iterator[(K, V)] = underlying.iterator + override def knownSize: Int = underlying.knownSize + override def isEmpty: Boolean = underlying.isEmpty + } + + // Ideally this is public, but bincompat + @SerialVersionUID(3L) + private class Keys[K](underlying: SomeMapOps[K, _]) extends AbstractView[K] { + def iterator: Iterator[K] = underlying.keysIterator + override def knownSize: Int = underlying.knownSize + override def isEmpty: Boolean = underlying.isEmpty + } + + // Ideally this is public, but bincompat + @SerialVersionUID(3L) + private class Values[+V](underlying: SomeMapOps[_, V]) extends AbstractView[V] { + def iterator: Iterator[V] = underlying.valuesIterator + override def knownSize: Int = underlying.knownSize + override def isEmpty: Boolean = underlying.isEmpty + } + + @SerialVersionUID(3L) + class MapValues[K, +V, +W](underlying: SomeMapOps[K, V], f: V => W) extends AbstractMapView[K, W] { + def iterator: Iterator[(K, W)] = underlying.iterator.map(kv => (kv._1, f(kv._2))) + def get(key: K): Option[W] = underlying.get(key).map(f) + override def knownSize: Int = underlying.knownSize + override def isEmpty: Boolean = underlying.isEmpty + } + + @SerialVersionUID(3L) + class FilterKeys[K, +V](underlying: SomeMapOps[K, V], p: K => Boolean) extends AbstractMapView[K, V] { + def iterator: Iterator[(K, V)] = underlying.iterator.filter { case (k, _) => p(k) } + def get(key: K): Option[V] = if (p(key)) underlying.get(key) else None + override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = iterator.isEmpty + } + + @SerialVersionUID(3L) + class Filter[K, +V](underlying: SomeMapOps[K, V], isFlipped: Boolean, p: ((K, V)) => Boolean) extends AbstractMapView[K, V] { + def iterator: Iterator[(K, V)] = underlying.iterator.filterImpl(p, isFlipped) + def get(key: K): Option[V] = underlying.get(key) match { + case s @ Some(v) if p((key, v)) != isFlipped => s + case _ => None + } + override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = iterator.isEmpty + } + + @SerialVersionUID(3L) + class TapEach[K, +V, +U](underlying: SomeMapOps[K, V], f: ((K, V)) => U) extends AbstractMapView[K, V] { + override def get(key: K): Option[V] = { + underlying.get(key) match { + case s @ Some(v) => + f((key, v)) + s + case None => None + } + } + override def iterator: Iterator[(K, V)] = underlying.iterator.tapEach(f) + override def knownSize: Int = underlying.knownSize + override def isEmpty: Boolean = underlying.isEmpty + } + + override def newBuilder[X, Y]: Builder[(X, Y), MapView[X, Y]] = mutable.HashMap.newBuilder[X, Y].mapResult(_.view) + + override def empty[K, V]: MapView[K, V] = EmptyMapView.asInstanceOf[MapView[K, V]] + + override def from[K, V](it: IterableOnce[(K, V)]): View[(K, V)] = View.from(it) + + override def from[K, V](it: SomeMapOps[K, V]): MapView[K, V] = it match { + case mv: MapView[K, V] => mv + case other => new MapView.Id(other) + } + + override def apply[K, V](elems: (K, V)*): MapView[K, V] = from(elems.toMap) +} + +trait MapViewFactory extends collection.MapFactory[({ type l[X, Y] = View[(X, Y)]})#l] { + + def newBuilder[X, Y]: Builder[(X, Y), MapView[X, Y]] + + def empty[X, Y]: MapView[X, Y] + + def from[K, V](it: SomeMapOps[K, V]): MapView[K, V] + + override def apply[K, V](elems: (K, V)*): MapView[K, V] = from(elems.toMap) +} + +/** Explicit instantiation of the `MapView` trait to reduce class file size in subclasses. */ +@SerialVersionUID(3L) +abstract class AbstractMapView[K, +V] extends AbstractView[(K, V)] with MapView[K, V] + diff --git a/library/src/scala/collection/Searching.scala b/library/src/scala/collection/Searching.scala new file mode 100644 index 000000000000..7148f54606bf --- /dev/null +++ b/library/src/scala/collection/Searching.scala @@ -0,0 +1,58 @@ +/* + * 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.collection + +import scala.language.`2.13` +import scala.language.implicitConversions +import scala.collection.generic.IsSeq + +object Searching { + + /** The result of performing a search on a sorted sequence + * + * Example usage: + * + * {{{ + * val list = List(1, 3, 4, 5) // list must be sorted before searching + * list.search(4) // Found(2) + * list.search(2) // InsertionPoint(1) + * }}} + * + * */ + sealed abstract class SearchResult { + /** The index corresponding to the element searched for in the sequence, if it was found, + * or the index where the element would be inserted in the sequence, if it was not in the sequence */ + def insertionPoint: Int + } + + /** The result of performing a search on a sorted sequence, where the element was found. + * + * @param foundIndex the index corresponding to the element searched for in the sequence + */ + case class Found(foundIndex: Int) extends SearchResult { + override def insertionPoint: Int = foundIndex + } + + /** The result of performing a search on a sorted sequence, where the element was not found + * + * @param insertionPoint the index where the element would be inserted in the sequence + */ + case class InsertionPoint(insertionPoint: Int) extends SearchResult + + @deprecated("Search methods are defined directly on SeqOps and do not require scala.collection.Searching any more", "2.13.0") + class SearchImpl[Repr, A](private val coll: SeqOps[A, AnyConstr, _]) extends AnyVal + + @deprecated("Search methods are defined directly on SeqOps and do not require scala.collection.Searching any more", "2.13.0") + implicit def search[Repr, A](coll: Repr)(implicit fr: IsSeq[Repr]): SearchImpl[Repr, fr.A] = + new SearchImpl(fr.conversion(coll)) +} diff --git a/library/src/scala/collection/Seq.scala b/library/src/scala/collection/Seq.scala new file mode 100644 index 000000000000..2f960a47f54f --- /dev/null +++ b/library/src/scala/collection/Seq.scala @@ -0,0 +1,1198 @@ +/* + * 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.collection + +import scala.language.`2.13` +import scala.collection.immutable.Range +import scala.util.hashing.MurmurHash3 +import Searching.{Found, InsertionPoint, SearchResult} +import scala.annotation.nowarn + +/** Base trait for sequence collections + * + * @tparam A the element type of the collection + */ +trait Seq[+A] + extends Iterable[A] + with PartialFunction[Int, A] + with SeqOps[A, Seq, Seq[A]] + with IterableFactoryDefaults[A, Seq] + with Equals { + + override def iterableFactory: SeqFactory[Seq] = Seq + + def canEqual(that: Any): Boolean = true + + override def equals(o: Any): Boolean = + (this eq o.asInstanceOf[AnyRef]) || (o match { + case seq: Seq[A @unchecked] if seq.canEqual(this) => sameElements(seq) + case _ => false + }) + + override def hashCode(): Int = MurmurHash3.seqHash(this) + + override def toString(): String = super[Iterable].toString() + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "Seq" +} + +/** + * $factoryInfo + * @define coll sequence + * @define Coll `Seq` + */ +@SerialVersionUID(3L) +object Seq extends SeqFactory.Delegate[Seq](immutable.Seq) + +/** Base trait for Seq operations + * + * @tparam A the element type of the collection + * @tparam CC type constructor of the collection (e.g. `List`, `Set`). Operations returning a collection + * with a different type of element `B` (e.g. `map`) return a `CC[B]`. + * @tparam C type of the collection (e.g. `List[Int]`, `String`, `BitSet`). Operations returning a collection + * with the same type of element (e.g. `drop`, `filter`) return a `C`. + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * + * Note: may not terminate for infinite-sized collections. + * + * @define willNotTerminateInf + * + * Note: will not terminate for infinite-sized collections. + * + * @define coll sequence + * @define Coll `Seq` + */ +transparent trait SeqOps[+A, +CC[_], +C] extends Any + with IterableOps[A, CC, C] { self => + + override def view: SeqView[A] = new SeqView.Id[A](this) + + /** Gets the element at the specified index. This operation is provided for convenience in `Seq`. It should + * not be assumed to be efficient unless you have an `IndexedSeq`. */ + @throws[IndexOutOfBoundsException] + def apply(i: Int): A + + /** The length (number of elements) of the $coll. `size` is an alias for `length` in `Seq` collections. */ + def length: Int + + /** A copy of the $coll with an element prepended. + * + * Also, the original $coll is not modified, so you will want to capture the result. + * + * Example: + * {{{ + * scala> val x = List(1) + * x: List[Int] = List(1) + * + * scala> val y = 2 +: x + * y: List[Int] = List(2, 1) + * + * scala> println(x) + * List(1) + * }}} + * + * @param elem the prepended element + * @tparam B the element type of the returned $coll. + * + * @return a new $coll consisting of `value` followed + * by all elements of this $coll. + */ + def prepended[B >: A](elem: B): CC[B] = iterableFactory.from(new View.Prepended(elem, this)) + + /** Alias for `prepended`. + * + * Note that :-ending operators are right associative (see example). + * A mnemonic for `+:` vs. `:+` is: the COLon goes on the COLlection side. + */ + @`inline` final def +: [B >: A](elem: B): CC[B] = prepended(elem) + + /** A copy of this $coll with an element appended. + * + * $willNotTerminateInf + * + * Example: + * {{{ + * scala> val a = List(1) + * a: List[Int] = List(1) + * + * scala> val b = a :+ 2 + * b: List[Int] = List(1, 2) + * + * scala> println(a) + * List(1) + * }}} + * + * @param elem the appended element + * @tparam B the element type of the returned $coll. + * @return a new $coll consisting of + * all elements of this $coll followed by `value`. + */ + def appended[B >: A](elem: B): CC[B] = iterableFactory.from(new View.Appended(this, elem)) + + /** Alias for `appended`. + * + * Note that :-ending operators are right associative (see example). + * A mnemonic for `+:` vs. `:+` is: the COLon goes on the COLlection side. + */ + @`inline` final def :+ [B >: A](elem: B): CC[B] = appended(elem) + + /** As with `:++`, returns a new collection containing the elements from the left operand followed by the + * elements from the right operand. + * + * It differs from `:++` in that the right operand determines the type of + * the resulting collection rather than the left one. + * Mnemonic: the COLon is on the side of the new COLlection type. + * + * @param prefix the iterable to prepend. + * @tparam B the element type of the returned collection. + * @return a new $coll which contains all elements of `prefix` followed + * by all the elements of this $coll. + */ + def prependedAll[B >: A](prefix: IterableOnce[B]): CC[B] = iterableFactory.from(prefix match { + case prefix: Iterable[B] => new View.Concat(prefix, this) + case _ => prefix.iterator ++ iterator + }) + + /** Alias for `prependedAll`. */ + @`inline` override final def ++: [B >: A](prefix: IterableOnce[B]): CC[B] = prependedAll(prefix) + + /** Returns a new $coll containing the elements from the left hand operand followed by the elements from the + * right hand operand. The element type of the $coll is the most specific superclass encompassing + * the element types of the two operands. + * + * @param suffix the iterable to append. + * @tparam B the element type of the returned collection. + * @return a new collection of type `CC[B]` which contains all elements + * of this $coll followed by all elements of `suffix`. + */ + def appendedAll[B >: A](suffix: IterableOnce[B]): CC[B] = super.concat(suffix) + + /** Alias for `appendedAll`. */ + @inline final def :++ [B >: A](suffix: IterableOnce[B]): CC[B] = appendedAll(suffix) + + // Make `concat` an alias for `appendedAll` so that it benefits from performance + // overrides of this method + @inline final override def concat[B >: A](suffix: IterableOnce[B]): CC[B] = appendedAll(suffix) + + /** Produces a new sequence which contains all elements of this $coll and also all elements of + * a given sequence. `xs union ys` is equivalent to `xs ++ ys`. + * + * @param that the sequence to add. + * @tparam B the element type of the returned $coll. + * @return a new collection which contains all elements of this $coll + * followed by all elements of `that`. + */ + @deprecated("Use `concat` instead", "2.13.0") + @inline final def union[B >: A](that: Seq[B]): CC[B] = concat(that) + + final override def size: Int = length + + /** Selects all the elements of this $coll ignoring the duplicates. + * + * @return a new $coll consisting of all the elements of this $coll without duplicates. + */ + def distinct: C = distinctBy(identity) + + /** Selects all the elements of this $coll ignoring the duplicates as determined by `==` after applying + * the transforming function `f`. + * + * @param f The transforming function whose result is used to determine the uniqueness of each element + * @tparam B the type of the elements after being transformed by `f` + * @return a new $coll consisting of all the elements of this $coll without duplicates. + */ + def distinctBy[B](f: A => B): C = fromSpecific(new View.DistinctBy(this, f)) + + /** Returns a new $coll with the elements of this $coll in reverse order. + * + * $willNotTerminateInf + * $willForceEvaluation + * + * @return a new $coll with all elements of this $coll in reverse order. + */ + def reverse: C = fromSpecific(reversed) + + /** An iterator yielding the elements of this $coll in reverse order. + * + * $willNotTerminateInf + * + * Note: `xs.reverseIterator` is the same as `xs.reverse.iterator` but might be more efficient. + * + * @return an iterator yielding the elements of this $coll in reverse order. + */ + def reverseIterator: Iterator[A] = reversed.iterator + + /** Tests whether this $coll contains the given sequence at a given index. + * + * '''Note''': If the both the receiver object `this` and the argument + * `that` are infinite sequences this method may not terminate. + * + * @param that the sequence to test + * @param offset the index where the sequence is searched. + * @return `true` if the sequence `that` is contained in this $coll at + * index `offset`, otherwise `false`. + */ + def startsWith[B >: A](that: IterableOnce[B], offset: Int = 0): Boolean = { + val i = iterator drop offset + val j = that.iterator + while (j.hasNext && i.hasNext) + if (i.next() != j.next()) + return false + + !j.hasNext + } + + /** Tests whether this $coll ends with the given sequence. + * $willNotTerminateInf + * @param that the sequence to test + * @return `true` if this $coll has `that` as a suffix, `false` otherwise. + */ + def endsWith[B >: A](that: Iterable[B]): Boolean = { + if (that.isEmpty) true + else { + val i = iterator.drop(length - that.size) + val j = that.iterator + while (i.hasNext && j.hasNext) + if (i.next() != j.next()) + return false + + !j.hasNext + } + } + + /** Tests whether this $coll contains given index. + * + * The implementations of methods `apply` and `isDefinedAt` turn a `Seq[A]` into + * a `PartialFunction[Int, A]`. + * + * @param idx the index to test + * @return `true` if this $coll contains an element at position `idx`, `false` otherwise. + */ + def isDefinedAt(idx: Int): Boolean = idx >= 0 && lengthIs > idx + + /** A copy of this $coll with an element value appended until a given target length is reached. + * + * @param len the target length + * @param elem the padding value + * @tparam B the element type of the returned $coll. + * @return a new $ccoll consisting of + * all elements of this $coll followed by the minimal number of occurrences of `elem` so + * that the resulting collection has a length of at least `len`. + */ + def padTo[B >: A](len: Int, elem: B): CC[B] = iterableFactory.from(new View.PadTo(this, len, elem)) + + /** Computes the length of the longest segment that starts from the first element + * and whose elements all satisfy some predicate. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @return the length of the longest segment of this $coll that starts from the first element + * such that every element of the segment satisfies the predicate `p`. + */ + final def segmentLength(p: A => Boolean): Int = segmentLength(p, 0) + + /** Computes the length of the longest segment that starts from some index + * and whose elements all satisfy some predicate. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @param from the index where the search starts. + * @return the length of the longest segment of this $coll starting from index `from` + * such that every element of the segment satisfies the predicate `p`. + */ + def segmentLength(p: A => Boolean, from: Int): Int = { + var i = 0 + val it = iterator.drop(from) + while (it.hasNext && p(it.next())) + i += 1 + i + } + + /** Returns the length of the longest prefix whose elements all satisfy some predicate. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @return the length of the longest prefix of this $coll + * such that every element of the segment satisfies the predicate `p`. + */ + @deprecated("Use segmentLength instead of prefixLength", "2.13.0") + @`inline` final def prefixLength(p: A => Boolean): Int = segmentLength(p, 0) + + /** Finds index of the first element satisfying some predicate after or at some start index. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @param from the start index + * @return the index `>= from` of the first element of this $coll that satisfies the predicate `p`, + * or `-1`, if none exists. + */ + def indexWhere(p: A => Boolean, from: Int): Int = iterator.indexWhere(p, from) + + /** Finds index of the first element satisfying some predicate. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @return the index `>= 0` of the first element of this $coll that satisfies the predicate `p`, + * or `-1`, if none exists. + */ + @deprecatedOverriding("Override indexWhere(p, from) instead - indexWhere(p) calls indexWhere(p, 0)", "2.13.0") + def indexWhere(p: A => Boolean): Int = indexWhere(p, 0) + + /** Finds index of first occurrence of some value in this $coll after or at some start index. + * + * @param elem the element value to search for. + * @tparam B the type of the element `elem`. + * @param from the start index + * @return the index `>= from` of the first element of this $coll that is equal (as determined by `==`) + * to `elem`, or `-1`, if none exists. + */ + def indexOf[B >: A](elem: B, from: Int): Int = indexWhere(elem == _, from) + + /** Finds index of first occurrence of some value in this $coll. + * + * @param elem the element value to search for. + * @tparam B the type of the element `elem`. + * @return the index `>= 0` of the first element of this $coll that is equal (as determined by `==`) + * to `elem`, or `-1`, if none exists. + */ + @deprecatedOverriding("Override indexOf(elem, from) instead - indexOf(elem) calls indexOf(elem, 0)", "2.13.0") + def indexOf[B >: A](elem: B): Int = indexOf(elem, 0) + + /** Finds index of last occurrence of some value in this $coll before or at a given end index. + * + * $willNotTerminateInf + * + * @param elem the element value to search for. + * @param end the end index. + * @tparam B the type of the element `elem`. + * @return the index `<= end` of the last element of this $coll that is equal (as determined by `==`) + * to `elem`, or `-1`, if none exists. + */ + def lastIndexOf[B >: A](elem: B, end: Int = length - 1): Int = lastIndexWhere(elem == _, end) + + /** Finds index of last element satisfying some predicate before or at given end index. + * + * $willNotTerminateInf + * + * @param p the predicate used to test elements. + * @return the index `<= end` of the last element of this $coll that satisfies the predicate `p`, + * or `-1`, if none exists. + */ + def lastIndexWhere(p: A => Boolean, end: Int): Int = { + var i = length - 1 + val it = reverseIterator + while (it.hasNext && { val elem = it.next(); (i > end || !p(elem)) }) i -= 1 + i + } + + /** Finds index of last element satisfying some predicate. + * + * $willNotTerminateInf + * + * @param p the predicate used to test elements. + * @return the index of the last element of this $coll that satisfies the predicate `p`, + * or `-1`, if none exists. + */ + @deprecatedOverriding("Override lastIndexWhere(p, end) instead - lastIndexWhere(p) calls lastIndexWhere(p, Int.MaxValue)", "2.13.0") + def lastIndexWhere(p: A => Boolean): Int = lastIndexWhere(p, Int.MaxValue) + + @inline private[this] def toGenericSeq: scala.collection.Seq[A] = this match { + case s: scala.collection.Seq[A] => s + case _ => toSeq + } + + /** Finds first index after or at a start index where this $coll contains a given sequence as a slice. + * $mayNotTerminateInf + * @param that the sequence to test + * @param from the start index + * @return the first index `>= from` such that the elements of this $coll starting at this index + * match the elements of sequence `that`, or `-1` if no such subsequence exists. + */ + // TODO Should be implemented in a way that preserves laziness + def indexOfSlice[B >: A](that: Seq[B], from: Int): Int = + if (that.isEmpty && from == 0) 0 + else { + val l = knownSize + val tl = that.knownSize + if (l >= 0 && tl >= 0) { + val clippedFrom = math.max(0, from) + if (from > l) -1 + else if (tl < 1) clippedFrom + else if (l < tl) -1 + else SeqOps.kmpSearch(toGenericSeq, clippedFrom, l, that, 0, tl, forward = true) + } + else { + var i = from + var s: scala.collection.Seq[A] = toGenericSeq.drop(i) + while (!s.isEmpty) { + if (s startsWith that) + return i + + i += 1 + s = s.tail + } + -1 + } + } + + /** Finds first index where this $coll contains a given sequence as a slice. + * $mayNotTerminateInf + * @param that the sequence to test + * @return the first index `>= 0` such that the elements of this $coll starting at this index + * match the elements of sequence `that`, or `-1` if no such subsequence exists. + */ + @deprecatedOverriding("Override indexOfSlice(that, from) instead - indexOfSlice(that) calls indexOfSlice(that, 0)", "2.13.0") + def indexOfSlice[B >: A](that: Seq[B]): Int = indexOfSlice(that, 0) + + /** Finds last index before or at a given end index where this $coll contains a given sequence as a slice. + * + * $willNotTerminateInf + * + * @param that the sequence to test + * @param end the end index + * @return the last index `<= end` such that the elements of this $coll starting at this index + * match the elements of sequence `that`, or `-1` if no such subsequence exists. + */ + def lastIndexOfSlice[B >: A](that: Seq[B], end: Int): Int = { + val l = length + val tl = that.length + val clippedL = math.min(l-tl, end) + + if (end < 0) -1 + else if (tl < 1) clippedL + else if (l < tl) -1 + else SeqOps.kmpSearch(toGenericSeq, 0, clippedL+tl, that, 0, tl, forward = false) + } + + /** Finds last index where this $coll contains a given sequence as a slice. + * + * $willNotTerminateInf + * + * @param that the sequence to test + * @return the last index such that the elements of this $coll starting at this index + * match the elements of sequence `that`, or `-1` if no such subsequence exists. + */ + @deprecatedOverriding("Override lastIndexOfSlice(that, end) instead - lastIndexOfSlice(that) calls lastIndexOfSlice(that, Int.MaxValue)", "2.13.0") + def lastIndexOfSlice[B >: A](that: Seq[B]): Int = lastIndexOfSlice(that, Int.MaxValue) + + /** Finds the last element of the $coll satisfying a predicate, if any. + * + * $willNotTerminateInf + * + * @param p the predicate used to test elements. + * @return an option value containing the last element in the $coll + * that satisfies `p`, or `None` if none exists. + */ + def findLast(p: A => Boolean): Option[A] = { + val it = reverseIterator + while (it.hasNext) { + val elem = it.next() + if (p(elem)) return Some(elem) + } + None + } + + /** Tests whether this $coll contains a given sequence as a slice. + * $mayNotTerminateInf + * @param that the sequence to test + * @return `true` if this $coll contains a slice with the same elements + * as `that`, otherwise `false`. + */ + def containsSlice[B >: A](that: Seq[B]): Boolean = indexOfSlice(that) != -1 + + /** Tests whether this $coll contains a given value as an element. + * $mayNotTerminateInf + * + * @param elem the element to test. + * @return `true` if this $coll has an element that is equal (as + * determined by `==`) to `elem`, `false` otherwise. + */ + def contains[A1 >: A](elem: A1): Boolean = exists (_ == elem) + + @deprecated("Use .reverseIterator.map(f).to(...) instead of .reverseMap(f)", "2.13.0") + def reverseMap[B](f: A => B): CC[B] = iterableFactory.from(new View.Map(View.fromIteratorProvider(() => reverseIterator), f)) + + /** Iterates over distinct permutations of elements. + * + * $willForceEvaluation + * + * @return An Iterator which traverses the distinct permutations of this $coll. + * @example {{{ + * Seq('a', 'b', 'b').permutations.foreach(println) + * // List(a, b, b) + * // List(b, a, b) + * // List(b, b, a) + * }}} + */ + def permutations: Iterator[C] = + if (isEmpty) Iterator.single(coll) + else new PermutationsItr + + /** Iterates over combinations of elements. + * + * A '''combination''' of length `n` is a sequence of `n` elements selected in order of their first index in this sequence. + * + * For example, `"xyx"` has two combinations of length 2. The `x` is selected first: `"xx"`, `"xy"`. + * The sequence `"yx"` is not returned as a combination because it is subsumed by `"xy"`. + * + * If there is more than one way to generate the same combination, only one will be returned. + * + * For example, the result `"xy"` arbitrarily selected one of the `x` elements. + * + * As a further illustration, `"xyxx"` has three different ways to generate `"xy"` because there are three elements `x` + * to choose from. Moreover, there are three unordered pairs `"xx"` but only one is returned. + * + * It is not specified which of these equal combinations is returned. It is an implementation detail + * that should not be relied on. For example, the combination `"xx"` does not necessarily contain + * the first `x` in this sequence. This behavior is observable if the elements compare equal + * but are not identical. + * + * As a consequence, `"xyx".combinations(3).next()` is `"xxy"`: the combination does not reflect the order + * of the original sequence, but the order in which elements were selected, by "first index"; + * the order of each `x` element is also arbitrary. + * + * $willForceEvaluation + * + * @return An Iterator which traverses the n-element combinations of this $coll. + * @example {{{ + * Seq('a', 'b', 'b', 'b', 'c').combinations(2).foreach(println) + * // List(a, b) + * // List(a, c) + * // List(b, b) + * // List(b, c) + * Seq('b', 'a', 'b').combinations(2).foreach(println) + * // List(b, b) + * // List(b, a) + * }}} + */ + def combinations(n: Int): Iterator[C] = + if (n < 0 || n > size) Iterator.empty + else new CombinationsItr(n) + + private class PermutationsItr extends AbstractIterator[C] { + private[this] val (elms, idxs) = init() + private[this] var _hasNext = true + + def hasNext = _hasNext + @throws[NoSuchElementException] + def next(): C = { + if (!hasNext) + Iterator.empty.next() + + val forcedElms = new mutable.ArrayBuffer[A](elms.size) ++= elms + val result = (newSpecificBuilder ++= forcedElms).result() + var i = idxs.length - 2 + while(i >= 0 && idxs(i) >= idxs(i+1)) + i -= 1 + + if (i < 0) + _hasNext = false + else { + var j = idxs.length - 1 + while(idxs(j) <= idxs(i)) j -= 1 + swap(i,j) + + val len = (idxs.length - i) / 2 + var k = 1 + while (k <= len) { + swap(i+k, idxs.length - k) + k += 1 + } + } + result + } + private def swap(i: Int, j: Int): Unit = { + val tmpI = idxs(i) + idxs(i) = idxs(j) + idxs(j) = tmpI + val tmpE = elms(i) + elms(i) = elms(j) + elms(j) = tmpE + } + + private[this] def init() = { + val m = mutable.HashMap[A, Int]() + val (es, is) = (self.toGenericSeq map (e => (e, m.getOrElseUpdate(e, m.size))) sortBy (_._2)).unzip + + (es.to(mutable.ArrayBuffer), is.toArray) + } + } + + private class CombinationsItr(n: Int) extends AbstractIterator[C] { + // generating all nums such that: + // (1) nums(0) + .. + nums(length-1) = n + // (2) 0 <= nums(i) <= cnts(i), where 0 <= i <= cnts.length-1 + private[this] val (elms, cnts, nums) = init() + private[this] val offs = cnts.scanLeft(0)(_ + _) + private[this] var _hasNext = true + + def hasNext = _hasNext + def next(): C = { + if (!hasNext) + Iterator.empty.next() + + /* Calculate this result. */ + val buf = newSpecificBuilder + for(k <- 0 until nums.length; j <- 0 until nums(k)) + buf += elms(offs(k)+j) + val res = buf.result() + + /* Prepare for the next call to next. */ + var idx = nums.length - 1 + while (idx >= 0 && nums(idx) == cnts(idx)) + idx -= 1 + + idx = nums.lastIndexWhere(_ > 0, idx - 1) + + if (idx < 0) + _hasNext = false + else { + // OPT: hand rolled version of `sum = nums.view(idx + 1, nums.length).sum + 1` + var sum = 1 + var i = idx + 1 + while (i < nums.length) { + sum += nums(i) + i += 1 + } + nums(idx) -= 1 + for (k <- (idx+1) until nums.length) { + nums(k) = sum min cnts(k) + sum -= nums(k) + } + } + + res + } + + /** Rearrange seq to newSeq a0a0..a0a1..a1...ak..ak such that + * seq.count(_ == aj) == cnts(j) + * + * @return (newSeq,cnts,nums) + */ + private def init(): (IndexedSeq[A], Array[Int], Array[Int]) = { + val m = mutable.HashMap[A, Int]() + + // e => (e, weight(e)) + val (es, is) = (self.toGenericSeq map (e => (e, m.getOrElseUpdate(e, m.size))) sortBy (_._2)).unzip + val cs = new Array[Int](m.size) + is foreach (i => cs(i) += 1) + val ns = new Array[Int](cs.length) + + var r = n + 0 until ns.length foreach { k => + ns(k) = r min cs(k) + r -= ns(k) + } + (es.to(IndexedSeq), cs, ns) + } + } + + /** Sorts this $coll according to an Ordering. + * + * The sort is stable. That is, elements that are equal (as determined by + * `ord.compare`) appear in the same order in the sorted sequence as in the original. + * + * @see [[scala.math.Ordering]] + * + * $willForceEvaluation + * + * @param ord the ordering to be used to compare elements. + * @return a $coll consisting of the elements of this $coll + * sorted according to the ordering `ord`. + */ + def sorted[B >: A](implicit ord: Ordering[B]): C = { + val len = this.length + val b = newSpecificBuilder + if (len == 1) b += head + else if (len > 1) { + b.sizeHint(len) + val arr = new Array[Any](len) + @annotation.unused val copied = copyToArray(arr) + //assert(copied == len) + java.util.Arrays.sort(arr.asInstanceOf[Array[AnyRef]], ord.asInstanceOf[Ordering[AnyRef]]) + var i = 0 + while (i < len) { + b += arr(i).asInstanceOf[A] + i += 1 + } + } + b.result() + } + + /** Sorts this $coll according to a comparison function. + * $willNotTerminateInf + * $willForceEvaluation + * + * The sort is stable. That is, elements that are equal + * (`lt` returns false for both directions of comparison) + * appear in the same order in the sorted sequence as in the original. + * + * @param lt a predicate that is true if + * its first argument strictly precedes its second argument in + * the desired ordering. + * @return a $coll consisting of the elements of this $coll + * sorted according to the comparison function `lt`. + * @example {{{ + * List("Steve", "Bobby", "Tom", "John", "Bob").sortWith((x, y) => x.take(3).compareTo(y.take(3)) < 0) = + * List("Bobby", "Bob", "John", "Steve", "Tom") + * }}} + */ + def sortWith(lt: (A, A) => Boolean): C = sorted(Ordering.fromLessThan(lt)) + + /** Sorts this $coll according to the Ordering which results from transforming + * an implicitly given Ordering with a transformation function. + * $willNotTerminateInf + * $willForceEvaluation + * + * The sort is stable. That is, elements that are equal (as determined by + * `ord.compare`) appear in the same order in the sorted sequence as in the original. + * + * @see [[scala.math.Ordering]] + * @param f the transformation function mapping elements + * to some other domain `B`. + * @param ord the ordering assumed on domain `B`. + * @tparam B the target type of the transformation `f`, and the type where + * the ordering `ord` is defined. + * @return a $coll consisting of the elements of this $coll + * sorted according to the ordering where `x < y` if + * `ord.lt(f(x), f(y))`. + * + * @example {{{ + * val words = "The quick brown fox jumped over the lazy dog".split(' ') + * // this works because scala.Ordering will implicitly provide an Ordering[Tuple2[Int, Char]] + * words.sortBy(x => (x.length, x.head)) + * res0: Array[String] = Array(The, dog, fox, the, lazy, over, brown, quick, jumped) + * }}} + */ + def sortBy[B](f: A => B)(implicit ord: Ordering[B]): C = sorted(ord on f) + + /** Produces the range of all indices of this sequence. + * $willForceEvaluation + * + * @return a `Range` value from `0` to one less than the length of this $coll. + */ + def indices: Range = Range(0, length) + + override final def sizeCompare(otherSize: Int): Int = lengthCompare(otherSize) + + /** Compares the length of this $coll to a test value. + * + * @param len the test value that gets compared with the length. + * @return A value `x` where + * {{{ + * x < 0 if this.length < len + * x == 0 if this.length == len + * x > 0 if this.length > len + * }}} + * The method as implemented here does not call `length` directly; its running time + * is `O(length min len)` instead of `O(length)`. The method should be overridden + * if computing `length` is cheap and `knownSize` returns `-1`. + * + * @see [[lengthIs]] + */ + def lengthCompare(len: Int): Int = super.sizeCompare(len) + + override final def sizeCompare(that: Iterable[_]): Int = lengthCompare(that) + + /** Compares the length of this $coll to the size of another `Iterable`. + * + * @param that the `Iterable` whose size is compared with this $coll's length. + * @return A value `x` where + * {{{ + * x < 0 if this.length < that.size + * x == 0 if this.length == that.size + * x > 0 if this.length > that.size + * }}} + * The method as implemented here does not call `length` or `size` directly; its running time + * is `O(this.length min that.size)` instead of `O(this.length + that.size)`. + * The method should be overridden if computing `size` is cheap and `knownSize` returns `-1`. + */ + def lengthCompare(that: Iterable[_]): Int = super.sizeCompare(that) + + /** Returns a value class containing operations for comparing the length of this $coll to a test value. + * + * These operations are implemented in terms of [[lengthCompare(Int) `lengthCompare(Int)`]], and + * allow the following more readable usages: + * + * {{{ + * this.lengthIs < len // this.lengthCompare(len) < 0 + * this.lengthIs <= len // this.lengthCompare(len) <= 0 + * this.lengthIs == len // this.lengthCompare(len) == 0 + * this.lengthIs != len // this.lengthCompare(len) != 0 + * this.lengthIs >= len // this.lengthCompare(len) >= 0 + * this.lengthIs > len // this.lengthCompare(len) > 0 + * }}} + */ + @inline final def lengthIs: IterableOps.SizeCompareOps = new IterableOps.SizeCompareOps(this) + + override def isEmpty: Boolean = lengthCompare(0) == 0 + + /** Checks whether corresponding elements of the given iterable collection + * compare equal (with respect to `==`) to elements of this $coll. + * + * @param that the collection to compare + * @tparam B the type of the elements of collection `that`. + * @return `true` if both collections contain equal elements in the same order, `false` otherwise. + */ + def sameElements[B >: A](that: IterableOnce[B]): Boolean = { + val thisKnownSize = knownSize + if (thisKnownSize != -1) { + val thatKnownSize = that.knownSize + if (thatKnownSize != -1) { + if (thisKnownSize != thatKnownSize) return false + if (thisKnownSize == 0) return true + } + } + iterator.sameElements(that) + } + + /** Tests whether every element of this $coll relates to the + * corresponding element of another sequence by satisfying a test predicate. + * + * @param that the other sequence + * @param p the test predicate, which relates elements from both sequences + * @tparam B the type of the elements of `that` + * @return `true` if both sequences have the same length and + * `p(x, y)` is `true` for all corresponding elements `x` of this $coll + * and `y` of `that`, otherwise `false`. + */ + def corresponds[B](that: Seq[B])(p: (A, B) => Boolean): Boolean = { + val i = iterator + val j = that.iterator + while (i.hasNext && j.hasNext) + if (!p(i.next(), j.next())) + return false + !i.hasNext && !j.hasNext + } + + /** Computes the multiset difference between this $coll and another sequence. + * + * @param that the sequence of elements to remove + * @return a new $coll which contains all elements of this $coll + * except some of the occurrences of elements that also appear in `that`. + * If an element value `x` appears + * ''n'' times in `that`, then the first ''n'' occurrences of `x` will not form + * part of the result, but any following occurrences will. + */ + def diff[B >: A](that: Seq[B]): C = { + val occ = occCounts(that) + fromSpecific(iterator.filter { x => + var include = false + occ.updateWith(x) { + case None => { + include = true + None + } + case Some(1) => None + case Some(n) => Some(n - 1) + } + include + }) + } + + /** Computes the multiset intersection between this $coll and another sequence. + * + * @param that the sequence of elements to intersect with. + * @return a new $coll which contains all elements of this $coll + * which also appear in `that`. + * If an element value `x` appears + * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained + * in the result, but any following occurrences will be omitted. + */ + def intersect[B >: A](that: Seq[B]): C = { + val occ = occCounts(that) + fromSpecific(iterator.filter { x => + var include = true + occ.updateWith(x) { + case None => { + include = false + None + } + case Some(1) => None + case Some(n) => Some(n - 1) + } + include + }) + } + + /** Produces a new $coll where a slice of elements in this $coll is replaced by another sequence. + * + * Patching at negative indices is the same as patching starting at 0. + * Patching at indices at or larger than the length of the original $coll appends the patch to the end. + * If the `replaced` count would exceed the available elements, the difference in excess is ignored. + * + * @param from the index of the first replaced element + * @param other the replacement sequence + * @param replaced the number of elements to drop in the original $coll + * @tparam B the element type of the returned $coll. + * @return a new $coll consisting of all elements of this $coll + * except that `replaced` elements starting from `from` are replaced + * by all the elements of `other`. + */ + def patch[B >: A](from: Int, other: IterableOnce[B], replaced: Int): CC[B] = + iterableFactory.from(new View.Patched(this, from, other, replaced)) + + /** A copy of this $coll with one single replaced element. + * @param index the position of the replacement + * @param elem the replacing element + * @tparam B the element type of the returned $coll. + * @return a new $coll which is a copy of this $coll with the element at position `index` replaced by `elem`. + * @throws IndexOutOfBoundsException if `index` does not satisfy `0 <= index < length`. In case of a + * lazy collection this exception may be thrown at a later time or not at + * all (if the end of the collection is never evaluated). + */ + def updated[B >: A](index: Int, elem: B): CC[B] = { + if(index < 0) throw new IndexOutOfBoundsException(index.toString) + val k = knownSize + if(k >= 0 && index >= k) throw new IndexOutOfBoundsException(index.toString) + iterableFactory.from(new View.Updated(this, index, elem)) + } + + protected[collection] def occCounts[B](sq: Seq[B]): mutable.Map[B, Int] = { + val occ = new mutable.HashMap[B, Int]() + for (y <- sq) occ.updateWith(y) { + case None => Some(1) + case Some(n) => Some(n + 1) + } + occ + } + + /** Searches this sorted sequence for a specific element. If the sequence is an + * `IndexedSeq`, a binary search is used. Otherwise, a linear search is used. + * + * The sequence should be sorted with the same `Ordering` before calling; otherwise, + * the results are undefined. + * + * @see [[scala.collection.IndexedSeq]] + * @see [[scala.math.Ordering]] + * @see [[scala.collection.SeqOps]], method `sorted` + * + * @param elem the element to find. + * @param ord the ordering to be used to compare elements. + * + * @return a `Found` value containing the index corresponding to the element in the + * sequence, or the `InsertionPoint` where the element would be inserted if + * the element is not in the sequence. + */ + def search[B >: A](elem: B)(implicit ord: Ordering[B]): SearchResult = + linearSearch(view, elem, 0)(ord) + + /** Searches within an interval in this sorted sequence for a specific element. If this + * sequence is an `IndexedSeq`, a binary search is used. Otherwise, a linear search + * is used. + * + * The sequence should be sorted with the same `Ordering` before calling; otherwise, + * the results are undefined. + * + * @see [[scala.collection.IndexedSeq]] + * @see [[scala.math.Ordering]] + * @see [[scala.collection.SeqOps]], method `sorted` + * + * @param elem the element to find. + * @param from the index where the search starts. + * @param to the index following where the search ends. + * @param ord the ordering to be used to compare elements. + * + * @return a `Found` value containing the index corresponding to the element in the + * sequence, or the `InsertionPoint` where the element would be inserted if + * the element is not in the sequence. + * + * @note if `to <= from`, the search space is empty, and an `InsertionPoint` at `from` + * is returned + */ + def search[B >: A](elem: B, from: Int, to: Int) (implicit ord: Ordering[B]): SearchResult = + linearSearch(view.slice(from, to), elem, math.max(0, from))(ord) + + private[this] def linearSearch[B >: A](c: View[A], elem: B, offset: Int) + (implicit ord: Ordering[B]): SearchResult = { + var idx = offset + val it = c.iterator + while (it.hasNext) { + val cur = it.next() + if (ord.equiv(elem, cur)) return Found(idx) + else if (ord.lt(elem, cur)) return InsertionPoint(idx) + idx += 1 + } + InsertionPoint(idx) + } +} + +object SeqOps { + + // KMP search utilities + + /** A KMP implementation, based on the undoubtedly reliable wikipedia entry. + * Note: I made this private to keep it from entering the API. That can be reviewed. + * + * @param S Sequence that may contain target + * @param m0 First index of S to consider + * @param m1 Last index of S to consider (exclusive) + * @param W Target sequence + * @param n0 First index of W to match + * @param n1 Last index of W to match (exclusive) + * @param forward Direction of search (from beginning==true, from end==false) + * @return Index of start of sequence if found, -1 if not (relative to beginning of S, not m0). + */ + private def kmpSearch[B](S: scala.collection.Seq[B], m0: Int, m1: Int, W: scala.collection.Seq[B], n0: Int, n1: Int, forward: Boolean): Int = { + // Check for redundant case when target has single valid element + def clipR(x: Int, y: Int) = if (x < y) x else -1 + def clipL(x: Int, y: Int) = if (x > y) x else -1 + + if (n1 == n0+1) { + if (forward) + clipR(S.indexOf(W(n0), m0), m1) + else + clipL(S.lastIndexOf(W(n0), m1-1), m0-1) + } + + // Check for redundant case when both sequences are same size + else if (m1-m0 == n1-n0) { + // Accepting a little slowness for the uncommon case. + if (S.iterator.slice(m0, m1).sameElements(W.iterator.slice(n0, n1))) m0 + else -1 + } + // Now we know we actually need KMP search, so do it + else S match { + case xs: scala.collection.IndexedSeq[_] => + // We can index into S directly; it should be adequately fast + val Wopt = kmpOptimizeWord(W, n0, n1, forward) + val T = kmpJumpTable(Wopt, n1-n0) + var i, m = 0 + val zero = if (forward) m0 else m1-1 + val delta = if (forward) 1 else -1 + while (i+m < m1-m0) { + if (Wopt(i) == S(zero+delta*(i+m))) { + i += 1 + if (i == n1-n0) return (if (forward) m+m0 else m1-m-i) + } + else { + val ti = T(i) + m += i - ti + if (i > 0) i = ti + } + } + -1 + case _ => + // We had better not index into S directly! + val iter = S.iterator.drop(m0) + val Wopt = kmpOptimizeWord(W, n0, n1, forward = true) + val T = kmpJumpTable(Wopt, n1-n0) + val cache = new Array[AnyRef](n1-n0) // Ring buffer--need a quick way to do a look-behind + var largest = 0 + var i, m = 0 + var answer = -1 + while (m+m0+n1-n0 <= m1) { + while (i+m >= largest) { + cache(largest%(n1-n0)) = iter.next().asInstanceOf[AnyRef] + largest += 1 + } + if (Wopt(i) == cache((i+m)%(n1-n0)).asInstanceOf[B]) { + i += 1 + if (i == n1-n0) { + if (forward) return m+m0 + else { + i -= 1 + answer = m+m0 + val ti = T(i) + m += i - ti + if (i > 0) i = ti + } + } + } + else { + val ti = T(i) + m += i - ti + if (i > 0) i = ti + } + } + answer + } + } + + /** Make sure a target sequence has fast, correctly-ordered indexing for KMP. + * + * @param W The target sequence + * @param n0 The first element in the target sequence that we should use + * @param n1 The far end of the target sequence that we should use (exclusive) + * @return Target packed in an IndexedSeq (taken from iterator unless W already is an IndexedSeq) + */ + private def kmpOptimizeWord[B](W: scala.collection.Seq[B], n0: Int, n1: Int, forward: Boolean): IndexedSeqView[B] = W match { + case iso: IndexedSeq[B] => + // Already optimized for indexing--use original (or custom view of original) + if (forward && n0==0 && n1==W.length) iso.view + else if (forward) new AbstractIndexedSeqView[B] { + val length = n1 - n0 + def apply(x: Int) = iso(n0 + x) + } + else new AbstractIndexedSeqView[B] { + def length = n1 - n0 + def apply(x: Int) = iso(n1 - 1 - x) + } + case _ => + // W is probably bad at indexing. Pack in array (in correct orientation) + // Would be marginally faster to special-case each direction + new AbstractIndexedSeqView[B] { + private[this] val Warr = new Array[AnyRef](n1-n0) + private[this] val delta = if (forward) 1 else -1 + private[this] val done = if (forward) n1-n0 else -1 + val wit = W.iterator.drop(n0) + var i = if (forward) 0 else (n1-n0-1) + while (i != done) { + Warr(i) = wit.next().asInstanceOf[AnyRef] + i += delta + } + + val length = n1 - n0 + def apply(x: Int) = Warr(x).asInstanceOf[B] + } + } + + /** Make a jump table for KMP search. + * + * @param Wopt The target sequence + * @param wlen Just in case we're only IndexedSeq and not IndexedSeqOptimized + * @return KMP jump table for target sequence + */ + private def kmpJumpTable[B](Wopt: IndexedSeqView[B], wlen: Int) = { + val arr = new Array[Int](wlen) + var pos = 2 + var cnd = 0 + arr(0) = -1 + arr(1) = 0 + while (pos < wlen) { + if (Wopt(pos-1) == Wopt(cnd)) { + arr(pos) = cnd + 1 + pos += 1 + cnd += 1 + } + else if (cnd > 0) { + cnd = arr(cnd) + } + else { + arr(pos) = 0 + pos += 1 + } + } + arr + } +} + +/** Explicit instantiation of the `Seq` trait to reduce class file size in subclasses. */ +abstract class AbstractSeq[+A] extends AbstractIterable[A] with Seq[A] diff --git a/library/src/scala/collection/SeqMap.scala b/library/src/scala/collection/SeqMap.scala new file mode 100644 index 000000000000..17d187975af2 --- /dev/null +++ b/library/src/scala/collection/SeqMap.scala @@ -0,0 +1,41 @@ +/* + * 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.collection + +import scala.language.`2.13` +import scala.annotation.nowarn + +/** + * A generic trait for ordered maps. Concrete classes have to provide + * functionality for the abstract methods in `SeqMap`. + * + * Note that when checking for equality [[SeqMap]] does not take into account + * ordering. + * + * @tparam K the type of the keys contained in this linked map. + * @tparam V the type of the values associated with the keys in this linked map. + * @define coll immutable seq map + * @define Coll `immutable.SeqMap` + */ + +trait SeqMap[K, +V] extends Map[K, V] + with MapOps[K, V, SeqMap, SeqMap[K, V]] + with MapFactoryDefaults[K, V, SeqMap, Iterable] { + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "SeqMap" + + override def mapFactory: MapFactory[SeqMap] = SeqMap +} + +object SeqMap extends MapFactory.Delegate[immutable.SeqMap](immutable.SeqMap) + diff --git a/library/src/scala/collection/SeqView.scala b/library/src/scala/collection/SeqView.scala new file mode 100644 index 000000000000..3f6a4c5c3ea4 --- /dev/null +++ b/library/src/scala/collection/SeqView.scala @@ -0,0 +1,215 @@ +/* + * 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 collection + +import scala.language.`2.13` +import scala.annotation.nowarn +import scala.collection.generic.CommonErrors + + +trait SeqView[+A] extends SeqOps[A, View, View[A]] with View[A] { + override def view: SeqView[A] = this + + override def map[B](f: A => B): SeqView[B] = new SeqView.Map(this, f) + override def appended[B >: A](elem: B): SeqView[B] = new SeqView.Appended(this, elem) + override def prepended[B >: A](elem: B): SeqView[B] = new SeqView.Prepended(elem, this) + override def reverse: SeqView[A] = new SeqView.Reverse(this) + override def take(n: Int): SeqView[A] = new SeqView.Take(this, n) + override def drop(n: Int): SeqView[A] = new SeqView.Drop(this, n) + override def takeRight(n: Int): SeqView[A] = new SeqView.TakeRight(this, n) + override def dropRight(n: Int): SeqView[A] = new SeqView.DropRight(this, n) + override def tapEach[U](f: A => U): SeqView[A] = new SeqView.Map(this, { (a: A) => f(a); a }) + + def concat[B >: A](suffix: SeqView.SomeSeqOps[B]): SeqView[B] = new SeqView.Concat(this, suffix) + def appendedAll[B >: A](suffix: SeqView.SomeSeqOps[B]): SeqView[B] = new SeqView.Concat(this, suffix) + def prependedAll[B >: A](prefix: SeqView.SomeSeqOps[B]): SeqView[B] = new SeqView.Concat(prefix, this) + + override def sorted[B >: A](implicit ord: Ordering[B]): SeqView[A] = new SeqView.Sorted(this, ord) + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "SeqView" +} + +object SeqView { + + /** A `SeqOps` whose collection type and collection type constructor are unknown */ + private type SomeSeqOps[+A] = SeqOps[A, AnyConstr, _] + + /** A view that doesn’t apply any transformation to an underlying sequence */ + @SerialVersionUID(3L) + class Id[+A](underlying: SomeSeqOps[A]) extends AbstractSeqView[A] { + def apply(idx: Int): A = underlying.apply(idx) + def length: Int = underlying.length + def iterator: Iterator[A] = underlying.iterator + override def knownSize: Int = underlying.knownSize + override def isEmpty: Boolean = underlying.isEmpty + } + + @SerialVersionUID(3L) + class Map[+A, +B](underlying: SomeSeqOps[A], f: A => B) extends View.Map[A, B](underlying, f) with SeqView[B] { + def apply(idx: Int): B = f(underlying(idx)) + def length: Int = underlying.length + } + + @SerialVersionUID(3L) + class Appended[+A](underlying: SomeSeqOps[A], elem: A) extends View.Appended(underlying, elem) with SeqView[A] { + def apply(idx: Int): A = if (idx == underlying.length) elem else underlying(idx) + def length: Int = underlying.length + 1 + } + + @SerialVersionUID(3L) + class Prepended[+A](elem: A, underlying: SomeSeqOps[A]) extends View.Prepended(elem, underlying) with SeqView[A] { + def apply(idx: Int): A = if (idx == 0) elem else underlying(idx - 1) + def length: Int = underlying.length + 1 + } + + @SerialVersionUID(3L) + class Concat[A](prefix: SomeSeqOps[A], suffix: SomeSeqOps[A]) extends View.Concat[A](prefix, suffix) with SeqView[A] { + def apply(idx: Int): A = { + val l = prefix.length + if (idx < l) prefix(idx) else suffix(idx - l) + } + def length: Int = prefix.length + suffix.length + } + + @SerialVersionUID(3L) + class Reverse[A](underlying: SomeSeqOps[A]) extends AbstractSeqView[A] { + def apply(i: Int) = underlying.apply(size - 1 - i) + def length = underlying.size + def iterator: Iterator[A] = underlying.reverseIterator + override def knownSize: Int = underlying.knownSize + override def isEmpty: Boolean = underlying.isEmpty + } + + @SerialVersionUID(3L) + class Take[+A](underlying: SomeSeqOps[A], n: Int) extends View.Take(underlying, n) with SeqView[A] { + def apply(idx: Int): A = if (idx < n) { + underlying(idx) + } else { + throw ( + if (underlying.knownSize >= 0) CommonErrors.indexOutOfBounds(index = idx, max = knownSize - 1) + else CommonErrors.indexOutOfBounds(index = idx) + ) + } + def length: Int = underlying.length min normN + } + + @SerialVersionUID(3L) + class TakeRight[+A](underlying: SomeSeqOps[A], n: Int) extends View.TakeRight(underlying, n) with SeqView[A] { + private[this] val delta = (underlying.size - (n max 0)) max 0 + def length = underlying.size - delta + @throws[IndexOutOfBoundsException] + def apply(i: Int) = underlying.apply(i + delta) + } + + @SerialVersionUID(3L) + class Drop[A](underlying: SomeSeqOps[A], n: Int) extends View.Drop[A](underlying, n) with SeqView[A] { + def length = (underlying.size - normN) max 0 + @throws[IndexOutOfBoundsException] + def apply(i: Int) = underlying.apply(i + normN) + override def drop(n: Int): SeqView[A] = new Drop(underlying, this.n + n) + } + + @SerialVersionUID(3L) + class DropRight[A](underlying: SomeSeqOps[A], n: Int) extends View.DropRight[A](underlying, n) with SeqView[A] { + private[this] val len = (underlying.size - (n max 0)) max 0 + def length = len + @throws[IndexOutOfBoundsException] + def apply(i: Int) = underlying.apply(i) + } + + @SerialVersionUID(3L) + class Sorted[A, B >: A] private (private[this] var underlying: SomeSeqOps[A], + private[this] val len: Int, + ord: Ordering[B]) + extends SeqView[A] { + outer => + + // force evaluation immediately by calling `length` so infinite collections + // hang on `sorted`/`sortWith`/`sortBy` rather than on arbitrary method calls + def this(underlying: SomeSeqOps[A], ord: Ordering[B]) = this(underlying, underlying.length, ord) + + @SerialVersionUID(3L) + private[this] class ReverseSorted extends SeqView[A] { + private[this] lazy val _reversed = new SeqView.Reverse(_sorted) + + def apply(i: Int): A = _reversed.apply(i) + def length: Int = len + def iterator: Iterator[A] = Iterator.empty ++ _reversed.iterator // very lazy + override def knownSize: Int = len + override def isEmpty: Boolean = len == 0 + override def to[C1](factory: Factory[A, C1]): C1 = _reversed.to(factory) + override def reverse: SeqView[A] = outer + override protected def reversed: Iterable[A] = outer + + override def sorted[B1 >: A](implicit ord1: Ordering[B1]): SeqView[A] = + if (ord1 == Sorted.this.ord) outer + else if (ord1.isReverseOf(Sorted.this.ord)) this + else new Sorted(elems, len, ord1) + } + + @volatile private[this] var evaluated = false + + private[this] lazy val _sorted: Seq[A] = { + val res = { + val len = this.len + if (len == 0) Nil + else if (len == 1) List(underlying.head) + else { + val arr = new Array[Any](len) // Array[Any] =:= Array[AnyRef] + @annotation.unused val copied = underlying.copyToArray(arr) + //assert(copied == len) + java.util.Arrays.sort(arr.asInstanceOf[Array[AnyRef]], ord.asInstanceOf[Ordering[AnyRef]]) + // casting the Array[AnyRef] to Array[A] and creating an ArraySeq from it + // is safe because: + // - the ArraySeq is immutable, and items that are not of type A + // cannot be added to it + // - we know it only contains items of type A (and if this collection + // contains items of another type, we'd get a CCE anyway) + // - the cast doesn't actually do anything in the runtime because the + // type of A is not known and Array[_] is Array[AnyRef] + immutable.ArraySeq.unsafeWrapArray(arr.asInstanceOf[Array[A]]) + } + } + evaluated = true + underlying = null + res + } + + private[this] def elems: SomeSeqOps[A] = { + val orig = underlying + if (evaluated) _sorted else orig + } + + def apply(i: Int): A = _sorted.apply(i) + def length: Int = len + def iterator: Iterator[A] = Iterator.empty ++ _sorted.iterator // very lazy + override def knownSize: Int = len + override def isEmpty: Boolean = len == 0 + override def to[C1](factory: Factory[A, C1]): C1 = _sorted.to(factory) + override def reverse: SeqView[A] = new ReverseSorted + // we know `_sorted` is either tiny or has efficient random access, + // so this is acceptable for `reversed` + override protected def reversed: Iterable[A] = new ReverseSorted + + override def sorted[B1 >: A](implicit ord1: Ordering[B1]): SeqView[A] = + if (ord1 == this.ord) this + else if (ord1.isReverseOf(this.ord)) reverse + else new Sorted(elems, len, ord1) + } +} + +/** Explicit instantiation of the `SeqView` trait to reduce class file size in subclasses. */ +@SerialVersionUID(3L) +abstract class AbstractSeqView[+A] extends AbstractView[A] with SeqView[A] diff --git a/library/src/scala/collection/Set.scala b/library/src/scala/collection/Set.scala new file mode 100644 index 000000000000..78bf6351c84c --- /dev/null +++ b/library/src/scala/collection/Set.scala @@ -0,0 +1,268 @@ +/* + * 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 collection + +import scala.language.`2.13` +import scala.util.hashing.MurmurHash3 +import java.lang.String + +import scala.annotation.nowarn + +/** Base trait for set collections. + */ +trait Set[A] + extends Iterable[A] + with SetOps[A, Set, Set[A]] + with Equals + with IterableFactoryDefaults[A, Set] { + + def canEqual(that: Any) = true + + /** + * Equality of sets is implemented using the lookup method [[contains]]. This method returns `true` if + * - the argument `that` is a `Set`, + * - the two sets have the same [[size]], and + * - for every `element` this set, `other.contains(element) == true`. + * + * The implementation of `equals` checks the [[canEqual]] method, so subclasses of `Set` can narrow down the equality + * to specific set types. The `Set` implementations in the standard library can all be compared, their `canEqual` + * methods return `true`. + * + * Note: The `equals` method only respects the equality laws (symmetry, transitivity) if the two sets use the same + * element equivalence function in their lookup operation. For example, the element equivalence operation in a + * [[scala.collection.immutable.TreeSet]] is defined by its ordering. Comparing a `TreeSet` with a `HashSet` leads + * to unexpected results if `ordering.equiv(e1, e2)` (used for lookup in `TreeSet`) is different from `e1 == e2` + * (used for lookup in `HashSet`). + * + * {{{ + * scala> import scala.collection.immutable._ + * scala> val ord: Ordering[String] = _ compareToIgnoreCase _ + * + * scala> TreeSet("A")(ord) == HashSet("a") + * val res0: Boolean = false + * + * scala> HashSet("a") == TreeSet("A")(ord) + * val res1: Boolean = true + * }}} + * + * + * @param that The set to which this set is compared + * @return `true` if the two sets are equal according to the description + */ + override def equals(that: Any): Boolean = + (this eq that.asInstanceOf[AnyRef]) || (that match { + case set: Set[A @unchecked] if set.canEqual(this) => + (this.size == set.size) && { + try this.subsetOf(set) + catch { case _: ClassCastException => false } // PR #9565 / scala/bug#12228 + } + case _ => + false + }) + + override def hashCode(): Int = MurmurHash3.setHash(this) + + override def iterableFactory: IterableFactory[Set] = Set + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "Set" + + override def toString(): String = super[Iterable].toString() // Because `Function1` overrides `toString` too +} + +/** Base trait for set operations + * + * @define coll set + * @define Coll `Set` + */ +transparent trait SetOps[A, +CC[_], +C <: SetOps[A, CC, C]] + extends IterableOps[A, CC, C] + with (A => Boolean) { + + def contains(elem: A): Boolean + + /** Tests if some element is contained in this set. + * + * This method is equivalent to `contains`. It allows sets to be interpreted as predicates. + * @param elem the element to test for membership. + * @return `true` if `elem` is contained in this set, `false` otherwise. + */ + @`inline` final def apply(elem: A): Boolean = this.contains(elem) + + /** Tests whether this set is a subset of another set. + * + * @param that the set to test. + * @return `true` if this set is a subset of `that`, i.e. if + * every element of this set is also an element of `that`. + */ + def subsetOf(that: Set[A]): Boolean = this.forall(that) + + /** An iterator over all subsets of this set of the given size. + * If the requested size is impossible, an empty iterator is returned. + * + * @param len the size of the subsets. + * @return the iterator. + */ + def subsets(len: Int): Iterator[C] = { + if (len < 0 || len > size) Iterator.empty + else new SubsetsItr(this.to(IndexedSeq), len) + } + + /** An iterator over all subsets of this set. + * + * @return the iterator. + */ + def subsets(): Iterator[C] = new AbstractIterator[C] { + private[this] val elms = SetOps.this.to(IndexedSeq) + private[this] var len = 0 + private[this] var itr: Iterator[C] = Iterator.empty + + def hasNext = len <= elms.size || itr.hasNext + def next() = { + if (!itr.hasNext) { + if (len > elms.size) Iterator.empty.next() + else { + itr = new SubsetsItr(elms, len) + len += 1 + } + } + + itr.next() + } + } + + /** An Iterator including all subsets containing exactly len elements. + * If the elements in 'This' type is ordered, then the subsets will also be in the same order. + * ListSet(1,2,3).subsets => {{1},{2},{3},{1,2},{1,3},{2,3},{1,2,3}} + * + * $willForceEvaluation + * + */ + private class SubsetsItr(elms: IndexedSeq[A], len: Int) extends AbstractIterator[C] { + private[this] val idxs = Array.range(0, len+1) + private[this] var _hasNext = true + idxs(len) = elms.size + + def hasNext = _hasNext + @throws[NoSuchElementException] + def next(): C = { + if (!hasNext) Iterator.empty.next() + + val buf = newSpecificBuilder + idxs.slice(0, len) foreach (idx => buf += elms(idx)) + val result = buf.result() + + var i = len - 1 + while (i >= 0 && idxs(i) == idxs(i+1)-1) i -= 1 + + if (i < 0) _hasNext = false + else { + idxs(i) += 1 + for (j <- (i+1) until len) + idxs(j) = idxs(j-1) + 1 + } + + result + } + } + + /** Computes the intersection between this set and another set. + * + * @param that the set to intersect with. + * @return a new set consisting of all elements that are both in this + * set and in the given set `that`. + */ + def intersect(that: Set[A]): C = this.filter(that) + + /** Alias for `intersect` */ + @`inline` final def & (that: Set[A]): C = intersect(that) + + /** Computes the difference of this set and another set. + * + * @param that the set of elements to exclude. + * @return a set containing those elements of this + * set that are not also contained in the given set `that`. + */ + def diff(that: Set[A]): C + + /** Alias for `diff` */ + @`inline` final def &~ (that: Set[A]): C = this diff that + + @deprecated("Consider requiring an immutable Set", "2.13.0") + def -- (that: IterableOnce[A]): C = { + val toRemove = that.iterator.to(immutable.Set) + fromSpecific(view.filterNot(toRemove)) + } + + @deprecated("Consider requiring an immutable Set or fall back to Set.diff", "2.13.0") + def - (elem: A): C = diff(Set(elem)) + + @deprecated("Use &- with an explicit collection argument instead of - with varargs", "2.13.0") + def - (elem1: A, elem2: A, elems: A*): C = diff(elems.toSet + elem1 + elem2) + + /** Creates a new $ccoll by adding all elements contained in another collection to this $coll, omitting duplicates. + * + * Example: + * {{{ + * scala> val a = Set(1, 2) concat Set(2, 3) + * a: scala.collection.immutable.Set[Int] = Set(1, 2, 3) + * }}} + * + * @param that the collection containing the elements to add. + * @return a new $coll with the given elements added, omitting duplicates. + */ + def concat(that: collection.IterableOnce[A]): C = this match { + case optimizedSet @ (_ : scala.collection.immutable.Set.Set1[A] | _: scala.collection.immutable.Set.Set2[A] | _: scala.collection.immutable.Set.Set3[A] | _: scala.collection.immutable.Set.Set4[A]) => + // StrictOptimizedSetOps optimization of concat (these Sets cannot extend StrictOptimizedSetOps because of binary-incompatible return type; cf. PR #10036) + var result = optimizedSet.asInstanceOf[scala.collection.immutable.SetOps[A, scala.collection.immutable.Set, scala.collection.immutable.Set[A]]] + val it = that.iterator + while (it.hasNext) result = result + it.next() + result.asInstanceOf[C] + case _ => fromSpecific(that match { + case that: collection.Iterable[A] => new View.Concat(this, that) + case _ => iterator.concat(that.iterator) + }) + } + + @deprecated("Consider requiring an immutable Set or fall back to Set.union", "2.13.0") + def + (elem: A): C = fromSpecific(new View.Appended(this, elem)) + + @deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0") + def + (elem1: A, elem2: A, elems: A*): C = fromSpecific(new View.Concat(new View.Appended(new View.Appended(this, elem1), elem2), elems)) + + /** Alias for `concat` */ + @`inline` final def ++ (that: collection.IterableOnce[A]): C = concat(that) + + /** Computes the union between of set and another set. + * + * @param that the set to form the union with. + * @return a new set consisting of all elements that are in this + * set or in the given set `that`. + */ + @`inline` final def union(that: Set[A]): C = concat(that) + + /** Alias for `union` */ + @`inline` final def | (that: Set[A]): C = concat(that) +} + +/** + * $factoryInfo + * @define coll set + * @define Coll `Set` + */ +@SerialVersionUID(3L) +object Set extends IterableFactory.Delegate[Set](immutable.Set) + +/** Explicit instantiation of the `Set` trait to reduce class file size in subclasses. */ +abstract class AbstractSet[A] extends AbstractIterable[A] with Set[A] diff --git a/library/src/scala/collection/SortedMap.scala b/library/src/scala/collection/SortedMap.scala new file mode 100644 index 000000000000..d4f1aae2b62e --- /dev/null +++ b/library/src/scala/collection/SortedMap.scala @@ -0,0 +1,221 @@ +/* + * 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 collection + +import scala.language.`2.13` +import scala.annotation.{implicitNotFound, nowarn} + +/** A Map whose keys are sorted according to a [[scala.math.Ordering]]*/ +trait SortedMap[K, +V] + extends Map[K, V] + with SortedMapOps[K, V, SortedMap, SortedMap[K, V]] + with SortedMapFactoryDefaults[K, V, SortedMap, Iterable, Map]{ + + def unsorted: Map[K, V] = this + + def sortedMapFactory: SortedMapFactory[SortedMap] = SortedMap + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "SortedMap" + + override def equals(that: Any): Boolean = that match { + case _ if this eq that.asInstanceOf[AnyRef] => true + case sm: SortedMap[K @unchecked, _] if sm.ordering == this.ordering => + (sm canEqual this) && + (this.size == sm.size) && { + val i1 = this.iterator + val i2 = sm.iterator + var allEqual = true + while (allEqual && i1.hasNext) { + val kv1 = i1.next() + val kv2 = i2.next() + allEqual = ordering.equiv(kv1._1, kv2._1) && kv1._2 == kv2._2 + } + allEqual + } + case _ => super.equals(that) + } +} + +transparent trait SortedMapOps[K, +V, +CC[X, Y] <: Map[X, Y] with SortedMapOps[X, Y, CC, _], +C <: SortedMapOps[K, V, CC, C]] + extends MapOps[K, V, Map, C] + with SortedOps[K, C] { + + /** The companion object of this sorted map, providing various factory methods. + * + * @note When implementing a custom collection type and refining `CC` to the new type, this + * method needs to be overridden to return a factory for the new type (the compiler will + * issue an error otherwise). + */ + def sortedMapFactory: SortedMapFactory[CC] + + /** Similar to `mapFromIterable`, but returns a SortedMap collection type. + * Note that the return type is now `CC[K2, V2]`. + */ + @`inline` protected final def sortedMapFromIterable[K2, V2](it: Iterable[(K2, V2)])(implicit ordering: Ordering[K2]): CC[K2, V2] = sortedMapFactory.from(it) + + def unsorted: Map[K, V] + + /** + * Creates an iterator over all the key/value pairs + * contained in this map having a key greater than or + * equal to `start` according to the ordering of + * this map. x.iteratorFrom(y) is equivalent + * to but often more efficient than x.from(y).iterator. + * + * @param start The lower bound (inclusive) + * on the keys to be returned + */ + def iteratorFrom(start: K): Iterator[(K, V)] + + /** + * Creates an iterator over all the keys(or elements) contained in this + * collection greater than or equal to `start` + * according to the ordering of this collection. x.keysIteratorFrom(y) + * is equivalent to but often more efficient than + * x.from(y).keysIterator. + * + * @param start The lower bound (inclusive) + * on the keys to be returned + */ + def keysIteratorFrom(start: K): Iterator[K] + + /** + * Creates an iterator over all the values contained in this + * map that are associated with a key greater than or equal to `start` + * according to the ordering of this map. x.valuesIteratorFrom(y) is + * equivalent to but often more efficient than + * x.from(y).valuesIterator. + * + * @param start The lower bound (inclusive) + * on the keys to be returned + */ + def valuesIteratorFrom(start: K): Iterator[V] = iteratorFrom(start).map(_._2) + + def firstKey: K = head._1 + def lastKey: K = last._1 + + /** Find the element with smallest key larger than or equal to a given key. + * @param key The given key. + * @return `None` if there is no such node. + */ + def minAfter(key: K): Option[(K, V)] = rangeFrom(key).headOption + + /** Find the element with largest key less than a given key. + * @param key The given key. + * @return `None` if there is no such node. + */ + def maxBefore(key: K): Option[(K, V)] = rangeUntil(key).lastOption + + def rangeTo(to: K): C = { + val i = keySet.rangeFrom(to).iterator + if (i.isEmpty) return coll + val next = i.next() + if (ordering.compare(next, to) == 0) + if (i.isEmpty) coll + else rangeUntil(i.next()) + else + rangeUntil(next) + } + + override def keySet: SortedSet[K] = new KeySortedSet + + /** The implementation class of the set returned by `keySet` */ + protected class KeySortedSet extends SortedSet[K] with GenKeySet with GenKeySortedSet { + def diff(that: Set[K]): SortedSet[K] = fromSpecific(view.filterNot(that)) + def rangeImpl(from: Option[K], until: Option[K]): SortedSet[K] = { + val map = SortedMapOps.this.rangeImpl(from, until) + new map.KeySortedSet + } + } + + /** A generic trait that is reused by sorted keyset implementations */ + protected trait GenKeySortedSet extends GenKeySet { this: SortedSet[K] => + implicit def ordering: Ordering[K] = SortedMapOps.this.ordering + def iteratorFrom(start: K): Iterator[K] = SortedMapOps.this.keysIteratorFrom(start) + } + + // And finally, we add new overloads taking an ordering + /** Builds a new sorted map by applying a function to all elements of this $coll. + * + * @param f the function to apply to each element. + * @return a new $coll resulting from applying the given function + * `f` to each element of this $coll and collecting the results. + */ + def map[K2, V2](f: ((K, V)) => (K2, V2))(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = + sortedMapFactory.from(new View.Map[(K, V), (K2, V2)](this, f)) + + /** Builds a new sorted map by applying a function to all elements of this $coll + * and using the elements of the resulting collections. + * + * @param f the function to apply to each element. + * @return a new $coll resulting from applying the given collection-valued function + * `f` to each element of this $coll and concatenating the results. + */ + def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)])(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = + sortedMapFactory.from(new View.FlatMap(this, f)) + + /** Builds a new sorted map by applying a partial function to all elements of this $coll + * on which the function is defined. + * + * @param pf the partial function which filters and maps the $coll. + * @return a new $coll resulting from applying the given partial function + * `pf` to each element on which it is defined and collecting the results. + * The order of the elements is preserved. + */ + def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)])(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = + sortedMapFactory.from(new View.Collect(this, pf)) + + override def concat[V2 >: V](suffix: IterableOnce[(K, V2)]): CC[K, V2] = sortedMapFactory.from(suffix match { + case it: Iterable[(K, V2)] => new View.Concat(this, it) + case _ => iterator.concat(suffix.iterator) + })(using ordering) + + /** Alias for `concat` */ + @`inline` override final def ++ [V2 >: V](xs: IterableOnce[(K, V2)]): CC[K, V2] = concat(xs) + + @deprecated("Consider requiring an immutable Map or fall back to Map.concat", "2.13.0") + override def + [V1 >: V](kv: (K, V1)): CC[K, V1] = sortedMapFactory.from(new View.Appended(this, kv))(using ordering) + + @deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0") + override def + [V1 >: V](elem1: (K, V1), elem2: (K, V1), elems: (K, V1)*): CC[K, V1] = sortedMapFactory.from(new View.Concat(new View.Appended(new View.Appended(this, elem1), elem2), elems))(using ordering) +} + +object SortedMapOps { + private[collection] final val ordMsg = "No implicit Ordering[${K2}] found to build a SortedMap[${K2}, ${V2}]. You may want to upcast to a Map[${K}, ${V}] first by calling `unsorted`." + + /** Specializes `MapWithFilter` for sorted Map collections + * + * @define coll sorted map collection + */ + class WithFilter[K, +V, +IterableCC[_], +MapCC[X, Y] <: Map[X, Y], +CC[X, Y] <: Map[X, Y] with SortedMapOps[X, Y, CC, _]]( + self: SortedMapOps[K, V, CC, _] with MapOps[K, V, MapCC, _] with IterableOps[(K, V), IterableCC, _], + p: ((K, V)) => Boolean + ) extends MapOps.WithFilter[K, V, IterableCC, MapCC](self, p) { + + def map[K2 : Ordering, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = + self.sortedMapFactory.from(new View.Map(filtered, f)) + + def flatMap[K2 : Ordering, V2](f: ((K, V)) => IterableOnce[(K2, V2)]): CC[K2, V2] = + self.sortedMapFactory.from(new View.FlatMap(filtered, f)) + + override def withFilter(q: ((K, V)) => Boolean): WithFilter[K, V, IterableCC, MapCC, CC] = + new WithFilter[K, V, IterableCC, MapCC, CC](self, (kv: (K, V)) => p(kv) && q(kv)) + + } + +} + +@SerialVersionUID(3L) +object SortedMap extends SortedMapFactory.Delegate[SortedMap](immutable.SortedMap) diff --git a/library/src/scala/collection/SortedOps.scala b/library/src/scala/collection/SortedOps.scala new file mode 100644 index 000000000000..4fefcc4038d0 --- /dev/null +++ b/library/src/scala/collection/SortedOps.scala @@ -0,0 +1,91 @@ +/* + * 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.collection + +import scala.language.`2.13` + +/** Base trait for sorted collections */ +transparent trait SortedOps[A, +C] { + + def ordering: Ordering[A] + + /** Returns the first key of the collection. */ + def firstKey: A + + /** Returns the last key of the collection. */ + def lastKey: A + + /** Comparison function that orders keys. */ + @deprecated("Use ordering.compare instead", "2.13.0") + @deprecatedOverriding("Use ordering.compare instead", "2.13.0") + @inline def compare(k0: A, k1: A): Int = ordering.compare(k0, k1) + + /** Creates a ranged projection of this collection. Any mutations in the + * ranged projection will update this collection and vice versa. + * + * Note: keys are not guaranteed to be consistent between this collection + * and the projection. This is the case for buffers where indexing is + * relative to the projection. + * + * @param from The lower-bound (inclusive) of the ranged projection. + * `None` if there is no lower bound. + * @param until The upper-bound (exclusive) of the ranged projection. + * `None` if there is no upper bound. + */ + def rangeImpl(from: Option[A], until: Option[A]): C + + /** Creates a ranged projection of this collection with both a lower-bound + * and an upper-bound. + * + * @param from The lower-bound (inclusive) of the ranged projection. + * @param until The upper-bound (exclusive) of the ranged projection. + */ + def range(from: A, until: A): C = rangeImpl(Some(from), Some(until)) + + /** Creates a ranged projection of this collection with no upper-bound. + * + * @param from The lower-bound (inclusive) of the ranged projection. + */ + @deprecated("Use rangeFrom", "2.13.0") + final def from(from: A): C = rangeFrom(from) + + /** Creates a ranged projection of this collection with no upper-bound. + * + * @param from The lower-bound (inclusive) of the ranged projection. + */ + def rangeFrom(from: A): C = rangeImpl(Some(from), None) + + /** Creates a ranged projection of this collection with no lower-bound. + * + * @param until The upper-bound (exclusive) of the ranged projection. + */ + @deprecated("Use rangeUntil", "2.13.0") + final def until(until: A): C = rangeUntil(until) + + /** Creates a ranged projection of this collection with no lower-bound. + * + * @param until The upper-bound (exclusive) of the ranged projection. + */ + def rangeUntil(until: A): C = rangeImpl(None, Some(until)) + + /** Create a range projection of this collection with no lower-bound. + * @param to The upper-bound (inclusive) of the ranged projection. + */ + @deprecated("Use rangeTo", "2.13.0") + final def to(to: A): C = rangeTo(to) + + /** Create a range projection of this collection with no lower-bound. + * @param to The upper-bound (inclusive) of the ranged projection. + */ + def rangeTo(to: A): C +} diff --git a/library/src/scala/collection/SortedSet.scala b/library/src/scala/collection/SortedSet.scala new file mode 100644 index 000000000000..9290087cf06b --- /dev/null +++ b/library/src/scala/collection/SortedSet.scala @@ -0,0 +1,191 @@ +/* + * 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.collection + +import scala.language.`2.13` +import scala.annotation.{implicitNotFound, nowarn} +import scala.annotation.unchecked.uncheckedVariance + +/** Base type of sorted sets */ +trait SortedSet[A] extends Set[A] + with SortedSetOps[A, SortedSet, SortedSet[A]] + with SortedSetFactoryDefaults[A, SortedSet, Set] { + + def unsorted: Set[A] = this + + def sortedIterableFactory: SortedIterableFactory[SortedSet] = SortedSet + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "SortedSet" + + override def equals(that: Any): Boolean = that match { + case _ if this eq that.asInstanceOf[AnyRef] => true + case ss: SortedSet[A @unchecked] if ss.ordering == this.ordering => + (ss canEqual this) && + (this.size == ss.size) && { + val i1 = this.iterator + val i2 = ss.iterator + var allEqual = true + while (allEqual && i1.hasNext) + allEqual = ordering.equiv(i1.next(), i2.next()) + allEqual + } + case _ => + super.equals(that) + } + +} + +transparent trait SortedSetOps[A, +CC[X] <: SortedSet[X], +C <: SortedSetOps[A, CC, C]] + extends SetOps[A, Set, C] + with SortedOps[A, C] { + + /** The companion object of this sorted set, providing various factory methods. + * + * @note When implementing a custom collection type and refining `CC` to the new type, this + * method needs to be overridden to return a factory for the new type (the compiler will + * issue an error otherwise). + */ + def sortedIterableFactory: SortedIterableFactory[CC] + + /** Widens the type of this set to its unsorted counterpart. */ + def unsorted: Set[A] + + /** + * Creates an iterator that contains all values from this collection + * greater than or equal to `start` according to the ordering of + * this collection. x.iteratorFrom(y) is equivalent to but will usually + * be more efficient than x.from(y).iterator + * + * @param start The lower-bound (inclusive) of the iterator + */ + def iteratorFrom(start: A): Iterator[A] + + @deprecated("Use `iteratorFrom` instead.", "2.13.0") + @`inline` def keysIteratorFrom(start: A): Iterator[A] = iteratorFrom(start) + + def firstKey: A = head + def lastKey: A = last + + /** Find the smallest element larger than or equal to a given key. + * @param key The given key. + * @return `None` if there is no such node. + */ + def minAfter(key: A): Option[A] = rangeFrom(key).headOption + + /** Find the largest element less than a given key. + * @param key The given key. + * @return `None` if there is no such node. + */ + def maxBefore(key: A): Option[A] = rangeUntil(key).lastOption + + override def min[B >: A](implicit ord: Ordering[B]): A = + if (isEmpty) throw new UnsupportedOperationException("empty.min") + else if (ord == ordering) head + else if (ord isReverseOf ordering) last + else super.min[B] // need the type annotation for it to infer the correct implicit + + override def max[B >: A](implicit ord: Ordering[B]): A = + if (isEmpty) throw new UnsupportedOperationException("empty.max") + else if (ord == ordering) last + else if (ord isReverseOf ordering) head + else super.max[B] // need the type annotation for it to infer the correct implicit + + def rangeTo(to: A): C = { + val i = rangeFrom(to).iterator + if (i.isEmpty) return coll + val next = i.next() + if (ordering.compare(next, to) == 0) + if (i.isEmpty) coll + else rangeUntil(i.next()) + else + rangeUntil(next) + } + + /** Builds a new sorted collection by applying a function to all elements of this $coll. + * + * @param f the function to apply to each element. + * @tparam B the element type of the returned collection. + * @return a new $coll resulting from applying the given function + * `f` to each element of this $coll and collecting the results. + */ + def map[B](f: A => B)(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = + sortedIterableFactory.from(new View.Map(this, f)) + + /** Builds a new sorted collection by applying a function to all elements of this $coll + * and using the elements of the resulting collections. + * + * @param f the function to apply to each element. + * @tparam B the element type of the returned collection. + * @return a new $coll resulting from applying the given collection-valued function + * `f` to each element of this $coll and concatenating the results. + */ + def flatMap[B](f: A => IterableOnce[B])(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = + sortedIterableFactory.from(new View.FlatMap(this, f)) + + /** Returns a $coll formed from this $coll and another iterable collection + * by combining corresponding elements in pairs. + * If one of the two collections is longer than the other, its remaining elements are ignored. + * + * @param that The iterable providing the second half of each result pair + * @tparam B the type of the second half of the returned pairs + * @return a new $coll containing pairs consisting of corresponding elements of this $coll and `that`. + * The length of the returned collection is the minimum of the lengths of this $coll and `that`. + */ + def zip[B](that: IterableOnce[B])(implicit @implicitNotFound(SortedSetOps.zipOrdMsg) ev: Ordering[(A @uncheckedVariance, B)]): CC[(A @uncheckedVariance, B)] = // sound bcs of VarianceNote + sortedIterableFactory.from(that match { + case that: Iterable[B] => new View.Zip(this, that) + case _ => iterator.zip(that) + }) + + /** Builds a new sorted collection by applying a partial function to all elements of this $coll + * on which the function is defined. + * + * @param pf the partial function which filters and maps the $coll. + * @tparam B the element type of the returned collection. + * @return a new $coll resulting from applying the given partial function + * `pf` to each element on which it is defined and collecting the results. + * The order of the elements is preserved. + */ + def collect[B](pf: scala.PartialFunction[A, B])(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = + sortedIterableFactory.from(new View.Collect(this, pf)) +} + +object SortedSetOps { + private[collection] final val ordMsg = "No implicit Ordering[${B}] found to build a SortedSet[${B}]. You may want to upcast to a Set[${A}] first by calling `unsorted`." + private[collection] final val zipOrdMsg = "No implicit Ordering[${B}] found to build a SortedSet[(${A}, ${B})]. You may want to upcast to a Set[${A}] first by calling `unsorted`." + + /** Specialize `WithFilter` for sorted collections + * + * @define coll sorted collection + */ + class WithFilter[+A, +IterableCC[_], +CC[X] <: SortedSet[X]]( + self: SortedSetOps[A, CC, _] with IterableOps[A, IterableCC, _], + p: A => Boolean + ) extends IterableOps.WithFilter[A, IterableCC](self, p) { + + def map[B : Ordering](f: A => B): CC[B] = + self.sortedIterableFactory.from(new View.Map(filtered, f)) + + def flatMap[B : Ordering](f: A => IterableOnce[B]): CC[B] = + self.sortedIterableFactory.from(new View.FlatMap(filtered, f)) + + override def withFilter(q: A => Boolean): WithFilter[A, IterableCC, CC] = + new WithFilter[A, IterableCC, CC](self, (a: A) => p(a) && q(a)) + } + +} + +@SerialVersionUID(3L) +object SortedSet extends SortedIterableFactory.Delegate[SortedSet](immutable.SortedSet) + diff --git a/library/src/scala/collection/Stepper.scala b/library/src/scala/collection/Stepper.scala new file mode 100644 index 000000000000..8755142d7209 --- /dev/null +++ b/library/src/scala/collection/Stepper.scala @@ -0,0 +1,369 @@ +/* + * 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.collection + +import scala.language.`2.13` +import java.util.function.{Consumer, DoubleConsumer, IntConsumer, LongConsumer} +import java.util.{PrimitiveIterator, Spliterator, Iterator => JIterator} +import java.{lang => jl} + +import scala.collection.Stepper.EfficientSplit + +/** Steppers exist to enable creating Java streams over Scala collections, see + * [[scala.jdk.StreamConverters]]. Besides that use case, they allow iterating over collections + * holding unboxed primitives (e.g., `Array[Int]`) without boxing the elements. + * + * Steppers have an iterator-like interface with methods `hasStep` and `nextStep()`. The difference + * to iterators - and the reason `Stepper` is not a subtype of `Iterator` - is that there are + * hand-specialized variants of `Stepper` for `Int`, `Long` and `Double` ([[IntStepper]], etc.). + * These enable iterating over collections holding unboxed primitives (e.g., Arrays, + * [[scala.jdk.Accumulator]]s) without boxing the elements. + * + * The selection of primitive types (`Int`, `Long` and `Double`) matches the hand-specialized + * variants of Java Streams ([[java.util.stream.Stream]], [[java.util.stream.IntStream]], etc.) + * and the corresponding Java Spliterators ([[java.util.Spliterator]], [[java.util.Spliterator.OfInt]], etc.). + * + * Steppers can be converted to Scala Iterators, Java Iterators and Java Spliterators. Primitive + * Steppers are converted to the corresponding primitive Java Iterators and Spliterators. + * + * @tparam A the element type of the Stepper + */ +trait Stepper[@specialized(Double, Int, Long) +A] { + /** Check if there's an element available. */ + def hasStep: Boolean + + /** Return the next element and advance the stepper */ + def nextStep(): A + + /** Split this stepper, if applicable. The elements of the current Stepper are split up between + * the resulting Stepper and the current stepper. + * + * May return `null`, in which case the current Stepper yields the same elements as before. + * + * See method `trySplit` in [[java.util.Spliterator]]. + */ + def trySplit(): Stepper[A] + + /** Returns an estimate of the number of elements of this Stepper, or [[Long.MaxValue]]. See + * method `estimateSize` in [[java.util.Spliterator]]. + */ + def estimateSize: Long + + /** Returns a set of characteristics of this Stepper and its elements. See method + * `characteristics` in [[java.util.Spliterator]]. + */ + def characteristics: Int + + /** Returns a [[java.util.Spliterator]] corresponding to this Stepper. + * + * Note that the return type is `Spliterator[_]` instead of `Spliterator[A]` to allow returning + * a [[java.util.Spliterator.OfInt]] (which is a `Spliterator[Integer]`) in the subclass [[IntStepper]] + * (which is a `Stepper[Int]`). + */ + def spliterator[B >: A]: Spliterator[_] + + /** Returns a Java [[java.util.Iterator]] corresponding to this Stepper. + * + * Note that the return type is `Iterator[_]` instead of `Iterator[A]` to allow returning + * a [[java.util.PrimitiveIterator.OfInt]] (which is a `Iterator[Integer]`) in the subclass + * [[IntStepper]] (which is a `Stepper[Int]`). + */ + def javaIterator[B >: A]: JIterator[_] + + /** Returns an [[Iterator]] corresponding to this Stepper. Note that Iterators corresponding to + * primitive Steppers box the elements. + */ + def iterator: Iterator[A] = new AbstractIterator[A] { + def hasNext: Boolean = hasStep + def next(): A = nextStep() + } +} + +object Stepper { + /** A marker trait that indicates that a `Stepper` can call `trySplit` with at worst O(log N) time + * and space complexity, and that the division is likely to be reasonably even. Steppers marked + * with `EfficientSplit` can be converted to parallel streams with the `asJavaParStream` method + * defined in [[scala.jdk.StreamConverters]]. + */ + trait EfficientSplit + + private[collection] final def throwNSEE(): Nothing = throw new NoSuchElementException("Empty Stepper") + + /* These adapter classes can wrap an AnyStepper of a numeric type into a possibly widened primitive Stepper type. + * This provides a basis for more efficient stream processing on unboxed values provided that the original source + * of the data is boxed. In other cases native implementations of the primitive stepper types should be provided + * (see for example IntArrayStepper and WidenedByteArrayStepper). */ + + private[collection] class UnboxingDoubleStepper(st: AnyStepper[Double]) extends DoubleStepper { + def hasStep: Boolean = st.hasStep + def nextStep(): Double = st.nextStep() + def estimateSize: Long = st.estimateSize + def characteristics: Int = st.characteristics + def trySplit(): DoubleStepper = { + val s = st.trySplit() + if (s == null) null else new UnboxingDoubleStepper(s) + } + } + + private[collection] class UnboxingIntStepper(st: AnyStepper[Int]) extends IntStepper { + def hasStep: Boolean = st.hasStep + def nextStep(): Int = st.nextStep() + def estimateSize: Long = st.estimateSize + def characteristics: Int = st.characteristics + def trySplit(): IntStepper = { + val s = st.trySplit() + if (s == null) null else new UnboxingIntStepper(s) + } + } + + private[collection] class UnboxingLongStepper(st: AnyStepper[Long]) extends LongStepper { + def hasStep: Boolean = st.hasStep + def nextStep(): Long = st.nextStep() + def estimateSize: Long = st.estimateSize + def characteristics: Int = st.characteristics + def trySplit(): LongStepper = { + val s = st.trySplit() + if (s == null) null else new UnboxingLongStepper(s) + } + } + + private[collection] class UnboxingByteStepper(st: AnyStepper[Byte]) extends IntStepper { + def hasStep: Boolean = st.hasStep + def nextStep(): Int = st.nextStep() + def estimateSize: Long = st.estimateSize + def characteristics: Int = st.characteristics + def trySplit(): IntStepper = { + val s = st.trySplit() + if (s == null) null else new UnboxingByteStepper(s) + } + } + + private[collection] class UnboxingCharStepper(st: AnyStepper[Char]) extends IntStepper { + def hasStep: Boolean = st.hasStep + def nextStep(): Int = st.nextStep() + def estimateSize: Long = st.estimateSize + def characteristics: Int = st.characteristics + def trySplit(): IntStepper = { + val s = st.trySplit() + if (s == null) null else new UnboxingCharStepper(s) + } + } + + private[collection] class UnboxingShortStepper(st: AnyStepper[Short]) extends IntStepper { + def hasStep: Boolean = st.hasStep + def nextStep(): Int = st.nextStep() + def estimateSize: Long = st.estimateSize + def characteristics: Int = st.characteristics + def trySplit(): IntStepper = { + val s = st.trySplit() + if (s == null) null else new UnboxingShortStepper(s) + } + } + + private[collection] class UnboxingFloatStepper(st: AnyStepper[Float]) extends DoubleStepper { + def hasStep: Boolean = st.hasStep + def nextStep(): Double = st.nextStep() + def estimateSize: Long = st.estimateSize + def characteristics: Int = st.characteristics + def trySplit(): DoubleStepper = { + val s = st.trySplit() + if (s == null) null else new UnboxingFloatStepper(s) + } + } +} + +/** A Stepper for arbitrary element types. See [[Stepper]]. */ +trait AnyStepper[+A] extends Stepper[A] { + def trySplit(): AnyStepper[A] + + def spliterator[B >: A]: Spliterator[B] = new AnyStepper.AnyStepperSpliterator(this) + + def javaIterator[B >: A]: JIterator[B] = new JIterator[B] { + def hasNext: Boolean = hasStep + def next(): B = nextStep() + } +} + +object AnyStepper { + class AnyStepperSpliterator[A](s: AnyStepper[A]) extends Spliterator[A] { + def tryAdvance(c: Consumer[_ >: A]): Boolean = + if (s.hasStep) { c.accept(s.nextStep()); true } else false + def trySplit(): Spliterator[A] = { + val sp = s.trySplit() + if (sp == null) null else sp.spliterator + } + def estimateSize(): Long = s.estimateSize + def characteristics(): Int = s.characteristics + // Override for efficiency: implement with hasStep / nextStep instead of tryAdvance + override def forEachRemaining(c: Consumer[_ >: A]): Unit = + while (s.hasStep) { c.accept(s.nextStep()) } + } + + def ofSeqDoubleStepper(st: DoubleStepper): AnyStepper[Double] = new BoxedDoubleStepper(st) + def ofParDoubleStepper(st: DoubleStepper with EfficientSplit): AnyStepper[Double] with EfficientSplit = new BoxedDoubleStepper(st) with EfficientSplit + + def ofSeqIntStepper(st: IntStepper): AnyStepper[Int] = new BoxedIntStepper(st) + def ofParIntStepper(st: IntStepper with EfficientSplit): AnyStepper[Int] with EfficientSplit = new BoxedIntStepper(st) with EfficientSplit + + def ofSeqLongStepper(st: LongStepper): AnyStepper[Long] = new BoxedLongStepper(st) + def ofParLongStepper(st: LongStepper with EfficientSplit): AnyStepper[Long] with EfficientSplit = new BoxedLongStepper(st) with EfficientSplit + + private[collection] class BoxedDoubleStepper(st: DoubleStepper) extends AnyStepper[Double] { + def hasStep: Boolean = st.hasStep + def nextStep(): Double = st.nextStep() + def estimateSize: Long = st.estimateSize + def characteristics: Int = st.characteristics + def trySplit(): AnyStepper[Double] = { + val s = st.trySplit() + if (s == null) null else new BoxedDoubleStepper(s) + } + } + + private[collection] class BoxedIntStepper(st: IntStepper) extends AnyStepper[Int] { + def hasStep: Boolean = st.hasStep + def nextStep(): Int = st.nextStep() + def estimateSize: Long = st.estimateSize + def characteristics: Int = st.characteristics + def trySplit(): AnyStepper[Int] = { + val s = st.trySplit() + if (s == null) null else new BoxedIntStepper(s) + } + } + + private[collection] class BoxedLongStepper(st: LongStepper) extends AnyStepper[Long] { + def hasStep: Boolean = st.hasStep + def nextStep(): Long = st.nextStep() + def estimateSize: Long = st.estimateSize + def characteristics: Int = st.characteristics + def trySplit(): AnyStepper[Long] = { + val s = st.trySplit() + if (s == null) null else new BoxedLongStepper(s) + } + } +} + +/** A Stepper for Ints. See [[Stepper]]. */ +trait IntStepper extends Stepper[Int] { + def trySplit(): IntStepper + + def spliterator[B >: Int]: Spliterator.OfInt = new IntStepper.IntStepperSpliterator(this) + + def javaIterator[B >: Int]: PrimitiveIterator.OfInt = new PrimitiveIterator.OfInt { + def hasNext: Boolean = hasStep + def nextInt(): Int = nextStep() + } +} +object IntStepper { + class IntStepperSpliterator(s: IntStepper) extends Spliterator.OfInt { + def tryAdvance(c: IntConsumer): Boolean = + if (s.hasStep) { c.accept(s.nextStep()); true } else false + // Override for efficiency: don't wrap the function and call the `tryAdvance` overload + override def tryAdvance(c: Consumer[_ >: jl.Integer]): Boolean = (c: AnyRef) match { + case ic: IntConsumer => tryAdvance(ic) + case _ => if (s.hasStep) { c.accept(jl.Integer.valueOf(s.nextStep())); true } else false + } + // override required for dotty#6152 + override def trySplit(): Spliterator.OfInt = { + val sp = s.trySplit() + if (sp == null) null else sp.spliterator + } + def estimateSize(): Long = s.estimateSize + def characteristics(): Int = s.characteristics + // Override for efficiency: implement with hasStep / nextStep instead of tryAdvance + override def forEachRemaining(c: IntConsumer): Unit = + while (s.hasStep) { c.accept(s.nextStep()) } + // Override for efficiency: implement with hasStep / nextStep instead of tryAdvance + override def forEachRemaining(c: Consumer[_ >: jl.Integer]): Unit = (c: AnyRef) match { + case ic: IntConsumer => forEachRemaining(ic) + case _ => while (s.hasStep) { c.accept(jl.Integer.valueOf(s.nextStep())) } + } + } +} + +/** A Stepper for Doubles. See [[Stepper]]. */ +trait DoubleStepper extends Stepper[Double] { + def trySplit(): DoubleStepper + + def spliterator[B >: Double]: Spliterator.OfDouble = new DoubleStepper.DoubleStepperSpliterator(this) + + def javaIterator[B >: Double]: PrimitiveIterator.OfDouble = new PrimitiveIterator.OfDouble { + def hasNext: Boolean = hasStep + def nextDouble(): Double = nextStep() + } +} + +object DoubleStepper { + class DoubleStepperSpliterator(s: DoubleStepper) extends Spliterator.OfDouble { + def tryAdvance(c: DoubleConsumer): Boolean = + if (s.hasStep) { c.accept(s.nextStep()); true } else false + // Override for efficiency: don't wrap the function and call the `tryAdvance` overload + override def tryAdvance(c: Consumer[_ >: jl.Double]): Boolean = (c: AnyRef) match { + case ic: DoubleConsumer => tryAdvance(ic) + case _ => if (s.hasStep) { c.accept(java.lang.Double.valueOf(s.nextStep())); true } else false + } + // override required for dotty#6152 + override def trySplit(): Spliterator.OfDouble = { + val sp = s.trySplit() + if (sp == null) null else sp.spliterator + } + def estimateSize(): Long = s.estimateSize + def characteristics(): Int = s.characteristics + // Override for efficiency: implement with hasStep / nextStep instead of tryAdvance + override def forEachRemaining(c: DoubleConsumer): Unit = + while (s.hasStep) { c.accept(s.nextStep()) } + // Override for efficiency: implement with hasStep / nextStep instead of tryAdvance + override def forEachRemaining(c: Consumer[_ >: jl.Double]): Unit = (c: AnyRef) match { + case ic: DoubleConsumer => forEachRemaining(ic) + case _ => while (s.hasStep) { c.accept(jl.Double.valueOf(s.nextStep())) } + } + } +} + +/** A Stepper for Longs. See [[Stepper]]. */ +trait LongStepper extends Stepper[Long] { + def trySplit(): LongStepper + + def spliterator[B >: Long]: Spliterator.OfLong = new LongStepper.LongStepperSpliterator(this) + + def javaIterator[B >: Long]: PrimitiveIterator.OfLong = new PrimitiveIterator.OfLong { + def hasNext: Boolean = hasStep + def nextLong(): Long = nextStep() + } +} + +object LongStepper { + class LongStepperSpliterator(s: LongStepper) extends Spliterator.OfLong { + def tryAdvance(c: LongConsumer): Boolean = + if (s.hasStep) { c.accept(s.nextStep()); true } else false + // Override for efficiency: don't wrap the function and call the `tryAdvance` overload + override def tryAdvance(c: Consumer[_ >: jl.Long]): Boolean = (c: AnyRef) match { + case ic: LongConsumer => tryAdvance(ic) + case _ => if (s.hasStep) { c.accept(java.lang.Long.valueOf(s.nextStep())); true } else false + } + // override required for dotty#6152 + override def trySplit(): Spliterator.OfLong = { + val sp = s.trySplit() + if (sp == null) null else sp.spliterator + } + def estimateSize(): Long = s.estimateSize + def characteristics(): Int = s.characteristics + // Override for efficiency: implement with hasStep / nextStep instead of tryAdvance + override def forEachRemaining(c: LongConsumer): Unit = + while (s.hasStep) { c.accept(s.nextStep()) } + // Override for efficiency: implement with hasStep / nextStep instead of tryAdvance + override def forEachRemaining(c: Consumer[_ >: jl.Long]): Unit = (c: AnyRef) match { + case ic: LongConsumer => forEachRemaining(ic) + case _ => while (s.hasStep) { c.accept(jl.Long.valueOf(s.nextStep())) } + } + } +} diff --git a/library/src/scala/collection/StepperShape.scala b/library/src/scala/collection/StepperShape.scala new file mode 100644 index 000000000000..ec193cc27fc2 --- /dev/null +++ b/library/src/scala/collection/StepperShape.scala @@ -0,0 +1,115 @@ +/* + * 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.collection + +import java.{lang => jl} + +import scala.language.`2.13` +import scala.collection.Stepper.EfficientSplit + +/** An implicit StepperShape instance is used in the [[IterableOnce.stepper]] to return a possibly + * specialized Stepper `S` according to the element type `T`. + */ +sealed trait StepperShape[-T, S <: Stepper[_]] { + /** Return the Int constant (as defined in the `StepperShape` companion object) for this `StepperShape`. */ + def shape: StepperShape.Shape + + /** Create an unboxing primitive sequential Stepper from a boxed `AnyStepper`. + * This is an identity operation for reference shapes. */ + def seqUnbox(st: AnyStepper[T]): S + + /** Create an unboxing primitive parallel (i.e. `with EfficientSplit`) Stepper from a boxed `AnyStepper`. + * This is an identity operation for reference shapes. */ + def parUnbox(st: AnyStepper[T] with EfficientSplit): S with EfficientSplit +} + +object StepperShape extends StepperShapeLowPriority1 { + class Shape private[StepperShape] (private val s: Int) extends AnyVal + + // reference + val ReferenceShape = new Shape(0) + + // primitive + val IntShape = new Shape(1) + val LongShape = new Shape(2) + val DoubleShape = new Shape(3) + + // widening + val ByteShape = new Shape(4) + val ShortShape = new Shape(5) + val CharShape = new Shape(6) + val FloatShape = new Shape(7) + + implicit val intStepperShape: StepperShape[Int, IntStepper] = new StepperShape[Int, IntStepper] { + def shape = IntShape + def seqUnbox(st: AnyStepper[Int]): IntStepper = new Stepper.UnboxingIntStepper(st) + def parUnbox(st: AnyStepper[Int] with EfficientSplit): IntStepper with EfficientSplit = new Stepper.UnboxingIntStepper(st) with EfficientSplit + } + implicit val jIntegerStepperShape: StepperShape[jl.Integer, IntStepper] = intStepperShape.asInstanceOf[StepperShape[jl.Integer, IntStepper]] + + implicit val longStepperShape: StepperShape[Long, LongStepper] = new StepperShape[Long, LongStepper] { + def shape = LongShape + def seqUnbox(st: AnyStepper[Long]): LongStepper = new Stepper.UnboxingLongStepper(st) + def parUnbox(st: AnyStepper[Long] with EfficientSplit): LongStepper with EfficientSplit = new Stepper.UnboxingLongStepper(st) with EfficientSplit + } + implicit val jLongStepperShape: StepperShape[jl.Long, LongStepper] = longStepperShape.asInstanceOf[StepperShape[jl.Long, LongStepper]] + + implicit val doubleStepperShape: StepperShape[Double, DoubleStepper] = new StepperShape[Double, DoubleStepper] { + def shape = DoubleShape + def seqUnbox(st: AnyStepper[Double]): DoubleStepper = new Stepper.UnboxingDoubleStepper(st) + def parUnbox(st: AnyStepper[Double] with EfficientSplit): DoubleStepper with EfficientSplit = new Stepper.UnboxingDoubleStepper(st) with EfficientSplit + } + implicit val jDoubleStepperShape: StepperShape[jl.Double, DoubleStepper] = doubleStepperShape.asInstanceOf[StepperShape[jl.Double, DoubleStepper]] + + implicit val byteStepperShape: StepperShape[Byte, IntStepper] = new StepperShape[Byte, IntStepper] { + def shape = ByteShape + def seqUnbox(st: AnyStepper[Byte]): IntStepper = new Stepper.UnboxingByteStepper(st) + def parUnbox(st: AnyStepper[Byte] with EfficientSplit): IntStepper with EfficientSplit = new Stepper.UnboxingByteStepper(st) with EfficientSplit + } + implicit val jByteStepperShape: StepperShape[jl.Byte, IntStepper] = byteStepperShape.asInstanceOf[StepperShape[jl.Byte, IntStepper]] + + implicit val shortStepperShape: StepperShape[Short, IntStepper] = new StepperShape[Short, IntStepper] { + def shape = ShortShape + def seqUnbox(st: AnyStepper[Short]): IntStepper = new Stepper.UnboxingShortStepper(st) + def parUnbox(st: AnyStepper[Short] with EfficientSplit): IntStepper with EfficientSplit = new Stepper.UnboxingShortStepper(st) with EfficientSplit + } + implicit val jShortStepperShape: StepperShape[jl.Short, IntStepper] = shortStepperShape.asInstanceOf[StepperShape[jl.Short, IntStepper]] + + implicit val charStepperShape: StepperShape[Char, IntStepper] = new StepperShape[Char, IntStepper] { + def shape = CharShape + def seqUnbox(st: AnyStepper[Char]): IntStepper = new Stepper.UnboxingCharStepper(st) + def parUnbox(st: AnyStepper[Char] with EfficientSplit): IntStepper with EfficientSplit = new Stepper.UnboxingCharStepper(st) with EfficientSplit + } + implicit val jCharacterStepperShape: StepperShape[jl.Character, IntStepper] = charStepperShape.asInstanceOf[StepperShape[jl.Character, IntStepper]] + + implicit val floatStepperShape: StepperShape[Float, DoubleStepper] = new StepperShape[Float, DoubleStepper] { + def shape = FloatShape + def seqUnbox(st: AnyStepper[Float]): DoubleStepper = new Stepper.UnboxingFloatStepper(st) + def parUnbox(st: AnyStepper[Float] with EfficientSplit): DoubleStepper with EfficientSplit = new Stepper.UnboxingFloatStepper(st) with EfficientSplit + } + implicit val jFloatStepperShape: StepperShape[jl.Float, DoubleStepper] = floatStepperShape.asInstanceOf[StepperShape[jl.Float, DoubleStepper]] +} + +trait StepperShapeLowPriority1 extends StepperShapeLowPriority2 { + implicit def anyStepperShape[T]: StepperShape[T, AnyStepper[T]] = anyStepperShapePrototype.asInstanceOf[StepperShape[T, AnyStepper[T]]] +} + +trait StepperShapeLowPriority2 { + implicit def baseStepperShape[T]: StepperShape[T, Stepper[T]] = anyStepperShapePrototype.asInstanceOf[StepperShape[T, Stepper[T]]] + + protected val anyStepperShapePrototype: StepperShape[AnyRef, Stepper[AnyRef]] = new StepperShape[AnyRef, Stepper[AnyRef]] { + def shape = StepperShape.ReferenceShape + def seqUnbox(st: AnyStepper[AnyRef]): Stepper[AnyRef] = st + def parUnbox(st: AnyStepper[AnyRef] with EfficientSplit): Stepper[AnyRef] with EfficientSplit = st + } +} \ No newline at end of file diff --git a/library/src/scala/collection/StrictOptimizedIterableOps.scala b/library/src/scala/collection/StrictOptimizedIterableOps.scala new file mode 100644 index 000000000000..81c8a79aedf4 --- /dev/null +++ b/library/src/scala/collection/StrictOptimizedIterableOps.scala @@ -0,0 +1,285 @@ +/* + * 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 collection + +import scala.language.`2.13` +import scala.annotation.nowarn +import scala.annotation.unchecked.uncheckedVariance +import scala.runtime.Statics + +/** + * Trait that overrides iterable operations to take advantage of strict builders. + * + * @tparam A Elements type + * @tparam CC Collection type constructor + * @tparam C Collection type + */ +transparent trait StrictOptimizedIterableOps[+A, +CC[_], +C] + extends Any + with IterableOps[A, CC, C] { + + // Optimized, push-based version of `partition` + override def partition(p: A => Boolean): (C, C) = { + val l, r = newSpecificBuilder + iterator.foreach(x => (if (p(x)) l else r) += x) + (l.result(), r.result()) + } + + override def span(p: A => Boolean): (C, C) = { + val first = newSpecificBuilder + val second = newSpecificBuilder + val it = iterator + var inFirst = true + while (it.hasNext && inFirst) { + val a = it.next() + if (p(a)) { + first += a + } else { + second += a + inFirst = false + } + } + while (it.hasNext) { + second += it.next() + } + (first.result(), second.result()) + } + + override def unzip[A1, A2](implicit asPair: A => (A1, A2)): (CC[A1], CC[A2]) = { + val first = iterableFactory.newBuilder[A1] + val second = iterableFactory.newBuilder[A2] + foreach { a => + val pair = asPair(a) + first += pair._1 + second += pair._2 + } + (first.result(), second.result()) + } + + override def unzip3[A1, A2, A3](implicit asTriple: A => (A1, A2, A3)): (CC[A1], CC[A2], CC[A3]) = { + val b1 = iterableFactory.newBuilder[A1] + val b2 = iterableFactory.newBuilder[A2] + val b3 = iterableFactory.newBuilder[A3] + + foreach { xyz => + val triple = asTriple(xyz) + b1 += triple._1 + b2 += triple._2 + b3 += triple._3 + } + (b1.result(), b2.result(), b3.result()) + } + + // The implementations of the following operations are not fundamentally different from + // the view-based implementations, but they turn out to be slightly faster because + // a couple of indirection levels are removed + + override def map[B](f: A => B): CC[B] = + strictOptimizedMap(iterableFactory.newBuilder, f) + + /** + * @param b Builder to use to build the resulting collection + * @param f Element transformation function + * @tparam B Type of elements of the resulting collection (e.g. `String`) + * @tparam C2 Type of the resulting collection (e.g. `List[String]`) + * @return The resulting collection + */ + @inline protected[this] final def strictOptimizedMap[B, C2](b: mutable.Builder[B, C2], f: A => B): C2 = { + val it = iterator + while (it.hasNext) { + b += f(it.next()) + } + b.result() + } + + override def flatMap[B](f: A => IterableOnce[B]): CC[B] = + strictOptimizedFlatMap(iterableFactory.newBuilder, f) + + /** + * @param b Builder to use to build the resulting collection + * @param f Element transformation function + * @tparam B Type of elements of the resulting collection (e.g. `String`) + * @tparam C2 Type of the resulting collection (e.g. `List[String]`) + * @return The resulting collection + */ + @inline protected[this] final def strictOptimizedFlatMap[B, C2](b: mutable.Builder[B, C2], f: A => IterableOnce[B]): C2 = { + val it = iterator + while (it.hasNext) { + b ++= f(it.next()) + } + b.result() + } + + /** + * @param that Elements to concatenate to this collection + * @param b Builder to use to build the resulting collection + * @tparam B Type of elements of the resulting collections (e.g. `Int`) + * @tparam C2 Type of the resulting collection (e.g. `List[Int]`) + * @return The resulting collection + */ + @inline protected[this] final def strictOptimizedConcat[B >: A, C2](that: IterableOnce[B], b: mutable.Builder[B, C2]): C2 = { + b ++= this + b ++= that + b.result() + } + + override def collect[B](pf: PartialFunction[A, B]): CC[B] = + strictOptimizedCollect(iterableFactory.newBuilder, pf) + + /** + * @param b Builder to use to build the resulting collection + * @param pf Element transformation partial function + * @tparam B Type of elements of the resulting collection (e.g. `String`) + * @tparam C2 Type of the resulting collection (e.g. `List[String]`) + * @return The resulting collection + */ + @inline protected[this] final def strictOptimizedCollect[B, C2](b: mutable.Builder[B, C2], pf: PartialFunction[A, B]): C2 = { + val marker = Statics.pfMarker + val it = iterator + while (it.hasNext) { + val elem = it.next() + val v = pf.applyOrElse(elem, ((x: A) => marker).asInstanceOf[Function[A, B]]) + if (marker ne v.asInstanceOf[AnyRef]) b += v + } + b.result() + } + + override def flatten[B](implicit toIterableOnce: A => IterableOnce[B]): CC[B] = + strictOptimizedFlatten(iterableFactory.newBuilder) + + /** + * @param b Builder to use to build the resulting collection + * @param toIterableOnce Evidence that `A` can be seen as an `IterableOnce[B]` + * @tparam B Type of elements of the resulting collection (e.g. `Int`) + * @tparam C2 Type of the resulting collection (e.g. `List[Int]`) + * @return The resulting collection + */ + @inline protected[this] final def strictOptimizedFlatten[B, C2](b: mutable.Builder[B, C2])(implicit toIterableOnce: A => IterableOnce[B]): C2 = { + val it = iterator + while (it.hasNext) { + b ++= toIterableOnce(it.next()) + } + b.result() + } + + override def zip[B](that: IterableOnce[B]): CC[(A @uncheckedVariance, B)] = + strictOptimizedZip(that, iterableFactory.newBuilder[(A, B)]) + + /** + * @param that Collection to zip with this collection + * @param b Builder to use to build the resulting collection + * @tparam B Type of elements of the second collection (e.g. `String`) + * @tparam C2 Type of the resulting collection (e.g. `List[(Int, String)]`) + * @return The resulting collection + */ + @inline protected[this] final def strictOptimizedZip[B, C2](that: IterableOnce[B], b: mutable.Builder[(A, B), C2]): C2 = { + val it1 = iterator + val it2 = that.iterator + while (it1.hasNext && it2.hasNext) { + b += ((it1.next(), it2.next())) + } + b.result() + } + + override def zipWithIndex: CC[(A @uncheckedVariance, Int)] = { + val b = iterableFactory.newBuilder[(A, Int)] + var i = 0 + val it = iterator + while (it.hasNext) { + b += ((it.next(), i)) + i += 1 + } + b.result() + } + + override def scanLeft[B](z: B)(op: (B, A) => B): CC[B] = { + val b = iterableFactory.newBuilder[B] + b.sizeHint(this, delta = 0) + var acc = z + b += acc + val it = iterator + while (it.hasNext) { + acc = op(acc, it.next()) + b += acc + } + b.result() + } + + override def filter(pred: A => Boolean): C = filterImpl(pred, isFlipped = false) + + override def filterNot(pred: A => Boolean): C = filterImpl(pred, isFlipped = true) + + protected[collection] def filterImpl(pred: A => Boolean, isFlipped: Boolean): C = { + val b = newSpecificBuilder + val it = iterator + while (it.hasNext) { + val elem = it.next() + if (pred(elem) != isFlipped) { + b += elem + } + } + b.result() + } + + // Optimized, push-based version of `partitionMap` + override def partitionMap[A1, A2](f: A => Either[A1, A2]): (CC[A1], CC[A2]) = { + val l = iterableFactory.newBuilder[A1] + val r = iterableFactory.newBuilder[A2] + foreach { x => + f(x) match { + case Left(x1) => l += x1 + case Right(x2) => r += x2 + } + } + (l.result(), r.result()) + } + + // Optimization avoids creation of second collection + override def tapEach[U](f: A => U): C = { + foreach(f) + coll + } + + /** A collection containing the last `n` elements of this collection. + * $willForceEvaluation + */ + override def takeRight(n: Int): C = { + val b = newSpecificBuilder + b.sizeHintBounded(n, toIterable: @nowarn("cat=deprecation")) + val lead = iterator drop n + val it = iterator + while (lead.hasNext) { + lead.next() + it.next() + } + while (it.hasNext) b += it.next() + b.result() + } + + /** The rest of the collection without its `n` last elements. For + * linear, immutable collections this should avoid making a copy. + * $willForceEvaluation + */ + override def dropRight(n: Int): C = { + val b = newSpecificBuilder + if (n >= 0) b.sizeHint(this, delta = -n) + val lead = iterator drop n + val it = iterator + while (lead.hasNext) { + b += it.next() + lead.next() + } + b.result() + } +} diff --git a/library/src/scala/collection/StrictOptimizedMapOps.scala b/library/src/scala/collection/StrictOptimizedMapOps.scala new file mode 100644 index 000000000000..cc594f268c07 --- /dev/null +++ b/library/src/scala/collection/StrictOptimizedMapOps.scala @@ -0,0 +1,50 @@ +/* + * 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.collection + +import scala.language.`2.13` + +/** + * Trait that overrides map operations to take advantage of strict builders. + * + * @tparam K Type of keys + * @tparam V Type of values + * @tparam CC Collection type constructor + * @tparam C Collection type + */ +transparent trait StrictOptimizedMapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] + extends MapOps[K, V, CC, C] + with StrictOptimizedIterableOps[(K, V), Iterable, C] { + + override def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = + strictOptimizedMap(mapFactory.newBuilder, f) + + override def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]): CC[K2, V2] = + strictOptimizedFlatMap(mapFactory.newBuilder, f) + + override def concat[V2 >: V](suffix: IterableOnce[(K, V2)]): CC[K, V2] = + strictOptimizedConcat(suffix, mapFactory.newBuilder) + + override def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]): CC[K2, V2] = + strictOptimizedCollect(mapFactory.newBuilder, pf) + + @deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0") + override def + [V1 >: V](elem1: (K, V1), elem2: (K, V1), elems: (K, V1)*): CC[K, V1] = { + val b = mapFactory.newBuilder[K, V1] + b ++= this + b += elem1 + b += elem2 + if (elems.nonEmpty) b ++= elems + b.result() + } +} diff --git a/library/src/scala/collection/StrictOptimizedSeqOps.scala b/library/src/scala/collection/StrictOptimizedSeqOps.scala new file mode 100644 index 000000000000..cf59f7b05b9e --- /dev/null +++ b/library/src/scala/collection/StrictOptimizedSeqOps.scala @@ -0,0 +1,110 @@ +/* + * 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.collection + +import scala.language.`2.13` + +/** + * Trait that overrides operations on sequences in order + * to take advantage of strict builders. + */ +transparent trait StrictOptimizedSeqOps [+A, +CC[_], +C] + extends Any + with SeqOps[A, CC, C] + with StrictOptimizedIterableOps[A, CC, C] { + + override def distinctBy[B](f: A => B): C = { + val builder = newSpecificBuilder + val seen = mutable.HashSet.empty[B] + val it = this.iterator + while (it.hasNext) { + val next = it.next() + if (seen.add(f(next))) builder += next + } + builder.result() + } + + override def prepended[B >: A](elem: B): CC[B] = { + val b = iterableFactory.newBuilder[B] + b.sizeHint(this, delta = 1) + b += elem + b ++= this + b.result() + } + + override def appended[B >: A](elem: B): CC[B] = { + val b = iterableFactory.newBuilder[B] + b.sizeHint(this, delta = 1) + b ++= this + b += elem + b.result() + } + + override def appendedAll[B >: A](suffix: IterableOnce[B]): CC[B] = + strictOptimizedConcat(suffix, iterableFactory.newBuilder) + + override def prependedAll[B >: A](prefix: IterableOnce[B]): CC[B] = { + val b = iterableFactory.newBuilder[B] + b ++= prefix + b ++= this + b.result() + } + + override def padTo[B >: A](len: Int, elem: B): CC[B] = { + val b = iterableFactory.newBuilder[B] + val L = size + b.sizeHint(math.max(L, len)) + var diff = len - L + b ++= this + while (diff > 0) { + b += elem + diff -= 1 + } + b.result() + } + + override def diff[B >: A](that: Seq[B]): C = + if (isEmpty || that.isEmpty) coll + else { + val occ = occCounts(that) + val b = newSpecificBuilder + for (x <- this) { + occ.updateWith(x) { + case None => { + b.addOne(x) + None + } + case Some(1) => None + case Some(n) => Some(n - 1) + } + } + b.result() + } + + override def intersect[B >: A](that: Seq[B]): C = + if (isEmpty || that.isEmpty) empty + else { + val occ = occCounts(that) + val b = newSpecificBuilder + for (x <- this) { + occ.updateWith(x) { + case None => None + case Some(n) => { + b.addOne(x) + if (n == 1) None else Some(n - 1) + } + } + } + b.result() + } +} diff --git a/library/src/scala/collection/StrictOptimizedSetOps.scala b/library/src/scala/collection/StrictOptimizedSetOps.scala new file mode 100644 index 000000000000..cef517c39fd6 --- /dev/null +++ b/library/src/scala/collection/StrictOptimizedSetOps.scala @@ -0,0 +1,31 @@ +/* + * 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.collection + +import scala.language.`2.13` + +/** + * Trait that overrides set operations to take advantage of strict builders. + * + * @tparam A Elements type + * @tparam CC Collection type constructor + * @tparam C Collection type + */ +transparent trait StrictOptimizedSetOps[A, +CC[_], +C <: SetOps[A, CC, C]] + extends SetOps[A, CC, C] + with StrictOptimizedIterableOps[A, CC, C] { + + override def concat(that: IterableOnce[A]): C = + strictOptimizedConcat(that, newSpecificBuilder) + +} diff --git a/library/src/scala/collection/StrictOptimizedSortedMapOps.scala b/library/src/scala/collection/StrictOptimizedSortedMapOps.scala new file mode 100644 index 000000000000..c71ad8d1e447 --- /dev/null +++ b/library/src/scala/collection/StrictOptimizedSortedMapOps.scala @@ -0,0 +1,47 @@ +/* + * 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.collection + +import scala.language.`2.13` +import scala.annotation.implicitNotFound + +/** + * Trait that overrides sorted map operations to take advantage of strict builders. + * + * @tparam K Type of keys + * @tparam V Type of values + * @tparam CC Collection type constructor + * @tparam C Collection type + */ +transparent trait StrictOptimizedSortedMapOps[K, +V, +CC[X, Y] <: Map[X, Y] with SortedMapOps[X, Y, CC, _], +C <: SortedMapOps[K, V, CC, C]] + extends SortedMapOps[K, V, CC, C] + with StrictOptimizedMapOps[K, V, Map, C] { + + override def map[K2, V2](f: ((K, V)) => (K2, V2))(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = + strictOptimizedMap(sortedMapFactory.newBuilder, f) + + override def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)])(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = + strictOptimizedFlatMap(sortedMapFactory.newBuilder, f) + + override def concat[V2 >: V](xs: IterableOnce[(K, V2)]): CC[K, V2] = + strictOptimizedConcat(xs, sortedMapFactory.newBuilder(using ordering)) + + override def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)])(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = + strictOptimizedCollect(sortedMapFactory.newBuilder, pf) + + @deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0") + override def + [V1 >: V](elem1: (K, V1), elem2: (K, V1), elems: (K, V1)*): CC[K, V1] = { + val m = ((this + elem1).asInstanceOf[Map[K, V]] + elem2).asInstanceOf[CC[K, V1]] + if(elems.isEmpty) m else m.concat(elems).asInstanceOf[CC[K, V1]] + } +} diff --git a/library/src/scala/collection/StrictOptimizedSortedSetOps.scala b/library/src/scala/collection/StrictOptimizedSortedSetOps.scala new file mode 100644 index 000000000000..aef85be58bb3 --- /dev/null +++ b/library/src/scala/collection/StrictOptimizedSortedSetOps.scala @@ -0,0 +1,43 @@ +/* + * 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 collection + +import scala.language.`2.13` +import scala.annotation.implicitNotFound +import scala.annotation.unchecked.uncheckedVariance + +/** + * Trait that overrides sorted set operations to take advantage of strict builders. + * + * @tparam A Elements type + * @tparam CC Collection type constructor + * @tparam C Collection type + */ +transparent trait StrictOptimizedSortedSetOps[A, +CC[X] <: SortedSet[X], +C <: SortedSetOps[A, CC, C]] + extends SortedSetOps[A, CC, C] + with StrictOptimizedSetOps[A, Set, C] { + + override def map[B](f: A => B)(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = + strictOptimizedMap(sortedIterableFactory.newBuilder, f) + + override def flatMap[B](f: A => IterableOnce[B])(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = + strictOptimizedFlatMap(sortedIterableFactory.newBuilder, f) + + override def zip[B](that: IterableOnce[B])(implicit @implicitNotFound(SortedSetOps.zipOrdMsg) ev: Ordering[(A @uncheckedVariance, B)]): CC[(A @uncheckedVariance, B)] = + strictOptimizedZip(that, sortedIterableFactory.newBuilder[(A, B)]) + + override def collect[B](pf: PartialFunction[A, B])(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = + strictOptimizedCollect(sortedIterableFactory.newBuilder, pf) + +} diff --git a/library/src/scala/collection/StringOps.scala b/library/src/scala/collection/StringOps.scala new file mode 100644 index 000000000000..20f51e35da20 --- /dev/null +++ b/library/src/scala/collection/StringOps.scala @@ -0,0 +1,1651 @@ +/* + * 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 collection + +import scala.language.`2.13` +import java.lang.{StringBuilder => JStringBuilder} + +import scala.collection.Stepper.EfficientSplit +import scala.collection.convert.impl.{CharStringStepper, CodePointStringStepper} +import scala.collection.immutable.{ArraySeq, WrappedString} +import scala.collection.mutable.StringBuilder +import scala.math.{ScalaNumber, max, min} +import scala.reflect.ClassTag +import scala.util.matching.Regex + +object StringOps { + // just statics for companion class. + private final val LF = 0x0A + private final val CR = 0x0D + + private class StringIterator(private[this] val s: String) extends AbstractIterator[Char] { + private[this] var pos = 0 + def hasNext: Boolean = pos < s.length + def next(): Char = { + if (pos >= s.length) Iterator.empty.next() + val r = s.charAt(pos) + pos += 1 + r + } + } + + private class ReverseIterator(private[this] val s: String) extends AbstractIterator[Char] { + private[this] var pos = s.length-1 + def hasNext: Boolean = pos >= 0 + def next(): Char = { + if (pos < 0) Iterator.empty.next() + val r = s.charAt(pos) + pos -= 1 + r + } + } + + private class GroupedIterator(s: String, groupSize: Int) extends AbstractIterator[String] { + private[this] var pos = 0 + def hasNext: Boolean = pos < s.length + def next(): String = { + if(pos >= s.length) Iterator.empty.next() + val r = s.slice(pos, pos+groupSize) + pos += groupSize + r + } + } + + /** A lazy filtered string. No filtering is applied until one of `foreach`, `map` or `flatMap` is called. */ + class WithFilter(p: Char => Boolean, s: String) { + + /** Apply `f` to each element for its side effects. + * Note: [U] parameter needed to help scalac's type inference. + */ + def foreach[U](f: Char => U): Unit = { + val len = s.length + var i = 0 + while(i < len) { + val x = s.charAt(i) + if(p(x)) f(x) + i += 1 + } + } + + /** Builds a new collection by applying a function to all chars of this filtered string. + * + * @param f the function to apply to each char. + * @return a new collection resulting from applying the given function + * `f` to each char of this string and collecting the results. + */ + def map[B](f: Char => B): immutable.IndexedSeq[B] = { + val len = s.length + val b = immutable.IndexedSeq.newBuilder[B] + b.sizeHint(len) + var i = 0 + while (i < len) { + val x = s.charAt(i) + if(p(x)) b.addOne(f(x)) + i += 1 + } + b.result() + } + + /** Builds a new string by applying a function to all chars of this filtered string. + * + * @param f the function to apply to each char. + * @return a new string resulting from applying the given function + * `f` to each char of this string and collecting the results. + */ + def map(f: Char => Char): String = { + val len = s.length + val sb = new JStringBuilder(len) + var i = 0 + while (i < len) { + val x = s.charAt(i) + if(p(x)) sb.append(f(x)) + i += 1 + } + sb.toString + } + + /** Builds a new collection by applying a function to all chars of this filtered string + * and using the elements of the resulting collections. + * + * @param f the function to apply to each char. + * @return a new collection resulting from applying the given collection-valued function + * `f` to each char of this string and concatenating the results. + */ + def flatMap[B](f: Char => IterableOnce[B]): immutable.IndexedSeq[B] = { + val len = s.length + val b = immutable.IndexedSeq.newBuilder[B] + var i = 0 + while (i < len) { + val x = s.charAt(i) + if(p(x)) b.addAll(f(x)) + i += 1 + } + b.result() + } + + /** Builds a new string by applying a function to all chars of this filtered string + * and using the elements of the resulting Strings. + * + * @param f the function to apply to each char. + * @return a new string resulting from applying the given string-valued function + * `f` to each char of this string and concatenating the results. + */ + def flatMap(f: Char => String): String = { + val len = s.length + val sb = new JStringBuilder + var i = 0 + while (i < len) { + val x = s.charAt(i) + if(p(x)) sb.append(f(x)) + i += 1 + } + sb.toString + } + + /** Creates a new non-strict filter which combines this filter with the given predicate. */ + def withFilter(q: Char => Boolean): WithFilter = new WithFilter(a => p(a) && q(a), s) + } + + /** Avoid an allocation in [[collect]]. */ + private val fallback: Any => Any = _ => fallback +} + +/** Provides extension methods for strings. + * + * Some of these methods treat strings as a plain collection of [[Char]]s + * without any regard for Unicode handling. Unless the user takes Unicode + * handling in to account or makes sure the strings don't require such handling, + * these methods may result in unpaired or invalidly paired surrogate code + * units. + * + * @define unicodeunaware This method treats a string as a plain sequence of + * Char code units and makes no attempt to keep + * surrogate pairs or codepoint sequences together. + * The user is responsible for making sure such cases + * are handled correctly. Failing to do so may result in + * an invalid Unicode string. + */ +final class StringOps(private val s: String) extends AnyVal { + import StringOps._ + + @inline def view: StringView = new StringView(s) + + @inline def size: Int = s.length + + @inline def knownSize: Int = s.length + + /** Get the char at the specified index. */ + @inline def apply(i: Int): Char = s.charAt(i) + + def sizeCompare(otherSize: Int): Int = Integer.compare(s.length, otherSize) + + def lengthCompare(len: Int): Int = Integer.compare(s.length, len) + + def sizeIs: Int = s.length + + def lengthIs: Int = s.length + + /** Builds a new collection by applying a function to all chars of this string. + * + * @param f the function to apply to each char. + * @return a new collection resulting from applying the given function + * `f` to each char of this string and collecting the results. + */ + def map[B](f: Char => B): immutable.IndexedSeq[B] = { + val len = s.length + val dst = new Array[AnyRef](len) + var i = 0 + while (i < len) { + dst(i) = f(s charAt i).asInstanceOf[AnyRef] + i += 1 + } + new ArraySeq.ofRef(dst).asInstanceOf[immutable.IndexedSeq[B]] + } + + /** Builds a new string by applying a function to all chars of this string. + * + * @param f the function to apply to each char. + * @return a new string resulting from applying the given function + * `f` to each char of this string and collecting the results. + */ + def map(f: Char => Char): String = { + val len = s.length + val dst = new Array[Char](len) + var i = 0 + while (i < len) { + dst(i) = f(s charAt i) + i += 1 + } + new String(dst) + } + + /** Builds a new collection by applying a function to all chars of this string + * and using the elements of the resulting collections. + * + * @param f the function to apply to each char. + * @return a new collection resulting from applying the given collection-valued function + * `f` to each char of this string and concatenating the results. + */ + def flatMap[B](f: Char => IterableOnce[B]): immutable.IndexedSeq[B] = { + val len = s.length + val b = immutable.IndexedSeq.newBuilder[B] + var i = 0 + while (i < len) { + b.addAll(f(s.charAt(i))) + i += 1 + } + b.result() + } + + /** Builds a new string by applying a function to all chars of this string + * and using the elements of the resulting strings. + * + * @param f the function to apply to each char. + * @return a new string resulting from applying the given string-valued function + * `f` to each char of this string and concatenating the results. + */ + def flatMap(f: Char => String): String = { + val len = s.length + val sb = new JStringBuilder + var i = 0 + while (i < len) { + sb append f(s.charAt(i)) + i += 1 + } + sb.toString + } + + /** Builds a new String by applying a partial function to all chars of this String + * on which the function is defined. + * + * @param pf the partial function which filters and maps the String. + * @return a new String resulting from applying the given partial function + * `pf` to each char on which it is defined and collecting the results. + */ + def collect(pf: PartialFunction[Char, Char]): String = { + val fallback: Any => Any = StringOps.fallback + var i = 0 + val b = new StringBuilder + while (i < s.length) { + val v = pf.applyOrElse(s.charAt(i), fallback) + if (v.asInstanceOf[AnyRef] ne fallback) b.addOne(v.asInstanceOf[Char]) + i += 1 + } + b.result() + } + + /** Builds a new collection by applying a partial function to all chars of this String + * on which the function is defined. + * + * @param pf the partial function which filters and maps the String. + * @tparam B the element type of the returned collection. + * @return a new collection resulting from applying the given partial function + * `pf` to each char on which it is defined and collecting the results. + */ + def collect[B](pf: PartialFunction[Char, B]): immutable.IndexedSeq[B] = { + val fallback: Any => Any = StringOps.fallback + var i = 0 + val b = immutable.IndexedSeq.newBuilder[B] + while (i < s.length) { + val v = pf.applyOrElse(s.charAt(i), fallback) + if (v.asInstanceOf[AnyRef] ne fallback) b.addOne(v.asInstanceOf[B]) + i += 1 + } + b.result() + } + + /** Returns a new collection containing the chars from this string followed by the elements from the + * right hand operand. + * + * @param suffix the collection to append. + * @return a new collection which contains all chars + * of this string followed by all elements of `suffix`. + */ + def concat[B >: Char](suffix: IterableOnce[B]): immutable.IndexedSeq[B] = { + val b = immutable.IndexedSeq.newBuilder[B] + val k = suffix.knownSize + b.sizeHint(s.length + (if(k >= 0) k else 16)) + b.addAll(new WrappedString(s)) + b.addAll(suffix) + b.result() + } + + /** Returns a new string containing the chars from this string followed by the chars from the + * right hand operand. + * + * @param suffix the collection to append. + * @return a new string which contains all chars + * of this string followed by all chars of `suffix`. + */ + def concat(suffix: IterableOnce[Char]): String = { + val k = suffix.knownSize + val sb = new JStringBuilder(s.length + (if(k >= 0) k else 16)) + sb.append(s) + for (ch <- suffix.iterator) sb.append(ch) + sb.toString + } + + /** Returns a new string containing the chars from this string followed by the chars from the + * right hand operand. + * + * @param suffix the string to append. + * @return a new string which contains all chars + * of this string followed by all chars of `suffix`. + */ + @inline def concat(suffix: String): String = s + suffix + + /** Alias for `concat` */ + @inline def ++[B >: Char](suffix: Iterable[B]): immutable.IndexedSeq[B] = concat(suffix) + + /** Alias for `concat` */ + @inline def ++(suffix: IterableOnce[Char]): String = concat(suffix) + + /** Alias for `concat` */ + def ++(xs: String): String = concat(xs) + + /** Returns a collection with an element appended until a given target length is reached. + * + * @param len the target length + * @param elem the padding value + * @return a collection consisting of + * this string followed by the minimal number of occurrences of `elem` so + * that the resulting collection has a length of at least `len`. + */ + def padTo[B >: Char](len: Int, elem: B): immutable.IndexedSeq[B] = { + val sLen = s.length + if (sLen >= len) new WrappedString(s) else { + val b = immutable.IndexedSeq.newBuilder[B] + b.sizeHint(len) + b.addAll(new WrappedString(s)) + var i = sLen + while (i < len) { + b.addOne(elem) + i += 1 + } + b.result() + } + } + + /** Returns a string with a char appended until a given target length is reached. + * + * @param len the target length + * @param elem the padding value + * @return a string consisting of + * this string followed by the minimal number of occurrences of `elem` so + * that the resulting string has a length of at least `len`. + */ + def padTo(len: Int, elem: Char): String = { + val sLen = s.length + if (sLen >= len) s else { + val sb = new JStringBuilder(len) + sb.append(s) + // With JDK 11, this can written as: + // sb.append(String.valueOf(elem).repeat(len - sLen)) + var i = sLen + while (i < len) { + sb.append(elem) + i += 1 + } + sb.toString + } + } + + /** A copy of the string with an element prepended */ + def prepended[B >: Char](elem: B): immutable.IndexedSeq[B] = { + val b = immutable.IndexedSeq.newBuilder[B] + b.sizeHint(s.length + 1) + b.addOne(elem) + b.addAll(new WrappedString(s)) + b.result() + } + + /** Alias for `prepended` */ + @inline def +: [B >: Char] (elem: B): immutable.IndexedSeq[B] = prepended(elem) + + /** A copy of the string with an char prepended */ + def prepended(c: Char): String = + new JStringBuilder(s.length + 1).append(c).append(s).toString + + /** Alias for `prepended` */ + @inline def +: (c: Char): String = prepended(c) + + /** A copy of the string with all elements from a collection prepended */ + def prependedAll[B >: Char](prefix: IterableOnce[B]): immutable.IndexedSeq[B] = { + val b = immutable.IndexedSeq.newBuilder[B] + val k = prefix.knownSize + b.sizeHint(s.length + (if(k >= 0) k else 16)) + b.addAll(prefix) + b.addAll(new WrappedString(s)) + b.result() + } + + /** Alias for `prependedAll` */ + @inline def ++: [B >: Char] (prefix: IterableOnce[B]): immutable.IndexedSeq[B] = prependedAll(prefix) + + /** A copy of the string with another string prepended */ + def prependedAll(prefix: String): String = prefix + s + + /** Alias for `prependedAll` */ + @inline def ++: (prefix: String): String = prependedAll(prefix) + + /** A copy of the string with an element appended */ + def appended[B >: Char](elem: B): immutable.IndexedSeq[B] = { + val b = immutable.IndexedSeq.newBuilder[B] + b.sizeHint(s.length + 1) + b.addAll(new WrappedString(s)) + b.addOne(elem) + b.result() + } + + /** Alias for `appended` */ + @inline def :+ [B >: Char](elem: B): immutable.IndexedSeq[B] = appended(elem) + + /** A copy of the string with an element appended */ + def appended(c: Char): String = + new JStringBuilder(s.length + 1).append(s).append(c).toString + + /** Alias for `appended` */ + @inline def :+ (c: Char): String = appended(c) + + /** A copy of the string with all elements from a collection appended */ + @inline def appendedAll[B >: Char](suffix: IterableOnce[B]): immutable.IndexedSeq[B] = + concat(suffix) + + /** Alias for `appendedAll` */ + @inline def :++ [B >: Char](suffix: IterableOnce[B]): immutable.IndexedSeq[B] = + concat(suffix) + + /** A copy of the string with another string appended */ + @inline def appendedAll(suffix: String): String = s + suffix + + /** Alias for `appendedAll` */ + @inline def :++ (suffix: String): String = s + suffix + + /** Produces a new collection where a slice of characters in this string is replaced by another collection. + * + * Patching at negative indices is the same as patching starting at 0. + * Patching at indices at or larger than the length of the original string appends the patch to the end. + * If more values are replaced than actually exist, the excess is ignored. + * + * @param from the index of the first replaced char + * @param other the replacement collection + * @param replaced the number of chars to drop in the original string + * @return a new collection consisting of all chars of this string + * except that `replaced` chars starting from `from` are replaced + * by `other`. + */ + def patch[B >: Char](from: Int, other: IterableOnce[B], replaced: Int): immutable.IndexedSeq[B] = { + val len = s.length + @inline def slc(off: Int, length: Int): WrappedString = + new WrappedString(s.substring(off, off+length)) + val b = immutable.IndexedSeq.newBuilder[B] + val k = other.knownSize + if(k >= 0) b.sizeHint(len + k - replaced) + val chunk1 = if(from > 0) min(from, len) else 0 + if(chunk1 > 0) b.addAll(slc(0, chunk1)) + b ++= other + val remaining = len - chunk1 - replaced + if(remaining > 0) b.addAll(slc(len - remaining, remaining)) + b.result() + } + + /** Produces a new collection where a slice of characters in this string is replaced by another collection. + * + * Patching at negative indices is the same as patching starting at 0. + * Patching at indices at or larger than the length of the original string appends the patch to the end. + * If more values are replaced than actually exist, the excess is ignored. + * + * @param from the index of the first replaced char + * @param other the replacement string + * @param replaced the number of chars to drop in the original string + * @return a new string consisting of all chars of this string + * except that `replaced` chars starting from `from` are replaced + * by `other`. + * @note $unicodeunaware + */ + def patch(from: Int, other: IterableOnce[Char], replaced: Int): String = + patch(from, other.iterator.mkString, replaced) + + /** Produces a new string where a slice of characters in this string is replaced by another string. + * + * Patching at negative indices is the same as patching starting at 0. + * Patching at indices at or larger than the length of the original string appends the patch to the end. + * If more values are replaced than actually exist, the excess is ignored. + * + * @param from the index of the first replaced char + * @param other the replacement string + * @param replaced the number of chars to drop in the original string + * @return a new string consisting of all chars of this string + * except that `replaced` chars starting from `from` are replaced + * by `other`. + * @note $unicodeunaware + */ + def patch(from: Int, other: String, replaced: Int): String = { + val len = s.length + val sb = new JStringBuilder(len + other.size - replaced) + val chunk1 = if(from > 0) min(from, len) else 0 + if(chunk1 > 0) sb.append(s, 0, chunk1) + sb.append(other) + val remaining = len - chunk1 - replaced + if(remaining > 0) sb.append(s, len - remaining, len) + sb.toString + } + + /** A copy of this string with one single replaced element. + * @param index the position of the replacement + * @param elem the replacing element + * @return a new string which is a copy of this string with the element at position `index` replaced by `elem`. + * @throws IndexOutOfBoundsException if `index` does not satisfy `0 <= index < length`. + * @note $unicodeunaware + */ + def updated(index: Int, elem: Char): String = { + val sb = new JStringBuilder(s.length).append(s) + sb.setCharAt(index, elem) + sb.toString + } + + /** Tests whether this string contains the given character. + * + * @param elem the character to test. + * @return `true` if this string has an element that is equal (as + * determined by `==`) to `elem`, `false` otherwise. + */ + def contains(elem: Char): Boolean = s.indexOf(elem) >= 0 + + /** Displays all elements of this string in a string using start, end, and + * separator strings. + * + * @param start the starting string. + * @param sep the separator string. + * @param end the ending string. + * @return The resulting string + * begins with the string `start` and ends with the string + * `end`. Inside, the string chars of this string are separated by + * the string `sep`. + * @note $unicodeunaware + */ + final def mkString(start: String, sep: String, end: String): String = + addString(new StringBuilder(), start, sep, end).toString + + /** Displays all elements of this string in a string using a separator string. + * + * @param sep the separator string. + * @return In the resulting string + * the chars of this string are separated by the string `sep`. + * @note $unicodeunaware + */ + @inline final def mkString(sep: String): String = + if (sep.isEmpty || s.length < 2) s + else mkString("", sep, "") + + /** Returns this string */ + @inline final def mkString: String = s + + /** Appends this string to a string builder. */ + @inline final def addString(b: StringBuilder): b.type = b.append(s) + + /** Appends this string to a string builder using a separator string. */ + @inline final def addString(b: StringBuilder, sep: String): b.type = + addString(b, "", sep, "") + + /** Appends this string to a string builder using start, end and separator strings. */ + final def addString(b: StringBuilder, start: String, sep: String, end: String): b.type = { + val jsb = b.underlying + if (start.length != 0) jsb.append(start) + val len = s.length + if (len != 0) { + if (sep.isEmpty) jsb.append(s) + else { + jsb.ensureCapacity(jsb.length + len + end.length + (len - 1) * sep.length) + jsb.append(s.charAt(0)) + var i = 1 + while (i < len) { + jsb.append(sep) + jsb.append(s.charAt(i)) + i += 1 + } + } + } + if (end.length != 0) jsb.append(end) + b + } + + /** Selects an interval of elements. The returned string is made up + * of all elements `x` which satisfy the invariant: + * {{{ + * from <= indexOf(x) < until + * }}} + * + * @param from the lowest index to include from this string. + * @param until the lowest index to EXCLUDE from this string. + * @return a string containing the elements greater than or equal to + * index `from` extending up to (but not including) index `until` + * of this string. + * @note $unicodeunaware + */ + def slice(from: Int, until: Int): String = { + val start = from max 0 + val end = until min s.length + + if (start >= end) "" + else s.substring(start, end) + } + + // Note: String.repeat is added in JDK 11. + /** Return the current string concatenated `n` times. + */ + def *(n: Int): String = + if (n <= 0) { + "" + } else { + val sb = new JStringBuilder(s.length * n) + var i = 0 + while (i < n) { + sb.append(s) + i += 1 + } + sb.toString + } + + @inline private def isLineBreak(c: Char) = c == CR || c == LF + @inline private def isLineBreak2(c0: Char, c: Char) = c0 == CR && c == LF + + /** Strip the trailing line separator from this string if there is one. + * The line separator is taken as `"\n"`, `"\r"`, or `"\r\n"`. + */ + def stripLineEnd: String = + if (s.isEmpty) s + else { + var i = s.length - 1 + val last = apply(i) + if (!isLineBreak(last)) s + else { + if (i > 0 && isLineBreak2(apply(i - 1), last)) i -= 1 + s.substring(0, i) + } + } + + /** Return an iterator of all lines embedded in this string, + * including trailing line separator characters. + * + * The empty string yields an empty iterator. + */ + def linesWithSeparators: Iterator[String] = linesSeparated(stripped = false) + + /** Lines in this string, where a line is terminated by + * `"\n"`, `"\r"`, `"\r\n"`, or the end of the string. + * A line may be empty. Line terminators are removed. + */ + def linesIterator: Iterator[String] = linesSeparated(stripped = true) + + // if `stripped`, exclude the line separators + private def linesSeparated(stripped: Boolean): Iterator[String] = new AbstractIterator[String] { + def hasNext: Boolean = !done + def next(): String = if (done) Iterator.empty.next() else advance() + + private[this] val len = s.length + private[this] var index = 0 + @inline private def done = index >= len + private def advance(): String = { + val start = index + while (!done && !isLineBreak(apply(index))) index += 1 + var end = index + if (!done) { + val c = apply(index) + index += 1 + if (!done && isLineBreak2(c, apply(index))) index += 1 + if (!stripped) end = index + } + s.substring(start, end) + } + } + + /** Return all lines in this string in an iterator, excluding trailing line + * end characters; i.e., apply `.stripLineEnd` to all lines + * returned by `linesWithSeparators`. + */ + @deprecated("Use `linesIterator`, because JDK 11 adds a `lines` method on String", "2.13.0") + def lines: Iterator[String] = linesIterator + + /** Returns this string with first character converted to upper case. + * If the first character of the string is capitalized, it is returned unchanged. + * This method does not convert characters outside the Basic Multilingual Plane (BMP). + */ + def capitalize: String = + if (s == null || s.length == 0 || !s.charAt(0).isLower) s + else updated(0, s.charAt(0).toUpper) + + /** Returns this string with the given `prefix` stripped. If this string does not + * start with `prefix`, it is returned unchanged. + */ + def stripPrefix(prefix: String) = + if (s startsWith prefix) s.substring(prefix.length) + else s + + /** Returns this string with the given `suffix` stripped. If this string does not + * end with `suffix`, it is returned unchanged. + */ + def stripSuffix(suffix: String) = + if (s endsWith suffix) s.substring(0, s.length - suffix.length) + else s + + /** Replace all literal occurrences of `literal` with the literal string `replacement`. + * This method is equivalent to [[java.lang.String#replace(CharSequence,CharSequence)]]. + * + * @param literal the string which should be replaced everywhere it occurs + * @param replacement the replacement string + * @return the resulting string + */ + @deprecated("Use `s.replace` as an exact replacement", "2.13.2") + def replaceAllLiterally(literal: String, replacement: String): String = s.replace(literal, replacement) + + /** For every line in this string: + * + * Strip a leading prefix consisting of blanks or control characters + * followed by `marginChar` from the line. + */ + def stripMargin(marginChar: Char): String = { + val sb = new JStringBuilder(s.length) + for (line <- linesWithSeparators) { + val len = line.length + var index = 0 + while (index < len && line.charAt(index) <= ' ') index += 1 + val stripped = + if (index < len && line.charAt(index) == marginChar) line.substring(index + 1) + else line + sb.append(stripped) + } + sb.toString + } + + /** For every line in this string: + * + * Strip a leading prefix consisting of blanks or control characters + * followed by `|` from the line. + */ + def stripMargin: String = stripMargin('|') + + private[this] def escape(ch: Char): String = if ( + (ch >= 'a') && (ch <= 'z') || + (ch >= 'A') && (ch <= 'Z') || + (ch >= '0' && ch <= '9')) ch.toString + else "\\" + ch + + /** Split this string around the separator character + * + * If this string is the empty string, returns an array of strings + * that contains a single empty string. + * + * If this string is not the empty string, returns an array containing + * the substrings terminated by the start of the string, the end of the + * string or the separator character, excluding empty trailing substrings + * + * If the separator character is a surrogate character, only split on + * matching surrogate characters if they are not part of a surrogate pair + * + * The behaviour follows, and is implemented in terms of String.split(re: String) + * + * + * @example {{{ + * "a.b".split('.') //returns Array("a", "b") + * + * //splitting the empty string always returns the array with a single + * //empty string + * "".split('.') //returns Array("") + * + * //only trailing empty substrings are removed + * "a.".split('.') //returns Array("a") + * ".a.".split('.') //returns Array("", "a") + * "..a..".split('.') //returns Array("", "", "a") + * + * //all parts are empty and trailing + * ".".split('.') //returns Array() + * "..".split('.') //returns Array() + * + * //surrogate pairs + * val high = 0xD852.toChar + * val low = 0xDF62.toChar + * val highstring = high.toString + * val lowstring = low.toString + * + * //well-formed surrogate pairs are not split + * val highlow = highstring + lowstring + * highlow.split(high) //returns Array(highlow) + * + * //bare surrogate characters are split + * val bare = "_" + highstring + "_" + * bare.split(high) //returns Array("_", "_") + * + * }}} + * + * @param separator the character used as a delimiter + */ + def split(separator: Char): Array[String] = s.split(escape(separator)) + + @throws(classOf[java.util.regex.PatternSyntaxException]) + def split(separators: Array[Char]): Array[String] = { + val re = separators.foldLeft("[")(_+escape(_)) + "]" + s.split(re) + } + + /** You can follow a string with `.r`, turning it into a `Regex`. E.g. + * + * `"""A\w*""".r` is the regular expression for ASCII-only identifiers starting with `A`. + * + * `"""(?\d\d)-(?\d\d)-(?\d\d\d\d)""".r` matches dates + * and provides its subcomponents through groups named "month", "day" and + * "year". + */ + def r: Regex = new Regex(s) + + /** You can follow a string with `.r(g1, ... , gn)`, turning it into a `Regex`, + * with group names g1 through gn. + * + * `"""(\d\d)-(\d\d)-(\d\d\d\d)""".r("month", "day", "year")` matches dates + * and provides its subcomponents through groups named "month", "day" and + * "year". + * + * @param groupNames The names of the groups in the pattern, in the order they appear. + */ + @deprecated("use inline group names like (?X) instead", "2.13.7") + def r(groupNames: String*): Regex = new Regex(s, groupNames: _*) + + /** + * @throws java.lang.IllegalArgumentException If the string does not contain a parsable `Boolean`. + */ + def toBoolean: Boolean = toBooleanImpl(s) + + /** + * Try to parse as a `Boolean` + * @return `Some(true)` if the string is "true" case insensitive, + * `Some(false)` if the string is "false" case insensitive, + * and `None` if the string is anything else + * @throws java.lang.NullPointerException if the string is `null` + */ + def toBooleanOption: Option[Boolean] = StringParsers.parseBool(s) + + /** + * Parse as a `Byte` (string must contain only decimal digits and optional leading `-` or `+`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Byte`. + */ + def toByte: Byte = java.lang.Byte.parseByte(s) + + /** + * Try to parse as a `Byte` + * @return `Some(value)` if the string contains a valid byte value, otherwise `None` + * @throws java.lang.NullPointerException if the string is `null` + */ + def toByteOption: Option[Byte] = StringParsers.parseByte(s) + + /** + * Parse as a `Short` (string must contain only decimal digits and optional leading `-` or `+`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Short`. + */ + def toShort: Short = java.lang.Short.parseShort(s) + + /** + * Try to parse as a `Short` + * @return `Some(value)` if the string contains a valid short value, otherwise `None` + * @throws java.lang.NullPointerException if the string is `null` + */ + def toShortOption: Option[Short] = StringParsers.parseShort(s) + + /** + * Parse as an `Int` (string must contain only decimal digits and optional leading `-` or `+`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Int`. + */ + def toInt: Int = java.lang.Integer.parseInt(s) + + /** + * Try to parse as an `Int` + * @return `Some(value)` if the string contains a valid Int value, otherwise `None` + * @throws java.lang.NullPointerException if the string is `null` + */ + def toIntOption: Option[Int] = StringParsers.parseInt(s) + + /** + * Parse as a `Long` (string must contain only decimal digits and optional leading `-` or `+`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Long`. + */ + def toLong: Long = java.lang.Long.parseLong(s) + + /** + * Try to parse as a `Long` + * @return `Some(value)` if the string contains a valid long value, otherwise `None` + * @throws java.lang.NullPointerException if the string is `null` + */ + def toLongOption: Option[Long] = StringParsers.parseLong(s) + + /** + * Parse as a `Float` (surrounding whitespace is removed with a `trim`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Float`. + * @throws java.lang.NullPointerException If the string is null. + */ + def toFloat: Float = java.lang.Float.parseFloat(s) + + /** + * Try to parse as a `Float` + * @return `Some(value)` if the string is a parsable `Float`, `None` otherwise + * @throws java.lang.NullPointerException If the string is null + */ + def toFloatOption: Option[Float] = StringParsers.parseFloat(s) + + /** + * Parse as a `Double` (surrounding whitespace is removed with a `trim`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Double`. + * @throws java.lang.NullPointerException If the string is null. + */ + def toDouble: Double = java.lang.Double.parseDouble(s) + + /** + * Try to parse as a `Double` + * @return `Some(value)` if the string is a parsable `Double`, `None` otherwise + * @throws java.lang.NullPointerException If the string is null + */ + def toDoubleOption: Option[Double] = StringParsers.parseDouble(s) + + private[this] def toBooleanImpl(s: String): Boolean = + if (s == null) throw new IllegalArgumentException("For input string: \"null\"") + else if (s.equalsIgnoreCase("true")) true + else if (s.equalsIgnoreCase("false")) false + else throw new IllegalArgumentException("For input string: \""+s+"\"") + + def toArray[B >: Char](implicit tag: ClassTag[B]): Array[B] = + if (tag == ClassTag.Char) s.toCharArray.asInstanceOf[Array[B]] + else new WrappedString(s).toArray[B] + + private[this] def unwrapArg(arg: Any): AnyRef = arg match { + case x: ScalaNumber => x.underlying + case x => x.asInstanceOf[AnyRef] + } + + /** Uses the underlying string as a pattern (in a fashion similar to + * printf in C), and uses the supplied arguments to fill in the + * holes. + * + * The interpretation of the formatting patterns is described in + * [[java.util.Formatter]], with the addition that + * classes deriving from `ScalaNumber` (such as [[scala.BigInt]] and + * [[scala.BigDecimal]]) are unwrapped to pass a type which `Formatter` understands. + * + * See [[scala.StringContext#f]] for a formatting interpolator that + * checks the format string at compilation. + * + * @param args the arguments used to instantiating the pattern. + * @throws java.util.IllegalFormatException if the format contains syntax or conversion errors + */ + def format(args: Any*): String = + java.lang.String.format(s, args.map(unwrapArg): _*) + + /** Like `format(args*)` but takes an initial `Locale` parameter + * which influences formatting as in `java.lang.String`'s format. + * + * The interpretation of the formatting patterns is described in + * [[java.util.Formatter]], with the addition that + * classes deriving from `ScalaNumber` (such as `scala.BigInt` and + * `scala.BigDecimal`) are unwrapped to pass a type which `Formatter` + * understands. + * + * @param l an instance of `java.util.Locale` + * @param args the arguments used to instantiating the pattern. + * @throws java.util.IllegalFormatException if the format contains syntax or conversion errors + */ + def formatLocal(l: java.util.Locale, args: Any*): String = + java.lang.String.format(l, s, args.map(unwrapArg): _*) + + def compare(that: String): Int = s.compareTo(that) + + /** Returns true if `this` is less than `that` */ + def < (that: String): Boolean = compare(that) < 0 + + /** Returns true if `this` is greater than `that`. */ + def > (that: String): Boolean = compare(that) > 0 + + /** Returns true if `this` is less than or equal to `that`. */ + def <= (that: String): Boolean = compare(that) <= 0 + + /** Returns true if `this` is greater than or equal to `that`. */ + def >= (that: String): Boolean = compare(that) >= 0 + + /** Counts the number of chars in this string which satisfy a predicate */ + def count(p: (Char) => Boolean): Int = { + var i, res = 0 + val len = s.length + while(i < len) { + if(p(s.charAt(i))) res += 1 + i += 1 + } + res + } + + /** Apply `f` to each element for its side effects. + * Note: [U] parameter needed to help scalac's type inference. + */ + def foreach[U](f: Char => U): Unit = { + val len = s.length + var i = 0 + while(i < len) { + f(s.charAt(i)) + i += 1 + } + } + + /** Tests whether a predicate holds for all chars of this string. + * + * @param p the predicate used to test elements. + * @return `true` if this string is empty or the given predicate `p` + * holds for all chars of this string, otherwise `false`. + */ + def forall(@deprecatedName("f", "2.13.3") p: Char => Boolean): Boolean = { + var i = 0 + val len = s.length + while(i < len) { + if(!p(s.charAt(i))) return false + i += 1 + } + true + } + + /** Applies the given binary operator `op` to the given initial value `z` and all chars + * in this string, going left to right. Returns the initial value if this string is + * empty. + * + * If `x,,1,,`, `x,,2,,`, ..., `x,,n,,` are the chars in this string, the + * result is `op( op( ... op( op(z, x,,1,,), x,,2,,) ... ), x,,n,,)`. + * + * @param z An initial value. + * @param op A binary operator. + * @tparam B The result type of the binary operator. + * @return The result of applying `op` to `z` and all chars in this string, + * going left to right. Returns `z` if this string is empty. + */ + def foldLeft[B](z: B)(op: (B, Char) => B): B = { + var v = z + var i = 0 + val len = s.length + while(i < len) { + v = op(v, s.charAt(i)) + i += 1 + } + v + } + + /** Applies the given binary operator `op` to all chars in this string and the given + * initial value `z`, going right to left. Returns the initial value if this string is + * empty. + * + * If `x,,1,,`, `x,,2,,`, ..., `x,,n,,` are the chars in this string, the + * result is `op(x,,1,,, op(x,,2,,, op( ... op(x,,n,,, z) ... )))`. + * + * @param z An initial value. + * @param op A binary operator. + * @tparam B The result type of the binary operator. + * @return The result of applying `op` to all chars in this string + * and `z`, going right to left. Returns `z` if this string + * is empty. + */ + def foldRight[B](z: B)(op: (Char, B) => B): B = { + var v = z + var i = s.length - 1 + while(i >= 0) { + v = op(s.charAt(i), v) + i -= 1 + } + v + } + + /** Alias for [[foldLeft]]. + * + * The type parameter is more restrictive than for `foldLeft` to be + * consistent with [[IterableOnceOps.fold]]. + * + * @tparam A1 The type parameter for the binary operator, a supertype of `Char`. + * @param z An initial value. + * @param op A binary operator. + * @return The result of applying `op` to `z` and all chars in this string, + * going left to right. Returns `z` if this string is empty. + */ + @inline def fold[A1 >: Char](z: A1)(op: (A1, A1) => A1): A1 = foldLeft(z)(op) + + /** Selects the first char of this string. + * @return the first char of this string. + * @throws NoSuchElementException if the string is empty. + */ + def head: Char = if(s.isEmpty) throw new NoSuchElementException("head of empty String") else s.charAt(0) + + /** Optionally selects the first char. + * @return the first char of this string if it is nonempty, + * `None` if it is empty. + */ + def headOption: Option[Char] = + if(s.isEmpty) None else Some(s.charAt(0)) + + /** Selects the last char of this string. + * @return the last char of this string. + * @throws NoSuchElementException if the string is empty. + */ + def last: Char = if(s.isEmpty) throw new NoSuchElementException("last of empty String") else s.charAt(s.length-1) + + /** Optionally selects the last char. + * @return the last char of this string if it is nonempty, + * `None` if it is empty. + */ + def lastOption: Option[Char] = + if(s.isEmpty) None else Some(s.charAt(s.length-1)) + + /** Produces the range of all indices of this string. + * + * @return a `Range` value from `0` to one less than the length of this string. + */ + def indices: Range = Range(0, s.length) + + /** Iterator can be used only once */ + def iterator: Iterator[Char] = new StringIterator(s) + + /** Stepper can be used with Java 8 Streams. This method is equivalent to a call to + * [[charStepper]]. See also [[codePointStepper]]. + */ + @inline def stepper: IntStepper with EfficientSplit = charStepper + + /** Steps over characters in this string. Values are packed in `Int` for efficiency + * and compatibility with Java 8 Streams which have an efficient specialization for `Int`. + */ + @inline def charStepper: IntStepper with EfficientSplit = new CharStringStepper(s, 0, s.length) + + /** Steps over code points in this string. + */ + @inline def codePointStepper: IntStepper with EfficientSplit = new CodePointStringStepper(s, 0, s.length) + + /** Tests whether the string is not empty. */ + @inline def nonEmpty: Boolean = !s.isEmpty + + /** Returns new sequence with elements in reversed order. + * @note $unicodeunaware + */ + def reverse: String = new JStringBuilder(s).reverse().toString + + /** An iterator yielding chars in reversed order. + * + * Note: `xs.reverseIterator` is the same as `xs.reverse.iterator` but implemented more efficiently. + * + * @return an iterator yielding the chars of this string in reversed order + */ + def reverseIterator: Iterator[Char] = new ReverseIterator(s) + + /** Creates a non-strict filter of this string. + * + * @note the difference between `c filter p` and `c withFilter p` is that + * the former creates a new string, whereas the latter only + * restricts the domain of subsequent `map`, `flatMap`, `foreach`, + * and `withFilter` operations. + * + * @param p the predicate used to test elements. + * @return an object of class `stringOps.WithFilter`, which supports + * `map`, `flatMap`, `foreach`, and `withFilter` operations. + * All these operations apply to those chars of this string + * which satisfy the predicate `p`. + */ + def withFilter(p: Char => Boolean): StringOps.WithFilter = new StringOps.WithFilter(p, s) + + /** The rest of the string without its first char. + * @throws UnsupportedOperationException if the string is empty. + * @note $unicodeunaware + */ + def tail: String = if(s.isEmpty) throw new UnsupportedOperationException("tail of empty String") else slice(1, s.length) + + /** The initial part of the string without its last char. + * @throws UnsupportedOperationException if the string is empty. + * @note $unicodeunaware + */ + def init: String = if(s.isEmpty) throw new UnsupportedOperationException("init of empty String") else slice(0, s.length-1) + + /** A string containing the first `n` chars of this string. + * @note $unicodeunaware + */ + def take(n: Int): String = slice(0, min(n, s.length)) + + /** The rest of the string without its `n` first chars. + * @note $unicodeunaware + */ + def drop(n: Int): String = slice(min(n, s.length), s.length) + + /** A string containing the last `n` chars of this string. + * @note $unicodeunaware + */ + def takeRight(n: Int): String = drop(s.length - max(n, 0)) + + /** The rest of the string without its `n` last chars. + * @note $unicodeunaware + */ + def dropRight(n: Int): String = take(s.length - max(n, 0)) + + /** Iterates over the tails of this string. The first value will be this + * string and the final one will be an empty string, with the intervening + * values the results of successive applications of `tail`. + * + * @return an iterator over all the tails of this string + * @note $unicodeunaware + */ + def tails: Iterator[String] = iterateUntilEmpty(_.tail) + + /** Iterates over the inits of this string. The first value will be this + * string and the final one will be an empty string, with the intervening + * values the results of successive applications of `init`. + * + * @return an iterator over all the inits of this string + * @note $unicodeunaware + */ + def inits: Iterator[String] = iterateUntilEmpty(_.init) + + // A helper for tails and inits. + private[this] def iterateUntilEmpty(f: String => String): Iterator[String] = + Iterator.iterate(s)(f).takeWhile(x => !x.isEmpty) ++ Iterator.single("") + + /** Selects all chars of this string which satisfy a predicate. */ + def filter(pred: Char => Boolean): String = { + val len = s.length + val sb = new JStringBuilder(len) + var i = 0 + while (i < len) { + val x = s.charAt(i) + if(pred(x)) sb.append(x) + i += 1 + } + if(len == sb.length()) s else sb.toString + } + + /** Selects all chars of this string which do not satisfy a predicate. */ + @inline def filterNot(pred: Char => Boolean): String = filter(c => !pred(c)) + + /** Copy chars of this string to an array. + * Fills the given array `xs` starting at index 0. + * Copying will stop once either the entire string has been copied + * or the end of the array is reached + * + * @param xs the array to fill. + */ + @inline def copyToArray(xs: Array[Char]): Int = + copyToArray(xs, 0, Int.MaxValue) + + /** Copy chars of this string to an array. + * Fills the given array `xs` starting at index `start`. + * Copying will stop once either the entire string has been copied + * or the end of the array is reached + * + * @param xs the array to fill. + * @param start the starting index. + */ + @inline def copyToArray(xs: Array[Char], start: Int): Int = + copyToArray(xs, start, Int.MaxValue) + + /** Copy chars of this string to an array. + * Fills the given array `xs` starting at index `start` with at most `len` chars. + * Copying will stop once either the entire string has been copied, + * or the end of the array is reached or `len` chars have been copied. + * + * @param xs the array to fill. + * @param start the starting index. + * @param len the maximal number of elements to copy. + */ + def copyToArray(xs: Array[Char], start: Int, len: Int): Int = { + val copied = IterableOnce.elemsToCopyToArray(s.length, xs.length, start, len) + if (copied > 0) { + s.getChars(0, copied, xs, start) + } + copied + } + + /** Finds index of the first char satisfying some predicate after or at some start index. + * + * @param p the predicate used to test elements. + * @param from the start index + * @return the index `>= from` of the first element of this string that satisfies the predicate `p`, + * or `-1`, if none exists. + */ + def indexWhere(p: Char => Boolean, from: Int = 0): Int = { + val len = s.length + var i = from + while(i < len) { + if(p(s.charAt(i))) return i + i += 1 + } + -1 + } + + /** Finds index of the last char satisfying some predicate before or at some end index. + * + * @param p the predicate used to test elements. + * @param end the end index + * @return the index `<= end` of the last element of this string that satisfies the predicate `p`, + * or `-1`, if none exists. + */ + def lastIndexWhere(p: Char => Boolean, end: Int = Int.MaxValue): Int = { + val len = s.length + var i = min(end, len-1) + while(i >= 0) { + if(p(s.charAt(i))) return i + i -= 1 + } + -1 + } + + /** Tests whether a predicate holds for at least one char of this string. */ + def exists(p: Char => Boolean): Boolean = indexWhere(p) != -1 + + /** Finds the first char of the string satisfying a predicate, if any. + * + * @param p the predicate used to test elements. + * @return an option value containing the first element in the string + * that satisfies `p`, or `None` if none exists. + */ + def find(p: Char => Boolean): Option[Char] = indexWhere(p) match { + case -1 => None + case i => Some(s.charAt(i)) + } + + /** Drops longest prefix of chars that satisfy a predicate. + * + * @param p The predicate used to test elements. + * @return the longest suffix of this string whose first element + * does not satisfy the predicate `p`. + */ + def dropWhile(p: Char => Boolean): String = indexWhere(c => !p(c)) match { + case -1 => "" + case i => s.substring(i) + } + + /** Takes longest prefix of chars that satisfy a predicate. */ + def takeWhile(p: Char => Boolean): String = indexWhere(c => !p(c)) match { + case -1 => s + case i => s.substring(0, i) + } + + /** Splits this string into two at a given position. + * Note: `c splitAt n` is equivalent to `(c take n, c drop n)`. + * + * @param n the position at which to split. + * @return a pair of strings consisting of the first `n` + * chars of this string, and the other chars. + * @note $unicodeunaware + */ + def splitAt(n: Int): (String, String) = (take(n), drop(n)) + + /** Splits this string into a prefix/suffix pair according to a predicate. + * + * Note: `c span p` is equivalent to (but more efficient than) + * `(c takeWhile p, c dropWhile p)`, provided the evaluation of the + * predicate `p` does not cause any side-effects. + * + * @param p the test predicate + * @return a pair consisting of the longest prefix of this string whose + * chars all satisfy `p`, and the rest of this string. + */ + def span(p: Char => Boolean): (String, String) = indexWhere(c => !p(c)) match { + case -1 => (s, "") + case i => (s.substring(0, i), s.substring(i)) + } + + /** Partitions elements in fixed size strings. + * @see [[scala.collection.Iterator]], method `grouped` + * + * @param size the number of elements per group + * @return An iterator producing strings of size `size`, except the + * last will be less than size `size` if the elements don't divide evenly. + * @note $unicodeunaware + */ + def grouped(size: Int): Iterator[String] = new StringOps.GroupedIterator(s, size) + + /** A pair of, first, all chars that satisfy predicate `p` and, second, all chars that do not. */ + def partition(p: Char => Boolean): (String, String) = { + val res1, res2 = new JStringBuilder + var i = 0 + val len = s.length + while(i < len) { + val x = s.charAt(i) + (if(p(x)) res1 else res2).append(x) + i += 1 + } + (res1.toString, res2.toString) + } + + /** Applies a function `f` to each character of the string and returns a pair of strings: the first one + * made of those characters returned by `f` that were wrapped in [[scala.util.Left]], and the second + * one made of those wrapped in [[scala.util.Right]]. + * + * Example: + * {{{ + * val xs = "1one2two3three" partitionMap { c => + * if (c > 'a') Left(c) else Right(c) + * } + * // xs == ("onetwothree", "123") + * }}} + * + * @param f the 'split function' mapping the elements of this string to an [[scala.util.Either]] + * + * @return a pair of strings: the first one made of those characters returned by `f` that were wrapped in [[scala.util.Left]], + * and the second one made of those wrapped in [[scala.util.Right]]. + */ + def partitionMap(f: Char => Either[Char,Char]): (String, String) = { + val res1, res2 = new JStringBuilder + var i = 0 + val len = s.length + while(i < len) { + f(s.charAt(i)) match { + case Left(c) => res1.append(c) + case Right(c) => res2.append(c) + } + i += 1 + } + (res1.toString, res2.toString) + } + + /** Analogous to `zip` except that the elements in each collection are not consumed until a strict operation is + * invoked on the returned `LazyZip2` decorator. + * + * Calls to `lazyZip` can be chained to support higher arities (up to 4) without incurring the expense of + * constructing and deconstructing intermediary tuples. + * + * {{{ + * val xs = List(1, 2, 3) + * val res = (xs lazyZip xs lazyZip xs lazyZip xs).map((a, b, c, d) => a + b + c + d) + * // res == List(4, 8, 12) + * }}} + * + * @param that the iterable providing the second element of each eventual pair + * @tparam B the type of the second element in each eventual pair + * @return a decorator `LazyZip2` that allows strict operations to be performed on the lazily evaluated pairs + * or chained calls to `lazyZip`. Implicit conversion to `Iterable[(A, B)]` is also supported. + */ + def lazyZip[B](that: Iterable[B]): LazyZip2[Char, B, String] = new LazyZip2(s, new WrappedString(s), that) + + + /* ************************************************************************************************************ + The remaining methods are provided for completeness but they delegate to WrappedString implementations which + may not provide the best possible performance. We need them in `StringOps` because their return type + mentions `C` (which is `String` in `StringOps` and `WrappedString` in `WrappedString`). + ************************************************************************************************************ */ + + + /** Computes the multiset difference between this string and another sequence. + * + * @param that the sequence of chars to remove + * @return a new string which contains all chars of this string + * except some of occurrences of elements that also appear in `that`. + * If an element value `x` appears + * ''n'' times in `that`, then the first ''n'' occurrences of `x` will not form + * part of the result, but any following occurrences will. + * @note $unicodeunaware + */ + def diff[B >: Char](that: Seq[B]): String = new WrappedString(s).diff(that).unwrap + + /** Computes the multiset intersection between this string and another sequence. + * + * @param that the sequence of chars to intersect with. + * @return a new string which contains all chars of this string + * which also appear in `that`. + * If an element value `x` appears + * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained + * in the result, but any following occurrences will be omitted. + * @note $unicodeunaware + */ + def intersect[B >: Char](that: Seq[B]): String = new WrappedString(s).intersect(that).unwrap + + /** Selects all distinct chars of this string ignoring the duplicates. + * + * @note $unicodeunaware + */ + def distinct: String = new WrappedString(s).distinct.unwrap + + /** Selects all distinct chars of this string ignoring the duplicates as determined by `==` after applying + * the transforming function `f`. + * + * @param f The transforming function whose result is used to determine the uniqueness of each element + * @tparam B the type of the elements after being transformed by `f` + * @return a new string consisting of all the chars of this string without duplicates. + * @note $unicodeunaware + */ + def distinctBy[B](f: Char => B): String = new WrappedString(s).distinctBy(f).unwrap + + /** Sorts the characters of this string according to an Ordering. + * + * The sort is stable. That is, elements that are equal (as determined by + * `ord.compare`) appear in the same order in the sorted sequence as in the original. + * + * @see [[scala.math.Ordering]] + * + * @param ord the ordering to be used to compare elements. + * @return a string consisting of the chars of this string + * sorted according to the ordering `ord`. + * @note $unicodeunaware + */ + def sorted[B >: Char](implicit ord: Ordering[B]): String = new WrappedString(s).sorted(ord).unwrap + + /** Sorts this string according to a comparison function. + * + * The sort is stable. That is, elements that are equal (as determined by + * `lt`) appear in the same order in the sorted sequence as in the original. + * + * @param lt the comparison function which tests whether + * its first argument precedes its second argument in + * the desired ordering. + * @return a string consisting of the elements of this string + * sorted according to the comparison function `lt`. + * @note $unicodeunaware + */ + def sortWith(lt: (Char, Char) => Boolean): String = new WrappedString(s).sortWith(lt).unwrap + + /** Sorts this string according to the Ordering which results from transforming + * an implicitly given Ordering with a transformation function. + * + * The sort is stable. That is, elements that are equal (as determined by + * `ord.compare`) appear in the same order in the sorted sequence as in the original. + * + * @see [[scala.math.Ordering]] + * @param f the transformation function mapping elements + * to some other domain `B`. + * @param ord the ordering assumed on domain `B`. + * @tparam B the target type of the transformation `f`, and the type where + * the ordering `ord` is defined. + * @return a string consisting of the chars of this string + * sorted according to the ordering where `x < y` if + * `ord.lt(f(x), f(y))`. + * @note $unicodeunaware + */ + def sortBy[B](f: Char => B)(implicit ord: Ordering[B]): String = new WrappedString(s).sortBy(f)(ord).unwrap + + /** Partitions this string into a map of strings according to some discriminator function. + * + * @param f the discriminator function. + * @tparam K the type of keys returned by the discriminator function. + * @return A map from keys to strings such that the following invariant holds: + * {{{ + * (xs groupBy f)(k) = xs filter (x => f(x) == k) + * }}} + * That is, every key `k` is bound to a string of those elements `x` + * for which `f(x)` equals `k`. + * @note $unicodeunaware + */ + def groupBy[K](f: Char => K): immutable.Map[K, String] = new WrappedString(s).groupBy(f).view.mapValues(_.unwrap).toMap + + /** Groups chars in fixed size blocks by passing a "sliding window" + * over them (as opposed to partitioning them, as is done in grouped.) + * @see [[scala.collection.Iterator]], method `sliding` + * + * @param size the number of chars per group + * @param step the distance between the first chars of successive groups + * @return An iterator producing strings of size `size`, except the + * last element (which may be the only element) will be truncated + * if there are fewer than `size` chars remaining to be grouped. + * @note $unicodeunaware + */ + def sliding(size: Int, step: Int = 1): Iterator[String] = new WrappedString(s).sliding(size, step).map(_.unwrap) + + /** Iterates over combinations of elements. + * + * A '''combination''' of length `n` is a sequence of `n` elements selected in order of their first index in this sequence. + * + * For example, `"xyx"` has two combinations of length 2. The `x` is selected first: `"xx"`, `"xy"`. + * The sequence `"yx"` is not returned as a combination because it is subsumed by `"xy"`. + * + * If there is more than one way to generate the same combination, only one will be returned. + * + * For example, the result `"xy"` arbitrarily selected one of the `x` elements. + * + * As a further illustration, `"xyxx"` has three different ways to generate `"xy"` because there are three elements `x` + * to choose from. Moreover, there are three unordered pairs `"xx"` but only one is returned. + * + * It is not specified which of these equal combinations is returned. It is an implementation detail + * that should not be relied on. For example, the combination `"xx"` does not necessarily contain + * the first `x` in this sequence. This behavior is observable if the elements compare equal + * but are not identical. + * + * As a consequence, `"xyx".combinations(3).next()` is `"xxy"`: the combination does not reflect the order + * of the original sequence, but the order in which elements were selected, by "first index"; + * the order of each `x` element is also arbitrary. + * + * @return An Iterator which traverses the n-element combinations of this string. + * @example {{{ + * "abbbc".combinations(2).foreach(println) + * // ab + * // ac + * // bb + * // bc + * "bab".combinations(2).foreach(println) + * // bb + * // ba + * }}} + * @note $unicodeunaware + */ + def combinations(n: Int): Iterator[String] = new WrappedString(s).combinations(n).map(_.unwrap) + + /** Iterates over distinct permutations of elements. + * + * @return An Iterator which traverses the distinct permutations of this string. + * @example {{{ + * "abb".permutations.foreach(println) + * // abb + * // bab + * // bba + * }}} + * @note $unicodeunaware + */ + def permutations: Iterator[String] = new WrappedString(s).permutations.map(_.unwrap) +} + +final case class StringView(s: String) extends AbstractIndexedSeqView[Char] { + def length = s.length + @throws[StringIndexOutOfBoundsException] + def apply(n: Int) = s.charAt(n) + override def toString: String = s"StringView($s)" +} diff --git a/library/src/scala/collection/StringParsers.scala b/library/src/scala/collection/StringParsers.scala new file mode 100644 index 000000000000..cfaa4f86985f --- /dev/null +++ b/library/src/scala/collection/StringParsers.scala @@ -0,0 +1,322 @@ +/* + * 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 collection + +import scala.language.`2.13` +import scala.annotation.tailrec + +/** A module containing the implementations of parsers from strings to numeric types, and boolean + */ +private[scala] object StringParsers { + + //compile-time constant helpers + + //Int.MinValue == -2147483648 + private final val intOverflowBoundary = -214748364 + private final val intOverflowDigit = 9 + //Long.MinValue == -9223372036854775808L + private final val longOverflowBoundary = -922337203685477580L + private final val longOverflowDigit = 9 + + private final val POS = true + + @inline + private[this] final def decValue(ch: Char): Int = java.lang.Character.digit(ch, 10) + + @inline + private[this] final def stepToOverflow(from: String, len: Int, agg: Int, isPositive: Boolean, min: Int): Option[Int] = { + @tailrec + def rec(i: Int, agg: Int): Option[Int] = + if (agg < min) None + else if (i == len) { + if (!isPositive) Some(agg) + else if (agg == min) None + else Some(-agg) + } + else { + val digit = decValue(from.charAt(i)) + if (digit == -1) None + else rec(i + 1, agg * 10 - digit) + } + rec(1, agg) + } + + @inline + private[this] final def isDigit(c: Char): Boolean = c >= '0' && c <= '9' + + //bool + @inline + final def parseBool(from: String): Option[Boolean] = + if (from.equalsIgnoreCase("true")) Some(true) + else if (from.equalsIgnoreCase("false")) Some(false) + else None + + //integral types + final def parseByte(from: String): Option[Byte] = { + val len = from.length() + //empty strings parse to None + if (len == 0) None + else { + val first = from.charAt(0) + val v = decValue(first) + if (len == 1) { + //"+" and "-" parse to None + if (v > -1) Some(v.toByte) + else None + } + else if (v > -1) stepToOverflow(from, len, -v, POS, Byte.MinValue).map(_.toByte) + else if (first == '+') stepToOverflow(from, len, 0, POS, Byte.MinValue).map(_.toByte) + else if (first == '-') stepToOverflow(from, len, 0, !POS, Byte.MinValue).map(_.toByte) + else None + } + } + + final def parseShort(from: String): Option[Short] = { + val len = from.length() + //empty strings parse to None + if (len == 0) None + else { + val first = from.charAt(0) + val v = decValue(first) + if (len == 1) { + //"+" and "-" parse to None + if (v > -1) Some(v.toShort) + else None + } + else if (v > -1) stepToOverflow(from, len, -v, POS, Short.MinValue).map(_.toShort) + else if (first == '+') stepToOverflow(from, len, 0, POS, Short.MinValue).map(_.toShort) + else if (first == '-') stepToOverflow(from, len, 0, !POS, Short.MinValue).map(_.toShort) + else None + } + } + + final def parseInt(from: String): Option[Int] = { + val len = from.length() + + @tailrec + def step(i: Int, agg: Int, isPositive: Boolean): Option[Int] = { + if (i == len) { + if (!isPositive) Some(agg) + else if (agg == Int.MinValue) None + else Some(-agg) + } + else if (agg < intOverflowBoundary) None + else { + val digit = decValue(from.charAt(i)) + if (digit == -1 || (agg == intOverflowBoundary && digit == intOverflowDigit)) None + else step(i + 1, (agg * 10) - digit, isPositive) + } + } + //empty strings parse to None + if (len == 0) None + else { + val first = from.charAt(0) + val v = decValue(first) + if (len == 1) { + //"+" and "-" parse to None + if (v > -1) Some(v) + else None + } + else if (v > -1) step(1, -v, POS) + else if (first == '+') step(1, 0, POS) + else if (first == '-') step(1, 0, !POS) + else None + } + } + + final def parseLong(from: String): Option[Long] = { + //like parseInt, but Longer + val len = from.length() + + @tailrec + def step(i: Int, agg: Long, isPositive: Boolean): Option[Long] = { + if (i == len) { + if (isPositive && agg == Long.MinValue) None + else if (isPositive) Some(-agg) + else Some(agg) + } + else if (agg < longOverflowBoundary) None + else { + val digit = decValue(from.charAt(i)) + if (digit == -1 || (agg == longOverflowBoundary && digit == longOverflowDigit)) None + else step(i + 1, agg * 10 - digit, isPositive) + } + } + //empty strings parse to None + if (len == 0) None + else { + val first = from.charAt(0) + val v = decValue(first).toLong + if (len == 1) { + //"+" and "-" parse to None + if (v > -1) Some(v) + else None + } + else if (v > -1) step(1, -v, POS) + else if (first == '+') step(1, 0, POS) + else if (first == '-') step(1, 0, !POS) + else None + } + } + + //floating point + final def checkFloatFormat(format: String): Boolean = { + //indices are tracked with a start index which points *at* the first index + //and an end index which points *after* the last index + //so that slice length === end - start + //thus start == end <=> empty slice + //and format.substring(start, end) is equivalent to the slice + + //some utilities for working with index bounds into the original string + @inline + def forAllBetween(start: Int, end: Int, pred: Char => Boolean): Boolean = { + @tailrec + def rec(i: Int): Boolean = i >= end || pred(format.charAt(i)) && rec(i + 1) + rec(start) + } + + //one after last index for the predicate to hold, or `from` if none hold + //may point after the end of the string + @inline + def skipIndexWhile(predicate: Char => Boolean, from: Int, until: Int): Int = { + @tailrec @inline + def rec(i: Int): Int = if ((i < until) && predicate(format.charAt(i))) rec(i + 1) + else i + rec(from) + } + + + def isHexFloatLiteral(startIndex: Int, endIndex: Int): Boolean = { + def isHexDigit(ch: Char) = ((ch >= '0' && ch <= '9') || + (ch >= 'a' && ch <= 'f') || + (ch >= 'A' && ch <= 'F')) + + def prefixOK(startIndex: Int, endIndex: Int): Boolean = { + val len = endIndex - startIndex + (len > 0) && { + //the prefix part is + //hexDigits + //hexDigits. + //hexDigits.hexDigits + //.hexDigits + //but not . + if (format.charAt(startIndex) == '.') { + (len > 1) && forAllBetween(startIndex + 1, endIndex, isHexDigit) + } else { + val noLeading = skipIndexWhile(isHexDigit, startIndex, endIndex) + (noLeading >= endIndex) || + ((format.charAt(noLeading) == '.') && forAllBetween(noLeading + 1, endIndex, isHexDigit)) + } + } + } + + def postfixOK(startIndex: Int, endIndex: Int): Boolean = + (startIndex < endIndex) && { + (forAllBetween(startIndex, endIndex, isDigit)) || { + val startchar = format.charAt(startIndex) + (startchar == '+' || startchar == '-') && + (endIndex - startIndex > 1) && + forAllBetween(startIndex + 1, endIndex, isDigit) + } + } + // prefix [pP] postfix + val pIndex = format.indexWhere(ch => ch == 'p' || ch == 'P', startIndex) + (pIndex <= endIndex) && prefixOK(startIndex, pIndex) && postfixOK(pIndex + 1, endIndex) + } + + def isDecFloatLiteral(startIndex: Int, endIndex: Int): Boolean = { + //invariant: endIndex > startIndex + + def isExp(c: Char): Boolean = c == 'e' || c == 'E' + + def expOK(startIndex: Int, endIndex: Int): Boolean = + (startIndex < endIndex) && { + val startChar = format.charAt(startIndex) + if (startChar == '+' || startChar == '-') + (endIndex > (startIndex + 1)) && + skipIndexWhile(isDigit, startIndex + 1, endIndex) == endIndex + else skipIndexWhile(isDigit, startIndex, endIndex) == endIndex + } + + //significant can be one of + //* digits.digits + //* .digits + //* digits. + //but not just . + val startChar = format.charAt(startIndex) + if (startChar == '.') { + val noSignificant = skipIndexWhile(isDigit, startIndex + 1, endIndex) + // a digit is required followed by optional exp + (noSignificant > startIndex + 1) && (noSignificant >= endIndex || + isExp(format.charAt(noSignificant)) && expOK(noSignificant + 1, endIndex) + ) + } + else if (isDigit(startChar)) { + // one set of digits, then optionally a period, then optionally another set of digits, then optionally an exponent + val noInt = skipIndexWhile(isDigit, startIndex, endIndex) + // just the digits + (noInt == endIndex) || { + if (format.charAt(noInt) == '.') { + val noSignificant = skipIndexWhile(isDigit, noInt + 1, endIndex) + (noSignificant >= endIndex) || //no exponent + isExp(format.charAt(noSignificant)) && expOK(noSignificant + 1, endIndex) + } else + isExp(format.charAt(noInt)) && expOK(noInt + 1, endIndex) + } + } + else false + } + + //count 0x00 to 0x20 as "whitespace", and nothing else + val unspacedStart = format.indexWhere(ch => ch.toInt > 0x20) + val unspacedEnd = format.lastIndexWhere(ch => ch.toInt > 0x20) + 1 + + if (unspacedStart == -1 || unspacedStart >= unspacedEnd || unspacedEnd <= 0) false + else { + //all formats can have a sign + val unsigned = { + val startchar = format.charAt(unspacedStart) + if (startchar == '-' || startchar == '+') unspacedStart + 1 else unspacedStart + } + if (unsigned >= unspacedEnd) false + //that's it for NaN and Infinity + else if (format.charAt(unsigned) == 'N') format.substring(unsigned, unspacedEnd) == "NaN" + else if (format.charAt(unsigned) == 'I') format.substring(unsigned, unspacedEnd) == "Infinity" + else { + //all other formats can have a format suffix + val desuffixed = { + val endchar = format.charAt(unspacedEnd - 1) + if (endchar == 'f' || endchar == 'F' || endchar == 'd' || endchar == 'D') unspacedEnd - 1 + else unspacedEnd + } + val len = desuffixed - unsigned + if (len <= 0) false + else if (len >= 2 && (format.charAt(unsigned + 1) == 'x' || format.charAt(unsigned + 1) == 'X')) + format.charAt(unsigned) == '0' && isHexFloatLiteral(unsigned + 2, desuffixed) + else isDecFloatLiteral(unsigned, desuffixed) + } + } + } + + @inline + def parseFloat(from: String): Option[Float] = + if (checkFloatFormat(from)) Some(java.lang.Float.parseFloat(from)) + else None + + @inline + def parseDouble(from: String): Option[Double] = + if (checkFloatFormat(from)) Some(java.lang.Double.parseDouble(from)) + else None + +} diff --git a/library/src/scala/collection/View.scala b/library/src/scala/collection/View.scala new file mode 100644 index 000000000000..690ba49aafe8 --- /dev/null +++ b/library/src/scala/collection/View.scala @@ -0,0 +1,536 @@ +/* + * 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.collection + +import scala.language.`2.13` +import scala.annotation.{nowarn, tailrec} +import scala.collection.mutable.{ArrayBuffer, Builder} +import scala.collection.immutable.LazyList + +/** Views are collections whose transformation operations are non strict: the resulting elements + * are evaluated only when the view is effectively traversed (e.g. using `foreach` or `foldLeft`), + * or when the view is converted to a strict collection type (using the `to` operation). + * @define coll view + * @define Coll `View` + */ +trait View[+A] extends Iterable[A] with IterableOps[A, View, View[A]] with IterableFactoryDefaults[A, View] with Serializable { + + override def view: View[A] = this + + override def iterableFactory: IterableFactory[View] = View + + override def empty: scala.collection.View[A] = iterableFactory.empty + + override def toString: String = className + "()" + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "View" + + @deprecated("Views no longer know about their underlying collection type; .force always returns an IndexedSeq", "2.13.0") + @`inline` def force: IndexedSeq[A] = toIndexedSeq +} + +/** This object reifies operations on views as case classes + * + * @define Coll View + * @define coll view + */ +@SerialVersionUID(3L) +object View extends IterableFactory[View] { + + /** + * @return A `View[A]` whose underlying iterator is provided by the `it` parameter-less function. + * + * @param it Function creating the iterator to be used by the view. This function must always return + * a fresh `Iterator`, otherwise the resulting view will be effectively iterable only once. + * + * @tparam A View element type + */ + def fromIteratorProvider[A](it: () => Iterator[A]): View[A] = new AbstractView[A] { + def iterator = it() + } + + /** + * @return A view iterating over the given `Iterable` + * + * @param it The `IterableOnce` to view. A proper `Iterable` is used directly. If it is really only + * `IterableOnce` it gets memoized on the first traversal. + * + * @tparam E View element type + */ + def from[E](it: IterableOnce[E]): View[E] = it match { + case it: View[E] => it + case it: Iterable[E] => View.fromIteratorProvider(() => it.iterator) + case _ => LazyList.from(it).view + } + + def empty[A]: View[A] = Empty + + def newBuilder[A]: Builder[A, View[A]] = ArrayBuffer.newBuilder[A].mapResult(from) + + override def apply[A](xs: A*): View[A] = new Elems(xs: _*) + + /** The empty view */ + @SerialVersionUID(3L) + case object Empty extends AbstractView[Nothing] { + def iterator = Iterator.empty + override def knownSize = 0 + override def isEmpty: Boolean = true + } + + /** A view with exactly one element */ + @SerialVersionUID(3L) + class Single[A](a: A) extends AbstractView[A] { + def iterator: Iterator[A] = Iterator.single(a) + override def knownSize: Int = 1 + override def isEmpty: Boolean = false + } + + /** A view with given elements */ + @SerialVersionUID(3L) + class Elems[A](xs: A*) extends AbstractView[A] { + def iterator = xs.iterator + override def knownSize = xs.knownSize + override def isEmpty: Boolean = xs.isEmpty + } + + /** A view containing the results of some element computation a number of times. */ + @SerialVersionUID(3L) + class Fill[A](n: Int)(elem: => A) extends AbstractView[A] { + def iterator = Iterator.fill(n)(elem) + override def knownSize: Int = 0 max n + override def isEmpty: Boolean = n <= 0 + } + + /** A view containing values of a given function over a range of integer values starting from 0. */ + @SerialVersionUID(3L) + class Tabulate[A](n: Int)(f: Int => A) extends AbstractView[A] { + def iterator: Iterator[A] = Iterator.tabulate(n)(f) + override def knownSize: Int = 0 max n + override def isEmpty: Boolean = n <= 0 + } + + /** A view containing repeated applications of a function to a start value */ + @SerialVersionUID(3L) + class Iterate[A](start: A, len: Int)(f: A => A) extends AbstractView[A] { + def iterator: Iterator[A] = Iterator.iterate(start)(f).take(len) + override def knownSize: Int = 0 max len + override def isEmpty: Boolean = len <= 0 + } + + /** A view that uses a function `f` to produce elements of type `A` and update + * an internal state `S`. + */ + @SerialVersionUID(3L) + class Unfold[A, S](initial: S)(f: S => Option[(A, S)]) extends AbstractView[A] { + def iterator: Iterator[A] = Iterator.unfold(initial)(f) + } + + /** An `IterableOps` whose collection type and collection type constructor are unknown */ + type SomeIterableOps[A] = IterableOps[A, AnyConstr, _] + + /** A view that filters an underlying collection. */ + @SerialVersionUID(3L) + class Filter[A](val underlying: SomeIterableOps[A], val p: A => Boolean, val isFlipped: Boolean) extends AbstractView[A] { + def iterator = underlying.iterator.filterImpl(p, isFlipped) + override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = iterator.isEmpty + } + + object Filter { + def apply[A](underlying: Iterable[A], p: A => Boolean, isFlipped: Boolean): Filter[A] = + underlying match { + case filter: Filter[A] if filter.isFlipped == isFlipped => new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) + case _ => new Filter(underlying, p, isFlipped) + } + } + + /** A view that removes the duplicated elements as determined by the transformation function `f` */ + @SerialVersionUID(3L) + class DistinctBy[A, B](underlying: SomeIterableOps[A], f: A => B) extends AbstractView[A] { + def iterator: Iterator[A] = underlying.iterator.distinctBy(f) + override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = underlying.isEmpty + } + + @SerialVersionUID(3L) + class LeftPartitionMapped[A, A1, A2](underlying: SomeIterableOps[A], f: A => Either[A1, A2]) extends AbstractView[A1] { + def iterator: AbstractIterator[A1] = new AbstractIterator[A1] { + private[this] val self = underlying.iterator + private[this] var hd: A1 = _ + private[this] var hdDefined: Boolean = false + def hasNext = hdDefined || { + @tailrec + def findNext(): Boolean = + if (self.hasNext) { + f(self.next()) match { + case Left(a1) => hd = a1; hdDefined = true; true + case Right(_) => findNext() + } + } else false + findNext() + } + def next() = + if (hasNext) { + hdDefined = false + hd + } else Iterator.empty.next() + } + } + + @SerialVersionUID(3L) + class RightPartitionMapped[A, A1, A2](underlying: SomeIterableOps[A], f: A => Either[A1, A2]) extends AbstractView[A2] { + def iterator: AbstractIterator[A2] = new AbstractIterator[A2] { + private[this] val self = underlying.iterator + private[this] var hd: A2 = _ + private[this] var hdDefined: Boolean = false + def hasNext = hdDefined || { + @tailrec + def findNext(): Boolean = + if (self.hasNext) { + f(self.next()) match { + case Left(_) => findNext() + case Right(a2) => hd = a2; hdDefined = true; true + } + } else false + findNext() + } + def next() = + if (hasNext) { + hdDefined = false + hd + } else Iterator.empty.next() + } + } + + /** A view that drops leading elements of the underlying collection. */ + @SerialVersionUID(3L) + class Drop[A](underlying: SomeIterableOps[A], n: Int) extends AbstractView[A] { + def iterator = underlying.iterator.drop(n) + protected val normN = n max 0 + override def knownSize = { + val size = underlying.knownSize + if (size >= 0) (size - normN) max 0 else -1 + } + override def isEmpty: Boolean = iterator.isEmpty + } + + /** A view that drops trailing elements of the underlying collection. */ + @SerialVersionUID(3L) + class DropRight[A](underlying: SomeIterableOps[A], n: Int) extends AbstractView[A] { + def iterator = dropRightIterator(underlying.iterator, n) + protected val normN = n max 0 + override def knownSize = { + val size = underlying.knownSize + if (size >= 0) (size - normN) max 0 else -1 + } + override def isEmpty: Boolean = + if(knownSize >= 0) knownSize == 0 + else iterator.isEmpty + } + + @SerialVersionUID(3L) + class DropWhile[A](underlying: SomeIterableOps[A], p: A => Boolean) extends AbstractView[A] { + def iterator = underlying.iterator.dropWhile(p) + override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = iterator.isEmpty + } + + /** A view that takes leading elements of the underlying collection. */ + @SerialVersionUID(3L) + class Take[+A](underlying: SomeIterableOps[A], n: Int) extends AbstractView[A] { + def iterator = underlying.iterator.take(n) + protected val normN = n max 0 + override def knownSize = { + val size = underlying.knownSize + if (size >= 0) size min normN else -1 + } + override def isEmpty: Boolean = iterator.isEmpty + } + + /** A view that takes trailing elements of the underlying collection. */ + @SerialVersionUID(3L) + class TakeRight[+A](underlying: SomeIterableOps[A], n: Int) extends AbstractView[A] { + def iterator = takeRightIterator(underlying.iterator, n) + protected val normN = n max 0 + override def knownSize = { + val size = underlying.knownSize + if (size >= 0) size min normN else -1 + } + override def isEmpty: Boolean = + if(knownSize >= 0) knownSize == 0 + else iterator.isEmpty + } + + @SerialVersionUID(3L) + class TakeWhile[A](underlying: SomeIterableOps[A], p: A => Boolean) extends AbstractView[A] { + def iterator: Iterator[A] = underlying.iterator.takeWhile(p) + override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = iterator.isEmpty + } + + @SerialVersionUID(3L) + class ScanLeft[+A, +B](underlying: SomeIterableOps[A], z: B, op: (B, A) => B) extends AbstractView[B] { + def iterator: Iterator[B] = underlying.iterator.scanLeft(z)(op) + override def knownSize: Int = { + val size = underlying.knownSize + if (size >= 0) size + 1 else -1 + } + override def isEmpty: Boolean = iterator.isEmpty + } + + /** A view that maps elements of the underlying collection. */ + @SerialVersionUID(3L) + class Map[+A, +B](underlying: SomeIterableOps[A], f: A => B) extends AbstractView[B] { + def iterator = underlying.iterator.map(f) + override def knownSize = underlying.knownSize + override def isEmpty: Boolean = underlying.isEmpty + } + + /** A view that flatmaps elements of the underlying collection. */ + @SerialVersionUID(3L) + class FlatMap[A, B](underlying: SomeIterableOps[A], f: A => IterableOnce[B]) extends AbstractView[B] { + def iterator = underlying.iterator.flatMap(f) + override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = iterator.isEmpty + } + + /** A view that collects elements of the underlying collection. */ + @SerialVersionUID(3L) + class Collect[+A, B](underlying: SomeIterableOps[A], pf: PartialFunction[A, B]) extends AbstractView[B] { + def iterator = underlying.iterator.collect(pf) + } + + /** A view that concatenates elements of the prefix collection or iterator with the elements + * of the suffix collection or iterator. + */ + @SerialVersionUID(3L) + class Concat[A](prefix: SomeIterableOps[A], suffix: SomeIterableOps[A]) extends AbstractView[A] { + def iterator = prefix.iterator ++ suffix.iterator + override def knownSize = { + val prefixSize = prefix.knownSize + if (prefixSize >= 0) { + val suffixSize = suffix.knownSize + if (suffixSize >= 0) prefixSize + suffixSize + else -1 + } + else -1 + } + override def isEmpty: Boolean = prefix.isEmpty && suffix.isEmpty + } + + /** A view that zips elements of the underlying collection with the elements + * of another collection. + */ + @SerialVersionUID(3L) + class Zip[A, B](underlying: SomeIterableOps[A], other: Iterable[B]) extends AbstractView[(A, B)] { + def iterator = underlying.iterator.zip(other) + override def knownSize = { + val s1 = underlying.knownSize + if (s1 == 0) 0 else { + val s2 = other.knownSize + if (s2 == 0) 0 else s1 min s2 + } + } + override def isEmpty: Boolean = underlying.isEmpty || other.isEmpty + } + + /** A view that zips elements of the underlying collection with the elements + * of another collection. If one of the two collections is shorter than the other, + * placeholder elements are used to extend the shorter collection to the length of the longer. + */ + @SerialVersionUID(3L) + class ZipAll[A, B](underlying: SomeIterableOps[A], other: Iterable[B], thisElem: A, thatElem: B) extends AbstractView[(A, B)] { + def iterator = underlying.iterator.zipAll(other, thisElem, thatElem) + override def knownSize = { + val s1 = underlying.knownSize + if(s1 == -1) -1 else { + val s2 = other.knownSize + if(s2 == -1) -1 else s1 max s2 + } + } + override def isEmpty: Boolean = underlying.isEmpty && other.isEmpty + } + + /** A view that appends an element to its elements */ + @SerialVersionUID(3L) + class Appended[+A](underlying: SomeIterableOps[A], elem: A) extends AbstractView[A] { + def iterator: Iterator[A] = new Concat(underlying, new View.Single(elem)).iterator + override def knownSize: Int = { + val size = underlying.knownSize + if (size >= 0) size + 1 else -1 + } + override def isEmpty: Boolean = false + } + + /** A view that prepends an element to its elements */ + @SerialVersionUID(3L) + class Prepended[+A](elem: A, underlying: SomeIterableOps[A]) extends AbstractView[A] { + def iterator: Iterator[A] = new Concat(new View.Single(elem), underlying).iterator + override def knownSize: Int = { + val size = underlying.knownSize + if (size >= 0) size + 1 else -1 + } + override def isEmpty: Boolean = false + } + + @SerialVersionUID(3L) + class Updated[A](underlying: SomeIterableOps[A], index: Int, elem: A) extends AbstractView[A] { + def iterator: Iterator[A] = new AbstractIterator[A] { + private[this] val it = underlying.iterator + private[this] var i = 0 + def next(): A = { + val value = if (i == index) { it.next(); elem } else it.next() + i += 1 + value + } + def hasNext: Boolean = + if(it.hasNext) true + else if(index >= i) throw new IndexOutOfBoundsException(index.toString) + else false + } + override def knownSize: Int = underlying.knownSize + override def isEmpty: Boolean = iterator.isEmpty + } + + @SerialVersionUID(3L) + private[collection] class Patched[A](underlying: SomeIterableOps[A], from: Int, other: IterableOnce[A], replaced: Int) extends AbstractView[A] { + // we may be unable to traverse `other` more than once, so we need to cache it if that's the case + private val _other: Iterable[A] = other match { + case other: Iterable[A] => other + case other => LazyList.from(other) + } + + def iterator: Iterator[A] = underlying.iterator.patch(from, _other.iterator, replaced) + override def knownSize: Int = if (underlying.knownSize == 0 && _other.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = if (knownSize == 0) true else iterator.isEmpty + } + + @SerialVersionUID(3L) + class ZipWithIndex[A](underlying: SomeIterableOps[A]) extends AbstractView[(A, Int)] { + def iterator: Iterator[(A, Int)] = underlying.iterator.zipWithIndex + override def knownSize: Int = underlying.knownSize + override def isEmpty: Boolean = underlying.isEmpty + } + + @SerialVersionUID(3L) + class PadTo[A](underlying: SomeIterableOps[A], len: Int, elem: A) extends AbstractView[A] { + def iterator: Iterator[A] = underlying.iterator.padTo(len, elem) + + override def knownSize: Int = { + val size = underlying.knownSize + if (size >= 0) size max len else -1 + } + override def isEmpty: Boolean = underlying.isEmpty && len <= 0 + } + + private[collection] def takeRightIterator[A](it: Iterator[A], n: Int): Iterator[A] = { + val k = it.knownSize + if(k == 0 || n <= 0) Iterator.empty + else if(n == Int.MaxValue) it + else if(k > 0) it.drop((k-n) max 0) + else new TakeRightIterator[A](it, n) + } + + private final class TakeRightIterator[A](private[this] var underlying: Iterator[A], maxlen: Int) extends AbstractIterator[A] { + private[this] var len: Int = -1 + private[this] var pos: Int = 0 + private[this] var buf: ArrayBuffer[AnyRef] = _ + def init(): Unit = if(buf eq null) { + buf = new ArrayBuffer[AnyRef](maxlen min 256) + len = 0 + while(underlying.hasNext) { + val n = underlying.next().asInstanceOf[AnyRef] + if(pos >= buf.length) buf.addOne(n) + else buf(pos) = n + pos += 1 + if(pos == maxlen) pos = 0 + len += 1 + } + underlying = null + if(len > maxlen) len = maxlen + pos = pos - len + if(pos < 0) pos += maxlen + } + override def knownSize = len + def hasNext: Boolean = { + init() + len > 0 + } + def next(): A = { + init() + if(len == 0) Iterator.empty.next() + else { + val x = buf(pos).asInstanceOf[A] + pos += 1 + if(pos == maxlen) pos = 0 + len -= 1 + x + } + } + override def drop(n: Int): Iterator[A] = { + init() + if (n > 0) { + len = (len - n) max 0 + pos = (pos + n) % maxlen + } + this + } + } + + private[collection] def dropRightIterator[A](it: Iterator[A], n: Int): Iterator[A] = { + if(n <= 0) it + else { + val k = it.knownSize + if(k >= 0) it.take(k - n) + else new DropRightIterator[A](it, n) + } + } + + private final class DropRightIterator[A](private[this] var underlying: Iterator[A], maxlen: Int) extends AbstractIterator[A] { + private[this] var len: Int = -1 // known size or -1 if the end of `underlying` has not been seen yet + private[this] var pos: Int = 0 + private[this] var buf: ArrayBuffer[AnyRef] = _ + def init(): Unit = if(buf eq null) { + buf = new ArrayBuffer[AnyRef](maxlen min 256) + while(pos < maxlen && underlying.hasNext) { + buf.addOne(underlying.next().asInstanceOf[AnyRef]) + pos += 1 + } + if(!underlying.hasNext) len = 0 + pos = 0 + } + override def knownSize = len + def hasNext: Boolean = { + init() + len != 0 + } + def next(): A = { + if(!hasNext) Iterator.empty.next() + else { + val x = buf(pos).asInstanceOf[A] + if(len == -1) { + buf(pos) = underlying.next().asInstanceOf[AnyRef] + if(!underlying.hasNext) len = 0 + } else len -= 1 + pos += 1 + if(pos == maxlen) pos = 0 + x + } + } + } +} + +/** Explicit instantiation of the `View` trait to reduce class file size in subclasses. */ +@SerialVersionUID(3L) +abstract class AbstractView[+A] extends scala.collection.AbstractIterable[A] with View[A] diff --git a/library/src/scala/collection/WithFilter.scala b/library/src/scala/collection/WithFilter.scala new file mode 100644 index 000000000000..da65ba6fab27 --- /dev/null +++ b/library/src/scala/collection/WithFilter.scala @@ -0,0 +1,72 @@ +/* + * 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.collection + +import scala.language.`2.13` + +/** A template trait that contains just the `map`, `flatMap`, `foreach` and `withFilter` methods + * of trait `Iterable`. + * + * @tparam A Element type (e.g. `Int`) + * @tparam CC Collection type constructor (e.g. `List`) + * + * @define coll collection + */ +@SerialVersionUID(3L) +abstract class WithFilter[+A, +CC[_]] extends Serializable { + + /** Builds a new collection by applying a function to all elements of the + * `filtered` outer $coll. + * + * @param f the function to apply to each element. + * @tparam B the element type of the returned collection. + * @return a new $coll resulting from applying + * the given function `f` to each element of the filtered outer $coll + * and collecting the results. + */ + def map[B](f: A => B): CC[B] + + /** Builds a new collection by applying a function to all elements of the + * `filtered` outer $coll containing this `WithFilter` instance that satisfy + * + * @param f the function to apply to each element. + * @tparam B the element type of the returned collection. + * @return a new $coll resulting from applying + * the given collection-valued function `f` to each element + * of the filtered outer $coll and + * concatenating the results. + */ + def flatMap[B](f: A => IterableOnce[B]): CC[B] + + /** Applies a function `f` to all elements of the `filtered` outer $coll. + * + * @param f the function that is applied for its side-effect to every element. + * The result of function `f` is discarded. + * + * @tparam U the type parameter describing the result of function `f`. + * This result will always be ignored. Typically `U` is `Unit`, + * but this is not necessary. + */ + def foreach[U](f: A => U): Unit + + /** Further refines the filter for this `filtered` $coll. + * + * @param q the predicate used to test elements. + * @return an object of class `WithFilter`, which supports + * `map`, `flatMap`, `foreach`, and `withFilter` operations. + * All these operations apply to those elements of this $coll which + * also satisfy both `p` and `q` predicates. + */ + def withFilter(q: A => Boolean): WithFilter[A, CC] + +} diff --git a/library/src/scala/collection/concurrent/BasicNode.java b/library/src/scala/collection/concurrent/BasicNode.java new file mode 100644 index 000000000000..b6a628d1295e --- /dev/null +++ b/library/src/scala/collection/concurrent/BasicNode.java @@ -0,0 +1,19 @@ +/* + * 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.collection.concurrent; + +public abstract class BasicNode { + + public abstract String string(int lev); + +} diff --git a/library/src/scala/collection/concurrent/CNodeBase.java b/library/src/scala/collection/concurrent/CNodeBase.java new file mode 100644 index 000000000000..4033c12af449 --- /dev/null +++ b/library/src/scala/collection/concurrent/CNodeBase.java @@ -0,0 +1,37 @@ +/* + * 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.collection.concurrent; + +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +abstract class CNodeBase extends MainNode { + + @SuppressWarnings("unchecked") + public static final AtomicIntegerFieldUpdater> updater = + AtomicIntegerFieldUpdater.newUpdater((Class>) (Class) CNodeBase.class, "csize"); + + public volatile int csize = -1; + + public boolean CAS_SIZE(int oldval, int nval) { + return updater.compareAndSet(this, oldval, nval); + } + + public void WRITE_SIZE(int nval) { + updater.set(this, nval); + } + + public int READ_SIZE() { + return updater.get(this); + } + +} diff --git a/library/src/scala/collection/concurrent/Gen.java b/library/src/scala/collection/concurrent/Gen.java new file mode 100644 index 000000000000..548c1892321f --- /dev/null +++ b/library/src/scala/collection/concurrent/Gen.java @@ -0,0 +1,15 @@ +/* + * 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.collection.concurrent; + +final class Gen {} diff --git a/library/src/scala/collection/concurrent/INodeBase.java b/library/src/scala/collection/concurrent/INodeBase.java new file mode 100644 index 000000000000..b16265c68ea3 --- /dev/null +++ b/library/src/scala/collection/concurrent/INodeBase.java @@ -0,0 +1,39 @@ +/* + * 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.collection.concurrent; + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +abstract class INodeBase extends BasicNode { + + @SuppressWarnings("unchecked") + public static final AtomicReferenceFieldUpdater, MainNode> updater = + AtomicReferenceFieldUpdater.newUpdater((Class>) (Class) INodeBase.class, (Class>) (Class) MainNode.class, "mainnode"); + + static final Object RESTART = new Object(); + + static final Object NO_SUCH_ELEMENT_SENTINEL = new Object(); + + public volatile MainNode mainnode = null; + + public final Gen gen; + + public INodeBase(Gen generation) { + gen = generation; + } + + public BasicNode prev() { + return null; + } + +} diff --git a/library/src/scala/collection/concurrent/MainNode.java b/library/src/scala/collection/concurrent/MainNode.java new file mode 100644 index 000000000000..1bfc11594ec9 --- /dev/null +++ b/library/src/scala/collection/concurrent/MainNode.java @@ -0,0 +1,46 @@ +/* + * 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.collection.concurrent; + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +abstract class MainNode extends BasicNode { + + @SuppressWarnings("unchecked") + public static final AtomicReferenceFieldUpdater, MainNode> updater = + AtomicReferenceFieldUpdater.newUpdater((Class>) (Class) MainNode.class, (Class>) (Class) MainNode.class, "prev"); + + public volatile MainNode prev = null; + + public abstract int cachedSize(Object ct); + + // standard contract + public abstract int knownSize(); + + public boolean CAS_PREV(MainNode oldval, MainNode nval) { + return updater.compareAndSet(this, oldval, nval); + } + + public void WRITE_PREV(MainNode nval) { + updater.set(this, nval); + } + + // do we need this? unclear in the javadocs... + // apparently not - volatile reads are supposed to be safe + // regardless of whether there are concurrent ARFU updates + @Deprecated @SuppressWarnings("unchecked") + public MainNode READ_PREV() { + return (MainNode) updater.get(this); + } + +} diff --git a/library/src/scala/collection/concurrent/Map.scala b/library/src/scala/collection/concurrent/Map.scala new file mode 100644 index 000000000000..818fcda2a510 --- /dev/null +++ b/library/src/scala/collection/concurrent/Map.scala @@ -0,0 +1,191 @@ +/* + * 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 collection.concurrent + +import scala.language.`2.13` +import scala.annotation.tailrec + +/** A template trait for mutable maps that allow concurrent access. + * + * $concurrentmapinfo + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html#concurrent_maps "Scala's Collection Library overview"]] + * section on `Concurrent Maps` for more information. + * + * @tparam K the key type of the map + * @tparam V the value type of the map + * + * @define Coll `concurrent.Map` + * @define coll concurrent map + * @define concurrentmapinfo + * This is a base trait for all Scala concurrent map implementations. It + * provides all of the methods a `Map` does, with the difference that all the + * changes are atomic. It also describes methods specific to concurrent maps. + * + * @define atomicop + * This is an atomic operation. + */ +trait Map[K, V] extends scala.collection.mutable.Map[K, V] { + + /** + * Associates the given key with a given value, unless the key was already + * associated with some other value. + * + * $atomicop + * + * @param k key with which the specified value is to be associated with + * @param v value to be associated with the specified key + * @return `Some(oldvalue)` if there was a value `oldvalue` previously + * associated with the specified key, or `None` if there was no + * mapping for the specified key + */ + def putIfAbsent(k: K, v: V): Option[V] + + /** + * Removes the entry for the specified key if it's currently mapped to the + * specified value. + * + * $atomicop + * + * @param k key for which the entry should be removed + * @param v value expected to be associated with the specified key if + * the removal is to take place + * @return `true` if the removal took place, `false` otherwise + */ + def remove(k: K, v: V): Boolean + + /** + * Replaces the entry for the given key only if it was previously mapped to + * a given value. + * + * $atomicop + * + * @param k key for which the entry should be replaced + * @param oldvalue value expected to be associated with the specified key + * if replacing is to happen + * @param newvalue value to be associated with the specified key + * @return `true` if the entry was replaced, `false` otherwise + */ + def replace(k: K, oldvalue: V, newvalue: V): Boolean + + /** + * Replaces the entry for the given key only if it was previously mapped + * to some value. + * + * $atomicop + * + * @param k key for which the entry should be replaced + * @param v value to be associated with the specified key + * @return `Some(v)` if the given key was previously mapped to some value `v`, or `None` otherwise + */ + def replace(k: K, v: V): Option[V] + + override def getOrElseUpdate(key: K, @deprecatedName("op", since="2.13.13") defaultValue: => V): V = get(key) match { + case Some(v) => v + case None => + val v = defaultValue + putIfAbsent(key, v) match { + case Some(ov) => ov + case None => v + } + } + + /** + * Removes the entry for the specified key if it's currently mapped to the + * specified value. Comparison to the specified value is done using reference + * equality. + * + * Not all map implementations can support removal based on reference + * equality, and for those implementations, object equality is used instead. + * + * $atomicop + * + * @param k key for which the entry should be removed + * @param v value expected to be associated with the specified key if + * the removal is to take place + * @return `true` if the removal took place, `false` otherwise + */ + // TODO: make part of the API in a future version + private[collection] def removeRefEq(k: K, v: V): Boolean = remove(k, v) + + /** + * Replaces the entry for the given key only if it was previously mapped to + * a given value. Comparison to the specified value is done using reference + * equality. + * + * Not all map implementations can support replacement based on reference + * equality, and for those implementations, object equality is used instead. + * + * $atomicop + * + * @param k key for which the entry should be replaced + * @param oldValue value expected to be associated with the specified key + * if replacing is to happen + * @param newValue value to be associated with the specified key + * @return `true` if the entry was replaced, `false` otherwise + */ + // TODO: make part of the API in a future version + private[collection] def replaceRefEq(k: K, oldValue: V, newValue: V): Boolean = replace(k, oldValue, newValue) + + /** + * Update a mapping for the specified key and its current optionally mapped value + * (`Some` if there is current mapping, `None` if not). + * + * If the remapping function returns `Some(v)`, the mapping is updated with the new value `v`. + * If the remapping function returns `None`, the mapping is removed (or remains absent if initially absent). + * If the function itself throws an exception, the exception is rethrown, and the current mapping is left unchanged. + * + * If the map is updated by another concurrent access, the remapping function will be retried until successfully updated. + * + * @param key the key value + * @param remappingFunction a function that receives current optionally mapped value and return a new mapping + * @return the new value associated with the specified key + */ + override def updateWith(key: K)(remappingFunction: Option[V] => Option[V]): Option[V] = updateWithAux(key)(remappingFunction) + + @tailrec + private def updateWithAux(key: K)(remappingFunction: Option[V] => Option[V]): Option[V] = { + val previousValue = get(key) + val nextValue = remappingFunction(previousValue) + previousValue match { + case Some(prev) => nextValue match { + case Some(next) => if (replaceRefEq(key, prev, next)) return nextValue + case _ => if (removeRefEq(key, prev)) return None + } + case _ => nextValue match { + case Some(next) => if (putIfAbsent(key, next).isEmpty) return nextValue + case _ => return None + } + } + updateWithAux(key)(remappingFunction) + } + + private[collection] def filterInPlaceImpl(p: (K, V) => Boolean): this.type = { + val it = iterator + while (it.hasNext) { + val (k, v) = it.next() + if (!p(k, v)) removeRefEq(k, v) + } + this + } + + private[collection] def mapValuesInPlaceImpl(f: (K, V) => V): this.type = { + val it = iterator + while (it.hasNext) { + val (k, v) = it.next() + replaceRefEq(k, v, f(k, v)) + } + this + } +} diff --git a/library/src/scala/collection/concurrent/TrieMap.scala b/library/src/scala/collection/concurrent/TrieMap.scala new file mode 100644 index 000000000000..4e3916c6981c --- /dev/null +++ b/library/src/scala/collection/concurrent/TrieMap.scala @@ -0,0 +1,1201 @@ +/* + * 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 collection +package concurrent + +import scala.language.`2.13` +import java.util.concurrent.atomic._ +import scala.{unchecked => uc} +import scala.annotation.tailrec +import scala.collection.concurrent.TrieMap.RemovalPolicy +import scala.collection.generic.DefaultSerializable +import scala.collection.immutable.{List, Nil} +import scala.collection.mutable.GrowableBuilder +import scala.util.Try +import scala.util.hashing.Hashing + +private[collection] final class INode[K, V](bn: MainNode[K, V], g: Gen, equiv: Equiv[K]) extends INodeBase[K, V](g) { + import INodeBase._ + + WRITE(bn) + + def this(g: Gen, equiv: Equiv[K]) = this(null, g, equiv) + + def WRITE(nval: MainNode[K, V]) = INodeBase.updater.set(this, nval) + + def CAS(old: MainNode[K, V], n: MainNode[K, V]) = INodeBase.updater.compareAndSet(this, old, n) + + def gcasRead(ct: TrieMap[K, V]): MainNode[K, V] = GCAS_READ(ct) + + def GCAS_READ(ct: TrieMap[K, V]): MainNode[K, V] = { + val m = /*READ*/mainnode + val prevval = /*READ*/m.prev + if (prevval eq null) m + else GCAS_Complete(m, ct) + } + + @tailrec private def GCAS_Complete(m: MainNode[K, V], ct: TrieMap[K, V]): MainNode[K, V] = if (m eq null) null else { + // complete the GCAS + val prev = /*READ*/m.prev + val ctr = ct.readRoot(abort = true) + + prev match { + case null => + m + case fn: FailedNode[_, _] => // try to commit to previous value + if (CAS(m, fn.prev)) fn.prev + else GCAS_Complete(/*READ*/mainnode, ct) + case vn: MainNode[_, _] => + // Assume that you've read the root from the generation G. + // Assume that the snapshot algorithm is correct. + // ==> you can only reach nodes in generations <= G. + // ==> `gen` is <= G. + // We know that `ctr.gen` is >= G. + // ==> if `ctr.gen` = `gen` then they are both equal to G. + // ==> otherwise, we know that either `ctr.gen` > G, `gen` < G, + // or both + if ((ctr.gen eq gen) && ct.nonReadOnly) { + // try to commit + if (m.CAS_PREV(prev, null)) m + else GCAS_Complete(m, ct) + } else { + // try to abort + m.CAS_PREV(prev, new FailedNode(prev)) + GCAS_Complete(/*READ*/mainnode, ct) + } + } + } + + def GCAS(old: MainNode[K, V], n: MainNode[K, V], ct: TrieMap[K, V]): Boolean = { + n.WRITE_PREV(old) + if (CAS(old, n)) { + GCAS_Complete(n, ct) + /*READ*/n.prev eq null + } else false + } + + private def equal(k1: K, k2: K, ct: TrieMap[K, V]) = ct.equality.equiv(k1, k2) + + private def inode(cn: MainNode[K, V]) = { + val nin = new INode[K, V](gen, equiv) + nin.WRITE(cn) + nin + } + + def copyToGen(ngen: Gen, ct: TrieMap[K, V]) = { + val nin = new INode[K, V](ngen, equiv) + val main = GCAS_READ(ct) + nin.WRITE(main) + nin + } + + /** Inserts a key value pair, overwriting the old pair if the keys match. + * + * @return true if successful, false otherwise + */ + @tailrec def rec_insert(k: K, v: V, hc: Int, lev: Int, parent: INode[K, V], startgen: Gen, ct: TrieMap[K, V]): Boolean = { + val m = GCAS_READ(ct) // use -Yinline! + + m match { + case cn: CNode[K, V] => // 1) a multiway node + val idx = (hc >>> lev) & 0x1f + val flag = 1 << idx + val bmp = cn.bitmap + val mask = flag - 1 + val pos = Integer.bitCount(bmp & mask) + if ((bmp & flag) != 0) { + // 1a) insert below + cn.array(pos) match { + case in: INode[K, V] @uc => + if (startgen eq in.gen) in.rec_insert(k, v, hc, lev + 5, this, startgen, ct) + else { + if (GCAS(cn, cn.renewed(startgen, ct), ct)) rec_insert(k, v, hc, lev, parent, startgen, ct) + else false + } + case sn: SNode[K, V] @uc => + if (sn.hc == hc && equal(sn.k, k, ct)) GCAS(cn, cn.updatedAt(pos, new SNode(sn.k, v, hc), gen), ct) + else { + val rn = if (cn.gen eq gen) cn else cn.renewed(gen, ct) + val nn = rn.updatedAt(pos, inode(CNode.dual(sn, sn.hc, new SNode(k, v, hc), hc, lev + 5, gen, equiv)), gen) + GCAS(cn, nn, ct) + } + case basicNode => throw new MatchError(basicNode) + } + } else { + val rn = if (cn.gen eq gen) cn else cn.renewed(gen, ct) + val ncnode = rn.insertedAt(pos, flag, k, v, hc, gen) + GCAS(cn, ncnode, ct) + } + case tn: TNode[K, V] => + clean(parent, ct, lev - 5) + false + case ln: LNode[K, V] => // 3) an l-node + val nn = ln.inserted(k, v) + GCAS(ln, nn, ct) + case mainNode => throw new MatchError(mainNode) + } + } + + + + /** Inserts a new key value pair, given that a specific condition is met. + * + * @param cond KEY_PRESENT_OR_ABSENT - don't care if the key was there, insert or overwrite + * KEY_ABSENT - key wasn't there, insert only, do not overwrite + * KEY_PRESENT - key was there, overwrite only, do not insert + * other value `v` - only overwrite if the current value is this + * @param fullEquals whether to use reference or full equals when comparing `v` to the current value + * @param hc the hashcode of `k` + * + * @return null if unsuccessful, Option[V] otherwise (indicating previous value bound to the key) + */ + @tailrec def rec_insertif(k: K, v: V, hc: Int, cond: AnyRef, fullEquals: Boolean, lev: Int, parent: INode[K, V], startgen: Gen, ct: TrieMap[K, V]): Option[V] = { + val m = GCAS_READ(ct) // use -Yinline! + + m match { + case cn: CNode[K, V] => // 1) a multiway node + val idx = (hc >>> lev) & 0x1f + val flag = 1 << idx + val bmp = cn.bitmap + val mask = flag - 1 + val pos = Integer.bitCount(bmp & mask) + if ((bmp & flag) != 0) { + // 1a) insert below + cn.array(pos) match { + case in: INode[K, V] @uc => + if (startgen eq in.gen) in.rec_insertif(k, v, hc, cond, fullEquals, lev + 5, this, startgen, ct) + else { + if (GCAS(cn, cn.renewed(startgen, ct), ct)) rec_insertif(k, v, hc, cond, fullEquals, lev, parent, startgen, ct) + else null + } + case sn: SNode[K, V] @uc => cond match { + case INode.KEY_PRESENT_OR_ABSENT => + if (sn.hc == hc && equal(sn.k, k, ct)) { + if (GCAS(cn, cn.updatedAt(pos, new SNode(sn.k, v, hc), gen), ct)) Some(sn.v) else null + } else { + val rn = if (cn.gen eq gen) cn else cn.renewed(gen, ct) + val nn = rn.updatedAt(pos, inode(CNode.dual(sn, sn.hc, new SNode(k, v, hc), hc, lev + 5, gen, equiv)), gen) + if (GCAS(cn, nn, ct)) None + else null + } + case INode.KEY_ABSENT => + if (sn.hc == hc && equal(sn.k, k, ct)) Some(sn.v) + else { + val rn = if (cn.gen eq gen) cn else cn.renewed(gen, ct) + val nn = rn.updatedAt(pos, inode(CNode.dual(sn, sn.hc, new SNode(k, v, hc), hc, lev + 5, gen, equiv)), gen) + if (GCAS(cn, nn, ct)) None + else null + } + case INode.KEY_PRESENT => + if (sn.hc == hc && equal(sn.k, k, ct)) { + if (GCAS(cn, cn.updatedAt(pos, new SNode(k, v, hc), gen), ct)) Some(sn.v) else null + } else None + case otherv => + if (sn.hc == hc && equal(sn.k, k, ct) && (if (fullEquals) sn.v == otherv else sn.v.asInstanceOf[AnyRef] eq otherv)) { + if (GCAS(cn, cn.updatedAt(pos, new SNode(k, v, hc), gen), ct)) Some(sn.v) else null + } else None + } + case basicNode => throw new MatchError(basicNode) + } + } else cond match { + case INode.KEY_PRESENT_OR_ABSENT | INode.KEY_ABSENT => + val rn = if (cn.gen eq gen) cn else cn.renewed(gen, ct) + val ncnode = rn.insertedAt(pos, flag, k, v, hc, gen) + if (GCAS(cn, ncnode, ct)) None else null + case INode.KEY_PRESENT => None + case otherv => None + } + case sn: TNode[K, V] => + clean(parent, ct, lev - 5) + null + case ln: LNode[K, V] => // 3) an l-node + def insertln() = { + val nn = ln.inserted(k, v) + GCAS(ln, nn, ct) + } + cond match { + case INode.KEY_PRESENT_OR_ABSENT => + val optv = ln.get(k) + if (insertln()) optv else null + case INode.KEY_ABSENT => + ln.get(k) match { + case None => if (insertln()) None else null + case optv => optv + } + case INode.KEY_PRESENT => + ln.get(k) match { + case Some(v0) => if (insertln()) Some(v0) else null + case None => None + } + case otherv => + ln.get(k) match { + case Some(v0) if (if (fullEquals) v0 == otherv else v0.asInstanceOf[AnyRef] eq otherv) => + if (insertln()) Some(otherv.asInstanceOf[V]) else null + case _ => None + } + } + case mainNode => throw new MatchError(mainNode) + } + } + + /** Looks up the value associated with the key. + * + * @param hc the hashcode of `k` + * + * @return NO_SUCH_ELEMENT_SENTINEL if no value has been found, RESTART if the operation wasn't successful, + * or any other value otherwise + */ + @tailrec def rec_lookup(k: K, hc: Int, lev: Int, parent: INode[K, V], startgen: Gen, ct: TrieMap[K, V]): AnyRef = { + val m = GCAS_READ(ct) // use -Yinline! + + m match { + case cn: CNode[K, V] => // 1) a multinode + val idx = (hc >>> lev) & 0x1f + val flag = 1 << idx + val bmp = cn.bitmap + if ((bmp & flag) == 0) NO_SUCH_ELEMENT_SENTINEL // 1a) bitmap shows no binding + else { // 1b) bitmap contains a value - descend + val pos = if (bmp == 0xffffffff) idx else Integer.bitCount(bmp & (flag - 1)) + val sub = cn.array(pos) + sub match { + case in: INode[K, V] @uc => + if (ct.isReadOnly || (startgen eq in.gen)) in.rec_lookup(k, hc, lev + 5, this, startgen, ct) + else { + if (GCAS(cn, cn.renewed(startgen, ct), ct)) rec_lookup(k, hc, lev, parent, startgen, ct) + else RESTART + } + case sn: SNode[K, V] @uc => // 2) singleton node + if (sn.hc == hc && equal(sn.k, k, ct)) sn.v.asInstanceOf[AnyRef] + else NO_SUCH_ELEMENT_SENTINEL + case basicNode => throw new MatchError(basicNode) + } + } + case tn: TNode[_, _] => // 3) non-live node + def cleanReadOnly(tn: TNode[K, V]) = if (ct.nonReadOnly) { + clean(parent, ct, lev - 5) + RESTART + } else { + if (tn.hc == hc && tn.k == k) tn.v.asInstanceOf[AnyRef] + else NO_SUCH_ELEMENT_SENTINEL + } + cleanReadOnly(tn) + case ln: LNode[K, V] => // 5) an l-node + ln.get(k).asInstanceOf[Option[AnyRef]].getOrElse(NO_SUCH_ELEMENT_SENTINEL) + case mainNode => throw new MatchError(mainNode) + } + } + + /** Removes the key associated with the given value. + * + * @param hc the hashcode of `k` + * + * @param removalPolicy policy deciding whether to remove `k` based on `v` and the + * current value associated with `k` (Always, FullEquals, or ReferenceEq) + * + * @return null if not successful, an Option[V] indicating the previous value otherwise + */ + def rec_remove( + k: K, + v: V, + removalPolicy: Int, + hc: Int, + lev: Int, + parent: INode[K, V], + startgen: Gen, + ct: TrieMap[K, V]): Option[V] = { + + GCAS_READ(ct) match { + case cn: CNode[K, V] => + val idx = (hc >>> lev) & 0x1f + val bmp = cn.bitmap + val flag = 1 << idx + if ((bmp & flag) == 0) None + else { + val pos = Integer.bitCount(bmp & (flag - 1)) + val sub = cn.array(pos) + val res = sub match { + case in: INode[K, V] @uc => + if (startgen eq in.gen) in.rec_remove(k, v, removalPolicy, hc, lev + 5, this, startgen, ct) + else { + if (GCAS(cn, cn.renewed(startgen, ct), ct)) rec_remove(k, v, removalPolicy, hc, lev, parent, startgen, ct) + else null + } + case sn: SNode[K, V] @uc => + if (sn.hc == hc && equal(sn.k, k, ct) && RemovalPolicy.shouldRemove(removalPolicy)(sn.v, v)) { + val ncn = cn.removedAt(pos, flag, gen).toContracted(lev) + if (GCAS(cn, ncn, ct)) Some(sn.v) else null + } else None + case basicNode => throw new MatchError(basicNode) + } + + if (res == None || (res eq null)) res + else { + @tailrec def cleanParent(nonlive: AnyRef): Unit = { + val cn = parent.GCAS_READ(ct) + cn match { + case cn: CNode[K, V] => + val idx = (hc >>> (lev - 5)) & 0x1f + val bmp = cn.bitmap + val flag = 1 << idx + if ((bmp & flag) == 0) {} // somebody already removed this i-node, we're done + else { + val pos = Integer.bitCount(bmp & (flag - 1)) + val sub = cn.array(pos) + if (sub eq this) (nonlive: @uc) match { + case tn: TNode[K, V] @uc => + val ncn = cn.updatedAt(pos, tn.copyUntombed, gen).toContracted(lev - 5) + if (!parent.GCAS(cn, ncn, ct)) + if (ct.readRoot().gen == startgen) cleanParent(nonlive) + } + } + case _ => // parent is no longer a cnode, we're done + } + } + + if (parent ne null) { // never tomb at root + val n = GCAS_READ(ct) + if (n.isInstanceOf[TNode[_, _]]) + cleanParent(n) + } + + res + } + } + case tn: TNode[K, V] => + clean(parent, ct, lev - 5) + null + case ln: LNode[K, V] => + if (removalPolicy == RemovalPolicy.Always) { + val optv = ln.get(k) + val nn = ln.removed(k, ct) + if (GCAS(ln, nn, ct)) optv else null + } else ln.get(k) match { + case optv @ Some(v0) if RemovalPolicy.shouldRemove(removalPolicy)(v, v0) => + val nn = ln.removed(k, ct) + if (GCAS(ln, nn, ct)) optv else null + case _ => None + } + case mainNode => throw new MatchError(mainNode) + } + } + + private def clean(nd: INode[K, V], ct: TrieMap[K, V], lev: Int): Unit = { + val m = nd.GCAS_READ(ct) + m match { + case cn: CNode[K, V] => nd.GCAS(cn, cn.toCompressed(ct, lev, gen), ct) + case _ => + } + } + + def isNullInode(ct: TrieMap[K, V]) = GCAS_READ(ct) eq null + + def cachedSize(ct: TrieMap[K, V]): Int = + GCAS_READ(ct).cachedSize(ct) + + def knownSize(ct: TrieMap[K, V]): Int = + GCAS_READ(ct).knownSize() + + /* this is a quiescent method! */ + def string(lev: Int) = "%sINode -> %s".format(" " * lev, mainnode match { + case null => "" + case tn: TNode[_, _] => "TNode(%s, %s, %d, !)".format(tn.k, tn.v, tn.hc) + case cn: CNode[_, _] => cn.string(lev) + case ln: LNode[_, _] => ln.string(lev) + case x => "".format(x) + }) + +} + + +private[concurrent] object INode { + //////////////////////////////////////////////////////////////////////////////////////////////////// + // Arguments for `cond` argument in TrieMap#rec_insertif + //////////////////////////////////////////////////////////////////////////////////////////////////// + final val KEY_PRESENT = new AnyRef + final val KEY_ABSENT = new AnyRef + final val KEY_PRESENT_OR_ABSENT = new AnyRef + + def newRootNode[K, V](equiv: Equiv[K]) = { + val gen = new Gen + val cn = new CNode[K, V](0, new Array(0), gen) + new INode[K, V](cn, gen, equiv) + } +} + + +private[concurrent] final class FailedNode[K, V](p: MainNode[K, V]) extends MainNode[K, V] { + WRITE_PREV(p) + + def string(lev: Int): Nothing = throw new UnsupportedOperationException + + def cachedSize(ct: AnyRef): Int = throw new UnsupportedOperationException + + def knownSize: Int = throw new UnsupportedOperationException + + override def toString = "FailedNode(%s)".format(p) +} + + +private[concurrent] trait KVNode[K, V] { + def kvPair: (K, V) +} + + +private[collection] final class SNode[K, V](final val k: K, final val v: V, final val hc: Int) + extends BasicNode with KVNode[K, V] { + def copy = new SNode(k, v, hc) + def copyTombed = new TNode(k, v, hc) + def copyUntombed = new SNode(k, v, hc) + def kvPair = (k, v) + def string(lev: Int) = (" " * lev) + "SNode(%s, %s, %x)".format(k, v, hc) +} + +// Tomb Node, used to ensure proper ordering during removals +private[collection] final class TNode[K, V](final val k: K, final val v: V, final val hc: Int) + extends MainNode[K, V] with KVNode[K, V] { + def copy = new TNode(k, v, hc) + def copyTombed = new TNode(k, v, hc) + def copyUntombed = new SNode(k, v, hc) + def kvPair = (k, v) + def cachedSize(ct: AnyRef): Int = 1 + def knownSize: Int = 1 + def string(lev: Int) = (" " * lev) + "TNode(%s, %s, %x, !)".format(k, v, hc) +} + +// List Node, leaf node that handles hash collisions +private[collection] final class LNode[K, V](val entries: List[(K, V)], equiv: Equiv[K]) + extends MainNode[K, V] { + + def this(k: K, v: V, equiv: Equiv[K]) = this((k -> v) :: Nil, equiv) + + def this(k1: K, v1: V, k2: K, v2: V, equiv: Equiv[K]) = + this(if (equiv.equiv(k1, k2)) (k2 -> v2) :: Nil else (k1 -> v1) :: (k2 -> v2) :: Nil, equiv) + + def inserted(k: K, v: V) = { + var k0: K = k + @tailrec + def remove(elems: List[(K, V)], acc: List[(K, V)]): List[(K, V)] = { + if (elems.isEmpty) acc + else if (equiv.equiv(elems.head._1, k)) { + k0 = elems.head._1 + acc ::: elems.tail + } else remove(elems.tail, elems.head :: acc) + } + val e = remove(entries, Nil) + new LNode((k0 -> v) :: e, equiv) + } + + def removed(k: K, ct: TrieMap[K, V]): MainNode[K, V] = { + val updmap = entries.filterNot(entry => equiv.equiv(entry._1, k)) + if (updmap.sizeIs > 1) new LNode(updmap, equiv) + else { + val (k, v) = updmap.iterator.next() + new TNode(k, v, ct.computeHash(k)) // create it tombed so that it gets compressed on subsequent accesses + } + } + + def get(k: K): Option[V] = entries.find(entry => equiv.equiv(entry._1, k)).map(_._2) + + def cachedSize(ct: AnyRef): Int = entries.size + + def knownSize: Int = -1 // shouldn't ever be empty, and the size of a list is not known + + def string(lev: Int) = (" " * lev) + "LNode(%s)".format(entries.mkString(", ")) + +} + +// Ctrie Node, contains bitmap and array of references to branch nodes +private[collection] final class CNode[K, V](val bitmap: Int, val array: Array[BasicNode], val gen: Gen) extends CNodeBase[K, V] { + // this should only be called from within read-only snapshots + def cachedSize(ct: AnyRef): Int = { + val currsz = READ_SIZE() + if (currsz != -1) currsz + else { + val sz = computeSize(ct.asInstanceOf[TrieMap[K, V]]) + while (READ_SIZE() == -1) CAS_SIZE(-1, sz) + READ_SIZE() + } + } + + def knownSize: Int = READ_SIZE() // this should only ever return -1 if unknown + + // lends itself towards being parallelizable by choosing + // a random starting offset in the array + // => if there are concurrent size computations, they start + // at different positions, so they are more likely to + // to be independent + private def computeSize(ct: TrieMap[K, V]): Int = { + var i = 0 + var sz = 0 + val offset = + if (array.length > 0) + //util.Random.nextInt(array.length) /* <-- benchmarks show that this causes observable contention */ + java.util.concurrent.ThreadLocalRandom.current.nextInt(0, array.length) + else 0 + while (i < array.length) { + val pos = (i + offset) % array.length + array(pos) match { + case sn: SNode[_, _] => sz += 1 + case in: INode[K, V] @uc => sz += in.cachedSize(ct) + case basicNode => throw new MatchError(basicNode) + } + i += 1 + } + sz + } + + def updatedAt(pos: Int, nn: BasicNode, gen: Gen) = { + val len = array.length + val narr = new Array[BasicNode](len) + Array.copy(array, 0, narr, 0, len) + narr(pos) = nn + new CNode[K, V](bitmap, narr, gen) + } + + def removedAt(pos: Int, flag: Int, gen: Gen) = { + val arr = array + val len = arr.length + val narr = new Array[BasicNode](len - 1) + Array.copy(arr, 0, narr, 0, pos) + Array.copy(arr, pos + 1, narr, pos, len - pos - 1) + new CNode[K, V](bitmap ^ flag, narr, gen) + } + + def insertedAt(pos: Int, flag: Int, k: K, v: V, hc: Int, gen: Gen) = { + val len = array.length + val bmp = bitmap + val narr = new Array[BasicNode](len + 1) + Array.copy(array, 0, narr, 0, pos) + narr(pos) = new SNode(k, v, hc) + Array.copy(array, pos, narr, pos + 1, len - pos) + new CNode[K, V](bmp | flag, narr, gen) + } + + /** Returns a copy of this cnode such that all the i-nodes below it are copied + * to the specified generation `ngen`. + */ + def renewed(ngen: Gen, ct: TrieMap[K, V]) = { + var i = 0 + val arr = array + val len = arr.length + val narr = new Array[BasicNode](len) + while (i < len) { + arr(i) match { + case in: INode[K, V] @uc => narr(i) = in.copyToGen(ngen, ct) + case bn: BasicNode => narr(i) = bn + } + i += 1 + } + new CNode[K, V](bitmap, narr, ngen) + } + + private def resurrect(inode: INode[K, V], inodemain: AnyRef): BasicNode = inodemain match { + case tn: TNode[_, _] => tn.copyUntombed + case _ => inode + } + + def toContracted(lev: Int): MainNode[K, V] = if (array.length == 1 && lev > 0) array(0) match { + case sn: SNode[K, V] @uc => sn.copyTombed + case _ => this + } else this + + // - if the branching factor is 1 for this CNode, and the child + // is a tombed SNode, returns its tombed version + // - otherwise, if there is at least one non-null node below, + // returns the version of this node with at least some null-inodes + // removed (those existing when the op began) + // - if there are only null-i-nodes below, returns null + def toCompressed(ct: TrieMap[K, V], lev: Int, gen: Gen) = { + val bmp = bitmap + var i = 0 + val arr = array + val tmparray = new Array[BasicNode](arr.length) + while (i < arr.length) { // construct new bitmap + val sub = arr(i) + sub match { + case in: INode[K, V] @uc => + val inodemain = in.gcasRead(ct) + assert(inodemain ne null) + tmparray(i) = resurrect(in, inodemain) + case sn: SNode[K, V] @uc => + tmparray(i) = sn + case basicNode => throw new MatchError(basicNode) + } + i += 1 + } + + new CNode[K, V](bmp, tmparray, gen).toContracted(lev) + } + + def string(lev: Int): String = "CNode %x\n%s".format(bitmap, array.map(_.string(lev + 1)).mkString("\n")) + + override def toString = { + def elems: Seq[String] = array.flatMap { + case sn: SNode[K, V] @uc => Iterable.single(sn.kvPair._2.toString) + case in: INode[K, V] @uc => Iterable.single(augmentString(in.toString).drop(14) + "(" + in.gen + ")") + case basicNode => throw new MatchError(basicNode) + } + f"CNode(sz: ${elems.size}%d; ${elems.sorted.mkString(", ")})" + } +} + +private[concurrent] object CNode { + + def dual[K, V](x: SNode[K, V], xhc: Int, y: SNode[K, V], yhc: Int, lev: Int, gen: Gen, equiv: Equiv[K]): MainNode[K, V] = if (lev < 35) { + val xidx = (xhc >>> lev) & 0x1f + val yidx = (yhc >>> lev) & 0x1f + val bmp = (1 << xidx) | (1 << yidx) + if (xidx == yidx) { + val subinode = new INode[K, V](gen, equiv)//(TrieMap.inodeupdater) + subinode.mainnode = dual(x, xhc, y, yhc, lev + 5, gen, equiv) + new CNode(bmp, Array(subinode), gen) + } else { + if (xidx < yidx) new CNode(bmp, Array(x, y), gen) + else new CNode(bmp, Array(y, x), gen) + } + } else { + new LNode(x.k, x.v, y.k, y.v, equiv) + } + +} + + +private[concurrent] case class RDCSS_Descriptor[K, V](old: INode[K, V], expectedmain: MainNode[K, V], nv: INode[K, V]) { + @volatile var committed = false +} + + +/** A concurrent hash-trie or TrieMap is a concurrent thread-safe lock-free + * implementation of a hash array mapped trie. It is used to implement the + * concurrent map abstraction. It has particularly scalable concurrent insert + * and remove operations and is memory-efficient. It supports O(1), atomic, + * lock-free snapshots which are used to implement linearizable lock-free size, + * iterator and clear operations. The cost of evaluating the (lazy) snapshot is + * distributed across subsequent updates, thus making snapshot evaluation horizontally scalable. + * + * For details, see: [[http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf]] + */ +@SerialVersionUID(-5212455458703321708L) +final class TrieMap[K, V] private (r: AnyRef, rtupd: AtomicReferenceFieldUpdater[TrieMap[K, V], AnyRef], hashf: Hashing[K], ef: Equiv[K]) + extends scala.collection.mutable.AbstractMap[K, V] + with scala.collection.concurrent.Map[K, V] + with scala.collection.mutable.MapOps[K, V, TrieMap, TrieMap[K, V]] + with scala.collection.MapFactoryDefaults[K, V, TrieMap, mutable.Iterable] + with DefaultSerializable { + + private[this] var hashingobj = if (hashf.isInstanceOf[Hashing.Default[_]]) new TrieMap.MangledHashing[K] else hashf + private[this] var equalityobj = ef + @transient + private[this] var rootupdater = rtupd + def hashing = hashingobj + def equality = equalityobj + @volatile private var root = r + + def this(hashf: Hashing[K], ef: Equiv[K]) = this( + INode.newRootNode(ef), + AtomicReferenceFieldUpdater.newUpdater(classOf[TrieMap[K, V]], classOf[AnyRef], "root"), + hashf, + ef + ) + + def this() = this(Hashing.default, Equiv.universal) + + override def mapFactory: MapFactory[TrieMap] = TrieMap + + /* internal methods */ + + private def writeObject(out: java.io.ObjectOutputStream): Unit = { + out.writeObject(hashingobj) + out.writeObject(equalityobj) + + val it = iterator + while (it.hasNext) { + val (k, v) = it.next() + out.writeObject(k) + out.writeObject(v) + } + out.writeObject(TrieMapSerializationEnd) + } + + private def readObject(in: java.io.ObjectInputStream): Unit = { + root = INode.newRootNode(equality) + rootupdater = AtomicReferenceFieldUpdater.newUpdater(classOf[TrieMap[K, V]], classOf[AnyRef], "root") + + hashingobj = in.readObject().asInstanceOf[Hashing[K]] + equalityobj = in.readObject().asInstanceOf[Equiv[K]] + + var obj: AnyRef = in.readObject() + + while (obj != TrieMapSerializationEnd) { + obj = in.readObject() + if (obj != TrieMapSerializationEnd) { + val k = obj.asInstanceOf[K] + val v = in.readObject().asInstanceOf[V] + update(k, v) + } + } + } + + private def CAS_ROOT(ov: AnyRef, nv: AnyRef) = rootupdater.compareAndSet(this, ov, nv) + + private[collection] def readRoot(abort: Boolean = false): INode[K, V] = RDCSS_READ_ROOT(abort) + + private[concurrent] def RDCSS_READ_ROOT(abort: Boolean = false): INode[K, V] = { + val r = /*READ*/root + r match { + case in: INode[K, V] @uc => in + case desc: RDCSS_Descriptor[K, V] @uc => RDCSS_Complete(abort) + case x => throw new MatchError(x) + } + } + + @tailrec private def RDCSS_Complete(abort: Boolean): INode[K, V] = { + val v = /*READ*/root + v match { + case in: INode[K, V] @uc => in + case desc: RDCSS_Descriptor[K, V] @uc => + val RDCSS_Descriptor(ov, exp, nv) = desc + if (abort) { + if (CAS_ROOT(desc, ov)) ov + else RDCSS_Complete(abort) + } else { + val oldmain = ov.gcasRead(this) + if (oldmain eq exp) { + if (CAS_ROOT(desc, nv)) { + desc.committed = true + nv + } else RDCSS_Complete(abort) + } else { + if (CAS_ROOT(desc, ov)) ov + else RDCSS_Complete(abort) + } + } + case x => throw new MatchError(x) + } + } + + private def RDCSS_ROOT(ov: INode[K, V], expectedmain: MainNode[K, V], nv: INode[K, V]): Boolean = { + val desc = RDCSS_Descriptor(ov, expectedmain, nv) + if (CAS_ROOT(ov, desc)) { + RDCSS_Complete(abort = false) + /*READ*/desc.committed + } else false + } + + @tailrec private def inserthc(k: K, hc: Int, v: V): Unit = { + val r = RDCSS_READ_ROOT() + if (!r.rec_insert(k, v, hc, 0, null, r.gen, this)) inserthc(k, hc, v) + } + + @tailrec private def insertifhc(k: K, hc: Int, v: V, cond: AnyRef, fullEquals: Boolean): Option[V] = { + val r = RDCSS_READ_ROOT() + + val ret = r.rec_insertif(k, v, hc, cond, fullEquals, 0, null, r.gen, this) + if (ret eq null) insertifhc(k, hc, v, cond, fullEquals) + else ret + } + + /** Finds the value associated with this key + * + * @param k the key to look up + * @param hc the hashcode of `k` + * + * @return the value: V associated with `k`, if it exists. Otherwise, INodeBase.NO_SUCH_ELEMENT_SENTINEL + */ + @tailrec private def lookuphc(k: K, hc: Int): AnyRef = { + val r = RDCSS_READ_ROOT() + val res = r.rec_lookup(k, hc, 0, null, r.gen, this) + if (res eq INodeBase.RESTART) lookuphc(k, hc) + else res + } + + /** Removes a key-value pair from the map + * + * @param k the key to remove + * @param v the value compare with the value found associated with the key + * @param removalPolicy policy deciding whether to remove `k` based on `v` and the + * current value associated with `k` (Always, FullEquals, or ReferenceEq) + * @return an Option[V] indicating the previous value + */ + @tailrec private def removehc(k: K, v: V, removalPolicy: Int, hc: Int): Option[V] = { + val r = RDCSS_READ_ROOT() + val res = r.rec_remove(k, v, removalPolicy, hc, 0, null, r.gen, this) + if (res ne null) res + else removehc(k, v, removalPolicy, hc) + } + + + def string = RDCSS_READ_ROOT().string(0) + + /* public methods */ + + def isReadOnly = rootupdater eq null + + def nonReadOnly = rootupdater ne null + + /** Returns a snapshot of this TrieMap. + * This operation is lock-free and linearizable. + * + * The snapshot is lazily updated - the first time some branch + * in the snapshot or this TrieMap are accessed, they are rewritten. + * This means that the work of rebuilding both the snapshot and this + * TrieMap is distributed across all the threads doing updates or accesses + * subsequent to the snapshot creation. + */ + @tailrec def snapshot(): TrieMap[K, V] = { + val r = RDCSS_READ_ROOT() + val expmain = r.gcasRead(this) + if (RDCSS_ROOT(r, expmain, r.copyToGen(new Gen, this))) new TrieMap(r.copyToGen(new Gen, this), rootupdater, hashing, equality) + else snapshot() + } + + /** Returns a read-only snapshot of this TrieMap. + * This operation is lock-free and linearizable. + * + * The snapshot is lazily updated - the first time some branch + * of this TrieMap are accessed, it is rewritten. The work of creating + * the snapshot is thus distributed across subsequent updates + * and accesses on this TrieMap by all threads. + * Note that the snapshot itself is never rewritten unlike when calling + * the `snapshot` method, but the obtained snapshot cannot be modified. + * + * This method is used by other methods such as `size` and `iterator`. + */ + @tailrec def readOnlySnapshot(): scala.collection.Map[K, V] = { + val r = RDCSS_READ_ROOT() + val expmain = r.gcasRead(this) + if (RDCSS_ROOT(r, expmain, r.copyToGen(new Gen, this))) new TrieMap(r, null, hashing, equality) + else readOnlySnapshot() + } + + @tailrec override def clear(): Unit = { + val r = RDCSS_READ_ROOT() + if (!RDCSS_ROOT(r, r.gcasRead(this), INode.newRootNode[K, V](equality))) clear() + } + + def computeHash(k: K) = hashingobj.hash(k) + + @deprecated("Use getOrElse(k, null) instead.", "2.13.0") + def lookup(k: K): V = { + val hc = computeHash(k) + val lookupRes = lookuphc(k, hc) + val res = if (lookupRes == INodeBase.NO_SUCH_ELEMENT_SENTINEL) null else lookupRes + res.asInstanceOf[V] + } + + override def apply(k: K): V = { + val hc = computeHash(k) + val res = lookuphc(k, hc) + if (res eq INodeBase.NO_SUCH_ELEMENT_SENTINEL) throw new NoSuchElementException + else res.asInstanceOf[V] + } + + def get(k: K): Option[V] = { + val hc = computeHash(k) + val res = lookuphc(k, hc) + if (res eq INodeBase.NO_SUCH_ELEMENT_SENTINEL) None else Some(res).asInstanceOf[Option[V]] + } + + override def put(key: K, value: V): Option[V] = { + val hc = computeHash(key) + insertifhc(key, hc, value, INode.KEY_PRESENT_OR_ABSENT, fullEquals = false /* unused */) + } + + override def update(k: K, v: V): Unit = { + val hc = computeHash(k) + inserthc(k, hc, v) + } + + def addOne(kv: (K, V)) = { + update(kv._1, kv._2) + this + } + + override def remove(k: K): Option[V] = { + val hc = computeHash(k) + removehc(k = k, v = null.asInstanceOf[V], RemovalPolicy.Always, hc = hc) + } + + def subtractOne(k: K) = { + remove(k) + this + } + + def putIfAbsent(k: K, v: V): Option[V] = { + val hc = computeHash(k) + insertifhc(k, hc, v, INode.KEY_ABSENT, fullEquals = false /* unused */) + } + + // TODO once computeIfAbsent is added to concurrent.Map, + // move the comment there and tweak the 'at most once' part + /** If the specified key is not already in the map, computes its value using + * the given thunk `defaultValue` and enters it into the map. + * + * If the specified mapping function throws an exception, + * that exception is rethrown. + * + * Note: This method will invoke `defaultValue` at most once. + * However, `defaultValue` may be invoked without the result being added to the map if + * a concurrent process is also trying to add a value corresponding to the + * same key `k`. + * + * @param k the key to modify + * @param defaultValue the expression that computes the value + * @return the newly added value + */ + override def getOrElseUpdate(k: K, @deprecatedName("op", since="2.13.13") defaultValue: => V): V = { + val hc = computeHash(k) + lookuphc(k, hc) match { + case INodeBase.NO_SUCH_ELEMENT_SENTINEL => + val v = defaultValue + insertifhc(k, hc, v, INode.KEY_ABSENT, fullEquals = false /* unused */) match { + case Some(oldValue) => oldValue + case None => v + } + case oldValue => oldValue.asInstanceOf[V] + } + } + + def remove(k: K, v: V): Boolean = { + val hc = computeHash(k) + removehc(k, v, RemovalPolicy.FullEquals, hc).nonEmpty + } + + override private[collection] def removeRefEq(k: K, v: V): Boolean = { + val hc = computeHash(k) + removehc(k, v, RemovalPolicy.ReferenceEq, hc).nonEmpty + } + + def replace(k: K, oldvalue: V, newvalue: V): Boolean = { + val hc = computeHash(k) + insertifhc(k, hc, newvalue, oldvalue.asInstanceOf[AnyRef], fullEquals = true).nonEmpty + } + + override private[collection] def replaceRefEq(k: K, oldValue: V, newValue: V): Boolean = { + val hc = computeHash(k) + insertifhc(k, hc, newValue, oldValue.asInstanceOf[AnyRef], fullEquals = false).nonEmpty + } + + def replace(k: K, v: V): Option[V] = { + val hc = computeHash(k) + insertifhc(k, hc, v, INode.KEY_PRESENT, fullEquals = false /* unused */) + } + + def iterator: Iterator[(K, V)] = { + if (nonReadOnly) readOnlySnapshot().iterator + else new TrieMapIterator(0, this) + } + + //////////////////////////////////////////////////////////////////////////// + // + // scala/bug#10177 These methods need overrides as the inherited implementations + // call `.iterator` more than once, which doesn't guarantee a coherent + // view of the data if there is a concurrent writer + // Note that the we don't need overrides for keysIterator or valuesIterator + // TrieMapTest validates the behaviour. + override def values: Iterable[V] = { + if (nonReadOnly) readOnlySnapshot().values + else super.values + } + override def keySet: Set[K] = { + if (nonReadOnly) readOnlySnapshot().keySet + else super.keySet + } + + override def view: MapView[K, V] = if (nonReadOnly) readOnlySnapshot().view else super.view + + @deprecated("Use .view.filterKeys(f). A future version will include a strict version of this method (for now, .view.filterKeys(p).toMap).", "2.13.0") + override def filterKeys(p: K => Boolean): collection.MapView[K, V] = view.filterKeys(p) + + @deprecated("Use .view.mapValues(f). A future version will include a strict version of this method (for now, .view.mapValues(f).toMap).", "2.13.0") + override def mapValues[W](f: V => W): collection.MapView[K, W] = view.mapValues(f) + // END extra overrides + /////////////////////////////////////////////////////////////////// + + override def size: Int = + if (nonReadOnly) readOnlySnapshot().size + else RDCSS_READ_ROOT().cachedSize(this) + override def knownSize: Int = + if (nonReadOnly) -1 + else RDCSS_READ_ROOT().knownSize(this) + override def isEmpty: Boolean = + (if (nonReadOnly) readOnlySnapshot() else this).sizeIs == 0 // sizeIs checks knownSize + override protected[this] def className = "TrieMap" + + override def lastOption: Option[(K, V)] = if (isEmpty) None else Try(last).toOption +} + + +@SerialVersionUID(3L) +object TrieMap extends MapFactory[TrieMap] { + + def empty[K, V]: TrieMap[K, V] = new TrieMap[K, V] + + def from[K, V](it: IterableOnce[(K, V)]): TrieMap[K, V] = new TrieMap[K, V]() ++= it + + def newBuilder[K, V]: mutable.GrowableBuilder[(K, V), TrieMap[K, V]] = new GrowableBuilder(empty[K, V]) + + @transient + val inodeupdater: AtomicReferenceFieldUpdater[INodeBase[_, _], MainNode[_, _]] = AtomicReferenceFieldUpdater.newUpdater(classOf[INodeBase[_, _]], classOf[MainNode[_, _]], "mainnode") + + class MangledHashing[K] extends Hashing[K] { + def hash(k: K): Int = scala.util.hashing.byteswap32(k.##) + } + + private[concurrent] object RemovalPolicy { + final val Always = 0 + final val FullEquals = 1 + final val ReferenceEq = 2 + + def shouldRemove[V](removalPolicy: Int)(a: V, b: V): Boolean = + removalPolicy match { + case Always => true + case FullEquals => a == b + case ReferenceEq => a.asInstanceOf[AnyRef] eq b.asInstanceOf[AnyRef] + } + } +} + +// 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)] { + private val stack = new Array[Array[BasicNode]](7) + private val stackpos = new Array[Int](7) + private var depth = -1 + private var subiter: Iterator[(K, V)] = null + private var current: KVNode[K, V] = null + + if (mustInit) initialize() + + def hasNext = (current ne null) || (subiter ne null) + + def next() = if (hasNext) { + var r: (K, V) = null + if (subiter ne null) { + r = subiter.next() + checkSubiter() + } else { + r = current.kvPair + advance() + } + r + } else Iterator.empty.next() + + private def readin(in: INode[K, V]) = in.gcasRead(ct) match { + case cn: CNode[K, V] => + depth += 1 + stack(depth) = cn.array + stackpos(depth) = -1 + advance() + case tn: TNode[K, V] => + current = tn + case ln: LNode[K, V] => + subiter = ln.entries.iterator + checkSubiter() + case null => + current = null + case mainNode => throw new MatchError(mainNode) + } + + private def checkSubiter() = if (!subiter.hasNext) { + subiter = null + advance() + } + + private def initialize(): Unit = { + assert(ct.isReadOnly) + + val r = ct.RDCSS_READ_ROOT() + readin(r) + } + + @tailrec + final def advance(): Unit = if (depth >= 0) { + val npos = stackpos(depth) + 1 + if (npos < stack(depth).length) { + stackpos(depth) = npos + stack(depth)(npos) match { + case sn: SNode[K, V] @uc => current = sn + case in: INode[K, V] @uc => readin(in) + case basicNode => throw new MatchError(basicNode) + } + } else { + depth -= 1 + advance() + } + } else current = null + + protected def newIterator(_lev: Int, _ct: TrieMap[K, V], _mustInit: Boolean): TrieMapIterator[K, V] = new TrieMapIterator[K, V](_lev, _ct, _mustInit) + + protected def dupTo(it: TrieMapIterator[K, V]): Unit = { + it.level = this.level + it.ct = this.ct + it.depth = this.depth + it.current = this.current + + // these need a deep copy + Array.copy(this.stack, 0, it.stack, 0, 7) + Array.copy(this.stackpos, 0, it.stackpos, 0, 7) + + // this one needs to be evaluated + if (this.subiter == null) it.subiter = null + else { + val lst = this.subiter.to(immutable.List) + this.subiter = lst.iterator + it.subiter = lst.iterator + } + } + + /** 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) { + // the case where an LNode is being iterated + val it = newIterator(level + 1, ct, _mustInit = false) + it.depth = -1 + it.subiter = this.subiter + it.current = null + this.subiter = null + advance() + this.level += 1 + Seq(it, this) + } else if (depth == -1) { + this.level += 1 + Seq(this) + } else { + var d = 0 + while (d <= depth) { + val rem = stack(d).length - 1 - stackpos(d) + if (rem > 0) { + val (arr1, arr2) = stack(d).drop(stackpos(d) + 1).splitAt(rem / 2) + stack(d) = arr1 + stackpos(d) = -1 + val it = newIterator(level + 1, ct, _mustInit = false) + it.stack(0) = arr2 + it.stackpos(0) = -1 + it.depth = 0 + it.advance() // <-- fix it + this.level += 1 + return Seq(this, it) + } + d += 1 + } + this.level += 1 + Seq(this) + } + +} + +/** Only used for ctrie serialization. */ +@SerialVersionUID(3L) +private[concurrent] case object TrieMapSerializationEnd diff --git a/library/src/scala/collection/convert/AsJavaConverters.scala b/library/src/scala/collection/convert/AsJavaConverters.scala new file mode 100644 index 000000000000..f82546e251a7 --- /dev/null +++ b/library/src/scala/collection/convert/AsJavaConverters.scala @@ -0,0 +1,261 @@ +/* + * 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 collection +package convert + +import scala.language.`2.13` +import java.util.{concurrent => juc} +import java.{lang => jl, util => ju} + +import scala.{unchecked => uc} + +/** Defines converter methods from Scala to Java collections. + * These methods are available through the [[scala.jdk.javaapi.CollectionConverters]] object. + */ +trait AsJavaConverters { + import JavaCollectionWrappers._ + + /** + * Converts a Scala `Iterator` to a Java `Iterator`. + * + * The returned Java `Iterator` is backed by the provided Scala `Iterator` and any side-effects of + * using it via the Java interface will be visible via the Scala interface and vice versa. + * + * If the Scala `Iterator` was previously obtained from an implicit or explicit call of + * `asScala` then the original Java `Iterator` will be returned. + * + * @param i The Scala `Iterator` to be converted. + * @return A Java `Iterator` view of the argument. + */ + def asJava[A](i: Iterator[A]): ju.Iterator[A] = i match { + case null => null + case wrapper: JIteratorWrapper[A @uc] => wrapper.underlying + case _ => new IteratorWrapper(i) + } + + /** + * Converts a Scala `Iterator` to a Java `Enumeration`. + * + * The returned Java `Enumeration` is backed by the provided Scala `Iterator` and any side-effects + * of using it via the Java interface will be visible via the Scala interface and vice versa. + * + * If the Scala `Iterator` was previously obtained from an implicit or explicit call of + * `asScala` then the original Java `Enumeration` will be returned. + * + * @param i The Scala `Iterator` to be converted. + * @return A Java `Enumeration` view of the argument. + */ + def asJavaEnumeration[A](i: Iterator[A]): ju.Enumeration[A] = i match { + case null => null + case wrapper: JEnumerationWrapper[A @uc] => wrapper.underlying + case _ => new IteratorWrapper(i) + } + + /** + * Converts a Scala `Iterable` to a Java `Iterable`. + * + * The returned Java `Iterable` is backed by the provided Scala `Iterable` and any side-effects of + * using it via the Java interface will be visible via the Scala interface and vice versa. + * + * If the Scala `Iterable` was previously obtained from an implicit or explicit call of + * `asScala` then the original Java `Iterable` will be returned. + * + * @param i The Scala `Iterable` to be converted. + * @return A Java `Iterable` view of the argument. + */ + def asJava[A](i: Iterable[A]): jl.Iterable[A] = i match { + case null => null + case wrapper: JIterableWrapper[A @uc] => wrapper.underlying + case _ => new IterableWrapper(i) + } + + /** + * Converts a Scala `Iterable` to an immutable Java `Collection`. + * + * If the Scala `Iterable` was previously obtained from an implicit or explicit call of + * `asScala` then the original Java `Collection` will be returned. + * + * @param i The Scala `Iterable` to be converted. + * @return A Java `Collection` view of the argument. + */ + def asJavaCollection[A](i: Iterable[A]): ju.Collection[A] = i match { + case null => null + case wrapper: JCollectionWrapper[A @uc] => wrapper.underlying + case _ => new IterableWrapper(i) + } + + /** + * Converts a Scala mutable `Buffer` to a Java List. + * + * The returned Java List is backed by the provided Scala `Buffer` and any side-effects of using + * it via the Java interface will be visible via the Scala interface and vice versa. + * + * If the Scala `Buffer` was previously obtained from an implicit or explicit call of + * `asScala` then the original Java `List` will be returned. + * + * @param b The Scala `Buffer` to be converted. + * @return A Java `List` view of the argument. + */ + def asJava[A](b: mutable.Buffer[A]): ju.List[A] = b match { + case null => null + case wrapper: JListWrapper[A @uc] => wrapper.underlying + case _ => new MutableBufferWrapper(b) + } + + /** + * Converts a Scala mutable `Seq` to a Java `List`. + * + * The returned Java `List` is backed by the provided Scala `Seq` and any side-effects of using it + * via the Java interface will be visible via the Scala interface and vice versa. + * + * If the Scala `Seq` was previously obtained from an implicit or explicit call of + * `asScala` then the original Java `List` will be returned. + * + * @param s The Scala `Seq` to be converted. + * @return A Java `List` view of the argument. + */ + def asJava[A](s: mutable.Seq[A]): ju.List[A] = s match { + case null => null + case wrapper: JListWrapper[A @uc] => wrapper.underlying + case _ => new MutableSeqWrapper(s) + } + + /** + * Converts a Scala `Seq` to a Java `List`. + * + * The returned Java `List` is backed by the provided Scala `Seq` and any side-effects of using it + * via the Java interface will be visible via the Scala interface and vice versa. + * + * If the Scala `Seq` was previously obtained from an implicit or explicit call of + * `asScala` then the original Java `List` will be returned. + * + * @param s The Scala `Seq` to be converted. + * @return A Java `List` view of the argument. + */ + def asJava[A](s: Seq[A]): ju.List[A] = s match { + case null => null + case wrapper: JListWrapper[A @uc] => wrapper.underlying + case _ => new SeqWrapper(s) + } + + /** + * Converts a Scala mutable `Set` to a Java `Set`. + * + * The returned Java `Set` is backed by the provided Scala `Set` and any side-effects of using it + * via the Java interface will be visible via the Scala interface and vice versa. + * + * If the Scala `Set` was previously obtained from an implicit or explicit call of + * `asScala` then the original Java `Set` will be returned. + * + * @param s The Scala mutable `Set` to be converted. + * @return A Java `Set` view of the argument. + */ + def asJava[A](s: mutable.Set[A]): ju.Set[A] = s match { + case null => null + case wrapper: JSetWrapper[A @uc] => wrapper.underlying + case _ => new MutableSetWrapper(s) + } + + /** + * Converts a Scala `Set` to a Java `Set`. + * + * The returned Java `Set` is backed by the provided Scala `Set` and any side-effects of using it + * via the Java interface will be visible via the Scala interface and vice versa. + * + * If the Scala `Set` was previously obtained from an implicit or explicit call of + * `asScala` then the original Java `Set` will be returned. + * + * @param s The Scala `Set` to be converted. + * @return A Java `Set` view of the argument. + */ + def asJava[A](s: Set[A]): ju.Set[A] = s match { + case null => null + case wrapper: JSetWrapper[A @uc] => wrapper.underlying + case _ => new SetWrapper(s) + } + + /** + * Converts a Scala mutable `Map` to a Java `Map`. + * + * The returned Java `Map` is backed by the provided Scala `Map` and any side-effects of using it + * via the Java interface will be visible via the Scala interface and vice versa. + * + * If the Scala `Map` was previously obtained from an implicit or explicit call of + * `asScala` then the original Java `Map` will be returned. + * + * @param m The Scala mutable `Map` to be converted. + * @return A Java `Map` view of the argument. + */ + def asJava[K, V](m: mutable.Map[K, V]): ju.Map[K, V] = m match { + case null => null + case wrapper: JMapWrapper[K @uc, V @uc] => wrapper.underlying + case _ => new MutableMapWrapper(m) + } + + /** + * Converts a Scala mutable `Map` to a Java `Dictionary`. + * + * The returned Java `Dictionary` is backed by the provided Scala `Dictionary` and any + * side-effects of using it via the Java interface will be visible via the Scala interface and + * vice versa. + * + * If the Scala `Map` was previously obtained from an implicit or explicit call of + * `asScala` then the original Java `Dictionary` will be returned. + * + * @param m The Scala `Map` to be converted. + * @return A Java `Dictionary` view of the argument. + */ + def asJavaDictionary[K, V](m: mutable.Map[K, V]): ju.Dictionary[K, V] = m match { + case null => null + case wrapper: JDictionaryWrapper[K @uc, V @uc] => wrapper.underlying + case _ => new DictionaryWrapper(m) + } + + /** + * Converts a Scala `Map` to a Java `Map`. + * + * The returned Java `Map` is backed by the provided Scala `Map` and any side-effects of using it + * via the Java interface will be visible via the Scala interface and vice versa. + * + * If the Scala `Map` was previously obtained from an implicit or explicit call of + * `asScala` then the original Java `Map` will be returned. + * + * @param m The Scala `Map` to be converted. + * @return A Java `Map` view of the argument. + */ + def asJava[K, V](m: Map[K, V]): ju.Map[K, V] = m match { + case null => null + case wrapper: JMapWrapper[K @uc, V @uc] => wrapper.underlying + case _ => new MapWrapper(m) + } + + /** + * Converts a Scala mutable `concurrent.Map` to a Java `ConcurrentMap`. + * + * The returned Java `ConcurrentMap` is backed by the provided Scala `concurrent.Map` and any + * side-effects of using it via the Java interface will be visible via the Scala interface and + * vice versa. + * + * If the Scala `concurrent.Map` was previously obtained from an implicit or explicit call of + * `asScala` then the original Java `ConcurrentMap` will be returned. + * + * @param m The Scala `concurrent.Map` to be converted. + * @return A Java `ConcurrentMap` view of the argument. + */ + def asJava[K, V](m: concurrent.Map[K, V]): juc.ConcurrentMap[K, V] = m match { + case null => null + case wrapper: JConcurrentMapWrapper[K @uc, V @uc] => wrapper.underlying + case _ => new ConcurrentMapWrapper(m) + } +} diff --git a/library/src/scala/collection/convert/AsJavaExtensions.scala b/library/src/scala/collection/convert/AsJavaExtensions.scala new file mode 100644 index 000000000000..406ad7e8b82f --- /dev/null +++ b/library/src/scala/collection/convert/AsJavaExtensions.scala @@ -0,0 +1,109 @@ +/* + * 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 collection +package convert + +import scala.language.`2.13` +import java.util.{concurrent => juc} +import java.{lang => jl, util => ju} + +/** Defines `asJava` extension methods, available through [[scala.jdk.CollectionConverters]]. */ +trait AsJavaExtensions { + import scala.jdk.javaapi.{CollectionConverters => conv} + + implicit class IteratorHasAsJava[A](i: Iterator[A]) { + /** Converts a Scala `Iterator` to a Java `Iterator`, see + * [[AsJavaConverters.asJava[A](i:Iterator[A])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. + */ + def asJava: ju.Iterator[A] = conv.asJava(i) + + /** Converts a Scala `Iterator` to a Java `Enumeration`, see + * [[AsJavaConverters.asJavaEnumeration `scala.jdk.javaapi.CollectionConverters.asJavaEnumeration`]]. + */ + def asJavaEnumeration: ju.Enumeration[A] = conv.asJavaEnumeration(i) + } + + implicit class IterableHasAsJava[A](i: Iterable[A]) { + /** Converts a Scala `Iterable` to a Java `Iterable`, see + * [[AsJavaConverters.asJava[A](i:Iterable[A])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. + */ + def asJava: jl.Iterable[A] = conv.asJava(i) + + /** Converts a Scala `Iterator` to a Java `Collection`, see + * [[AsJavaConverters.asJavaCollection `scala.jdk.javaapi.CollectionConverters.asJavaCollection`]]. + */ + def asJavaCollection: ju.Collection[A] = conv.asJavaCollection(i) + } + + implicit class BufferHasAsJava[A](b: mutable.Buffer[A]) { + /** Converts a Scala `Buffer` to a Java `List`, see + * [[AsJavaConverters.asJava[A](b:scala\.collection\.mutable\.Buffer[A])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. + */ + def asJava: ju.List[A] = conv.asJava(b) + } + + implicit class MutableSeqHasAsJava[A](s: mutable.Seq[A]) { + /** Converts a Scala `Seq` to a Java `List`, see + * [[AsJavaConverters.asJava[A](s:scala\.collection\.mutable\.Seq[A])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. + */ + def asJava: ju.List[A] = conv.asJava(s) + } + + implicit class SeqHasAsJava[A](s: Seq[A]) { + /** Converts a Scala `Seq` to a Java `List`, see + * [[AsJavaConverters.asJava[A](s:scala\.collection\.Seq[A])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. + */ + def asJava: ju.List[A] = conv.asJava(s) + } + + implicit class MutableSetHasAsJava[A](s: mutable.Set[A]) { + /** Converts a Scala `mutable.Set` to a Java `Set`, see + * [[AsJavaConverters.asJava[A](s:scala\.collection\.mutable\.Set[A])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. + */ + def asJava: ju.Set[A] = conv.asJava(s) + } + + implicit class SetHasAsJava[A](s: Set[A]) { + /** Converts a Scala `Set` to a Java `Set`, see + * [[AsJavaConverters.asJava[A](s:scala\.collection\.Set[A])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. + */ + def asJava: ju.Set[A] = conv.asJava(s) + } + + implicit class MutableMapHasAsJava[K, V](m: mutable.Map[K, V]) { + /** Converts a Scala `mutable.Map` to a Java `Map`, see + * [[AsJavaConverters.asJava[K,V](m:scala\.collection\.mutable\.Map[K,V])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. + */ + def asJava: ju.Map[K, V] = conv.asJava(m) + + /** Converts a Scala `mutable.Map` to a Java `Map`, see + * [[AsJavaConverters.asJavaDictionary `scala.jdk.javaapi.CollectionConverters.asJavaDictionary`]]. + */ + def asJavaDictionary: ju.Dictionary[K, V] = conv.asJavaDictionary(m) + } + + implicit class MapHasAsJava[K, V](m: Map[K, V]) { + /** Converts a Scala `Map` to a Java `Map`, see + * [[AsJavaConverters.asJava[K,V](m:scala\.collection\.Map[K,V])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. + */ + def asJava: ju.Map[K, V] = conv.asJava(m) + } + + implicit class ConcurrentMapHasAsJava[K, V](m: concurrent.Map[K, V]) { + /** Converts a Scala `concurrent.Map` to a Java `ConcurrentMap`, see + * [[AsJavaConverters.asJava[K,V](m:scala\.collection\.concurrent\.Map[K,V])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. + */ + def asJava: juc.ConcurrentMap[K, V] = conv.asJava(m) + } +} diff --git a/library/src/scala/collection/convert/AsScalaConverters.scala b/library/src/scala/collection/convert/AsScalaConverters.scala new file mode 100644 index 000000000000..dac9b9484369 --- /dev/null +++ b/library/src/scala/collection/convert/AsScalaConverters.scala @@ -0,0 +1,208 @@ +/* + * 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 collection +package convert + +import scala.language.`2.13` +import java.util.{concurrent => juc} +import java.{lang => jl, util => ju} + +import scala.{unchecked => uc} + +/** Defines converter methods from Java to Scala collections. + * These methods are available through the [[scala.jdk.javaapi.CollectionConverters]] object. + */ +trait AsScalaConverters { + import JavaCollectionWrappers._ + + /** + * Converts a Java `Iterator` to a Scala `Iterator`. + * + * The returned Scala `Iterator` is backed by the provided Java `Iterator` and any side-effects of + * using it via the Scala interface will be visible via the Java interface and vice versa. + * + * If the Java `Iterator` was previously obtained from an implicit or explicit call of + * `asJava` then the original Scala `Iterator` will be returned. + * + * @param i The Java `Iterator` to be converted. + * @return A Scala `Iterator` view of the argument. + */ + def asScala[A](i: ju.Iterator[A]): Iterator[A] = i match { + case null => null + case wrapper: IteratorWrapper[A @uc] => wrapper.underlying + case _ => new JIteratorWrapper(i) + } + + /** + * Converts a Java `Enumeration` to a Scala `Iterator`. + * + * The returned Scala `Iterator` is backed by the provided Java `Enumeration` and any side-effects + * of using it via the Scala interface will be visible via the Java interface and vice versa. + * + * If the Java `Enumeration` was previously obtained from an implicit or explicit call of + * `asJavaEnumeration` then the original Scala `Iterator` will be returned. + * + * @param e The Java `Enumeration` to be converted. + * @return A Scala `Iterator` view of the argument. + */ + def asScala[A](e: ju.Enumeration[A]): Iterator[A] = e match { + case null => null + case wrapper: IteratorWrapper[A @uc] => wrapper.underlying + case _ => new JEnumerationWrapper(e) + } + + /** + * Converts a Java `Iterable` to a Scala `Iterable`. + * + * The returned Scala `Iterable` is backed by the provided Java `Iterable` and any side-effects of + * using it via the Scala interface will be visible via the Java interface and vice versa. + * + * If the Java `Iterable` was previously obtained from an implicit or explicit call of + * `asJava` then the original Scala `Iterable` will be returned. + * + * @param i The Java `Iterable` to be converted. + * @return A Scala `Iterable` view of the argument. + */ + def asScala[A](i: jl.Iterable[A]): Iterable[A] = i match { + case null => null + case wrapper: IterableWrapper[A @uc] => wrapper.underlying + case _ => new JIterableWrapper(i) + } + + /** + * Converts a Java `Collection` to a Scala `Iterable`. + * + * If the Java `Collection` was previously obtained from an implicit or explicit call of + * `asJavaCollection` then the original Scala `Iterable` will be returned. + * + * @param c The Java `Collection` to be converted. + * @return A Scala `Iterable` view of the argument. + */ + def asScala[A](c: ju.Collection[A]): Iterable[A] = c match { + case null => null + case wrapper: IterableWrapper[A @uc] => wrapper.underlying + case _ => new JCollectionWrapper(c) + } + + /** + * Converts a Java `List` to a Scala mutable `Buffer`. + * + * The returned Scala `Buffer` is backed by the provided Java `List` and any side-effects of using + * it via the Scala interface will be visible via the Java interface and vice versa. + * + * If the Java `List` was previously obtained from an implicit or explicit call of + * `asJava` then the original Scala `Buffer` will be returned. + * + * @param l The Java `List` to be converted. + * @return A Scala mutable `Buffer` view of the argument. + */ + def asScala[A](l: ju.List[A]): mutable.Buffer[A] = l match { + case null => null + case wrapper: MutableBufferWrapper[A @uc] => wrapper.underlying + case _ => new JListWrapper(l) + } + + /** + * Converts a Java `Set` to a Scala mutable `Set`. + * + * The returned Scala `Set` is backed by the provided Java `Set` and any side-effects of using it + * via the Scala interface will be visible via the Java interface and vice versa. + * + * If the Java `Set` was previously obtained from an implicit or explicit call of + * `asJava` then the original Scala `Set` will be returned. + * + * @param s The Java `Set` to be converted. + * @return A Scala mutable `Set` view of the argument. + */ + def asScala[A](s: ju.Set[A]): mutable.Set[A] = s match { + case null => null + case wrapper: MutableSetWrapper[A @uc] => wrapper.underlying + case _ => new JSetWrapper(s) + } + + /** + * Converts a Java `Map` to a Scala mutable `Map`. + * + * The returned Scala `Map` is backed by the provided Java `Map` and any side-effects of using it + * via the Scala interface will be visible via the Java interface and vice versa. + * + * If the Java `Map` was previously obtained from an implicit or explicit call of + * `asJava` then the original Scala `Map` will be returned. + * + * If the wrapped map is synchronized (e.g. from `java.util.Collections.synchronizedMap`), it is + * your responsibility to wrap all non-atomic operations with `underlying.synchronized`. + * This includes `get`, as `java.util.Map`'s API does not allow for an atomic `get` when `null` + * values may be present. + * + * @param m The Java `Map` to be converted. + * @return A Scala mutable `Map` view of the argument. + */ + def asScala[K, V](m: ju.Map[K, V]): mutable.Map[K, V] = m match { + case null => null + case wrapper: MutableMapWrapper[K @uc, V @uc] => wrapper.underlying + case _ => new JMapWrapper(m) + } + + /** + * Converts a Java `ConcurrentMap` to a Scala mutable `ConcurrentMap`. + * + * The returned Scala `ConcurrentMap` is backed by the provided Java `ConcurrentMap` and any + * side-effects of using it via the Scala interface will be visible via the Java interface and + * vice versa. + * + * If the Java `ConcurrentMap` was previously obtained from an implicit or explicit call of + * `asJava` then the original Scala `ConcurrentMap` will be returned. + * + * @param m The Java `ConcurrentMap` to be converted. + * @return A Scala mutable `ConcurrentMap` view of the argument. + */ + def asScala[K, V](m: juc.ConcurrentMap[K, V]): concurrent.Map[K, V] = m match { + case null => null + case wrapper: ConcurrentMapWrapper[K @uc, V @uc] => wrapper.underlyingConcurrentMap + case _ => new JConcurrentMapWrapper(m) + } + + /** + * Converts a Java `Dictionary` to a Scala mutable `Map`. + * + * The returned Scala `Map` is backed by the provided Java `Dictionary` and any side-effects of + * using it via the Scala interface will be visible via the Java interface and vice versa. + * + * If the Java `Dictionary` was previously obtained from an implicit or explicit call of + * `asJavaDictionary` then the original Scala `Map` will be returned. + * + * @param d The Java `Dictionary` to be converted. + * @return A Scala mutable `Map` view of the argument. + */ + def asScala[K, V](d: ju.Dictionary[K, V]): mutable.Map[K, V] = d match { + case null => null + case wrapper: DictionaryWrapper[K @uc, V @uc] => wrapper.underlying + case _ => new JDictionaryWrapper(d) + } + + /** + * Converts a Java `Properties` to a Scala mutable `Map[String, String]`. + * + * The returned Scala `Map[String, String]` is backed by the provided Java `Properties` and any + * side-effects of using it via the Scala interface will be visible via the Java interface and + * vice versa. + * + * @param p The Java `Properties` to be converted. + * @return A Scala mutable `Map[String, String]` view of the argument. + */ + def asScala(p: ju.Properties): mutable.Map[String, String] = p match { + case null => null + case _ => new JPropertiesWrapper(p) + } +} diff --git a/library/src/scala/collection/convert/AsScalaExtensions.scala b/library/src/scala/collection/convert/AsScalaExtensions.scala new file mode 100644 index 000000000000..65f017495280 --- /dev/null +++ b/library/src/scala/collection/convert/AsScalaExtensions.scala @@ -0,0 +1,94 @@ +/* + * 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 collection +package convert + +import scala.language.`2.13` +import java.util.{concurrent => juc} +import java.{lang => jl, util => ju} + +/** Defines `asScala` extension methods, available through [[scala.jdk.CollectionConverters]]. */ +trait AsScalaExtensions { + import scala.jdk.javaapi.{CollectionConverters => conv} + + implicit class IteratorHasAsScala[A](i: ju.Iterator[A]) { + /** Converts a Java `Iterator` to a Scala `Iterator`, see + * [[AsScalaConverters.asScala[A](i:java\.util\.Iterator[A])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. + */ + def asScala: Iterator[A] = conv.asScala(i) + } + + implicit class EnumerationHasAsScala[A](e: ju.Enumeration[A]) { + /** Converts a Java `Enumeration` to a Scala `Iterator`, see + * [[AsScalaConverters.asScala[A](e:java\.util\.Enumeration[A])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. + */ + def asScala: Iterator[A] = conv.asScala(e) + } + + implicit class IterableHasAsScala[A](i: jl.Iterable[A]) { + /** Converts a Java `Iterable` to a Scala `Iterable`, see + * [[AsScalaConverters.asScala[A](i:Iterable[A])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. + */ + def asScala: Iterable[A] = conv.asScala(i) + } + + implicit class CollectionHasAsScala[A](c: ju.Collection[A]) { + /** Converts a Java `Collection` to a Scala `Iterable`, see + * [[AsScalaConverters.asScala[A](c:java\.util\.Collection[A])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. + */ + def asScala: Iterable[A] = conv.asScala(c) + } + + implicit class ListHasAsScala[A](l: ju.List[A]) { + /** Converts a Java `List` to a Scala `Buffer`, see + * [[AsScalaConverters.asScala[A](l:java\.util\.List[A])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. + */ + def asScala: mutable.Buffer[A] = conv.asScala(l) + } + + implicit class SetHasAsScala[A](s: ju.Set[A]) { + /** Converts a Java `Set` to a Scala `Set`, see + * [[AsScalaConverters.asScala[A](s:java\.util\.Set[A])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. + */ + def asScala: mutable.Set[A] = conv.asScala(s) + } + + implicit class MapHasAsScala[K, V](m: ju.Map[K, V]) { + /** Converts a Java `Map` to a Scala `Map`, see + * [[AsScalaConverters.asScala[A,B](m:java\.util\.Map[A,B])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. + */ + def asScala: mutable.Map[K, V] = conv.asScala(m) + } + + implicit class ConcurrentMapHasAsScala[K, V](m: juc.ConcurrentMap[K, V]) { + /** Converts a Java `ConcurrentMap` to a Scala `concurrent.Map`, see + * [[AsScalaConverters.asScala[A,B](m:java\.util\.concurrent\.ConcurrentMap[A,B])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. + */ + def asScala: concurrent.Map[K, V] = conv.asScala(m) + } + + implicit class DictionaryHasAsScala[K, V](d: ju.Dictionary[K, V]) { + /** Converts a Java `Dictionary` to a Scala `Map`, see + * [[AsScalaConverters.asScala[A,B](d:java\.util\.Dictionary[A,B])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. + */ + def asScala: mutable.Map[K, V] = conv.asScala(d) + } + + implicit class PropertiesHasAsScala(i: ju.Properties) { + /** Converts a Java `Properties` to a Scala `Map`, see + * [[AsScalaConverters.asScala(p:java\.util\.Properties)* `scala.jdk.javaapi.CollectionConverters.asScala`]]. + */ + def asScala: mutable.Map[String, String] = conv.asScala(i) + } +} diff --git a/library/src/scala/collection/convert/ImplicitConversions.scala b/library/src/scala/collection/convert/ImplicitConversions.scala new file mode 100644 index 000000000000..ef72a79d5795 --- /dev/null +++ b/library/src/scala/collection/convert/ImplicitConversions.scala @@ -0,0 +1,182 @@ +/* + * 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 collection +package convert + +import scala.language.`2.13` +import java.util.{concurrent => juc} +import java.{lang => jl, util => ju} + +import scala.collection.JavaConverters._ +import scala.language.implicitConversions + +/** Defines implicit converter methods from Java to Scala collections. */ +@deprecated("Use `scala.jdk.CollectionConverters` instead", "2.13.0") +trait ToScalaImplicits { + /** Implicitly converts a Java `Iterator` to a Scala `Iterator`. + * @see [[JavaConverters.asScalaIterator]] + */ + implicit def `iterator asScala`[A](it: ju.Iterator[A]): Iterator[A] = asScalaIterator(it) + + /** Implicitly converts a Java `Enumeration` to a Scala `Iterator`. + * @see [[JavaConverters.enumerationAsScalaIterator]] + */ + implicit def `enumeration AsScalaIterator`[A](i: ju.Enumeration[A]): Iterator[A] = enumerationAsScalaIterator(i) + + /** Implicitly converts a Java `Iterable` to a Scala `Iterable`. + * @see [[JavaConverters.iterableAsScalaIterable]] + */ + implicit def `iterable AsScalaIterable`[A](i: jl.Iterable[A]): Iterable[A] = iterableAsScalaIterable(i) + + /** Implicitly converts a Java `Collection` to an Scala `Iterable`. + * @see [[JavaConverters.collectionAsScalaIterable]] + */ + implicit def `collection AsScalaIterable`[A](i: ju.Collection[A]): Iterable[A] = collectionAsScalaIterable(i) + + /** Implicitly converts a Java `List` to a Scala mutable `Buffer`. + * @see [[JavaConverters.asScalaBuffer]] + */ + implicit def `list asScalaBuffer`[A](l: ju.List[A]): mutable.Buffer[A] = asScalaBuffer(l) + + /** Implicitly converts a Java `Set` to a Scala mutable `Set`. + * @see [[JavaConverters.asScalaSet]] + */ + implicit def `set asScala`[A](s: ju.Set[A]): mutable.Set[A] = asScalaSet(s) + + /** Implicitly converts a Java `Map` to a Scala mutable `Map`. + * @see [[JavaConverters.mapAsScalaMap]] + */ + implicit def `map AsScala`[K, V](m: ju.Map[K, V]): mutable.Map[K, V] = mapAsScalaMap(m) + + /** Implicitly converts a Java `ConcurrentMap` to a Scala mutable `ConcurrentMap`. + * @see [[JavaConverters.mapAsScalaConcurrentMap]] + */ + implicit def `map AsScalaConcurrentMap`[K, V](m: juc.ConcurrentMap[K, V]): concurrent.Map[K, V] = mapAsScalaConcurrentMap(m) + + /** Implicitly converts a Java `Dictionary` to a Scala mutable `Map`. + * @see [[JavaConverters.dictionaryAsScalaMap]] + */ + implicit def `dictionary AsScalaMap`[K, V](p: ju.Dictionary[K, V]): mutable.Map[K, V] = dictionaryAsScalaMap(p) + + /** Implicitly converts a Java `Properties` to a Scala `mutable Map[String, String]`. + * @see [[JavaConverters.propertiesAsScalaMap]] + */ + implicit def `properties AsScalaMap`(p: ju.Properties): mutable.Map[String, String] = propertiesAsScalaMap(p) +} + +/** Defines implicit conversions from Scala to Java collections. */ +@deprecated("Use `scala.jdk.CollectionConverters` instead", "2.13.0") +trait ToJavaImplicits { + /** Implicitly converts a Scala `Iterator` to a Java `Iterator`. + * @see [[JavaConverters.asJavaIterator]] + */ + implicit def `iterator asJava`[A](it: Iterator[A]): ju.Iterator[A] = asJavaIterator(it) + + /** Implicitly converts a Scala `Iterator` to a Java `Enumeration`. + * @see [[JavaConverters.asJavaEnumeration]] + */ + implicit def `enumeration asJava`[A](it: Iterator[A]): ju.Enumeration[A] = asJavaEnumeration(it) + + /** Implicitly converts a Scala `Iterable` to a Java `Iterable`. + * @see [[JavaConverters.asJavaIterable]] + */ + implicit def `iterable asJava`[A](i: Iterable[A]): jl.Iterable[A] = asJavaIterable(i) + + /** Implicitly converts a Scala `Iterable` to an immutable Java `Collection`. + * @see [[JavaConverters.asJavaCollection]] + */ + implicit def `collection asJava`[A](it: Iterable[A]): ju.Collection[A] = asJavaCollection(it) + + /** Implicitly converts a Scala mutable `Buffer` to a Java `List`. + * @see [[JavaConverters.bufferAsJavaList]] + */ + implicit def `buffer AsJavaList`[A](b: mutable.Buffer[A]): ju.List[A] = bufferAsJavaList(b) + + /** Implicitly converts a Scala mutable `Seq` to a Java `List`. + * @see [[JavaConverters.mutableSeqAsJavaList]] + */ + implicit def `mutableSeq AsJavaList`[A](seq: mutable.Seq[A]): ju.List[A] = mutableSeqAsJavaList(seq) + + /** Implicitly converts a Scala `Seq` to a Java `List`. + * @see [[JavaConverters.seqAsJavaList]] + */ + implicit def `seq AsJavaList`[A](seq: Seq[A]): ju.List[A] = seqAsJavaList(seq) + + /** Implicitly converts a Scala mutable `Set` to a Java `Set`. + * @see [[JavaConverters.mutableSetAsJavaSet]] + */ + implicit def `mutableSet AsJavaSet`[A](s: mutable.Set[A]): ju.Set[A] = mutableSetAsJavaSet(s) + + /** Implicitly converts a Scala `Set` to a Java `Set`. + * @see [[JavaConverters.setAsJavaSet]] + */ + implicit def `set AsJavaSet`[A](s: Set[A]): ju.Set[A] = setAsJavaSet(s) + + /** Implicitly converts a Scala mutable `Map` to a Java `Map`. + * @see [[JavaConverters.mutableMapAsJavaMap]] + */ + implicit def `mutableMap AsJavaMap`[K, V](m: mutable.Map[K, V]): ju.Map[K, V] = mutableMapAsJavaMap(m) + + /** Implicitly converts a Scala mutable `Map` to a Java `Dictionary`. + * @see [[JavaConverters.asJavaDictionary]] + */ + implicit def `dictionary asJava`[K, V](m: mutable.Map[K, V]): ju.Dictionary[K, V] = asJavaDictionary(m) + + /** Implicitly converts a Scala `Map` to a Java `Map`. + * @see [[JavaConverters.mapAsJavaMap]] + */ + implicit def `map AsJavaMap`[K, V](m: Map[K, V]): ju.Map[K, V] = mapAsJavaMap(m) + + /** Implicitly converts a Scala mutable `concurrent.Map` to a Java `ConcurrentMap`. + * @see [[JavaConverters.mapAsJavaConcurrentMap]] + */ + implicit def `map AsJavaConcurrentMap`[K, V](m: concurrent.Map[K, V]): juc.ConcurrentMap[K, V] = mapAsJavaConcurrentMap(m) +} + +/** + * Convenience for miscellaneous implicit conversions from Scala to Java collections API. + * + * It is recommended to use explicit conversions provided by [[collection.JavaConverters]] instead. + * Implicit conversions may cause unexpected issues, see [[ImplicitConversions]]. + */ +@deprecated("Use `scala.jdk.CollectionConverters` instead", "2.13.0") +object ImplicitConversionsToJava extends ToJavaImplicits + +/** + * Convenience for miscellaneous implicit conversions from Java to Scala collections API. + * + * It is recommended to use explicit conversions provided by [[collection.JavaConverters]] instead. + * Implicit conversions may cause unexpected issues, see [[ImplicitConversions]]. + */ +@deprecated("Use `scala.jdk.CollectionConverters` instead", "2.13.0") +object ImplicitConversionsToScala extends ToScalaImplicits + +/** + * Convenience for miscellaneous implicit conversions between Java and Scala collections API. + * + * It is recommended to use explicit conversions provided by [[collection.JavaConverters]] instead. + * Implicit conversions may cause unexpected issues. Example: + * + * {{{ + * import collection.convert.ImplicitConversions._ + * case class StringBox(s: String) + * val m = Map(StringBox("one") -> "uno") + * m.get("one") + * }}} + * + * The above example returns `null` instead of producing a type error at compile-time. The map is + * implicitly converted to a `java.util.Map` which provides a method `get(x: AnyRef)`. + */ +@deprecated("Use `scala.jdk.CollectionConverters` instead", "2.13.0") +object ImplicitConversions extends ToScalaImplicits with ToJavaImplicits diff --git a/library/src/scala/collection/convert/JavaCollectionWrappers.scala b/library/src/scala/collection/convert/JavaCollectionWrappers.scala new file mode 100644 index 000000000000..1679491efcb0 --- /dev/null +++ b/library/src/scala/collection/convert/JavaCollectionWrappers.scala @@ -0,0 +1,636 @@ +/* + * 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 collection +package convert + +import scala.language.`2.13` +import java.util.{concurrent => juc} +import java.util.{NavigableMap} +import java.{lang => jl, util => ju} + +import scala.jdk.CollectionConverters._ +import scala.util.Try +import scala.util.chaining._ +import scala.util.control.ControlThrowable + +/** Wrappers for exposing Scala collections as Java collections and vice-versa */ +@SerialVersionUID(3L) +// not private[convert] because `WeakHashMap` uses JMapWrapper +private[collection] object JavaCollectionWrappers extends Serializable { + @SerialVersionUID(3L) + class IteratorWrapper[A](val underlying: Iterator[A]) extends ju.Iterator[A] with ju.Enumeration[A] with Serializable { + def hasNext = underlying.hasNext + def next() = underlying.next() + def hasMoreElements = underlying.hasNext + def nextElement() = underlying.next() + override def remove(): Nothing = throw new UnsupportedOperationException + override def equals(other: Any): Boolean = other match { + case that: IteratorWrapper[_] => this.underlying == that.underlying + case _ => false + } + override def hashCode: Int = underlying.hashCode() + } + + @SerialVersionUID(3L) + class JIteratorWrapper[A](val underlying: ju.Iterator[A]) extends AbstractIterator[A] with Serializable { + def hasNext = underlying.hasNext + def next() = underlying.next + override def equals(other: Any): Boolean = other match { + case that: JIteratorWrapper[_] => this.underlying == that.underlying + case _ => false + } + override def hashCode: Int = underlying.hashCode() + } + + @SerialVersionUID(3L) + class JEnumerationWrapper[A](val underlying: ju.Enumeration[A]) extends AbstractIterator[A] with Serializable { + def hasNext = underlying.hasMoreElements + def next() = underlying.nextElement + override def equals(other: Any): Boolean = other match { + case that: JEnumerationWrapper[_] => this.underlying == that.underlying + case _ => false + } + override def hashCode: Int = underlying.hashCode() + } + + trait IterableWrapperTrait[A] extends ju.AbstractCollection[A] { + val underlying: Iterable[A] + def size = underlying.size + override def iterator: IteratorWrapper[A] = new IteratorWrapper(underlying.iterator) + override def isEmpty = underlying.isEmpty + } + + @SerialVersionUID(3L) + class IterableWrapper[A](val underlying: Iterable[A]) extends ju.AbstractCollection[A] with IterableWrapperTrait[A] with Serializable { + override def equals(other: Any): Boolean = other match { + case that: IterableWrapper[_] => this.underlying == that.underlying + case _ => false + } + override def hashCode: Int = underlying.hashCode() + } + + @SerialVersionUID(3L) + class JIterableWrapper[A](val underlying: jl.Iterable[A]) + extends AbstractIterable[A] + with StrictOptimizedIterableOps[A, Iterable, Iterable[A]] + with Serializable { + def iterator = underlying.iterator.asScala + override def iterableFactory: mutable.ArrayBuffer.type = mutable.ArrayBuffer + override def isEmpty: Boolean = !underlying.iterator().hasNext + override def equals(other: Any): Boolean = other match { + case that: JIterableWrapper[_] => this.underlying == that.underlying + case _ => false + } + override def hashCode: Int = underlying.hashCode() + } + + @SerialVersionUID(3L) + class JCollectionWrapper[A](val underlying: ju.Collection[A]) + extends AbstractIterable[A] + with StrictOptimizedIterableOps[A, Iterable, Iterable[A]] + with Serializable { + def iterator = underlying.iterator.asScala + override def size = underlying.size + override def knownSize: Int = if (underlying.isEmpty) 0 else super.knownSize + override def isEmpty = underlying.isEmpty + override def iterableFactory: mutable.ArrayBuffer.type = mutable.ArrayBuffer + override def equals(other: Any): Boolean = other match { + case that: JCollectionWrapper[_] => this.underlying == that.underlying + case _ => false + } + override def hashCode: Int = underlying.hashCode() + } + + @SerialVersionUID(3L) + class SeqWrapper[A](val underlying: Seq[A]) extends ju.AbstractList[A] with IterableWrapperTrait[A] with Serializable { + def get(i: Int) = underlying(i) + } + + @SerialVersionUID(3L) + class MutableSeqWrapper[A](val underlying: mutable.Seq[A]) extends ju.AbstractList[A] with IterableWrapperTrait[A] with Serializable { + def get(i: Int) = underlying(i) + override def set(i: Int, elem: A) = { + val p = underlying(i) + underlying(i) = elem + p + } + } + + @SerialVersionUID(3L) + class MutableBufferWrapper[A](val underlying: mutable.Buffer[A]) extends ju.AbstractList[A] with IterableWrapperTrait[A] with Serializable { + def get(i: Int) = underlying(i) + override def set(i: Int, elem: A) = { val p = underlying(i); underlying(i) = elem; p } + override def add(elem: A) = { underlying += elem; true } + override def remove(i: Int) = underlying remove i + } + + @SerialVersionUID(3L) + class JListWrapper[A](val underlying: ju.List[A]) + extends mutable.AbstractBuffer[A] + with SeqOps[A, mutable.Buffer, mutable.Buffer[A]] + with StrictOptimizedSeqOps[A, mutable.Buffer, mutable.Buffer[A]] + with IterableFactoryDefaults[A, mutable.Buffer] + with Serializable { + def length = underlying.size + override def knownSize: Int = if (underlying.isEmpty) 0 else super.knownSize + override def isEmpty = underlying.isEmpty + override def iterator: Iterator[A] = underlying.iterator.asScala + def apply(i: Int) = underlying.get(i) + def update(i: Int, elem: A) = underlying.set(i, elem) + def prepend(elem: A) = { underlying.subList(0, 0) add elem; this } + def addOne(elem: A): this.type = { underlying add elem; this } + def insert(idx: Int,elem: A): Unit = underlying.subList(0, idx).add(elem) + def insertAll(i: Int, elems: IterableOnce[A]) = { + val ins = underlying.subList(0, i) + elems.iterator.foreach(ins.add(_)) + } + def remove(i: Int) = underlying.remove(i) + def clear() = underlying.clear() + // Note: Clone cannot just call underlying.clone because in Java, only specific collections + // expose clone methods. Generically, they're protected. + override def clone(): JListWrapper[A] = new JListWrapper(new ju.ArrayList[A](underlying)) + def patchInPlace(from: Int, patch: scala.collection.IterableOnce[A], replaced: Int): this.type = { + remove(from, replaced) + insertAll(from, patch) + this + } + def remove(from: Int, n: Int): Unit = underlying.subList(from, from+n).clear() + override def iterableFactory: mutable.ArrayBuffer.type = mutable.ArrayBuffer + override def subtractOne(elem: A): this.type = { underlying.remove(elem.asInstanceOf[AnyRef]); this } + } + + @SerialVersionUID(3L) + class SetWrapper[A](underlying: Set[A]) extends ju.AbstractSet[A] with Serializable { self => + // Note various overrides to avoid performance gotchas. + override def contains(o: Object): Boolean = { + try { underlying.contains(o.asInstanceOf[A]) } + catch { case cce: ClassCastException => false } + } + override def isEmpty = underlying.isEmpty + def size = underlying.size + def iterator: ju.Iterator[A] = new ju.Iterator[A] { + val ui = underlying.iterator + var prev: Option[A] = None + def hasNext = ui.hasNext + def next = { val e = ui.next(); prev = Some(e); e } + override def remove() = prev match { + case Some(e) => + underlying match { + case ms: mutable.Set[a] => + ms remove e + prev = None + case _ => + throw new UnsupportedOperationException("remove") + } + case _ => + throw new IllegalStateException("next must be called at least once before remove") + } + } + } + + @SerialVersionUID(3L) + class MutableSetWrapper[A](val underlying: mutable.Set[A]) extends SetWrapper[A](underlying) with Serializable { + override def add(elem: A) = { + val sz = underlying.size + underlying += elem + sz < underlying.size + } + override def remove(elem: AnyRef) = + try underlying.remove(elem.asInstanceOf[A]) + catch { case ex: ClassCastException => false } + override def clear() = underlying.clear() + } + + @SerialVersionUID(3L) + class JSetWrapper[A](val underlying: ju.Set[A]) + extends mutable.AbstractSet[A] + with mutable.SetOps[A, mutable.Set, mutable.Set[A]] + with StrictOptimizedSetOps[A, mutable.Set, mutable.Set[A]] + with Serializable { + + override def size: Int = underlying.size + override def isEmpty: Boolean = underlying.isEmpty + override def knownSize: Int = if (underlying.isEmpty) 0 else super.knownSize + def iterator: Iterator[A] = underlying.iterator.asScala + + def contains(elem: A): Boolean = underlying.contains(elem) + + def addOne(elem: A): this.type = { underlying add elem; this } + def subtractOne(elem: A): this.type = { underlying remove elem; this } + + override def remove(elem: A): Boolean = underlying remove elem + + override def clear(): Unit = { + underlying.clear() + } + + override def empty: mutable.Set[A] = new JSetWrapper(new ju.HashSet[A]) + + // Note: Clone cannot just call underlying.clone because in Java, only specific collections + // expose clone methods. Generically, they're protected. + override def clone(): mutable.Set[A] = new JSetWrapper[A](new ju.LinkedHashSet[A](underlying)) + + override def iterableFactory: IterableFactory[mutable.Set] = mutable.HashSet + + override def filterInPlace(p: A => Boolean): this.type = { + if (underlying.size() > 0) underlying.removeIf(!p(_)) + this + } + } + + @SerialVersionUID(3L) + class MapWrapper[K, V](underlying: Map[K, V]) extends ju.AbstractMap[K, V] with Serializable { self => + override def size = underlying.size + + override def get(key: AnyRef): V = try { + underlying get key.asInstanceOf[K] match { + case None => null.asInstanceOf[V] + case Some(v) => v + } + } catch { + case ex: ClassCastException => null.asInstanceOf[V] + } + + override def entrySet: ju.Set[ju.Map.Entry[K, V]] = new ju.AbstractSet[ju.Map.Entry[K, V]] { + def size = self.size + + def iterator: ju.Iterator[ju.Map.Entry[K, V]] = new ju.Iterator[ju.Map.Entry[K, V]] { + val ui = underlying.iterator + var prev : Option[K] = None + + def hasNext = ui.hasNext + + def next(): ju.Map.Entry[K, V] = { + val (k, v) = ui.next() + prev = Some(k) + new ju.Map.Entry[K, V] { + def getKey = k + def getValue = v + def setValue(v1 : V) = self.put(k, v1) + + // It's important that this implementation conform to the contract + // specified in the javadocs of java.util.Map.Entry.hashCode + // + // See https://github.com/scala/bug/issues/10663 + override def hashCode = { + (if (k == null) 0 else k.hashCode()) ^ + (if (v == null) 0 else v.hashCode()) + } + + override def equals(other: Any) = other match { + case e: ju.Map.Entry[_, _] => k == e.getKey && v == e.getValue + case _ => false + } + } + } + + override def remove(): Unit = { + prev match { + case Some(k) => + underlying match { + case mm: mutable.Map[a, _] => + mm -= k + prev = None + case _ => + throw new UnsupportedOperationException("remove") + } + case _ => + throw new IllegalStateException("next must be called at least once before remove") + } + } + } + } + + override def containsKey(key: AnyRef): Boolean = try { + // Note: Subclass of collection.Map with specific key type may redirect generic + // contains to specific contains, which will throw a ClassCastException if the + // wrong type is passed. This is why we need a type cast to A inside a try/catch. + underlying.contains(key.asInstanceOf[K]) + } catch { + case ex: ClassCastException => false + } + } + + @SerialVersionUID(3L) + class MutableMapWrapper[K, V](val underlying: mutable.Map[K, V]) extends MapWrapper[K, V](underlying) { + override def put(k: K, v: V) = underlying.put(k, v) match { + case Some(v1) => v1 + case None => null.asInstanceOf[V] + } + + override def remove(k: AnyRef): V = try { + underlying remove k.asInstanceOf[K] match { + case None => null.asInstanceOf[V] + case Some(v) => v + } + } catch { + case ex: ClassCastException => null.asInstanceOf[V] + } + + override def clear() = underlying.clear() + } + + @SerialVersionUID(3L) + abstract class AbstractJMapWrapper[K, V] + extends mutable.AbstractMap[K, V] + with JMapWrapperLike[K, V, mutable.Map, mutable.Map[K, V]] with Serializable + + trait JMapWrapperLike[K, V, +CC[X, Y] <: mutable.MapOps[X, Y, CC, _], +C <: mutable.MapOps[K, V, CC, C]] + extends mutable.MapOps[K, V, CC, C] + with StrictOptimizedMapOps[K, V, CC, C] + with StrictOptimizedIterableOps[(K, V), mutable.Iterable, C] { + + def underlying: ju.Map[K, V] + + override def size = underlying.size + + // support Some(null) if currently bound to null + def get(k: K) = { + val v = underlying.get(k) + if (v != null) + Some(v) + else if (underlying.containsKey(k)) + Some(null.asInstanceOf[V]) + else + None + } + + override def getOrElseUpdate(key: K, op: => V): V = + underlying.computeIfAbsent(key, _ => op) match { + case null => update(key, null.asInstanceOf[V]); null.asInstanceOf[V] + case v => v + } + + def addOne(kv: (K, V)): this.type = { underlying.put(kv._1, kv._2); this } + def subtractOne(key: K): this.type = { underlying remove key; this } + + // support Some(null) if currently bound to null + override def put(k: K, v: V): Option[V] = + if (v == null) { + val present = underlying.containsKey(k) + val result = underlying.put(k, v) + if (present) Some(result) else None + } else { + var result: Option[V] = None + def recompute(k0: K, v0: V): V = v.tap(_ => + if (v0 != null) result = Some(v0) + else if (underlying.containsKey(k0)) result = Some(null.asInstanceOf[V]) + ) + underlying.compute(k, recompute) + result + } + + override def update(k: K, v: V): Unit = underlying.put(k, v) + + override def updateWith(key: K)(remappingFunction: Option[V] => Option[V]): Option[V] = { + def remap(k: K, v: V): V = + remappingFunction(Option(v)) match { + case Some(null) => throw PutNull + case Some(x) => x + case None => null.asInstanceOf[V] + } + try Option(underlying.compute(key, remap)) + catch { + case PutNull => update(key, null.asInstanceOf[V]); Some(null.asInstanceOf[V]) + } + } + + // support Some(null) if currently bound to null + override def remove(k: K): Option[V] = { + var result: Option[V] = None + def recompute(k0: K, v0: V): V = { + if (v0 != null) result = Some(v0) + else if (underlying.containsKey(k0)) result = Some(null.asInstanceOf[V]) + null.asInstanceOf[V] + } + underlying.compute(k, recompute) + result + } + + def iterator: Iterator[(K, V)] = new AbstractIterator[(K, V)] { + val ui = underlying.entrySet.iterator + def hasNext = ui.hasNext + def next() = { val e = ui.next(); (e.getKey, e.getValue) } + } + + override def foreachEntry[U](f: (K, V) => U): Unit = { + val i = underlying.entrySet().iterator() + while (i.hasNext) { + val entry = i.next() + f(entry.getKey, entry.getValue) + } + } + + override def clear() = underlying.clear() + + } + + /** Wraps a Java map as a Scala one. If the map is to support concurrent access, + * use [[JConcurrentMapWrapper]] instead. If the wrapped map is synchronized + * (e.g. from `java.util.Collections.synchronizedMap`), it is your responsibility + * to wrap all non-atomic operations with `underlying.synchronized`. + * This includes `get`, as `java.util.Map`'s API does not allow for an + * atomic `get` when `null` values may be present. + */ + @SerialVersionUID(3L) + class JMapWrapper[K, V](val underlying : ju.Map[K, V]) + extends AbstractJMapWrapper[K, V] with Serializable { + + override def isEmpty: Boolean = underlying.isEmpty + override def knownSize: Int = if (underlying.isEmpty) 0 else super.knownSize + override def empty: JMapWrapper[K, V] = new JMapWrapper(new ju.HashMap[K, V]) + } + + @SerialVersionUID(3L) + class ConcurrentMapWrapper[K, V](underlying: concurrent.Map[K, V]) extends MutableMapWrapper[K, V](underlying) with juc.ConcurrentMap[K, V] { + + def underlyingConcurrentMap: concurrent.Map[K, V] = underlying + + override def putIfAbsent(k: K, v: V) = underlying.putIfAbsent(k, v).getOrElse(null.asInstanceOf[V]) + + override def remove(k: AnyRef, v: AnyRef) = + try underlying.remove(k.asInstanceOf[K], v.asInstanceOf[V]) + catch { case ex: ClassCastException => false } + + override def replace(k: K, v: V): V = underlying.replace(k, v).getOrElse(null.asInstanceOf[V]) + + override def replace(k: K, oldval: V, newval: V) = underlying.replace(k, oldval, newval) + } + + /** Wraps a concurrent Java map as a Scala one. Single-element concurrent + * access is supported; multi-element operations such as maps and filters + * are not guaranteed to be atomic. + */ + @SerialVersionUID(3L) + class JConcurrentMapWrapper[K, V](val underlying: juc.ConcurrentMap[K, V]) + extends AbstractJMapWrapper[K, V] + with concurrent.Map[K, V] { + + override def get(k: K) = Option(underlying get k) + + override def getOrElseUpdate(key: K, op: => V): V = + underlying.computeIfAbsent(key, _ => op) match { + case null => super/*[concurrent.Map]*/.getOrElseUpdate(key, op) + case v => v + } + + override def isEmpty: Boolean = underlying.isEmpty + override def knownSize: Int = if (underlying.isEmpty) 0 else super.knownSize + override def empty: JConcurrentMapWrapper[K, V] = new JConcurrentMapWrapper(new juc.ConcurrentHashMap[K, V]) + + def putIfAbsent(k: K, v: V): Option[V] = Option(underlying.putIfAbsent(k, v)) + + def remove(k: K, v: V): Boolean = underlying.remove(k, v) + + def replace(k: K, v: V): Option[V] = Option(underlying.replace(k, v)) + + def replace(k: K, oldvalue: V, newvalue: V): Boolean = underlying.replace(k, oldvalue, newvalue) + + override def lastOption: Option[(K, V)] = + underlying match { + case nav: NavigableMap[K @unchecked, V @unchecked] => Option(nav.lastEntry).map(e => (e.getKey, e.getValue)) + case _ if isEmpty => None + case _ => Try(last).toOption + } + + override def updateWith(key: K)(remappingFunction: Option[V] => Option[V]): Option[V] = { + def remap(k: K, v: V): V = + remappingFunction(Option(v)) match { + case Some(null) => throw PutNull // see scala/scala#10129 + case Some(x) => x + case None => null.asInstanceOf[V] + } + try Option(underlying.compute(key, remap)) + catch { + case PutNull => super/*[concurrent.Map]*/.updateWith(key)(remappingFunction) + } + } + } + + @SerialVersionUID(3L) + class DictionaryWrapper[K, V](val underlying: mutable.Map[K, V]) extends ju.Dictionary[K, V] with Serializable { + def size: Int = underlying.size + def isEmpty: Boolean = underlying.isEmpty + def keys: ju.Enumeration[K] = underlying.keysIterator.asJavaEnumeration + def elements: ju.Enumeration[V] = underlying.valuesIterator.asJavaEnumeration + def get(key: AnyRef) = try { + underlying get key.asInstanceOf[K] match { + case None => null.asInstanceOf[V] + case Some(v) => v + } + } catch { + case ex: ClassCastException => null.asInstanceOf[V] + } + def put(key: K, value: V): V = underlying.put(key, value) match { + case Some(v) => v + case None => null.asInstanceOf[V] + } + override def remove(key: AnyRef) = try { + underlying remove key.asInstanceOf[K] match { + case None => null.asInstanceOf[V] + case Some(v) => v + } + } catch { + case ex: ClassCastException => null.asInstanceOf[V] + } + + override def equals(other: Any): Boolean = other match { + case that: DictionaryWrapper[_, _] => this.underlying == that.underlying + case _ => false + } + + override def hashCode: Int = underlying.hashCode() + } + + @SerialVersionUID(3L) + class JDictionaryWrapper[K, V](val underlying: ju.Dictionary[K, V]) extends mutable.AbstractMap[K, V] with Serializable { + override def size: Int = underlying.size + override def isEmpty: Boolean = underlying.isEmpty + override def knownSize: Int = if (underlying.isEmpty) 0 else super.knownSize + + def get(k: K) = Option(underlying get k) + + def addOne(kv: (K, V)): this.type = { underlying.put(kv._1, kv._2); this } + def subtractOne(key: K): this.type = { underlying remove key; this } + + override def put(k: K, v: V): Option[V] = Option(underlying.put(k, v)) + + override def update(k: K, v: V): Unit = { underlying.put(k, v) } + + override def remove(k: K): Option[V] = Option(underlying remove k) + def iterator = underlying.keys.asScala map (k => (k, underlying get k)) + + override def clear() = iterator.foreach(entry => underlying.remove(entry._1)) + + override def mapFactory: mutable.HashMap.type = mutable.HashMap + } + + @SerialVersionUID(3L) + class JPropertiesWrapper(underlying: ju.Properties) + extends mutable.AbstractMap[String, String] + with mutable.MapOps[String, String, mutable.Map, mutable.Map[String, String]] + with StrictOptimizedMapOps[String, String, mutable.Map, mutable.Map[String, String]] + with StrictOptimizedIterableOps[(String, String), mutable.Iterable, mutable.Map[String, String]] + with Serializable { + + override def size = underlying.size + override def isEmpty: Boolean = underlying.isEmpty + override def knownSize: Int = size + def get(k: String) = { + val v = underlying get k + if (v != null) Some(v.asInstanceOf[String]) else None + } + + def addOne(kv: (String, String)): this.type = { underlying.put(kv._1, kv._2); this } + def subtractOne(key: String): this.type = { underlying remove key; this } + + override def put(k: String, v: String): Option[String] = { + val r = underlying.put(k, v) + if (r != null) Some(r.asInstanceOf[String]) else None + } + + override def update(k: String, v: String): Unit = { underlying.put(k, v) } + + override def remove(k: String): Option[String] = { + val r = underlying remove k + if (r != null) Some(r.asInstanceOf[String]) else None + } + + def iterator: Iterator[(String, String)] = new AbstractIterator[(String, String)] { + val ui = underlying.entrySet.iterator + def hasNext = ui.hasNext + def next() = { + val e = ui.next() + (e.getKey.asInstanceOf[String], e.getValue.asInstanceOf[String]) + } + } + + override def clear() = underlying.clear() + + override def empty: JPropertiesWrapper = new JPropertiesWrapper(new ju.Properties) + + def getProperty(key: String) = underlying.getProperty(key) + + def getProperty(key: String, defaultValue: String) = + underlying.getProperty(key, defaultValue) + + def setProperty(key: String, value: String) = + underlying.setProperty(key, value) + + override def mapFactory: mutable.HashMap.type = mutable.HashMap + } + + /** Thrown when certain Map operations attempt to put a null value. */ + private val PutNull = new ControlThrowable {} +} diff --git a/library/src/scala/collection/convert/StreamExtensions.scala b/library/src/scala/collection/convert/StreamExtensions.scala new file mode 100644 index 000000000000..4457989ead40 --- /dev/null +++ b/library/src/scala/collection/convert/StreamExtensions.scala @@ -0,0 +1,481 @@ +/* + * 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.collection.convert + +import scala.language.`2.13` +import java.util.Spliterator +import java.util.stream._ +import java.{lang => jl} + +import scala.annotation.implicitNotFound +import scala.collection.Stepper.EfficientSplit +import scala.collection._ +import scala.collection.convert.StreamExtensions.{AccumulatorFactoryInfo, StreamShape, StreamUnboxer} +import scala.jdk.CollectionConverters._ +import scala.jdk._ + +/** Defines extension methods to create Java Streams for Scala collections, available through + * [[scala.jdk.javaapi.StreamConverters]]. + */ +trait StreamExtensions { + // collections + + implicit class IterableHasSeqStream[A](cc: IterableOnce[A]) { + /** Create a sequential [[java.util.stream.Stream Java Stream]] for this collection. If the + * collection contains primitive values, a corresponding specialized Stream is returned (e.g., + * [[java.util.stream.IntStream `IntStream`]]). + */ + def asJavaSeqStream[S <: BaseStream[_, _], St <: Stepper[_]](implicit s: StreamShape[A, S, St], st: StepperShape[A, St]): S = + s.fromStepper(cc.stepper, par = false) + } + + // Not `CC[X] <: IterableOnce[X]`, but `C` with an extra constraint, to support non-parametric classes like IntAccumulator + implicit class IterableNonGenericHasParStream[A, C <: IterableOnce[_]](c: C)(implicit ev: C <:< IterableOnce[A]) { + private type IterableOnceWithEfficientStepper = IterableOnce[A] { + def stepper[S <: Stepper[_]](implicit shape : StepperShape[A, S]) : S with EfficientSplit + } + + /** Create a parallel [[java.util.stream.Stream Java Stream]] for this collection. If the + * collection contains primitive values, a corresponding specialized Stream is returned (e.g., + * [[java.util.stream.IntStream `IntStream`]]). + */ + def asJavaParStream[S <: BaseStream[_, _], St <: Stepper[_]](implicit + s: StreamShape[A, S, St], + st: StepperShape[A, St], + @implicitNotFound("`parStream` can only be called on collections where `stepper` returns a `Stepper with EfficientSplit`") + isEfficient: C <:< IterableOnceWithEfficientStepper): S = + s.fromStepper(ev(c).stepper, par = true) + } + + // maps + + implicit class MapHasSeqKeyValueStream[K, V, CC[X, Y] <: collection.MapOps[X, Y, collection.Map, _]](cc: CC[K, V]) { + /** Create a sequential [[java.util.stream.Stream Java Stream]] for the keys of this map. If + * the keys are primitive values, a corresponding specialized Stream is returned (e.g., + * [[java.util.stream.IntStream `IntStream`]]). + */ + def asJavaSeqKeyStream[S <: BaseStream[_, _], St <: Stepper[_]](implicit s: StreamShape[K, S, St], st: StepperShape[K, St]): S = + s.fromStepper(cc.keyStepper, par = false) + + /** Create a sequential [[java.util.stream.Stream Java Stream]] for the values of this map. If + * the values are primitives, a corresponding specialized Stream is returned (e.g., + * [[java.util.stream.IntStream `IntStream`]]). + */ + def asJavaSeqValueStream[S <: BaseStream[_, _], St <: Stepper[_]](implicit s: StreamShape[V, S, St], st: StepperShape[V, St]): S = + s.fromStepper(cc.valueStepper, par = false) + + // The asJavaSeqStream extension method for IterableOnce doesn't apply because its `CC` takes a single type parameter, whereas the one here takes two + /** Create a sequential [[java.util.stream.Stream Java Stream]] for the `(key, value)` pairs of + * this map. + */ + def asJavaSeqStream[S <: BaseStream[_, _], St <: Stepper[_]](implicit s: StreamShape[(K, V), S, St], st: StepperShape[(K, V), St]): S = + s.fromStepper(cc.stepper, par = false) + } + + + implicit class MapHasParKeyValueStream[K, V, CC[X, Y] <: collection.MapOps[X, Y, collection.Map, _]](cc: CC[K, V]) { + private type MapOpsWithEfficientKeyStepper = collection.MapOps[K, V, collection.Map, _] { def keyStepper[S <: Stepper[_]](implicit shape : StepperShape[K, S]) : S with EfficientSplit } + private type MapOpsWithEfficientValueStepper = collection.MapOps[K, V, collection.Map, _] { def valueStepper[S <: Stepper[_]](implicit shape : StepperShape[V, S]) : S with EfficientSplit } + private type MapOpsWithEfficientStepper = collection.MapOps[K, V, collection.Map, _] { def stepper[S <: Stepper[_]](implicit shape : StepperShape[(K, V), S]) : S with EfficientSplit } + + /** Create a parallel [[java.util.stream.Stream Java Stream]] for the keys of this map. If + * the keys are primitive values, a corresponding specialized Stream is returned (e.g., + * [[java.util.stream.IntStream `IntStream`]]). + */ + def asJavaParKeyStream[S <: BaseStream[_, _], St <: Stepper[_]](implicit + s: StreamShape[K, S, St], + st: StepperShape[K, St], + @implicitNotFound("parKeyStream can only be called on maps where `keyStepper` returns a `Stepper with EfficientSplit`") + isEfficient: CC[K, V] <:< MapOpsWithEfficientKeyStepper): S = + s.fromStepper(cc.keyStepper, par = true) + + /** Create a parallel [[java.util.stream.Stream Java Stream]] for the values of this map. If + * the values are primitives, a corresponding specialized Stream is returned (e.g., + * [[java.util.stream.IntStream `IntStream`]]). + */ + def asJavaParValueStream[S <: BaseStream[_, _], St <: Stepper[_]](implicit + s: StreamShape[V, S, St], + st: StepperShape[V, St], + @implicitNotFound("parValueStream can only be called on maps where `valueStepper` returns a `Stepper with EfficientSplit`") + isEfficient: CC[K, V] <:< MapOpsWithEfficientValueStepper): S = + s.fromStepper(cc.valueStepper, par = true) + + // The asJavaParStream extension method for IterableOnce doesn't apply because its `CC` takes a single type parameter, whereas the one here takes two + /** Create a parallel [[java.util.stream.Stream Java Stream]] for the `(key, value)` pairs of + * this map. + */ + def asJavaParStream[S <: BaseStream[_, _], St <: Stepper[_]](implicit + s: StreamShape[(K, V), S, St], + st: StepperShape[(K, V), St], + @implicitNotFound("parStream can only be called on maps where `stepper` returns a `Stepper with EfficientSplit`") + isEfficient: CC[K, V] <:< MapOpsWithEfficientStepper): S = + s.fromStepper(cc.stepper, par = true) + } + + // steppers + + implicit class StepperHasSeqStream[A](stepper: Stepper[A]) { + /** Create a sequential [[java.util.stream.Stream Java Stream]] for this stepper. If the + * stepper yields primitive values, a corresponding specialized Stream is returned (e.g., + * [[java.util.stream.IntStream `IntStream`]]). + */ + def asJavaSeqStream[S <: BaseStream[_, _], St <: Stepper[_]](implicit s: StreamShape[A, S, St], st: StepperShape[A, St]): S = { + val sStepper = stepper match { + case as: AnyStepper[A] => st.seqUnbox(as) + case _ => stepper.asInstanceOf[St] + } + s.fromStepper(sStepper, par = false) + } + } + + implicit class StepperHasParStream[A](stepper: Stepper[A] with EfficientSplit) { + /** Create a parallel [[java.util.stream.Stream Java Stream]] for this stepper. If the + * stepper yields primitive values, a corresponding specialized Stream is returned (e.g., + * [[java.util.stream.IntStream `IntStream`]]). + */ + def asJavaParStream[S <: BaseStream[_, _], St <: Stepper[_]](implicit s: StreamShape[A, S, St], st: StepperShape[A, St]): S = { + val sStepper = stepper match { + case as: AnyStepper[A] with EfficientSplit => st.parUnbox(as) + case _ => stepper.asInstanceOf[St] + } + s.fromStepper(sStepper, par = true) + } + } + + // arrays + // uses the JDK array spliterators (`DoubleArraySpliterator`). users can also call + // `array.stepper.seqStream`, which then uses the Scala steppers (`DoubleArrayStepper`). the + // steppers are also available on byte/short/char/float arrays (`WidenedByteArrayStepper`), + // JDK spliterators only for double/int/long/reference. + + implicit class DoubleArrayHasSeqParStream(a: Array[Double]) { + /** Create a sequential [[java.util.stream.DoubleStream Java DoubleStream]] for this array. */ + def asJavaSeqStream: DoubleStream = java.util.Arrays.stream(a) + /** Create a parallel [[java.util.stream.DoubleStream Java DoubleStream]] for this array. */ + def asJavaParStream: DoubleStream = asJavaSeqStream.parallel + } + + implicit class IntArrayHasSeqParStream(a: Array[Int]) { + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for this array. */ + def asJavaSeqStream: IntStream = java.util.Arrays.stream(a) + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for this array. */ + def asJavaParStream: IntStream = asJavaSeqStream.parallel + } + + implicit class LongArrayHasSeqParStream(a: Array[Long]) { + /** Create a sequential [[java.util.stream.LongStream Java LongStream]] for this array. */ + def asJavaSeqStream: LongStream = java.util.Arrays.stream(a) + /** Create a parallel [[java.util.stream.LongStream Java LongStream]] for this array. */ + def asJavaParStream: LongStream = asJavaSeqStream.parallel + } + + implicit class AnyArrayHasSeqParStream[A <: AnyRef](a: Array[A]) { + /** Create a sequential [[java.util.stream.Stream Java Stream]] for this array. */ + def asJavaSeqStream: Stream[A] = java.util.Arrays.stream(a) + /** Create a parallel [[java.util.stream.Stream Java Stream]] for this array. */ + def asJavaParStream: Stream[A] = asJavaSeqStream.parallel + } + + implicit class ByteArrayHasSeqParStream(a: Array[Byte]) { + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for this array. */ + def asJavaSeqStream: IntStream = a.stepper.asJavaSeqStream + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for this array. */ + def asJavaParStream: IntStream = a.stepper.asJavaParStream + } + + implicit class ShortArrayHasSeqParStream(a: Array[Short]) { + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for this array. */ + def asJavaSeqStream: IntStream = a.stepper.asJavaSeqStream + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for this array. */ + def asJavaParStream: IntStream = a.stepper.asJavaParStream + } + + implicit class CharArrayHasSeqParStream(a: Array[Char]) { + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for this array. */ + def asJavaSeqStream: IntStream = a.stepper.asJavaSeqStream + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for this array. */ + def asJavaParStream: IntStream = a.stepper.asJavaParStream + } + + implicit class FloatArrayHasSeqParStream(a: Array[Float]) { + /** Create a sequential [[java.util.stream.DoubleStream Java DoubleStream]] for this array. */ + def asJavaSeqStream: DoubleStream = a.stepper.asJavaSeqStream + /** Create a parallel [[java.util.stream.DoubleStream Java DoubleStream]] for this array. */ + def asJavaParStream: DoubleStream = a.stepper.asJavaParStream + } + + + + // strings + + implicit class StringHasSeqParStream(s: String) { + /** + * A sequential stream on the characters of a string, same as [[asJavaSeqCharStream]]. See also + * [[asJavaSeqCodePointStream]]. + */ + def asJavaSeqStream: IntStream = StreamSupport.intStream(s.stepper.spliterator, /* par = */ false) + /** + * A parallel stream on the characters of a string, same as [[asJavaParCharStream]]. See also + * [[asJavaParCodePointStream]]. + */ + def asJavaParStream: IntStream = StreamSupport.intStream(s.stepper.spliterator, /* par = */ true) + + /** A sequential stream on the characters of a string. See also [[asJavaSeqCodePointStream]]. */ + def asJavaSeqCharStream: IntStream = StreamSupport.intStream(s.charStepper.spliterator, /* par = */ false) + /** A parallel stream on the characters of a string. See also [[asJavaParCodePointStream]]. */ + def asJavaParCharStream: IntStream = StreamSupport.intStream(s.charStepper.spliterator, /* par = */ true) + + /** A sequential stream on the code points of a string. See also [[asJavaSeqCharStream]]. */ + def asJavaSeqCodePointStream: IntStream = StreamSupport.intStream(s.codePointStepper.spliterator, /* par = */ false) + /** A parallel stream on the code points of a string. See also [[asJavaParCharStream]]. */ + def asJavaParCodePointStream: IntStream = StreamSupport.intStream(s.codePointStepper.spliterator, /* par = */ true) + } + + // toScala for streams + + implicit class StreamHasToScala[A](stream: Stream[A]) { + /** + * Copy the elements of this stream into a Scala collection. + * + * Converting a parallel streams to an [[scala.jdk.Accumulator]] using `stream.toScala(Accumulator)` + * builds the result in parallel. + * + * A `toScala(Accumulator)` call automatically converts streams of boxed integers, longs or + * doubles are converted to the primitive accumulators ([[scala.jdk.IntAccumulator]], etc.). + * + * When converting a parallel stream to a different Scala collection, the stream is first + * converted into an [[scala.jdk.Accumulator]], which supports parallel building. The accumulator is + * then converted to the target collection. Note that the stream is processed eagerly while + * building the accumulator, even if the target collection is lazy. + * + * Sequential streams are directly converted to the target collection. If the target collection + * is lazy, the conversion is lazy as well. + */ + def toScala[C1](factory: collection.Factory[A, C1])(implicit info: AccumulatorFactoryInfo[A, C1]): C1 = { + + def anyAcc = stream.collect(AnyAccumulator.supplier[A], AnyAccumulator.adder[A], AnyAccumulator.merger[A]) + if (info.companion == AnyAccumulator) anyAcc.asInstanceOf[C1] + else if (info.companion == IntAccumulator) stream.asInstanceOf[Stream[Int]].collect(IntAccumulator.supplier, IntAccumulator.boxedAdder, IntAccumulator.merger).asInstanceOf[C1] + else if (info.companion == LongAccumulator) stream.asInstanceOf[Stream[Long]].collect(LongAccumulator.supplier, LongAccumulator.boxedAdder, LongAccumulator.merger).asInstanceOf[C1] + else if (info.companion == DoubleAccumulator) stream.asInstanceOf[Stream[Double]].collect(DoubleAccumulator.supplier, DoubleAccumulator.boxedAdder, DoubleAccumulator.merger).asInstanceOf[C1] + else if (stream.isParallel) anyAcc.to(factory) + else factory.fromSpecific(stream.iterator.asScala) + } + + /** Convert a generic Java Stream wrapping a primitive type to a corresponding primitive + * Stream. + */ + def asJavaPrimitiveStream[S](implicit unboxer: StreamUnboxer[A, S]): S = unboxer(stream) + } + + implicit class IntStreamHasToScala(stream: IntStream) { + /** + * Copy the elements of this stream into a Scala collection. + * + * Converting a parallel streams to an [[scala.jdk.Accumulator]] using `stream.toScala(Accumulator)` + * builds the result in parallel. + * + * A `toScala(Accumulator)` call automatically converts the `IntStream` to a primitive + * [[scala.jdk.IntAccumulator]]. + * + * When converting a parallel stream to a different Scala collection, the stream is first + * converted into an [[scala.jdk.Accumulator]], which supports parallel building. The accumulator is + * then converted to the target collection. Note that the stream is processed eagerly while + * building the accumulator, even if the target collection is lazy. + * + * Sequential streams are directly converted to the target collection. If the target collection + * is lazy, the conversion is lazy as well. + */ + def toScala[C1](factory: collection.Factory[Int, C1])(implicit info: AccumulatorFactoryInfo[Int, C1]): C1 = { + def intAcc = stream.collect(IntAccumulator.supplier, IntAccumulator.adder, IntAccumulator.merger) + if (info.companion == AnyAccumulator) stream.collect(AnyAccumulator.supplier[Int], AnyAccumulator.unboxedIntAdder, AnyAccumulator.merger[Int]).asInstanceOf[C1] + else if (info.companion == IntAccumulator) intAcc.asInstanceOf[C1] + else if (stream.isParallel) intAcc.to(factory) + else factory.fromSpecific(stream.iterator.asInstanceOf[java.util.Iterator[Int]].asScala) + } + } + + implicit class LongStreamHasToScala(stream: LongStream) { + /** + * Copy the elements of this stream into a Scala collection. + * + * Converting a parallel streams to an [[scala.jdk.Accumulator]] using `stream.toScala(Accumulator)` + * builds the result in parallel. + * + * A `toScala(Accumulator)` call automatically converts the `LongStream` to a primitive + * [[scala.jdk.LongAccumulator]]. + * + * When converting a parallel stream to a different Scala collection, the stream is first + * converted into an [[scala.jdk.Accumulator]], which supports parallel building. The accumulator is + * then converted to the target collection. Note that the stream is processed eagerly while + * building the accumulator, even if the target collection is lazy. + * + * Sequential streams are directly converted to the target collection. If the target collection + * is lazy, the conversion is lazy as well. + */ + def toScala[C1](factory: collection.Factory[Long, C1])(implicit info: AccumulatorFactoryInfo[Long, C1]): C1 = { + def longAcc = stream.collect(LongAccumulator.supplier, LongAccumulator.adder, LongAccumulator.merger) + if (info.companion == AnyAccumulator) stream.collect(AnyAccumulator.supplier[Long], AnyAccumulator.unboxedLongAdder, AnyAccumulator.merger[Long]).asInstanceOf[C1] + else if (info.companion == LongAccumulator) longAcc.asInstanceOf[C1] + else if (stream.isParallel) longAcc.to(factory) + else factory.fromSpecific(stream.iterator.asInstanceOf[java.util.Iterator[Long]].asScala) + } + } + + implicit class DoubleStreamHasToScala(stream: DoubleStream) { + /** + * Copy the elements of this stream into a Scala collection. + * + * Converting a parallel streams to an [[scala.jdk.Accumulator]] using `stream.toScala(Accumulator)` + * builds the result in parallel. + * + * A `toScala(Accumulator)` call automatically converts the `DoubleStream` to a primitive + * [[scala.jdk.DoubleAccumulator]]. + * + * When converting a parallel stream to a different Scala collection, the stream is first + * converted into an [[scala.jdk.Accumulator]], which supports parallel building. The accumulator is + * then converted to the target collection. Note that the stream is processed eagerly while + * building the accumulator, even if the target collection is lazy. + * + * Sequential streams are directly converted to the target collection. If the target collection + * is lazy, the conversion is lazy as well. + */ + def toScala[C1](factory: collection.Factory[Double, C1])(implicit info: AccumulatorFactoryInfo[Double, C1]): C1 = { + def doubleAcc = stream.collect(DoubleAccumulator.supplier, DoubleAccumulator.adder, DoubleAccumulator.merger) + if (info.companion == AnyAccumulator) stream.collect(AnyAccumulator.supplier[Double], AnyAccumulator.unboxedDoubleAdder, AnyAccumulator.merger[Double]).asInstanceOf[C1] + else if (info.companion == DoubleAccumulator) doubleAcc.asInstanceOf[C1] + else if (stream.isParallel) doubleAcc.to(factory) + else factory.fromSpecific(stream.iterator.asInstanceOf[java.util.Iterator[Double]].asScala) + } + } +} + +object StreamExtensions { + /** An implicit StreamShape instance connects element types with the corresponding specialized + * Stream and Stepper types. This is used in `asJavaStream` extension methods to create + * generic or primitive streams according to the element type. + */ + sealed trait StreamShape[T, S <: BaseStream[_, _], St <: Stepper[_]] { + final def fromStepper(st: St, par: Boolean): S = mkStream(st, par) + protected def mkStream(st: St, par: Boolean): S + } + + object StreamShape extends StreamShapeLowPriority1 { + // primitive + implicit val intStreamShape : StreamShape[Int , IntStream , IntStepper] = mkIntStreamShape[Int] + implicit val longStreamShape : StreamShape[Long , LongStream , LongStepper] = mkLongStreamShape[Long] + implicit val doubleStreamShape: StreamShape[Double, DoubleStream, DoubleStepper] = mkDoubleStreamShape[Double] + + // widening + implicit val byteStreamShape : StreamShape[Byte , IntStream , IntStepper] = mkIntStreamShape[Byte] + implicit val shortStreamShape: StreamShape[Short, IntStream , IntStepper] = mkIntStreamShape[Short] + implicit val charStreamShape : StreamShape[Char , IntStream , IntStepper] = mkIntStreamShape[Char] + implicit val floatStreamShape: StreamShape[Float, DoubleStream, DoubleStepper] = mkDoubleStreamShape[Float] + + // boxed java primitives + + implicit val jIntegerStreamShape : StreamShape[jl.Integer , IntStream , IntStepper ] = mkIntStreamShape[jl.Integer] + implicit val jLongStreamShape : StreamShape[jl.Long , LongStream , LongStepper ] = mkLongStreamShape[jl.Long] + implicit val jDoubleStreamShape : StreamShape[jl.Double , DoubleStream, DoubleStepper] = mkDoubleStreamShape[jl.Double] + implicit val jByteStreamShape : StreamShape[jl.Byte , IntStream , IntStepper ] = mkIntStreamShape[jl.Byte] + implicit val jShortStreamShape : StreamShape[jl.Short , IntStream , IntStepper ] = mkIntStreamShape[jl.Short] + implicit val jCharacterStreamShape : StreamShape[jl.Character, IntStream , IntStepper ] = mkIntStreamShape[jl.Character] + implicit val jFloatStreamShape : StreamShape[jl.Float , DoubleStream, DoubleStepper] = mkDoubleStreamShape[jl.Float] + + private def mkIntStreamShape[T]: StreamShape[T, IntStream, IntStepper] = new StreamShape[T, IntStream, IntStepper] { + protected def mkStream(st: IntStepper, par: Boolean): IntStream = StreamSupport.intStream(st.spliterator, par) + } + + private def mkLongStreamShape[T]: StreamShape[T, LongStream, LongStepper] = new StreamShape[T, LongStream, LongStepper] { + protected def mkStream(st: LongStepper, par: Boolean): LongStream = StreamSupport.longStream(st.spliterator, par) + } + + private def mkDoubleStreamShape[T]: StreamShape[T, DoubleStream, DoubleStepper] = new StreamShape[T, DoubleStream, DoubleStepper] { + protected def mkStream(st: DoubleStepper, par: Boolean): DoubleStream = StreamSupport.doubleStream(st.spliterator, par) + } + } + + trait StreamShapeLowPriority1 { + // reference + implicit def anyStreamShape[T]: StreamShape[T, Stream[T], Stepper[T]] = anyStreamShapePrototype.asInstanceOf[StreamShape[T, Stream[T], Stepper[T]]] + + private[this] val anyStreamShapePrototype: StreamShape[AnyRef, Stream[AnyRef], Stepper[AnyRef]] = new StreamShape[AnyRef, Stream[AnyRef], Stepper[AnyRef]] { + def mkStream(s: Stepper[AnyRef], par: Boolean): Stream[AnyRef] = StreamSupport.stream(s.spliterator.asInstanceOf[Spliterator[AnyRef]], par) + } + } + + /** Connects a stream element type `A` to the corresponding, potentially specialized, Stream type. + * Used in the `stream.asJavaPrimitiveStream` extension method. + */ + sealed trait StreamUnboxer[A, S] { + def apply(s: Stream[A]): S + } + object StreamUnboxer { + implicit val intStreamUnboxer: StreamUnboxer[Int, IntStream] = new StreamUnboxer[Int, IntStream] { + def apply(s: Stream[Int]): IntStream = s.mapToInt(x => x) + } + implicit val javaIntegerStreamUnboxer: StreamUnboxer[jl.Integer, IntStream] = intStreamUnboxer.asInstanceOf[StreamUnboxer[jl.Integer, IntStream]] + + implicit val longStreamUnboxer: StreamUnboxer[Long, LongStream] = new StreamUnboxer[Long, LongStream] { + def apply(s: Stream[Long]): LongStream = s.mapToLong(x => x) + } + implicit val javaLongStreamUnboxer: StreamUnboxer[jl.Long, LongStream] = longStreamUnboxer.asInstanceOf[StreamUnboxer[jl.Long, LongStream]] + + implicit val doubleStreamUnboxer: StreamUnboxer[Double, DoubleStream] = new StreamUnboxer[Double, DoubleStream] { + def apply(s: Stream[Double]): DoubleStream = s.mapToDouble(x => x) + } + implicit val javaDoubleStreamUnboxer: StreamUnboxer[jl.Double, DoubleStream] = doubleStreamUnboxer.asInstanceOf[StreamUnboxer[jl.Double, DoubleStream]] + } + + + + /** An implicit `AccumulatorFactoryInfo` connects primitive element types to the corresponding + * specialized [[scala.jdk.Accumulator]] factory. This is used in the `stream.toScala` extension methods + * to ensure collecting a primitive stream into a primitive accumulator does not box. + * + * When converting to a collection other than `Accumulator`, the generic + * `noAccumulatorFactoryInfo` is passed. + */ + trait AccumulatorFactoryInfo[A, C] { + val companion: AnyRef + } + trait LowPriorityAccumulatorFactoryInfo { + implicit def noAccumulatorFactoryInfo[A, C]: AccumulatorFactoryInfo[A, C] = noAccumulatorFactoryInfoPrototype.asInstanceOf[AccumulatorFactoryInfo[A, C]] + private val noAccumulatorFactoryInfoPrototype: AccumulatorFactoryInfo[AnyRef, AnyRef] = new AccumulatorFactoryInfo[AnyRef, AnyRef] { + val companion: AnyRef = null + } + } + object AccumulatorFactoryInfo extends LowPriorityAccumulatorFactoryInfo { + implicit def anyAccumulatorFactoryInfo[A]: AccumulatorFactoryInfo[A, AnyAccumulator[A]] = anyAccumulatorFactoryInfoPrototype.asInstanceOf[AccumulatorFactoryInfo[A, AnyAccumulator[A]]] + + private object anyAccumulatorFactoryInfoPrototype extends AccumulatorFactoryInfo[AnyRef, AnyAccumulator[AnyRef]] { + val companion: AnyRef = AnyAccumulator + } + + implicit val intAccumulatorFactoryInfo: AccumulatorFactoryInfo[Int, IntAccumulator] = new AccumulatorFactoryInfo[Int, IntAccumulator] { + val companion: AnyRef = IntAccumulator + } + + implicit val longAccumulatorFactoryInfo: AccumulatorFactoryInfo[Long, LongAccumulator] = new AccumulatorFactoryInfo[Long, LongAccumulator] { + val companion: AnyRef = LongAccumulator + } + + implicit val doubleAccumulatorFactoryInfo: AccumulatorFactoryInfo[Double, DoubleAccumulator] = new AccumulatorFactoryInfo[Double, DoubleAccumulator] { + val companion: AnyRef = DoubleAccumulator + } + + implicit val jIntegerAccumulatorFactoryInfo: AccumulatorFactoryInfo[jl.Integer, IntAccumulator] = intAccumulatorFactoryInfo.asInstanceOf[AccumulatorFactoryInfo[jl.Integer, IntAccumulator]] + implicit val jLongAccumulatorFactoryInfo: AccumulatorFactoryInfo[jl.Long, IntAccumulator] = longAccumulatorFactoryInfo.asInstanceOf[AccumulatorFactoryInfo[jl.Long, IntAccumulator]] + implicit val jDoubleAccumulatorFactoryInfo: AccumulatorFactoryInfo[jl.Double, IntAccumulator] = doubleAccumulatorFactoryInfo.asInstanceOf[AccumulatorFactoryInfo[jl.Double, IntAccumulator]] + } +} diff --git a/library/src/scala/collection/convert/impl/ArrayStepper.scala b/library/src/scala/collection/convert/impl/ArrayStepper.scala new file mode 100644 index 000000000000..b53dab574909 --- /dev/null +++ b/library/src/scala/collection/convert/impl/ArrayStepper.scala @@ -0,0 +1,80 @@ +/* + * 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.collection.convert +package impl + +import scala.language.`2.13` +import scala.collection._ + +private[collection] class ObjectArrayStepper[A <: Object](underlying: Array[A], _i0: Int, _iN: Int) + extends IndexedStepperBase[AnyStepper[A], ObjectArrayStepper[A]](_i0, _iN) + with AnyStepper[A] { + def nextStep(): A = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + protected def semiclone(half: Int): ObjectArrayStepper[A] = new ObjectArrayStepper[A](underlying, i0, half) +} + +private[collection] class BoxedBooleanArrayStepper(underlying: Array[Boolean], _i0: Int, _iN: Int) + extends IndexedStepperBase[AnyStepper[Boolean], BoxedBooleanArrayStepper](_i0, _iN) + with AnyStepper[Boolean] { + def nextStep(): Boolean = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + protected def semiclone(half: Int): BoxedBooleanArrayStepper = new BoxedBooleanArrayStepper(underlying, i0, half) +} + +private[collection] class WidenedByteArrayStepper(underlying: Array[Byte], _i0: Int, _iN: Int) + extends IndexedStepperBase[IntStepper, WidenedByteArrayStepper](_i0, _iN) + with IntStepper { + def nextStep(): Int = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + protected def semiclone(half: Int): WidenedByteArrayStepper = new WidenedByteArrayStepper(underlying, i0, half) +} + +private[collection] class WidenedCharArrayStepper(underlying: Array[Char], _i0: Int, _iN: Int) + extends IndexedStepperBase[IntStepper, WidenedCharArrayStepper](_i0, _iN) + with IntStepper { + def nextStep(): Int = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + protected def semiclone(half: Int): WidenedCharArrayStepper = new WidenedCharArrayStepper(underlying, i0, half) +} + +private[collection] class WidenedShortArrayStepper(underlying: Array[Short], _i0: Int, _iN: Int) + extends IndexedStepperBase[IntStepper, WidenedShortArrayStepper](_i0, _iN) + with IntStepper { + def nextStep(): Int = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + protected def semiclone(half: Int): WidenedShortArrayStepper = new WidenedShortArrayStepper(underlying, i0, half) +} + +private[collection] class WidenedFloatArrayStepper(underlying: Array[Float], _i0: Int, _iN: Int) + extends IndexedStepperBase[DoubleStepper, WidenedFloatArrayStepper](_i0, _iN) + with DoubleStepper { + def nextStep(): Double = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + protected def semiclone(half: Int): WidenedFloatArrayStepper = new WidenedFloatArrayStepper(underlying, i0, half) +} + +private[collection] class DoubleArrayStepper(underlying: Array[Double], _i0: Int, _iN: Int) + extends IndexedStepperBase[DoubleStepper, DoubleArrayStepper](_i0, _iN) + with DoubleStepper { + def nextStep(): Double = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + protected def semiclone(half: Int): DoubleArrayStepper = new DoubleArrayStepper(underlying, i0, half) +} + +private[collection] class IntArrayStepper(underlying: Array[Int], _i0: Int, _iN: Int) + extends IndexedStepperBase[IntStepper, IntArrayStepper](_i0, _iN) + with IntStepper { + def nextStep(): Int = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + protected def semiclone(half: Int): IntArrayStepper = new IntArrayStepper(underlying, i0, half) +} + +private[collection] class LongArrayStepper(underlying: Array[Long], _i0: Int, _iN: Int) + extends IndexedStepperBase[LongStepper, LongArrayStepper](_i0, _iN) + with LongStepper { + def nextStep(): Long = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + protected def semiclone(half: Int): LongArrayStepper = new LongArrayStepper(underlying, i0, half) +} diff --git a/library/src/scala/collection/convert/impl/BinaryTreeStepper.scala b/library/src/scala/collection/convert/impl/BinaryTreeStepper.scala new file mode 100644 index 000000000000..87823ced9cee --- /dev/null +++ b/library/src/scala/collection/convert/impl/BinaryTreeStepper.scala @@ -0,0 +1,249 @@ +/* + * 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.collection.convert +package impl + +import scala.language.`2.13` +import java.util.Spliterator + +import annotation.tailrec +import scala.collection.Stepper.EfficientSplit +import scala.collection._ + + +private[collection] object BinaryTreeStepper { + val emptyStack = new Array[AnyRef](0) +} + + +/** A generic stepper that can traverse ordered binary trees. + * The tree is assumed to have all the stuff on the left first, then the root, then everything on the right. + * + * Splits occur at the root of whatever has not yet been traversed (the substepper steps up to but + * does not include the root). + * + * The stepper maintains an internal stack, not relying on the tree traversal to be reversible. Trees with + * nodes that maintain a parent pointer may be traversed slightly faster without a stack, but splitting is + * more awkward. + * + * Algorithmically, this class implements a simple state machine that unrolls the left-leaning links in + * a binary tree onto a stack. At all times, the machine should be in one of these states: + * 1. Empty: `myCurrent` is `null` and `index` is `-1`. `stack` should also be `Array.empty` then. + * 2. Ready: `myCurrent` is not `null` and contains the next `A` to be extracted + * 3. Pending: `myCurrent` is `null` and `stack(index)` contains the next node to visit + * + * Subclasses should allow this class to do all the work of maintaining state; `next` should simply + * reduce `maxLength` by one, and consume `myCurrent` and set it to `null` if `hasNext` is true. + */ +private[collection] abstract class BinaryTreeStepperBase[A, T >: Null <: AnyRef, Sub >: Null, Semi <: Sub with BinaryTreeStepperBase[A, T, _, _]]( + protected var maxLength: Int, protected var myCurrent: T, protected var stack: Array[AnyRef], protected var index: Int, + protected val left: T => T, protected val right: T => T +) +extends EfficientSplit { + /** Unrolls a subtree onto the stack starting from a particular node, returning + * the last node found. This final node is _not_ placed on the stack, and + * may have things to its right. + */ + @tailrec protected final def unroll(from: T): T = { + val l = left(from) + if (l eq null) from + else { + if (index+1 >= stack.length) stack = java.util.Arrays.copyOf(stack, 4 + stack.length*2) + index += 1 + stack(index) = from + unroll(l) + } + } + + /** Takes a subtree whose left side, if any, has already been visited, and unrolls + * the right side of the tree onto the stack, thereby detaching that node of + * the subtree from the stack entirely (so it is ready to use). It returns + * the node that is being detached. Note that the node must _not_ already be + * on the stack. + */ + protected final def detach(node: T): node.type = { + val r = right(node) + if (r ne null) { + val last = unroll(r) + if (index+1 >= stack.length) stack = java.util.Arrays.copyOf(stack, 4 + stack.length*2) + index += 1 + stack(index) = last + } + node + } + + /** Given an empty state and the root of a new tree, initialize the tree properly + * to be in an (appropriate) ready state. Will do all sorts of wrong stuff if the + * tree is not already empty. + * + * Right now overwrites everything so could allow reuse, but isn't used for it. + */ + private[impl] final def initialize(root: T, size: Int): Unit = + if (root eq null) { + maxLength = 0 + myCurrent = null + stack = BinaryTreeStepper.emptyStack + index = -1 + } + else { + maxLength = size + index = -1 + myCurrent = detach(unroll(root)) + } + + protected def semiclone(maxL: Int, myC: T, stk: Array[AnyRef], ix: Int): Semi + + def characteristics: Int = Spliterator.ORDERED + + def estimateSize: Long = if (hasStep) maxLength else 0 + + def hasStep: Boolean = (myCurrent ne null) || (maxLength > 0 && { + if (index < 0) { maxLength = 0; stack = BinaryTreeStepper.emptyStack; false } + else { + val ans = stack(index).asInstanceOf[T] + index -= 1 + myCurrent = detach(ans) + true + } + }) + + /** Splits the tree at the root by giving everything unrolled on the stack to a new stepper, + * detaching the root, and leaving the right-hand side of the root unrolled. + * + * If the tree is empty or only has one element left, it returns `null` instead of splitting. + */ + def trySplit(): Sub = + if (!hasStep || index < 0) null + else { + val root = stack(0).asInstanceOf[T] + val leftStack = + if (index > 0) java.util.Arrays.copyOfRange(stack, 1, index+1) + else BinaryTreeStepper.emptyStack + val leftIndex = index - 1 + val leftCurrent = myCurrent + var leftMax = maxLength + index = -1 + detach(root) + myCurrent = root + leftMax -= 2+index + maxLength -= 2+leftIndex + semiclone(leftMax, leftCurrent, leftStack, leftIndex) + } +} + + +private[collection] final class AnyBinaryTreeStepper[A, T >: Null <: AnyRef]( + _maxLength: Int, _myCurrent: T, _stack: Array[AnyRef], _index: Int, _left: T => T, _right: T => T, protected val extract: T => A +) +extends BinaryTreeStepperBase[A, T, AnyStepper[A], AnyBinaryTreeStepper[A, T]](_maxLength, _myCurrent, _stack, _index, _left, _right) +with AnyStepper[A] { + def nextStep(): A = + if (hasStep) { + val ans = extract(myCurrent) + myCurrent = null + maxLength -= 1 + ans + } + else Stepper.throwNSEE() + + def semiclone(maxL: Int, myC: T, stk: Array[AnyRef], ix: Int): AnyBinaryTreeStepper[A, T] = + new AnyBinaryTreeStepper[A, T](maxL, myC, stk, ix, left, right, extract) +} +private[collection] object AnyBinaryTreeStepper { + def from[A, T >: Null <: AnyRef](maxLength: Int, root: T, left: T => T, right: T => T, extract: T => A): AnyBinaryTreeStepper[A, T] = { + val ans = new AnyBinaryTreeStepper(0, null, BinaryTreeStepper.emptyStack, -1, left, right, extract) + ans.initialize(root, maxLength) + ans + } +} + + +private[collection] final class DoubleBinaryTreeStepper[T >: Null <: AnyRef]( + _maxLength: Int, _myCurrent: T, _stack: Array[AnyRef], _index: Int, _left: T => T, _right: T => T, protected val extract: T => Double +) +extends BinaryTreeStepperBase[Double, T, DoubleStepper, DoubleBinaryTreeStepper[T]](_maxLength, _myCurrent, _stack, _index, _left, _right) +with DoubleStepper { + def nextStep(): Double = + if (hasStep) { + val ans = extract(myCurrent) + myCurrent = null + maxLength -= 1 + ans + } + else Stepper.throwNSEE() + + def semiclone(maxL: Int, myC: T, stk: Array[AnyRef], ix: Int): DoubleBinaryTreeStepper[T] = + new DoubleBinaryTreeStepper[T](maxL, myC, stk, ix, left, right, extract) +} +private [collection] object DoubleBinaryTreeStepper { + def from[T >: Null <: AnyRef](maxLength: Int, root: T, left: T => T, right: T => T, extract: T => Double): DoubleBinaryTreeStepper[T] = { + val ans = new DoubleBinaryTreeStepper(0, null, BinaryTreeStepper.emptyStack, -1, left, right, extract) + ans.initialize(root, maxLength) + ans + } +} + + +private[collection] final class IntBinaryTreeStepper[T >: Null <: AnyRef]( + _maxLength: Int, _myCurrent: T, _stack: Array[AnyRef], _index: Int, _left: T => T, _right: T => T, protected val extract: T => Int +) +extends BinaryTreeStepperBase[Int, T, IntStepper, IntBinaryTreeStepper[T]](_maxLength, _myCurrent, _stack, _index, _left, _right) +with IntStepper { + def nextStep(): Int = + if (hasStep) { + val ans = extract(myCurrent) + myCurrent = null + maxLength -= 1 + ans + } + else Stepper.throwNSEE() + + def semiclone(maxL: Int, myC: T, stk: Array[AnyRef], ix: Int): IntBinaryTreeStepper[T] = + new IntBinaryTreeStepper[T](maxL, myC, stk, ix, left, right, extract) +} +private [collection] object IntBinaryTreeStepper { + def from[T >: Null <: AnyRef](maxLength: Int, root: T, left: T => T, right: T => T, extract: T => Int): IntBinaryTreeStepper[T] = { + val ans = new IntBinaryTreeStepper(0, null, BinaryTreeStepper.emptyStack, -1, left, right, extract) + ans.initialize(root, maxLength) + ans + } +} + + + +private[collection] final class LongBinaryTreeStepper[T >: Null <: AnyRef]( + _maxLength: Int, _myCurrent: T, _stack: Array[AnyRef], _index: Int, _left: T => T, _right: T => T, protected val extract: T => Long +) +extends BinaryTreeStepperBase[Long, T, LongStepper, LongBinaryTreeStepper[T]](_maxLength, _myCurrent, _stack, _index, _left, _right) +with LongStepper { + def nextStep(): Long = + if (hasStep) { + val ans = extract(myCurrent) + myCurrent = null + maxLength -= 1 + ans + } + else Stepper.throwNSEE() + + def semiclone(maxL: Int, myC: T, stk: Array[AnyRef], ix: Int): LongBinaryTreeStepper[T] = + new LongBinaryTreeStepper[T](maxL, myC, stk, ix, left, right, extract) +} +private [collection] object LongBinaryTreeStepper { + def from[T >: Null <: AnyRef](maxLength: Int, root: T, left: T => T, right: T => T, extract: T => Long): LongBinaryTreeStepper[T] = { + val ans = new LongBinaryTreeStepper(0, null, BinaryTreeStepper.emptyStack, -1, left, right, extract) + ans.initialize(root, maxLength) + ans + } +} + + diff --git a/library/src/scala/collection/convert/impl/BitSetStepper.scala b/library/src/scala/collection/convert/impl/BitSetStepper.scala new file mode 100644 index 000000000000..a7fa44685d80 --- /dev/null +++ b/library/src/scala/collection/convert/impl/BitSetStepper.scala @@ -0,0 +1,119 @@ +/* + * 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.collection.convert +package impl + +import scala.language.`2.13` +import scala.collection.Stepper.EfficientSplit +import scala.collection.{BitSetOps, IntStepper, Stepper} + + +private[collection] final class BitSetStepper( + private var underlying: BitSetOps[_], + private var cache0: Long, private var cache1: Long, + _i0: Int, _iN: Int, + private var cacheIndex: Int +) +extends InOrderStepperBase[IntStepper, BitSetStepper](_i0, _iN) +with IntStepper { + import BitSetOps.{WordLength, LogWL} + + // When `found` is set, `i0` is an element that exists + protected var found: Boolean = false + + @annotation.tailrec + protected def findNext(): Boolean = + if (i0 >= iN) false + else { + val ix = i0 >> LogWL + if (ix == cacheIndex || ix == cacheIndex+1) { + val i = scanLong(if (ix == cacheIndex) cache0 else cache1, i0 & (WordLength - 1)) + if (i >= 0) { + i0 = (i0 & ~(WordLength - 1)) | i + found = (i0 < iN) + found + } + else { + i0 = (i0 & ~(WordLength - 1)) + WordLength + findNext() + } + } + else if (underlying eq null) { + i0 = iN + found = false + found + } + else { + cacheIndex = ix + cache0 = underlying.word(cacheIndex) + cache1 = if ((iN - 1) >> LogWL == ix) -1L else underlying.word(cacheIndex+1) + findNext() + } + } + + def semiclone(half: Int): BitSetStepper = + if (underlying == null) { + val ans = new BitSetStepper(null, cache0, cache1, i0, half, cacheIndex) + ans.found = found + i0 = half + found = false + ans + } + else { + // Set up new stepper + val ixNewN = (half - 1) >> LogWL + val ans = + new BitSetStepper(if (ixNewN <= cacheIndex + 1) null else underlying, cache0, cache1, i0, half, cacheIndex) + if (found) ans.found = true + + // Advance old stepper to breakpoint + val ixOld0 = half >> LogWL + if (ixOld0 > cacheIndex + 1) { + cache0 = underlying.word(ixOld0) + cache1 = if (((iN - 1) >> LogWL) == ixOld0) -1L else underlying.word(ixOld0+1) + cacheIndex = ixOld0 + i0 = half + found = false + } + + // Return new stepper + ans + } + + @annotation.tailrec + private[this] def scanLong(bits: Long, from: Int): Int = + if (from >= WordLength) -1 + else if ((bits & (1L << from)) != 0) from + else scanLong(bits, from + 1) + + def nextStep(): Int = + if (found || findNext()) { + found = false + val ans = i0 + i0 += 1 + ans + } + else Stepper.throwNSEE() +} + +private[collection] object BitSetStepper { + def from(bs: scala.collection.BitSetOps[_]): IntStepper with EfficientSplit = + new BitSetStepper( + if (bs.nwords <= 2) null else bs, + if (bs.nwords <= 0) -1L else bs.word(0), + if (bs.nwords <= 1) -1L else bs.word(1), + 0, + bs.nwords * BitSetOps.WordLength, + 0 + ) +} diff --git a/library/src/scala/collection/convert/impl/ChampStepper.scala b/library/src/scala/collection/convert/impl/ChampStepper.scala new file mode 100644 index 000000000000..2973cd1ba9d2 --- /dev/null +++ b/library/src/scala/collection/convert/impl/ChampStepper.scala @@ -0,0 +1,246 @@ +/* + * 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.collection.convert +package impl + +import scala.language.`2.13` +import scala.collection.Stepper.EfficientSplit +import scala.collection._ +import scala.collection.immutable.Node + +/** A stepper that is a slightly elaborated version of the ChampBaseIterator; + * the main difference is that it knows when it should stop instead of running + * to the end of all trees. + */ +private[collection] abstract class ChampStepperBase[ + A, T <: Node[T], Sub >: Null, Semi <: Sub with ChampStepperBase[A, T, _, _] +](protected var maxSize: Int) +extends EfficientSplit { + import Node.MaxDepth + + // Much of this code is identical to ChampBaseIterator. If you change that, look here too! + + protected var currentValueCursor: Int = 0 + protected var currentValueLength: Int = 0 + protected var currentValueNode: T = _ + + private var currentStackLevel: Int = -1 + private var nodeCursorsAndLengths: Array[Int] = _ + private var nodes: Array[T] = _ + + private def initNodes(): Unit = { + if (nodeCursorsAndLengths eq null) { + nodeCursorsAndLengths = new Array[Int](MaxDepth * 2) + nodes = new Array[Node[T]](MaxDepth).asInstanceOf[Array[T]] + } + } + def initRoot(rootNode: T): Unit = { + if (rootNode.hasNodes) pushNode(rootNode) + if (rootNode.hasPayload) setupPayloadNode(rootNode) + } + + private final def setupPayloadNode(node: T): Unit = { + currentValueNode = node + currentValueCursor = 0 + currentValueLength = node.payloadArity + } + + private final def pushNode(node: T): Unit = { + initNodes() + currentStackLevel = currentStackLevel + 1 + + val cursorIndex = currentStackLevel * 2 + val lengthIndex = currentStackLevel * 2 + 1 + + nodes(currentStackLevel) = node + nodeCursorsAndLengths(cursorIndex) = 0 + nodeCursorsAndLengths(lengthIndex) = node.nodeArity + } + + private final def popNode(): Unit = { + currentStackLevel = currentStackLevel - 1 + } + + /** + * Searches for next node that contains payload values, + * and pushes encountered sub-nodes on a stack for depth-first traversal. + */ + private final def searchNextValueNode(): Boolean = { + while (currentStackLevel >= 0) { + val cursorIndex = currentStackLevel * 2 + val lengthIndex = currentStackLevel * 2 + 1 + + val nodeCursor = nodeCursorsAndLengths(cursorIndex) + val nodeLength = nodeCursorsAndLengths(lengthIndex) + + if (nodeCursor < nodeLength) { + nodeCursorsAndLengths(cursorIndex) += 1 + + val nextNode = nodes(currentStackLevel).getNode(nodeCursor) + + if (nextNode.hasNodes) { pushNode(nextNode) } + if (nextNode.hasPayload) { setupPayloadNode(nextNode) ; return true } + } else { + popNode() + } + } + false + } + + def characteristics: Int = 0 + + def estimateSize: Long = if (hasStep) maxSize else 0L + + def semiclone(): Semi + + final def hasStep: Boolean = maxSize > 0 && { + val ans = (currentValueCursor < currentValueLength) || searchNextValueNode() + if (!ans) maxSize = 0 + ans + } + + final def trySplit(): Sub = + if (!hasStep) null + else { + var fork = 0 + while (fork <= currentStackLevel && nodeCursorsAndLengths(2*fork) >= nodeCursorsAndLengths(2*fork + 1)) fork += 1 + if (fork > currentStackLevel && currentValueCursor > currentValueLength -2) null + else { + val semi = semiclone() + semi.maxSize = maxSize + semi.currentValueCursor = currentValueCursor + semi.currentValueNode = currentValueNode + if (fork > currentStackLevel) { + // Just need to finish the current node + semi.currentStackLevel = -1 + val i = (currentValueCursor + currentValueLength) >>> 1 + semi.currentValueLength = i + currentValueCursor = i + } + else { + // Need (at least some of) the full stack, so make an identical copy + semi.nodeCursorsAndLengths = java.util.Arrays.copyOf(nodeCursorsAndLengths, nodeCursorsAndLengths.length) + semi.nodes = java.util.Arrays.copyOf(nodes.asInstanceOf[Array[Node[T]]], nodes.length).asInstanceOf[Array[T]] + semi.currentStackLevel = currentStackLevel + semi.currentValueLength = currentValueLength + + // Split the top level of the stack where there's still something to split + // Could make this more efficient by duplicating code from searchNextValueNode + // instead of setting up for it to run normally. But splits tend to be rare, + // so it's not critically important. + // + // Note that this split can be kind of uneven; if we knew how many child nodes there + // were we could do better. + val i = (nodeCursorsAndLengths(2*fork) + nodeCursorsAndLengths(2*fork + 1)) >>> 1 + semi.nodeCursorsAndLengths(2*fork + 1) = i + var j = currentStackLevel + while (j > fork) { + nodeCursorsAndLengths(2*j) = nodeCursorsAndLengths(2*j + 1) + j -= 1 + } + nodeCursorsAndLengths(2*fork) = i + searchNextValueNode() + } + semi + } + } +} + + +private[collection] final class AnyChampStepper[A, T >: Null <: Node[T]](_maxSize: Int, protected val extract: (T, Int) => A) +extends ChampStepperBase[A, T, AnyStepper[A], AnyChampStepper[A, T]](_maxSize) +with AnyStepper[A] { + def nextStep(): A = + if (hasStep) { + val ans = extract(currentValueNode, currentValueCursor) + currentValueCursor += 1 + maxSize -= 1 + ans + } + else Stepper.throwNSEE() + + def semiclone(): AnyChampStepper[A, T] = new AnyChampStepper[A, T](0, extract) +} +private[collection] object AnyChampStepper { + def from[A, T >: Null <: Node[T]](maxSize: Int, root: T, extract: (T, Int) => A): AnyChampStepper[A, T] = { + val ans = new AnyChampStepper[A, T](maxSize, extract) + ans.initRoot(root) + ans + } +} + +private[collection] final class DoubleChampStepper[T >: Null <: Node[T]](_maxSize: Int, protected val extract: (T, Int) => Double) +extends ChampStepperBase[Double, T, DoubleStepper, DoubleChampStepper[T]](_maxSize) +with DoubleStepper { + def nextStep(): Double = + if (hasStep) { + val ans = extract(currentValueNode, currentValueCursor) + currentValueCursor += 1 + maxSize -= 1 + ans + } + else Stepper.throwNSEE() + + def semiclone(): DoubleChampStepper[T] = new DoubleChampStepper[T](0, extract) +} +private[collection] object DoubleChampStepper { + def from[T >: Null <: Node[T]](maxSize: Int, root: T, extract: (T, Int) => Double): DoubleChampStepper[T] = { + val ans = new DoubleChampStepper[T](maxSize, extract) + ans.initRoot(root) + ans + } +} + +private[collection] final class IntChampStepper[T >: Null <: Node[T]](_maxSize: Int, protected val extract: (T, Int) => Int) +extends ChampStepperBase[Int, T, IntStepper, IntChampStepper[T]](_maxSize) +with IntStepper { + def nextStep(): Int = + if (hasStep) { + val ans = extract(currentValueNode, currentValueCursor) + currentValueCursor += 1 + maxSize -= 1 + ans + } + else Stepper.throwNSEE() + + def semiclone(): IntChampStepper[T] = new IntChampStepper[T](0, extract) +} +private[collection] object IntChampStepper { + def from[T >: Null <: Node[T]](maxSize: Int, root: T, extract: (T, Int) => Int): IntChampStepper[T] = { + val ans = new IntChampStepper[T](maxSize, extract) + ans.initRoot(root) + ans + } +} + +private[collection] final class LongChampStepper[T >: Null <: Node[T]](_maxSize: Int, protected val extract: (T, Int) => Long) +extends ChampStepperBase[Long, T, LongStepper, LongChampStepper[T]](_maxSize) +with LongStepper { + def nextStep(): Long = + if (hasStep) { + val ans = extract(currentValueNode, currentValueCursor) + currentValueCursor += 1 + maxSize -= 1 + ans + } + else Stepper.throwNSEE() + + def semiclone(): LongChampStepper[T] = new LongChampStepper[T](0, extract) +} +private[collection] object LongChampStepper { + def from[T >: Null <: Node[T]](maxSize: Int, root: T, extract: (T, Int) => Long): LongChampStepper[T] = { + val ans = new LongChampStepper[T](maxSize, extract) + ans.initRoot(root) + ans + } +} diff --git a/library/src/scala/collection/convert/impl/InOrderStepperBase.scala b/library/src/scala/collection/convert/impl/InOrderStepperBase.scala new file mode 100644 index 000000000000..544bfff010d8 --- /dev/null +++ b/library/src/scala/collection/convert/impl/InOrderStepperBase.scala @@ -0,0 +1,54 @@ +/* + * 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.collection.convert +package impl + +import scala.language.`2.13` +import java.util.Spliterator + +import scala.collection.Stepper.EfficientSplit + +/** Abstracts all the generic operations of stepping over a collection + * that has an indexable ordering but may have gaps. + * + * For collections that are guaranteed to not have gaps, use `IndexedStepperBase` instead. + */ +private[convert] abstract class InOrderStepperBase[Sub >: Null, Semi <: Sub](protected var i0: Int, protected var iN: Int) +extends EfficientSplit { + /** Set `true` if the element at `i0` is known to be there. `false` if either not known or is a gap. + */ + protected def found: Boolean + + /** Advance `i0` over any gaps, updating internal state so `found` is correct at the new position. + * Returns the new value of `found`. + */ + protected def findNext(): Boolean + + protected def semiclone(half: Int): Semi + + final def hasStep: Boolean = found || findNext() + + def characteristics: Int = Spliterator.ORDERED + + def estimateSize: Long = iN - i0 + + def trySplit(): Sub = { + if (iN-1 > i0) { + val half = (i0 + iN) >>> 1 + val ans = semiclone(half) + i0 = half + ans + } + else null + } +} diff --git a/library/src/scala/collection/convert/impl/IndexedSeqStepper.scala b/library/src/scala/collection/convert/impl/IndexedSeqStepper.scala new file mode 100644 index 000000000000..5e4717c55fe1 --- /dev/null +++ b/library/src/scala/collection/convert/impl/IndexedSeqStepper.scala @@ -0,0 +1,45 @@ +/* + * 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.collection.convert +package impl + +import scala.language.`2.13` +import scala.collection._ + +private[collection] class AnyIndexedSeqStepper[A](underlying: collection.IndexedSeqOps[A, AnyConstr, _], _i0: Int, _iN: Int) + extends IndexedStepperBase[AnyStepper[A], AnyIndexedSeqStepper[A]](_i0, _iN) + with AnyStepper[A] { + def nextStep(): A = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + protected def semiclone(half: Int): AnyIndexedSeqStepper[A] = new AnyIndexedSeqStepper[A](underlying, i0, half) +} + +private[collection] class DoubleIndexedSeqStepper[CC <: collection.IndexedSeqOps[Double, AnyConstr, _]](underlying: CC, _i0: Int, _iN: Int) + extends IndexedStepperBase[DoubleStepper, DoubleIndexedSeqStepper[CC]](_i0, _iN) + with DoubleStepper { + def nextStep(): Double = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + protected def semiclone(half: Int): DoubleIndexedSeqStepper[CC] = new DoubleIndexedSeqStepper[CC](underlying, i0, half) +} + +private[collection] class IntIndexedSeqStepper[CC <: collection.IndexedSeqOps[Int, AnyConstr, _]](underlying: CC, _i0: Int, _iN: Int) + extends IndexedStepperBase[IntStepper, IntIndexedSeqStepper[CC]](_i0, _iN) + with IntStepper { + def nextStep(): Int = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + protected def semiclone(half: Int): IntIndexedSeqStepper[CC] = new IntIndexedSeqStepper[CC](underlying, i0, half) +} + +private[collection] class LongIndexedSeqStepper[CC <: collection.IndexedSeqOps[Long, AnyConstr, _]](underlying: CC, _i0: Int, _iN: Int) + extends IndexedStepperBase[LongStepper, LongIndexedSeqStepper[CC]](_i0, _iN) + with LongStepper { + def nextStep(): Long = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + protected def semiclone(half: Int): LongIndexedSeqStepper[CC] = new LongIndexedSeqStepper[CC](underlying, i0, half) +} diff --git a/library/src/scala/collection/convert/impl/IndexedStepperBase.scala b/library/src/scala/collection/convert/impl/IndexedStepperBase.scala new file mode 100644 index 000000000000..3acb743e7c57 --- /dev/null +++ b/library/src/scala/collection/convert/impl/IndexedStepperBase.scala @@ -0,0 +1,41 @@ +/* + * 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.collection.convert +package impl + +import scala.language.`2.13` +import java.util.Spliterator + +import scala.collection.Stepper.EfficientSplit + +/** Abstracts all the generic operations of stepping over an indexable collection */ +private[convert] abstract class IndexedStepperBase[Sub >: Null, Semi <: Sub](protected var i0: Int, protected var iN: Int) + extends EfficientSplit { + protected def semiclone(half: Int): Semi + + def hasStep: Boolean = i0 < iN + + def characteristics: Int = Spliterator.ORDERED + Spliterator.SIZED + Spliterator.SUBSIZED + + def estimateSize: Long = iN - i0 + + def trySplit(): Sub = { + if (iN-1 > i0) { + val half = (i0+iN) >>> 1 + val ans = semiclone(half) + i0 = half + ans + } + else null + } +} diff --git a/library/src/scala/collection/convert/impl/IteratorStepper.scala b/library/src/scala/collection/convert/impl/IteratorStepper.scala new file mode 100644 index 000000000000..9f8aab9c2165 --- /dev/null +++ b/library/src/scala/collection/convert/impl/IteratorStepper.scala @@ -0,0 +1,130 @@ +/* + * 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.collection.convert +package impl + +import scala.language.`2.13` +import java.util.Spliterator + +import scala.collection.{AnyStepper, DoubleStepper, IntStepper, LongStepper, Stepper} +import scala.jdk.{AnyAccumulator, DoubleAccumulator, IntAccumulator, LongAccumulator} + +private[collection] class AnyIteratorStepper[A](_underlying: Iterator[A]) + extends IteratorStepperBase[A, AnyStepper[A], AnyIteratorStepper[A]](_underlying) + with AnyStepper[A] { + protected def semiclone(): AnyIteratorStepper[A] = new AnyIteratorStepper(null) + + def nextStep(): A = if (proxied ne null) proxied.nextStep() else underlying.next() + + def trySplit(): AnyStepper[A] = if (proxied ne null) proxied.trySplit() else { + val acc = new AnyAccumulator[A] + var i = 0 + val n = nextChunkSize & 0xFFFFFFFC + while (i < n && underlying.hasNext) { acc += underlying.next(); i += 1 } + if (i < n || !underlying.hasNext) { + proxied = acc.stepper + proxied.trySplit() + } + else { + val ans = semiclone() + ans.proxied = acc.stepper + nextChunkSize = if ((nextChunkSize&3) == 3) { if (n < 0x40000000) n*2 else n } else nextChunkSize + 1 + ans + } + } +} + +private[collection] class DoubleIteratorStepper(_underlying: Iterator[Double]) + extends IteratorStepperBase[Double, DoubleStepper, DoubleIteratorStepper](_underlying) + with DoubleStepper { + protected def semiclone(): DoubleIteratorStepper = new DoubleIteratorStepper(null) + + def nextStep(): Double = if (proxied ne null) proxied.nextStep() else underlying.next() + + def trySplit(): DoubleStepper = if (proxied ne null) proxied.trySplit() else { + val acc = new DoubleAccumulator + var i = 0 + val n = nextChunkSize & 0xFFFFFFFC + while (i < n && underlying.hasNext) { acc += underlying.next(); i += 1 } + if (i < n || !underlying.hasNext) { + proxied = acc.stepper + proxied.trySplit() + } + else { + val ans = semiclone() + ans.proxied = acc.stepper + nextChunkSize = if ((nextChunkSize&3) == 3) { if (n < 0x40000000) n*2 else n } else nextChunkSize + 1 + ans + } + } +} + +private[collection] class IntIteratorStepper(_underlying: Iterator[Int]) + extends IteratorStepperBase[Int, IntStepper, IntIteratorStepper](_underlying) + with IntStepper { + protected def semiclone(): IntIteratorStepper = new IntIteratorStepper(null) + + def nextStep(): Int = if (proxied ne null) proxied.nextStep() else underlying.next() + + def trySplit(): IntStepper = if (proxied ne null) proxied.trySplit() else { + val acc = new IntAccumulator + var i = 0 + val n = nextChunkSize & 0xFFFFFFFC + while (i < n && underlying.hasNext) { acc += underlying.next(); i += 1 } + if (i < n || !underlying.hasNext) { + proxied = acc.stepper + proxied.trySplit() + } + else { + val ans = semiclone() + ans.proxied = acc.stepper + nextChunkSize = if ((nextChunkSize&3) == 3) { if (n < 0x40000000) n*2 else n } else nextChunkSize + 1 + ans + } + } +} + +private[collection] class LongIteratorStepper(_underlying: Iterator[Long]) + extends IteratorStepperBase[Long, LongStepper, LongIteratorStepper](_underlying) + with LongStepper { + protected def semiclone(): LongIteratorStepper = new LongIteratorStepper(null) + + def nextStep(): Long = if (proxied ne null) proxied.nextStep() else underlying.next() + + def trySplit(): LongStepper = if (proxied ne null) proxied.trySplit() else { + val acc = new LongAccumulator + var i = 0 + val n = nextChunkSize & 0xFFFFFFFC + while (i < n && underlying.hasNext) { acc += underlying.next(); i += 1 } + if (i < n || !underlying.hasNext) { + proxied = acc.stepper + proxied.trySplit() + } + else { + val ans = semiclone() + ans.proxied = acc.stepper + nextChunkSize = if ((nextChunkSize&3) == 3) { if (n < 0x40000000) n*2 else n } else nextChunkSize + 1 + ans + } + } +} + +/** Common functionality for Steppers that step through an Iterator, caching the results as needed when a split is requested. */ +private[convert] abstract class IteratorStepperBase[A, SP >: Null <: Stepper[A], Semi <: SP](final protected val underlying: Iterator[A]) { + final protected var nextChunkSize = 16 + final protected var proxied: SP = null + protected def semiclone(): Semi // Must initialize with null iterator! + def characteristics: Int = if (proxied ne null) Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED else Spliterator.ORDERED + def estimateSize: Long = if (proxied ne null) proxied.estimateSize else Long.MaxValue + def hasStep: Boolean = if (proxied ne null) proxied.hasStep else underlying.hasNext +} diff --git a/library/src/scala/collection/convert/impl/NumericRangeStepper.scala b/library/src/scala/collection/convert/impl/NumericRangeStepper.scala new file mode 100644 index 000000000000..a44efa52248c --- /dev/null +++ b/library/src/scala/collection/convert/impl/NumericRangeStepper.scala @@ -0,0 +1,39 @@ +/* + * 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.collection.convert +package impl + +import scala.language.`2.13` +import scala.collection.{AnyStepper, IntStepper, LongStepper, Stepper} +import scala.collection.immutable.NumericRange + +private[collection] class AnyNumericRangeStepper[A](underlying: NumericRange[A], _i0: Int, _iN: Int) +extends IndexedStepperBase[AnyStepper[A], AnyNumericRangeStepper[A]](_i0, _iN) +with AnyStepper[A] { + def nextStep(): A = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + def semiclone(half: Int) = new AnyNumericRangeStepper[A](underlying, i0, half) +} + +private[collection] class IntNumericRangeStepper(underlying: NumericRange[Int], _i0: Int, _iN: Int) +extends IndexedStepperBase[IntStepper, IntNumericRangeStepper](_i0, _iN) +with IntStepper { + def nextStep(): Int = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + def semiclone(half: Int) = new IntNumericRangeStepper(underlying, i0, half) +} + +private[collection] class LongNumericRangeStepper(underlying: NumericRange[Long], _i0: Int, _iN: Int) +extends IndexedStepperBase[LongStepper, LongNumericRangeStepper](_i0, _iN) +with LongStepper { + def nextStep(): Long = if (hasStep) { val j = i0; i0 += 1; underlying(j) } else Stepper.throwNSEE() + def semiclone(half: Int) = new LongNumericRangeStepper(underlying, i0, half) +} diff --git a/library/src/scala/collection/convert/impl/RangeStepper.scala b/library/src/scala/collection/convert/impl/RangeStepper.scala new file mode 100644 index 000000000000..283975ff0332 --- /dev/null +++ b/library/src/scala/collection/convert/impl/RangeStepper.scala @@ -0,0 +1,41 @@ +/* + * 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.collection.convert +package impl + +import scala.language.`2.13` +import scala.collection.{IntStepper, Stepper} + +/** Implements Stepper on an integer Range. You don't actually need the Range to do this, + * so only the relevant parts are included. Because the arguments are protected, they are + * not error-checked; `Range` is required to provide valid arguments. + */ +private[collection] final class RangeStepper(protected var myNext: Int, myStep: Int, _i0: Int, _iN: Int) +extends IndexedStepperBase[IntStepper, RangeStepper](_i0, _iN) +with IntStepper { + def nextStep(): Int = + if (hasStep) { + val ans = myNext + myNext += myStep + i0 += 1 + ans + } + else Stepper.throwNSEE() + protected def semiclone(half: Int): RangeStepper = new RangeStepper(myNext, myStep, i0, half) + override def trySplit(): IntStepper = { + val old_i0 = i0 + val ans = super.trySplit() + myNext += (i0 - old_i0) * myStep + ans + } +} diff --git a/library/src/scala/collection/convert/impl/StringStepper.scala b/library/src/scala/collection/convert/impl/StringStepper.scala new file mode 100644 index 000000000000..72d4e67ef1cb --- /dev/null +++ b/library/src/scala/collection/convert/impl/StringStepper.scala @@ -0,0 +1,59 @@ +/* + * 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.collection.convert +package impl + +import scala.language.`2.13` +import java.lang.Character.{charCount, isLowSurrogate} +import java.util.Spliterator + +import scala.collection.Stepper.EfficientSplit +import scala.collection.{IntStepper, Stepper} + +/** Implements `Stepper` on a `String` where you step through chars packed into `Int`. + */ +private[collection] final class CharStringStepper(underlying: String, _i0: Int, _iN: Int) +extends IndexedStepperBase[IntStepper, CharStringStepper](_i0, _iN) +with IntStepper { + def nextStep(): Int = + if (hasStep) { val j = i0; i0 += 1; underlying.charAt(j) } + else Stepper.throwNSEE() + + def semiclone(half: Int): CharStringStepper = new CharStringStepper(underlying, i0, half) +} + +/** Implements `Stepper` on a `String` where you step through code points. + */ +private[collection] final class CodePointStringStepper(underlying: String, private var i0: Int, private var iN: Int) +extends IntStepper with EfficientSplit { + def characteristics: Int = Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED + def estimateSize: Long = iN - i0 + def hasStep: Boolean = i0 < iN + def nextStep(): Int = { + if (hasStep) { + val cp = underlying.codePointAt(i0) + i0 += charCount(cp) + cp + } + else Stepper.throwNSEE() + } + def trySplit(): CodePointStringStepper = + if (iN - 3 > i0) { + var half = (i0 + iN) >>> 1 + if (isLowSurrogate(underlying.charAt(half))) half -= 1 + val ans = new CodePointStringStepper(underlying, i0, half) + i0 = half + ans + } + else null +} diff --git a/library/src/scala/collection/convert/impl/TableStepper.scala b/library/src/scala/collection/convert/impl/TableStepper.scala new file mode 100644 index 000000000000..324732a0c2d1 --- /dev/null +++ b/library/src/scala/collection/convert/impl/TableStepper.scala @@ -0,0 +1,139 @@ +/* + * 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.collection.convert +package impl + +import scala.language.`2.13` +import scala.collection.Stepper.EfficientSplit +import scala.collection._ + +private[collection] abstract class TableStepperBase[A, I >: Null <: AnyRef, Sub >: Null, Semi <: Sub with TableStepperBase[A, I, _, _]]( + protected var maxLength: Int, protected val table: Array[I], protected var i0: Int, protected val iN: Int +) +extends EfficientSplit { + // Always holds table(i0); if `null` it is time to switch to the next element + protected var myCurrent: I = if (i0 < iN) table(i0) else null + + // Only call this when `myCurrent` is null (meaning we need to advance) + @annotation.tailrec + protected final def findNextCurrent(): Boolean = + if (i0 < iN) { + i0 += 1 + if (i0 >= iN) false + else { + myCurrent = table(i0) + if (myCurrent eq null) findNextCurrent() + else true + } + } + else false + + protected def semiclone(half: Int): Semi + + def characteristics: Int = 0 + + def estimateSize: Long = if (!hasStep) { maxLength = 0; 0 } else maxLength + + def hasStep: Boolean = (myCurrent ne null) || findNextCurrent() + + def trySplit(): Sub = { + if (iN-1 > i0 && maxLength > 0) { + val half = (i0 + iN) >>> 1 + val ans = semiclone(half) + ans.myCurrent = myCurrent + myCurrent = table(half) + var inLeft = if (ans.myCurrent ne null) 1 else 0 + var inRight = if (myCurrent ne null) 1 else 0 + if (iN - i0 < 32) { + var i = i0+1 + while (i < half && (table(i) ne null)) { i += 1; inLeft += 1 } + i = half+1 + while (i < iN && (table(i) ne null)) { i += 1; inRight += 1 } + } + maxLength -= inLeft + ans.maxLength -= inRight + i0 = half + ans + } + else null + } +} + + +private[collection] final class AnyTableStepper[A, I >: Null <: AnyRef]( + _maxLength: Int, _table: Array[I], iterate: I => I, extract: I => A, _i0: Int, _iN: Int +) +extends TableStepperBase[A, I, AnyStepper[A], AnyTableStepper[A, I]](_maxLength, _table, _i0, _iN) +with AnyStepper[A] { + def nextStep(): A = + if (hasStep) { + val ans = extract(myCurrent) + myCurrent = iterate(myCurrent) + ans + } + else Stepper.throwNSEE() + + def semiclone(half: Int): AnyTableStepper[A, I] = new AnyTableStepper[A, I](maxLength, table, iterate, extract, i0, half) +} + + +private[collection] final class DoubleTableStepper[I >: Null <: AnyRef]( + _maxLength: Int, _table: Array[I], iterate: I => I, extract: I => Double, _i0: Int, _iN: Int +) +extends TableStepperBase[Double, I, DoubleStepper, DoubleTableStepper[I]](_maxLength, _table, _i0, _iN) +with DoubleStepper { + def nextStep(): Double = + if (hasStep) { + val ans = extract(myCurrent) + myCurrent = iterate(myCurrent) + ans + } + else Stepper.throwNSEE() + + def semiclone(half: Int): DoubleTableStepper[I] = new DoubleTableStepper[I](maxLength, table, iterate, extract, i0, half) +} + + +private[collection] final class IntTableStepper[I >: Null <: AnyRef]( + _maxLength: Int, _table: Array[I], iterate: I => I, extract: I => Int, _i0: Int, _iN: Int +) +extends TableStepperBase[Int, I, IntStepper, IntTableStepper[I]](_maxLength, _table, _i0, _iN) +with IntStepper { + def nextStep(): Int = + if (hasStep) { + val ans = extract(myCurrent) + myCurrent = iterate(myCurrent) + ans + } + else Stepper.throwNSEE() + + def semiclone(half: Int): IntTableStepper[I] = new IntTableStepper[I](maxLength, table, iterate, extract, i0, half) +} + + +private[collection] final class LongTableStepper[I >: Null <: AnyRef]( + _maxLength: Int, _table: Array[I], iterate: I => I, extract: I => Long, _i0: Int, _iN: Int +) +extends TableStepperBase[Long, I, LongStepper, LongTableStepper[I]](_maxLength, _table, _i0, _iN) +with LongStepper { + def nextStep(): Long = + if (hasStep) { + val ans = extract(myCurrent) + myCurrent = iterate(myCurrent) + ans + } + else Stepper.throwNSEE() + + def semiclone(half: Int): LongTableStepper[I] = new LongTableStepper[I](maxLength, table, iterate, extract, i0, half) +} + diff --git a/library/src/scala/collection/convert/impl/VectorStepper.scala b/library/src/scala/collection/convert/impl/VectorStepper.scala new file mode 100644 index 000000000000..85f349922f48 --- /dev/null +++ b/library/src/scala/collection/convert/impl/VectorStepper.scala @@ -0,0 +1,132 @@ +/* + * 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.collection.convert +package impl + +import scala.language.`2.13` +import scala.collection._ + +private[convert] abstract class VectorStepperBase[Sub >: Null, Semi <: Sub]( + _i0: Int, + _iN: Int, + protected val displayN: Int, + protected val trunk: Array[AnyRef] +) +extends IndexedStepperBase[Sub, Semi](_i0, _iN) { + protected var index: Int = 32 // Force an advanceData on the first element + protected var leaves: Array[AnyRef] = null + protected var index1: Int = 32 // Force advanceData to defer to initTo on the first element + protected var twigs: Array[AnyRef] = null + + protected final def advanceData(iX: Int): Unit = { + index1 += 1 + if (index1 >= 32) initTo(iX) + else { + leaves = twigs(index1).asInstanceOf[Array[AnyRef]] + index = 0 + } + } + protected final def initTo(iX: Int): Unit = displayN match { + case 0 => + leaves = trunk + index = iX + case 1 => + twigs = trunk + index1 = iX >>> 5 + leaves = twigs(index1).asInstanceOf[Array[AnyRef]] + index = iX & 0x1F + case _ => + var n = displayN + var dataN = trunk + while (n > 2) { + dataN = dataN((iX >> (5*n)) & 0x1F).asInstanceOf[Array[AnyRef]] + n -= 1 + } + twigs = dataN((iX >>> 10) & 0x1F).asInstanceOf[Array[AnyRef]] + index1 = (iX >> 5) & 0x1F + leaves = twigs(index1).asInstanceOf[Array[AnyRef]] + index = iX & 0x1F + } +} + +private[collection] class AnyVectorStepper[A](_i0: Int, _iN: Int, _displayN: Int, _trunk: Array[AnyRef]) +extends VectorStepperBase[AnyStepper[A], AnyVectorStepper[A]](_i0, _iN, _displayN, _trunk) +with AnyStepper[A] { + def nextStep(): A = if (hasStep) { + index += 1 + if (index >= 32) advanceData(i0) + i0 += 1 + leaves(index).asInstanceOf[A] + } else Stepper.throwNSEE() + def semiclone(half: Int): AnyVectorStepper[A] = { + val ans = new AnyVectorStepper[A](i0, half, displayN, trunk) + index = 32 + index1 = 32 + i0 = half + ans + } +} + +private[collection] class DoubleVectorStepper(_i0: Int, _iN: Int, _displayN: Int, _trunk: Array[AnyRef]) +extends VectorStepperBase[DoubleStepper, DoubleVectorStepper](_i0, _iN, _displayN, _trunk) +with DoubleStepper { + def nextStep(): Double = if (hasStep) { + index += 1 + if (index >= 32) advanceData(i0) + i0 += 1 + leaves(index).asInstanceOf[Double] + } else Stepper.throwNSEE() + def semiclone(half: Int): DoubleVectorStepper = { + val ans = new DoubleVectorStepper(i0, half, displayN, trunk) + index = 32 + index1 = 32 + i0 = half + ans + } +} + +private[collection] class IntVectorStepper(_i0: Int, _iN: Int, _displayN: Int, _trunk: Array[AnyRef]) +extends VectorStepperBase[IntStepper, IntVectorStepper](_i0, _iN, _displayN, _trunk) +with IntStepper { + def nextStep(): Int = if (hasStep) { + index += 1 + if (index >= 32) advanceData(i0) + i0 += 1 + leaves(index).asInstanceOf[Int] + } else Stepper.throwNSEE() + def semiclone(half: Int): IntVectorStepper = { + val ans = new IntVectorStepper(i0, half, displayN, trunk) + index = 32 + index1 = 32 + i0 = half + ans + } +} + +private[collection] class LongVectorStepper(_i0: Int, _iN: Int, _displayN: Int, _trunk: Array[AnyRef]) +extends VectorStepperBase[LongStepper, LongVectorStepper](_i0, _iN, _displayN, _trunk) +with LongStepper { + def nextStep(): Long = if (hasStep) { + index += 1 + if (index >= 32) advanceData(i0) + i0 += 1 + leaves(index).asInstanceOf[Long] + } else Stepper.throwNSEE() + def semiclone(half: Int): LongVectorStepper = { + val ans = new LongVectorStepper(i0, half, displayN, trunk) + index = 32 + index1 = 32 + i0 = half + ans + } +} diff --git a/library/src/scala/collection/generic/BitOperations.scala b/library/src/scala/collection/generic/BitOperations.scala new file mode 100644 index 000000000000..39aa29d1daf9 --- /dev/null +++ b/library/src/scala/collection/generic/BitOperations.scala @@ -0,0 +1,51 @@ +/* + * 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.collection +package generic + +import scala.language.`2.13` + +/** Some bit operations. + * + * See [[https://www.drmaciver.com/2008/08/unsigned-comparison-in-javascala/]] for + * an explanation of unsignedCompare. + */ +private[collection] object BitOperations { + trait Int { + type Int = scala.Int + def zero(i: Int, mask: Int) = (i & mask) == 0 + def mask(i: Int, mask: Int) = i & (complement(mask - 1) ^ mask) + def hasMatch(key: Int, prefix: Int, m: Int) = mask(key, m) == prefix + def unsignedCompare(i: Int, j: Int) = (i < j) ^ (i < 0) ^ (j < 0) + def shorter(m1: Int, m2: Int) = unsignedCompare(m2, m1) + def complement(i: Int) = (-1) ^ i + def bits(num: Int) = 31 to 0 by -1 map (i => (num >>> i & 1) != 0) + def bitString(num: Int, sep: String = "") = bits(num) map (b => if (b) "1" else "0") mkString sep + def highestOneBit(j: Int) = java.lang.Integer.highestOneBit(j) + } + object Int extends Int + + trait Long { + type Long = scala.Long + def zero(i: Long, mask: Long) = (i & mask) == 0L + def mask(i: Long, mask: Long) = i & (complement(mask - 1) ^ mask) + def hasMatch(key: Long, prefix: Long, m: Long) = mask(key, m) == prefix + def unsignedCompare(i: Long, j: Long) = (i < j) ^ (i < 0L) ^ (j < 0L) + def shorter(m1: Long, m2: Long) = unsignedCompare(m2, m1) + def complement(i: Long) = (-1L) ^ i + def bits(num: Long) = 63L to 0L by -1L map (i => (num >>> i & 1L) != 0L) + def bitString(num: Long, sep: String = "") = bits(num) map (b => if (b) "1" else "0") mkString sep + def highestOneBit(j: Long) = java.lang.Long.highestOneBit(j) + } + object Long extends Long +} diff --git a/library/src/scala/collection/generic/CommonErrors.scala b/library/src/scala/collection/generic/CommonErrors.scala new file mode 100644 index 000000000000..3ced8e3debbf --- /dev/null +++ b/library/src/scala/collection/generic/CommonErrors.scala @@ -0,0 +1,30 @@ +/* + * 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.collection +package generic + +import scala.language.`2.13` + +/** Some precomputed common errors to reduce the generated code size. + */ +private[collection] object CommonErrors { + /** IndexOutOfBounds exception with a known max index */ + @noinline + def indexOutOfBounds(index: Int, max: Int): IndexOutOfBoundsException = + new IndexOutOfBoundsException(s"$index is out of bounds (min 0, max ${max})") + + /** IndexOutOfBounds exception with an unknown max index. */ + @noinline + def indexOutOfBounds(index: Int): IndexOutOfBoundsException = + new IndexOutOfBoundsException(s"$index is out of bounds (min 0, max unknown)") +} diff --git a/library/src/scala/collection/generic/DefaultSerializationProxy.scala b/library/src/scala/collection/generic/DefaultSerializationProxy.scala new file mode 100644 index 000000000000..056e53b6f882 --- /dev/null +++ b/library/src/scala/collection/generic/DefaultSerializationProxy.scala @@ -0,0 +1,88 @@ +/* + * 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.collection.generic + +import java.io.{ObjectInputStream, ObjectOutputStream} + +import scala.language.`2.13` +import scala.collection.{Factory, Iterable} +import scala.collection.mutable.Builder + +/** The default serialization proxy for collection implementations. + * + * This class is `final` and requires an extra `Factory` object rather than leaving the details of creating a `Builder` + * to an abstract method that could be implemented by a subclass. This is necessary because the factory is needed + * for deserializing this class's private state, which happens before any subclass fields would be deserialized. Any + * additional state required to create the proper `Builder` needs to be captured by the `factory`. + */ +@SerialVersionUID(3L) +final class DefaultSerializationProxy[A](factory: Factory[A, Any], @transient private[this] val coll: Iterable[A]) extends Serializable { + + @transient protected var builder: Builder[A, Any] = _ + + private[this] def writeObject(out: ObjectOutputStream): Unit = { + out.defaultWriteObject() + val k = coll.knownSize + out.writeInt(k) + var count = 0 + coll.foreach { x => + out.writeObject(x) + count += 1 + } + if(k >= 0) { + if(count != k) throw new IllegalStateException(s"Illegal size $count of collection, expected $k") + } else out.writeObject(SerializeEnd) + } + + private[this] def readObject(in: ObjectInputStream): Unit = { + in.defaultReadObject() + builder = factory.newBuilder + val k = in.readInt() + if(k >= 0) { + builder.sizeHint(k) + var count = 0 + while(count < k) { + builder += in.readObject().asInstanceOf[A] + count += 1 + } + } else { + while (true) in.readObject match { + case SerializeEnd => return + case a => builder += a.asInstanceOf[A] + } + } + } + + protected[this] def readResolve(): Any = builder.result() +} + +@SerialVersionUID(3L) +private[collection] case object SerializeEnd + +/** Mix-in trait to enable DefaultSerializationProxy for the standard collection types. Depending on the type + * it is mixed into, it will dynamically choose `iterableFactory`, `mapFactory`, `sortedIterableFactory` or + * `sortedMapFactory` for deserialization into the respective `CC` type. Override `writeReplace` or implement + * it directly without using this trait if you need a non-standard factory or if you want to use a different + * serialization scheme. + */ +transparent trait DefaultSerializable extends Serializable { this: scala.collection.Iterable[_] => + protected[this] def writeReplace(): AnyRef = { + val f: Factory[Any, Any] = this match { + case it: scala.collection.SortedMap[_, _] => it.sortedMapFactory.sortedMapFactory[Any, Any](using it.ordering.asInstanceOf[Ordering[Any]]).asInstanceOf[Factory[Any, Any]] + case it: scala.collection.Map[_, _] => it.mapFactory.mapFactory[Any, Any].asInstanceOf[Factory[Any, Any]] + case it: scala.collection.SortedSet[_] => it.sortedIterableFactory.evidenceIterableFactory[Any](using it.ordering.asInstanceOf[Ordering[Any]]) + case it => it.iterableFactory.iterableFactory + } + new DefaultSerializationProxy(f, this) + } +} diff --git a/library/src/scala/collection/generic/IsIterable.scala b/library/src/scala/collection/generic/IsIterable.scala new file mode 100644 index 000000000000..fd5db475536f --- /dev/null +++ b/library/src/scala/collection/generic/IsIterable.scala @@ -0,0 +1,166 @@ +/* + * 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.collection +package generic + +import scala.language.`2.13` + +/** A trait which can be used to avoid code duplication when defining extension + * methods that should be applicable both to existing Scala collections (i.e., + * types extending `Iterable`) as well as other (potentially user-defined) + * types that could be converted to a Scala collection type. This trait + * makes it possible to treat Scala collections and types that can be implicitly + * converted to a collection type uniformly. For example, one can provide + * extension methods that work both on collection types and on `String`s (`String`s + * do not extend `Iterable`, but can be converted to `Iterable`) + * + * `IsIterable` provides three members: + * + * 1. type member `A`, which represents the element type of the target `Iterable[A]` + * 1. type member `C`, which represents the type returned by transformation operations that preserve the collection’s elements type + * 1. method `apply`, which provides a way to convert between the type we wish to add extension methods to, `Repr`, and `IterableOps[A, Iterable, C]`. + * + * ===Usage=== + * + * One must provide `IsIterable` as an implicit parameter type of an implicit + * conversion. Its usage is shown below. Our objective in the following example + * is to provide a generic extension method `mapReduce` to any type that extends + * or can be converted to `Iterable`. In our example, this includes + * `String`. + * + * {{{ + * import scala.collection.{Iterable, IterableOps} + * import scala.collection.generic.IsIterable + * + * class ExtensionMethods[Repr, I <: IsIterable[Repr]](coll: Repr, it: I) { + * def mapReduce[B](mapper: it.A => B)(reducer: (B, B) => B): B = { + * val iter = it(coll).iterator + * var res = mapper(iter.next()) + * while (iter.hasNext) + * res = reducer(res, mapper(iter.next())) + * res + * } + * } + * + * implicit def withExtensions[Repr](coll: Repr)(implicit it: IsIterable[Repr]): ExtensionMethods[Repr, it.type] = + * new ExtensionMethods(coll, it) + * + * // See it in action! + * List(1, 2, 3).mapReduce(_ * 2)(_ + _) // res0: Int = 12 + * "Yeah, well, you know, that's just, like, your opinion, man.".mapReduce(x => 1)(_ + _) // res1: Int = 59 + *}}} + * + * Here, we begin by creating a class `ExtensionMethods` which contains our + * `mapReduce` extension method. + * + * Note that `ExtensionMethods` takes a constructor argument `coll` of type `Repr`, where + * `Repr` represents (typically) the collection type, and an argument `it` of a subtype of `IsIterable[Repr]`. + * The body of the method starts by converting the `coll` argument to an `IterableOps` in order to + * call the `iterator` method on it. + * The remaining of the implementation is straightforward. + * + * The `withExtensions` implicit conversion makes the `mapReduce` operation available + * on any type `Repr` for which it exists an implicit `IsIterable[Repr]` instance. + * Note how we keep track of the precise type of the implicit `it` argument by using the + * `it.type` singleton type, rather than the wider `IsIterable[Repr]` type. We do that + * so that the information carried by the type members `A` and `C` of the `it` argument + * is not lost. + * + * When the `mapReduce` method is called on some type of which it is not + * a member, implicit search is triggered. Because implicit conversion + * `withExtensions` is generic, it will be applied as long as an implicit + * value of type `IsIterable[Repr]` can be found. Given that the + * `IsIterable` companion object contains implicit members that return values of type + * `IsIterable`, this requirement is typically satisfied, and the chain + * of interactions described in the previous paragraph is set into action. + * (See the `IsIterable` companion object, which contains a precise + * specification of the available implicits.) + * + * ''Note'': Currently, it's not possible to combine the implicit conversion and + * the class with the extension methods into an implicit class due to + * limitations of type inference. + * + * ===Implementing `IsIterable` for New Types=== + * + * One must simply provide an implicit value of type `IsIterable` + * specific to the new type, or an implicit conversion which returns an + * instance of `IsIterable` specific to the new type. + * + * Below is an example of an implementation of the `IsIterable` trait + * where the `Repr` type is `Range`. + * + *{{{ + * implicit val rangeRepr: IsIterable[Range] { type A = Int; type C = IndexedSeq[Int] } = + * new IsIterable[Range] { + * type A = Int + * type C = IndexedSeq[Int] + * def apply(coll: Range): IterableOps[Int, IndexedSeq, IndexedSeq[Int]] = coll + * } + *}}} + * + * (Note that in practice the `IsIterable[Range]` instance is already provided by + * the standard library, and it is defined as an `IsSeq[Range]` instance) + */ +transparent trait IsIterable[Repr] extends IsIterableOnce[Repr] { + + /** The type returned by transformation operations that preserve the same elements + * type (e.g. `filter`, `take`). + * + * In practice, this type is often `Repr` itself, excepted in the case + * of `SeqView[A]` (and other `View[A]` subclasses), where it is “only” `View[A]`. + */ + type C + + @deprecated("'conversion' is now a method named 'apply'", "2.13.0") + override val conversion: Repr => IterableOps[A, Iterable, C] = apply(_) + + /** A conversion from the type `Repr` to `IterableOps[A, Iterable, C]` */ + def apply(coll: Repr): IterableOps[A, Iterable, C] + +} + +object IsIterable extends IsIterableLowPriority { + + // Straightforward case: IterableOps subclasses + implicit def iterableOpsIsIterable[A0, CC0[X] <: IterableOps[X, Iterable, CC0[X]]]: IsIterable[CC0[A0]] { type A = A0; type C = CC0[A0] } = + new IsIterable[CC0[A0]] { + type A = A0 + type C = CC0[A0] + def apply(coll: CC0[A]): IterableOps[A, Iterable, C] = coll + } + + // The `BitSet` type can not be unified with the `CC0` parameter of + // the above definition because it does not take a type parameter. + // Hence the need for a separate case: + implicit def bitSetOpsIsIterable[C0 <: BitSet with BitSetOps[C0]]: IsIterable[C0] { type A = Int; type C = C0 } = + new IsIterable[C0] { + type A = Int + type C = C0 + def apply(coll: C0): IterableOps[Int, Iterable, C0] = coll + } + +} + +transparent trait IsIterableLowPriority { + + // Makes `IsSeq` instances visible in `IsIterable` companion + implicit def isSeqLikeIsIterable[Repr](implicit + isSeqLike: IsSeq[Repr] + ): IsIterable[Repr] { type A = isSeqLike.A; type C = isSeqLike.C } = isSeqLike + + // Makes `IsMap` instances visible in `IsIterable` companion + implicit def isMapLikeIsIterable[Repr](implicit + isMapLike: IsMap[Repr] + ): IsIterable[Repr] { type A = isMapLike.A; type C = isMapLike.C } = isMapLike + +} diff --git a/library/src/scala/collection/generic/IsIterableOnce.scala b/library/src/scala/collection/generic/IsIterableOnce.scala new file mode 100644 index 000000000000..a5c2c4889722 --- /dev/null +++ b/library/src/scala/collection/generic/IsIterableOnce.scala @@ -0,0 +1,73 @@ +/* + * 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 collection +package generic + +import scala.language.`2.13` + +/** Type class witnessing that a collection representation type `Repr` has + * elements of type `A` and has a conversion to `IterableOnce[A]`. + * + * This type enables simple enrichment of `IterableOnce`s with extension + * methods which can make full use of the mechanics of the Scala collections + * framework in their implementation. + * + * Example usage, + * {{{ + * class FilterMapImpl[Repr, I <: IsIterableOnce[Repr]](coll: Repr, it: I) { + * final def filterMap[B, That](f: it.A => Option[B])(implicit bf: BuildFrom[Repr, B, That]): That = { + * val b = bf.newBuilder(coll) + * for(e <- it(coll).iterator) f(e) foreach (b +=) + * b.result() + * } + * } + * implicit def filterMap[Repr](coll: Repr)(implicit it: IsIterableOnce[Repr]): FilterMapImpl[Repr, it.type] = + * new FilterMapImpl(coll, it) + * + * List(1, 2, 3, 4, 5) filterMap (i => if(i % 2 == 0) Some(i) else None) + * // == List(2, 4) + * }}} + */ +transparent trait IsIterableOnce[Repr] { + + /** The type of elements we can traverse over (e.g. `Int`). */ + type A + + @deprecated("'conversion' is now a method named 'apply'", "2.13.0") + val conversion: Repr => IterableOnce[A] = apply(_) + + /** A conversion from the representation type `Repr` to a `IterableOnce[A]`. */ + def apply(coll: Repr): IterableOnce[A] + +} + +object IsIterableOnce extends IsIterableOnceLowPriority { + + // Straightforward case: IterableOnce subclasses + implicit def iterableOnceIsIterableOnce[CC0[A] <: IterableOnce[A], A0]: IsIterableOnce[CC0[A0]] { type A = A0 } = + new IsIterableOnce[CC0[A0]] { + type A = A0 + def apply(coll: CC0[A0]): IterableOnce[A0] = coll + } + +} + +transparent trait IsIterableOnceLowPriority { + + // Makes `IsIterable` instance visible in `IsIterableOnce` companion + implicit def isIterableLikeIsIterableOnce[Repr](implicit + isIterableLike: IsIterable[Repr] + ): IsIterableOnce[Repr] { type A = isIterableLike.A } = isIterableLike + +} diff --git a/library/src/scala/collection/generic/IsMap.scala b/library/src/scala/collection/generic/IsMap.scala new file mode 100644 index 000000000000..47eac5587f11 --- /dev/null +++ b/library/src/scala/collection/generic/IsMap.scala @@ -0,0 +1,116 @@ +/* + * 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.collection +package generic + +import scala.language.`2.13` +import IsMap.Tupled +import scala.collection.immutable.{IntMap, LongMap} + +/** + * Type class witnessing that a collection type `Repr` + * has keys of type `K`, values of type `V` and has a conversion to + * `MapOps[K, V, Iterable, C]`, for some types `K`, `V` and `C`. + * + * This type enables simple enrichment of `Map`s with extension methods. + * + * @see [[scala.collection.generic.IsIterable]] + * @tparam Repr Collection type (e.g. `Map[Int, String]`) + */ +transparent trait IsMap[Repr] extends IsIterable[Repr] { + + /** The type of keys */ + type K + + /** The type of values */ + type V + + type A = (K, V) + + /** A conversion from the type `Repr` to `MapOps[K, V, Iterable, C]` + * + * @note The third type parameter of the returned `MapOps` value is + * still `Iterable` (and not `Map`) because `MapView[K, V]` only + * extends `MapOps[K, V, View, View[A]]`. + */ + override def apply(c: Repr): MapOps[K, V, Tupled[Iterable]#Ap, C] + +} + +object IsMap { + + /** Convenient type level function that takes a unary type constructor `F[_]` + * and returns a binary type constructor that tuples its parameters and passes + * them to `F`. + * + * `Tupled[F]#Ap` is equivalent to `({ type Ap[X, +Y] = F[(X, Y)] })#Ap`. + */ + type Tupled[F[+_]] = { type Ap[X, Y] = F[(X, Y)] } + + // Map collections + implicit def mapOpsIsMap[CC0[X, Y] <: MapOps[X, Y, Tupled[Iterable]#Ap, CC0[X, Y]], K0, V0]: IsMap[CC0[K0, V0]] { type K = K0; type V = V0; type C = CC0[K, V] } = + new IsMap[CC0[K0, V0]] { + type K = K0 + type V = V0 + type C = CC0[K0, V0] + def apply(c: CC0[K0, V0]): MapOps[K0, V0, Tupled[Iterable]#Ap, C] = c + } + + // MapView + implicit def mapViewIsMap[CC0[X, Y] <: MapView[X, Y], K0, V0]: IsMap[CC0[K0, V0]] { type K = K0; type V = V0; type C = View[(K0, V0)] } = + new IsMap[CC0[K0, V0]] { + type K = K0 + type V = V0 + type C = View[(K, V)] + def apply(c: CC0[K0, V0]): MapOps[K0, V0, Tupled[Iterable]#Ap, View[(K0, V0)]] = c + } + + // AnyRefMap has stricter bounds than the ones used by the mapOpsIsMap definition + @deprecated("AnyRefMap is deprecated", "2.13.16") + implicit def anyRefMapIsMap[K0 <: AnyRef, V0]: IsMap[mutable.AnyRefMap[K0, V0]] { type K = K0; type V = V0; type C = mutable.AnyRefMap[K0, V0] } = + new IsMap[mutable.AnyRefMap[K0, V0]] { + type K = K0 + type V = V0 + type C = mutable.AnyRefMap[K0, V0] + def apply(c: mutable.AnyRefMap[K0, V0]): MapOps[K0, V0, Tupled[Iterable]#Ap, mutable.AnyRefMap[K0, V0]] = c + } + + // IntMap takes one type parameter only whereas mapOpsIsMap uses a parameter CC0 with two type parameters + implicit def intMapIsMap[V0]: IsMap[IntMap[V0]] { type K = Int; type V = V0; type C = IntMap[V0] } = + new IsMap[IntMap[V0]] { + type K = Int + type V = V0 + type C = IntMap[V0] + def apply(c: IntMap[V0]): MapOps[Int, V0, Tupled[Iterable]#Ap, IntMap[V0]] = c + } + + // LongMap is in a similar situation as IntMap + implicit def longMapIsMap[V0]: IsMap[LongMap[V0]] { type K = Long; type V = V0; type C = LongMap[V0] } = + new IsMap[LongMap[V0]] { + type K = Long + type V = V0 + type C = LongMap[V0] + def apply(c: LongMap[V0]): MapOps[Long, V0, Tupled[Iterable]#Ap, LongMap[V0]] = c + } + + // mutable.LongMap is in a similar situation as LongMap and IntMap + implicit def mutableLongMapIsMap[V0]: IsMap[mutable.LongMap[V0]] { type K = Long; type V = V0; type C = mutable.LongMap[V0] } = + new IsMap[mutable.LongMap[V0]] { + type K = Long + type V = V0 + type C = mutable.LongMap[V0] + def apply(c: mutable.LongMap[V0]): MapOps[Long, V0, Tupled[Iterable]#Ap, mutable.LongMap[V0]] = c + } + + +} diff --git a/library/src/scala/collection/generic/IsSeq.scala b/library/src/scala/collection/generic/IsSeq.scala new file mode 100644 index 000000000000..2707ce988e34 --- /dev/null +++ b/library/src/scala/collection/generic/IsSeq.scala @@ -0,0 +1,115 @@ +/* + * 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.collection +package generic + +import scala.language.`2.13` +import scala.reflect.ClassTag + +/** Type class witnessing that a collection representation type `Repr` has + * elements of type `A` and has a conversion to `SeqOps[A, Iterable, C]`, for + * some types `A` and `C`. + * + * This type enables simple enrichment of `Seq`s with extension methods which + * can make full use of the mechanics of the Scala collections framework in + * their implementation. + * + * @see [[scala.collection.generic.IsIterable]] + */ +transparent trait IsSeq[Repr] extends IsIterable[Repr] { + + @deprecated("'conversion' is now a method named 'apply'", "2.13.0") + override val conversion: Repr => SeqOps[A, Iterable, C] = apply(_) + + /** A conversion from the type `Repr` to `SeqOps[A, Iterable, C]` + * + * @note The second type parameter of the returned `SeqOps` value is + * still `Iterable` (and not `Seq`) because `SeqView[A]` only + * extends `SeqOps[A, View, View[A]]`. + */ + def apply(coll: Repr): SeqOps[A, Iterable, C] +} + +object IsSeq { + + private val seqOpsIsSeqVal: IsSeq[Seq[Any]] = + new IsSeq[Seq[Any]] { + type A = Any + type C = Any + def apply(coll: Seq[Any]): SeqOps[Any, Iterable, Any] = coll + } + + implicit def seqOpsIsSeq[CC0[X] <: SeqOps[X, Iterable, CC0[X]], A0]: IsSeq[CC0[A0]] { type A = A0; type C = CC0[A0] } = + seqOpsIsSeqVal.asInstanceOf[IsSeq[CC0[A0]] { type A = A0; type C = CC0[A0] }] + + implicit def seqViewIsSeq[CC0[X] <: SeqView[X], A0]: IsSeq[CC0[A0]] { type A = A0; type C = View[A0] } = + new IsSeq[CC0[A0]] { + type A = A0 + type C = View[A] + def apply(coll: CC0[A0]): SeqOps[A0, View, View[A0]] = coll + } + + implicit val stringIsSeq: IsSeq[String] { type A = Char; type C = String } = + new IsSeq[String] { + type A = Char + type C = String + def apply(s: String): SeqOps[Char, immutable.IndexedSeq, String] = + new SeqOps[Char, immutable.ArraySeq, String] { + def length: Int = s.length + def apply(i: Int): Char = s.charAt(i) + def toIterable: Iterable[Char] = new immutable.WrappedString(s) + protected[this] def coll: String = s + protected[this] def fromSpecific(coll: IterableOnce[Char]): String = coll.iterator.mkString + def iterableFactory: IterableFactory[immutable.ArraySeq] = immutable.ArraySeq.untagged + override def empty: String = "" + protected[this] def newSpecificBuilder: mutable.Builder[Char, String] = new StringBuilder + def iterator: Iterator[Char] = s.iterator + } + } + + implicit val stringViewIsSeq: IsSeq[StringView] { type A = Char; type C = View[Char] } = + new IsSeq[StringView] { + type A = Char + type C = View[Char] + def apply(coll: StringView): SeqOps[Char, View, View[Char]] = coll + } + + implicit def arrayIsSeq[A0 : ClassTag]: IsSeq[Array[A0]] { type A = A0; type C = Array[A0] } = + new IsSeq[Array[A0]] { + type A = A0 + type C = Array[A0] + def apply(a: Array[A0]): SeqOps[A0, Seq, Array[A0]] = + new SeqOps[A, mutable.ArraySeq, Array[A]] { + def apply(i: Int): A = a(i) + def length: Int = a.length + def toIterable: Iterable[A] = mutable.ArraySeq.make(a) + protected def coll: Array[A] = a + protected def fromSpecific(coll: IterableOnce[A]): Array[A] = Array.from(coll) + def iterableFactory: IterableFactory[mutable.ArraySeq] = mutable.ArraySeq.untagged + override def empty: Array[A] = Array.empty[A] + protected def newSpecificBuilder: mutable.Builder[A, Array[A]] = Array.newBuilder + def iterator: Iterator[A] = a.iterator + } + } + + // `Range` can not be unified with the `CC0` parameter of the + // `seqOpsIsSeq` definition because it does not take a type parameter. + // Hence the need for a separate case: + implicit def rangeIsSeq[C0 <: Range]: IsSeq[C0] { type A = Int; type C = immutable.IndexedSeq[Int] } = + new IsSeq[C0] { + type A = Int + type C = immutable.IndexedSeq[Int] + def apply(coll: C0): SeqOps[Int, Seq, immutable.IndexedSeq[Int]] = coll + } + +} diff --git a/library/src/scala/collection/generic/Subtractable.scala b/library/src/scala/collection/generic/Subtractable.scala new file mode 100644 index 000000000000..2e06819b6832 --- /dev/null +++ b/library/src/scala/collection/generic/Subtractable.scala @@ -0,0 +1,64 @@ +/* + * 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 collection +package generic + +import scala.language.`2.13` + +/** This trait represents collection-like objects that can be reduced + * using a '+' operator. It defines variants of `-` and `--` + * as convenience methods in terms of single-element removal `-`. + * + * @tparam A the type of the elements of the $coll. + * @tparam Repr the type of the $coll itself + * @define coll collection + * @define Coll Subtractable + */ +@deprecated("Subtractable is deprecated. This is now implemented as part of SetOps, MapOps, etc.", "2.13.0") +trait Subtractable[A, +Repr <: Subtractable[A, Repr]] { self => + + /** The representation object of type `Repr` which contains the collection's elements + */ + protected def repr: Repr + + /** Creates a new $coll from this $coll with an element removed. + * @param elem the element to remove + * @return a new collection that contains all elements of the current $coll + * except one less occurrence of `elem`. + */ + def -(elem: A): Repr + + /** Creates a new $coll from this $coll with some elements removed. + * + * This method takes two or more elements to be removed. Another overloaded + * variant of this method handles the case where a single element is + * removed. + * @param elem1 the first element to remove. + * @param elem2 the second element to remove. + * @param elems the remaining elements to remove. + * @return a new $coll that contains all elements of the current $coll + * except one less occurrence of each of the given elements. + */ + def -(elem1: A, elem2: A, elems: A*): Repr = + this - elem1 - elem2 -- elems + + /** Creates a new $coll from this $coll by removing all elements of another + * collection. + * + * @param xs the collection containing the removed elements. + * @return a new $coll that contains all elements of the current $coll + * except one less occurrence of each of the elements of `elems`. + */ + def --(xs: IterableOnce[A]): Repr = (repr /: xs.iterator) (_ - _) +} diff --git a/library/src/scala/collection/generic/package.scala b/library/src/scala/collection/generic/package.scala new file mode 100644 index 000000000000..0dc855d81d54 --- /dev/null +++ b/library/src/scala/collection/generic/package.scala @@ -0,0 +1,35 @@ +/* + * 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.collection + +import scala.language.`2.13` + +package object generic { + @deprecated("Clearable was moved from collection.generic to collection.mutable", "2.13.0") + type Clearable = scala.collection.mutable.Clearable + + @deprecated("Use scala.collection.BuildFrom instead", "2.13.0") + type CanBuildFrom[-From, -A, +C] = scala.collection.BuildFrom[From, A, C] + + @deprecated("Growable was moved from collection.generic to collection.mutable", "2.13.0") + type Growable[-A] = scala.collection.mutable.Growable[A] + + @deprecated("Shrinkable was moved from collection.generic to collection.mutable", "2.13.0") + type Shrinkable[-A] = scala.collection.mutable.Shrinkable[A] + + @deprecated("Use IsIterable instead", "2.13.0") + type IsTraversableLike[Repr] = IsIterable[Repr] + + @deprecated("Use IsIterableOnce instead", "2.13.0") + type IsTraversableOnce[Repr] = IsIterableOnce[Repr] +} diff --git a/library/src/scala/collection/immutable/ArraySeq.scala b/library/src/scala/collection/immutable/ArraySeq.scala new file mode 100644 index 000000000000..9cd2700779fb --- /dev/null +++ b/library/src/scala/collection/immutable/ArraySeq.scala @@ -0,0 +1,695 @@ +/* + * 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.collection +package immutable + +import scala.language.`2.13` +import java.util.Arrays + +import scala.annotation.unchecked.uncheckedVariance +import scala.collection.Stepper.EfficientSplit +import scala.collection.mutable.{ArrayBuffer, ArrayBuilder, Builder, ArraySeq => MutableArraySeq} +import scala.collection.convert.impl._ +import scala.reflect.ClassTag +import scala.runtime.ScalaRunTime +import scala.util.Sorting +import scala.util.hashing.MurmurHash3 + +/** + * An immutable array. + * + * Supports efficient indexed access and has a small memory footprint. + * + * @define coll immutable array + * @define Coll `ArraySeq` + */ +sealed abstract class ArraySeq[+A] + extends AbstractSeq[A] + with IndexedSeq[A] + with IndexedSeqOps[A, ArraySeq, ArraySeq[A]] + with StrictOptimizedSeqOps[A, ArraySeq, ArraySeq[A]] + with EvidenceIterableFactoryDefaults[A, ArraySeq, ClassTag] + with Serializable { + + /** The tag of the element type. This does not have to be equal to the element type of this ArraySeq. A primitive + * ArraySeq can be backed by an array of boxed values and a reference ArraySeq can be backed by an array of a supertype + * or subtype of the element type. */ + protected def elemTag: ClassTag[_] + + override def iterableFactory: SeqFactory[ArraySeq] = ArraySeq.untagged + + /** The wrapped mutable `Array` that backs this `ArraySeq`. Any changes to this array will break + * the expected immutability. Its element type does not have to be equal to the element type of this ArraySeq. + * A primitive ArraySeq can be backed by an array of boxed values and a reference ArraySeq can be backed by an + * array of a supertype or subtype of the element type. */ + def unsafeArray: Array[_] + + protected def evidenceIterableFactory: ArraySeq.type = ArraySeq + protected def iterableEvidence: ClassTag[A @uncheckedVariance] = elemTag.asInstanceOf[ClassTag[A]] + + def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit + + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): A + + override def updated[B >: A](index: Int, elem: B): ArraySeq[B] = { + val dest = new Array[Any](length) + Array.copy(unsafeArray, 0, dest, 0, length) + dest(index) = elem + ArraySeq.unsafeWrapArray(dest).asInstanceOf[ArraySeq[B]] + } + + override def map[B](f: A => B): ArraySeq[B] = { + val a = new Array[Any](size) + var i = 0 + while (i < a.length){ + a(i) = f(apply(i)) + i += 1 + } + ArraySeq.unsafeWrapArray(a).asInstanceOf[ArraySeq[B]] + } + + override def prepended[B >: A](elem: B): ArraySeq[B] = + ArraySeq.unsafeWrapArray(unsafeArray.prepended[Any](elem)).asInstanceOf[ArraySeq[B]] + + override def appended[B >: A](elem: B): ArraySeq[B] = + ArraySeq.unsafeWrapArray(unsafeArray.appended[Any](elem)).asInstanceOf[ArraySeq[B]] + + /** Fast concatenation of two [[ArraySeq]]s. + * + * @return null if optimisation not possible. + */ + private def appendedAllArraySeq[B >: A](that: ArraySeq[B]): ArraySeq[B] = { + // Optimise concatenation of two ArraySeqs + // For ArraySeqs with sizes of [100, 1000, 10000] this is [3.5, 4.1, 5.2]x as fast + if (isEmpty) + that + else if (that.isEmpty) + this + else { + val thisIsObj = this.unsafeArray.isInstanceOf[Array[AnyRef]] + val thatIsObj = that.unsafeArray.isInstanceOf[Array[AnyRef]] + val mismatch = thisIsObj != thatIsObj + if (mismatch) + // Combining primatives and objects: abort + null + else if (thisIsObj) { + // A and B are objects + val ax = this.unsafeArray.asInstanceOf[Array[A]] + val ay = that.unsafeArray.asInstanceOf[Array[B]] + val len = ax.length + ay.length + val a = new Array[AnyRef](len) + System.arraycopy(ax, 0, a, 0, ax.length) + System.arraycopy(ay, 0, a, ax.length, ay.length) + ArraySeq.unsafeWrapArray(a).asInstanceOf[ArraySeq[B]] + } else { + // A is a primative and B = A. Use this instance's protected ClassTag. + val ax = this.unsafeArray.asInstanceOf[Array[A]] + val ay = that.unsafeArray.asInstanceOf[Array[A]] + val len = ax.length + ay.length + val a = iterableEvidence.newArray(len) + System.arraycopy(ax, 0, a, 0, ax.length) + System.arraycopy(ay, 0, a, ax.length, ay.length) + ArraySeq.unsafeWrapArray(a).asInstanceOf[ArraySeq[B]] + } + } + } + + override def appendedAll[B >: A](suffix: collection.IterableOnce[B]): ArraySeq[B] = { + def genericResult = { + val k = suffix.knownSize + if (k == 0) this + else { + val b = ArrayBuilder.make[Any] + if(k >= 0) b.sizeHint(k + unsafeArray.length) + b.addAll(unsafeArray) + b.addAll(suffix) + ArraySeq.unsafeWrapArray(b.result()).asInstanceOf[ArraySeq[B]] + } + } + + suffix match { + case that: ArraySeq[_] => + val result = appendedAllArraySeq(that.asInstanceOf[ArraySeq[B]]) + if (result == null) genericResult + else result + case _ => + genericResult + } + } + + override def prependedAll[B >: A](prefix: collection.IterableOnce[B]): ArraySeq[B] = { + def genericResult = { + val k = prefix.knownSize + if (k == 0) this + else { + val b = ArrayBuilder.make[Any] + if(k >= 0) b.sizeHint(k + unsafeArray.length) + b.addAll(prefix) + if(k < 0) b.sizeHint(b.length + unsafeArray.length) + b.addAll(unsafeArray) + ArraySeq.unsafeWrapArray(b.result()).asInstanceOf[ArraySeq[B]] + } + } + + prefix match { + case that: ArraySeq[_] => + val result = that.asInstanceOf[ArraySeq[B]].appendedAllArraySeq(this) + if (result == null) genericResult + else result + case _ => + genericResult + } + } + + override def zip[B](that: collection.IterableOnce[B]): ArraySeq[(A, B)] = + that match { + case bs: ArraySeq[B] => + ArraySeq.tabulate(length min bs.length) { i => + (apply(i), bs(i)) + } + case _ => + strictOptimizedZip[B, ArraySeq[(A, B)]](that, iterableFactory.newBuilder) + } + + override def take(n: Int): ArraySeq[A] = + if (unsafeArray.length <= n) + this + else + ArraySeq.unsafeWrapArray(new ArrayOps(unsafeArray).take(n)).asInstanceOf[ArraySeq[A]] + + override def takeRight(n: Int): ArraySeq[A] = + if (unsafeArray.length <= n) + this + else + ArraySeq.unsafeWrapArray(new ArrayOps(unsafeArray).takeRight(n)).asInstanceOf[ArraySeq[A]] + + override def drop(n: Int): ArraySeq[A] = + if (n <= 0) + this + else + ArraySeq.unsafeWrapArray(new ArrayOps(unsafeArray).drop(n)).asInstanceOf[ArraySeq[A]] + + override def dropRight(n: Int): ArraySeq[A] = + if (n <= 0) + this + else + ArraySeq.unsafeWrapArray(new ArrayOps(unsafeArray).dropRight(n)).asInstanceOf[ArraySeq[A]] + + override def slice(from: Int, until: Int): ArraySeq[A] = + if (from <= 0 && unsafeArray.length <= until) + this + else + ArraySeq.unsafeWrapArray(new ArrayOps(unsafeArray).slice(from, until)).asInstanceOf[ArraySeq[A]] + + override def foldLeft[B](z: B)(f: (B, A) => B): B = { + // For ArraySeqs with sizes of [100, 1000, 10000] this is [1.3, 1.8, 1.8]x as fast + // as the same while-loop over this instead of unsafeArray. + val array = unsafeArray + var b = z + var i = 0 + while (i < array.length) { + val a = array(i).asInstanceOf[A] + b = f(b, a) + i += 1 + } + b + } + + override def foldRight[B](z: B)(f: (A, B) => B): B = { + // For ArraySeqs with sizes of [100, 1000, 10000] this is [1.6, 1.8, 2.7]x as fast + // as the same while-loop over this instead of unsafeArray. + val array = unsafeArray + var b = z + var i = array.length + while (i > 0) { + i -= 1 + val a = array(i).asInstanceOf[A] + b = f(a, b) + } + b + } + + override def tail: ArraySeq[A] = ArraySeq.unsafeWrapArray(new ArrayOps(unsafeArray).tail).asInstanceOf[ArraySeq[A]] + + override def reverse: ArraySeq[A] = ArraySeq.unsafeWrapArray(new ArrayOps(unsafeArray).reverse).asInstanceOf[ArraySeq[A]] + + override protected[this] def className = "ArraySeq" + + override def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): Int = { + val copied = IterableOnce.elemsToCopyToArray(length, xs.length, start, len) + if(copied > 0) { + Array.copy(unsafeArray, 0, xs, start, copied) + } + copied + } + + override protected final def applyPreferredMaxLength: Int = Int.MaxValue + + override def sorted[B >: A](implicit ord: Ordering[B]): ArraySeq[A] = + if(unsafeArray.length <= 1) this + else { + val a = Array.copyAs[AnyRef](unsafeArray, length)(ClassTag.AnyRef) + Arrays.sort(a, ord.asInstanceOf[Ordering[AnyRef]]) + new ArraySeq.ofRef[AnyRef](a).asInstanceOf[ArraySeq[A]] + } +} + +/** + * $factoryInfo + * @define coll immutable array + * @define Coll `ArraySeq` + */ +@SerialVersionUID(3L) +object ArraySeq extends StrictOptimizedClassTagSeqFactory[ArraySeq] { self => + val untagged: SeqFactory[ArraySeq] = new ClassTagSeqFactory.AnySeqDelegate(self) + + private[this] lazy val emptyImpl = new ArraySeq.ofRef[Nothing](new Array[Nothing](0)) + + def empty[A : ClassTag]: ArraySeq[A] = emptyImpl + + def from[A](it: scala.collection.IterableOnce[A])(implicit tag: ClassTag[A]): ArraySeq[A] = it match { + case as: ArraySeq[A] => as + case _ => unsafeWrapArray(Array.from[A](it)) + } + + def newBuilder[A : ClassTag]: Builder[A, ArraySeq[A]] = + ArrayBuffer.newBuilder[A].mapResult(b => unsafeWrapArray[A](b.toArray)) + + override def fill[A : ClassTag](n: Int)(elem: => A): ArraySeq[A] = tabulate(n)(_ => elem) + + override def tabulate[A : ClassTag](n: Int)(f: Int => A): ArraySeq[A] = { + val elements = Array.ofDim[A](scala.math.max(n, 0)) + var i = 0 + while (i < n) { + ScalaRunTime.array_update(elements, i, f(i)) + i = i + 1 + } + ArraySeq.unsafeWrapArray(elements) + } + + /** + * Wrap an existing `Array` into an `ArraySeq` of the proper primitive specialization type + * without copying. Any changes to wrapped array will break the expected immutability. + * + * Note that an array containing boxed primitives can be wrapped in an `ArraySeq` without + * copying. For example, `val a: Array[Any] = Array(1)` is an array of `Object` at runtime, + * containing `Integer`s. An `ArraySeq[Int]` can be obtained with a cast: + * `ArraySeq.unsafeWrapArray(a).asInstanceOf[ArraySeq[Int]]`. The values are still + * boxed, the resulting instance is an [[ArraySeq.ofRef]]. Writing + * `ArraySeq.unsafeWrapArray(a.asInstanceOf[Array[Int]])` does not work, it throws a + * `ClassCastException` at runtime. + */ + def unsafeWrapArray[T](x: Array[T]): ArraySeq[T] = ((x: @unchecked) match { + case null => null + case x: Array[AnyRef] => new ofRef[AnyRef](x) + case x: Array[Int] => new ofInt(x) + case x: Array[Double] => new ofDouble(x) + case x: Array[Long] => new ofLong(x) + case x: Array[Float] => new ofFloat(x) + case x: Array[Char] => new ofChar(x) + case x: Array[Byte] => new ofByte(x) + case x: Array[Short] => new ofShort(x) + case x: Array[Boolean] => new ofBoolean(x) + case x: Array[Unit] => new ofUnit(x) + }).asInstanceOf[ArraySeq[T]] + + @SerialVersionUID(3L) + final class ofRef[T <: AnyRef](val unsafeArray: Array[T]) extends ArraySeq[T] { + def elemTag: ClassTag[T] = ClassTag[T](unsafeArray.getClass.getComponentType) + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): T = unsafeArray(i) + override def hashCode = MurmurHash3.arraySeqHash(unsafeArray) + override def equals(that: Any): Boolean = that match { + case that: ofRef[_] => + Array.equals( + this.unsafeArray.asInstanceOf[Array[AnyRef]], + that.unsafeArray.asInstanceOf[Array[AnyRef]]) + case _ => super.equals(that) + } + override def sorted[B >: T](implicit ord: Ordering[B]): ArraySeq.ofRef[T] = { + if(unsafeArray.length <= 1) this + else { + val a = unsafeArray.clone() + Arrays.sort(a, ord.asInstanceOf[Ordering[T]]) + new ArraySeq.ofRef(a) + } + } + override def iterator: Iterator[T] = new ArrayOps.ArrayIterator[T](unsafeArray) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[T, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + new ObjectArrayStepper(unsafeArray, 0, unsafeArray.length) + else shape.parUnbox(new ObjectArrayStepper(unsafeArray, 0, unsafeArray.length).asInstanceOf[AnyStepper[T] with EfficientSplit]) + ).asInstanceOf[S with EfficientSplit] + } + + @SerialVersionUID(3L) + final class ofByte(val unsafeArray: Array[Byte]) extends ArraySeq[Byte] { + // Type erases to `ManifestFactory.ByteManifest`, but can't annotate that because it's not accessible + protected def elemTag: ClassTag.Byte.type = ClassTag.Byte + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Byte = unsafeArray(i) + override def hashCode = MurmurHash3.arraySeqHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofByte => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + override def sorted[B >: Byte](implicit ord: Ordering[B]): ArraySeq[Byte] = + if(length <= 1) this + else if(ord eq Ordering.Byte) { + val a = unsafeArray.clone() + Arrays.sort(a) + new ArraySeq.ofByte(a) + } else super.sorted[B] + override def iterator: Iterator[Byte] = new ArrayOps.ArrayIterator[Byte](unsafeArray) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Byte, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + AnyStepper.ofParIntStepper(new WidenedByteArrayStepper(unsafeArray, 0, unsafeArray.length)) + else new WidenedByteArrayStepper(unsafeArray, 0, unsafeArray.length) + ).asInstanceOf[S with EfficientSplit] + override def updated[B >: Byte](index: Int, elem: B): ArraySeq[B] = + elem match { + case b: Byte => new ArraySeq.ofByte(unsafeArray.updated(index, b)) + case _ => super.updated(index, elem) + } + override def appended[B >: Byte](elem: B): ArraySeq[B] = + elem match { + case b: Byte => new ArraySeq.ofByte(unsafeArray.appended(b)) + case _ => super.appended(elem) + } + override def prepended[B >: Byte](elem: B): ArraySeq[B] = + elem match { + case b: Byte => new ArraySeq.ofByte(unsafeArray.prepended(b)) + case _ => super.prepended(elem) + } + } + + @SerialVersionUID(3L) + final class ofShort(val unsafeArray: Array[Short]) extends ArraySeq[Short] { + // Type erases to `ManifestFactory.ShortManifest`, but can't annotate that because it's not accessible + protected def elemTag: ClassTag.Short.type = ClassTag.Short + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Short = unsafeArray(i) + override def hashCode = MurmurHash3.arraySeqHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofShort => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + override def sorted[B >: Short](implicit ord: Ordering[B]): ArraySeq[Short] = + if(length <= 1) this + else if(ord eq Ordering.Short) { + val a = unsafeArray.clone() + Arrays.sort(a) + new ArraySeq.ofShort(a) + } else super.sorted[B] + override def iterator: Iterator[Short] = new ArrayOps.ArrayIterator[Short](unsafeArray) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Short, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + AnyStepper.ofParIntStepper(new WidenedShortArrayStepper(unsafeArray, 0, unsafeArray.length)) + else new WidenedShortArrayStepper(unsafeArray, 0, unsafeArray.length) + ).asInstanceOf[S with EfficientSplit] + override def updated[B >: Short](index: Int, elem: B): ArraySeq[B] = + elem match { + case b: Short => new ArraySeq.ofShort(unsafeArray.updated(index, b)) + case _ => super.updated(index, elem) + } + override def appended[B >: Short](elem: B): ArraySeq[B] = + elem match { + case b: Short => new ArraySeq.ofShort(unsafeArray.appended(b)) + case _ => super.appended(elem) + } + override def prepended[B >: Short](elem: B): ArraySeq[B] = + elem match { + case b: Short => new ArraySeq.ofShort(unsafeArray.prepended(b)) + case _ => super.prepended(elem) + } + } + + @SerialVersionUID(3L) + final class ofChar(val unsafeArray: Array[Char]) extends ArraySeq[Char] { + // Type erases to `ManifestFactory.CharManifest`, but can't annotate that because it's not accessible + protected def elemTag: ClassTag.Char.type = ClassTag.Char + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Char = unsafeArray(i) + override def hashCode = MurmurHash3.arraySeqHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofChar => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + override def sorted[B >: Char](implicit ord: Ordering[B]): ArraySeq[Char] = + if(length <= 1) this + else if(ord eq Ordering.Char) { + val a = unsafeArray.clone() + Arrays.sort(a) + new ArraySeq.ofChar(a) + } else super.sorted[B] + override def iterator: Iterator[Char] = new ArrayOps.ArrayIterator[Char](unsafeArray) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Char, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + AnyStepper.ofParIntStepper(new WidenedCharArrayStepper(unsafeArray, 0, unsafeArray.length)) + else new WidenedCharArrayStepper(unsafeArray, 0, unsafeArray.length) + ).asInstanceOf[S with EfficientSplit] + override def updated[B >: Char](index: Int, elem: B): ArraySeq[B] = + elem match { + case b: Char => new ArraySeq.ofChar(unsafeArray.updated(index, b)) + case _ => super.updated(index, elem) + } + override def appended[B >: Char](elem: B): ArraySeq[B] = + elem match { + case b: Char => new ArraySeq.ofChar(unsafeArray.appended(b)) + case _ => super.appended(elem) + } + override def prepended[B >: Char](elem: B): ArraySeq[B] = + elem match { + case b: Char => new ArraySeq.ofChar(unsafeArray.prepended(b)) + case _ => super.prepended(elem) + } + + override def addString(sb: StringBuilder, start: String, sep: String, end: String): sb.type = + (new MutableArraySeq.ofChar(unsafeArray)).addString(sb, start, sep, end) + } + + @SerialVersionUID(3L) + final class ofInt(val unsafeArray: Array[Int]) extends ArraySeq[Int] { + // Type erases to `ManifestFactory.IntManifest`, but can't annotate that because it's not accessible + protected def elemTag: ClassTag.Int.type = ClassTag.Int + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Int = unsafeArray(i) + override def hashCode = MurmurHash3.arraySeqHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofInt => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + override def sorted[B >: Int](implicit ord: Ordering[B]): ArraySeq[Int] = + if(length <= 1) this + else if(ord eq Ordering.Int) { + val a = unsafeArray.clone() + Arrays.sort(a) + new ArraySeq.ofInt(a) + } else super.sorted[B] + override def iterator: Iterator[Int] = new ArrayOps.ArrayIterator[Int](unsafeArray) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Int, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + AnyStepper.ofParIntStepper(new IntArrayStepper(unsafeArray, 0, unsafeArray.length)) + else new IntArrayStepper(unsafeArray, 0, unsafeArray.length) + ).asInstanceOf[S with EfficientSplit] + override def updated[B >: Int](index: Int, elem: B): ArraySeq[B] = + elem match { + case b: Int => new ArraySeq.ofInt(unsafeArray.updated(index, b)) + case _ => super.updated(index, elem) + } + override def appended[B >: Int](elem: B): ArraySeq[B] = + elem match { + case b: Int => new ArraySeq.ofInt(unsafeArray.appended(b)) + case _ => super.appended(elem) + } + override def prepended[B >: Int](elem: B): ArraySeq[B] = + elem match { + case b: Int => new ArraySeq.ofInt(unsafeArray.prepended(b)) + case _ => super.prepended(elem) + } + } + + @SerialVersionUID(3L) + final class ofLong(val unsafeArray: Array[Long]) extends ArraySeq[Long] { + // Type erases to `ManifestFactory.LongManifest`, but can't annotate that because it's not accessible + protected def elemTag: ClassTag.Long.type = ClassTag.Long + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Long = unsafeArray(i) + override def hashCode = MurmurHash3.arraySeqHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofLong => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + override def sorted[B >: Long](implicit ord: Ordering[B]): ArraySeq[Long] = + if(length <= 1) this + else if(ord eq Ordering.Long) { + val a = unsafeArray.clone() + Arrays.sort(a) + new ArraySeq.ofLong(a) + } else super.sorted[B] + override def iterator: Iterator[Long] = new ArrayOps.ArrayIterator[Long](unsafeArray) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Long, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + AnyStepper.ofParLongStepper(new LongArrayStepper(unsafeArray, 0, unsafeArray.length)) + else new LongArrayStepper(unsafeArray, 0, unsafeArray.length) + ).asInstanceOf[S with EfficientSplit] + override def updated[B >: Long](index: Int, elem: B): ArraySeq[B] = + elem match { + case b: Long => new ArraySeq.ofLong(unsafeArray.updated(index, b)) + case _ => super.updated(index, elem) + } + override def appended[B >: Long](elem: B): ArraySeq[B] = + elem match { + case b: Long => new ArraySeq.ofLong(unsafeArray.appended(b)) + case _ => super.appended(elem) + } + override def prepended[B >: Long](elem: B): ArraySeq[B] = + elem match { + case b: Long => new ArraySeq.ofLong(unsafeArray.prepended(b)) + case _ => super.prepended(elem) + } + } + + @SerialVersionUID(3L) + final class ofFloat(val unsafeArray: Array[Float]) extends ArraySeq[Float] { + // Type erases to `ManifestFactory.FloatManifest`, but can't annotate that because it's not accessible + protected def elemTag: ClassTag.Float.type = ClassTag.Float + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Float = unsafeArray(i) + override def hashCode = MurmurHash3.arraySeqHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofFloat => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + override def iterator: Iterator[Float] = new ArrayOps.ArrayIterator[Float](unsafeArray) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Float, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + AnyStepper.ofParDoubleStepper(new WidenedFloatArrayStepper(unsafeArray, 0, unsafeArray.length)) + else new WidenedFloatArrayStepper(unsafeArray, 0, unsafeArray.length) + ).asInstanceOf[S with EfficientSplit] + override def updated[B >: Float](index: Int, elem: B): ArraySeq[B] = + elem match { + case b: Float => new ArraySeq.ofFloat(unsafeArray.updated(index, b)) + case _ => super.updated(index, elem) + } + override def appended[B >: Float](elem: B): ArraySeq[B] = + elem match { + case b: Float => new ArraySeq.ofFloat(unsafeArray.appended(b)) + case _ => super.appended(elem) + } + override def prepended[B >: Float](elem: B): ArraySeq[B] = + elem match { + case b: Float => new ArraySeq.ofFloat(unsafeArray.prepended(b)) + case _ => super.prepended(elem) + } + } + + @SerialVersionUID(3L) + final class ofDouble(val unsafeArray: Array[Double]) extends ArraySeq[Double] { + // Type erases to `ManifestFactory.DoubleManifest`, but can't annotate that because it's not accessible + protected def elemTag: ClassTag.Double.type = ClassTag.Double + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Double = unsafeArray(i) + override def hashCode = MurmurHash3.arraySeqHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofDouble => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + override def iterator: Iterator[Double] = new ArrayOps.ArrayIterator[Double](unsafeArray) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Double, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + AnyStepper.ofParDoubleStepper(new DoubleArrayStepper(unsafeArray, 0, unsafeArray.length)) + else new DoubleArrayStepper(unsafeArray, 0, unsafeArray.length) + ).asInstanceOf[S with EfficientSplit] + override def updated[B >: Double](index: Int, elem: B): ArraySeq[B] = + elem match { + case b: Double => new ArraySeq.ofDouble(unsafeArray.updated(index, b)) + case _ => super.updated(index, elem) + } + override def appended[B >: Double](elem: B): ArraySeq[B] = + elem match { + case b: Double => new ArraySeq.ofDouble(unsafeArray.appended(b)) + case _ => super.appended(elem) + } + override def prepended[B >: Double](elem: B): ArraySeq[B] = + elem match { + case b: Double => new ArraySeq.ofDouble(unsafeArray.prepended(b)) + case _ => super.prepended(elem) + } + } + + @SerialVersionUID(3L) + final class ofBoolean(val unsafeArray: Array[Boolean]) extends ArraySeq[Boolean] { + // Type erases to `ManifestFactory.BooleanManifest`, but can't annotate that because it's not accessible + protected def elemTag: ClassTag.Boolean.type = ClassTag.Boolean + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Boolean = unsafeArray(i) + override def hashCode = MurmurHash3.arraySeqHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofBoolean => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + override def sorted[B >: Boolean](implicit ord: Ordering[B]): ArraySeq[Boolean] = + if(length <= 1) this + else if(ord eq Ordering.Boolean) { + val a = unsafeArray.clone() + Sorting.stableSort(a) + new ArraySeq.ofBoolean(a) + } else super.sorted[B] + override def iterator: Iterator[Boolean] = new ArrayOps.ArrayIterator[Boolean](unsafeArray) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Boolean, S]): S with EfficientSplit = + new BoxedBooleanArrayStepper(unsafeArray, 0, unsafeArray.length).asInstanceOf[S with EfficientSplit] + override def updated[B >: Boolean](index: Int, elem: B): ArraySeq[B] = + elem match { + case b: Boolean => new ArraySeq.ofBoolean(unsafeArray.updated(index, b)) + case _ => super.updated(index, elem) + } + override def appended[B >: Boolean](elem: B): ArraySeq[B] = + elem match { + case b: Boolean => new ArraySeq.ofBoolean(unsafeArray.appended(b)) + case _ => super.appended(elem) + } + override def prepended[B >: Boolean](elem: B): ArraySeq[B] = + elem match { + case b: Boolean => new ArraySeq.ofBoolean(unsafeArray.prepended(b)) + case _ => super.prepended(elem) + } + } + + @SerialVersionUID(3L) + final class ofUnit(val unsafeArray: Array[Unit]) extends ArraySeq[Unit] { + // Type erases to `ManifestFactory.UnitManifest`, but can't annotate that because it's not accessible + protected def elemTag: ClassTag.Unit.type = ClassTag.Unit + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Unit = unsafeArray(i) + override def hashCode = MurmurHash3.arraySeqHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofUnit => unsafeArray.length == that.unsafeArray.length + case _ => super.equals(that) + } + override def iterator: Iterator[Unit] = new ArrayOps.ArrayIterator[Unit](unsafeArray) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Unit, S]): S with EfficientSplit = + new ObjectArrayStepper[AnyRef](unsafeArray.asInstanceOf[Array[AnyRef]], 0, unsafeArray.length).asInstanceOf[S with EfficientSplit] + } +} diff --git a/library/src/scala/collection/immutable/BitSet.scala b/library/src/scala/collection/immutable/BitSet.scala new file mode 100644 index 000000000000..c454727fe4b9 --- /dev/null +++ b/library/src/scala/collection/immutable/BitSet.scala @@ -0,0 +1,376 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import BitSetOps.{LogWL, updateArray} +import mutable.Builder +import scala.annotation.{implicitNotFound, nowarn} + +/** A class for immutable bitsets. + * $bitsetinfo + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html#immutable-bitsets "Scala's Collection Library overview"]] + * section on `Immutable BitSets` for more information. + * + * @define Coll `immutable.BitSet` + * @define coll immutable bitset + */ +sealed abstract class BitSet + extends AbstractSet[Int] + with SortedSet[Int] + with SortedSetOps[Int, SortedSet, BitSet] + with StrictOptimizedSortedSetOps[Int, SortedSet, BitSet] + with collection.BitSet + with collection.BitSetOps[BitSet] + with Serializable { + + override def unsorted: Set[Int] = this + + override protected def fromSpecific(coll: IterableOnce[Int]): BitSet = bitSetFactory.fromSpecific(coll) + override protected def newSpecificBuilder: Builder[Int, BitSet] = bitSetFactory.newBuilder + override def empty: BitSet = bitSetFactory.empty + + def bitSetFactory: BitSet.type = BitSet + + protected[collection] def fromBitMaskNoCopy(elems: Array[Long]): BitSet = BitSet.fromBitMaskNoCopy(elems) + + def incl(elem: Int): BitSet = { + require(elem >= 0, "bitset element must be >= 0") + if (contains(elem)) this + else { + val idx = elem >> LogWL + updateWord(idx, word(idx) | (1L << elem)) + } + } + + def excl(elem: Int): BitSet = { + require(elem >= 0, "bitset element must be >= 0") + if (contains(elem)) { + val idx = elem >> LogWL + updateWord(idx, word(idx) & ~(1L << elem)) + } else this + } + + /** Update word at index `idx`; enlarge set if `idx` outside range of set. + */ + protected def updateWord(idx: Int, w: Long): BitSet + + override def map(f: Int => Int): BitSet = strictOptimizedMap(newSpecificBuilder, f) + override def map[B](f: Int => B)(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = + super[StrictOptimizedSortedSetOps].map(f) + + override def flatMap(f: Int => IterableOnce[Int]): BitSet = strictOptimizedFlatMap(newSpecificBuilder, f) + override def flatMap[B](f: Int => IterableOnce[B])(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = + super[StrictOptimizedSortedSetOps].flatMap(f) + + override def collect(pf: PartialFunction[Int, Int]): BitSet = strictOptimizedCollect(newSpecificBuilder, pf) + override def collect[B](pf: scala.PartialFunction[Int, B])(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = + super[StrictOptimizedSortedSetOps].collect(pf) + + // necessary for disambiguation + override def zip[B](that: scala.IterableOnce[B])(implicit @implicitNotFound(collection.BitSet.zipOrdMsg) ev: Ordering[(Int, B)]): SortedSet[(Int, B)] = + super.zip(that) + + protected[this] def writeReplace(): AnyRef = new BitSet.SerializationProxy(this) +} + +/** + * $factoryInfo + * @define Coll `immutable.BitSet` + * @define coll immutable bitset + */ +@nowarn("cat=deprecation&msg=Implementation classes of BitSet should not be accessed directly") +@SerialVersionUID(3L) +object BitSet extends SpecificIterableFactory[Int, BitSet] { + + def fromSpecific(it: scala.collection.IterableOnce[Int]): BitSet = + it match { + case bs: BitSet => bs + case _ => (newBuilder ++= it).result() + } + + final val empty: BitSet = new BitSet1(0L) + + def newBuilder: Builder[Int, BitSet] = + mutable.BitSet.newBuilder.mapResult(bs => fromBitMaskNoCopy(bs.elems)) + + private def createSmall(a: Long, b: Long): BitSet = if (b == 0L) new BitSet1(a) else new BitSet2(a, b) + + /** A bitset containing all the bits in an array */ + def fromBitMask(elems: Array[Long]): BitSet = { + val len = elems.length + if (len == 0) empty + else if (len == 1) new BitSet1(elems(0)) + else if (len == 2) createSmall(elems(0), elems(1)) + else { + val a = java.util.Arrays.copyOf(elems, len) + new BitSetN(a) + } + } + + /** A bitset containing all the bits in an array, wrapping the existing + * array without copying. + */ + def fromBitMaskNoCopy(elems: Array[Long]): BitSet = { + val len = elems.length + if (len == 0) empty + else if (len == 1) new BitSet1(elems(0)) + else if (len == 2) createSmall(elems(0), elems(1)) + else new BitSetN(elems) + } + + @deprecated("Implementation classes of BitSet should not be accessed directly", "2.13.0") + class BitSet1(val elems: Long) extends BitSet { + protected[collection] def nwords = 1 + protected[collection] def word(idx: Int) = if (idx == 0) elems else 0L + protected[collection] def updateWord(idx: Int, w: Long): BitSet = + if (idx == 0) new BitSet1(w) + else if (idx == 1) createSmall(elems, w) + else this.fromBitMaskNoCopy(updateArray(Array(elems), idx, w)) + + + override def diff(other: collection.Set[Int]): BitSet = other match { + case bs: collection.BitSet => bs.nwords match { + case 0 => this + case _ => + val newElems = elems & ~bs.word(0) + if (newElems == 0L) this.empty else new BitSet1(newElems) + } + case _ => super.diff(other) + } + + override def filterImpl(pred: Int => Boolean, isFlipped: Boolean): BitSet = { + val _elems = BitSetOps.computeWordForFilter(pred, isFlipped, elems, 0) + if (_elems == 0L) this.empty else new BitSet1(_elems) + } + } + + @deprecated("Implementation classes of BitSet should not be accessed directly", "2.13.0") + class BitSet2(val elems0: Long, val elems1: Long) extends BitSet { + protected[collection] def nwords = 2 + protected[collection] def word(idx: Int) = if (idx == 0) elems0 else if (idx == 1) elems1 else 0L + protected[collection] def updateWord(idx: Int, w: Long): BitSet = + if (idx == 0) new BitSet2(w, elems1) + else if (idx == 1) createSmall(elems0, w) + else this.fromBitMaskNoCopy(updateArray(Array(elems0, elems1), idx, w)) + + + override def diff(other: collection.Set[Int]): BitSet = other match { + case bs: collection.BitSet => bs.nwords match { + case 0 => this + case 1 => + new BitSet2(elems0 & ~bs.word(0), elems1) + case _ => + val _elems0 = elems0 & ~bs.word(0) + val _elems1 = elems1 & ~bs.word(1) + + if (_elems1 == 0L) { + if (_elems0 == 0L) { + this.empty + } else { + new BitSet1(_elems0) + } + } else { + new BitSet2(_elems0, _elems1) + } + } + case _ => super.diff(other) + } + + override def filterImpl(pred: Int => Boolean, isFlipped: Boolean): BitSet = { + val _elems0 = BitSetOps.computeWordForFilter(pred, isFlipped, elems0, 0) + val _elems1 = BitSetOps.computeWordForFilter(pred, isFlipped, elems1, 1) + + if (_elems1 == 0L) { + if (_elems0 == 0L) { + this.empty + } + else new BitSet1(_elems0) + } + else new BitSet2(_elems0, _elems1) + } + } + + @deprecated("Implementation classes of BitSet should not be accessed directly", "2.13.0") + class BitSetN(val elems: Array[Long]) extends BitSet { + protected[collection] def nwords = elems.length + + protected[collection] def word(idx: Int) = if (idx < nwords) elems(idx) else 0L + + protected[collection] def updateWord(idx: Int, w: Long): BitSet = this.fromBitMaskNoCopy(updateArray(elems, idx, w)) + + override def diff(that: collection.Set[Int]): BitSet = that match { + case bs: collection.BitSet => + /* + * Algorithm: + * + * We iterate, word-by-word, backwards from the shortest of the two bitsets (this, or bs) i.e. the one with + * the fewer words. Two extra concerns for optimization are described below. + * + * Array Shrinking: + * If `this` is not longer than `bs`, then since we must iterate through the full array of words, + * we can track the new highest index word which is non-zero, at little additional cost. At the end, the new + * Array[Long] allocated for the returned BitSet will only be of size `maxNonZeroIndex + 1` + * + * Tracking Changes: + * If the two sets are disjoint, then we can return `this`. Therefor, until at least one change is detected, + * we check each word for if it has changed from its corresponding word in `this`. Once a single change is + * detected, we stop checking because the cost of the new Array must be paid anyways. + */ + + val bsnwords = bs.nwords + val thisnwords = nwords + if (bsnwords >= thisnwords) { + // here, we may have opportunity to shrink the size of the array + // so, track the highest index which is non-zero. That ( + 1 ) will be our new array length + var i = thisnwords - 1 + var currentWord = 0L + // if there are never any changes, we can return `this` at the end + var anyChanges = false + while (i >= 0 && currentWord == 0L) { + val oldWord = word(i) + currentWord = oldWord & ~bs.word(i) + anyChanges ||= currentWord != oldWord + i -= 1 + } + i match { + case -1 => + if (anyChanges) { + if (currentWord == 0) { + this.empty + } else { + new BitSet1(currentWord) + } + } else { + this + } + case 0 => + val oldFirstWord = word(0) + val firstWord = oldFirstWord & ~bs.word(0) + anyChanges ||= firstWord != oldFirstWord + if (anyChanges) { + new BitSet2(firstWord, currentWord) + } else { + this + } + case _ => + val minimumNonZeroIndex: Int = i + 1 + while (!anyChanges && i >= 0) { + val oldWord = word(i) + currentWord = oldWord & ~bs.word(i) + anyChanges ||= currentWord != oldWord + i -= 1 + } + if (anyChanges) { + val newArray = elems.take(minimumNonZeroIndex + 1) + newArray(i + 1) = currentWord + while (i >= 0) { + newArray(i) = word(i) & ~bs.word(i) + i -= 1 + } + new BitSetN(newArray) + } else { + this + } + } + } else { + var i = bsnwords - 1 + var anyChanges = false + var currentWord = 0L + while (i >= 0 && !anyChanges) { + val oldWord = word(i) + currentWord = oldWord & ~bs.word(i) + anyChanges ||= currentWord != oldWord + i -= 1 + } + if (anyChanges) { + val newElems = elems.clone() + newElems(i + 1) = currentWord + while (i >= 0) { + newElems(i) = word(i) & ~bs.word(i) + i -= 1 + } + this.fromBitMaskNoCopy(newElems) + } else { + this + } + } + case _ => super.diff(that) + } + + + override def filterImpl(pred: Int => Boolean, isFlipped: Boolean): BitSet = { + // here, we may have opportunity to shrink the size of the array + // so, track the highest index which is non-zero. That ( + 1 ) will be our new array length + var i = nwords - 1 + var currentWord = 0L + // if there are never any changes, we can return `this` at the end + var anyChanges = false + while (i >= 0 && currentWord == 0L) { + val oldWord = word(i) + currentWord = BitSetOps.computeWordForFilter(pred, isFlipped, oldWord, i) + anyChanges ||= currentWord != oldWord + i -= 1 + } + i match { + case -1 => + if (anyChanges) { + if (currentWord == 0) { + this.empty + } else { + new BitSet1(currentWord) + } + } else { + this + } + case 0 => + val oldFirstWord = word(0) + val firstWord = BitSetOps.computeWordForFilter(pred, isFlipped, oldFirstWord, 0) + anyChanges ||= firstWord != oldFirstWord + if (anyChanges) { + new BitSet2(firstWord, currentWord) + } else { + this + } + case _ => + val minimumNonZeroIndex: Int = i + 1 + while (!anyChanges && i >= 0) { + val oldWord = word(i) + currentWord = BitSetOps.computeWordForFilter(pred, isFlipped, oldWord, i) + anyChanges ||= currentWord != oldWord + i -= 1 + } + if (anyChanges) { + val newArray = elems.take(minimumNonZeroIndex + 1) + newArray(i + 1) = currentWord + while (i >= 0) { + newArray(i) = BitSetOps.computeWordForFilter(pred, isFlipped, word(i), i) + i -= 1 + } + new BitSetN(newArray) + } else { + this + } + } + } + + override def toBitMask: Array[Long] = elems.clone() + } + + @SerialVersionUID(3L) + private final class SerializationProxy(coll: BitSet) extends scala.collection.BitSet.SerializationProxy(coll) { + protected[this] def readResolve(): Any = BitSet.fromBitMaskNoCopy(elems) + } +} diff --git a/library/src/scala/collection/immutable/ChampCommon.scala b/library/src/scala/collection/immutable/ChampCommon.scala new file mode 100644 index 000000000000..899525e822a6 --- /dev/null +++ b/library/src/scala/collection/immutable/ChampCommon.scala @@ -0,0 +1,253 @@ +/* + * 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.collection.immutable + +import scala.language.`2.13` +import scala.collection.AbstractIterator +import java.lang.Integer.bitCount +import java.lang.Math.ceil +import java.lang.System.arraycopy + +private[collection] object Node { + final val HashCodeLength = 32 + + final val BitPartitionSize = 5 + + final val BitPartitionMask = (1 << BitPartitionSize) - 1 + + final val MaxDepth = ceil(HashCodeLength.toDouble / BitPartitionSize).toInt + + final val BranchingFactor = 1 << BitPartitionSize + + final def maskFrom(hash: Int, shift: Int): Int = (hash >>> shift) & BitPartitionMask + + final def bitposFrom(mask: Int): Int = 1 << mask + + final def indexFrom(bitmap: Int, bitpos: Int): Int = bitCount(bitmap & (bitpos - 1)) + + final def indexFrom(bitmap: Int, mask: Int, bitpos: Int): Int = if (bitmap == -1) mask else indexFrom(bitmap, bitpos) + +} + +private[collection] abstract class Node[T <: Node[T]] { + + def hasNodes: Boolean + + def nodeArity: Int + + def getNode(index: Int): T + + def hasPayload: Boolean + + def payloadArity: Int + + def getPayload(index: Int): Any + + def getHash(index: Int): Int + + def cachedJavaKeySetHashCode: Int + + private final def arrayIndexOutOfBounds(as: Array[_], ix:Int): ArrayIndexOutOfBoundsException = + new ArrayIndexOutOfBoundsException(s"$ix is out of bounds (min 0, max ${as.length-1}") + + protected final def removeElement(as: Array[Int], ix: Int): Array[Int] = { + if (ix < 0) throw arrayIndexOutOfBounds(as, ix) + if (ix > as.length - 1) throw arrayIndexOutOfBounds(as, ix) + val result = new Array[Int](as.length - 1) + arraycopy(as, 0, result, 0, ix) + arraycopy(as, ix + 1, result, ix, as.length - ix - 1) + result + } + + protected final def removeAnyElement(as: Array[Any], ix: Int): Array[Any] = { + if (ix < 0) throw arrayIndexOutOfBounds(as, ix) + if (ix > as.length - 1) throw arrayIndexOutOfBounds(as, ix) + val result = new Array[Any](as.length - 1) + arraycopy(as, 0, result, 0, ix) + arraycopy(as, ix + 1, result, ix, as.length - ix - 1) + result + } + + protected final def insertElement(as: Array[Int], ix: Int, elem: Int): Array[Int] = { + if (ix < 0) throw arrayIndexOutOfBounds(as, ix) + if (ix > as.length) throw arrayIndexOutOfBounds(as, ix) + val result = new Array[Int](as.length + 1) + arraycopy(as, 0, result, 0, ix) + result(ix) = elem + arraycopy(as, ix, result, ix + 1, as.length - ix) + result + } + protected final def insertAnyElement(as: Array[Any], ix: Int, elem: Int): Array[Any] = { + if (ix < 0) throw arrayIndexOutOfBounds(as, ix) + if (ix > as.length) throw arrayIndexOutOfBounds(as, ix) + val result = new Array[Any](as.length + 1) + arraycopy(as, 0, result, 0, ix) + result(ix) = elem + arraycopy(as, ix, result, ix + 1, as.length - ix) + result + } +} + +/** + * Base class for fixed-stack iterators that traverse a hash-trie. The iterator performs a + * depth-first pre-order traversal, which yields first all payload elements of the current + * node before traversing sub-nodes (left to right). + * + * @tparam T the trie node type we are iterating over + */ +private[immutable] abstract class ChampBaseIterator[A, T <: Node[T]] extends AbstractIterator[A] { + + import Node.MaxDepth + + // Note--this code is duplicated to a large extent both in + // ChampBaseReverseIterator and in convert.impl.ChampStepperBase. + // If you change this code, check those also in case they also + // need to be modified. + + protected var currentValueCursor: Int = 0 + protected var currentValueLength: Int = 0 + protected var currentValueNode: T = _ + + private[this] var currentStackLevel: Int = -1 + private[this] var nodeCursorsAndLengths: Array[Int] = _ + private[this] var nodes: Array[T] = _ + private def initNodes(): Unit = { + if (nodeCursorsAndLengths eq null) { + nodeCursorsAndLengths = new Array[Int](MaxDepth * 2) + nodes = new Array[Node[T]](MaxDepth).asInstanceOf[Array[T]] + } + } + + def this(rootNode: T) = { + this() + if (rootNode.hasNodes) pushNode(rootNode) + if (rootNode.hasPayload) setupPayloadNode(rootNode) + } + + private final def setupPayloadNode(node: T): Unit = { + currentValueNode = node + currentValueCursor = 0 + currentValueLength = node.payloadArity + } + + private final def pushNode(node: T): Unit = { + initNodes() + currentStackLevel = currentStackLevel + 1 + + val cursorIndex = currentStackLevel * 2 + val lengthIndex = currentStackLevel * 2 + 1 + + nodes(currentStackLevel) = node + nodeCursorsAndLengths(cursorIndex) = 0 + nodeCursorsAndLengths(lengthIndex) = node.nodeArity + } + + private final def popNode(): Unit = { + currentStackLevel = currentStackLevel - 1 + } + + /** + * Searches for next node that contains payload values, + * and pushes encountered sub-nodes on a stack for depth-first traversal. + */ + private final def searchNextValueNode(): Boolean = { + while (currentStackLevel >= 0) { + val cursorIndex = currentStackLevel * 2 + val lengthIndex = currentStackLevel * 2 + 1 + + val nodeCursor = nodeCursorsAndLengths(cursorIndex) + val nodeLength = nodeCursorsAndLengths(lengthIndex) + + if (nodeCursor < nodeLength) { + nodeCursorsAndLengths(cursorIndex) += 1 + + val nextNode = nodes(currentStackLevel).getNode(nodeCursor) + + if (nextNode.hasNodes) { pushNode(nextNode) } + if (nextNode.hasPayload) { setupPayloadNode(nextNode) ; return true } + } else { + popNode() + } + } + + return false + } + + final def hasNext = (currentValueCursor < currentValueLength) || searchNextValueNode() + +} + +/** + * Base class for fixed-stack iterators that traverse a hash-trie in reverse order. The base + * iterator performs a depth-first post-order traversal, traversing sub-nodes (right to left). + * + * @tparam T the trie node type we are iterating over + */ +private[immutable] abstract class ChampBaseReverseIterator[A, T <: Node[T]] extends AbstractIterator[A] { + + import Node.MaxDepth + + protected var currentValueCursor: Int = -1 + protected var currentValueNode: T = _ + + private[this] var currentStackLevel: Int = -1 + private[this] val nodeIndex: Array[Int] = new Array[Int](MaxDepth + 1) + private[this] val nodeStack: Array[T] = new Array[Node[T]](MaxDepth + 1).asInstanceOf[Array[T]] + + def this(rootNode: T) = { + this() + pushNode(rootNode) + searchNextValueNode() + } + + private final def setupPayloadNode(node: T): Unit = { + currentValueNode = node + currentValueCursor = node.payloadArity - 1 + } + + private final def pushNode(node: T): Unit = { + currentStackLevel = currentStackLevel + 1 + + nodeStack(currentStackLevel) = node + nodeIndex(currentStackLevel) = node.nodeArity - 1 + } + + private final def popNode(): Unit = { + currentStackLevel = currentStackLevel - 1 + } + + /** + * Searches for rightmost node that contains payload values, + * and pushes encountered sub-nodes on a stack for depth-first traversal. + */ + private final def searchNextValueNode(): Boolean = { + while (currentStackLevel >= 0) { + val nodeCursor = nodeIndex(currentStackLevel) ; nodeIndex(currentStackLevel) = nodeCursor - 1 + + if (nodeCursor >= 0) { + val nextNode = nodeStack(currentStackLevel).getNode(nodeCursor) + pushNode(nextNode) + } else { + val currNode = nodeStack(currentStackLevel) + popNode() + + if (currNode.hasPayload) { setupPayloadNode(currNode) ; return true } + } + } + + return false + } + + final def hasNext = (currentValueCursor >= 0) || searchNextValueNode() + +} diff --git a/library/src/scala/collection/immutable/HashMap.scala b/library/src/scala/collection/immutable/HashMap.scala new file mode 100644 index 000000000000..eefdf6b6adbb --- /dev/null +++ b/library/src/scala/collection/immutable/HashMap.scala @@ -0,0 +1,2423 @@ +/* + * 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 collection.immutable + +import scala.language.`2.13` +import java.lang.Integer.bitCount +import java.lang.System.arraycopy + +import scala.annotation.unchecked.{uncheckedVariance => uV} +import scala.collection.Hashing.improve +import scala.collection.Stepper.EfficientSplit +import scala.collection.generic.DefaultSerializable +import scala.collection.mutable, mutable.ReusableBuilder +import scala.collection.{Iterator, MapFactory, MapFactoryDefaults, Stepper, StepperShape, mutable} +import scala.runtime.AbstractFunction2 +import scala.runtime.Statics.releaseFence +import scala.util.hashing.MurmurHash3 + +/** This class implements immutable maps using a Compressed Hash-Array Mapped Prefix-tree. + * See paper https://michael.steindorfer.name/publications/oopsla15.pdf for more details. + * + * @tparam K the type of the keys contained in this hash set. + * @tparam V the type of the values associated with the keys in this hash map. + * + * @define Coll `immutable.HashMap` + * @define coll immutable champ hash map + */ + +final class HashMap[K, +V] private[immutable] (private[immutable] val rootNode: BitmapIndexedMapNode[K, V]) + extends AbstractMap[K, V] + with StrictOptimizedMapOps[K, V, HashMap, HashMap[K, V]] + with MapFactoryDefaults[K, V, HashMap, Iterable] + with DefaultSerializable { + + def this() = this(MapNode.empty) + + // This release fence is present because rootNode may have previously been mutated during construction. + releaseFence() + + override def mapFactory: MapFactory[HashMap] = HashMap + + override def knownSize: Int = rootNode.size + + override def size: Int = rootNode.size + + override def isEmpty: Boolean = rootNode.size == 0 + + override def keySet: Set[K] = if (size == 0) Set.empty else new HashKeySet + + private[immutable] final class HashKeySet extends ImmutableKeySet { + + private[this] def newKeySetOrThis(newHashMap: HashMap[K, _]): Set[K] = + if (newHashMap eq HashMap.this) this else newHashMap.keySet + private[this] def newKeySetOrThis(newRootNode: BitmapIndexedMapNode[K, _]): Set[K] = + if (newRootNode eq rootNode) this else new HashMap(newRootNode).keySet + + override def incl(elem: K): Set[K] = { + val originalHash = elem.## + val improvedHash = improve(originalHash) + val newNode = rootNode.updated(elem, null.asInstanceOf[V], originalHash, improvedHash, 0, replaceValue = false) + newKeySetOrThis(newNode) + } + override def excl(elem: K): Set[K] = newKeySetOrThis(HashMap.this - elem) + override def filter(pred: K => Boolean): Set[K] = newKeySetOrThis(HashMap.this.filter(kv => pred(kv._1))) + override def filterNot(pred: K => Boolean): Set[K] = newKeySetOrThis(HashMap.this.filterNot(kv => pred(kv._1))) + } + + def iterator: Iterator[(K, V)] = { + if (isEmpty) Iterator.empty + else new MapKeyValueTupleIterator[K, V](rootNode) + } + + override def keysIterator: Iterator[K] = { + if (isEmpty) Iterator.empty + else new MapKeyIterator[K, V](rootNode) + } + override def valuesIterator: Iterator[V] = { + if (isEmpty) Iterator.empty + else new MapValueIterator[K, V](rootNode) + } + + protected[immutable] def reverseIterator: Iterator[(K, V)] = { + if (isEmpty) Iterator.empty + else new MapKeyValueTupleReverseIterator[K, V](rootNode) + } + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[(K, V), S]): S with EfficientSplit = + shape. + parUnbox(collection.convert.impl.AnyChampStepper.from[(K, V), MapNode[K, V]](size, rootNode, (node, i) => node.getPayload(i))) + + override def keyStepper[S <: Stepper[_]](implicit shape: StepperShape[K, S]): S with EfficientSplit = { + import collection.convert.impl._ + val s = shape.shape match { + case StepperShape.IntShape => IntChampStepper.from[ MapNode[K, V]](size, rootNode, (node, i) => node.getKey(i).asInstanceOf[Int]) + case StepperShape.LongShape => LongChampStepper.from[ MapNode[K, V]](size, rootNode, (node, i) => node.getKey(i).asInstanceOf[Long]) + case StepperShape.DoubleShape => DoubleChampStepper.from[MapNode[K, V]](size, rootNode, (node, i) => node.getKey(i).asInstanceOf[Double]) + case _ => shape.parUnbox(AnyChampStepper.from[K, MapNode[K, V]](size, rootNode, (node, i) => node.getKey(i))) + } + s.asInstanceOf[S with EfficientSplit] + } + + override def valueStepper[S <: Stepper[_]](implicit shape: StepperShape[V, S]): S with EfficientSplit = { + import collection.convert.impl._ + val s = shape.shape match { + case StepperShape.IntShape => IntChampStepper.from[ MapNode[K, V]](size, rootNode, (node, i) => node.getValue(i).asInstanceOf[Int]) + case StepperShape.LongShape => LongChampStepper.from[ MapNode[K, V]](size, rootNode, (node, i) => node.getValue(i).asInstanceOf[Long]) + case StepperShape.DoubleShape => DoubleChampStepper.from[MapNode[K, V]](size, rootNode, (node, i) => node.getValue(i).asInstanceOf[Double]) + case _ => shape.parUnbox(AnyChampStepper.from[V, MapNode[K, V]](size, rootNode, (node, i) => node.getValue(i))) + } + s.asInstanceOf[S with EfficientSplit] + } + + override final def contains(key: K): Boolean = { + val keyUnimprovedHash = key.## + val keyHash = improve(keyUnimprovedHash) + rootNode.containsKey(key, keyUnimprovedHash, keyHash, 0) + } + + override def apply(key: K): V = { + val keyUnimprovedHash = key.## + val keyHash = improve(keyUnimprovedHash) + rootNode.apply(key, keyUnimprovedHash, keyHash, 0) + } + + def get(key: K): Option[V] = { + val keyUnimprovedHash = key.## + val keyHash = improve(keyUnimprovedHash) + rootNode.get(key, keyUnimprovedHash, keyHash, 0) + } + + override def getOrElse[V1 >: V](key: K, default: => V1): V1 = { + val keyUnimprovedHash = key.## + val keyHash = improve(keyUnimprovedHash) + rootNode.getOrElse(key, keyUnimprovedHash, keyHash, 0, default) + } + + @inline private[this] def newHashMapOrThis[V1 >: V](newRootNode: BitmapIndexedMapNode[K, V1]): HashMap[K, V1] = + if (newRootNode eq rootNode) this else new HashMap(newRootNode) + + def updated[V1 >: V](key: K, value: V1): HashMap[K, V1] = { + val keyUnimprovedHash = key.## + newHashMapOrThis(rootNode.updated(key, value, keyUnimprovedHash, improve(keyUnimprovedHash), 0, replaceValue = true)) + } + + // preemptively overridden in anticipation of performance optimizations + override def updatedWith[V1 >: V](key: K)(remappingFunction: Option[V] => Option[V1]): HashMap[K, V1] = + super.updatedWith[V1](key)(remappingFunction) + + def removed(key: K): HashMap[K, V] = { + val keyUnimprovedHash = key.## + newHashMapOrThis(rootNode.removed(key, keyUnimprovedHash, improve(keyUnimprovedHash), 0)) + } + + override def concat[V1 >: V](that: scala.IterableOnce[(K, V1)]): HashMap[K, V1] = that match { + case hm: HashMap[K, V1] => + if (isEmpty) hm + else { + val newNode = rootNode.concat(hm.rootNode, 0) + if (newNode eq hm.rootNode) hm + else newHashMapOrThis(newNode) + } + case hm: mutable.HashMap[K @unchecked, V @unchecked] => + val iter = hm.nodeIterator + var current = rootNode + while (iter.hasNext) { + val next = iter.next() + val originalHash = hm.unimproveHash(next.hash) + val improved = improve(originalHash) + current = current.updated(next.key, next.value, originalHash, improved, 0, replaceValue = true) + + if (current ne rootNode) { + var shallowlyMutableNodeMap = Node.bitposFrom(Node.maskFrom(improved, 0)) + + while (iter.hasNext) { + val next = iter.next() + val originalHash = hm.unimproveHash(next.hash) + shallowlyMutableNodeMap = current.updateWithShallowMutations(next.key, next.value, originalHash, improve(originalHash), 0, shallowlyMutableNodeMap) + } + return new HashMap(current) + } + } + this + case lhm: mutable.LinkedHashMap[K @unchecked, V @unchecked] => + val iter = lhm.entryIterator + var current = rootNode + while (iter.hasNext) { + val next = iter.next() + val originalHash = lhm.unimproveHash(next.hash) + val improved = improve(originalHash) + current = current.updated(next.key, next.value, originalHash, improved, 0, replaceValue = true) + + if (current ne rootNode) { + var shallowlyMutableNodeMap = Node.bitposFrom(Node.maskFrom(improved, 0)) + + while (iter.hasNext) { + val next = iter.next() + val originalHash = lhm.unimproveHash(next.hash) + shallowlyMutableNodeMap = current.updateWithShallowMutations(next.key, next.value, originalHash, improve(originalHash), 0, shallowlyMutableNodeMap) + } + return new HashMap(current) + } + } + this + case _ => + class accum extends AbstractFunction2[K, V1, Unit] with Function1[(K, V1), Unit] { + var changed = false + var shallowlyMutableNodeMap: Int = 0 + var current: BitmapIndexedMapNode[K, V1] = rootNode + def apply(kv: (K, V1)) = apply(kv._1, kv._2) + def apply(key: K, value: V1): Unit = { + val originalHash = key.## + val improved = improve(originalHash) + if (!changed) { + current = current.updated(key, value, originalHash, improved, 0, replaceValue = true) + if (current ne rootNode) { + // Note: We could have started with shallowlyMutableNodeMap = 0, however this way, in the case that + // the first changed key ended up in a subnode beneath root, we mark that root right away as being + // shallowly mutable. + // + // since key->value has just been inserted, and certainly caused a new root node to be created, we can say with + // certainty that it either caused a new subnode to be created underneath `current`, in which case we should + // carry on mutating that subnode, or it ended up as a child data pair of the root, in which case, no harm is + // done by including its bit position in the shallowlyMutableNodeMap anyways. + changed = true + shallowlyMutableNodeMap = Node.bitposFrom(Node.maskFrom(improved, 0)) + } + } else { + shallowlyMutableNodeMap = current.updateWithShallowMutations(key, value, originalHash, improved, 0, shallowlyMutableNodeMap) + } + } + } + that match { + case thatMap: Map[K, V1] => + if (thatMap.isEmpty) this + else { + val accum = new accum + thatMap.foreachEntry(accum) + newHashMapOrThis(accum.current) + } + case _ => + val it = that.iterator + if (it.isEmpty) this + else { + val accum = new accum + it.foreach(accum) + newHashMapOrThis(accum.current) + } + } + } + + override def tail: HashMap[K, V] = this - head._1 + + override def init: HashMap[K, V] = this - last._1 + + override def head: (K, V) = iterator.next() + + override def last: (K, V) = reverseIterator.next() + + override def foreach[U](f: ((K, V)) => U): Unit = rootNode.foreach(f) + + override def foreachEntry[U](f: (K, V) => U): Unit = rootNode.foreachEntry(f) + + /** Applies a function to each key, value, and **original** hash value in this Map */ + @inline private[collection] def foreachWithHash(f: (K, V, Int) => Unit): Unit = rootNode.foreachWithHash(f) + + override def equals(that: Any): Boolean = + that match { + case map: HashMap[_, _] => (this eq map) || (this.rootNode == map.rootNode) + case _ => super.equals(that) + } + + override def hashCode(): Int = { + if (isEmpty) MurmurHash3.emptyMapHash + else { + // Optimized to avoid recomputation of key hashcodes as these are cached in the nodes and can be assumed to be + // immutable. + val hashIterator = new MapKeyValueTupleHashIterator(rootNode) + val hash = MurmurHash3.unorderedHash(hashIterator, MurmurHash3.mapSeed) + // assert(hash == super.hashCode()) + hash + } + } + + override protected[this] def className = "HashMap" + + /** Merges this HashMap with an other HashMap by combining all key-value pairs of both maps, and delegating to a merge + * function to resolve any key collisions between the two HashMaps. + * + * @example {{{ + * val left = HashMap(1 -> 1, 2 -> 1) + * val right = HashMap(2 -> 2, 3 -> 2) + * + * val merged = left.merged(right){ case ((k0, v0), (k1, v1)) => (k0 + k1) -> (v0 + v1) } + * // HashMap(1 -> 1, 3 -> 2, 4 -> 3) + * + * }}} + * + * @param that the HashMap to merge this HashMap with + * @param mergef the merge function which resolves collisions between the two HashMaps. If `mergef` is null, then + * keys from `this` will overwrite keys from `that`, making the behaviour equivalent to + * `that.concat(this)` + * + * @note In cases where `mergef` returns keys which themselves collide with other keys returned by `merge`, or + * found in `this` or `that`, it is not defined which value will be chosen. For example: + * + * Colliding multiple results of merging: + * {{{ + * // key `3` collides between a result of merging keys `1` and `2` + * val left = HashMap(1 -> 1, 2 -> 2) + * val right = HashMap(1 -> 1, 2 -> 2) + * + * val merged = left.merged(right){ case (_, (_, v1)) => 3 -> v1 } + * // HashMap(3 -> 2) is returned, but it could also have returned HashMap(3 -> 1) + * }}} + * Colliding results of merging with other keys: + * {{{ + * // key `2` collides between a result of merging `1`, and existing key `2` + * val left = HashMap(1 -> 1, 2 -> 1) + * val right = HashMap(1 -> 2) + * + * val merged = left.merged(right)((_,_) => 2 -> 3) + * // HashMap(2 -> 1) is returned, but it could also have returned HashMap(2 -> 3) + * }}} + * + */ + def merged[V1 >: V](that: HashMap[K, V1])(mergef: ((K, V), (K, V1)) => (K, V1)): HashMap[K, V1] = + if (mergef == null) { + that ++ this + } else { + if (isEmpty) that + else if (that.isEmpty) this + else if (size == 1) { + val payload@(k, v) = rootNode.getPayload(0) + val originalHash = rootNode.getHash(0) + val improved = improve(originalHash) + + if (that.rootNode.containsKey(k, originalHash, improved, 0)) { + val thatPayload = that.rootNode.getTuple(k, originalHash, improved, 0) + val (mergedK, mergedV) = mergef(payload, thatPayload) + val mergedOriginalHash = mergedK.## + val mergedImprovedHash = improve(mergedOriginalHash) + new HashMap(that.rootNode.removed(thatPayload._1, originalHash, improved, 0).updated(mergedK, mergedV, mergedOriginalHash, mergedImprovedHash, 0, replaceValue = true)) + } else { + new HashMap(that.rootNode.updated(k, v, originalHash, improved, 0, replaceValue = true)) + } + } else if (that.size == 0) { + val thatPayload@(k, v) = rootNode.getPayload(0) + val thatOriginalHash = rootNode.getHash(0) + val thatImproved = improve(thatOriginalHash) + + if (rootNode.containsKey(k, thatOriginalHash, thatImproved, 0)) { + val payload = rootNode.getTuple(k, thatOriginalHash, thatImproved, 0) + val (mergedK, mergedV) = mergef(payload, thatPayload) + val mergedOriginalHash = mergedK.## + val mergedImprovedHash = improve(mergedOriginalHash) + new HashMap(rootNode.updated(mergedK, mergedV, mergedOriginalHash, mergedImprovedHash, 0, replaceValue = true)) + } else { + new HashMap(rootNode.updated(k, v, thatOriginalHash, thatImproved, 0, replaceValue = true)) + } + } else { + val builder = new HashMapBuilder[K, V1] + rootNode.mergeInto(that.rootNode, builder, 0)(mergef) + builder.result() + } + } + + override def transform[W](f: (K, V) => W): HashMap[K, W] = + newHashMapOrThis(rootNode.transform[Any](f)).asInstanceOf[HashMap[K, W]] + + override protected[collection] def filterImpl(pred: ((K, V)) => Boolean, isFlipped: Boolean): HashMap[K, V] = { + val newRootNode = rootNode.filterImpl(pred, isFlipped) + if (newRootNode eq rootNode) this + else if (newRootNode.size == 0) HashMap.empty + else new HashMap(newRootNode) + } + + override def removedAll(keys: IterableOnce[K]): HashMap[K, V] = { + if (isEmpty) { + this + } else { + keys match { + case hashSet: HashSet[K] => + if (hashSet.isEmpty) { + this + } else { + // TODO: Remove all keys from the hashSet in a sub-linear fashion by only visiting the nodes in the tree + // This can be a direct port of the implementation of `SetNode[A]#diff(SetNode[A])` + val newRootNode = new MapNodeRemoveAllSetNodeIterator(hashSet.rootNode).removeAll(rootNode) + if (newRootNode eq rootNode) this + else if (newRootNode.size <= 0) HashMap.empty + else new HashMap(newRootNode) + } + case hashSet: collection.mutable.HashSet[K] => + if (hashSet.isEmpty) { + this + } else { + val iter = hashSet.nodeIterator + var curr = rootNode + + while (iter.hasNext) { + val next = iter.next() + val originalHash = hashSet.unimproveHash(next.hash) + val improved = improve(originalHash) + curr = curr.removed(next.key, originalHash, improved, 0) + if (curr.size == 0) { + return HashMap.empty + } + } + newHashMapOrThis(curr) + } + case lhashSet: collection.mutable.LinkedHashSet[K] => + if (lhashSet.isEmpty) { + this + } else { + val iter = lhashSet.entryIterator + var curr = rootNode + + while (iter.hasNext) { + val next = iter.next() + val originalHash = lhashSet.unimproveHash(next.hash) + val improved = improve(originalHash) + curr = curr.removed(next.key, originalHash, improved, 0) + if (curr.size == 0) { + return HashMap.empty + } + } + newHashMapOrThis(curr) + } + case _ => + val iter = keys.iterator + var curr = rootNode + while (iter.hasNext) { + val next = iter.next() + val originalHash = next.## + val improved = improve(originalHash) + curr = curr.removed(next, originalHash, improved, 0) + if (curr.size == 0) { + return HashMap.empty + } + } + newHashMapOrThis(curr) + } + } + } + + override def partition(p: ((K, V)) => Boolean): (HashMap[K, V], HashMap[K, V]) = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + // + // In particular, `partition` could be optimized to traverse the trie node-by-node, splitting each node into two, + // based on the result of applying `p` to its elements and subnodes. + super.partition(p) + } + + override def take(n: Int): HashMap[K, V] = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + // + // In particular, `take` could be optimized to construct a new trie structure by visiting each node, and including + // those nodes in the resulting trie, until `n` total elements have been included. + super.take(n) + } + + override def takeRight(n: Int): HashMap[K, V] = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + // + // In particular, `take` could be optimized to construct a new trie structure by visiting each node in reverse, and + // and including those nodes in the resulting trie, until `n` total elements have been included. + super.takeRight(n) + } + + override def takeWhile(p: ((K, V)) => Boolean): HashMap[K, V] = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + // + // In particular, `takeWhile` could be optimized to construct a new trie structure by visiting each node, and + // including those nodes in the resulting trie, until `p` returns `false` + super.takeWhile(p) + } + + override def dropWhile(p: ((K, V)) => Boolean): HashMap[K, V] = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + // + // In particular, `dropWhile` could be optimized to construct a new trie structure by visiting each node, and + // dropping those nodes in the resulting trie, until `p` returns `true` + super.dropWhile(p) + } + + override def dropRight(n: Int): HashMap[K, V] = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + // + // In particular, `dropRight` could be optimized to construct a new trie structure by visiting each node, in reverse + // order, and dropping all nodes until `n` elements have been dropped + super.dropRight(n) + } + + override def drop(n: Int): HashMap[K, V] = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + // + // In particular, `dropRight` could be optimized to construct a new trie structure by visiting each node, and + // dropping all nodes until `n` elements have been dropped + super.drop(n) + } + + override def span(p: ((K, V)) => Boolean): (HashMap[K, V], HashMap[K, V]) = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + // + // In particular, `scan` could be optimized to construct a new trie structure by visiting each node, and + // keeping each node and element until `p` returns false, then including the remaining nodes in the second result. + // This would avoid having to rebuild most of the trie, and would eliminate the need to perform hashing and equality + // checks. + super.span(p) + } + +} + +private[immutable] object MapNode { + + private final val EmptyMapNode = new BitmapIndexedMapNode(0, 0, Array.empty, Array.empty, 0, 0) + + def empty[K, V]: BitmapIndexedMapNode[K, V] = EmptyMapNode.asInstanceOf[BitmapIndexedMapNode[K, V]] + + final val TupleLength = 2 + +} + + +private[immutable] sealed abstract class MapNode[K, +V] extends Node[MapNode[K, V @uV]] { + def apply(key: K, originalHash: Int, hash: Int, shift: Int): V + + def get(key: K, originalHash: Int, hash: Int, shift: Int): Option[V] + + def getOrElse[V1 >: V](key: K, originalHash: Int, hash: Int, shift: Int, f: => V1): V1 + + def containsKey(key: K, originalHash: Int, hash: Int, shift: Int): Boolean + + /** Returns a MapNode with the passed key-value assignment added + * + * @param key the key to add to the MapNode + * @param value the value to associate with `key` + * @param originalHash the original hash of `key` + * @param hash the improved hash of `key` + * @param shift the shift of the node (distanceFromRoot * BitPartitionSize) + * @param replaceValue if true, then the value currently associated to `key` will be replaced with the passed value + * argument. + * if false, then the key will be inserted if not already present, however if the key is present + * then the passed value will not replace the current value. That is, if `false`, then this + * method has `update if not exists` semantics. + */ + def updated[V1 >: V](key: K, value: V1, originalHash: Int, hash: Int, shift: Int, replaceValue: Boolean): MapNode[K, V1] + + def removed[V1 >: V](key: K, originalHash: Int, hash: Int, shift: Int): MapNode[K, V1] + + def hasNodes: Boolean + + def nodeArity: Int + + def getNode(index: Int): MapNode[K, V] + + def hasPayload: Boolean + + def payloadArity: Int + + def getKey(index: Int): K + + def getValue(index: Int): V + + def getPayload(index: Int): (K, V) + + def size: Int + + def foreach[U](f: ((K, V)) => U): Unit + + def foreachEntry[U](f: (K, V) => U): Unit + + def foreachWithHash(f: (K, V, Int) => Unit): Unit + + def transform[W](f: (K, V) => W): MapNode[K, W] + + def copy(): MapNode[K, V] + + def concat[V1 >: V](that: MapNode[K, V1], shift: Int): MapNode[K, V1] + + def filterImpl(pred: ((K, V)) => Boolean, isFlipped: Boolean): MapNode[K, V] + + /** Merges this node with that node, adding each resulting tuple to `builder` + * + * `this` should be a node from `left` hashmap in `left.merged(right)(mergef)` + * + * @param that node from the "right" HashMap. Must also be at the same "path" or "position" within the right tree, + * as `this` is, within the left tree + */ + def mergeInto[V1 >: V](that: MapNode[K, V1], builder: HashMapBuilder[K, V1], shift: Int)(mergef: ((K, V), (K, V1)) => (K, V1)): Unit + + /** Returns the exact (equal by reference) key, and value, associated to a given key. + * If the key is not bound to a value, then an exception is thrown + */ + def getTuple(key: K, originalHash: Int, hash: Int, shift: Int): (K, V) + + /** Adds all key-value pairs to a builder */ + def buildTo[V1 >: V](builder: HashMapBuilder[K, V1]): Unit +} + +private final class BitmapIndexedMapNode[K, +V]( + var dataMap: Int, + var nodeMap: Int, + var content: Array[Any], + var originalHashes: Array[Int], + var size: Int, + var cachedJavaKeySetHashCode: Int) extends MapNode[K, V] { + + releaseFence() + + import MapNode._ + import Node._ + + /* + assert(checkInvariantContentIsWellTyped()) + assert(checkInvariantSubNodesAreCompacted()) + + private final def checkInvariantSubNodesAreCompacted(): Boolean = + new MapKeyValueTupleIterator[K, V](this).size - payloadArity >= 2 * nodeArity + + private final def checkInvariantContentIsWellTyped(): Boolean = { + val predicate1 = TupleLength * payloadArity + nodeArity == content.length + + val predicate2 = Range(0, TupleLength * payloadArity) + .forall(i => content(i).isInstanceOf[MapNode[_, _]] == false) + + val predicate3 = Range(TupleLength * payloadArity, content.length) + .forall(i => content(i).isInstanceOf[MapNode[_, _]] == true) + + predicate1 && predicate2 && predicate3 + } + */ + + def getKey(index: Int): K = content(TupleLength * index).asInstanceOf[K] + def getValue(index: Int): V = content(TupleLength * index + 1).asInstanceOf[V] + + def getPayload(index: Int) = Tuple2( + content(TupleLength * index).asInstanceOf[K], + content(TupleLength * index + 1).asInstanceOf[V]) + + override def getHash(index: Int): Int = originalHashes(index) + + def getNode(index: Int): MapNode[K, V] = + content(content.length - 1 - index).asInstanceOf[MapNode[K, V]] + + def apply(key: K, originalHash: Int, keyHash: Int, shift: Int): V = { + val mask = maskFrom(keyHash, shift) + val bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + val index = indexFrom(dataMap, mask, bitpos) + if (key == getKey(index)) getValue(index) else throw new NoSuchElementException(s"key not found: $key") + } else if ((nodeMap & bitpos) != 0) { + getNode(indexFrom(nodeMap, mask, bitpos)).apply(key, originalHash, keyHash, shift + BitPartitionSize) + } else { + throw new NoSuchElementException(s"key not found: $key") + } + } + + def get(key: K, originalHash: Int, keyHash: Int, shift: Int): Option[V] = { + val mask = maskFrom(keyHash, shift) + val bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + val index = indexFrom(dataMap, mask, bitpos) + val key0 = this.getKey(index) + if (key == key0) Some(this.getValue(index)) else None + } else if ((nodeMap & bitpos) != 0) { + val index = indexFrom(nodeMap, mask, bitpos) + this.getNode(index).get(key, originalHash, keyHash, shift + BitPartitionSize) + } else { + None + } + } + + override def getTuple(key: K, originalHash: Int, hash: Int, shift: Int): (K, V) = { + val mask = maskFrom(hash, shift) + val bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + val index = indexFrom(dataMap, mask, bitpos) + val payload = getPayload(index) + if (key == payload._1) payload else Iterator.empty.next() + } else if ((nodeMap & bitpos) != 0) { + val index = indexFrom(nodeMap, mask, bitpos) + getNode(index).getTuple(key, originalHash, hash, shift + BitPartitionSize) + } else { + Iterator.empty.next() + } + } + + def getOrElse[V1 >: V](key: K, originalHash: Int, keyHash: Int, shift: Int, f: => V1): V1 = { + val mask = maskFrom(keyHash, shift) + val bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + val index = indexFrom(dataMap, mask, bitpos) + val key0 = this.getKey(index) + if (key == key0) getValue(index) else f + } else if ((nodeMap & bitpos) != 0) { + val index = indexFrom(nodeMap, mask, bitpos) + this.getNode(index).getOrElse(key, originalHash, keyHash, shift + BitPartitionSize, f) + } else { + f + } + } + + override def containsKey(key: K, originalHash: Int, keyHash: Int, shift: Int): Boolean = { + val mask = maskFrom(keyHash, shift) + val bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + val index = indexFrom(dataMap, mask, bitpos) + // assert(hashes(index) == computeHash(this.getKey(index)), (hashes.toSeq, content.toSeq, index, key, keyHash, shift)) + (originalHashes(index) == originalHash) && key == getKey(index) + } else if ((nodeMap & bitpos) != 0) { + getNode(indexFrom(nodeMap, mask, bitpos)).containsKey(key, originalHash, keyHash, shift + BitPartitionSize) + } else { + false + } + } + + + def updated[V1 >: V](key: K, value: V1, originalHash: Int, keyHash: Int, shift: Int, replaceValue: Boolean): BitmapIndexedMapNode[K, V1] = { + val mask = maskFrom(keyHash, shift) + val bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + val index = indexFrom(dataMap, mask, bitpos) + val key0 = getKey(index) + val key0UnimprovedHash = getHash(index) + if (key0UnimprovedHash == originalHash && key0 == key) { + if (replaceValue) { + val value0 = this.getValue(index) + if ((key0.asInstanceOf[AnyRef] eq key.asInstanceOf[AnyRef]) && (value0.asInstanceOf[AnyRef] eq value.asInstanceOf[AnyRef])) + this + else copyAndSetValue(bitpos, key, value) + } else this + } else { + val value0 = this.getValue(index) + val key0Hash = improve(key0UnimprovedHash) + val subNodeNew = mergeTwoKeyValPairs(key0, value0, key0UnimprovedHash, key0Hash, key, value, originalHash, keyHash, shift + BitPartitionSize) + + copyAndMigrateFromInlineToNode(bitpos, key0Hash, subNodeNew) + } + } else if ((nodeMap & bitpos) != 0) { + val index = indexFrom(nodeMap, mask, bitpos) + val subNode = this.getNode(index) + val subNodeNew = subNode.updated(key, value, originalHash, keyHash, shift + BitPartitionSize, replaceValue) + + if (subNodeNew eq subNode) this else copyAndSetNode(bitpos, subNode, subNodeNew) + } else copyAndInsertValue(bitpos, key, originalHash, keyHash, value) + } + + /** A variant of `updated` which performs shallow mutations on the root (`this`), and if possible, on immediately + * descendant child nodes (only one level beneath `this`) + * + * The caller should pass a bitmap of child nodes of this node, which this method may mutate. + * If this method may mutate a child node, then if the updated key-value belongs in that child node, it will + * be shallowly mutated (its children will not be mutated). + * + * If instead this method may not mutate the child node in which the to-be-updated key-value pair belongs, then + * that child will be updated immutably, but the result will be mutably re-inserted as a child of this node. + * + * @param key the key to update + * @param value the value to set `key` to + * @param originalHash key.## + * @param keyHash the improved hash + * @param shallowlyMutableNodeMap bitmap of child nodes of this node, which can be shallowly mutated + * during the call to this method + * + * @return Int which is the bitwise OR of shallowlyMutableNodeMap and any freshly created nodes, which will be + * available for mutations in subsequent calls. + */ + def updateWithShallowMutations[V1 >: V](key: K, value: V1, originalHash: Int, keyHash: Int, shift: Int, shallowlyMutableNodeMap: Int): Int = { + val mask = maskFrom(keyHash, shift) + val bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + val index = indexFrom(dataMap, mask, bitpos) + val key0 = getKey(index) + val key0UnimprovedHash = getHash(index) + if (key0UnimprovedHash == originalHash && key0 == key) { + val value0 = this.getValue(index) + if (!((key0.asInstanceOf[AnyRef] eq key.asInstanceOf[AnyRef]) && (value0.asInstanceOf[AnyRef] eq value.asInstanceOf[AnyRef]))) { + val dataIx = dataIndex(bitpos) + val idx = TupleLength * dataIx + content(idx + 1) = value + } + shallowlyMutableNodeMap + } else { + val value0 = this.getValue(index) + val key0Hash = improve(key0UnimprovedHash) + + val subNodeNew = mergeTwoKeyValPairs(key0, value0, key0UnimprovedHash, key0Hash, key, value, originalHash, keyHash, shift + BitPartitionSize) + migrateFromInlineToNodeInPlace(bitpos, key0Hash, subNodeNew) + shallowlyMutableNodeMap | bitpos + } + } else if ((nodeMap & bitpos) != 0) { + val index = indexFrom(nodeMap, mask, bitpos) + val subNode = this.getNode(index) + val subNodeSize = subNode.size + val subNodeHashCode = subNode.cachedJavaKeySetHashCode + + var returnMutableNodeMap = shallowlyMutableNodeMap + + val subNodeNew: MapNode[K, V1] = subNode match { + case subNodeBm: BitmapIndexedMapNode[K, V] if (bitpos & shallowlyMutableNodeMap) != 0 => + subNodeBm.updateWithShallowMutations(key, value, originalHash, keyHash, shift + BitPartitionSize, 0) + subNodeBm + case _ => + val result = subNode.updated(key, value, originalHash, keyHash, shift + BitPartitionSize, replaceValue = true) + if (result ne subNode) { + returnMutableNodeMap |= bitpos + } + result + } + + this.content(this.content.length - 1 - this.nodeIndex(bitpos)) = subNodeNew + this.size = this.size - subNodeSize + subNodeNew.size + this.cachedJavaKeySetHashCode = this.cachedJavaKeySetHashCode - subNodeHashCode + subNodeNew.cachedJavaKeySetHashCode + returnMutableNodeMap + } else { + val dataIx = dataIndex(bitpos) + val idx = TupleLength * dataIx + + val src = this.content + val dst = new Array[Any](src.length + TupleLength) + + // copy 'src' and insert 2 element(s) at position 'idx' + arraycopy(src, 0, dst, 0, idx) + dst(idx) = key + dst(idx + 1) = value + arraycopy(src, idx, dst, idx + TupleLength, src.length - idx) + + this.dataMap |= bitpos + this.content = dst + this.originalHashes = insertElement(originalHashes, dataIx, originalHash) + this.size += 1 + this.cachedJavaKeySetHashCode += keyHash + shallowlyMutableNodeMap + } + } + + def removed[V1 >: V](key: K, originalHash: Int, keyHash: Int, shift: Int): BitmapIndexedMapNode[K, V1] = { + val mask = maskFrom(keyHash, shift) + val bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + val index = indexFrom(dataMap, mask, bitpos) + val key0 = this.getKey(index) + + if (key0 == key) { + if (this.payloadArity == 2 && this.nodeArity == 0) { + /* + * Create new node with remaining pair. The new node will a) either become the new root + * returned, or b) unwrapped and inlined during returning. + */ + val newDataMap = if (shift == 0) (dataMap ^ bitpos) else bitposFrom(maskFrom(keyHash, 0)) + if (index == 0) + new BitmapIndexedMapNode[K, V1](newDataMap, 0, Array(getKey(1), getValue(1)), Array(originalHashes(1)), 1, improve(getHash(1))) + else + new BitmapIndexedMapNode[K, V1](newDataMap, 0, Array(getKey(0), getValue(0)), Array(originalHashes(0)), 1, improve(getHash(0))) + } else copyAndRemoveValue(bitpos, keyHash) + } else this + } else if ((nodeMap & bitpos) != 0) { + val index = indexFrom(nodeMap, mask, bitpos) + val subNode = this.getNode(index) + + val subNodeNew = subNode.removed(key, originalHash, keyHash, shift + BitPartitionSize) + // assert(subNodeNew.size != 0, "Sub-node must have at least one element.") + + if (subNodeNew eq subNode) return this + + // cache just in case subNodeNew is a hashCollision node, in which in which case a little arithmetic is avoided + // in Vector#length + val subNodeNewSize = subNodeNew.size + + if (subNodeNewSize == 1) { + if (this.size == subNode.size) { + // subNode is the only child (no other data or node children of `this` exist) + // escalate (singleton or empty) result + subNodeNew.asInstanceOf[BitmapIndexedMapNode[K, V]] + } else { + // inline value (move to front) + copyAndMigrateFromNodeToInline(bitpos, subNode, subNodeNew) + } + } else if (subNodeNewSize > 1) { + // modify current node (set replacement node) + copyAndSetNode(bitpos, subNode, subNodeNew) + } else this + } else this + } + + def mergeTwoKeyValPairs[V1 >: V](key0: K, value0: V1, originalHash0: Int, keyHash0: Int, key1: K, value1: V1, originalHash1: Int, keyHash1: Int, shift: Int): MapNode[K, V1] = { + // assert(key0 != key1) + + if (shift >= HashCodeLength) { + new HashCollisionMapNode[K, V1](originalHash0, keyHash0, Vector((key0, value0), (key1, value1))) + } else { + val mask0 = maskFrom(keyHash0, shift) + val mask1 = maskFrom(keyHash1, shift) + val newCachedHash = keyHash0 + keyHash1 + + if (mask0 != mask1) { + // unique prefixes, payload fits on same level + val dataMap = bitposFrom(mask0) | bitposFrom(mask1) + + if (mask0 < mask1) { + new BitmapIndexedMapNode[K, V1](dataMap, 0, Array(key0, value0, key1, value1), Array(originalHash0, originalHash1), 2, newCachedHash) + } else { + new BitmapIndexedMapNode[K, V1](dataMap, 0, Array(key1, value1, key0, value0), Array(originalHash1, originalHash0), 2, newCachedHash) + } + } else { + // identical prefixes, payload must be disambiguated deeper in the trie + val nodeMap = bitposFrom(mask0) + val node = mergeTwoKeyValPairs(key0, value0, originalHash0, keyHash0, key1, value1, originalHash1, keyHash1, shift + BitPartitionSize) + new BitmapIndexedMapNode[K, V1](0, nodeMap, Array(node), Array.emptyIntArray, node.size, node.cachedJavaKeySetHashCode) + } + } + } + + def hasNodes: Boolean = nodeMap != 0 + + def nodeArity: Int = bitCount(nodeMap) + + def hasPayload: Boolean = dataMap != 0 + + def payloadArity: Int = bitCount(dataMap) + + def dataIndex(bitpos: Int) = bitCount(dataMap & (bitpos - 1)) + + def nodeIndex(bitpos: Int) = bitCount(nodeMap & (bitpos - 1)) + + def copyAndSetValue[V1 >: V](bitpos: Int, newKey: K, newValue: V1): BitmapIndexedMapNode[K, V1] = { + val dataIx = dataIndex(bitpos) + val idx = TupleLength * dataIx + + val src = this.content + val dst = new Array[Any](src.length) + + // copy 'src' and set 1 element(s) at position 'idx' + arraycopy(src, 0, dst, 0, src.length) + //dst(idx) = newKey + dst(idx + 1) = newValue + new BitmapIndexedMapNode[K, V1](dataMap, nodeMap, dst, originalHashes, size, cachedJavaKeySetHashCode) + } + + def copyAndSetNode[V1 >: V](bitpos: Int, oldNode: MapNode[K, V1], newNode: MapNode[K, V1]): BitmapIndexedMapNode[K, V1] = { + val idx = this.content.length - 1 - this.nodeIndex(bitpos) + + val src = this.content + val dst = new Array[Any](src.length) + + // copy 'src' and set 1 element(s) at position 'idx' + arraycopy(src, 0, dst, 0, src.length) + dst(idx) = newNode + new BitmapIndexedMapNode[K, V1]( + dataMap, + nodeMap, + dst, + originalHashes, + size - oldNode.size + newNode.size, + cachedJavaKeySetHashCode - oldNode.cachedJavaKeySetHashCode + newNode.cachedJavaKeySetHashCode + ) + } + + def copyAndInsertValue[V1 >: V](bitpos: Int, key: K, originalHash: Int, keyHash: Int, value: V1): BitmapIndexedMapNode[K, V1] = { + val dataIx = dataIndex(bitpos) + val idx = TupleLength * dataIx + + val src = this.content + val dst = new Array[Any](src.length + TupleLength) + + // copy 'src' and insert 2 element(s) at position 'idx' + arraycopy(src, 0, dst, 0, idx) + dst(idx) = key + dst(idx + 1) = value + arraycopy(src, idx, dst, idx + TupleLength, src.length - idx) + + val dstHashes = insertElement(originalHashes, dataIx, originalHash) + + new BitmapIndexedMapNode[K, V1](dataMap | bitpos, nodeMap, dst, dstHashes, size + 1, cachedJavaKeySetHashCode + keyHash) + } + + def copyAndRemoveValue(bitpos: Int, keyHash: Int): BitmapIndexedMapNode[K, V] = { + val dataIx = dataIndex(bitpos) + val idx = TupleLength * dataIx + + val src = this.content + val dst = new Array[Any](src.length - TupleLength) + + // copy 'src' and remove 2 element(s) at position 'idx' + arraycopy(src, 0, dst, 0, idx) + arraycopy(src, idx + TupleLength, dst, idx, src.length - idx - TupleLength) + + val dstHashes = removeElement(originalHashes, dataIx) + + new BitmapIndexedMapNode[K, V](dataMap ^ bitpos, nodeMap, dst, dstHashes, size - 1, cachedJavaKeySetHashCode - keyHash) + } + + /** Variant of `copyAndMigrateFromInlineToNode` which mutates `this` rather than returning a new node. + * + * @param bitpos the bit position of the data to migrate to node + * @param keyHash the improved hash of the key currently at `bitpos` + * @param node the node to place at `bitpos` beneath `this` + */ + def migrateFromInlineToNodeInPlace[V1 >: V](bitpos: Int, keyHash: Int, node: MapNode[K, V1]): this.type = { + val dataIx = dataIndex(bitpos) + val idxOld = TupleLength * dataIx + val idxNew = this.content.length - TupleLength - nodeIndex(bitpos) + + val src = this.content + val dst = new Array[Any](src.length - TupleLength + 1) + + // copy 'src' and remove 2 element(s) at position 'idxOld' and + // insert 1 element(s) at position 'idxNew' + // assert(idxOld <= idxNew) + arraycopy(src, 0, dst, 0, idxOld) + arraycopy(src, idxOld + TupleLength, dst, idxOld, idxNew - idxOld) + dst(idxNew) = node + arraycopy(src, idxNew + TupleLength, dst, idxNew + 1, src.length - idxNew - TupleLength) + + val dstHashes = removeElement(originalHashes, dataIx) + + this.dataMap = dataMap ^ bitpos + this.nodeMap = nodeMap | bitpos + this.content = dst + this.originalHashes = dstHashes + this.size = size - 1 + node.size + this.cachedJavaKeySetHashCode = cachedJavaKeySetHashCode - keyHash + node.cachedJavaKeySetHashCode + this + } + + def copyAndMigrateFromInlineToNode[V1 >: V](bitpos: Int, keyHash: Int, node: MapNode[K, V1]): BitmapIndexedMapNode[K, V1] = { + val dataIx = dataIndex(bitpos) + val idxOld = TupleLength * dataIx + val idxNew = this.content.length - TupleLength - nodeIndex(bitpos) + + val src = this.content + val dst = new Array[Any](src.length - TupleLength + 1) + + // copy 'src' and remove 2 element(s) at position 'idxOld' and + // insert 1 element(s) at position 'idxNew' + // assert(idxOld <= idxNew) + arraycopy(src, 0, dst, 0, idxOld) + arraycopy(src, idxOld + TupleLength, dst, idxOld, idxNew - idxOld) + dst(idxNew) = node + arraycopy(src, idxNew + TupleLength, dst, idxNew + 1, src.length - idxNew - TupleLength) + + val dstHashes = removeElement(originalHashes, dataIx) + + new BitmapIndexedMapNode[K, V1]( + dataMap = dataMap ^ bitpos, + nodeMap = nodeMap | bitpos, + content = dst, + originalHashes = dstHashes, + size = size - 1 + node.size, + cachedJavaKeySetHashCode = cachedJavaKeySetHashCode - keyHash + node.cachedJavaKeySetHashCode + ) + } + + def copyAndMigrateFromNodeToInline[V1 >: V](bitpos: Int, oldNode: MapNode[K, V1], node: MapNode[K, V1]): BitmapIndexedMapNode[K, V1] = { + val idxOld = this.content.length - 1 - nodeIndex(bitpos) + val dataIxNew = dataIndex(bitpos) + val idxNew = TupleLength * dataIxNew + + val key = node.getKey(0) + val value = node.getValue(0) + val src = this.content + val dst = new Array[Any](src.length - 1 + TupleLength) + + // copy 'src' and remove 1 element(s) at position 'idxOld' and + // insert 2 element(s) at position 'idxNew' + // assert(idxOld >= idxNew) + arraycopy(src, 0, dst, 0, idxNew) + dst(idxNew) = key + dst(idxNew + 1) = value + arraycopy(src, idxNew, dst, idxNew + TupleLength, idxOld - idxNew) + arraycopy(src, idxOld + 1, dst, idxOld + TupleLength, src.length - idxOld - 1) + val hash = node.getHash(0) + val dstHashes = insertElement(originalHashes, dataIxNew, hash) + new BitmapIndexedMapNode[K, V1]( + dataMap = dataMap | bitpos, + nodeMap = nodeMap ^ bitpos, + content = dst, + originalHashes = dstHashes, + size = size - oldNode.size + 1, + cachedJavaKeySetHashCode = cachedJavaKeySetHashCode - oldNode.cachedJavaKeySetHashCode + node.cachedJavaKeySetHashCode + ) + } + + override def foreach[U](f: ((K, V)) => U): Unit = { + val iN = payloadArity // arity doesn't change during this operation + var i = 0 + while (i < iN) { + f(getPayload(i)) + i += 1 + } + + val jN = nodeArity // arity doesn't change during this operation + var j = 0 + while (j < jN) { + getNode(j).foreach(f) + j += 1 + } + } + + override def foreachEntry[U](f: (K, V) => U): Unit = { + val iN = payloadArity // arity doesn't change during this operation + var i = 0 + while (i < iN) { + f(getKey(i), getValue(i)) + i += 1 + } + + val jN = nodeArity // arity doesn't change during this operation + var j = 0 + while (j < jN) { + getNode(j).foreachEntry(f) + j += 1 + } + } + + override def foreachWithHash(f: (K, V, Int) => Unit): Unit = { + var i = 0 + val iN = payloadArity // arity doesn't change during this operation + while (i < iN) { + f(getKey(i), getValue(i), getHash(i)) + i += 1 + } + + val jN = nodeArity // arity doesn't change during this operation + var j = 0 + while (j < jN) { + getNode(j).foreachWithHash(f) + j += 1 + } + } + override def buildTo[V1 >: V](builder: HashMapBuilder[K, V1]): Unit = { + var i = 0 + val iN = payloadArity + val jN = nodeArity + while (i < iN) { + builder.addOne(getKey(i), getValue(i), getHash(i)) + i += 1 + } + + var j = 0 + while (j < jN) { + getNode(j).buildTo(builder) + j += 1 + } + } + + override def transform[W](f: (K, V) => W): BitmapIndexedMapNode[K, W] = { + var newContent: Array[Any] = null + val iN = payloadArity // arity doesn't change during this operation + val jN = nodeArity // arity doesn't change during this operation + val newContentLength = content.length + var i = 0 + while (i < iN) { + val key = getKey(i) + val value = getValue(i) + val newValue = f(key, value) + if (newContent eq null) { + if (newValue.asInstanceOf[AnyRef] ne value.asInstanceOf[AnyRef]) { + newContent = content.clone() + newContent(TupleLength * i + 1) = newValue + } + } else { + newContent(TupleLength * i + 1) = newValue + } + i += 1 + } + + var j = 0 + while (j < jN) { + val node = getNode(j) + val newNode = node.transform(f) + if (newContent eq null) { + if (newNode ne node) { + newContent = content.clone() + newContent(newContentLength - j - 1) = newNode + } + } else + newContent(newContentLength - j - 1) = newNode + j += 1 + } + if (newContent eq null) this.asInstanceOf[BitmapIndexedMapNode[K, W]] + else new BitmapIndexedMapNode[K, W](dataMap, nodeMap, newContent, originalHashes, size, cachedJavaKeySetHashCode) + } + + override def mergeInto[V1 >: V](that: MapNode[K, V1], builder: HashMapBuilder[K, V1], shift: Int)(mergef: ((K, V), (K, V1)) => (K, V1)): Unit = that match { + case bm: BitmapIndexedMapNode[K, V] @unchecked => + if (size == 0) { + that.buildTo(builder) + return + } else if (bm.size == 0) { + buildTo(builder) + return + } + + val allMap = dataMap | bm.dataMap | nodeMap | bm.nodeMap + + val minIndex: Int = Integer.numberOfTrailingZeros(allMap) + val maxIndex: Int = Node.BranchingFactor - Integer.numberOfLeadingZeros(allMap) + + { + var index = minIndex + var leftIdx = 0 + var rightIdx = 0 + + while (index < maxIndex) { + val bitpos = bitposFrom(index) + + if ((bitpos & dataMap) != 0) { + val leftKey = getKey(leftIdx) + val leftValue = getValue(leftIdx) + val leftOriginalHash = getHash(leftIdx) + if ((bitpos & bm.dataMap) != 0) { + // left data and right data + val rightKey = bm.getKey(rightIdx) + val rightValue = bm.getValue(rightIdx) + val rightOriginalHash = bm.getHash(rightIdx) + if (leftOriginalHash == rightOriginalHash && leftKey == rightKey) { + builder.addOne(mergef((leftKey, leftValue), (rightKey, rightValue))) + } else { + builder.addOne(leftKey, leftValue, leftOriginalHash) + builder.addOne(rightKey, rightValue, rightOriginalHash) + } + rightIdx += 1 + } else if ((bitpos & bm.nodeMap) != 0) { + // left data and right node + val subNode = bm.getNode(bm.nodeIndex(bitpos)) + val leftImprovedHash = improve(leftOriginalHash) + val removed = subNode.removed(leftKey, leftOriginalHash, leftImprovedHash, shift + BitPartitionSize) + if (removed eq subNode) { + // no overlap in leftData and rightNode, just build both children to builder + subNode.buildTo(builder) + builder.addOne(leftKey, leftValue, leftOriginalHash, leftImprovedHash) + } else { + // there is collision, so special treatment for that key + removed.buildTo(builder) + builder.addOne(mergef((leftKey, leftValue), subNode.getTuple(leftKey, leftOriginalHash, leftImprovedHash, shift + BitPartitionSize))) + } + } else { + // left data and nothing on right + builder.addOne(leftKey, leftValue, leftOriginalHash) + } + leftIdx += 1 + } else if ((bitpos & nodeMap) != 0) { + if ((bitpos & bm.dataMap) != 0) { + // left node and right data + val rightKey = bm.getKey(rightIdx) + val rightValue = bm.getValue(rightIdx) + val rightOriginalHash = bm.getHash(rightIdx) + val rightImprovedHash = improve(rightOriginalHash) + + val subNode = getNode(nodeIndex(bitpos)) + val removed = subNode.removed(rightKey, rightOriginalHash, rightImprovedHash, shift + BitPartitionSize) + if (removed eq subNode) { + // no overlap in leftNode and rightData, just build both children to builder + subNode.buildTo(builder) + builder.addOne(rightKey, rightValue, rightOriginalHash, rightImprovedHash) + } else { + // there is collision, so special treatment for that key + removed.buildTo(builder) + builder.addOne(mergef(subNode.getTuple(rightKey, rightOriginalHash, rightImprovedHash, shift + BitPartitionSize), (rightKey, rightValue))) + } + rightIdx += 1 + + } else if ((bitpos & bm.nodeMap) != 0) { + // left node and right node + getNode(nodeIndex(bitpos)).mergeInto(bm.getNode(bm.nodeIndex(bitpos)), builder, shift + BitPartitionSize)(mergef) + } else { + // left node and nothing on right + getNode(nodeIndex(bitpos)).buildTo(builder) + } + } else if ((bitpos & bm.dataMap) != 0) { + // nothing on left, right data + val dataIndex = bm.dataIndex(bitpos) + builder.addOne(bm.getKey(dataIndex),bm.getValue(dataIndex), bm.getHash(dataIndex)) + rightIdx += 1 + + } else if ((bitpos & bm.nodeMap) != 0) { + // nothing on left, right node + bm.getNode(bm.nodeIndex(bitpos)).buildTo(builder) + } + + index += 1 + } + } + case _: HashCollisionMapNode[_, _] => + throw new RuntimeException("Cannot merge BitmapIndexedMapNode with HashCollisionMapNode") + } + + override def equals(that: Any): Boolean = + that match { + case node: BitmapIndexedMapNode[_, _] => + (this eq node) || + (this.cachedJavaKeySetHashCode == node.cachedJavaKeySetHashCode) && + (this.nodeMap == node.nodeMap) && + (this.dataMap == node.dataMap) && + (this.size == node.size) && + java.util.Arrays.equals(this.originalHashes, node.originalHashes) && + deepContentEquality(this.content, node.content, content.length) + case _ => false + } + + @inline private def deepContentEquality(a1: Array[Any], a2: Array[Any], length: Int): Boolean = { + if (a1 eq a2) + true + else { + var isEqual = true + var i = 0 + + while (isEqual && i < length) { + isEqual = a1(i) == a2(i) + i += 1 + } + + isEqual + } + } + + override def hashCode(): Int = + throw new UnsupportedOperationException("Trie nodes do not support hashing.") + + override def concat[V1 >: V](that: MapNode[K, V1], shift: Int): BitmapIndexedMapNode[K, V1] = that match { + case bm: BitmapIndexedMapNode[K, V] @unchecked => + if (size == 0) return bm + else if (bm.size == 0 || (bm eq this)) return this + else if (bm.size == 1) { + val originalHash = bm.getHash(0) + return this.updated(bm.getKey(0), bm.getValue(0), originalHash, improve(originalHash), shift, replaceValue = true) + } + // if we go through the merge and the result does not differ from `bm`, we can just return `bm`, to improve sharing + // So, `anyChangesMadeSoFar` will be set to `true` as soon as we encounter a difference between the + // currently-being-computed result, and `bm` + var anyChangesMadeSoFar = false + + val allMap = dataMap | bm.dataMap | nodeMap | bm.nodeMap + + // minimumIndex is inclusive -- it is the first index for which there is data or nodes + val minimumBitPos: Int = Node.bitposFrom(Integer.numberOfTrailingZeros(allMap)) + // maximumIndex is inclusive -- it is the last index for which there is data or nodes + // it could not be exclusive, because then upper bound in worst case (Node.BranchingFactor) would be out-of-bound + // of int bitposition representation + val maximumBitPos: Int = Node.bitposFrom(Node.BranchingFactor - Integer.numberOfLeadingZeros(allMap) - 1) + + var leftNodeRightNode = 0 + var leftDataRightNode = 0 + var leftNodeRightData = 0 + var leftDataOnly = 0 + var rightDataOnly = 0 + var leftNodeOnly = 0 + var rightNodeOnly = 0 + var leftDataRightDataMigrateToNode = 0 + var leftDataRightDataRightOverwrites = 0 + + var dataToNodeMigrationTargets = 0 + + { + var bitpos = minimumBitPos + var leftIdx = 0 + var rightIdx = 0 + var finished = false + + while (!finished) { + + if ((bitpos & dataMap) != 0) { + if ((bitpos & bm.dataMap) != 0) { + val leftOriginalHash = getHash(leftIdx) + if (leftOriginalHash == bm.getHash(rightIdx) && getKey(leftIdx) == bm.getKey(rightIdx)) { + leftDataRightDataRightOverwrites |= bitpos + } else { + leftDataRightDataMigrateToNode |= bitpos + dataToNodeMigrationTargets |= Node.bitposFrom(Node.maskFrom(improve(leftOriginalHash), shift)) + } + rightIdx += 1 + } else if ((bitpos & bm.nodeMap) != 0) { + leftDataRightNode |= bitpos + } else { + leftDataOnly |= bitpos + } + leftIdx += 1 + } else if ((bitpos & nodeMap) != 0) { + if ((bitpos & bm.dataMap) != 0) { + leftNodeRightData |= bitpos + rightIdx += 1 + } else if ((bitpos & bm.nodeMap) != 0) { + leftNodeRightNode |= bitpos + } else { + leftNodeOnly |= bitpos + } + } else if ((bitpos & bm.dataMap) != 0) { + rightDataOnly |= bitpos + rightIdx += 1 + } else if ((bitpos & bm.nodeMap) != 0) { + rightNodeOnly |= bitpos + } + + if (bitpos == maximumBitPos) { + finished = true + } else { + bitpos = bitpos << 1 + } + } + } + + + val newDataMap = leftDataOnly | rightDataOnly | leftDataRightDataRightOverwrites + + val newNodeMap = + leftNodeRightNode | + leftDataRightNode | + leftNodeRightData | + leftNodeOnly | + rightNodeOnly | + dataToNodeMigrationTargets + + + if ((newDataMap == (rightDataOnly | leftDataRightDataRightOverwrites)) && (newNodeMap == rightNodeOnly)) { + // nothing from `this` will make it into the result -- return early + return bm + } + + val newDataSize = bitCount(newDataMap) + val newContentSize = (MapNode.TupleLength * newDataSize) + bitCount(newNodeMap) + + val newContent = new Array[Any](newContentSize) + val newOriginalHashes = new Array[Int](newDataSize) + var newSize = 0 + var newCachedHashCode = 0 + + { + var leftDataIdx = 0 + var rightDataIdx = 0 + var leftNodeIdx = 0 + var rightNodeIdx = 0 + + val nextShift = shift + Node.BitPartitionSize + + var compressedDataIdx = 0 + var compressedNodeIdx = 0 + + var bitpos = minimumBitPos + var finished = false + + while (!finished) { + + if ((bitpos & leftNodeRightNode) != 0) { + val rightNode = bm.getNode(rightNodeIdx) + val newNode = getNode(leftNodeIdx).concat(rightNode, nextShift) + if (rightNode ne newNode) { + anyChangesMadeSoFar = true + } + newContent(newContentSize - compressedNodeIdx - 1) = newNode + compressedNodeIdx += 1 + rightNodeIdx += 1 + leftNodeIdx += 1 + newSize += newNode.size + newCachedHashCode += newNode.cachedJavaKeySetHashCode + + } else if ((bitpos & leftDataRightNode) != 0) { + val newNode = { + val n = bm.getNode(rightNodeIdx) + val leftKey = getKey(leftDataIdx) + val leftValue = getValue(leftDataIdx) + val leftOriginalHash = getHash(leftDataIdx) + val leftImproved = improve(leftOriginalHash) + + val updated = n.updated(leftKey, leftValue, leftOriginalHash, leftImproved, nextShift, replaceValue = false) + + if (updated ne n) { + anyChangesMadeSoFar = true + } + + updated + } + + newContent(newContentSize - compressedNodeIdx - 1) = newNode + compressedNodeIdx += 1 + rightNodeIdx += 1 + leftDataIdx += 1 + newSize += newNode.size + newCachedHashCode += newNode.cachedJavaKeySetHashCode + } + else if ((bitpos & leftNodeRightData) != 0) { + anyChangesMadeSoFar = true + val newNode = { + val rightOriginalHash = bm.getHash(rightDataIdx) + getNode(leftNodeIdx).updated( + key = bm.getKey(rightDataIdx), + value = bm.getValue(rightDataIdx), + originalHash = bm.getHash(rightDataIdx), + hash = improve(rightOriginalHash), + shift = nextShift, + replaceValue = true + ) + } + + newContent(newContentSize - compressedNodeIdx - 1) = newNode + compressedNodeIdx += 1 + leftNodeIdx += 1 + rightDataIdx += 1 + newSize += newNode.size + newCachedHashCode += newNode.cachedJavaKeySetHashCode + + } else if ((bitpos & leftDataOnly) != 0) { + anyChangesMadeSoFar = true + val originalHash = originalHashes(leftDataIdx) + newContent(MapNode.TupleLength * compressedDataIdx) = getKey(leftDataIdx).asInstanceOf[AnyRef] + newContent(MapNode.TupleLength * compressedDataIdx + 1) = getValue(leftDataIdx).asInstanceOf[AnyRef] + newOriginalHashes(compressedDataIdx) = originalHash + + compressedDataIdx += 1 + leftDataIdx += 1 + newSize += 1 + newCachedHashCode += improve(originalHash) + } else if ((bitpos & rightDataOnly) != 0) { + val originalHash = bm.originalHashes(rightDataIdx) + newContent(MapNode.TupleLength * compressedDataIdx) = bm.getKey(rightDataIdx).asInstanceOf[AnyRef] + newContent(MapNode.TupleLength * compressedDataIdx + 1) = bm.getValue(rightDataIdx).asInstanceOf[AnyRef] + newOriginalHashes(compressedDataIdx) = originalHash + + compressedDataIdx += 1 + rightDataIdx += 1 + newSize += 1 + newCachedHashCode += improve(originalHash) + } else if ((bitpos & leftNodeOnly) != 0) { + anyChangesMadeSoFar = true + val newNode = getNode(leftNodeIdx) + newContent(newContentSize - compressedNodeIdx - 1) = newNode + compressedNodeIdx += 1 + leftNodeIdx += 1 + newSize += newNode.size + newCachedHashCode += newNode.cachedJavaKeySetHashCode + } else if ((bitpos & rightNodeOnly) != 0) { + val newNode = bm.getNode(rightNodeIdx) + newContent(newContentSize - compressedNodeIdx - 1) = newNode + compressedNodeIdx += 1 + rightNodeIdx += 1 + newSize += newNode.size + newCachedHashCode += newNode.cachedJavaKeySetHashCode + } else if ((bitpos & leftDataRightDataMigrateToNode) != 0) { + anyChangesMadeSoFar = true + val newNode = { + val leftOriginalHash = getHash(leftDataIdx) + val rightOriginalHash = bm.getHash(rightDataIdx) + + bm.mergeTwoKeyValPairs( + getKey(leftDataIdx), getValue(leftDataIdx), leftOriginalHash, improve(leftOriginalHash), + bm.getKey(rightDataIdx), bm.getValue(rightDataIdx), rightOriginalHash, improve(rightOriginalHash), + nextShift + ) + } + + newContent(newContentSize - compressedNodeIdx - 1) = newNode + compressedNodeIdx += 1 + leftDataIdx += 1 + rightDataIdx += 1 + newSize += newNode.size + newCachedHashCode += newNode.cachedJavaKeySetHashCode + } else if ((bitpos & leftDataRightDataRightOverwrites) != 0) { + val originalHash = bm.originalHashes(rightDataIdx) + newContent(MapNode.TupleLength * compressedDataIdx) = bm.getKey(rightDataIdx).asInstanceOf[AnyRef] + newContent(MapNode.TupleLength * compressedDataIdx + 1) = bm.getValue(rightDataIdx).asInstanceOf[AnyRef] + newOriginalHashes(compressedDataIdx) = originalHash + + compressedDataIdx += 1 + rightDataIdx += 1 + newSize += 1 + newCachedHashCode += improve(originalHash) + leftDataIdx += 1 + } + + if (bitpos == maximumBitPos) { + finished = true + } else { + bitpos = bitpos << 1 + } + } + } + + if (anyChangesMadeSoFar) + new BitmapIndexedMapNode( + dataMap = newDataMap, + nodeMap = newNodeMap, + content = newContent, + originalHashes = newOriginalHashes, + size = newSize, + cachedJavaKeySetHashCode = newCachedHashCode + ) + else bm + + case _ => + // should never happen -- hash collisions are never at the same level as bitmapIndexedMapNodes + throw new UnsupportedOperationException("Cannot concatenate a HashCollisionMapNode with a BitmapIndexedMapNode") + } + + override def copy(): BitmapIndexedMapNode[K, V] = { + val contentClone = content.clone() + val contentLength = contentClone.length + var i = bitCount(dataMap) * TupleLength + while (i < contentLength) { + contentClone(i) = contentClone(i).asInstanceOf[MapNode[K, V]].copy() + i += 1 + } + new BitmapIndexedMapNode[K, V](dataMap, nodeMap, contentClone, originalHashes.clone(), size, cachedJavaKeySetHashCode) + } + + override def filterImpl(pred: ((K, V)) => Boolean, flipped: Boolean): BitmapIndexedMapNode[K, V] = { + if (size == 0) this + else if (size == 1) { + if (pred(getPayload(0)) != flipped) this else MapNode.empty + } else if (nodeMap == 0) { + // Performance optimization for nodes of depth 1: + // + // this node has no "node" children, all children are inlined data elems, therefor logic is significantly simpler + // approach: + // * traverse the content array, accumulating in `newDataMap: Int` any bit positions of keys which pass the filter + // * (bitCount(newDataMap) * TupleLength) tells us the new content array and originalHashes array size, so now perform allocations + // * traverse the content array once more, placing each passing element (according to `newDatamap`) in the new content and originalHashes arrays + // + // note: + // * this optimization significantly improves performance of not only small trees, but also larger trees, since + // even non-root nodes are affected by this improvement, and large trees will consist of many nodes as + // descendants + // + val minimumIndex: Int = Integer.numberOfTrailingZeros(dataMap) + val maximumIndex: Int = Node.BranchingFactor - Integer.numberOfLeadingZeros(dataMap) + + var newDataMap = 0 + var newCachedHashCode = 0 + var dataIndex = 0 + + var i = minimumIndex + + while(i < maximumIndex) { + val bitpos = bitposFrom(i) + + if ((bitpos & dataMap) != 0) { + val payload = getPayload(dataIndex) + val passed = pred(payload) != flipped + + if (passed) { + newDataMap |= bitpos + newCachedHashCode += improve(getHash(dataIndex)) + } + + dataIndex += 1 + } + + i += 1 + } + + if (newDataMap == 0) { + MapNode.empty + } else if (newDataMap == dataMap) { + this + } else { + val newSize = Integer.bitCount(newDataMap) + val newContent = new Array[Any](newSize * TupleLength) + val newOriginalHashCodes = new Array[Int](newSize) + val newMaximumIndex: Int = Node.BranchingFactor - Integer.numberOfLeadingZeros(newDataMap) + + var j = Integer.numberOfTrailingZeros(newDataMap) + + var newDataIndex = 0 + + + while (j < newMaximumIndex) { + val bitpos = bitposFrom(j) + if ((bitpos & newDataMap) != 0) { + val oldIndex = indexFrom(dataMap, bitpos) + newContent(newDataIndex * TupleLength) = content(oldIndex * TupleLength) + newContent(newDataIndex * TupleLength + 1) = content(oldIndex * TupleLength + 1) + newOriginalHashCodes(newDataIndex) = originalHashes(oldIndex) + newDataIndex += 1 + } + j += 1 + } + + new BitmapIndexedMapNode(newDataMap, 0, newContent, newOriginalHashCodes, newSize, newCachedHashCode) + } + + + } else { + val allMap = dataMap | nodeMap + val minimumIndex: Int = Integer.numberOfTrailingZeros(allMap) + val maximumIndex: Int = Node.BranchingFactor - Integer.numberOfLeadingZeros(allMap) + + var oldDataPassThrough = 0 + + // bitmap of nodes which, when filtered, returned a single-element node. These must be migrated to data + var nodeMigrateToDataTargetMap = 0 + // the queue of single-element, post-filter nodes + var nodesToMigrateToData: mutable.Queue[MapNode[K, V]] = null + + // bitmap of all nodes which, when filtered, returned themselves. They are passed forward to the returned node + var nodesToPassThroughMap = 0 + + // bitmap of any nodes which, after being filtered, returned a node that is not empty, but also not `eq` itself + // These are stored for later inclusion into the final `content` array + // not named `newNodesMap` (plural) to avoid confusion with `newNodeMap` (singular) + var mapOfNewNodes = 0 + // each bit in `mapOfNewNodes` corresponds to one element in this queue + var newNodes: mutable.Queue[MapNode[K, V]] = null + + var newDataMap = 0 + var newNodeMap = 0 + var newSize = 0 + var newCachedHashCode = 0 + + var dataIndex = 0 + var nodeIndex = 0 + + var i = minimumIndex + while (i < maximumIndex) { + val bitpos = bitposFrom(i) + + if ((bitpos & dataMap) != 0) { + val payload = getPayload(dataIndex) + val passed = pred(payload) != flipped + + if (passed) { + newDataMap |= bitpos + oldDataPassThrough |= bitpos + newSize += 1 + newCachedHashCode += improve(getHash(dataIndex)) + } + + dataIndex += 1 + } else if ((bitpos & nodeMap) != 0) { + val oldSubNode = getNode(nodeIndex) + val newSubNode = oldSubNode.filterImpl(pred, flipped) + + newSize += newSubNode.size + newCachedHashCode += newSubNode.cachedJavaKeySetHashCode + + // if (newSubNode.size == 0) do nothing (drop it) + if (newSubNode.size > 1) { + newNodeMap |= bitpos + if (oldSubNode eq newSubNode) { + nodesToPassThroughMap |= bitpos + } else { + mapOfNewNodes |= bitpos + if (newNodes eq null) { + newNodes = mutable.Queue.empty + } + newNodes += newSubNode + } + } else if (newSubNode.size == 1) { + newDataMap |= bitpos + nodeMigrateToDataTargetMap |= bitpos + if (nodesToMigrateToData eq null) { + nodesToMigrateToData = mutable.Queue() + } + nodesToMigrateToData += newSubNode + } + + nodeIndex += 1 + } + + i += 1 + } + + if (newSize == 0) { + MapNode.empty + } else if (newSize == size) { + this + } else { + val newDataSize = bitCount(newDataMap) + val newContentSize = (MapNode.TupleLength * newDataSize) + bitCount(newNodeMap) + val newContent = new Array[Any](newContentSize) + val newOriginalHashes = new Array[Int](newDataSize) + + val newAllMap = newDataMap | newNodeMap + val maxIndex = Node.BranchingFactor - Integer.numberOfLeadingZeros(newAllMap) + + // note: We MUST start from the minimum index in the old (`this`) node, otherwise `old{Node,Data}Index` will + // not be incremented properly. Otherwise we could have started at Integer.numberOfTrailingZeroes(newAllMap) + var i = minimumIndex + + var oldDataIndex = 0 + var oldNodeIndex = 0 + + var newDataIndex = 0 + var newNodeIndex = 0 + + while (i < maxIndex) { + val bitpos = bitposFrom(i) + + if ((bitpos & oldDataPassThrough) != 0) { + newContent(newDataIndex * TupleLength) = getKey(oldDataIndex) + newContent(newDataIndex * TupleLength + 1) = getValue(oldDataIndex) + newOriginalHashes(newDataIndex) = getHash(oldDataIndex) + newDataIndex += 1 + oldDataIndex += 1 + } else if ((bitpos & nodesToPassThroughMap) != 0) { + newContent(newContentSize - newNodeIndex - 1) = getNode(oldNodeIndex) + newNodeIndex += 1 + oldNodeIndex += 1 + } else if ((bitpos & nodeMigrateToDataTargetMap) != 0) { + // we need not check for null here. If nodeMigrateToDataTargetMap != 0, then nodesMigrateToData must not be null + val node = nodesToMigrateToData.dequeue() + newContent(TupleLength * newDataIndex) = node.getKey(0) + newContent(TupleLength * newDataIndex + 1) = node.getValue(0) + newOriginalHashes(newDataIndex) = node.getHash(0) + newDataIndex += 1 + oldNodeIndex += 1 + } else if ((bitpos & mapOfNewNodes) != 0) { + newContent(newContentSize - newNodeIndex - 1) = newNodes.dequeue() + newNodeIndex += 1 + oldNodeIndex += 1 + } else if ((bitpos & dataMap) != 0) { + oldDataIndex += 1 + } else if ((bitpos & nodeMap) != 0) { + oldNodeIndex += 1 + } + + i += 1 + } + + new BitmapIndexedMapNode[K, V](newDataMap, newNodeMap, newContent, newOriginalHashes, newSize, newCachedHashCode) + } + } + } +} + +private final class HashCollisionMapNode[K, +V ]( + val originalHash: Int, + val hash: Int, + var content: Vector[(K, V @uV)] + ) extends MapNode[K, V] { + + import Node._ + + require(content.length >= 2) + + releaseFence() + + private[immutable] def indexOf(key: Any): Int = { + val iter = content.iterator + var i = 0 + while (iter.hasNext) { + if (iter.next()._1 == key) return i + i += 1 + } + -1 + } + + def size: Int = content.length + + def apply(key: K, originalHash: Int, hash: Int, shift: Int): V = get(key, originalHash, hash, shift).getOrElse(Iterator.empty.next()) + + def get(key: K, originalHash: Int, hash: Int, shift: Int): Option[V] = + if (this.hash == hash) { + val index = indexOf(key) + if (index >= 0) Some(content(index)._2) else None + } else None + + override def getTuple(key: K, originalHash: Int, hash: Int, shift: Int): (K, V) = { + val index = indexOf(key) + if (index >= 0) content(index) else Iterator.empty.next() + } + + def getOrElse[V1 >: V](key: K, originalHash: Int, hash: Int, shift: Int, f: => V1): V1 = { + if (this.hash == hash) { + indexOf(key) match { + case -1 => f + case other => content(other)._2 + } + } else f + } + + override def containsKey(key: K, originalHash: Int, hash: Int, shift: Int): Boolean = + this.hash == hash && indexOf(key) >= 0 + + def contains[V1 >: V](key: K, value: V1, hash: Int, shift: Int): Boolean = + this.hash == hash && { + val index = indexOf(key) + index >= 0 && (content(index)._2.asInstanceOf[AnyRef] eq value.asInstanceOf[AnyRef]) + } + + def updated[V1 >: V](key: K, value: V1, originalHash: Int, hash: Int, shift: Int, replaceValue: Boolean): MapNode[K, V1] = { + val index = indexOf(key) + if (index >= 0) { + if (replaceValue) { + if (content(index)._2.asInstanceOf[AnyRef] eq value.asInstanceOf[AnyRef]) { + this + } else { + new HashCollisionMapNode[K, V1](originalHash, hash, content.updated[(K, V1)](index, (key, value))) + } + } else { + this + } + } else { + new HashCollisionMapNode[K, V1](originalHash, hash, content.appended[(K, V1)]((key, value))) + } + } + + def removed[V1 >: V](key: K, originalHash: Int, hash: Int, shift: Int): MapNode[K, V1] = { + if (!this.containsKey(key, originalHash, hash, shift)) { + this + } else { + val updatedContent = content.filterNot(keyValuePair => keyValuePair._1 == key) + // assert(updatedContent.size == content.size - 1) + + updatedContent.size match { + case 1 => + val (k, v) = updatedContent(0) + new BitmapIndexedMapNode[K, V1](bitposFrom(maskFrom(hash, 0)), 0, Array(k, v), Array(originalHash), 1, hash) + case _ => new HashCollisionMapNode[K, V1](originalHash, hash, updatedContent) + } + } + } + + def hasNodes: Boolean = false + + def nodeArity: Int = 0 + + def getNode(index: Int): MapNode[K, V] = + throw new IndexOutOfBoundsException("No sub-nodes present in hash-collision leaf node.") + + def hasPayload: Boolean = true + + def payloadArity: Int = content.length + + def getKey(index: Int): K = getPayload(index)._1 + def getValue(index: Int): V = getPayload(index)._2 + + def getPayload(index: Int): (K, V) = content(index) + + override def getHash(index: Int): Int = originalHash + + def foreach[U](f: ((K, V)) => U): Unit = content.foreach(f) + + def foreachEntry[U](f: (K, V) => U): Unit = content.foreach { case (k, v) => f(k, v)} + + override def foreachWithHash(f: (K, V, Int) => Unit): Unit = { + val iter = content.iterator + while (iter.hasNext) { + val next = iter.next() + f(next._1, next._2, originalHash) + } + } + + override def transform[W](f: (K, V) => W): HashCollisionMapNode[K, W] = { + val newContent = Vector.newBuilder[(K, W)] + val contentIter = content.iterator + // true if any values have been transformed to a different value via `f` + var anyChanges = false + while(contentIter.hasNext) { + val (k, v) = contentIter.next() + val newValue = f(k, v) + newContent.addOne((k, newValue)) + anyChanges ||= (v.asInstanceOf[AnyRef] ne newValue.asInstanceOf[AnyRef]) + } + if (anyChanges) new HashCollisionMapNode(originalHash, hash, newContent.result()) + else this.asInstanceOf[HashCollisionMapNode[K, W]] + } + + override def equals(that: Any): Boolean = + that match { + case node: HashCollisionMapNode[_, _] => + (this eq node) || + (this.hash == node.hash) && + (this.content.length == node.content.length) && { + val iter = content.iterator + while (iter.hasNext) { + val (key, value) = iter.next() + val index = node.indexOf(key) + if (index < 0 || value != node.content(index)._2) { + return false + } + } + true + } + case _ => false + } + + override def concat[V1 >: V](that: MapNode[K, V1], shift: Int): HashCollisionMapNode[K, V1] = that match { + case hc: HashCollisionMapNode[K, V1] => + if (hc eq this) { + this + } else { + var newContent: VectorBuilder[(K, V1)] = null + val iter = content.iterator + while (iter.hasNext) { + val nextPayload = iter.next() + if (hc.indexOf(nextPayload._1) < 0) { + if (newContent eq null) { + newContent = new VectorBuilder[(K, V1)]() + newContent.addAll(hc.content) + } + newContent.addOne(nextPayload) + } + } + if (newContent eq null) hc else new HashCollisionMapNode(originalHash, hash, newContent.result()) + } + case _: BitmapIndexedMapNode[K, V1] => + // should never happen -- hash collisions are never at the same level as bitmapIndexedMapNodes + throw new UnsupportedOperationException("Cannot concatenate a HashCollisionMapNode with a BitmapIndexedMapNode") + } + + + override def mergeInto[V1 >: V](that: MapNode[K, V1], builder: HashMapBuilder[K, V1], shift: Int)(mergef: ((K, V), (K, V1)) => (K, V1)): Unit = that match { + case hc: HashCollisionMapNode[K, V1] => + val iter = content.iterator + val rightArray = hc.content.toArray[AnyRef] // really Array[(K, V1)] + + def rightIndexOf(key: K): Int = { + var i = 0 + while (i < rightArray.length) { + val elem = rightArray(i) + if ((elem ne null) && (elem.asInstanceOf[(K, V1)])._1 == key) return i + i += 1 + } + -1 + } + + while (iter.hasNext) { + val nextPayload = iter.next() + val index = rightIndexOf(nextPayload._1) + + if (index == -1) { + builder.addOne(nextPayload) + } else { + val rightPayload = rightArray(index).asInstanceOf[(K, V1)] + rightArray(index) = null + + builder.addOne(mergef(nextPayload, rightPayload)) + } + } + + var i = 0 + while (i < rightArray.length) { + val elem = rightArray(i) + if (elem ne null) builder.addOne(elem.asInstanceOf[(K, V1)]) + i += 1 + } + case _: BitmapIndexedMapNode[K, V1] => + throw new RuntimeException("Cannot merge HashCollisionMapNode with BitmapIndexedMapNode") + + } + + override def buildTo[V1 >: V](builder: HashMapBuilder[K, V1]): Unit = { + val iter = content.iterator + while (iter.hasNext) { + val (k, v) = iter.next() + builder.addOne(k, v, originalHash, hash) + } + } + + override def filterImpl(pred: ((K, V)) => Boolean, flipped: Boolean): MapNode[K, V] = { + val newContent = content.filterImpl(pred, flipped) + val newContentLength = newContent.length + if (newContentLength == 0) { + MapNode.empty + } else if (newContentLength == 1) { + val (k, v) = newContent.head + new BitmapIndexedMapNode[K, V](bitposFrom(maskFrom(hash, 0)), 0, Array(k, v), Array(originalHash), 1, hash) + } else if (newContentLength == content.length) this + else new HashCollisionMapNode(originalHash, hash, newContent) + } + + override def copy(): HashCollisionMapNode[K, V] = new HashCollisionMapNode[K, V](originalHash, hash, content) + + override def hashCode(): Int = + throw new UnsupportedOperationException("Trie nodes do not support hashing.") + + override def cachedJavaKeySetHashCode: Int = size * hash + +} + +private final class MapKeyIterator[K, V](rootNode: MapNode[K, V]) + extends ChampBaseIterator[K, MapNode[K, V]](rootNode) { + + def next() = { + if (!hasNext) Iterator.empty.next() + + val key = currentValueNode.getKey(currentValueCursor) + currentValueCursor += 1 + + key + } + +} + +private final class MapValueIterator[K, V](rootNode: MapNode[K, V]) + extends ChampBaseIterator[V, MapNode[K, V]](rootNode) { + + def next() = { + if (!hasNext) Iterator.empty.next() + + val value = currentValueNode.getValue(currentValueCursor) + currentValueCursor += 1 + + value + } +} + +private final class MapKeyValueTupleIterator[K, V](rootNode: MapNode[K, V]) + extends ChampBaseIterator[(K, V), MapNode[K, V]](rootNode) { + + def next() = { + if (!hasNext) Iterator.empty.next() + + val payload = currentValueNode.getPayload(currentValueCursor) + currentValueCursor += 1 + + payload + } + +} + +private final class MapKeyValueTupleReverseIterator[K, V](rootNode: MapNode[K, V]) + extends ChampBaseReverseIterator[(K, V), MapNode[K, V]](rootNode) { + + def next() = { + if (!hasNext) Iterator.empty.next() + + val payload = currentValueNode.getPayload(currentValueCursor) + currentValueCursor -= 1 + + payload + } +} + +private final class MapKeyValueTupleHashIterator[K, V](rootNode: MapNode[K, V]) + extends ChampBaseReverseIterator[Any, MapNode[K, V]](rootNode) { + private[this] var hash = 0 + private[this] var value: V = _ + override def hashCode(): Int = MurmurHash3.tuple2Hash(hash, value.##, MurmurHash3.productSeed) + def next(): MapKeyValueTupleHashIterator[K, V] = { + if (!hasNext) Iterator.empty.next() + + hash = currentValueNode.getHash(currentValueCursor) + value = currentValueNode.getValue(currentValueCursor) + currentValueCursor -= 1 + this + } +} + +/** Used in HashMap[K, V]#removeAll(HashSet[K]) */ +private final class MapNodeRemoveAllSetNodeIterator[K](rootSetNode: SetNode[K]) extends ChampBaseIterator[K, SetNode[K]](rootSetNode) { + /** Returns the result of immutably removing all keys in `rootSetNode` from `rootMapNode` */ + def removeAll[V](rootMapNode: BitmapIndexedMapNode[K, V]): BitmapIndexedMapNode[K, V] = { + var curr = rootMapNode + while (curr.size > 0 && hasNext) { + val originalHash = currentValueNode.getHash(currentValueCursor) + curr = curr.removed( + key = currentValueNode.getPayload(currentValueCursor), + keyHash = improve(originalHash), + originalHash = originalHash, + shift = 0 + ) + currentValueCursor += 1 + } + curr + } + + override def next(): K = Iterator.empty.next() +} + +/** + * $factoryInfo + * + * @define Coll `immutable.HashMap` + * @define coll immutable champ hash map + */ +@SerialVersionUID(3L) +object HashMap extends MapFactory[HashMap] { + + @transient + private final val EmptyMap = new HashMap(MapNode.empty) + + def empty[K, V]: HashMap[K, V] = + EmptyMap.asInstanceOf[HashMap[K, V]] + + def from[K, V](source: collection.IterableOnce[(K, V)]): HashMap[K, V] = + source match { + case hs: HashMap[K, V] => hs + case _ => (newBuilder[K, V] ++= source).result() + } + + /** Create a new Builder which can be reused after calling `result()` without an + * intermediate call to `clear()` in order to build multiple related results. + */ + def newBuilder[K, V]: ReusableBuilder[(K, V), HashMap[K, V]] = new HashMapBuilder[K, V] +} + + +/** A Builder for a HashMap. + * $multipleResults + */ +private[immutable] final class HashMapBuilder[K, V] extends ReusableBuilder[(K, V), HashMap[K, V]] { + import MapNode._ + import Node._ + + private def newEmptyRootNode = new BitmapIndexedMapNode[K, V](0, 0, Array.emptyObjectArray.asInstanceOf[Array[Any]], Array.emptyIntArray, 0, 0) + + /** The last given out HashMap as a return value of `result()`, if any, otherwise null. + * Indicates that on next add, the elements should be copied to an identical structure, before continuing + * mutations. */ + private var aliased: HashMap[K, V] = _ + + private def isAliased: Boolean = aliased != null + + /** The root node of the partially built hashmap. */ + private var rootNode: BitmapIndexedMapNode[K, V] = newEmptyRootNode + + private[immutable] def getOrElse[V0 >: V](key: K, value: V0): V0 = + if (rootNode.size == 0) value + else { + val originalHash = key.## + rootNode.getOrElse(key, originalHash, improve(originalHash), 0, value) + } + + /** Inserts element `elem` into array `as` at index `ix`, shifting right the trailing elems */ + private[this] def insertElement(as: Array[Int], ix: Int, elem: Int): Array[Int] = { + if (ix < 0) throw new ArrayIndexOutOfBoundsException + if (ix > as.length) throw new ArrayIndexOutOfBoundsException + val result = new Array[Int](as.length + 1) + arraycopy(as, 0, result, 0, ix) + result(ix) = elem + arraycopy(as, ix, result, ix + 1, as.length - ix) + result + } + + /** Inserts key-value into the bitmapIndexMapNode. Requires that this is a new key-value pair */ + private[this] def insertValue[V1 >: V](bm: BitmapIndexedMapNode[K, V],bitpos: Int, key: K, originalHash: Int, keyHash: Int, value: V1): Unit = { + val dataIx = bm.dataIndex(bitpos) + val idx = TupleLength * dataIx + + val src = bm.content + val dst = new Array[Any](src.length + TupleLength) + + // copy 'src' and insert 2 element(s) at position 'idx' + arraycopy(src, 0, dst, 0, idx) + dst(idx) = key + dst(idx + 1) = value + arraycopy(src, idx, dst, idx + TupleLength, src.length - idx) + + val dstHashes = insertElement(bm.originalHashes, dataIx, originalHash) + + bm.dataMap |= bitpos + bm.content = dst + bm.originalHashes = dstHashes + bm.size += 1 + bm.cachedJavaKeySetHashCode += keyHash + } + + /** Upserts a key/value pair into mapNode, mutably */ + private[immutable] def update(mapNode: MapNode[K, V], key: K, value: V, originalHash: Int, keyHash: Int, shift: Int): Unit = { + mapNode match { + case bm: BitmapIndexedMapNode[K, V] => + val mask = maskFrom(keyHash, shift) + val bitpos = bitposFrom(mask) + if ((bm.dataMap & bitpos) != 0) { + val index = indexFrom(bm.dataMap, mask, bitpos) + val key0 = bm.getKey(index) + val key0UnimprovedHash = bm.getHash(index) + + if (key0UnimprovedHash == originalHash && key0 == key) { + bm.content(TupleLength * index + 1) = value + } else { + val value0 = bm.getValue(index) + val key0Hash = improve(key0UnimprovedHash) + + val subNodeNew: MapNode[K, V] = + bm.mergeTwoKeyValPairs(key0, value0, key0UnimprovedHash, key0Hash, key, value, originalHash, keyHash, shift + BitPartitionSize) + + bm.migrateFromInlineToNodeInPlace(bitpos, key0Hash, subNodeNew) + } + + } else if ((bm.nodeMap & bitpos) != 0) { + val index = indexFrom(bm.nodeMap, mask, bitpos) + val subNode = bm.getNode(index) + val beforeSize = subNode.size + val beforeHash = subNode.cachedJavaKeySetHashCode + update(subNode, key, value, originalHash, keyHash, shift + BitPartitionSize) + bm.size += subNode.size - beforeSize + bm.cachedJavaKeySetHashCode += subNode.cachedJavaKeySetHashCode - beforeHash + } else { + insertValue(bm, bitpos, key, originalHash, keyHash, value) + } + case hc: HashCollisionMapNode[K, V] => + val index = hc.indexOf(key) + if (index < 0) { + hc.content = hc.content.appended((key, value)) + } else { + hc.content = hc.content.updated(index, (key, value)) + } + } + } + + /** If currently referencing aliased structure, copy elements to new mutable structure */ + private[this] def ensureUnaliased() = { + if (isAliased) copyElems() + aliased = null + } + + /** Copy elements to new mutable structure */ + private[this] def copyElems(): Unit = { + rootNode = rootNode.copy() + } + + override def result(): HashMap[K, V] = + if (rootNode.size == 0) { + HashMap.empty + } else if (aliased != null) { + aliased + } else { + aliased = new HashMap(rootNode) + releaseFence() + aliased + } + + override def addOne(elem: (K, V)): this.type = { + ensureUnaliased() + val h = elem._1.## + val im = improve(h) + update(rootNode, elem._1, elem._2, h, im, 0) + this + } + + def addOne(key: K, value: V): this.type = { + ensureUnaliased() + val originalHash = key.## + update(rootNode, key, value, originalHash, improve(originalHash), 0) + this + } + def addOne(key: K, value: V, originalHash: Int): this.type = { + ensureUnaliased() + update(rootNode, key, value, originalHash, improve(originalHash), 0) + this + } + def addOne(key: K, value: V, originalHash: Int, hash: Int): this.type = { + ensureUnaliased() + update(rootNode, key, value, originalHash, hash, 0) + this + } + + override def addAll(xs: IterableOnce[(K, V)]): this.type = { + ensureUnaliased() + xs match { + case hm: HashMap[K, V] => + new ChampBaseIterator[(K, V), MapNode[K, V]](hm.rootNode) { + while(hasNext) { + val originalHash = currentValueNode.getHash(currentValueCursor) + update( + mapNode = rootNode, + key = currentValueNode.getKey(currentValueCursor), + value = currentValueNode.getValue(currentValueCursor), + originalHash = originalHash, + keyHash = improve(originalHash), + shift = 0 + ) + currentValueCursor += 1 + } + + override def next() = Iterator.empty.next() + } + case hm: collection.mutable.HashMap[K, V] => + val iter = hm.nodeIterator + while (iter.hasNext) { + val next = iter.next() + val originalHash = hm.unimproveHash(next.hash) + val hash = improve(originalHash) + update(rootNode, next.key, next.value, originalHash, hash, 0) + } + case lhm: collection.mutable.LinkedHashMap[K, V] => + val iter = lhm.entryIterator + while (iter.hasNext) { + val next = iter.next() + val originalHash = lhm.unimproveHash(next.hash) + val hash = improve(originalHash) + update(rootNode, next.key, next.value, originalHash, hash, 0) + } + case thatMap: Map[K, V] => + thatMap.foreachEntry((key, value) => addOne(key, value)) + case other => + val it = other.iterator + while(it.hasNext) addOne(it.next()) + } + + this + } + + override def clear(): Unit = { + aliased = null + if (rootNode.size > 0) { + rootNode = newEmptyRootNode + } + } + + private[collection] def size: Int = rootNode.size + + override def knownSize: Int = rootNode.size +} diff --git a/library/src/scala/collection/immutable/HashSet.scala b/library/src/scala/collection/immutable/HashSet.scala new file mode 100644 index 000000000000..8ce8035fd015 --- /dev/null +++ b/library/src/scala/collection/immutable/HashSet.scala @@ -0,0 +1,2117 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import java.lang.Integer.{bitCount, numberOfTrailingZeros} +import java.lang.System.arraycopy + +import scala.collection.Hashing.improve +import scala.collection.Stepper.EfficientSplit +import scala.collection.generic.DefaultSerializable +import scala.collection.mutable.ReusableBuilder +import scala.runtime.Statics.releaseFence +import scala.util.hashing.MurmurHash3 + +/** This class implements immutable sets using a Compressed Hash-Array Mapped Prefix-tree. + * See paper https://michael.steindorfer.name/publications/oopsla15.pdf for more details. + * + * @tparam A the type of the elements contained in this hash set. + * @define Coll `immutable.HashSet` + * @define coll immutable champ hash set + */ +final class HashSet[A] private[immutable](private[immutable] val rootNode: BitmapIndexedSetNode[A]) + extends AbstractSet[A] + with StrictOptimizedSetOps[A, HashSet, HashSet[A]] + with IterableFactoryDefaults[A, HashSet] + with DefaultSerializable { + + def this() = this(SetNode.empty) + + // This release fence is present because rootNode may have previously been mutated during construction. + releaseFence() + + private[this] def newHashSetOrThis(newRootNode: BitmapIndexedSetNode[A]): HashSet[A] = + if (rootNode eq newRootNode) this else new HashSet(newRootNode) + + override def iterableFactory: IterableFactory[HashSet] = HashSet + + override def knownSize: Int = rootNode.size + + override def size: Int = rootNode.size + + override def isEmpty: Boolean = rootNode.size == 0 + + def iterator: Iterator[A] = { + if (isEmpty) Iterator.empty + else new SetIterator[A](rootNode) + } + + protected[immutable] def reverseIterator: Iterator[A] = new SetReverseIterator[A](rootNode) + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit = { + import convert.impl._ + val s = shape.shape match { + case StepperShape.IntShape => IntChampStepper.from[ SetNode[A]](size, rootNode, (node, i) => node.getPayload(i).asInstanceOf[Int]) + case StepperShape.LongShape => LongChampStepper.from[ SetNode[A]](size, rootNode, (node, i) => node.getPayload(i).asInstanceOf[Long]) + case StepperShape.DoubleShape => DoubleChampStepper.from[SetNode[A]](size, rootNode, (node, i) => node.getPayload(i).asInstanceOf[Double]) + case _ => shape.parUnbox(AnyChampStepper.from[A, SetNode[A]](size, rootNode, (node, i) => node.getPayload(i))) + } + s.asInstanceOf[S with EfficientSplit] + } + + def contains(element: A): Boolean = { + val elementUnimprovedHash = element.## + val elementHash = improve(elementUnimprovedHash) + rootNode.contains(element, elementUnimprovedHash, elementHash, 0) + } + + def incl(element: A): HashSet[A] = { + val elementUnimprovedHash = element.## + val elementHash = improve(elementUnimprovedHash) + val newRootNode = rootNode.updated(element, elementUnimprovedHash, elementHash, 0) + newHashSetOrThis(newRootNode) + } + + def excl(element: A): HashSet[A] = { + val elementUnimprovedHash = element.## + val elementHash = improve(elementUnimprovedHash) + val newRootNode = rootNode.removed(element, elementUnimprovedHash, elementHash, 0) + newHashSetOrThis(newRootNode) + } + + override def concat(that: IterableOnce[A]): HashSet[A] = + that match { + case hs: HashSet[A] => + if (isEmpty) hs + else { + val newNode = rootNode.concat(hs.rootNode, 0) + if (newNode eq hs.rootNode) hs + else newHashSetOrThis(newNode) + } + case hs: collection.mutable.HashSet[A] => + val iter = hs.nodeIterator + var current = rootNode + while (iter.hasNext) { + val next = iter.next() + val originalHash = hs.unimproveHash(next.hash) + val improved = improve(originalHash) + current = current.updated(next.key, originalHash, improved, 0) + + if (current ne rootNode) { + var shallowlyMutableNodeMap = Node.bitposFrom(Node.maskFrom(improved, 0)) + while (iter.hasNext) { + val next = iter.next() + val originalHash = hs.unimproveHash(next.hash) + val improved = improve(originalHash) + shallowlyMutableNodeMap = current.updateWithShallowMutations(next.key, originalHash, improved, 0, shallowlyMutableNodeMap) + } + return new HashSet(current) + } + } + this + case lhs: collection.mutable.LinkedHashSet[A] => + val iter = lhs.entryIterator + var current = rootNode + while (iter.hasNext) { + val next = iter.next() + val originalHash = lhs.unimproveHash(next.hash) + val improved = improve(originalHash) + current = current.updated(next.key, originalHash, improved, 0) + + if (current ne rootNode) { + var shallowlyMutableNodeMap = Node.bitposFrom(Node.maskFrom(improved, 0)) + while (iter.hasNext) { + val next = iter.next() + val originalHash = lhs.unimproveHash(next.hash) + val improved = improve(originalHash) + shallowlyMutableNodeMap = current.updateWithShallowMutations(next.key, originalHash, improved, 0, shallowlyMutableNodeMap) + } + return new HashSet(current) + } + } + this + case _ => + val iter = that.iterator + var current = rootNode + while (iter.hasNext) { + val element = iter.next() + val originalHash = element.## + val improved = improve(originalHash) + current = current.updated(element, originalHash, improved, 0) + + if (current ne rootNode) { + // Note: We could have started with shallowlyMutableNodeMap = 0, however this way, in the case that + // the first changed key ended up in a subnode beneath root, we mark that root right away as being + // shallowly mutable. + // + // since `element` has just been inserted, and certainly caused a new root node to be created, we can say with + // certainty that it either caused a new subnode to be created underneath `current`, in which case we should + // carry on mutating that subnode, or it ended up as a child data pair of the root, in which case, no harm is + // done by including its bit position in the shallowlyMutableNodeMap anyways. + var shallowlyMutableNodeMap = Node.bitposFrom(Node.maskFrom(improved, 0)) + while (iter.hasNext) { + val element = iter.next() + val originalHash = element.## + val improved = improve(originalHash) + shallowlyMutableNodeMap = current.updateWithShallowMutations(element, originalHash, improved, 0, shallowlyMutableNodeMap) + } + return new HashSet(current) + } + } + this + } + + override def tail: HashSet[A] = this - head + + override def init: HashSet[A] = this - last + + override def head: A = iterator.next() + + override def last: A = reverseIterator.next() + + override def foreach[U](f: A => U): Unit = rootNode.foreach(f) + + /** Applies a function f to each element, and its corresponding **original** hash, in this Set */ + @`inline` private[collection] def foreachWithHash(f: (A, Int) => Unit): Unit = rootNode.foreachWithHash(f) + + /** Applies a function f to each element, and its corresponding **original** hash, in this Set + * Stops iterating the first time that f returns `false`.*/ + @`inline` private[collection] def foreachWithHashWhile(f: (A, Int) => Boolean): Unit = rootNode.foreachWithHashWhile(f) + + // For binary compatibility, the method used to have this signature by mistake. + // protected is public in bytecode. + protected def subsetOf(that: Set[A]): Boolean = subsetOf(that: collection.Set[A]) + + override def subsetOf(that: collection.Set[A]): Boolean = isEmpty || !that.isEmpty && (that match { + case set: HashSet[A] => rootNode.subsetOf(set.rootNode, 0) + case _ => super.subsetOf(that) + }) + + override def equals(that: Any): Boolean = + that match { + case set: HashSet[_] => (this eq set) || (this.rootNode == set.rootNode) + case _ => super.equals(that) + } + + override protected[this] def className = "HashSet" + + override def hashCode(): Int = { + val it = new SetHashIterator(rootNode) + val hash = MurmurHash3.unorderedHash(it, MurmurHash3.setSeed) + //assert(hash == super.hashCode()) + hash + } + + override def diff(that: collection.Set[A]): HashSet[A] = { + if (isEmpty) { + this + } else { + that match { + case hashSet: HashSet[A] => + if (hashSet.isEmpty) this else { + val newRootNode = rootNode.diff(hashSet.rootNode, 0) + if (newRootNode.size == 0) HashSet.empty else newHashSetOrThis(rootNode.diff(hashSet.rootNode, 0)) + } + case hashSet: collection.mutable.HashSet[A] => + val iter = hashSet.nodeIterator + var curr = rootNode + while (iter.hasNext) { + val next = iter.next() + val originalHash = hashSet.unimproveHash(next.hash) + val improved = improve(originalHash) + curr = curr.removed(next.key, originalHash, improved, 0) + if (curr ne rootNode) { + if (curr.size == 0) { + return HashSet.empty + } + while (iter.hasNext) { + val next = iter.next() + val originalHash = hashSet.unimproveHash(next.hash) + val improved = improve(originalHash) + + curr.removeWithShallowMutations(next.key, originalHash, improved) + + if (curr.size == 0) { + return HashSet.empty + } + } + return new HashSet(curr) + } + } + this + + case other => + val thatKnownSize = other.knownSize + + if (thatKnownSize == 0) { + this + } else if (thatKnownSize <= size) { + /* this branch intentionally includes the case of thatKnownSize == -1. We know that HashSets are quite fast at look-up, so + we're likely to be the faster of the two at that. */ + removedAllWithShallowMutations(other) + } else { + // TODO: Develop more sophisticated heuristic for which branch to take + filterNot(other.contains) + } + } + + } + } + + /** Immutably removes all elements of `that` from this HashSet + * + * Mutation is used internally, but only on root SetNodes which this method itself creates. + * + * That is, this method is safe to call on published sets because it does not mutate `this` + */ + private[this] def removedAllWithShallowMutations(that: IterableOnce[A]): HashSet[A] = { + val iter = that.iterator + var curr = rootNode + while (iter.hasNext) { + val next = iter.next() + val originalHash = next.## + val improved = improve(originalHash) + curr = curr.removed(next, originalHash, improved, 0) + if (curr ne rootNode) { + if (curr.size == 0) { + return HashSet.empty + } + while (iter.hasNext) { + val next = iter.next() + val originalHash = next.## + val improved = improve(originalHash) + + curr.removeWithShallowMutations(next, originalHash, improved) + + if (curr.size == 0) { + return HashSet.empty + } + } + return new HashSet(curr) + } + } + this + } + + override def removedAll(that: IterableOnce[A]): HashSet[A] = that match { + case set: scala.collection.Set[A] => diff(set) + case range: Range if range.length > size => + filter { + case i: Int => !range.contains(i) + case _ => true + } + + case _ => + removedAllWithShallowMutations(that) + } + + override def partition(p: A => Boolean): (HashSet[A], HashSet[A]) = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + super.partition(p) + } + + override def span(p: A => Boolean): (HashSet[A], HashSet[A]) = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + super.span(p) + } + + override protected[collection] def filterImpl(pred: A => Boolean, isFlipped: Boolean): HashSet[A] = { + val newRootNode = rootNode.filterImpl(pred, isFlipped) + if (newRootNode eq rootNode) this + else if (newRootNode.size == 0) HashSet.empty + else new HashSet(newRootNode) + } + + override def intersect(that: collection.Set[A]): HashSet[A] = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + super.intersect(that) + } + + override def take(n: Int): HashSet[A] = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + super.take(n) + } + + override def takeRight(n: Int): HashSet[A] = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + super.takeRight(n) + } + + override def takeWhile(p: A => Boolean): HashSet[A] = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + super.takeWhile(p) + } + + override def drop(n: Int): HashSet[A] = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + super.drop(n) + } + + override def dropRight(n: Int): HashSet[A] = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + super.dropRight(n) + } + + override def dropWhile(p: A => Boolean): HashSet[A] = { + // This method has been preemptively overridden in order to ensure that an optimizing implementation may be included + // in a minor release without breaking binary compatibility. + super.dropWhile(p) + } +} + +private[immutable] object SetNode { + + private final val EmptySetNode = new BitmapIndexedSetNode(0, 0, Array.empty, Array.empty, 0, 0) + + def empty[A]: BitmapIndexedSetNode[A] = EmptySetNode.asInstanceOf[BitmapIndexedSetNode[A]] + + final val TupleLength = 1 + +} + +private[immutable] sealed abstract class SetNode[A] extends Node[SetNode[A]] { + + def contains(element: A, originalHash: Int, hash: Int, shift: Int): Boolean + + def updated(element: A, originalHash: Int, hash: Int, shift: Int): SetNode[A] + + def removed(element: A, originalHash: Int, hash: Int, shift: Int): SetNode[A] + + def hasNodes: Boolean + + def nodeArity: Int + + def getNode(index: Int): SetNode[A] + + def hasPayload: Boolean + + def payloadArity: Int + + def getPayload(index: Int): A + + def size: Int + + def foreach[U](f: A => U): Unit + + def subsetOf(that: SetNode[A], shift: Int): Boolean + + def copy(): SetNode[A] + + def filterImpl(pred: A => Boolean, flipped: Boolean): SetNode[A] + + def diff(that: SetNode[A], shift: Int): SetNode[A] + + def concat(that: SetNode[A], shift: Int): SetNode[A] + + def foreachWithHash(f: (A, Int) => Unit): Unit + + def foreachWithHashWhile(f: (A, Int) => Boolean): Boolean +} + +private final class BitmapIndexedSetNode[A]( + var dataMap: Int, + var nodeMap: Int, + var content: Array[Any], + var originalHashes: Array[Int], + var size: Int, + var cachedJavaKeySetHashCode: Int) extends SetNode[A] { + + import Node._ + import SetNode._ + + /* + assert(checkInvariantContentIsWellTyped()) + assert(checkInvariantSubNodesAreCompacted()) + + private final def checkInvariantSubNodesAreCompacted(): Boolean = + new SetIterator[A](this).size - payloadArity >= 2 * nodeArity + + private final def checkInvariantContentIsWellTyped(): Boolean = { + val predicate1 = TupleLength * payloadArity + nodeArity == content.length + + val predicate2 = Range(0, TupleLength * payloadArity) + .forall(i => content(i).isInstanceOf[SetNode[_]] == false) + + val predicate3 = Range(TupleLength * payloadArity, content.length) + .forall(i => content(i).isInstanceOf[SetNode[_]] == true) + + predicate1 && predicate2 && predicate3 + } + */ + + def getPayload(index: Int): A = content(index).asInstanceOf[A] + + override def getHash(index: Int): Int = originalHashes(index) + + def getNode(index: Int): SetNode[A] = content(content.length - 1 - index).asInstanceOf[SetNode[A]] + + def contains(element: A, originalHash: Int, elementHash: Int, shift: Int): Boolean = { + val mask = maskFrom(elementHash, shift) + val bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + val index = indexFrom(dataMap, mask, bitpos) + return originalHashes(index) == originalHash && element == this.getPayload(index) + } + + if ((nodeMap & bitpos) != 0) { + val index = indexFrom(nodeMap, mask, bitpos) + return this.getNode(index).contains(element, originalHash, elementHash, shift + BitPartitionSize) + } + + false + } + + def updated(element: A, originalHash: Int, elementHash: Int, shift: Int): BitmapIndexedSetNode[A] = { + val mask = maskFrom(elementHash, shift) + val bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + val index = indexFrom(dataMap, mask, bitpos) + val element0 = this.getPayload(index) + + if (element0.asInstanceOf[AnyRef] eq element.asInstanceOf[AnyRef]) { + return this + } else { + val element0UnimprovedHash = getHash(index) + val element0Hash = improve(element0UnimprovedHash) + if (originalHash == element0UnimprovedHash && element0 == element) { + return this + } else { + val subNodeNew = mergeTwoKeyValPairs(element0, element0UnimprovedHash, element0Hash, element, originalHash, elementHash, shift + BitPartitionSize) + return copyAndMigrateFromInlineToNode(bitpos, element0Hash, subNodeNew) + } + } + } + if ((nodeMap & bitpos) != 0) { + val index = indexFrom(nodeMap, mask, bitpos) + val subNode = this.getNode(index) + + val subNodeNew = subNode.updated(element, originalHash, elementHash, shift + BitPartitionSize) + if (subNode eq subNodeNew) { + return this + } else { + return copyAndSetNode(bitpos, subNode, subNodeNew) + } + } + + copyAndInsertValue(bitpos, element, originalHash, elementHash) + } + /** A variant of `updated` which performs shallow mutations on the root (`this`), and if possible, on immediately + * descendant child nodes (only one level beneath `this`) + * + * The caller should pass a bitmap of child nodes of this node, which this method may mutate. + * If this method may mutate a child node, then if the updated value is located in that child node, it will + * be shallowly mutated (its children will not be mutated). + * + * If instead this method may not mutate the child node in which the to-be-updated value is located, then + * that child will be updated immutably, but the result will be mutably re-inserted as a child of this node. + * + * @param key the key to update + * @param originalHash key.## + * @param keyHash the improved hash + * @param shallowlyMutableNodeMap bitmap of child nodes of this node, which can be shallowly mutated + * during the call to this method + * + * @return Int which is the bitwise OR of shallowlyMutableNodeMap and any freshly created nodes, which will be + * available for mutations in subsequent calls. + */ + def updateWithShallowMutations(element: A, originalHash: Int, elementHash: Int, shift: Int, shallowlyMutableNodeMap: Int): Int = { + val mask = maskFrom(elementHash, shift) + val bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + val index = indexFrom(dataMap, mask, bitpos) + val element0 = getPayload(index) + val element0UnimprovedHash = getHash(index) + if (element0UnimprovedHash == originalHash && element0 == element) { + shallowlyMutableNodeMap + } else { + val element0Hash = improve(element0UnimprovedHash) + val subNodeNew = mergeTwoKeyValPairs(element0, element0UnimprovedHash, element0Hash, element, originalHash, elementHash, shift + BitPartitionSize) + migrateFromInlineToNodeInPlace(bitpos, element0Hash, subNodeNew) + shallowlyMutableNodeMap | bitpos + } + } else if ((nodeMap & bitpos) != 0) { + val index = indexFrom(nodeMap, mask, bitpos) + val subNode = this.getNode(index) + val subNodeSize = subNode.size + val subNodeCachedJavaKeySetHashCode = subNode.cachedJavaKeySetHashCode + + var returnNodeMap = shallowlyMutableNodeMap + + val subNodeNew: SetNode[A] = subNode match { + case subNodeBm: BitmapIndexedSetNode[A] if (bitpos & shallowlyMutableNodeMap) != 0 => + subNodeBm.updateWithShallowMutations(element, originalHash, elementHash, shift + BitPartitionSize, 0) + subNodeBm + case _ => + val subNodeNew = subNode.updated(element, originalHash, elementHash, shift + BitPartitionSize) + if (subNodeNew ne subNode) { + returnNodeMap |= bitpos + } + subNodeNew + } + + this.content(this.content.length - 1 - this.nodeIndex(bitpos)) = subNodeNew + this.size = this.size - subNodeSize + subNodeNew.size + this.cachedJavaKeySetHashCode = this.cachedJavaKeySetHashCode - subNodeCachedJavaKeySetHashCode + subNodeNew.cachedJavaKeySetHashCode + returnNodeMap + } else { + val dataIx = dataIndex(bitpos) + val idx = dataIx + + val src = this.content + val dst = new Array[Any](src.length + TupleLength) + + // copy 'src' and insert 2 element(s) at position 'idx' + arraycopy(src, 0, dst, 0, idx) + dst(idx) = element + arraycopy(src, idx, dst, idx + TupleLength, src.length - idx) + + val dstHashes = insertElement(originalHashes, dataIx, originalHash) + + this.dataMap |= bitpos + this.content = dst + this.originalHashes = dstHashes + this.size += 1 + this.cachedJavaKeySetHashCode += elementHash + shallowlyMutableNodeMap + } + } + + + def removed(element: A, originalHash: Int, elementHash: Int, shift: Int): BitmapIndexedSetNode[A] = { + val mask = maskFrom(elementHash, shift) + val bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + val index = indexFrom(dataMap, mask, bitpos) + val element0 = this.getPayload(index) + + if (element0 == element) { + if (this.payloadArity == 2 && this.nodeArity == 0) { + // Create new node with remaining pair. The new node will a) either become the new root + // returned, or b) unwrapped and inlined during returning. + val newDataMap = if (shift == 0) (dataMap ^ bitpos) else bitposFrom(maskFrom(elementHash, 0)) + if (index == 0) new BitmapIndexedSetNode[A](newDataMap, 0, Array(getPayload(1)), Array(originalHashes(1)), size - 1, improve(originalHashes(1))) + else new BitmapIndexedSetNode[A](newDataMap, 0, Array(getPayload(0)), Array(originalHashes(0)), size - 1, improve(originalHashes(0))) + } + else copyAndRemoveValue(bitpos, elementHash) + } + else this + } + else if ((nodeMap & bitpos) != 0) { + val index = indexFrom(nodeMap, mask, bitpos) + val subNode = this.getNode(index) + val subNodeNew = subNode.removed(element, originalHash, elementHash, shift + BitPartitionSize) + + if (subNodeNew eq subNode) this + // if subNodeNew is a hashCollision node, size has cost in Vector#length + else subNodeNew.size match { + case 1 => + // subNode is the only child (no other data or node children of `this` exist) + // escalate (singleton or empty) result + if (this.size == subNode.size) subNodeNew.asInstanceOf[BitmapIndexedSetNode[A]] + // inline value (move to front) + else copyAndMigrateFromNodeToInline(bitpos, elementHash, subNode, subNodeNew) + case subNodeNewSize if subNodeNewSize > 1 => + // modify current node (set replacement node) + copyAndSetNode(bitpos, subNode, subNodeNew) + case _ => this + } + } + else this + } + /** Variant of `removed` which will perform mutation on only the top-level node (`this`), rather than return a new + * node + * + * Should only be called on root nodes, because shift is assumed to be 0 + * + * @param element the element to remove + * @param originalHash the original hash of `element` + * @param elementHash the improved hash of `element` + */ + def removeWithShallowMutations(element: A, originalHash: Int, elementHash: Int): this.type = { + val mask = maskFrom(elementHash, 0) + val bitpos = bitposFrom(mask) + + if ((dataMap & bitpos) != 0) { + val index = indexFrom(dataMap, mask, bitpos) + val element0 = this.getPayload(index) + + if (element0 == element) { + if (this.payloadArity == 2 && this.nodeArity == 0) { + val newDataMap = dataMap ^ bitpos + if (index == 0) { + val newContent = Array[Any](getPayload(1)) + val newOriginalHashes = Array(originalHashes(1)) + val newCachedJavaKeySetHashCode = improve(getHash(1)) + this.content = newContent + this.originalHashes = newOriginalHashes + this.cachedJavaKeySetHashCode = newCachedJavaKeySetHashCode + } else { + val newContent = Array[Any](getPayload(0)) + val newOriginalHashes = Array(originalHashes(0)) + val newCachedJavaKeySetHashCode = improve(getHash(0)) + this.content = newContent + this.originalHashes = newOriginalHashes + this.cachedJavaKeySetHashCode = newCachedJavaKeySetHashCode + } + this.dataMap = newDataMap + this.nodeMap = 0 + this.size = 1 + this + } + else { + val dataIx = dataIndex(bitpos) + val idx = TupleLength * dataIx + + val src = this.content + val dst = new Array[Any](src.length - TupleLength) + + arraycopy(src, 0, dst, 0, idx) + arraycopy(src, idx + TupleLength, dst, idx, src.length - idx - TupleLength) + + val dstHashes = removeElement(originalHashes, dataIx) + + this.dataMap = this.dataMap ^ bitpos + this.content = dst + this.originalHashes = dstHashes + this.size -= 1 + this.cachedJavaKeySetHashCode -= elementHash + this + } + } else this + } else if ((nodeMap & bitpos) != 0) { + val index = indexFrom(nodeMap, mask, bitpos) + val subNode = this.getNode(index) + + val subNodeNew = subNode.removed(element, originalHash, elementHash, BitPartitionSize).asInstanceOf[BitmapIndexedSetNode[A]] + + if (subNodeNew eq subNode) return this + + if (subNodeNew.size == 1) { + if (this.payloadArity == 0 && this.nodeArity == 1) { + this.dataMap = subNodeNew.dataMap + this.nodeMap = subNodeNew.nodeMap + this.content = subNodeNew.content + this.originalHashes = subNodeNew.originalHashes + this.size = subNodeNew.size + this.cachedJavaKeySetHashCode = subNodeNew.cachedJavaKeySetHashCode + this + } else { + migrateFromNodeToInlineInPlace(bitpos, originalHash, elementHash, subNode, subNodeNew) + this + } + } else { + // size must be > 1 + this.content(this.content.length - 1 - this.nodeIndex(bitpos)) = subNodeNew + this.size -= 1 + this.cachedJavaKeySetHashCode = this.cachedJavaKeySetHashCode - subNode.cachedJavaKeySetHashCode + subNodeNew.cachedJavaKeySetHashCode + this + } + } else this + } + + def mergeTwoKeyValPairs(key0: A, originalKeyHash0: Int, keyHash0: Int, key1: A, originalKeyHash1: Int, keyHash1: Int, shift: Int): SetNode[A] = { + // assert(key0 != key1) + + if (shift >= HashCodeLength) { + new HashCollisionSetNode[A](originalKeyHash0, keyHash0, Vector(key0, key1)) + } else { + val mask0 = maskFrom(keyHash0, shift) + val mask1 = maskFrom(keyHash1, shift) + + if (mask0 != mask1) { + // unique prefixes, payload fits on same level + val dataMap = bitposFrom(mask0) | bitposFrom(mask1) + val newCachedHashCode = keyHash0 + keyHash1 + + if (mask0 < mask1) { + new BitmapIndexedSetNode[A](dataMap, 0, Array(key0, key1), Array(originalKeyHash0, originalKeyHash1), 2, newCachedHashCode) + } else { + new BitmapIndexedSetNode[A](dataMap, 0, Array(key1, key0), Array(originalKeyHash1, originalKeyHash0), 2, newCachedHashCode) + } + } else { + // identical prefixes, payload must be disambiguated deeper in the trie + val nodeMap = bitposFrom(mask0) + val node = mergeTwoKeyValPairs(key0, originalKeyHash0, keyHash0, key1, originalKeyHash1, keyHash1, shift + BitPartitionSize) + + new BitmapIndexedSetNode[A](0, nodeMap, Array(node), Array.emptyIntArray, node.size, node.cachedJavaKeySetHashCode) + } + } + } + + def hasPayload: Boolean = dataMap != 0 + + def payloadArity: Int = bitCount(dataMap) + + def hasNodes: Boolean = nodeMap != 0 + + def nodeArity: Int = bitCount(nodeMap) + + def dataIndex(bitpos: Int) = bitCount(dataMap & (bitpos - 1)) + + def nodeIndex(bitpos: Int) = bitCount(nodeMap & (bitpos - 1)) + + def copyAndSetNode(bitpos: Int, oldNode: SetNode[A], newNode: SetNode[A]) = { + val idx = this.content.length - 1 - this.nodeIndex(bitpos) + + val src = this.content + val dst = new Array[Any](src.length) + + // copy 'src' and set 1 element(s) at position 'idx' + arraycopy(src, 0, dst, 0, src.length) + dst(idx) = newNode + new BitmapIndexedSetNode[A]( + dataMap = dataMap, + nodeMap = nodeMap, + content = dst, + originalHashes = originalHashes, + size = size - oldNode.size + newNode.size, + cachedJavaKeySetHashCode = cachedJavaKeySetHashCode - oldNode.cachedJavaKeySetHashCode + newNode.cachedJavaKeySetHashCode + ) + } + + def copyAndInsertValue(bitpos: Int, key: A, originalHash: Int, elementHash: Int) = { + val dataIx = dataIndex(bitpos) + val idx = TupleLength * dataIx + + val src = this.content + val dst = new Array[Any](src.length + 1) + + // copy 'src' and insert 1 element(s) at position 'idx' + arraycopy(src, 0, dst, 0, idx) + dst(idx) = key + arraycopy(src, idx, dst, idx + 1, src.length - idx) + val dstHashes = insertElement(originalHashes, dataIx, originalHash) + + new BitmapIndexedSetNode[A](dataMap | bitpos, nodeMap, dst, dstHashes, size + 1, cachedJavaKeySetHashCode + elementHash) + } + + def copyAndSetValue(bitpos: Int, key: A, originalHash: Int, elementHash: Int) = { + val dataIx = dataIndex(bitpos) + val idx = TupleLength * dataIx + + val src = this.content + val dst = new Array[Any](src.length) + + // copy 'src' and set 1 element(s) at position 'idx' + arraycopy(src, 0, dst, 0, src.length) + dst(idx) = key + + new BitmapIndexedSetNode[A](dataMap | bitpos, nodeMap, dst, originalHashes, size, cachedJavaKeySetHashCode) + } + + def copyAndRemoveValue(bitpos: Int, elementHash: Int) = { + val dataIx = dataIndex(bitpos) + val idx = TupleLength * dataIx + + val src = this.content + val dst = new Array[Any](src.length - 1) + + // copy 'src' and remove 1 element(s) at position 'idx' + arraycopy(src, 0, dst, 0, idx) + arraycopy(src, idx + 1, dst, idx, src.length - idx - 1) + val dstHashes = removeElement(originalHashes, dataIx) + new BitmapIndexedSetNode[A](dataMap ^ bitpos, nodeMap, dst, dstHashes, size - 1, cachedJavaKeySetHashCode - elementHash) + } + + def copyAndMigrateFromInlineToNode(bitpos: Int, elementHash: Int, node: SetNode[A]) = { + val dataIx = dataIndex(bitpos) + val idxOld = TupleLength * dataIx + val idxNew = this.content.length - TupleLength - nodeIndex(bitpos) + + val src = this.content + val dst = new Array[Any](src.length - 1 + 1) + + // copy 'src' and remove 1 element(s) at position 'idxOld' and + // insert 1 element(s) at position 'idxNew' + // assert(idxOld <= idxNew) + arraycopy(src, 0, dst, 0, idxOld) + arraycopy(src, idxOld + 1, dst, idxOld, idxNew - idxOld) + dst(idxNew) = node + arraycopy(src, idxNew + 1, dst, idxNew + 1, src.length - idxNew - 1) + val dstHashes = removeElement(originalHashes, dataIx) + new BitmapIndexedSetNode[A]( + dataMap = dataMap ^ bitpos, + nodeMap = nodeMap | bitpos, + content = dst, originalHashes = dstHashes, + size = size - 1 + node.size, + cachedJavaKeySetHashCode = cachedJavaKeySetHashCode - elementHash + node.cachedJavaKeySetHashCode + ) + } + /** Variant of `copyAndMigrateFromInlineToNode` which mutates `this` rather than returning a new node. + * + * Note: This method will mutate `this`, and will mutate `this.content` + * + * Mutation of `this.content` will occur as an optimization not possible in maps. Since TupleLength == 1 for sets, + * content array size does not change during inline <-> node migrations. Therefor, since we are updating in-place, + * we reuse this.content by shifting data/nodes around, rather than allocating a new array. + * + * @param bitpos the bit position of the data to migrate to node + * @param keyHash the improved hash of the element currently at `bitpos` + * @param node the node to place at `bitpos` + */ + def migrateFromInlineToNodeInPlace(bitpos: Int, keyHash: Int, node: SetNode[A]): this.type = { + val dataIx = dataIndex(bitpos) + val idxOld = TupleLength * dataIx + val idxNew = this.content.length - TupleLength - nodeIndex(bitpos) + + arraycopy(content, idxOld + TupleLength, content, idxOld, idxNew - idxOld) + content(idxNew) = node + + this.dataMap = this.dataMap ^ bitpos + this.nodeMap = this.nodeMap | bitpos + this.originalHashes = removeElement(originalHashes, dataIx) + this.size = this.size - 1 + node.size + this.cachedJavaKeySetHashCode = this.cachedJavaKeySetHashCode - keyHash + node.cachedJavaKeySetHashCode + this + } + + def copyAndMigrateFromNodeToInline(bitpos: Int, elementHash: Int, oldNode: SetNode[A], node: SetNode[A]) = { + val idxOld = this.content.length - 1 - nodeIndex(bitpos) + val dataIxNew = dataIndex(bitpos) + val idxNew = TupleLength * dataIxNew + + val src = this.content + val dst = new Array[Any](src.length - 1 + 1) + + // copy 'src' and remove 1 element(s) at position 'idxOld' and + // insert 1 element(s) at position 'idxNew' + // assert(idxOld >= idxNew) + arraycopy(src, 0, dst, 0, idxNew) + dst(idxNew) = node.getPayload(0) + arraycopy(src, idxNew, dst, idxNew + 1, idxOld - idxNew) + arraycopy(src, idxOld + 1, dst, idxOld + 1, src.length - idxOld - 1) + val hash = node.getHash(0) + val dstHashes = insertElement(originalHashes, dataIxNew, hash) + new BitmapIndexedSetNode[A]( + dataMap = dataMap | bitpos, + nodeMap = nodeMap ^ bitpos, + content = dst, + originalHashes = dstHashes, + size = size - oldNode.size + 1, + cachedJavaKeySetHashCode = cachedJavaKeySetHashCode - oldNode.cachedJavaKeySetHashCode + node.cachedJavaKeySetHashCode + ) + } + + /** Variant of `copyAndMigrateFromNodeToInline` which mutates `this` rather than returning a new node. + * + * Note: This method will mutate `this`, and will mutate `this.content` + * + * Mutation of `this.content` will occur as an optimization not possible in maps. Since TupleLength == 1 for sets, + * content array size does not change during inline <-> node migrations. Therefor, since we are updating in-place, + * we reuse this.content by shifting data/nodes around, rather than allocating a new array. + * + * @param bitpos the bit position of the node to migrate inline + * @param oldNode the node currently stored at position `bitpos` + * @param node the node containing the single element to migrate inline + */ + def migrateFromNodeToInlineInPlace(bitpos: Int, originalHash: Int, elementHash: Int, oldNode: SetNode[A], node: SetNode[A]): Unit = { + val idxOld = this.content.length - 1 - nodeIndex(bitpos) + val dataIxNew = dataIndex(bitpos) + val element = node.getPayload(0) + arraycopy(content, dataIxNew, content, dataIxNew + 1, idxOld - dataIxNew) + content(dataIxNew) = element + val hash = node.getHash(0) + val dstHashes = insertElement(originalHashes, dataIxNew, hash) + + this.dataMap = this.dataMap | bitpos + this.nodeMap = this.nodeMap ^ bitpos + this.originalHashes = dstHashes + this.size = this.size - oldNode.size + 1 + this.cachedJavaKeySetHashCode = this.cachedJavaKeySetHashCode - oldNode.cachedJavaKeySetHashCode + node.cachedJavaKeySetHashCode + } + + def foreach[U](f: A => U): Unit = { + val thisPayloadArity = payloadArity + var i = 0 + while (i < thisPayloadArity) { + f(getPayload(i)) + i += 1 + } + + val thisNodeArity = nodeArity + var j = 0 + while (j < thisNodeArity) { + getNode(j).foreach(f) + j += 1 + } + } + + def subsetOf(that: SetNode[A], shift: Int): Boolean = if (this eq that) true else that match { + case _: HashCollisionSetNode[A] => false + case node: BitmapIndexedSetNode[A] => + val thisBitmap = this.dataMap | this.nodeMap + val nodeBitmap = node.dataMap | node.nodeMap + + if ((thisBitmap | nodeBitmap) != nodeBitmap) + return false + + var bitmap = thisBitmap & nodeBitmap + var bitsToSkip = numberOfTrailingZeros(bitmap) + + var isValidSubset = true + while (isValidSubset && bitsToSkip < HashCodeLength) { + val bitpos = bitposFrom(bitsToSkip) + + isValidSubset = + if ((this.dataMap & bitpos) != 0) { + if ((node.dataMap & bitpos) != 0) { + // Data x Data + val payload0 = this.getPayload(indexFrom(this.dataMap, bitpos)) + val payload1 = node.getPayload(indexFrom(node.dataMap, bitpos)) + payload0 == payload1 + } else { + // Data x Node + val thisDataIndex = indexFrom(this.dataMap, bitpos) + val payload = this.getPayload(thisDataIndex) + val subNode = that.getNode(indexFrom(node.nodeMap, bitpos)) + val elementUnimprovedHash = getHash(thisDataIndex) + val elementHash = improve(elementUnimprovedHash) + subNode.contains(payload, elementUnimprovedHash, elementHash, shift + BitPartitionSize) + } + } else ((node.dataMap & bitpos) == 0) && { + // Node x Node + val subNode0 = this.getNode(indexFrom(this.nodeMap, bitpos)) + val subNode1 = node.getNode(indexFrom(node.nodeMap, bitpos)) + subNode0.subsetOf(subNode1, shift + BitPartitionSize) + } + + val newBitmap = bitmap ^ bitpos + bitmap = newBitmap + bitsToSkip = numberOfTrailingZeros(newBitmap) + } + isValidSubset + } + + override def filterImpl(pred: A => Boolean, flipped: Boolean): BitmapIndexedSetNode[A] = { + if (size == 0) this + else if (size == 1) { + if (pred(getPayload(0)) != flipped) this else SetNode.empty + } else if (nodeMap == 0) { + // Performance optimization for nodes of depth 1: + // + // this node has no "node" children, all children are inlined data elems, therefor logic is significantly simpler + // approach: + // * traverse the content array, accumulating in `newDataMap: Int` any bit positions of keys which pass the filter + // * (bitCount(newDataMap) * TupleLength) tells us the new content array and originalHashes array size, so now perform allocations + // * traverse the content array once more, placing each passing element (according to `newDatamap`) in the new content and originalHashes arrays + // + // note: + // * this optimization significantly improves performance of not only small trees, but also larger trees, since + // even non-root nodes are affected by this improvement, and large trees will consist of many nodes as + // descendants + // + val minimumIndex: Int = Integer.numberOfTrailingZeros(dataMap) + val maximumIndex: Int = Node.BranchingFactor - Integer.numberOfLeadingZeros(dataMap) + + var newDataMap = 0 + var newCachedHashCode = 0 + var dataIndex = 0 + + var i = minimumIndex + + while(i < maximumIndex) { + val bitpos = bitposFrom(i) + + if ((bitpos & dataMap) != 0) { + val payload = getPayload(dataIndex) + val passed = pred(payload) != flipped + + if (passed) { + newDataMap |= bitpos + newCachedHashCode += improve(getHash(dataIndex)) + } + + dataIndex += 1 + } + + i += 1 + } + + if (newDataMap == 0) { + SetNode.empty + } else if (newDataMap == dataMap) { + this + } else { + val newSize = Integer.bitCount(newDataMap) + val newContent = new Array[Any](newSize) + val newOriginalHashCodes = new Array[Int](newSize) + val newMaximumIndex: Int = Node.BranchingFactor - Integer.numberOfLeadingZeros(newDataMap) + + var j = Integer.numberOfTrailingZeros(newDataMap) + + var newDataIndex = 0 + + while (j < newMaximumIndex) { + val bitpos = bitposFrom(j) + if ((bitpos & newDataMap) != 0) { + val oldIndex = indexFrom(dataMap, bitpos) + newContent(newDataIndex) = content(oldIndex) + newOriginalHashCodes(newDataIndex) = originalHashes(oldIndex) + newDataIndex += 1 + } + j += 1 + } + + new BitmapIndexedSetNode(newDataMap, 0, newContent, newOriginalHashCodes, newSize, newCachedHashCode) + } + } else { + val allMap = dataMap | nodeMap + val minimumIndex: Int = Integer.numberOfTrailingZeros(allMap) + val maximumIndex: Int = Node.BranchingFactor - Integer.numberOfLeadingZeros(allMap) + + var oldDataPassThrough = 0 + + // bitmap of nodes which, when filtered, returned a single-element node. These must be migrated to data + var nodeMigrateToDataTargetMap = 0 + + // TODO: When filtering results in a single-elem node, simply `(A, originalHash, improvedHash)` could be returned, + // rather than a singleton node (to avoid pointlessly allocating arrays, nodes, which would just be inlined in + // the parent anyways). This would probably involve changing the return type of filterImpl to `AnyRef` which may + // return at runtime a SetNode[A], or a tuple of (A, Int, Int) + + // the queue of single-element, post-filter nodes + var nodesToMigrateToData: mutable.Queue[SetNode[A]] = null + + // bitmap of all nodes which, when filtered, returned themselves. They are passed forward to the returned node + var nodesToPassThroughMap = 0 + + // bitmap of any nodes which, after being filtered, returned a node that is not empty, but also not `eq` itself + // These are stored for later inclusion into the final `content` array + // not named `newNodesMap` (plural) to avoid confusion with `newNodeMap` (singular) + var mapOfNewNodes = 0 + // each bit in `mapOfNewNodes` corresponds to one element in this queue + var newNodes: mutable.Queue[SetNode[A]] = null + + var newDataMap = 0 + var newNodeMap = 0 + var newSize = 0 + var newCachedHashCode = 0 + + var dataIndex = 0 + var nodeIndex = 0 + + var i = minimumIndex + while (i < maximumIndex) { + val bitpos = bitposFrom(i) + + if ((bitpos & dataMap) != 0) { + val payload = getPayload(dataIndex) + val passed = pred(payload) != flipped + + if (passed) { + newDataMap |= bitpos + oldDataPassThrough |= bitpos + newSize += 1 + newCachedHashCode += improve(getHash(dataIndex)) + } + + dataIndex += 1 + } else if ((bitpos & nodeMap) != 0) { + val oldSubNode = getNode(nodeIndex) + val newSubNode = oldSubNode.filterImpl(pred, flipped) + + newSize += newSubNode.size + newCachedHashCode += newSubNode.cachedJavaKeySetHashCode + + // if (newSubNode.size == 0) do nothing (drop it) + if (newSubNode.size > 1) { + newNodeMap |= bitpos + if (oldSubNode eq newSubNode) { + nodesToPassThroughMap |= bitpos + } else { + mapOfNewNodes |= bitpos + if (newNodes eq null) { + newNodes = mutable.Queue.empty + } + newNodes += newSubNode + } + } else if (newSubNode.size == 1) { + newDataMap |= bitpos + nodeMigrateToDataTargetMap |= bitpos + if (nodesToMigrateToData eq null) { + nodesToMigrateToData = mutable.Queue.empty + } + nodesToMigrateToData += newSubNode + } + + nodeIndex += 1 + } + + i += 1 + } + + this.newNodeFrom( + newSize = newSize, + newDataMap = newDataMap, + newNodeMap = newNodeMap, + minimumIndex = minimumIndex, + oldDataPassThrough = oldDataPassThrough, + nodesToPassThroughMap = nodesToPassThroughMap, + nodeMigrateToDataTargetMap = nodeMigrateToDataTargetMap, + nodesToMigrateToData = nodesToMigrateToData, + mapOfNewNodes = mapOfNewNodes, + newNodes = newNodes, + newCachedHashCode = newCachedHashCode + ) + } + } + + override def diff(that: SetNode[A], shift: Int): BitmapIndexedSetNode[A] = that match { + case bm: BitmapIndexedSetNode[A] => + if (size == 0) this + else if (size == 1) { + val h = getHash(0) + if (that.contains(getPayload(0), h, improve(h), shift)) SetNode.empty else this + } else { + val allMap = dataMap | nodeMap + val minimumIndex: Int = Integer.numberOfTrailingZeros(allMap) + val maximumIndex: Int = Node.BranchingFactor - Integer.numberOfLeadingZeros(allMap) + + var oldDataPassThrough = 0 + + // bitmap of nodes which, when filtered, returned a single-element node. These must be migrated to data + var nodeMigrateToDataTargetMap = 0 + // the queue of single-element, post-filter nodes + var nodesToMigrateToData: mutable.Queue[SetNode[A]] = null + + // bitmap of all nodes which, when filtered, returned themselves. They are passed forward to the returned node + var nodesToPassThroughMap = 0 + + // bitmap of any nodes which, after being filtered, returned a node that is not empty, but also not `eq` itself + // These are stored for later inclusion into the final `content` array + // not named `newNodesMap` (plural) to avoid confusion with `newNodeMap` (singular) + var mapOfNewNodes = 0 + // each bit in `mapOfNewNodes` corresponds to one element in this queue + var newNodes: mutable.Queue[SetNode[A]] = null + + var newDataMap = 0 + var newNodeMap = 0 + var newSize = 0 + var newCachedHashCode = 0 + + var dataIndex = 0 + var nodeIndex = 0 + + var i = minimumIndex + while (i < maximumIndex) { + val bitpos = bitposFrom(i) + + if ((bitpos & dataMap) != 0) { + val payload = getPayload(dataIndex) + val originalHash = getHash(dataIndex) + val hash = improve(originalHash) + + if (!bm.contains(payload, originalHash, hash, shift)) { + newDataMap |= bitpos + oldDataPassThrough |= bitpos + newSize += 1 + newCachedHashCode += hash + } + + dataIndex += 1 + } else if ((bitpos & nodeMap) != 0) { + val oldSubNode = getNode(nodeIndex) + + val newSubNode: SetNode[A] = + if ((bitpos & bm.dataMap) != 0) { + val thatDataIndex = indexFrom(bm.dataMap, bitpos) + val thatPayload = bm.getPayload(thatDataIndex) + val thatOriginalHash = bm.getHash(thatDataIndex) + val thatHash = improve(thatOriginalHash) + oldSubNode.removed(thatPayload, thatOriginalHash, thatHash, shift + BitPartitionSize) + } else if ((bitpos & bm.nodeMap) != 0) { + oldSubNode.diff(bm.getNode(indexFrom(bm.nodeMap, bitpos)), shift + BitPartitionSize) + } else { + oldSubNode + } + + newSize += newSubNode.size + newCachedHashCode += newSubNode.cachedJavaKeySetHashCode + + // if (newSubNode.size == 0) do nothing (drop it) + if (newSubNode.size > 1) { + newNodeMap |= bitpos + if (oldSubNode eq newSubNode) { + nodesToPassThroughMap |= bitpos + } else { + mapOfNewNodes |= bitpos + if (newNodes eq null) { + newNodes = mutable.Queue.empty + } + newNodes += newSubNode + } + } else if (newSubNode.size == 1) { + newDataMap |= bitpos + nodeMigrateToDataTargetMap |= bitpos + if (nodesToMigrateToData eq null) { + nodesToMigrateToData = mutable.Queue.empty + } + nodesToMigrateToData += newSubNode + } + + nodeIndex += 1 + } + + i += 1 + } + this.newNodeFrom( + newSize = newSize, + newDataMap = newDataMap, + newNodeMap = newNodeMap, + minimumIndex = minimumIndex, + oldDataPassThrough = oldDataPassThrough, + nodesToPassThroughMap = nodesToPassThroughMap, + nodeMigrateToDataTargetMap = nodeMigrateToDataTargetMap, + nodesToMigrateToData = nodesToMigrateToData, + mapOfNewNodes = mapOfNewNodes, + newNodes = newNodes, + newCachedHashCode = newCachedHashCode + ) + } + case _: HashCollisionSetNode[A] => + // this branch should never happen, because HashCollisionSetNodes and BitMapIndexedSetNodes do not occur at the + // same depth + throw new RuntimeException("BitmapIndexedSetNode diff HashCollisionSetNode") + } + + /** Utility method only for use in `diff` and `filterImpl` + * + * @param newSize the size of the new SetNode + * @param newDataMap the dataMap of the new SetNode + * @param newNodeMap the nodeMap of the new SetNode + * @param minimumIndex the minimum index (in range of [0, 31]) for which there are sub-nodes or data beneath the new + * SetNode + * @param oldDataPassThrough bitmap representing all the data that are just passed from `this` to the new + * SetNode + * @param nodesToPassThroughMap bitmap representing all nodes that are just passed from `this` to the new SetNode + * @param nodeMigrateToDataTargetMap bitmap representing all positions which will now be data in the new SetNode, + * but which were nodes in `this` + * @param nodesToMigrateToData a queue (in order of child position) of single-element nodes, which will be migrated + * to data, in positions in the `nodeMigrateToDataTargetMap` + * @param mapOfNewNodes bitmap of positions of new nodes to include in the new SetNode + * @param newNodes queue in order of child position, of all new nodes to include in the new SetNode + * @param newCachedHashCode the cached java keyset hashcode of the new SetNode + */ + private[this] def newNodeFrom( + newSize: Int, + newDataMap: Int, + newNodeMap: Int, + minimumIndex: Int, + oldDataPassThrough: Int, + nodesToPassThroughMap: Int, + nodeMigrateToDataTargetMap: Int, + nodesToMigrateToData: mutable.Queue[SetNode[A]], + mapOfNewNodes: Int, + newNodes: mutable.Queue[SetNode[A]], + newCachedHashCode: Int): BitmapIndexedSetNode[A] = { + if (newSize == 0) { + SetNode.empty + } else if (newSize == size) { + this + } else { + val newDataSize = bitCount(newDataMap) + val newContentSize = newDataSize + bitCount(newNodeMap) + val newContent = new Array[Any](newContentSize) + val newOriginalHashes = new Array[Int](newDataSize) + + val newAllMap = newDataMap | newNodeMap + val maxIndex = Node.BranchingFactor - Integer.numberOfLeadingZeros(newAllMap) + + // note: We MUST start from the minimum index in the old (`this`) node, otherwise `old{Node,Data}Index` will + // not be incremented properly. Otherwise we could have started at Integer.numberOfTrailingZeroes(newAllMap) + var i = minimumIndex + + var oldDataIndex = 0 + var oldNodeIndex = 0 + + var newDataIndex = 0 + var newNodeIndex = 0 + + while (i < maxIndex) { + val bitpos = bitposFrom(i) + + if ((bitpos & oldDataPassThrough) != 0) { + newContent(newDataIndex) = getPayload(oldDataIndex) + newOriginalHashes(newDataIndex) = getHash(oldDataIndex) + newDataIndex += 1 + oldDataIndex += 1 + } else if ((bitpos & nodesToPassThroughMap) != 0) { + newContent(newContentSize - newNodeIndex - 1) = getNode(oldNodeIndex) + newNodeIndex += 1 + oldNodeIndex += 1 + } else if ((bitpos & nodeMigrateToDataTargetMap) != 0) { + // we need not check for null here. If nodeMigrateToDataTargetMap != 0, then nodesMigrateToData must not be null + val node = nodesToMigrateToData.dequeue() + newContent(newDataIndex) = node.getPayload(0) + newOriginalHashes(newDataIndex) = node.getHash(0) + newDataIndex += 1 + oldNodeIndex += 1 + } else if ((bitpos & mapOfNewNodes) != 0) { + // we need not check for null here. If mapOfNewNodes != 0, then newNodes must not be null + newContent(newContentSize - newNodeIndex - 1) = newNodes.dequeue() + newNodeIndex += 1 + oldNodeIndex += 1 + } else if ((bitpos & dataMap) != 0) { + oldDataIndex += 1 + } else if ((bitpos & nodeMap) != 0) { + oldNodeIndex += 1 + } + + i += 1 + } + + new BitmapIndexedSetNode[A](newDataMap, newNodeMap, newContent, newOriginalHashes, newSize, newCachedHashCode) + } + } + + + override def equals(that: Any): Boolean = + that match { + case node: BitmapIndexedSetNode[_] => + (this eq node) || + (this.cachedJavaKeySetHashCode == node.cachedJavaKeySetHashCode) && + (this.nodeMap == node.nodeMap) && + (this.dataMap == node.dataMap) && + (this.size == node.size) && + java.util.Arrays.equals(this.originalHashes, node.originalHashes) && + deepContentEquality(this.content, node.content, content.length) + case _ => false + } + + @`inline` private def deepContentEquality(a1: Array[Any], a2: Array[Any], length: Int): Boolean = { + if (a1 eq a2) + true + else { + var isEqual = true + var i = 0 + + while (isEqual && i < length) { + isEqual = a1(i) == a2(i) + i += 1 + } + + isEqual + } + } + + override def hashCode(): Int = + throw new UnsupportedOperationException("Trie nodes do not support hashing.") + + override def toString: String = f"BitmapIndexedSetNode(size=$size, dataMap=$dataMap%x, nodeMap=$nodeMap%x)" // content=${scala.runtime.ScalaRunTime.stringOf(content)} + + override def copy(): BitmapIndexedSetNode[A] = { + val contentClone = content.clone() + val contentLength = contentClone.length + var i = bitCount(dataMap) + while (i < contentLength) { + contentClone(i) = contentClone(i).asInstanceOf[SetNode[A]].copy() + i += 1 + } + new BitmapIndexedSetNode[A](dataMap, nodeMap, contentClone, originalHashes.clone(), size, cachedJavaKeySetHashCode) + } + + override def concat(that: SetNode[A], shift: Int): BitmapIndexedSetNode[A] = that match { + case bm: BitmapIndexedSetNode[A] => + if (size == 0) return bm + else if (bm.size == 0 || (bm eq this)) return this + else if (bm.size == 1) { + val originalHash = bm.getHash(0) + return this.updated(bm.getPayload(0), originalHash, improve(originalHash), shift) + } + + // if we go through the merge and the result does not differ from `this`, we can just return `this`, to improve sharing + // So, `anyChangesMadeSoFar` will be set to `true` as soon as we encounter a difference between the + // currently-being-computed result, and `this` + var anyChangesMadeSoFar = false + + // bitmap containing `1` in any position that has any descendant in either left or right, either data or node + val allMap = dataMap | bm.dataMap | nodeMap | bm.nodeMap + + // minimumIndex is inclusive -- it is the first index for which there is data or nodes + val minimumBitPos: Int = Node.bitposFrom(Integer.numberOfTrailingZeros(allMap)) + // maximumIndex is inclusive -- it is the last index for which there is data or nodes + // it could not be exclusive, because then upper bound in worst case (Node.BranchingFactor) would be out-of-bound + // of int bitposition representation + val maximumBitPos: Int = Node.bitposFrom(Node.BranchingFactor - Integer.numberOfLeadingZeros(allMap) - 1) + + var leftNodeRightNode = 0 + var leftDataRightNode = 0 + var leftNodeRightData = 0 + var leftDataOnly = 0 + var rightDataOnly = 0 + var leftNodeOnly = 0 + var rightNodeOnly = 0 + var leftDataRightDataMigrateToNode = 0 + var leftDataRightDataLeftOverwrites = 0 + + var dataToNodeMigrationTargets = 0 + + { + var bitpos = minimumBitPos + var leftIdx = 0 + var rightIdx = 0 + var finished = false + + while (!finished) { + + if ((bitpos & dataMap) != 0) { + if ((bitpos & bm.dataMap) != 0) { + if (getHash(leftIdx) == bm.getHash(rightIdx) && getPayload(leftIdx) == bm.getPayload(rightIdx)) { + leftDataRightDataLeftOverwrites |= bitpos + } else { + leftDataRightDataMigrateToNode |= bitpos + dataToNodeMigrationTargets |= Node.bitposFrom(Node.maskFrom(improve(getHash(leftIdx)), shift)) + } + rightIdx += 1 + } else if ((bitpos & bm.nodeMap) != 0) { + leftDataRightNode |= bitpos + } else { + leftDataOnly |= bitpos + } + leftIdx += 1 + } else if ((bitpos & nodeMap) != 0) { + if ((bitpos & bm.dataMap) != 0) { + leftNodeRightData |= bitpos + rightIdx += 1 + } else if ((bitpos & bm.nodeMap) != 0) { + leftNodeRightNode |= bitpos + } else { + leftNodeOnly |= bitpos + } + } else if ((bitpos & bm.dataMap) != 0) { + rightDataOnly |= bitpos + rightIdx += 1 + } else if ((bitpos & bm.nodeMap) != 0) { + rightNodeOnly |= bitpos + } + + if (bitpos == maximumBitPos) { + finished = true + } else { + bitpos = bitpos << 1 + } + } + } + + + val newDataMap = leftDataOnly | rightDataOnly | leftDataRightDataLeftOverwrites + + val newNodeMap = + leftNodeRightNode | + leftDataRightNode | + leftNodeRightData | + leftNodeOnly | + rightNodeOnly | + dataToNodeMigrationTargets + + + if ((newDataMap == (leftDataOnly | leftDataRightDataLeftOverwrites)) && (newNodeMap == leftNodeOnly)) { + // nothing from `bm` will make it into the result -- return early + return this + } + + val newDataSize = bitCount(newDataMap) + val newContentSize = newDataSize + bitCount(newNodeMap) + + val newContent = new Array[Any](newContentSize) + val newOriginalHashes = new Array[Int](newDataSize) + var newSize = 0 + var newCachedHashCode = 0 + + { + var leftDataIdx = 0 + var rightDataIdx = 0 + var leftNodeIdx = 0 + var rightNodeIdx = 0 + + val nextShift = shift + Node.BitPartitionSize + + var compressedDataIdx = 0 + var compressedNodeIdx = 0 + + var bitpos = minimumBitPos + var finished = false + + while (!finished) { + + if ((bitpos & leftNodeRightNode) != 0) { + val leftNode = getNode(leftNodeIdx) + val newNode = leftNode.concat(bm.getNode(rightNodeIdx), nextShift) + if (leftNode ne newNode) { + anyChangesMadeSoFar = true + } + newContent(newContentSize - compressedNodeIdx - 1) = newNode + compressedNodeIdx += 1 + rightNodeIdx += 1 + leftNodeIdx += 1 + newSize += newNode.size + newCachedHashCode += newNode.cachedJavaKeySetHashCode + + } else if ((bitpos & leftDataRightNode) != 0) { + anyChangesMadeSoFar = true + val newNode = { + val n = bm.getNode(rightNodeIdx) + val leftPayload = getPayload(leftDataIdx) + val leftOriginalHash = getHash(leftDataIdx) + val leftImproved = improve(leftOriginalHash) + n.updated(leftPayload, leftOriginalHash, leftImproved, nextShift) + } + + newContent(newContentSize - compressedNodeIdx - 1) = newNode + compressedNodeIdx += 1 + rightNodeIdx += 1 + leftDataIdx += 1 + newSize += newNode.size + newCachedHashCode += newNode.cachedJavaKeySetHashCode + } + else if ((bitpos & leftNodeRightData) != 0) { + val newNode = { + val rightOriginalHash = bm.getHash(rightDataIdx) + val leftNode = getNode(leftNodeIdx) + val updated = leftNode.updated( + element = bm.getPayload(rightDataIdx), + originalHash = bm.getHash(rightDataIdx), + hash = improve(rightOriginalHash), + shift = nextShift + ) + if (updated ne leftNode) { + anyChangesMadeSoFar = true + } + updated + } + + newContent(newContentSize - compressedNodeIdx - 1) = newNode + compressedNodeIdx += 1 + leftNodeIdx += 1 + rightDataIdx += 1 + newSize += newNode.size + newCachedHashCode += newNode.cachedJavaKeySetHashCode + + } else if ((bitpos & leftDataOnly) != 0) { + val originalHash = originalHashes(leftDataIdx) + newContent(compressedDataIdx) = getPayload(leftDataIdx).asInstanceOf[AnyRef] + newOriginalHashes(compressedDataIdx) = originalHash + + compressedDataIdx += 1 + leftDataIdx += 1 + newSize += 1 + newCachedHashCode += improve(originalHash) + } else if ((bitpos & rightDataOnly) != 0) { + anyChangesMadeSoFar = true + val originalHash = bm.originalHashes(rightDataIdx) + newContent(compressedDataIdx) = bm.getPayload(rightDataIdx).asInstanceOf[AnyRef] + newOriginalHashes(compressedDataIdx) = originalHash + + compressedDataIdx += 1 + rightDataIdx += 1 + newSize += 1 + newCachedHashCode += improve(originalHash) + } else if ((bitpos & leftNodeOnly) != 0) { + val newNode = getNode(leftNodeIdx) + newContent(newContentSize - compressedNodeIdx - 1) = newNode + compressedNodeIdx += 1 + leftNodeIdx += 1 + newSize += newNode.size + newCachedHashCode += newNode.cachedJavaKeySetHashCode + } else if ((bitpos & rightNodeOnly) != 0) { + anyChangesMadeSoFar = true + val newNode = bm.getNode(rightNodeIdx) + newContent(newContentSize - compressedNodeIdx - 1) = newNode + compressedNodeIdx += 1 + rightNodeIdx += 1 + newSize += newNode.size + newCachedHashCode += newNode.cachedJavaKeySetHashCode + } else if ((bitpos & leftDataRightDataMigrateToNode) != 0) { + anyChangesMadeSoFar = true + val newNode = { + val leftOriginalHash = getHash(leftDataIdx) + val rightOriginalHash = bm.getHash(rightDataIdx) + + bm.mergeTwoKeyValPairs( + getPayload(leftDataIdx), leftOriginalHash, improve(leftOriginalHash), + bm.getPayload(rightDataIdx), rightOriginalHash, improve(rightOriginalHash), + nextShift + ) + } + + newContent(newContentSize - compressedNodeIdx - 1) = newNode + compressedNodeIdx += 1 + leftDataIdx += 1 + rightDataIdx += 1 + newSize += newNode.size + newCachedHashCode += newNode.cachedJavaKeySetHashCode + } else if ((bitpos & leftDataRightDataLeftOverwrites) != 0) { + val originalHash = bm.originalHashes(rightDataIdx) + newContent(compressedDataIdx) = bm.getPayload(rightDataIdx).asInstanceOf[AnyRef] + newOriginalHashes(compressedDataIdx) = originalHash + + compressedDataIdx += 1 + rightDataIdx += 1 + newSize += 1 + newCachedHashCode += improve(originalHash) + leftDataIdx += 1 + } + + if (bitpos == maximumBitPos) { + finished = true + } else { + bitpos = bitpos << 1 + } + } + } + + if (anyChangesMadeSoFar) + new BitmapIndexedSetNode( + dataMap = newDataMap, + nodeMap = newNodeMap, + content = newContent, + originalHashes = newOriginalHashes, + size = newSize, + cachedJavaKeySetHashCode = newCachedHashCode + ) + else this + + case _ => + // should never happen -- hash collisions are never at the same level as bitmapIndexedSetNodes + throw new UnsupportedOperationException("Cannot concatenate a HashCollisionSetNode with a BitmapIndexedSetNode") + } + + override def foreachWithHash(f: (A, Int) => Unit): Unit = { + val iN = payloadArity // arity doesn't change during this operation + var i = 0 + while (i < iN) { + f(getPayload(i), getHash(i)) + i += 1 + } + + val jN = nodeArity // arity doesn't change during this operation + var j = 0 + while (j < jN) { + getNode(j).foreachWithHash(f) + j += 1 + } + } + + override def foreachWithHashWhile(f: (A, Int) => Boolean): Boolean = { + val thisPayloadArity = payloadArity + var pass = true + var i = 0 + while (i < thisPayloadArity && pass) { + pass &&= f(getPayload(i), getHash(i)) + i += 1 + } + + val thisNodeArity = nodeArity + var j = 0 + while (j < thisNodeArity && pass) { + pass &&= getNode(j).foreachWithHashWhile(f) + j += 1 + } + pass + } +} + +private final class HashCollisionSetNode[A](val originalHash: Int, val hash: Int, var content: Vector[A]) extends SetNode[A] { + + import Node._ + + require(content.length >= 2) + + def contains(element: A, originalHash: Int, hash: Int, shift: Int): Boolean = + this.hash == hash && content.contains(element) + + def updated(element: A, originalHash: Int, hash: Int, shift: Int): SetNode[A] = + if (this.contains(element, originalHash, hash, shift)) { + this + } else { + new HashCollisionSetNode[A](originalHash, hash, content.appended(element)) + } + + /** + * Remove an element from the hash collision node. + * + * When after deletion only one element remains, we return a bit-mapped indexed node with a + * singleton element and a hash-prefix for trie level 0. This node will be then a) either become + * the new root, or b) unwrapped and inlined deeper in the trie. + */ + def removed(element: A, originalHash: Int, hash: Int, shift: Int): SetNode[A] = + if (!this.contains(element, originalHash, hash, shift)) { + this + } else { + val updatedContent = content.filterNot(element0 => element0 == element) + // assert(updatedContent.size == content.size - 1) + + updatedContent.size match { + case 1 => new BitmapIndexedSetNode[A](bitposFrom(maskFrom(hash, 0)), 0, Array(updatedContent(0)), Array(originalHash), 1, hash) + case _ => new HashCollisionSetNode[A](originalHash, hash, updatedContent) + } + } + + def hasNodes: Boolean = false + + def nodeArity: Int = 0 + + def getNode(index: Int): SetNode[A] = + throw new IndexOutOfBoundsException("No sub-nodes present in hash-collision leaf node.") + + def hasPayload: Boolean = true + + def payloadArity: Int = content.length + + def getPayload(index: Int): A = content(index) + + override def getHash(index: Int): Int = originalHash + + def size: Int = content.length + + def foreach[U](f: A => U): Unit = { + val iter = content.iterator + while (iter.hasNext) { + f(iter.next()) + } + } + + + override def cachedJavaKeySetHashCode: Int = size * hash + + def subsetOf(that: SetNode[A], shift: Int): Boolean = if (this eq that) true else that match { + case node: HashCollisionSetNode[A] => + this.payloadArity <= node.payloadArity && this.content.forall(node.content.contains) + case _ => + false + } + + override def filterImpl(pred: A => Boolean, flipped: Boolean): SetNode[A] = { + val newContent = content.filterImpl(pred, flipped) + val newContentLength = newContent.length + if (newContentLength == 0) { + SetNode.empty + } else if (newContentLength == 1) { + new BitmapIndexedSetNode[A](bitposFrom(maskFrom(hash, 0)), 0, Array(newContent.head), Array(originalHash), 1, hash) + } else if (newContent.length == content.length) this + else new HashCollisionSetNode(originalHash, hash, newContent) + } + + override def diff(that: SetNode[A], shift: Int): SetNode[A] = + filterImpl(that.contains(_, originalHash, hash, shift), flipped = true) + + override def equals(that: Any): Boolean = + that match { + case node: HashCollisionSetNode[_] => + (this eq node) || + (this.hash == node.hash) && + (this.content.size == node.content.size) && + this.content.forall(node.content.contains) + case _ => false + } + + override def hashCode(): Int = + throw new UnsupportedOperationException("Trie nodes do not support hashing.") + + override def copy(): HashCollisionSetNode[A] = new HashCollisionSetNode[A](originalHash, hash, content) + + override def concat(that: SetNode[A], shift: Int): SetNode[A] = that match { + case hc: HashCollisionSetNode[A] => + if (hc eq this) { + this + } else { + var newContent: VectorBuilder[A] = null + val iter = hc.content.iterator + while (iter.hasNext) { + val nextPayload = iter.next() + if (!content.contains(nextPayload)) { + if (newContent eq null) { + newContent = new VectorBuilder() + newContent.addAll(this.content) + } + newContent.addOne(nextPayload) + } + } + if (newContent eq null) this else new HashCollisionSetNode(originalHash, hash, newContent.result()) + } + case _: BitmapIndexedSetNode[A] => + // should never happen -- hash collisions are never at the same level as bitmapIndexedSetNodes + throw new UnsupportedOperationException("Cannot concatenate a HashCollisionSetNode with a BitmapIndexedSetNode") + } + + override def foreachWithHash(f: (A, Int) => Unit): Unit = { + val iter = content.iterator + while (iter.hasNext) { + val next = iter.next() + f(next.asInstanceOf[A], originalHash) + } + } + + override def foreachWithHashWhile(f: (A, Int) => Boolean): Boolean = { + var stillGoing = true + val iter = content.iterator + while (iter.hasNext && stillGoing) { + val next = iter.next() + stillGoing &&= f(next.asInstanceOf[A], originalHash) + } + stillGoing + } +} + +private final class SetIterator[A](rootNode: SetNode[A]) + extends ChampBaseIterator[A, SetNode[A]](rootNode) { + + def next() = { + if (!hasNext) Iterator.empty.next() + + val payload = currentValueNode.getPayload(currentValueCursor) + currentValueCursor += 1 + + payload + } + +} + +private final class SetReverseIterator[A](rootNode: SetNode[A]) + extends ChampBaseReverseIterator[A, SetNode[A]](rootNode) { + + def next(): A = { + if (!hasNext) Iterator.empty.next() + + val payload = currentValueNode.getPayload(currentValueCursor) + currentValueCursor -= 1 + + payload + } + +} + +private final class SetHashIterator[A](rootNode: SetNode[A]) + extends ChampBaseIterator[AnyRef, SetNode[A]](rootNode) { + private[this] var hash = 0 + override def hashCode(): Int = hash + + def next(): AnyRef = { + if (!hasNext) Iterator.empty.next() + + hash = currentValueNode.getHash(currentValueCursor) + currentValueCursor += 1 + this + } + +} + + +/** + * $factoryInfo + * + * @define Coll `immutable.HashSet` + * @define coll immutable champ hash set + */ +@SerialVersionUID(3L) +object HashSet extends IterableFactory[HashSet] { + + @transient + private final val EmptySet = new HashSet(SetNode.empty) + + def empty[A]: HashSet[A] = + EmptySet.asInstanceOf[HashSet[A]] + + def from[A](source: collection.IterableOnce[A]): HashSet[A] = + source match { + case hs: HashSet[A] => hs + case _ if source.knownSize == 0 => empty[A] + case _ => (newBuilder[A] ++= source).result() + } + + /** Create a new Builder which can be reused after calling `result()` without an + * intermediate call to `clear()` in order to build multiple related results. + */ + def newBuilder[A]: ReusableBuilder[A, HashSet[A]] = new HashSetBuilder +} + +/** Builder for HashSet. + * $multipleResults + */ +private[collection] final class HashSetBuilder[A] extends ReusableBuilder[A, HashSet[A]] { + import Node._ + import SetNode._ + + private def newEmptyRootNode = new BitmapIndexedSetNode[A](0, 0, Array.emptyObjectArray.asInstanceOf[Array[Any]], Array.emptyIntArray, 0, 0) + + /** The last given out HashSet as a return value of `result()`, if any, otherwise null. + * Indicates that on next add, the elements should be copied to an identical structure, before continuing + * mutations. */ + private var aliased: HashSet[A] = _ + + private def isAliased: Boolean = aliased != null + + /** The root node of the partially built hashmap. */ + private var rootNode: BitmapIndexedSetNode[A] = newEmptyRootNode + + /** Inserts element `elem` into array `as` at index `ix`, shifting right the trailing elems */ + private def insertElement(as: Array[Int], ix: Int, elem: Int): Array[Int] = { + if (ix < 0) throw new ArrayIndexOutOfBoundsException + if (ix > as.length) throw new ArrayIndexOutOfBoundsException + val result = new Array[Int](as.length + 1) + arraycopy(as, 0, result, 0, ix) + result(ix) = elem + arraycopy(as, ix, result, ix + 1, as.length - ix) + result + } + + /** Inserts key-value into the bitmapIndexMapNode. Requires that this is a new key-value pair */ + private def insertValue[A1 >: A](bm: BitmapIndexedSetNode[A], bitpos: Int, key: A, originalHash: Int, keyHash: Int): Unit = { + val dataIx = bm.dataIndex(bitpos) + val idx = TupleLength * dataIx + + val src = bm.content + val dst = new Array[Any](src.length + TupleLength) + + // copy 'src' and insert 2 element(s) at position 'idx' + arraycopy(src, 0, dst, 0, idx) + dst(idx) = key + arraycopy(src, idx, dst, idx + TupleLength, src.length - idx) + + val dstHashes = insertElement(bm.originalHashes, dataIx, originalHash) + + bm.dataMap = bm.dataMap | bitpos + bm.content = dst + bm.originalHashes = dstHashes + bm.size += 1 + bm.cachedJavaKeySetHashCode += keyHash + } + + /** Mutates `bm` to replace inline data at bit position `bitpos` with updated key/value */ + private def setValue[A1 >: A](bm: BitmapIndexedSetNode[A], bitpos: Int, elem: A): Unit = { + val dataIx = bm.dataIndex(bitpos) + val idx = TupleLength * dataIx + bm.content(idx) = elem + } + + def update(setNode: SetNode[A], element: A, originalHash: Int, elementHash: Int, shift: Int): Unit = + setNode match { + case bm: BitmapIndexedSetNode[A] => + val mask = maskFrom(elementHash, shift) + val bitpos = bitposFrom(mask) + + if ((bm.dataMap & bitpos) != 0) { + val index = indexFrom(bm.dataMap, mask, bitpos) + val element0 = bm.getPayload(index) + val element0UnimprovedHash = bm.getHash(index) + + if (element0UnimprovedHash == originalHash && element0 == element) { + setValue(bm, bitpos, element0) + } else { + val element0Hash = improve(element0UnimprovedHash) + val subNodeNew = bm.mergeTwoKeyValPairs(element0, element0UnimprovedHash, element0Hash, element, originalHash, elementHash, shift + BitPartitionSize) + bm.migrateFromInlineToNodeInPlace(bitpos, element0Hash, subNodeNew) + } + } else if ((bm.nodeMap & bitpos) != 0) { + val index = indexFrom(bm.nodeMap, mask, bitpos) + val subNode = bm.getNode(index) + val beforeSize = subNode.size + val beforeHashCode = subNode.cachedJavaKeySetHashCode + update(subNode, element, originalHash, elementHash, shift + BitPartitionSize) + bm.size += subNode.size - beforeSize + bm.cachedJavaKeySetHashCode += subNode.cachedJavaKeySetHashCode - beforeHashCode + } else { + insertValue(bm, bitpos, element, originalHash, elementHash) + } + case hc: HashCollisionSetNode[A] => + val index = hc.content.indexOf(element) + if (index < 0) { + hc.content = hc.content.appended(element) + } else { + hc.content = hc.content.updated(index, element) + } + } + + /** If currently referencing aliased structure, copy elements to new mutable structure */ + private def ensureUnaliased():Unit = { + if (isAliased) copyElems() + aliased = null + } + + /** Copy elements to new mutable structure */ + private def copyElems(): Unit = { + rootNode = rootNode.copy() + } + + override def result(): HashSet[A] = + if (rootNode.size == 0) { + HashSet.empty + } else if (aliased != null) { + aliased + } else { + aliased = new HashSet(rootNode) + releaseFence() + aliased + } + + override def addOne(elem: A): this.type = { + ensureUnaliased() + val h = elem.## + val im = improve(h) + update(rootNode, elem, originalHash = h, elementHash = im, shift = 0) + this + } + + override def addAll(xs: IterableOnce[A]) = { + ensureUnaliased() + xs match { + case hm: HashSet[A] => + new ChampBaseIterator[A, SetNode[A]](hm.rootNode) { + while(hasNext) { + val originalHash = currentValueNode.getHash(currentValueCursor) + update( + setNode = rootNode, + element = currentValueNode.getPayload(currentValueCursor), + originalHash = originalHash, + elementHash = improve(originalHash), + shift = 0 + ) + currentValueCursor += 1 + } + override def next() = Iterator.empty.next() + } + case other => + val it = other.iterator + while(it.hasNext) addOne(it.next()) + } + + this + } + + override def clear(): Unit = { + aliased = null + if (rootNode.size > 0) { + // if rootNode is empty, we will not have given it away anyways, we instead give out the reused Set.empty + rootNode = newEmptyRootNode + } + } + + private[collection] def size: Int = rootNode.size + + override def knownSize: Int = rootNode.size +} diff --git a/library/src/scala/collection/immutable/IntMap.scala b/library/src/scala/collection/immutable/IntMap.scala new file mode 100644 index 000000000000..1728da6c5710 --- /dev/null +++ b/library/src/scala/collection/immutable/IntMap.scala @@ -0,0 +1,503 @@ +/* + * 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.collection +package immutable + +import scala.language.`2.13` +import scala.collection.generic.{BitOperations, DefaultSerializationProxy} +import scala.collection.mutable.{Builder, ImmutableBuilder} +import scala.annotation.tailrec +import scala.annotation.unchecked.uncheckedVariance +import scala.language.implicitConversions + +/** Utility class for integer maps. + */ +private[immutable] object IntMapUtils extends BitOperations.Int { + def branchMask(i: Int, j: Int) = highestOneBit(i ^ j) + + def join[T](p1: Int, t1: IntMap[T], p2: Int, t2: IntMap[T]): IntMap[T] = { + val m = branchMask(p1, p2) + val p = mask(p1, m) + if (zero(p1, m)) IntMap.Bin(p, m, t1, t2) + else IntMap.Bin(p, m, t2, t1) + } + + def bin[T](prefix: Int, mask: Int, left: IntMap[T], right: IntMap[T]): IntMap[T] = (left, right) match { + case (left, IntMap.Nil) => left + case (IntMap.Nil, right) => right + case (left, right) => IntMap.Bin(prefix, mask, left, right) + } +} + +import IntMapUtils.{Int => _, _} + +/** A companion object for integer maps. + * + * @define Coll `IntMap` + */ +object IntMap { + def empty[T] : IntMap[T] = IntMap.Nil + + def singleton[T](key: Int, value: T): IntMap[T] = IntMap.Tip(key, value) + + def apply[T](elems: (Int, T)*): IntMap[T] = + elems.foldLeft(empty[T])((x, y) => x.updated(y._1, y._2)) + + def from[V](coll: IterableOnce[(Int, V)]): IntMap[V] = + newBuilder[V].addAll(coll).result() + + private[immutable] case object Nil extends IntMap[Nothing] { + // Important! Without this equals method in place, an infinite + // loop from Map.equals => size => pattern-match-on-Nil => equals + // develops. Case objects and custom equality don't mix without + // careful handling. + override def equals(that : Any) = that match { + case _: this.type => true + case _: IntMap[_] => false // The only empty IntMaps are eq Nil + case _ => super.equals(that) + } + } + + private[immutable] case class Tip[+T](key: Int, value: T) extends IntMap[T]{ + def withValue[S](s: S) = + if (s.asInstanceOf[AnyRef] eq value.asInstanceOf[AnyRef]) this.asInstanceOf[IntMap.Tip[S]] + else IntMap.Tip(key, s) + } + + private[immutable] case class Bin[+T](prefix: Int, mask: Int, left: IntMap[T], right: IntMap[T]) extends IntMap[T] { + def bin[S](left: IntMap[S], right: IntMap[S]): IntMap[S] = { + if ((this.left eq left) && (this.right eq right)) this.asInstanceOf[IntMap.Bin[S]] + else IntMap.Bin[S](prefix, mask, left, right) + } + } + + def newBuilder[V]: Builder[(Int, V), IntMap[V]] = + new ImmutableBuilder[(Int, V), IntMap[V]](empty) { + def addOne(elem: (Int, V)): this.type = { elems = elems + elem; this } + } + + implicit def toFactory[V](dummy: IntMap.type): Factory[(Int, V), IntMap[V]] = ToFactory.asInstanceOf[Factory[(Int, V), IntMap[V]]] + + @SerialVersionUID(3L) + private[this] object ToFactory extends Factory[(Int, AnyRef), IntMap[AnyRef]] with Serializable { + def fromSpecific(it: IterableOnce[(Int, AnyRef)]): IntMap[AnyRef] = IntMap.from[AnyRef](it) + def newBuilder: Builder[(Int, AnyRef), IntMap[AnyRef]] = IntMap.newBuilder[AnyRef] + } + + implicit def toBuildFrom[V](factory: IntMap.type): BuildFrom[Any, (Int, V), IntMap[V]] = ToBuildFrom.asInstanceOf[BuildFrom[Any, (Int, V), IntMap[V]]] + private[this] object ToBuildFrom extends BuildFrom[Any, (Int, AnyRef), IntMap[AnyRef]] { + def fromSpecific(from: Any)(it: IterableOnce[(Int, AnyRef)]) = IntMap.from(it) + def newBuilder(from: Any) = IntMap.newBuilder[AnyRef] + } + + implicit def iterableFactory[V]: Factory[(Int, V), IntMap[V]] = toFactory(this) + implicit def buildFromIntMap[V]: BuildFrom[IntMap[_], (Int, V), IntMap[V]] = toBuildFrom(this) +} + +// Iterator over a non-empty IntMap. +private[immutable] abstract class IntMapIterator[V, T](it: IntMap[V]) extends AbstractIterator[T] { + + // Basically this uses a simple stack to emulate conversion over the tree. However + // because we know that Ints are at least 32 bits we can have at most 32 IntMap.Bins and + // one IntMap.Tip sitting on the tree at any point. Therefore we know the maximum stack + // depth is 33 and + var index = 0 + var buffer = new Array[AnyRef](33) + + def pop = { + index -= 1 + buffer(index).asInstanceOf[IntMap[V]] + } + + def push(x: IntMap[V]): Unit = { + buffer(index) = x.asInstanceOf[AnyRef] + index += 1 + } + push(it) + + /** + * What value do we assign to a tip? + */ + def valueOf(tip: IntMap.Tip[V]): T + + def hasNext = index != 0 + @tailrec + final def next(): T = + pop match { + case IntMap.Bin(_,_, t@IntMap.Tip(_, _), right) => { + push(right) + valueOf(t) + } + case IntMap.Bin(_, _, left, right) => { + push(right) + push(left) + next() + } + case t@IntMap.Tip(_, _) => valueOf(t) + // This should never happen. We don't allow IntMap.Nil in subtrees of the IntMap + // and don't return an IntMapIterator for IntMap.Nil. + case IntMap.Nil => throw new IllegalStateException("Empty maps not allowed as subtrees") + } +} + +private[immutable] class IntMapEntryIterator[V](it: IntMap[V]) extends IntMapIterator[V, (Int, V)](it) { + def valueOf(tip: IntMap.Tip[V]) = (tip.key, tip.value) +} + +private[immutable] class IntMapValueIterator[V](it: IntMap[V]) extends IntMapIterator[V, V](it) { + def valueOf(tip: IntMap.Tip[V]) = tip.value +} + +private[immutable] class IntMapKeyIterator[V](it: IntMap[V]) extends IntMapIterator[V, Int](it) { + def valueOf(tip: IntMap.Tip[V]) = tip.key +} + +import IntMap._ + +/** Specialised immutable map structure for integer keys, based on + * [[https://ittc.ku.edu/~andygill/papers/IntMap98.pdf Fast Mergeable Integer Maps]] + * by Okasaki and Gill. Essentially a trie based on binary digits of the integers. + * + * '''Note:''' This class is as of 2.8 largely superseded by HashMap. + * + * @tparam T type of the values associated with integer keys. + * + * @define Coll `immutable.IntMap` + * @define coll immutable integer map + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +sealed abstract class IntMap[+T] extends AbstractMap[Int, T] + with StrictOptimizedMapOps[Int, T, Map, IntMap[T]] + with Serializable { + + override protected def fromSpecific(coll: scala.collection.IterableOnce[(Int, T) @uncheckedVariance]): IntMap[T] = + intMapFrom[T](coll) + protected def intMapFrom[V2](coll: scala.collection.IterableOnce[(Int, V2)]): IntMap[V2] = { + val b = IntMap.newBuilder[V2] + b.sizeHint(coll) + b.addAll(coll) + b.result() + } + override protected def newSpecificBuilder: Builder[(Int, T), IntMap[T]] @uncheckedVariance = + new ImmutableBuilder[(Int, T), IntMap[T]](empty) { + def addOne(elem: (Int, T)): this.type = { elems = elems + elem; this } + } + + override def empty: IntMap[T] = IntMap.Nil + + override def toList = { + val buffer = new scala.collection.mutable.ListBuffer[(Int, T)] + foreach(buffer += _) + buffer.toList + } + + /** + * Iterator over key, value pairs of the map in unsigned order of the keys. + * + * @return an iterator over pairs of integer keys and corresponding values. + */ + def iterator: Iterator[(Int, T)] = this match { + case IntMap.Nil => Iterator.empty + case _ => new IntMapEntryIterator(this) + } + + /** + * Loops over the key, value pairs of the map in unsigned order of the keys. + */ + override final def foreach[U](f: ((Int, T)) => U): Unit = this match { + case IntMap.Bin(_, _, left, right) => { left.foreach(f); right.foreach(f) } + case IntMap.Tip(key, value) => f((key, value)) + case IntMap.Nil => + } + + override def foreachEntry[U](f: (Int, T) => U): Unit = this match { + case IntMap.Bin(_, _, left, right) => { left.foreachEntry(f); right.foreachEntry(f) } + case IntMap.Tip(key, value) => f(key, value) + case IntMap.Nil => + } + + override def keysIterator: Iterator[Int] = this match { + case IntMap.Nil => Iterator.empty + case _ => new IntMapKeyIterator(this) + } + + /** + * Loop over the keys of the map. The same as `keys.foreach(f)`, but may + * be more efficient. + * + * @param f The loop body + */ + final def foreachKey[U](f: Int => U): Unit = this match { + case IntMap.Bin(_, _, left, right) => { left.foreachKey(f); right.foreachKey(f) } + case IntMap.Tip(key, _) => f(key) + case IntMap.Nil => + } + + override def valuesIterator: Iterator[T] = this match { + case IntMap.Nil => Iterator.empty + case _ => new IntMapValueIterator(this) + } + + /** + * Loop over the values of the map. The same as `values.foreach(f)`, but may + * be more efficient. + * + * @param f The loop body + */ + final def foreachValue[U](f: T => U): Unit = this match { + case IntMap.Bin(_, _, left, right) => { left.foreachValue(f); right.foreachValue(f) } + case IntMap.Tip(_, value) => f(value) + case IntMap.Nil => + } + + override protected[this] def className = "IntMap" + + override def isEmpty = this eq IntMap.Nil + override def knownSize: Int = if (isEmpty) 0 else super.knownSize + override def filter(f: ((Int, T)) => Boolean): IntMap[T] = this match { + case IntMap.Bin(prefix, mask, left, right) => { + val (newleft, newright) = (left.filter(f), right.filter(f)) + if ((left eq newleft) && (right eq newright)) this + else bin(prefix, mask, newleft, newright) + } + case IntMap.Tip(key, value) => + if (f((key, value))) this + else IntMap.Nil + case IntMap.Nil => IntMap.Nil + } + + override def transform[S](f: (Int, T) => S): IntMap[S] = this match { + case b@IntMap.Bin(prefix, mask, left, right) => b.bin(left.transform(f), right.transform(f)) + case t@IntMap.Tip(key, value) => t.withValue(f(key, value)) + case IntMap.Nil => IntMap.Nil + } + + final override def size: Int = this match { + case IntMap.Nil => 0 + case IntMap.Tip(_, _) => 1 + case IntMap.Bin(_, _, left, right) => left.size + right.size + } + + @tailrec + final def get(key: Int): Option[T] = this match { + case IntMap.Bin(prefix, mask, left, right) => if (zero(key, mask)) left.get(key) else right.get(key) + case IntMap.Tip(key2, value) => if (key == key2) Some(value) else None + case IntMap.Nil => None + } + + @tailrec + final override def getOrElse[S >: T](key: Int, default: => S): S = this match { + case IntMap.Nil => default + case IntMap.Tip(key2, value) => if (key == key2) value else default + case IntMap.Bin(prefix, mask, left, right) => + if (zero(key, mask)) left.getOrElse(key, default) else right.getOrElse(key, default) + } + + @tailrec + final override def apply(key: Int): T = this match { + case IntMap.Bin(prefix, mask, left, right) => if (zero(key, mask)) left(key) else right(key) + case IntMap.Tip(key2, value) => if (key == key2) value else throw new IllegalArgumentException("Key not found") + case IntMap.Nil => throw new IllegalArgumentException("key not found") + } + + override def + [S >: T] (kv: (Int, S)): IntMap[S] = updated(kv._1, kv._2) + + override def updated[S >: T](key: Int, value: S): IntMap[S] = this match { + case IntMap.Bin(prefix, mask, left, right) => + if (!hasMatch(key, prefix, mask)) join(key, IntMap.Tip(key, value), prefix, this) + else if (zero(key, mask)) IntMap.Bin(prefix, mask, left.updated(key, value), right) + else IntMap.Bin(prefix, mask, left, right.updated(key, value)) + case IntMap.Tip(key2, value2) => + if (key == key2) IntMap.Tip(key, value) + else join(key, IntMap.Tip(key, value), key2, this) + case IntMap.Nil => IntMap.Tip(key, value) + } + + def map[V2](f: ((Int, T)) => (Int, V2)): IntMap[V2] = intMapFrom(new View.Map(this, f)) + + def flatMap[V2](f: ((Int, T)) => IterableOnce[(Int, V2)]): IntMap[V2] = intMapFrom(new View.FlatMap(this, f)) + + override def concat[V1 >: T](that: collection.IterableOnce[(Int, V1)]): IntMap[V1] = + super.concat(that).asInstanceOf[IntMap[V1]] // Already has correct type but not declared as such + + override def ++ [V1 >: T](that: collection.IterableOnce[(Int, V1)]): IntMap[V1] = concat(that) + + def collect[V2](pf: PartialFunction[(Int, T), (Int, V2)]): IntMap[V2] = + strictOptimizedCollect(IntMap.newBuilder[V2], pf) + + /** + * Updates the map, using the provided function to resolve conflicts if the key is already present. + * + * Equivalent to: + * {{{ + * this.get(key) match { + * case None => this.update(key, value) + * case Some(oldvalue) => this.update(key, f(oldvalue, value) + * } + * }}} + * + * @tparam S The supertype of values in this `LongMap`. + * @param key The key to update + * @param value The value to use if there is no conflict + * @param f The function used to resolve conflicts. + * @return The updated map. + */ + def updateWith[S >: T](key: Int, value: S, f: (T, S) => S): IntMap[S] = this match { + case IntMap.Bin(prefix, mask, left, right) => + if (!hasMatch(key, prefix, mask)) join(key, IntMap.Tip(key, value), prefix, this) + else if (zero(key, mask)) IntMap.Bin(prefix, mask, left.updateWith(key, value, f), right) + else IntMap.Bin(prefix, mask, left, right.updateWith(key, value, f)) + case IntMap.Tip(key2, value2) => + if (key == key2) IntMap.Tip(key, f(value2, value)) + else join(key, IntMap.Tip(key, value), key2, this) + case IntMap.Nil => IntMap.Tip(key, value) + } + + def removed (key: Int): IntMap[T] = this match { + case IntMap.Bin(prefix, mask, left, right) => + if (!hasMatch(key, prefix, mask)) this + else if (zero(key, mask)) bin(prefix, mask, left - key, right) + else bin(prefix, mask, left, right - key) + case IntMap.Tip(key2, _) => + if (key == key2) IntMap.Nil + else this + case IntMap.Nil => IntMap.Nil + } + + /** + * A combined transform and filter function. Returns an `IntMap` such that + * for each `(key, value)` mapping in this map, if `f(key, value) == None` + * the map contains no mapping for key, and if `f(key, value)`. + * + * @tparam S The type of the values in the resulting `LongMap`. + * @param f The transforming function. + * @return The modified map. + */ + def modifyOrRemove[S](f: (Int, T) => Option[S]): IntMap[S] = this match { + case IntMap.Bin(prefix, mask, left, right) => + val newleft = left.modifyOrRemove(f) + val newright = right.modifyOrRemove(f) + if ((left eq newleft) && (right eq newright)) this.asInstanceOf[IntMap[S]] + else bin(prefix, mask, newleft, newright) + case IntMap.Tip(key, value) => f(key, value) match { + case None => + IntMap.Nil + case Some(value2) => + //hack to preserve sharing + if (value.asInstanceOf[AnyRef] eq value2.asInstanceOf[AnyRef]) this.asInstanceOf[IntMap[S]] + else IntMap.Tip(key, value2) + } + case IntMap.Nil => + IntMap.Nil + } + + /** + * Forms a union map with that map, using the combining function to resolve conflicts. + * + * @tparam S The type of values in `that`, a supertype of values in `this`. + * @param that The map to form a union with. + * @param f The function used to resolve conflicts between two mappings. + * @return Union of `this` and `that`, with identical key conflicts resolved using the function `f`. + */ + def unionWith[S >: T](that: IntMap[S], f: (Int, S, S) => S): IntMap[S] = (this, that) match{ + case (IntMap.Bin(p1, m1, l1, r1), that@(IntMap.Bin(p2, m2, l2, r2))) => + if (shorter(m1, m2)) { + if (!hasMatch(p2, p1, m1)) join(p1, this, p2, that) + else if (zero(p2, m1)) IntMap.Bin(p1, m1, l1.unionWith(that, f), r1) + else IntMap.Bin(p1, m1, l1, r1.unionWith(that, f)) + } else if (shorter(m2, m1)){ + if (!hasMatch(p1, p2, m2)) join(p1, this, p2, that) + else if (zero(p1, m2)) IntMap.Bin(p2, m2, this.unionWith(l2, f), r2) + else IntMap.Bin(p2, m2, l2, this.unionWith(r2, f)) + } + else { + if (p1 == p2) IntMap.Bin(p1, m1, l1.unionWith(l2,f), r1.unionWith(r2, f)) + else join(p1, this, p2, that) + } + case (IntMap.Tip(key, value), x) => x.updateWith[S](key, value, (x, y) => f(key, y, x)) + case (x, IntMap.Tip(key, value)) => x.updateWith[S](key, value, (x, y) => f(key, x, y)) + case (IntMap.Nil, x) => x + case (x, IntMap.Nil) => x + } + + /** + * Forms the intersection of these two maps with a combining function. The + * resulting map is a map that has only keys present in both maps and has + * values produced from the original mappings by combining them with `f`. + * + * @tparam S The type of values in `that`. + * @tparam R The type of values in the resulting `LongMap`. + * @param that The map to intersect with. + * @param f The combining function. + * @return Intersection of `this` and `that`, with values for identical keys produced by function `f`. + */ + def intersectionWith[S, R](that: IntMap[S], f: (Int, T, S) => R): IntMap[R] = (this, that) match { + case (IntMap.Bin(p1, m1, l1, r1), that@IntMap.Bin(p2, m2, l2, r2)) => + if (shorter(m1, m2)) { + if (!hasMatch(p2, p1, m1)) IntMap.Nil + else if (zero(p2, m1)) l1.intersectionWith(that, f) + else r1.intersectionWith(that, f) + } else if (m1 == m2) bin(p1, m1, l1.intersectionWith(l2, f), r1.intersectionWith(r2, f)) + else { + if (!hasMatch(p1, p2, m2)) IntMap.Nil + else if (zero(p1, m2)) this.intersectionWith(l2, f) + else this.intersectionWith(r2, f) + } + case (IntMap.Tip(key, value), that) => that.get(key) match { + case None => IntMap.Nil + case Some(value2) => IntMap.Tip(key, f(key, value, value2)) + } + case (_, IntMap.Tip(key, value)) => this.get(key) match { + case None => IntMap.Nil + case Some(value2) => IntMap.Tip(key, f(key, value2, value)) + } + case (_, _) => IntMap.Nil + } + + /** + * Left biased intersection. Returns the map that has all the same mappings + * as this but only for keys which are present in the other map. + * + * @tparam R The type of values in `that`. + * @param that The map to intersect with. + * @return A map with all the keys both in `this` and `that`, mapped to corresponding values from `this`. + */ + def intersection[R](that: IntMap[R]): IntMap[T] = + this.intersectionWith(that, (key: Int, value: T, value2: R) => value) + + def ++[S >: T](that: IntMap[S]) = + this.unionWith[S](that, (key, x, y) => y) + + /** + * The entry with the lowest key value considered in unsigned order. + */ + @tailrec + final def firstKey: Int = this match { + case Bin(_, _, l, r) => l.firstKey + case Tip(k, v) => k + case IntMap.Nil => throw new IllegalStateException("Empty set") + } + + /** + * The entry with the highest key value considered in unsigned order. + */ + @tailrec + final def lastKey: Int = this match { + case Bin(_, _, l, r) => r.lastKey + case Tip(k, v) => k + case IntMap.Nil => throw new IllegalStateException("Empty set") + } + + protected[this] def writeReplace(): AnyRef = new DefaultSerializationProxy(IntMap.toFactory[T](IntMap), this) +} diff --git a/library/src/scala/collection/immutable/Iterable.scala b/library/src/scala/collection/immutable/Iterable.scala new file mode 100644 index 000000000000..0691663e7e4d --- /dev/null +++ b/library/src/scala/collection/immutable/Iterable.scala @@ -0,0 +1,38 @@ +/* + * 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.collection.immutable + +import scala.language.`2.13` +import scala.collection.{IterableFactory, IterableFactoryDefaults} + +/** A trait for collections that are guaranteed immutable. + * + * @tparam A the element type of the collection + * + * @define coll immutable collection + * @define Coll `immutable.Iterable` + */ +trait Iterable[+A] extends collection.Iterable[A] + with collection.IterableOps[A, Iterable, Iterable[A]] + with IterableFactoryDefaults[A, Iterable] { + + override def iterableFactory: IterableFactory[Iterable] = Iterable +} + +@SerialVersionUID(3L) +object Iterable extends IterableFactory.Delegate[Iterable](List) { + override def from[E](it: IterableOnce[E]): Iterable[E] = it match { + case iterable: Iterable[E] => iterable + case _ => super.from(it) + } +} diff --git a/library/src/scala/collection/immutable/LazyList.scala b/library/src/scala/collection/immutable/LazyList.scala new file mode 100644 index 000000000000..bcf5f5df17a8 --- /dev/null +++ b/library/src/scala/collection/immutable/LazyList.scala @@ -0,0 +1,1448 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import java.io.{ObjectInputStream, ObjectOutputStream} +import java.lang.{StringBuilder => JStringBuilder} + +import scala.annotation.tailrec +import scala.collection.generic.SerializeEnd +import scala.collection.mutable.{Builder, ReusableBuilder, StringBuilder} +import scala.language.implicitConversions +import scala.runtime.Statics + +/** This class implements an immutable linked list. We call it "lazy" + * because it computes its elements only when they are needed. + * + * Elements are memoized; that is, the value of each element is computed at most once. + * + * Elements are computed in order and are never skipped. + * As a consequence, accessing the tail causes the head to be computed first. + * + * How lazy is a `LazyList`? When you have a value of type `LazyList`, you + * don't know yet whether the list is empty. + * We say that it is lazy in its head. + * If you have tested that it is non-empty, + * then you also know that the head has been computed. + * + * It is also lazy in its tail, which is also a `LazyList`. + * You don't know whether the tail is empty until it is "forced", which is to say, + * until an element of the tail is computed. + * + * These important properties of `LazyList` depend on its construction using `#::` (or `#:::`). + * That operator is analogous to the "cons" of a strict `List`, `::`. + * It is "right-associative", so that the collection goes on the "right", + * and the element on the left of the operator is prepended to the collection. + * However, unlike the cons of a strict `List`, `#::` is lazy in its parameter, + * which is the element prepended to the left, and also lazy in its right-hand side, + * which is the `LazyList` being prepended to. + * (That is accomplished by implicitly wrapping the `LazyList`, as shown in the Scaladoc.) + * + * Other combinators from the collections API do not preserve this laziness. + * In particular, `++`, or `concat`, is "eager" or "strict" in its parameter + * and should not be used to compose `LazyList`s. + * + * A `LazyList` may be infinite. For example, `LazyList.from(0)` contains + * all of the natural numbers `0`, `1`, `2`, ... For infinite sequences, + * some methods (such as `count`, `sum`, `max` or `min`) will not terminate. + * + * Here is an example showing the Fibonacci sequence, + * which may be evaluated to an arbitrary number of elements: + * + * {{{ + * import scala.math.BigInt + * object Main extends App { + * val fibs: LazyList[BigInt] = + * BigInt(0) #:: BigInt(1) #:: fibs.zip(fibs.tail).map(n => n._1 + n._2) + * println { + * fibs.take(5).mkString(", ") + * } + * } + * // prints: 0, 1, 1, 2, 3 + * }}} + * + * To illustrate, let's add some output to the definition `fibs`, so we + * see what's going on. + * + * {{{ + * import scala.math.BigInt + * import scala.util.chaining._ + * object Main extends App { + * val fibs: LazyList[BigInt] = + * BigInt(0) #:: BigInt(1) #:: + * fibs.zip(fibs.tail).map(n => (n._1 + n._2) + * .tap(sum => println(s"Adding ${n._1} and ${n._2} => $sum"))) + * fibs.take(5).foreach(println) + * fibs.take(6).foreach(println) + * } + * + * // prints + * // + * // 0 + * // 1 + * // Adding 0 and 1 => 1 + * // 1 + * // Adding 1 and 1 => 2 + * // 2 + * // Adding 1 and 2 => 3 + * // 3 + * + * // And then prints + * // + * // 0 + * // 1 + * // 1 + * // 2 + * // 3 + * // Adding 2 and 3 => 5 + * // 5 + * }}} + * + * Note that the definition of `fibs` uses `val` not `def`. + * Memoization of the `LazyList` requires us to retain a reference to the computed values. + * + * `LazyList` is considered an immutable data structure, even though its elements are computed on demand. + * Once the values are memoized they do not change. + * Moreover, the `LazyList` itself is defined once and references to it are interchangeable. + * Values that have yet to be memoized still "exist"; they simply haven't been computed yet. + * + * Memoization can be a source of memory leaks and must be used with caution. + * It avoids recomputing elements of the list, but if a reference to the head + * is retained unintentionally, then all elements will be retained. + * + * The caveat that all elements are computed in order means + * that some operations, such as [[drop]], [[dropWhile]], [[flatMap]] or [[collect]], + * may process a large number of intermediate elements before returning. + * + * Here's an example that illustrates these behaviors. + * Let's begin with an iteration of the natural numbers. + * + * {{{ + * // We'll start with a silly iteration + * def loop(s: String, i: Int, iter: Iterator[Int]): Unit = { + * // Stop after 200,000 + * if (i < 200001) { + * if (i % 50000 == 0) println(s + i) + * loop(s, iter.next(), iter) + * } + * } + * + * // Our first LazyList definition will be a val definition + * val lazylist1: LazyList[Int] = { + * def loop(v: Int): LazyList[Int] = v #:: loop(v + 1) + * loop(0) + * } + * + * // Because lazylist1 is a val, everything that the iterator produces is held + * // by virtue of the fact that the head of the LazyList is held in lazylist1 + * val it1 = lazylist1.iterator + * loop("Iterator1: ", it1.next(), it1) + * + * // We can redefine this LazyList such that we retain only a reference to its Iterator. + * // That allows the LazyList to be garbage collected. + * // Using `def` to produce the LazyList in a method ensures + * // that no val is holding onto the head, as with lazylist1. + * def lazylist2: LazyList[Int] = { + * def loop(v: Int): LazyList[Int] = v #:: loop(v + 1) + * loop(0) + * } + * val it2 = lazylist2.iterator + * loop("Iterator2: ", it2.next(), it2) + * + * // And, of course, we don't actually need a LazyList at all for such a simple + * // problem. There's no reason to use a LazyList if you don't actually need + * // one. + * val it3 = new Iterator[Int] { + * var i = -1 + * def hasNext = true + * def next(): Int = { i += 1; i } + * } + * loop("Iterator3: ", it3.next(), it3) + * }}} + * + * In the `fibs` example earlier, the fact that `tail` works at all is of interest. + * `fibs` has an initial `(0, 1, LazyList(...))`, so `tail` is deterministic. + * If we defined `fibs` such that only `0` were concretely known, then the act + * of determining `tail` would require the evaluation of `tail`, so the + * computation would be unable to progress, as in this code: + * {{{ + * // The first time we try to access the tail we're going to need more + * // information which will require us to recurse, which will require us to + * // recurse, which... + * lazy val sov: LazyList[Vector[Int]] = Vector(0) #:: sov.zip(sov.tail).map { n => n._1 ++ n._2 } + * }}} + * + * The definition of `fibs` above creates a larger number of objects than + * necessary depending on how you might want to implement it. The following + * implementation provides a more "cost effective" implementation due to the + * fact that it has a more direct route to the numbers themselves: + * + * {{{ + * lazy val fib: LazyList[Int] = { + * def loop(h: Int, n: Int): LazyList[Int] = h #:: loop(n, h + n) + * loop(1, 1) + * } + * }}} + * + * The head, the tail and whether the list is empty is initially unknown. + * Once any of those are evaluated, they are all known, though if the tail is + * built with `#::` or `#:::`, its content still isn't evaluated. Instead, evaluating + * the tail's content is deferred until the tail's empty status, head or tail is + * evaluated. + * + * Delaying the evaluation of whether a LazyList is empty until it's needed + * allows LazyList to not eagerly evaluate any elements on a call to `filter`. + * + * Only when it's further evaluated (which may be never!) do any of the elements get forced. + * + * For example: + * + * {{{ + * def tailWithSideEffect: LazyList[Nothing] = { + * println("getting empty LazyList") + * LazyList.empty + * } + * + * val emptyTail = tailWithSideEffect // prints "getting empty LazyList" + * + * val suspended = 1 #:: tailWithSideEffect // doesn't print anything + * val tail = suspended.tail // although the tail is evaluated, *still* nothing is yet printed + * val filtered = tail.filter(_ => false) // still nothing is printed + * filtered.isEmpty // prints "getting empty LazyList" + * }}} + * + * ---- + * + * You may sometimes encounter an exception like the following: + * + * {{{ + * java.lang.RuntimeException: "LazyList evaluation depends on its own result (self-reference); see docs for more info + * }}} + * + * This exception occurs when a `LazyList` is attempting to derive its next element + * from itself, and is attempting to read the element currently being evaluated. + * As a trivial example: + * + * {{{ + * lazy val a: LazyList[Int] = 1 #:: 2 #:: a.filter(_ > 2) + * }}} + * + * When attempting to evaluate the third element of `a`, it will skip the first two + * elements and read the third, but that element is already being evaluated. This is + * often caused by a subtle logic error; in this case, using `>=` in the `filter` + * would fix the error. + * + * @tparam A the type of the elements contained in this lazy list. + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html#lazylists "Scala's Collection Library overview"]] + * section on `LazyLists` for a summary. + * @define Coll `LazyList` + * @define coll lazy list + * @define orderDependent + * @define orderDependentFold + * @define appendStackSafety Note: Repeated chaining of calls to append methods (`appended`, + * `appendedAll`, `lazyAppendedAll`) without forcing any of the + * intermediate resulting lazy lists may overflow the stack when + * the final result is forced. + * @define preservesLaziness This method preserves laziness; elements are only evaluated + * individually as needed. + * @define initiallyLazy This method does not evaluate anything until an operation is performed + * on the result (e.g. calling `head` or `tail`, or checking if it is empty). + * @define evaluatesAllElements This method evaluates all elements of the collection. + */ +@SerialVersionUID(4L) +final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => LazyList[A] */) + extends AbstractSeq[A] + with LinearSeq[A] + with LinearSeqOps[A, LazyList, LazyList[A]] + with IterableFactoryDefaults[A, LazyList] + with Serializable { + import LazyList._ + + // kount() // LazyListTest.countAlloc + + private def this(head: A, tail: LazyList[A]) = { + this(LazyList.EmptyMarker) + _head = head + _tail = tail + } + + // used to synchronize lazy state evaluation + // after initialization (`_head ne Uninitialized`) + // - `null` if this is an empty lazy list + // - `head: A` otherwise (can be `null`, `_tail == null` is used to test emptiness) + @volatile private[this] var _head: Any /* Uninitialized | A */ = + if (lazyState eq EmptyMarker) null else Uninitialized + + // when `_head eq Uninitialized` + // - `lazySate: () => LazyList[A]` + // - MidEvaluation while evaluating lazyState + // when `_head ne Uninitialized` + // - `null` if this is an empty lazy list + // - `tail: LazyList[A]` otherwise + private[this] var _tail: AnyRef /* () => LazyList[A] | MidEvaluation.type | LazyList[A] */ = + if (lazyState eq EmptyMarker) null else lazyState + + private def rawHead: Any = _head + private def rawTail: AnyRef = _tail + + @inline private def isEvaluated: Boolean = _head.asInstanceOf[AnyRef] ne Uninitialized + + private def initState(): Unit = synchronized { + if (!isEvaluated) { + // if it's already mid-evaluation, we're stuck in an infinite + // self-referential loop (also it's empty) + if (_tail eq MidEvaluation) + throw new RuntimeException( + "LazyList evaluation depends on its own result (self-reference); see docs for more info") + + val fun = _tail.asInstanceOf[() => LazyList[A]] + _tail = MidEvaluation + val l = + // `fun` returns a LazyList that represents the state (head/tail) of `this`. We call `l.evaluated` to ensure + // `l` is initialized, to prevent races when reading `rawTail` / `rawHead` below. + // Often, lazy lists are created with `newLL(eagerCons(...))` so `l` is already initialized, but `newLL` also + // accepts non-evaluated lazy lists. + try fun().evaluated + // restore `fun` in finally so we can try again later if an exception was thrown (similar to lazy val) + finally _tail = fun + _tail = l.rawTail + _head = l.rawHead + } + } + + @tailrec private def evaluated: LazyList[A] = + if (isEvaluated) { + if (_tail == null) Empty + else this + } else { + initState() + evaluated + } + + override def iterableFactory: SeqFactory[LazyList] = LazyList + + // NOTE: `evaluated; this eq Empty` would be wrong. Deserialization of `Empty` creates a new + // instance with `null` fields, but the `evaluated` method always returns the canonical `Empty`. + @inline override def isEmpty: Boolean = evaluated eq Empty + + /** @inheritdoc + * + * $preservesLaziness + */ + override def knownSize: Int = if (knownIsEmpty) 0 else -1 + + override def head: A = + // inlined `isEmpty` to make it clear that `rawHead` below is initialized + if (evaluated eq Empty) throw new NoSuchElementException("head of empty lazy list") + else rawHead.asInstanceOf[A] + + override def tail: LazyList[A] = + // inlined `isEmpty` to make it clear that `rawTail` below is initialized + if (evaluated eq Empty) throw new UnsupportedOperationException("tail of empty lazy list") + else rawTail.asInstanceOf[LazyList[A]] + + @inline private[this] def knownIsEmpty: Boolean = isEvaluated && isEmpty + @inline private def knownNonEmpty: Boolean = isEvaluated && !isEmpty + + /** Evaluates all undefined elements of the lazy list. + * + * This method detects cycles in lazy lists, and terminates after all + * elements of the cycle are evaluated. For example: + * + * {{{ + * val ring: LazyList[Int] = 1 #:: 2 #:: 3 #:: ring + * ring.force + * ring.toString + * + * // prints + * // + * // LazyList(1, 2, 3, ...) + * }}} + * + * This method will *not* terminate for non-cyclic infinite-sized collections. + * + * @return this + */ + def force: this.type = { + // Use standard 2x 1x iterator trick for cycle detection ("those" is slow one) + var these, those: LazyList[A] = this + if (!these.isEmpty) { + these = these.tail + } + while (those ne these) { + if (these.isEmpty) return this + these = these.tail + if (these.isEmpty) return this + these = these.tail + if (these eq those) return this + those = those.tail + } + this + } + + /** @inheritdoc + * + * The iterator returned by this method preserves laziness; elements are + * only evaluated individually as needed. + */ + override def iterator: Iterator[A] = + if (knownIsEmpty) Iterator.empty + else new LazyIterator(this) + + /** Apply the given function `f` to each element of this linear sequence + * (while respecting the order of the elements). + * + * @param f The treatment to apply to each element. + * @note Overridden here as final to trigger tail-call optimization, which + * replaces 'this' with 'tail' at each iteration. This is absolutely + * necessary for allowing the GC to collect the underlying LazyList as elements + * are consumed. + * @note This function will force the realization of the entire LazyList + * unless the `f` throws an exception. + */ + @tailrec + override def foreach[U](f: A => U): Unit = { + if (!isEmpty) { + f(head) + tail.foreach(f) + } + } + + /** LazyList specialization of foldLeft which allows GC to collect along the + * way. + * + * @tparam B The type of value being accumulated. + * @param z The initial value seeded into the function `op`. + * @param op The operation to perform on successive elements of the `LazyList`. + * @return The accumulated value from successive applications of `op`. + */ + @tailrec + override def foldLeft[B](z: B)(op: (B, A) => B): B = + if (isEmpty) z + else tail.foldLeft(op(z, head))(op) + + // LazyList.Empty doesn't use the SerializationProxy + protected[this] def writeReplace(): AnyRef = + if (knownNonEmpty) new SerializationProxy[A](this) else this + + override protected[this] def className = "LazyList" + + /** The lazy list resulting from the concatenation of this lazy list with the argument lazy list. + * + * $preservesLaziness + * + * $appendStackSafety + * + * @param suffix The collection that gets appended to this lazy list + * @return The lazy list containing elements of this lazy list and the iterable object. + */ + def lazyAppendedAll[B >: A](suffix: => collection.IterableOnce[B]): LazyList[B] = + newLL { + if (isEmpty) suffix match { + case lazyList: LazyList[B] => lazyList // don't recompute the LazyList + case coll if coll.knownSize == 0 => Empty + case coll => eagerHeadFromIterator(coll.iterator) + } + else eagerCons(head, tail lazyAppendedAll suffix) + } + + /** @inheritdoc + * + * $preservesLaziness + * + * $appendStackSafety + */ + override def appendedAll[B >: A](suffix: IterableOnce[B]): LazyList[B] = + if (knownIsEmpty) LazyList.from(suffix) + else lazyAppendedAll(suffix) + + /** @inheritdoc + * + * $preservesLaziness + * + * $appendStackSafety + */ + override def appended[B >: A](elem: B): LazyList[B] = + if (knownIsEmpty) eagerCons(elem, Empty) + else lazyAppendedAll(Iterator.single(elem)) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def scanLeft[B](z: B)(op: (B, A) => B): LazyList[B] = + if (knownIsEmpty) eagerCons(z, Empty) + else scanLeftImpl(z)(op) + + private def scanLeftImpl[B](z: B)(op: (B, A) => B): LazyList[B] = + eagerCons( + z, + newLL { + if (isEmpty) Empty + else tail.scanLeftImpl(op(z, head))(op) + } + ) + + /** LazyList specialization of reduceLeft which allows GC to collect + * along the way. + * + * @tparam B The type of value being accumulated. + * @param f The operation to perform on successive elements of the `LazyList`. + * @return The accumulated value from successive applications of `f`. + */ + override def reduceLeft[B >: A](f: (B, A) => B): B = { + if (isEmpty) throw new UnsupportedOperationException("empty.reduceLeft") + else { + var reducedRes: B = head + var left: LazyList[A] = tail + while (!left.isEmpty) { + reducedRes = f(reducedRes, left.head) + left = left.tail + } + reducedRes + } + } + + /** @inheritdoc + * + * $preservesLaziness + */ + override def partition(p: A => Boolean): (LazyList[A], LazyList[A]) = (filter(p), filterNot(p)) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def partitionMap[A1, A2](f: A => Either[A1, A2]): (LazyList[A1], LazyList[A2]) = { + val (left, right) = map(f).partition(_.isLeft) + (left.map(_.asInstanceOf[Left[A1, _]].value), right.map(_.asInstanceOf[Right[_, A2]].value)) + } + + /** @inheritdoc + * + * $preservesLaziness + */ + override def filter(pred: A => Boolean): LazyList[A] = + if (knownIsEmpty) Empty + else filterImpl(this, pred, isFlipped = false) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def filterNot(pred: A => Boolean): LazyList[A] = + if (knownIsEmpty) Empty + else filterImpl(this, pred, isFlipped = true) + + /** A `collection.WithFilter` which allows GC of the head of lazy list during processing. + * + * This method is not particularly useful for a lazy list, as [[filter]] already preserves + * laziness. + * + * The `collection.WithFilter` returned by this method preserves laziness; elements are + * only evaluated individually as needed. + */ + override def withFilter(p: A => Boolean): collection.WithFilter[A, LazyList] = + new LazyList.WithFilter(coll, p) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def prepended[B >: A](elem: B): LazyList[B] = eagerCons(elem, this) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def prependedAll[B >: A](prefix: collection.IterableOnce[B]): LazyList[B] = + if (knownIsEmpty) LazyList.from(prefix) + else if (prefix.knownSize == 0) this + else newLL(eagerHeadPrependIterator(prefix.iterator)(this)) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def map[B](f: A => B): LazyList[B] = + if (knownIsEmpty) Empty + else mapImpl(f) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def tapEach[U](f: A => U): LazyList[A] = map { a => f(a); a } + + private def mapImpl[B](f: A => B): LazyList[B] = + newLL { + if (isEmpty) Empty + else eagerCons(f(head), tail.mapImpl(f)) + } + + /** @inheritdoc + * + * $preservesLaziness + */ + override def collect[B](pf: PartialFunction[A, B]): LazyList[B] = + if (knownIsEmpty) Empty + else collectImpl(this, pf) + + /** @inheritdoc + * + * This method does not evaluate any elements further than + * the first element for which the partial function is defined. + */ + @tailrec + override def collectFirst[B](pf: PartialFunction[A, B]): Option[B] = + if (isEmpty) None + else { + val res = pf.applyOrElse(head, anyToMarker.asInstanceOf[A => B]) + if (res.asInstanceOf[AnyRef] eq Statics.pfMarker) tail.collectFirst(pf) + else Some(res) + } + + /** @inheritdoc + * + * This method does not evaluate any elements further than + * the first element matching the predicate. + */ + @tailrec + override def find(p: A => Boolean): Option[A] = + if (isEmpty) None + else { + val elem = head + if (p(elem)) Some(elem) + else tail.find(p) + } + + /** @inheritdoc + * + * $preservesLaziness + */ + // optimisations are not for speed, but for functionality + // see tickets #153, #498, #2147, and corresponding tests in run/ (as well as run/stream_flatmap_odds.scala) + override def flatMap[B](f: A => IterableOnce[B]): LazyList[B] = + if (knownIsEmpty) Empty + else flatMapImpl(this, f) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def flatten[B](implicit asIterable: A => IterableOnce[B]): LazyList[B] = flatMap(asIterable) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def zip[B](that: collection.IterableOnce[B]): LazyList[(A, B)] = + if (knownIsEmpty || that.knownSize == 0) Empty + else newLL(eagerHeadZipImpl(that.iterator)) + + private def eagerHeadZipImpl[B](it: Iterator[B]): LazyList[(A, B)] = + if (isEmpty || !it.hasNext) Empty + else eagerCons((head, it.next()), newLL { tail eagerHeadZipImpl it }) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def zipWithIndex: LazyList[(A, Int)] = this zip LazyList.from(0) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def zipAll[A1 >: A, B](that: collection.Iterable[B], thisElem: A1, thatElem: B): LazyList[(A1, B)] = { + if (knownIsEmpty) { + if (that.knownSize == 0) Empty + else LazyList.continually(thisElem) zip that + } else { + if (that.knownSize == 0) zip(LazyList.continually(thatElem)) + else newLL(eagerHeadZipAllImpl(that.iterator, thisElem, thatElem)) + } + } + + private def eagerHeadZipAllImpl[A1 >: A, B](it: Iterator[B], thisElem: A1, thatElem: B): LazyList[(A1, B)] = { + if (it.hasNext) { + if (isEmpty) eagerCons((thisElem, it.next()), newLL { LazyList.continually(thisElem) eagerHeadZipImpl it }) + else eagerCons((head, it.next()), newLL { tail.eagerHeadZipAllImpl(it, thisElem, thatElem) }) + } else { + if (isEmpty) Empty + else eagerCons((head, thatElem), tail zip LazyList.continually(thatElem)) + } + } + + /** @inheritdoc + * + * This method is not particularly useful for a lazy list, as [[zip]] already preserves + * laziness. + * + * The `collection.LazyZip2` returned by this method preserves laziness; elements are + * only evaluated individually as needed. + */ + // just in case it can be meaningfully overridden at some point + override def lazyZip[B](that: collection.Iterable[B]): LazyZip2[A, B, this.type] = + super.lazyZip(that) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def unzip[A1, A2](implicit asPair: A => (A1, A2)): (LazyList[A1], LazyList[A2]) = + (map(asPair(_)._1), map(asPair(_)._2)) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def unzip3[A1, A2, A3](implicit asTriple: A => (A1, A2, A3)): (LazyList[A1], LazyList[A2], LazyList[A3]) = + (map(asTriple(_)._1), map(asTriple(_)._2), map(asTriple(_)._3)) + + /** @inheritdoc + * + * $initiallyLazy + * Additionally, it preserves laziness for all except the first `n` elements. + */ + override def drop(n: Int): LazyList[A] = + if (n <= 0) this + else if (knownIsEmpty) Empty + else dropImpl(this, n) + + /** @inheritdoc + * + * $initiallyLazy + * Additionally, it preserves laziness for all elements after the predicate returns `false`. + */ + override def dropWhile(p: A => Boolean): LazyList[A] = + if (knownIsEmpty) Empty + else dropWhileImpl(this, p) + + /** @inheritdoc + * + * $initiallyLazy + */ + override def dropRight(n: Int): LazyList[A] = { + if (n <= 0) this + else if (knownIsEmpty) Empty + else newLL { + var scout = this + var remaining = n + // advance scout n elements ahead (or until empty) + while (remaining > 0 && !scout.isEmpty) { + remaining -= 1 + scout = scout.tail + } + eagerHeadDropRightImpl(scout) + } + } + + private def eagerHeadDropRightImpl(scout: LazyList[_]): LazyList[A] = + if (scout.isEmpty) Empty + else eagerCons(head, newLL(tail.eagerHeadDropRightImpl(scout.tail))) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def take(n: Int): LazyList[A] = + if (knownIsEmpty) Empty + else takeImpl(n) + + private def takeImpl(n: Int): LazyList[A] = { + if (n <= 0) Empty + else newLL { + if (isEmpty) Empty + else eagerCons(head, tail.takeImpl(n - 1)) + } + } + + /** @inheritdoc + * + * $preservesLaziness + */ + override def takeWhile(p: A => Boolean): LazyList[A] = + if (knownIsEmpty) Empty + else takeWhileImpl(p) + + private def takeWhileImpl(p: A => Boolean): LazyList[A] = + newLL { + if (isEmpty || !p(head)) Empty + else eagerCons(head, tail.takeWhileImpl(p)) + } + + /** @inheritdoc + * + * $initiallyLazy + */ + override def takeRight(n: Int): LazyList[A] = + if (n <= 0 || knownIsEmpty) Empty + else takeRightImpl(this, n) + + /** @inheritdoc + * + * $initiallyLazy + * Additionally, it preserves laziness for all but the first `from` elements. + */ + override def slice(from: Int, until: Int): LazyList[A] = take(until).drop(from) + + /** @inheritdoc + * + * $evaluatesAllElements + */ + override def reverse: LazyList[A] = reverseOnto(Empty) + + // need contravariant type B to make the compiler happy - still returns LazyList[A] + @tailrec + private def reverseOnto[B >: A](tl: LazyList[B]): LazyList[B] = + if (isEmpty) tl + else tail.reverseOnto(newLL(eagerCons(head, tl))) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def diff[B >: A](that: collection.Seq[B]): LazyList[A] = + if (knownIsEmpty) Empty + else super.diff(that) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def intersect[B >: A](that: collection.Seq[B]): LazyList[A] = + if (knownIsEmpty) Empty + else super.intersect(that) + + @tailrec + private def lengthGt(len: Int): Boolean = + if (len < 0) true + else if (isEmpty) false + else tail.lengthGt(len - 1) + + /** @inheritdoc + * + * The iterator returned by this method mostly preserves laziness; + * a single element ahead of the iterator is evaluated. + */ + override def grouped(size: Int): Iterator[LazyList[A]] = { + require(size > 0, "size must be positive, but was " + size) + slidingImpl(size = size, step = size) + } + + /** @inheritdoc + * + * The iterator returned by this method mostly preserves laziness; + * `size - step max 1` elements ahead of the iterator are evaluated. + */ + override def sliding(size: Int, step: Int): Iterator[LazyList[A]] = { + require(size > 0 && step > 0, s"size=$size and step=$step, but both must be positive") + slidingImpl(size = size, step = step) + } + + @inline private def slidingImpl(size: Int, step: Int): Iterator[LazyList[A]] = + if (knownIsEmpty) Iterator.empty + else new SlidingIterator[A](this, size = size, step = step) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def padTo[B >: A](len: Int, elem: B): LazyList[B] = + if (len <= 0) this + else newLL { + if (isEmpty) LazyList.fill(len)(elem) + else eagerCons(head, tail.padTo(len - 1, elem)) + } + + /** @inheritdoc + * + * $preservesLaziness + */ + override def patch[B >: A](from: Int, other: IterableOnce[B], replaced: Int): LazyList[B] = + if (knownIsEmpty) LazyList from other + else patchImpl(from, other, replaced) + + private def patchImpl[B >: A](from: Int, other: IterableOnce[B], replaced: Int): LazyList[B] = + newLL { + if (from <= 0) eagerHeadPrependIterator(other.iterator)(dropImpl(this, replaced)) + else if (isEmpty) eagerHeadFromIterator(other.iterator) + else eagerCons(head, tail.patchImpl(from - 1, other, replaced)) + } + + /** @inheritdoc + * + * $evaluatesAllElements + */ + // overridden just in case a lazy implementation is developed at some point + override def transpose[B](implicit asIterable: A => collection.Iterable[B]): LazyList[LazyList[B]] = super.transpose + + /** @inheritdoc + * + * $preservesLaziness + */ + override def updated[B >: A](index: Int, elem: B): LazyList[B] = + if (index < 0) throw new IndexOutOfBoundsException(s"$index") + else updatedImpl(index, elem, index) + + private def updatedImpl[B >: A](index: Int, elem: B, startIndex: Int): LazyList[B] = + newLL { + if (index <= 0) eagerCons(elem, tail) + else if (tail.isEmpty) throw new IndexOutOfBoundsException(startIndex.toString) + else eagerCons(head, tail.updatedImpl(index - 1, elem, startIndex)) + } + + /** Appends all elements of this $coll to a string builder using start, end, and separator strings. + * The written text begins with the string `start` and ends with the string `end`. + * Inside, the string representations (w.r.t. the method `toString`) + * of all elements of this $coll are separated by the string `sep`. + * + * An undefined state is represented with `"<not computed>"` and cycles are represented with `"<cycle>"`. + * + * $evaluatesAllElements + * + * @param sb the string builder to which elements are appended. + * @param start the starting string. + * @param sep the separator string. + * @param end the ending string. + * @return the string builder `b` to which elements were appended. + */ + override def addString(sb: StringBuilder, start: String, sep: String, end: String): sb.type = { + force + addStringNoForce(sb.underlying, start, sep, end) + sb + } + + private[this] def addStringNoForce(b: JStringBuilder, start: String, sep: String, end: String): b.type = { + b.append(start) + if (!isEvaluated) b.append("") + else if (!isEmpty) { + b.append(head) + var cursor = this + // explicit param to prevent an ObjectRef for cursor + @inline def appendHead(c: LazyList[A]): Unit = b.append(sep).append(c.head) + var scout = tail + if (cursor ne scout) { + cursor = scout + if (scout.knownNonEmpty) { + scout = scout.tail + // Use 2x 1x iterator trick for cycle detection; slow iterator can add strings + while ((cursor ne scout) && scout.knownNonEmpty) { + appendHead(cursor) + cursor = cursor.tail + scout = scout.tail + if (scout.knownNonEmpty) scout = scout.tail + } + } + } + if (!scout.knownNonEmpty) { // Not a cycle, scout hit an end (empty or non-evaluated) + while (cursor ne scout) { + appendHead(cursor) + cursor = cursor.tail + } + // if cursor (eq scout) has state defined, it is empty; else unknown state + if (!cursor.isEvaluated) b.append(sep).append("") + } else { + // Cycle: the scout is `knownNonEmpty` and `eq cursor`. + // if the cycle starts at `this`, its elements were already added + if (cursor ne this) { + // If we have a prefix of length P followed by a cycle of length C, + // the scout will be at position (P%C) in the cycle when the cursor + // enters it at P. They'll then collide when the scout advances another + // C - (P%C) ahead of the cursor. + // If we run the scout P farther, then it will be at the start of + // the cycle: (C - (P%C) + (P%C)) == C == 0. So if another runner + // starts at the beginning of the prefix, they'll collide exactly at + // the start of the loop. + var runner = this + while (runner ne scout) { + runner = runner.tail + scout = scout.tail + } + while({ + val ct = cursor.tail + if (ct ne scout) { + // In `lazy val xs: LazyList[Int] = 1 #:: 2 #:: xs`, method `#::` creates a LazyList instance which ends up as the 3rd element. + // That 3rd element initially has unknown head/tail. Once it completes, the tail is assigned to be `xs.tail`. + // So in memory the structure is `LLx(1, LLy(2, LLz(1, )))`. + // In `toString` we skip the last element to maintain the illusion. + appendHead(cursor) + } + cursor = ct + cursor ne scout + }) () + } + b.append(sep).append("") + } + } + b.append(end) + b + } + + /** $preservesLaziness + * + * @return a string representation of this collection. An undefined state is + * represented with `"<not computed>"` and cycles are represented with `"<cycle>"` + * + * Examples: + * + * - `"LazyList(4, <not computed>)"`, a non-empty lazy list ; + * - `"LazyList(1, 2, 3, <not computed>)"`, a lazy list with at least three elements ; + * - `"LazyList(1, 2, 3, <cycle>)"`, an infinite lazy list that contains + * a cycle at the fourth element. + */ + override def toString(): String = addStringNoForce(new JStringBuilder(className), "(", ", ", ")").toString + + /** @inheritdoc + * + * $preservesLaziness + */ + @deprecated("Check .knownSize instead of .hasDefiniteSize for more actionable information (see scaladoc for details)", "2.13.0") + override def hasDefiniteSize: Boolean = { + if (!isEvaluated) false + else if (isEmpty) true + else { + // Two-iterator trick (2x & 1x speed) for cycle detection. + var those = this + var these = tail + while (those ne these) { + if (!these.isEvaluated) return false + else if (these.isEmpty) return true + these = these.tail + if (!these.isEvaluated) return false + else if (these.isEmpty) return true + these = these.tail + if (those eq these) return false + those = those.tail + } + false // Cycle detected + } + } +} + +/** + * $factoryInfo + * @define coll lazy list + * @define Coll `LazyList` + */ +@SerialVersionUID(4L) +object LazyList extends SeqFactory[LazyList] { + + // LazyListTest.countAlloc + // var k = 0 + // def kount(): Unit = k += 1 + + private object Uninitialized extends Serializable + private object MidEvaluation + private object EmptyMarker + + private val Empty: LazyList[Nothing] = new LazyList(EmptyMarker) + + /** Creates a new LazyList. */ + @inline private def newLL[A](state: => LazyList[A]): LazyList[A] = new LazyList[A](() => state) + + /** Creates a new LazyList with evaluated `head` and `tail`. */ + @inline private def eagerCons[A](hd: A, tl: LazyList[A]): LazyList[A] = new LazyList[A](hd, tl) + + private val anyToMarker: Any => Any = _ => Statics.pfMarker + + /* All of the following `Impl` methods are carefully written so as not to + * leak the beginning of the `LazyList`. They copy the initial `LazyList` (`ll`) into + * `var rest`, which gets closed over as a `scala.runtime.ObjectRef`, thus not permanently + * leaking the head of the `LazyList`. Additionally, the methods are written so that, should + * an exception be thrown by the evaluation of the `LazyList` or any supplied function, they + * can continue their execution where they left off. + */ + + private def filterImpl[A](ll: LazyList[A], p: A => Boolean, isFlipped: Boolean): LazyList[A] = { + // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD + var restRef = ll // val restRef = new ObjectRef(ll) + newLL { + var elem: A = null.asInstanceOf[A] + var found = false + var rest = restRef // var rest = restRef.elem + while (!found && !rest.isEmpty) { + elem = rest.head + found = p(elem) != isFlipped + rest = rest.tail + restRef = rest // restRef.elem = rest + } + if (found) eagerCons(elem, filterImpl(rest, p, isFlipped)) else Empty + } + } + + private def collectImpl[A, B](ll: LazyList[A], pf: PartialFunction[A, B]): LazyList[B] = { + // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD + var restRef = ll // val restRef = new ObjectRef(ll) + newLL { + val marker = Statics.pfMarker + val toMarker = anyToMarker.asInstanceOf[A => B] // safe because Function1 is erased + + var res: B = marker.asInstanceOf[B] // safe because B is unbounded + var rest = restRef // var rest = restRef.elem + while((res.asInstanceOf[AnyRef] eq marker) && !rest.isEmpty) { + res = pf.applyOrElse(rest.head, toMarker) + rest = rest.tail + restRef = rest // restRef.elem = rest + } + if (res.asInstanceOf[AnyRef] eq marker) Empty + else eagerCons(res, collectImpl(rest, pf)) + } + } + + private def flatMapImpl[A, B](ll: LazyList[A], f: A => IterableOnce[B]): LazyList[B] = { + // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD + var restRef = ll // val restRef = new ObjectRef(ll) + newLL { + var it: Iterator[B] = null + var itHasNext = false + var rest = restRef // var rest = restRef.elem + while (!itHasNext && !rest.isEmpty) { + it = f(rest.head).iterator + itHasNext = it.hasNext + if (!itHasNext) { // wait to advance `rest` because `it.next()` can throw + rest = rest.tail + restRef = rest // restRef.elem = rest + } + } + if (itHasNext) { + val head = it.next() + rest = rest.tail + restRef = rest // restRef.elem = rest + eagerCons(head, newLL(eagerHeadPrependIterator(it)(flatMapImpl(rest, f)))) + } else Empty + } + } + + private def dropImpl[A](ll: LazyList[A], n: Int): LazyList[A] = { + // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD + var restRef = ll // val restRef = new ObjectRef(ll) + var iRef = n // val iRef = new IntRef(n) + newLL { + var rest = restRef // var rest = restRef.elem + var i = iRef // var i = iRef.elem + while (i > 0 && !rest.isEmpty) { + rest = rest.tail + restRef = rest // restRef.elem = rest + i -= 1 + iRef = i // iRef.elem = i + } + rest + } + } + + private def dropWhileImpl[A](ll: LazyList[A], p: A => Boolean): LazyList[A] = { + // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD + var restRef = ll // val restRef = new ObjectRef(ll) + newLL { + var rest = restRef // var rest = restRef.elem + while (!rest.isEmpty && p(rest.head)) { + rest = rest.tail + restRef = rest // restRef.elem = rest + } + rest + } + } + + private def takeRightImpl[A](ll: LazyList[A], n: Int): LazyList[A] = { + // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD + var restRef = ll // val restRef = new ObjectRef(ll) + var scoutRef = ll // val scoutRef = new ObjectRef(ll) + var remainingRef = n // val remainingRef = new IntRef(n) + newLL { + var scout = scoutRef // var scout = scoutRef.elem + var remaining = remainingRef // var remaining = remainingRef.elem + // advance `scout` `n` elements ahead (or until empty) + while (remaining > 0 && !scout.isEmpty) { + scout = scout.tail + scoutRef = scout // scoutRef.elem = scout + remaining -= 1 + remainingRef = remaining // remainingRef.elem = remaining + } + var rest = restRef // var rest = restRef.elem + // advance `rest` and `scout` in tandem until `scout` reaches the end + while(!scout.isEmpty) { + scout = scout.tail + scoutRef = scout // scoutRef.elem = scout + rest = rest.tail // can't throw an exception as `scout` has already evaluated its tail + restRef = rest // restRef.elem = rest + } + // `rest` is the last `n` elements (or all of them) + rest + } + } + + /** An alternative way of building and matching lazy lists using LazyList.cons(hd, tl). + */ + object cons { + /** A lazy list consisting of a given first element and remaining elements + * @param hd The first element of the result lazy list + * @param tl The remaining elements of the result lazy list + */ + def apply[A](hd: => A, tl: => LazyList[A]): LazyList[A] = newLL(eagerCons(hd, newLL(tl))) + + /** Maps a lazy list to its head and tail */ + def unapply[A](xs: LazyList[A]): Option[(A, LazyList[A])] = #::.unapply(xs) + } + + implicit def toDeferrer[A](l: => LazyList[A]): Deferrer[A] = new Deferrer[A](() => l) + + final class Deferrer[A] private[LazyList] (private val l: () => LazyList[A]) extends AnyVal { + /** Construct a LazyList consisting of a given first element followed by elements + * from another LazyList. + */ + def #:: [B >: A](elem: => B): LazyList[B] = newLL(eagerCons(elem, newLL(l()))) + /** Construct a LazyList consisting of the concatenation of the given LazyList and + * another LazyList. + */ + def #:::[B >: A](prefix: LazyList[B]): LazyList[B] = prefix lazyAppendedAll l() + } + + object #:: { + def unapply[A](s: LazyList[A]): Option[(A, LazyList[A])] = + if (!s.isEmpty) Some((s.head, s.tail)) else None + } + + def from[A](coll: collection.IterableOnce[A]): LazyList[A] = coll match { + case lazyList: LazyList[A] => lazyList + case _ if coll.knownSize == 0 => empty[A] + case _ => newLL(eagerHeadFromIterator(coll.iterator)) + } + + def empty[A]: LazyList[A] = Empty + + /** Creates a LazyList with the elements of an iterator followed by a LazyList suffix. + * Eagerly evaluates the first element. + */ + private def eagerHeadPrependIterator[A](it: Iterator[A])(suffix: => LazyList[A]): LazyList[A] = + if (it.hasNext) eagerCons(it.next(), newLL(eagerHeadPrependIterator(it)(suffix))) + else suffix + + /** Creates a LazyList from an Iterator. Eagerly evaluates the first element. */ + private def eagerHeadFromIterator[A](it: Iterator[A]): LazyList[A] = + if (it.hasNext) eagerCons(it.next(), newLL(eagerHeadFromIterator(it))) + else Empty + + override def concat[A](xss: collection.Iterable[A]*): LazyList[A] = + if (xss.knownSize == 0) empty + else newLL(eagerHeadConcatIterators(xss.iterator)) + + private def eagerHeadConcatIterators[A](it: Iterator[collection.Iterable[A]]): LazyList[A] = + if (!it.hasNext) Empty + else eagerHeadPrependIterator(it.next().iterator)(eagerHeadConcatIterators(it)) + + /** An infinite LazyList that repeatedly applies a given function to a start value. + * + * @param start the start value of the LazyList + * @param f the function that's repeatedly applied + * @return the LazyList returning the infinite sequence of values `start, f(start), f(f(start)), ...` + */ + def iterate[A](start: => A)(f: A => A): LazyList[A] = + newLL { + val head = start + eagerCons(head, iterate(f(head))(f)) + } + + /** + * Create an infinite LazyList starting at `start` and incrementing by + * step `step`. + * + * @param start the start value of the LazyList + * @param step the increment value of the LazyList + * @return the LazyList starting at value `start`. + */ + def from(start: Int, step: Int): LazyList[Int] = + newLL(eagerCons(start, from(start + step, step))) + + /** + * Create an infinite LazyList starting at `start` and incrementing by `1`. + * + * @param start the start value of the LazyList + * @return the LazyList starting at value `start`. + */ + def from(start: Int): LazyList[Int] = from(start, 1) + + /** + * Create an infinite LazyList containing the given element expression (which + * is computed for each occurrence). + * + * @param elem the element composing the resulting LazyList + * @return the LazyList containing an infinite number of elem + */ + def continually[A](elem: => A): LazyList[A] = newLL(eagerCons(elem, continually(elem))) + + override def fill[A](n: Int)(elem: => A): LazyList[A] = + if (n > 0) newLL(eagerCons(elem, LazyList.fill(n - 1)(elem))) else empty + + override def tabulate[A](n: Int)(f: Int => A): LazyList[A] = { + def at(index: Int): LazyList[A] = + if (index < n) newLL(eagerCons(f(index), at(index + 1))) else empty + + at(0) + } + + // significantly simpler than the iterator returned by Iterator.unfold + override def unfold[A, S](init: S)(f: S => Option[(A, S)]): LazyList[A] = + newLL { + f(init) match { + case Some((elem, state)) => eagerCons(elem, unfold(state)(f)) + case None => Empty + } + } + + /** The builder returned by this method only evaluates elements + * of collections added to it as needed. + * + * @tparam A the type of the ${coll}’s elements + * @return A builder for $Coll objects. + */ + def newBuilder[A]: Builder[A, LazyList[A]] = new LazyBuilder[A] + + private class LazyIterator[+A](private[this] var lazyList: LazyList[A]) extends AbstractIterator[A] { + override def hasNext: Boolean = !lazyList.isEmpty + + override def next(): A = + if (lazyList.isEmpty) Iterator.empty.next() + else { + val res = lazyList.head + lazyList = lazyList.tail + res + } + } + + private class SlidingIterator[A](private[this] var lazyList: LazyList[A], size: Int, step: Int) + extends AbstractIterator[LazyList[A]] { + private val minLen = size - step max 0 + private var first = true + + def hasNext: Boolean = + if (first) !lazyList.isEmpty + else lazyList.lengthGt(minLen) + + def next(): LazyList[A] = { + if (!hasNext) Iterator.empty.next() + else { + first = false + val list = lazyList + lazyList = list.drop(step) + list.take(size) + } + } + } + + private final class WithFilter[A] private[LazyList](lazyList: LazyList[A], p: A => Boolean) + extends collection.WithFilter[A, LazyList] { + private[this] val filtered = lazyList.filter(p) + def map[B](f: A => B): LazyList[B] = filtered.map(f) + def flatMap[B](f: A => IterableOnce[B]): LazyList[B] = filtered.flatMap(f) + def foreach[U](f: A => U): Unit = filtered.foreach(f) + def withFilter(q: A => Boolean): collection.WithFilter[A, LazyList] = new WithFilter(filtered, q) + } + + private final class LazyBuilder[A] extends ReusableBuilder[A, LazyList[A]] { + import LazyBuilder._ + + private[this] var next: DeferredState[A] = _ + private[this] var list: LazyList[A] = _ + + clear() + + override def clear(): Unit = { + val deferred = new DeferredState[A] + list = newLL(deferred.eval()) + next = deferred + } + + override def result(): LazyList[A] = { + next init Empty + list + } + + override def addOne(elem: A): this.type = { + val deferred = new DeferredState[A] + next init eagerCons(elem, newLL(deferred.eval())) + next = deferred + this + } + + // lazy implementation which doesn't evaluate the collection being added + override def addAll(xs: IterableOnce[A]): this.type = { + if (xs.knownSize != 0) { + val deferred = new DeferredState[A] + next init eagerHeadPrependIterator(xs.iterator)(deferred.eval()) + next = deferred + } + this + } + } + + private object LazyBuilder { + final class DeferredState[A] { + private[this] var _tail: () => LazyList[A] = _ + + def eval(): LazyList[A] = { + val state = _tail + if (state == null) throw new IllegalStateException("uninitialized") + state() + } + + // racy + def init(state: => LazyList[A]): Unit = { + if (_tail != null) throw new IllegalStateException("already initialized") + _tail = () => state + } + } + } + + /** This serialization proxy is used for LazyLists which start with a sequence of evaluated cons cells. + * The forced sequence is serialized in a compact, sequential format, followed by the unevaluated tail, which uses + * standard Java serialization to store the complete structure of unevaluated thunks. This allows the serialization + * of long evaluated lazy lists without exhausting the stack through recursive serialization of cons cells. + */ + @SerialVersionUID(4L) + final class SerializationProxy[A](@transient protected var coll: LazyList[A]) extends Serializable { + + private[this] def writeObject(out: ObjectOutputStream): Unit = { + out.defaultWriteObject() + var these = coll + while (these.knownNonEmpty) { + out.writeObject(these.head) + these = these.tail + } + out.writeObject(SerializeEnd) + out.writeObject(these) + } + + private[this] def readObject(in: ObjectInputStream): Unit = { + in.defaultReadObject() + val init = new mutable.ListBuffer[A] + var initRead = false + while (!initRead) in.readObject match { + case SerializeEnd => initRead = true + case a => init += a.asInstanceOf[A] + } + val tail = in.readObject().asInstanceOf[LazyList[A]] + // scala/scala#10118: caution that no code path can evaluate `tail.evaluated` + // before the resulting LazyList is returned + val it = init.toList.iterator + coll = newLL(eagerHeadPrependIterator(it)(tail)) + } + + private[this] def readResolve(): Any = coll + } +} diff --git a/library/src/scala/collection/immutable/List.scala b/library/src/scala/collection/immutable/List.scala new file mode 100644 index 000000000000..f7b828bb97b5 --- /dev/null +++ b/library/src/scala/collection/immutable/List.scala @@ -0,0 +1,697 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import scala.annotation.unchecked.uncheckedVariance +import scala.annotation.tailrec +import mutable.{Builder, ListBuffer} +import scala.collection.generic.{CommonErrors, DefaultSerializable} +import scala.runtime.Statics.releaseFence + +/** A class for immutable linked lists representing ordered collections + * of elements of type `A`. + * + * This class comes with two implementing case classes `scala.Nil` + * and `scala.::` that implement the abstract members `isEmpty`, + * `head` and `tail`. + * + * This class is optimal for last-in-first-out (LIFO), stack-like access patterns. If you need another access + * pattern, for example, random access or FIFO, consider using a collection more suited to this than `List`. + * + * ==Performance== + * '''Time:''' `List` has `O(1)` prepend and head/tail access. Most other operations are `O(n)` on the number of elements in the list. + * This includes the index-based lookup of elements, `length`, `append` and `reverse`. + * + * '''Space:''' `List` implements '''structural sharing''' of the tail list. This means that many operations are either + * zero- or constant-memory cost. + * {{{ + * val mainList = List(3, 2, 1) + * val with4 = 4 :: mainList // re-uses mainList, costs one :: instance + * val with42 = 42 :: mainList // also re-uses mainList, cost one :: instance + * val shorter = mainList.tail // costs nothing as it uses the same 2::1::Nil instances as mainList + * }}} + * + * @example {{{ + * // Make a list via the companion object factory + * val days = List("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday") + * + * // Make a list element-by-element + * val when = "AM" :: "PM" :: Nil + * + * // Pattern match + * days match { + * case firstDay :: otherDays => + * println("The first day of the week is: " + firstDay) + * case Nil => + * println("There don't seem to be any week days.") + * } + * }}} + * + * @note The functional list is characterized by persistence and structural sharing, thus offering considerable + * performance and space consumption benefits in some scenarios if used correctly. + * However, note that objects having multiple references into the same functional list (that is, + * objects that rely on structural sharing), will be serialized and deserialized with multiple lists, one for + * each reference to it. I.e. structural sharing is lost after serialization/deserialization. + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html#lists "Scala's Collection Library overview"]] + * section on `Lists` for more information. + * + * @define coll list + * @define Coll `List` + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +@SerialVersionUID(3L) +sealed abstract class List[+A] + extends AbstractSeq[A] + with LinearSeq[A] + with LinearSeqOps[A, List, List[A]] + with StrictOptimizedLinearSeqOps[A, List, List[A]] + with StrictOptimizedSeqOps[A, List, List[A]] + with IterableFactoryDefaults[A, List] + with DefaultSerializable { + + override def iterableFactory: SeqFactory[List] = List + + /** Adds an element at the beginning of this list. + * @param elem the element to prepend. + * @return a list which contains `x` as first element and + * which continues with this list. + * Example: + * {{{1 :: List(2, 3) = List(2, 3).::(1) = List(1, 2, 3)}}} + */ + def :: [B >: A](elem: B): List[B] = new ::(elem, this) + + /** Adds the elements of a given list in front of this list. + * + * Example: + * {{{List(1, 2) ::: List(3, 4) = List(3, 4).:::(List(1, 2)) = List(1, 2, 3, 4)}}} + * + * @param prefix The list elements to prepend. + * @return a list resulting from the concatenation of the given + * list `prefix` and this list. + */ + def ::: [B >: A](prefix: List[B]): List[B] = + if (isEmpty) prefix + else if (prefix.isEmpty) this + else { + val result = new ::[B](prefix.head, this) + var curr = result + var that = prefix.tail + while (!that.isEmpty) { + val temp = new ::[B](that.head, this) + curr.next = temp + curr = temp + that = that.tail + } + releaseFence() + result + } + + /** Adds the elements of a given list in reverse order in front of this list. + * `xs reverse_::: ys` is equivalent to + * `xs.reverse ::: ys` but is more efficient. + * + * @param prefix the prefix to reverse and then prepend + * @return the concatenation of the reversed prefix and the current list. + */ + def reverse_:::[B >: A](prefix: List[B]): List[B] = { + var these: List[B] = this + var pres = prefix + while (!pres.isEmpty) { + these = pres.head :: these + pres = pres.tail + } + these + } + + override final def isEmpty: Boolean = this eq Nil + + override def prepended[B >: A](elem: B): List[B] = elem :: this + + override def prependedAll[B >: A](prefix: collection.IterableOnce[B]): List[B] = prefix match { + case xs: List[B] => xs ::: this + case _ if prefix.knownSize == 0 => this + case b: ListBuffer[B] if this.isEmpty => b.toList + case _ => + val iter = prefix.iterator + if (iter.hasNext) { + val result = new ::[B](iter.next(), this) + var curr = result + while (iter.hasNext) { + val temp = new ::[B](iter.next(), this) + curr.next = temp + curr = temp + } + releaseFence() + result + } else { + this + } + } + + // When calling appendAll with another list `suffix`, avoid copying `suffix` + override def appendedAll[B >: A](suffix: collection.IterableOnce[B]): List[B] = suffix match { + case xs: List[B] => this ::: xs + case _ => super.appendedAll(suffix) + } + + override def take(n: Int): List[A] = if (isEmpty || n <= 0) Nil else { + val h = new ::(head, Nil) + var t = h + var rest = tail + var i = 1 + while ({if (rest.isEmpty) return this; i < n}) { + i += 1 + val nx = new ::(rest.head, Nil) + t.next = nx + t = nx + rest = rest.tail + } + releaseFence() + h + } + + /** @inheritdoc + * + * @example {{{ + * // Given a list + * val letters = List('a','b','c','d','e') + * + * // `slice` returns all elements beginning at index `from` and afterwards, + * // up until index `until` (excluding index `until`.) + * letters.slice(1,3) // Returns List('b','c') + * }}} + */ + override def slice(from: Int, until: Int): List[A] = { + val lo = scala.math.max(from, 0) + if (until <= lo || isEmpty) Nil + else this drop lo take (until - lo) + } + + override def takeRight(n: Int): List[A] = { + @tailrec + def loop(lead: List[A], lag: List[A]): List[A] = lead match { + case Nil => lag + case _ :: tail => loop(tail, lag.tail) + } + loop(drop(n), this) + } + + // dropRight is inherited from LinearSeq + + override def splitAt(n: Int): (List[A], List[A]) = { + val b = new ListBuffer[A] + var i = 0 + var these = this + while (!these.isEmpty && i < n) { + i += 1 + b += these.head + these = these.tail + } + (b.toList, these) + } + + override def updated[B >: A](index: Int, elem: B): List[B] = { + var i = 0 + var current = this + val prefix = ListBuffer.empty[B] + while (i < index && current.nonEmpty) { + i += 1 + prefix += current.head + current = current.tail + } + if (i == index && current.nonEmpty) { + prefix.prependToList(elem :: current.tail) + } else { + throw CommonErrors.indexOutOfBounds(index = index, max = length - 1) + } + } + + final override def map[B](f: A => B): List[B] = { + if (this eq Nil) Nil else { + val h = new ::[B](f(head), Nil) + var t: ::[B] = h + var rest = tail + while (rest ne Nil) { + val nx = new ::(f(rest.head), Nil) + t.next = nx + t = nx + rest = rest.tail + } + releaseFence() + h + } + } + + final override def collect[B](pf: PartialFunction[A, B]): List[B] = { + if (this eq Nil) Nil else { + var rest = this + var h: ::[B] = null + var x: Any = null + // Special case for first element + while (h eq null) { + x = pf.applyOrElse(rest.head, List.partialNotApplied) + if (x.asInstanceOf[AnyRef] ne List.partialNotApplied) h = new ::(x.asInstanceOf[B], Nil) + rest = rest.tail + if (rest eq Nil) return if (h eq null) Nil else h + } + var t = h + // Remaining elements + while (rest ne Nil) { + x = pf.applyOrElse(rest.head, List.partialNotApplied) + if (x.asInstanceOf[AnyRef] ne List.partialNotApplied) { + val nx = new ::(x.asInstanceOf[B], Nil) + t.next = nx + t = nx + } + rest = rest.tail + } + releaseFence() + h + } + } + + final override def flatMap[B](f: A => IterableOnce[B]): List[B] = { + var rest = this + var h: ::[B] = null + var t: ::[B] = null + while (rest ne Nil) { + val it = f(rest.head).iterator + while (it.hasNext) { + val nx = new ::(it.next(), Nil) + if (t eq null) { + h = nx + } else { + t.next = nx + } + t = nx + } + rest = rest.tail + } + if (h eq null) Nil else {releaseFence(); h} + } + + @inline final override def takeWhile(p: A => Boolean): List[A] = { + val b = new ListBuffer[A] + var these = this + while (!these.isEmpty && p(these.head)) { + b += these.head + these = these.tail + } + b.toList + } + + @inline final override def span(p: A => Boolean): (List[A], List[A]) = { + val b = new ListBuffer[A] + var these = this + while (!these.isEmpty && p(these.head)) { + b += these.head + these = these.tail + } + (b.toList, these) + } + + // Overridden with an implementation identical to the inherited one (at this time) + // solely so it can be finalized and thus inlinable. + @inline final override def foreach[U](f: A => U): Unit = { + var these = this + while (!these.isEmpty) { + f(these.head) + these = these.tail + } + } + + final override def reverse: List[A] = { + var result: List[A] = Nil + var these = this + while (!these.isEmpty) { + result = these.head :: result + these = these.tail + } + result + } + + final override def foldRight[B](z: B)(op: (A, B) => B): B = { + var acc = z + var these: List[A] = reverse + while (!these.isEmpty) { + acc = op(these.head, acc) + these = these.tail + } + acc + } + + // Copy/Paste overrides to avoid interface calls inside loops. + + override final def length: Int = { + var these = this + var len = 0 + while (!these.isEmpty) { + len += 1 + these = these.tail + } + len + } + + override final def lengthCompare(len: Int): Int = { + @tailrec def loop(i: Int, xs: List[A]): Int = { + if (i == len) + if (xs.isEmpty) 0 else 1 + else if (xs.isEmpty) + -1 + else + loop(i + 1, xs.tail) + } + if (len < 0) 1 + else loop(0, coll) + } + + override final def forall(p: A => Boolean): Boolean = { + var these: List[A] = this + while (!these.isEmpty) { + if (!p(these.head)) return false + these = these.tail + } + true + } + + override final def exists(p: A => Boolean): Boolean = { + var these: List[A] = this + while (!these.isEmpty) { + if (p(these.head)) return true + these = these.tail + } + false + } + + override final def contains[A1 >: A](elem: A1): Boolean = { + var these: List[A] = this + while (!these.isEmpty) { + if (these.head == elem) return true + these = these.tail + } + false + } + + override final def find(p: A => Boolean): Option[A] = { + var these: List[A] = this + while (!these.isEmpty) { + if (p(these.head)) return Some(these.head) + these = these.tail + } + None + } + + override def last: A = { + if (isEmpty) throw new NoSuchElementException("List.last") + else { + var these = this + var scout = tail + while (!scout.isEmpty) { + these = scout + scout = scout.tail + } + these.head + } + } + + override def corresponds[B](that: collection.Seq[B])(p: (A, B) => Boolean): Boolean = that match { + case that: LinearSeq[B] => + var i = this + var j = that + while (!(i.isEmpty || j.isEmpty)) { + if (!p(i.head, j.head)) + return false + i = i.tail + j = j.tail + } + i.isEmpty && j.isEmpty + case _ => + super.corresponds(that)(p) + } + + override protected[this] def className = "List" + + /** Builds a new list by applying a function to all elements of this list. + * Like `xs map f`, but returns `xs` unchanged if function + * `f` maps all elements to themselves (as determined by `eq`). + * + * @param f the function to apply to each element. + * @tparam B the element type of the returned collection. + * @return a list resulting from applying the given function + * `f` to each element of this list and collecting the results. + */ + @`inline` final def mapConserve[B >: A <: AnyRef](f: A => B): List[B] = { + // Note to developers: there exists a duplication between this function and `reflect.internal.util.Collections#map2Conserve`. + // If any successful optimization attempts or other changes are made, please rehash them there too. + @tailrec + def loop(mappedHead: List[B], mappedLast: ::[B], unchanged: List[A], pending: List[A]): List[B] = { + if (pending.isEmpty) { + if (mappedHead eq null) unchanged + else { + mappedLast.next = (unchanged: List[B]) + mappedHead + } + } + else { + val head0 = pending.head + val head1 = f(head0) + + if (head1 eq head0.asInstanceOf[AnyRef]) + loop(mappedHead, mappedLast, unchanged, pending.tail) + else { + var xc = unchanged + var mappedHead1: List[B] = mappedHead + var mappedLast1: ::[B] = mappedLast + while (xc ne pending) { + val next = new ::[B](xc.head, Nil) + if (mappedHead1 eq null) mappedHead1 = next + if (mappedLast1 ne null) mappedLast1.next = next + mappedLast1 = next + xc = xc.tail + } + val next = new ::(head1, Nil) + if (mappedHead1 eq null) mappedHead1 = next + if (mappedLast1 ne null) mappedLast1.next = next + mappedLast1 = next + val tail0 = pending.tail + loop(mappedHead1, mappedLast1, tail0, tail0) + + } + } + } + val result = loop(null, null, this, this) + releaseFence() + result + } + + override def filter(p: A => Boolean): List[A] = filterCommon(p, isFlipped = false) + + override def filterNot(p: A => Boolean): List[A] = filterCommon(p, isFlipped = true) + + private[this] def filterCommon(p: A => Boolean, isFlipped: Boolean): List[A] = { + + // everything seen so far so far is not included + @tailrec def noneIn(l: List[A]): List[A] = { + if (l.isEmpty) + Nil + else { + val h = l.head + val t = l.tail + if (p(h) != isFlipped) + allIn(l, t) + else + noneIn(t) + } + } + + // everything from 'start' is included, if everything from this point is in we can return the origin + // start otherwise if we discover an element that is out we must create a new partial list. + @tailrec def allIn(start: List[A], remaining: List[A]): List[A] = { + if (remaining.isEmpty) + start + else { + val x = remaining.head + if (p(x) != isFlipped) + allIn(start, remaining.tail) + else + partialFill(start, remaining) + } + } + + // we have seen elements that should be included then one that should be excluded, start building + def partialFill(origStart: List[A], firstMiss: List[A]): List[A] = { + val newHead = new ::(origStart.head, Nil) + var toProcess = origStart.tail + var currentLast = newHead + + // we know that all elements are :: until at least firstMiss.tail + while (!(toProcess eq firstMiss)) { + val newElem = new ::(toProcess.head, Nil) + currentLast.next = newElem + currentLast = newElem + toProcess = toProcess.tail + } + + // at this point newHead points to a list which is a duplicate of all the 'in' elements up to the first miss. + // currentLast is the last element in that list. + + // now we are going to try and share as much of the tail as we can, only moving elements across when we have to. + var next = firstMiss.tail + var nextToCopy = next // the next element we would need to copy to our list if we cant share. + while (!next.isEmpty) { + // generally recommended is next.isNonEmpty but this incurs an extra method call. + val head: A = next.head + if (p(head) != isFlipped) { + next = next.tail + } else { + // its not a match - do we have outstanding elements? + while (!(nextToCopy eq next)) { + val newElem = new ::(nextToCopy.head, Nil) + currentLast.next = newElem + currentLast = newElem + nextToCopy = nextToCopy.tail + } + nextToCopy = next.tail + next = next.tail + } + } + + // we have remaining elements - they are unchanged attach them to the end + if (!nextToCopy.isEmpty) + currentLast.next = nextToCopy + + newHead + } + + val result = noneIn(this) + releaseFence() + result + } + + override def partition(p: A => Boolean): (List[A], List[A]) = { + if (isEmpty) List.TupleOfNil + else super.partition(p) match { + case (Nil, xs) => (Nil, this) + case (xs, Nil) => (this, Nil) + case pair => pair + } + } + + final override def toList: List[A] = this + + // Override for performance + override def equals(o: scala.Any): Boolean = { + @tailrec def listEq(a: List[_], b: List[_]): Boolean = + (a eq b) || { + val aEmpty = a.isEmpty + val bEmpty = b.isEmpty + if (!(aEmpty || bEmpty) && a.head == b.head) { + listEq(a.tail, b.tail) + } + else { + aEmpty && bEmpty + } + } + + o match { + case that: List[_] => listEq(this, that) + case _ => super.equals(o) + } + } + + // TODO: uncomment once bincompat allows (reference: scala/scala#9365) + /* + // Override for performance: traverse only as much as needed + // and share tail when nothing needs to be filtered out anymore + override def diff[B >: A](that: collection.Seq[B]): AnyRef = { + if (that.isEmpty || this.isEmpty) this + else if (tail.isEmpty) if (that.contains(head)) Nil else this + else { + val occ = occCounts(that) + val b = new ListBuffer[A]() + @tailrec + def rec(remainder: List[A]): List[A] = { + if(occ.isEmpty) b.prependToList(remainder) + else remainder match { + case Nil => b.result() + case head :: next => { + occ.updateWith(head){ + case None => { + b.append(head) + None + } + case Some(1) => None + case Some(n) => Some(n - 1) + } + rec(next) + } + } + } + rec(this) + } + } + */ + +} + +// Internal code that mutates `next` _must_ call `Statics.releaseFence()` if either immediately, or +// before a newly-allocated, thread-local :: instance is aliased (e.g. in ListBuffer.toList) +final case class :: [+A](override val head: A, private[scala] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally + extends List[A] { + releaseFence() + override def headOption: Some[A] = Some(head) + override def tail: List[A] = next + + def next$access$1 = next + +} + +case object Nil extends List[Nothing] { + override def head: Nothing = throw new NoSuchElementException("head of empty list") + override def headOption: None.type = None + override def tail: Nothing = throw new UnsupportedOperationException("tail of empty list") + override def last: Nothing = throw new NoSuchElementException("last of empty list") + override def init: Nothing = throw new UnsupportedOperationException("init of empty list") + override def knownSize: Int = 0 + override def iterator: Iterator[Nothing] = Iterator.empty + override def unzip[A1, A2](implicit asPair: Nothing => (A1, A2)): (List[A1], List[A2]) = EmptyUnzip + + @transient + private[this] val EmptyUnzip = (Nil, Nil) +} + +/** + * $factoryInfo + * @define coll list + * @define Coll `List` + */ +@SerialVersionUID(3L) +object List extends StrictOptimizedSeqFactory[List] { + private val TupleOfNil = (Nil, Nil) + + def from[B](coll: collection.IterableOnce[B]): List[B] = Nil.prependedAll(coll) + + def newBuilder[A]: Builder[A, List[A]] = new ListBuffer() + + def empty[A]: List[A] = Nil + + @transient + private[collection] val partialNotApplied = new Function1[Any, Any] { def apply(x: Any): Any = this } +} diff --git a/library/src/scala/collection/immutable/ListMap.scala b/library/src/scala/collection/immutable/ListMap.scala new file mode 100644 index 000000000000..8b2cd61175fe --- /dev/null +++ b/library/src/scala/collection/immutable/ListMap.scala @@ -0,0 +1,373 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import scala.annotation.{nowarn, tailrec} +import scala.collection.mutable.ReusableBuilder +import scala.collection.generic.DefaultSerializable +import scala.runtime.Statics.releaseFence +import scala.util.hashing.MurmurHash3 + +/** + * This class implements immutable maps using a list-based data structure. List map iterators and + * traversal methods visit key-value pairs in the order they were first inserted. + * + * Entries are stored internally in reversed insertion order, which means the newest key is at the + * head of the list. As such, methods such as `head` and `tail` are O(n), while `last` and `init` + * are O(1). Other operations, such as inserting or removing entries, are also O(n), which makes + * this collection suitable only for a small number of elements. + * + * Instances of `ListMap` represent empty maps; they can be either created by calling the + * constructor directly, or by applying the function `ListMap.empty`. + * + * @tparam K the type of the keys contained in this list map + * @tparam V the type of the values associated with the keys + * + * @define Coll ListMap + * @define coll list map + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +sealed class ListMap[K, +V] + extends AbstractMap[K, V] + with SeqMap[K, V] + with StrictOptimizedMapOps[K, V, ListMap, ListMap[K, V]] + with MapFactoryDefaults[K, V, ListMap, Iterable] + with DefaultSerializable { + + override def mapFactory: MapFactory[ListMap] = ListMap + + override def size: Int = 0 + + override def isEmpty: Boolean = true + + override def knownSize: Int = 0 + def get(key: K): Option[V] = None + + def updated[V1 >: V](key: K, value: V1): ListMap[K, V1] = new ListMap.Node[K, V1](key, value, this) + + def removed(key: K): ListMap[K, V] = this + + def iterator: Iterator[(K, V)] = { + var curr: ListMap[K, V] = this + var res: List[(K, V)] = Nil + while (curr.nonEmpty) { + res = (curr.key, curr.value) :: res + curr = curr.next + } + res.iterator + } + + @nowarn("msg=overriding method keys") + override def keys: Iterable[K] = { + var curr: ListMap[K, V] = this + var res: List[K] = Nil + while (curr.nonEmpty) { + res = curr.key :: res + curr = curr.next + } + res + } + + override def hashCode(): Int = { + if (isEmpty) MurmurHash3.emptyMapHash + else { + // Can't efficiently override foreachEntry directly in ListMap because it would need to preserve iteration + // order be reversing the list first. But mapHash is symmetric so the reversed order is fine here. + val _reversed = new immutable.AbstractMap[K, V] { + override def isEmpty: Boolean = ListMap.this.isEmpty + override def removed(key: K): Map[K, V] = ListMap.this.removed(key) + override def updated[V1 >: V](key: K, value: V1): Map[K, V1] = ListMap.this.updated(key, value) + override def get(key: K): Option[V] = ListMap.this.get(key) + override def iterator: Iterator[(K, V)] = ListMap.this.iterator + override def foreachEntry[U](f: (K, V) => U): Unit = { + var curr: ListMap[K, V] = ListMap.this + while (curr.nonEmpty) { + f(curr.key, curr.value) + curr = curr.next + } + } + } + MurmurHash3.mapHash(_reversed) + } + } + + private[immutable] def key: K = throw new NoSuchElementException("key of empty map") + private[immutable] def value: V = throw new NoSuchElementException("value of empty map") + private[immutable] def next: ListMap[K, V] = throw new NoSuchElementException("next of empty map") + + override def foldRight[Z](z: Z)(op: ((K, V), Z) => Z): Z = ListMap.foldRightInternal(this, z, op) + override protected[this] def className = "ListMap" + +} + +/** + * $factoryInfo + * + * Note that each element insertion takes O(n) time, which means that creating a list map with + * n elements will take O(n^2^) time. This makes the builder suitable only for a small number of + * elements. + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html#list-maps "Scala's Collection Library overview"]] + * section on `List Maps` for more information. + * @define Coll ListMap + * @define coll list map + */ +@SerialVersionUID(3L) +object ListMap extends MapFactory[ListMap] { + /** + * Represents an entry in the `ListMap`. + */ + private[immutable] final class Node[K, V]( + override private[immutable] val key: K, + private[immutable] var _value: V, + private[immutable] var _init: ListMap[K, V] + ) extends ListMap[K, V] { + releaseFence() + + override private[immutable] def value: V = _value + + override def size: Int = sizeInternal(this, 0) + + @tailrec private[this] def sizeInternal(cur: ListMap[K, V], acc: Int): Int = + if (cur.isEmpty) acc + else sizeInternal(cur.next, acc + 1) + + override def isEmpty: Boolean = false + + override def knownSize: Int = -1 + + @throws[NoSuchElementException] + override def apply(k: K): V = applyInternal(this, k) + + @tailrec private[this] def applyInternal(cur: ListMap[K, V], k: K): V = + if (cur.isEmpty) throw new NoSuchElementException("key not found: " + k) + else if (k == cur.key) cur.value + else applyInternal(cur.next, k) + + override def get(k: K): Option[V] = getInternal(this, k) + + @tailrec private[this] def getInternal(cur: ListMap[K, V], k: K): Option[V] = + if (cur.isEmpty) None + else if (k == cur.key) Some(cur.value) + else getInternal(cur.next, k) + + override def contains(k: K): Boolean = containsInternal(this, k) + + @tailrec private[this] def containsInternal(cur: ListMap[K, V], k: K): Boolean = + if (cur.isEmpty) false + else if (k == cur.key) true + else containsInternal(cur.next, k) + + override def updated[V1 >: V](k: K, v: V1): ListMap[K, V1] = { + + var index = -1 // the index (in reverse) where the key to update exists, if it is found + var found = false // true if the key is found int he map + var isDifferent = false // true if the key was found and the values are different + + { + var curr: ListMap[K, V] = this + + while (curr.nonEmpty && !found) { + if (k == curr.key) { + found = true + isDifferent = v.asInstanceOf[AnyRef] ne curr.value.asInstanceOf[AnyRef] + } + index += 1 + curr = curr.init + } + } + + if (found) { + if (isDifferent) { + var newHead: ListMap.Node[K, V1] = null + var prev: ListMap.Node[K, V1] = null + var curr: ListMap[K, V1] = this + var i = 0 + while (i < index) { + val temp = new ListMap.Node(curr.key, curr.value, null) + if (prev ne null) { + prev._init = temp + } + prev = temp + curr = curr.init + if (newHead eq null) { + newHead = prev + } + i += 1 + } + val newNode = new ListMap.Node(curr.key, v, curr.init) + if (prev ne null) { + prev._init = newNode + } + releaseFence() + if (newHead eq null) newNode else newHead + } else { + this + } + } else { + new ListMap.Node(k, v, this) + } + } + + @tailrec private[this] def removeInternal(k: K, cur: ListMap[K, V], acc: List[ListMap[K, V]]): ListMap[K, V] = + if (cur.isEmpty) acc.last + else if (k == cur.key) acc.foldLeft(cur.next) { (t, h) => new Node(h.key, h.value, t) } + else removeInternal(k, cur.next, cur :: acc) + + override def removed(k: K): ListMap[K, V] = removeInternal(k, this, Nil) + + override private[immutable] def next: ListMap[K, V] = _init + + override def last: (K, V) = (key, value) + override def init: ListMap[K, V] = next + + } + + def empty[K, V]: ListMap[K, V] = EmptyListMap.asInstanceOf[ListMap[K, V]] + + private object EmptyListMap extends ListMap[Any, Nothing] + + def from[K, V](it: collection.IterableOnce[(K, V)]): ListMap[K, V] = + it match { + case lm: ListMap[K, V] => lm + case lhm: collection.mutable.LinkedHashMap[K, V] => + // by directly iterating through LinkedHashMap entries, we save creating intermediate tuples for each + // key-value pair + var current: ListMap[K, V] = empty[K, V] + var firstEntry = lhm._firstEntry + while (firstEntry ne null) { + current = new Node(firstEntry.key, firstEntry.value, current) + firstEntry = firstEntry.later + } + current + case _: collection.Map[K, V] | _: collection.MapView[K, V] => + // when creating from a map, we need not handle duplicate keys, so we can just append each key-value to the end + var current: ListMap[K, V] = empty[K, V] + val iter = it.iterator + while (iter.hasNext) { + val (k, v) = iter.next() + current = new Node(k, v, current) + } + current + + case _ => (newBuilder[K, V] ++= it).result() + } + + /** Returns a new ListMap builder + * + * The implementation safely handles additions after `result()` without calling `clear()` + * + * @tparam K the map key type + * @tparam V the map value type + */ + def newBuilder[K, V]: ReusableBuilder[(K, V), ListMap[K, V]] = new ListMapBuilder[K, V] + + @tailrec private def foldRightInternal[K, V, Z](map: ListMap[K, V], prevValue: Z, op: ((K, V), Z) => Z): Z = { + if (map.isEmpty) prevValue + else foldRightInternal(map.init, op(map.last, prevValue), op) + } +} + +/** Builder for ListMap. + * $multipleResults + */ +private[immutable] final class ListMapBuilder[K, V] extends mutable.ReusableBuilder[(K, V), ListMap[K, V]] { + private[this] var isAliased: Boolean = false + private[this] var underlying: ListMap[K, V] = ListMap.empty + + override def clear(): Unit = { + underlying = ListMap.empty + isAliased = false + } + + override def result(): ListMap[K, V] = { + isAliased = true + releaseFence() + underlying + } + + override def addOne(elem: (K, V)): this.type = addOne(elem._1, elem._2) + + @tailrec + private[this] def insertValueAtKeyReturnFound(m: ListMap[K, V], key: K, value: V): Boolean = m match { + case n: ListMap.Node[K, V] => + if (n.key == key) { + n._value = value + true + } else { + insertValueAtKeyReturnFound(n.init, key, value) + } + case _ => false + } + + def addOne(key: K, value: V): this.type = { + if (isAliased) { + underlying = underlying.updated(key, value) + } else { + if (!insertValueAtKeyReturnFound(underlying, key, value)) { + underlying = new ListMap.Node(key, value, underlying) + } + } + this + } + override def addAll(xs: IterableOnce[(K, V)]): this.type = { + if (isAliased) { + super.addAll(xs) + } else if (underlying.nonEmpty) { + xs match { + case m: collection.Map[K, V] => + // if it is a map, then its keys will not collide with themselves. + // therefor we only need to check the already-existing elements for collisions. + // No need to check the entire list + + val iter = m.iterator + var newUnderlying = underlying + while (iter.hasNext) { + val next = iter.next() + if (!insertValueAtKeyReturnFound(underlying, next._1, next._2)) { + newUnderlying = new ListMap.Node[K, V](next._1, next._2, newUnderlying) + } + } + underlying = newUnderlying + this + + case _ => + super.addAll(xs) + } + } else xs match { + case lhm: collection.mutable.LinkedHashMap[K, V] => + // special-casing LinkedHashMap avoids creating of Iterator and tuples for each key-value + var firstEntry = lhm._firstEntry + while (firstEntry ne null) { + underlying = new ListMap.Node(firstEntry.key, firstEntry.value, underlying) + firstEntry = firstEntry.later + } + this + + case _: collection.Map[K, V] | _: collection.MapView[K, V] => + val iter = xs.iterator + while (iter.hasNext) { + val (k, v) = iter.next() + underlying = new ListMap.Node(k, v, underlying) + } + + this + case _ => + super.addAll(xs) + } + } +} diff --git a/library/src/scala/collection/immutable/ListSet.scala b/library/src/scala/collection/immutable/ListSet.scala new file mode 100644 index 000000000000..4325bd500b14 --- /dev/null +++ b/library/src/scala/collection/immutable/ListSet.scala @@ -0,0 +1,139 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import mutable.{Builder, ImmutableBuilder} +import scala.annotation.tailrec +import scala.collection.generic.DefaultSerializable + +/** + * This class implements immutable sets using a list-based data structure. List set iterators and + * traversal methods visit elements in the order they were first inserted. + * + * Elements are stored internally in reversed insertion order, which means the newest element is at + * the head of the list. As such, methods such as `head` and `tail` are O(n), while `last` and + * `init` are O(1). Other operations, such as inserting or removing entries, are also O(n), which + * makes this collection suitable only for a small number of elements. + * + * Instances of `ListSet` represent empty sets; they can be either created by calling the + * constructor directly, or by applying the function `ListSet.empty`. + * + * @tparam A the type of the elements contained in this list set + * + * @define Coll ListSet + * @define coll list set + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +sealed class ListSet[A] + extends AbstractSet[A] + with StrictOptimizedSetOps[A, ListSet, ListSet[A]] + with IterableFactoryDefaults[A, ListSet] + with DefaultSerializable { + + override protected[this] def className: String = "ListSet" + + override def size: Int = 0 + override def knownSize: Int = 0 + override def isEmpty: Boolean = true + + def contains(elem: A): Boolean = false + + def incl(elem: A): ListSet[A] = new Node(elem) + def excl(elem: A): ListSet[A] = this + + def iterator: scala.collection.Iterator[A] = { + var curr: ListSet[A] = this + var res: List[A] = Nil + while (!curr.isEmpty) { + res = curr.elem :: res + curr = curr.next + } + res.iterator + } + + protected def elem: A = throw new NoSuchElementException("elem of empty set") + protected def next: ListSet[A] = throw new NoSuchElementException("next of empty set") + + override def iterableFactory: IterableFactory[ListSet] = ListSet + + /** + * Represents an entry in the `ListSet`. + */ + protected class Node(override protected val elem: A) extends ListSet[A] { + + override def size = sizeInternal(this, 0) + override def knownSize: Int = -1 + @tailrec private[this] def sizeInternal(n: ListSet[A], acc: Int): Int = + if (n.isEmpty) acc + else sizeInternal(n.next, acc + 1) + + override def isEmpty: Boolean = false + + override def contains(e: A): Boolean = containsInternal(this, e) + + @tailrec private[this] def containsInternal(n: ListSet[A], e: A): Boolean = + !n.isEmpty && (n.elem == e || containsInternal(n.next, e)) + + override def incl(e: A): ListSet[A] = if (contains(e)) this else new Node(e) + + override def excl(e: A): ListSet[A] = removeInternal(e, this, Nil) + + @tailrec private[this] def removeInternal(k: A, cur: ListSet[A], acc: List[ListSet[A]]): ListSet[A] = + if (cur.isEmpty) acc.last + else if (k == cur.elem) acc.foldLeft(cur.next)((t, h) => new t.Node(h.elem)) + else removeInternal(k, cur.next, cur :: acc) + + override protected def next: ListSet[A] = ListSet.this + + override def last: A = elem + + override def init: ListSet[A] = next + } +} + +/** + * $factoryInfo + * + * Note that each element insertion takes O(n) time, which means that creating a list set with + * n elements will take O(n^2^) time. This makes the builder suitable only for a small number of + * elements. + * + * @define Coll ListSet + * @define coll list set + */ +@SerialVersionUID(3L) +object ListSet extends IterableFactory[ListSet] { + + def from[E](it: scala.collection.IterableOnce[E]): ListSet[E] = + it match { + case ls: ListSet[E] => ls + case _ if it.knownSize == 0 => empty[E] + case _ => (newBuilder[E] ++= it).result() + } + + private object EmptyListSet extends ListSet[Any] { + override def knownSize: Int = 0 + } + private[collection] def emptyInstance: ListSet[Any] = EmptyListSet + + def empty[A]: ListSet[A] = EmptyListSet.asInstanceOf[ListSet[A]] + + def newBuilder[A]: Builder[A, ListSet[A]] = + new ImmutableBuilder[A, ListSet[A]](empty) { + def addOne(elem: A): this.type = { elems = elems + elem; this } + } +} diff --git a/library/src/scala/collection/immutable/LongMap.scala b/library/src/scala/collection/immutable/LongMap.scala new file mode 100644 index 000000000000..9832b4a7d55c --- /dev/null +++ b/library/src/scala/collection/immutable/LongMap.scala @@ -0,0 +1,491 @@ +/* + * 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.collection +package immutable + +import scala.language.`2.13` +import java.lang.IllegalStateException + +import scala.collection.generic.{BitOperations, DefaultSerializationProxy} +import scala.collection.mutable.{Builder, ImmutableBuilder, ListBuffer} +import scala.annotation.tailrec +import scala.annotation.unchecked.uncheckedVariance +import scala.language.implicitConversions + +/** Utility class for long maps. + */ +private[immutable] object LongMapUtils extends BitOperations.Long { + def branchMask(i: Long, j: Long) = highestOneBit(i ^ j) + + def join[T](p1: Long, t1: LongMap[T], p2: Long, t2: LongMap[T]): LongMap[T] = { + val m = branchMask(p1, p2) + val p = mask(p1, m) + if (zero(p1, m)) LongMap.Bin(p, m, t1, t2) + else LongMap.Bin(p, m, t2, t1) + } + + def bin[T](prefix: Long, mask: Long, left: LongMap[T], right: LongMap[T]): LongMap[T] = (left, right) match { + case (left, LongMap.Nil) => left + case (LongMap.Nil, right) => right + case (left, right) => LongMap.Bin(prefix, mask, left, right) + } +} + +import LongMapUtils.{Long => _, _} + +/** A companion object for long maps. + * + * @define Coll `LongMap` + */ +object LongMap { + def empty[T]: LongMap[T] = LongMap.Nil + def singleton[T](key: Long, value: T): LongMap[T] = LongMap.Tip(key, value) + def apply[T](elems: (Long, T)*): LongMap[T] = + elems.foldLeft(empty[T])((x, y) => x.updated(y._1, y._2)) + + def from[V](coll: IterableOnce[(Long, V)]): LongMap[V] = + newBuilder[V].addAll(coll).result() + + def newBuilder[V]: Builder[(Long, V), LongMap[V]] = + new ImmutableBuilder[(Long, V), LongMap[V]](empty) { + def addOne(elem: (Long, V)): this.type = { elems = elems + elem; this } + } + + private[immutable] case object Nil extends LongMap[Nothing] { + // Important, don't remove this! See IntMap for explanation. + override def equals(that : Any) = that match { + case _: this.type => true + case _: LongMap[_] => false // The only empty LongMaps are eq Nil + case _ => super.equals(that) + } + } + + private[immutable] case class Tip[+T](key: Long, value: T) extends LongMap[T] { + def withValue[S](s: S) = + if (s.asInstanceOf[AnyRef] eq value.asInstanceOf[AnyRef]) this.asInstanceOf[LongMap.Tip[S]] + else LongMap.Tip(key, s) + } + + private[immutable] case class Bin[+T](prefix: Long, mask: Long, left: LongMap[T], right: LongMap[T]) extends LongMap[T] { + def bin[S](left: LongMap[S], right: LongMap[S]): LongMap[S] = { + if ((this.left eq left) && (this.right eq right)) this.asInstanceOf[LongMap.Bin[S]] + else LongMap.Bin[S](prefix, mask, left, right) + } + } + + implicit def toFactory[V](dummy: LongMap.type): Factory[(Long, V), LongMap[V]] = ToFactory.asInstanceOf[Factory[(Long, V), LongMap[V]]] + + @SerialVersionUID(3L) + private[this] object ToFactory extends Factory[(Long, AnyRef), LongMap[AnyRef]] with Serializable { + def fromSpecific(it: IterableOnce[(Long, AnyRef)]): LongMap[AnyRef] = LongMap.from[AnyRef](it) + def newBuilder: Builder[(Long, AnyRef), LongMap[AnyRef]] = LongMap.newBuilder[AnyRef] + } + + implicit def toBuildFrom[V](factory: LongMap.type): BuildFrom[Any, (Long, V), LongMap[V]] = ToBuildFrom.asInstanceOf[BuildFrom[Any, (Long, V), LongMap[V]]] + private[this] object ToBuildFrom extends BuildFrom[Any, (Long, AnyRef), LongMap[AnyRef]] { + def fromSpecific(from: Any)(it: IterableOnce[(Long, AnyRef)]) = LongMap.from(it) + def newBuilder(from: Any) = LongMap.newBuilder[AnyRef] + } + + implicit def iterableFactory[V]: Factory[(Long, V), LongMap[V]] = toFactory(this) + implicit def buildFromLongMap[V]: BuildFrom[LongMap[_], (Long, V), LongMap[V]] = toBuildFrom(this) +} + +// Iterator over a non-empty LongMap. +private[immutable] abstract class LongMapIterator[V, T](it: LongMap[V]) extends AbstractIterator[T] { + + // Basically this uses a simple stack to emulate conversion over the tree. However + // because we know that Longs are only 64 bits we can have at most 64 LongMap.Bins and + // one LongMap.Tip sitting on the tree at any point. Therefore we know the maximum stack + // depth is 65 + var index = 0 + var buffer = new Array[AnyRef](65) + + def pop() = { + index -= 1 + buffer(index).asInstanceOf[LongMap[V]] + } + + def push(x: LongMap[V]): Unit = { + buffer(index) = x.asInstanceOf[AnyRef] + index += 1 + } + push(it) + + /** + * What value do we assign to a tip? + */ + def valueOf(tip: LongMap.Tip[V]): T + + def hasNext = index != 0 + @tailrec + final def next(): T = + pop() match { + case LongMap.Bin(_,_, t@LongMap.Tip(_, _), right) => { + push(right) + valueOf(t) + } + case LongMap.Bin(_, _, left, right) => { + push(right) + push(left) + next() + } + case t@LongMap.Tip(_, _) => valueOf(t) + // This should never happen. We don't allow LongMap.Nil in subtrees of the LongMap + // and don't return an LongMapIterator for LongMap.Nil. + case LongMap.Nil => throw new IllegalStateException("Empty maps not allowed as subtrees") + } +} + +private[immutable] class LongMapEntryIterator[V](it: LongMap[V]) extends LongMapIterator[V, (Long, V)](it){ + def valueOf(tip: LongMap.Tip[V]) = (tip.key, tip.value) +} + +private[immutable] class LongMapValueIterator[V](it: LongMap[V]) extends LongMapIterator[V, V](it){ + def valueOf(tip: LongMap.Tip[V]) = tip.value +} + +private[immutable] class LongMapKeyIterator[V](it: LongMap[V]) extends LongMapIterator[V, Long](it){ + def valueOf(tip: LongMap.Tip[V]) = tip.key +} + +/** + * Specialised immutable map structure for long keys, based on + * [[https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.37.5452 Fast Mergeable Long Maps]] + * by Okasaki and Gill. Essentially a trie based on binary digits of the integers. + * + * Note: This class is as of 2.8 largely superseded by HashMap. + * + * @tparam T type of the values associated with the long keys. + * + * @define Coll `immutable.LongMap` + * @define coll immutable long integer map + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +sealed abstract class LongMap[+T] extends AbstractMap[Long, T] + with StrictOptimizedMapOps[Long, T, Map, LongMap[T]] + with Serializable { + + override protected def fromSpecific(coll: scala.collection.IterableOnce[(Long, T)] @uncheckedVariance): LongMap[T] = { + //TODO should this be the default implementation of this method in StrictOptimizedIterableOps? + val b = newSpecificBuilder + b.sizeHint(coll) + b.addAll(coll) + b.result() + } + override protected def newSpecificBuilder: Builder[(Long, T), LongMap[T]] @uncheckedVariance = + new ImmutableBuilder[(Long, T), LongMap[T]](empty) { + def addOne(elem: (Long, T)): this.type = { elems = elems + elem; this } + } + + override def empty: LongMap[T] = LongMap.Nil + + override def toList = { + val buffer = new ListBuffer[(Long, T)] + foreach(buffer += _) + buffer.toList + } + + /** + * Iterator over key, value pairs of the map in unsigned order of the keys. + * + * @return an iterator over pairs of long keys and corresponding values. + */ + def iterator: Iterator[(Long, T)] = this match { + case LongMap.Nil => Iterator.empty + case _ => new LongMapEntryIterator(this) + } + + /** + * Loops over the key, value pairs of the map in unsigned order of the keys. + */ + override final def foreach[U](f: ((Long, T)) => U): Unit = this match { + case LongMap.Bin(_, _, left, right) => { left.foreach(f); right.foreach(f) } + case LongMap.Tip(key, value) => f((key, value)) + case LongMap.Nil => + } + + override final def foreachEntry[U](f: (Long, T) => U): Unit = this match { + case LongMap.Bin(_, _, left, right) => { left.foreachEntry(f); right.foreachEntry(f) } + case LongMap.Tip(key, value) => f(key, value) + case LongMap.Nil => + } + + override def keysIterator: Iterator[Long] = this match { + case LongMap.Nil => Iterator.empty + case _ => new LongMapKeyIterator(this) + } + + /** + * Loop over the keys of the map. The same as keys.foreach(f), but may + * be more efficient. + * + * @param f The loop body + */ + final def foreachKey[U](f: Long => U): Unit = this match { + case LongMap.Bin(_, _, left, right) => { left.foreachKey(f); right.foreachKey(f) } + case LongMap.Tip(key, _) => f(key) + case LongMap.Nil => + } + + override def valuesIterator: Iterator[T] = this match { + case LongMap.Nil => Iterator.empty + case _ => new LongMapValueIterator(this) + } + + /** + * Loop over the values of the map. The same as values.foreach(f), but may + * be more efficient. + * + * @param f The loop body + */ + final def foreachValue[U](f: T => U): Unit = this match { + case LongMap.Bin(_, _, left, right) => { left.foreachValue(f); right.foreachValue(f) } + case LongMap.Tip(_, value) => f(value) + case LongMap.Nil => + } + + override protected[this] def className = "LongMap" + + override def isEmpty = this eq LongMap.Nil + override def knownSize: Int = if (isEmpty) 0 else super.knownSize + override def filter(f: ((Long, T)) => Boolean): LongMap[T] = this match { + case LongMap.Bin(prefix, mask, left, right) => { + val (newleft, newright) = (left.filter(f), right.filter(f)) + if ((left eq newleft) && (right eq newright)) this + else bin(prefix, mask, newleft, newright) + } + case LongMap.Tip(key, value) => + if (f((key, value))) this + else LongMap.Nil + case LongMap.Nil => LongMap.Nil + } + + override def transform[S](f: (Long, T) => S): LongMap[S] = this match { + case b@LongMap.Bin(prefix, mask, left, right) => b.bin(left.transform(f), right.transform(f)) + case t@LongMap.Tip(key, value) => t.withValue(f(key, value)) + case LongMap.Nil => LongMap.Nil + } + + final override def size: Int = this match { + case LongMap.Nil => 0 + case LongMap.Tip(_, _) => 1 + case LongMap.Bin(_, _, left, right) => left.size + right.size + } + + @tailrec + final def get(key: Long): Option[T] = this match { + case LongMap.Bin(prefix, mask, left, right) => if (zero(key, mask)) left.get(key) else right.get(key) + case LongMap.Tip(key2, value) => if (key == key2) Some(value) else None + case LongMap.Nil => None + } + + @tailrec + final override def getOrElse[S >: T](key: Long, default: => S): S = this match { + case LongMap.Nil => default + case LongMap.Tip(key2, value) => if (key == key2) value else default + case LongMap.Bin(prefix, mask, left, right) => + if (zero(key, mask)) left.getOrElse(key, default) else right.getOrElse(key, default) + } + + @tailrec + final override def apply(key: Long): T = this match { + case LongMap.Bin(prefix, mask, left, right) => if (zero(key, mask)) left(key) else right(key) + case LongMap.Tip(key2, value) => if (key == key2) value else throw new IllegalArgumentException("Key not found") + case LongMap.Nil => throw new IllegalArgumentException("key not found") + } + + override def + [S >: T] (kv: (Long, S)): LongMap[S] = updated(kv._1, kv._2) + + override def updated[S >: T](key: Long, value: S): LongMap[S] = this match { + case LongMap.Bin(prefix, mask, left, right) => + if (!hasMatch(key, prefix, mask)) join(key, LongMap.Tip(key, value), prefix, this) + else if (zero(key, mask)) LongMap.Bin(prefix, mask, left.updated(key, value), right) + else LongMap.Bin(prefix, mask, left, right.updated(key, value)) + case LongMap.Tip(key2, value2) => + if (key == key2) LongMap.Tip(key, value) + else join(key, LongMap.Tip(key, value), key2, this) + case LongMap.Nil => LongMap.Tip(key, value) + } + + /** + * Updates the map, using the provided function to resolve conflicts if the key is already present. + * + * Equivalent to + * {{{ + * this.get(key) match { + * case None => this.update(key, value) + * case Some(oldvalue) => this.update(key, f(oldvalue, value) + * } + * }}} + * + * @tparam S The supertype of values in this `LongMap`. + * @param key The key to update. + * @param value The value to use if there is no conflict. + * @param f The function used to resolve conflicts. + * @return The updated map. + */ + def updateWith[S >: T](key: Long, value: S, f: (T, S) => S): LongMap[S] = this match { + case LongMap.Bin(prefix, mask, left, right) => + if (!hasMatch(key, prefix, mask)) join(key, LongMap.Tip(key, value), prefix, this) + else if (zero(key, mask)) LongMap.Bin(prefix, mask, left.updateWith(key, value, f), right) + else LongMap.Bin(prefix, mask, left, right.updateWith(key, value, f)) + case LongMap.Tip(key2, value2) => + if (key == key2) LongMap.Tip(key, f(value2, value)) + else join(key, LongMap.Tip(key, value), key2, this) + case LongMap.Nil => LongMap.Tip(key, value) + } + + def removed(key: Long): LongMap[T] = this match { + case LongMap.Bin(prefix, mask, left, right) => + if (!hasMatch(key, prefix, mask)) this + else if (zero(key, mask)) bin(prefix, mask, left - key, right) + else bin(prefix, mask, left, right - key) + case LongMap.Tip(key2, _) => + if (key == key2) LongMap.Nil + else this + case LongMap.Nil => LongMap.Nil + } + + /** + * A combined transform and filter function. Returns an `LongMap` such that + * for each `(key, value)` mapping in this map, if `f(key, value) == None` + * the map contains no mapping for key, and if `f(key, value)`. + * + * @tparam S The type of the values in the resulting `LongMap`. + * @param f The transforming function. + * @return The modified map. + */ + def modifyOrRemove[S](f: (Long, T) => Option[S]): LongMap[S] = this match { + case LongMap.Bin(prefix, mask, left, right) => { + val newleft = left.modifyOrRemove(f) + val newright = right.modifyOrRemove(f) + if ((left eq newleft) && (right eq newright)) this.asInstanceOf[LongMap[S]] + else bin(prefix, mask, newleft, newright) + } + case LongMap.Tip(key, value) => f(key, value) match { + case None => LongMap.Nil + case Some(value2) => + //hack to preserve sharing + if (value.asInstanceOf[AnyRef] eq value2.asInstanceOf[AnyRef]) this.asInstanceOf[LongMap[S]] + else LongMap.Tip(key, value2) + } + case LongMap.Nil => LongMap.Nil + } + + /** + * Forms a union map with that map, using the combining function to resolve conflicts. + * + * @tparam S The type of values in `that`, a supertype of values in `this`. + * @param that The map to form a union with. + * @param f The function used to resolve conflicts between two mappings. + * @return Union of `this` and `that`, with identical key conflicts resolved using the function `f`. + */ + def unionWith[S >: T](that: LongMap[S], f: (Long, S, S) => S): LongMap[S] = (this, that) match{ + case (LongMap.Bin(p1, m1, l1, r1), that@(LongMap.Bin(p2, m2, l2, r2))) => + if (shorter(m1, m2)) { + if (!hasMatch(p2, p1, m1)) join(p1, this, p2, that) + else if (zero(p2, m1)) LongMap.Bin(p1, m1, l1.unionWith(that, f), r1) + else LongMap.Bin(p1, m1, l1, r1.unionWith(that, f)) + } else if (shorter(m2, m1)){ + if (!hasMatch(p1, p2, m2)) join(p1, this, p2, that) + else if (zero(p1, m2)) LongMap.Bin(p2, m2, this.unionWith(l2, f), r2) + else LongMap.Bin(p2, m2, l2, this.unionWith(r2, f)) + } + else { + if (p1 == p2) LongMap.Bin(p1, m1, l1.unionWith(l2,f), r1.unionWith(r2, f)) + else join(p1, this, p2, that) + } + case (LongMap.Tip(key, value), x) => x.updateWith(key, value, (x, y) => f(key, y, x)) + case (x, LongMap.Tip(key, value)) => x.updateWith[S](key, value, (x, y) => f(key, x, y)) + case (LongMap.Nil, x) => x + case (x, LongMap.Nil) => x + } + + /** + * Forms the intersection of these two maps with a combining function. The + * resulting map is a map that has only keys present in both maps and has + * values produced from the original mappings by combining them with `f`. + * + * @tparam S The type of values in `that`. + * @tparam R The type of values in the resulting `LongMap`. + * @param that The map to intersect with. + * @param f The combining function. + * @return Intersection of `this` and `that`, with values for identical keys produced by function `f`. + */ + def intersectionWith[S, R](that: LongMap[S], f: (Long, T, S) => R): LongMap[R] = (this, that) match { + case (LongMap.Bin(p1, m1, l1, r1), that@LongMap.Bin(p2, m2, l2, r2)) => + if (shorter(m1, m2)) { + if (!hasMatch(p2, p1, m1)) LongMap.Nil + else if (zero(p2, m1)) l1.intersectionWith(that, f) + else r1.intersectionWith(that, f) + } else if (m1 == m2) bin(p1, m1, l1.intersectionWith(l2, f), r1.intersectionWith(r2, f)) + else { + if (!hasMatch(p1, p2, m2)) LongMap.Nil + else if (zero(p1, m2)) this.intersectionWith(l2, f) + else this.intersectionWith(r2, f) + } + case (LongMap.Tip(key, value), that) => that.get(key) match { + case None => LongMap.Nil + case Some(value2) => LongMap.Tip(key, f(key, value, value2)) + } + case (_, LongMap.Tip(key, value)) => this.get(key) match { + case None => LongMap.Nil + case Some(value2) => LongMap.Tip(key, f(key, value2, value)) + } + case (_, _) => LongMap.Nil + } + + /** + * Left biased intersection. Returns the map that has all the same mappings as this but only for keys + * which are present in the other map. + * + * @tparam R The type of values in `that`. + * @param that The map to intersect with. + * @return A map with all the keys both in `this` and `that`, mapped to corresponding values from `this`. + */ + def intersection[R](that: LongMap[R]): LongMap[T] = + this.intersectionWith(that, (key: Long, value: T, value2: R) => value) + + def ++[S >: T](that: LongMap[S]) = + this.unionWith[S](that, (key, x, y) => y) + + @tailrec + final def firstKey: Long = this match { + case LongMap.Bin(_, _, l, r) => l.firstKey + case LongMap.Tip(k, v) => k + case LongMap.Nil => throw new IllegalStateException("Empty set") + } + + @tailrec + final def lastKey: Long = this match { + case LongMap.Bin(_, _, l, r) => r.lastKey + case LongMap.Tip(k , v) => k + case LongMap.Nil => throw new IllegalStateException("Empty set") + } + + def map[V2](f: ((Long, T)) => (Long, V2)): LongMap[V2] = LongMap.from(new View.Map(coll, f)) + + def flatMap[V2](f: ((Long, T)) => IterableOnce[(Long, V2)]): LongMap[V2] = LongMap.from(new View.FlatMap(coll, f)) + + override def concat[V1 >: T](that: scala.collection.IterableOnce[(Long, V1)]): LongMap[V1] = + super.concat(that).asInstanceOf[LongMap[V1]] // Already has correct type but not declared as such + + override def ++ [V1 >: T](that: scala.collection.IterableOnce[(Long, V1)]): LongMap[V1] = concat(that) + + def collect[V2](pf: PartialFunction[(Long, T), (Long, V2)]): LongMap[V2] = + strictOptimizedCollect(LongMap.newBuilder[V2], pf) + + protected[this] def writeReplace(): AnyRef = new DefaultSerializationProxy(LongMap.toFactory[T](LongMap), this) +} diff --git a/library/src/scala/collection/immutable/Map.scala b/library/src/scala/collection/immutable/Map.scala new file mode 100644 index 000000000000..b262243901c9 --- /dev/null +++ b/library/src/scala/collection/immutable/Map.scala @@ -0,0 +1,713 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import scala.annotation.unchecked.uncheckedVariance +import scala.collection.generic.DefaultSerializable +import scala.collection.immutable.Map.Map4 +import scala.collection.mutable.{Builder, ReusableBuilder} +import SeqMap.{SeqMap1, SeqMap2, SeqMap3, SeqMap4} + +/** Base type of immutable Maps */ +trait Map[K, +V] + extends Iterable[(K, V)] + with collection.Map[K, V] + with MapOps[K, V, Map, Map[K, V]] + with MapFactoryDefaults[K, V, Map, Iterable] { + + override def mapFactory: scala.collection.MapFactory[Map] = Map + + override final def toMap[K2, V2](implicit ev: (K, V) <:< (K2, V2)): Map[K2, V2] = Map.from(this.asInstanceOf[Map[K2, V2]]) + + /** The same map with a given default function. + * Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc. + * are not affected by `withDefault`. + * + * Invoking transformer methods (e.g. `map`) will not preserve the default value. + * + * @param d the function mapping keys to values, used for non-present keys + * @return a wrapper of the map with a default value + */ + def withDefault[V1 >: V](d: K => V1): Map[K, V1] = new Map.WithDefault[K, V1](this, d) + + /** The same map with a given default value. + * Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc. + * are not affected by `withDefaultValue`. + * + * Invoking transformer methods (e.g. `map`) will not preserve the default value. + * + * @param d default value used for non-present keys + * @return a wrapper of the map with a default value + */ + def withDefaultValue[V1 >: V](d: V1): Map[K, V1] = new Map.WithDefault[K, V1](this, _ => d) +} + +/** Base trait of immutable Maps implementations + * + * @define coll immutable map + * @define Coll `immutable.Map` + */ +transparent trait MapOps[K, +V, +CC[X, +Y] <: MapOps[X, Y, CC, _], +C <: MapOps[K, V, CC, C]] + extends IterableOps[(K, V), Iterable, C] + with collection.MapOps[K, V, CC, C] { + + protected def coll: C with CC[K, V] + + /** Removes a key from this map, returning a new map. + * + * @param key the key to be removed + * @return a new map without a binding for ''key'' + */ + def removed(key: K): C + + /** Alias for `removed` */ + @`inline` final def - (key: K): C = removed(key) + + @deprecated("Use -- with an explicit collection", "2.13.0") + def - (key1: K, key2: K, keys: K*): C = removed(key1).removed(key2).removedAll(keys) + + /** Creates a new $coll from this $coll by removing all elements of another + * collection. + * + * $willForceEvaluation + * + * @param keys the collection containing the removed elements. + * @return a new $coll that contains all elements of the current $coll + * except one less occurrence of each of the elements of `elems`. + */ + def removedAll(keys: IterableOnce[K]): C = keys.iterator.foldLeft[C](coll)(_ - _) + + /** Alias for `removedAll` */ + @`inline` final override def -- (keys: IterableOnce[K]): C = removedAll(keys) + + /** Creates a new map obtained by updating this map with a given key/value pair. + * @param key the key + * @param value the value + * @tparam V1 the type of the added value + * @return A new map with the new key/value mapping added to this map. + */ + def updated[V1 >: V](key: K, value: V1): CC[K, V1] + + /** + * Update a mapping for the specified key and its current optionally mapped value + * (`Some` if there is current mapping, `None` if not). + * + * If the remapping function returns `Some(v)`, the mapping is updated with the new value `v`. + * If the remapping function returns `None`, the mapping is removed (or remains absent if initially absent). + * If the function itself throws an exception, the exception is rethrown, and the current mapping is left unchanged. + * + * @param key the key value + * @param remappingFunction a function that receives current optionally mapped value and return a new mapping + * @return A new map with the updated mapping with the key + */ + def updatedWith[V1 >: V](key: K)(remappingFunction: Option[V] => Option[V1]): CC[K,V1] = { + val previousValue = this.get(key) + remappingFunction(previousValue) match { + case None => previousValue.fold(coll)(_ => this.removed(key).coll) + case Some(nextValue) => + if (previousValue.exists(_.asInstanceOf[AnyRef] eq nextValue.asInstanceOf[AnyRef])) coll + else coll.updated(key, nextValue) + } + } + + /** + * Alias for `updated` + * + * @param kv the key/value pair. + * @tparam V1 the type of the value in the key/value pair. + * @return A new map with the new binding added to this map. + */ + override def + [V1 >: V](kv: (K, V1)): CC[K, V1] = updated(kv._1, kv._2) + + /** This function transforms all the values of mappings contained + * in this map with function `f`. + * + * @param f A function over keys and values + * @return the updated map + */ + def transform[W](f: (K, V) => W): CC[K, W] = map { case (k, v) => (k, f(k, v)) } + + override def keySet: Set[K] = new ImmutableKeySet + + /** The implementation class of the set returned by `keySet` */ + protected[immutable] class ImmutableKeySet extends AbstractSet[K] with GenKeySet with DefaultSerializable { + def incl(elem: K): Set[K] = if (this(elem)) this else empty ++ this + elem + def excl(elem: K): Set[K] = if (this(elem)) empty ++ this - elem else this + } + +} + +transparent trait StrictOptimizedMapOps[K, +V, +CC[X, +Y] <: MapOps[X, Y, CC, _], +C <: MapOps[K, V, CC, C]] + extends MapOps[K, V, CC, C] + with collection.StrictOptimizedMapOps[K, V, CC, C] + with StrictOptimizedIterableOps[(K, V), Iterable, C] { + + override def concat [V1 >: V](that: collection.IterableOnce[(K, V1)]): CC[K, V1] = { + var result: CC[K, V1] = coll + val it = that.iterator + while (it.hasNext) result = result + it.next() + result + } +} + + +/** + * $factoryInfo + * @define coll immutable map + * @define Coll `immutable.Map` + */ +@SerialVersionUID(3L) +object Map extends MapFactory[Map] { + + @SerialVersionUID(3L) + class WithDefault[K, +V](val underlying: Map[K, V], val defaultValue: K => V) + extends AbstractMap[K, V] + with MapOps[K, V, Map, WithDefault[K, V]] with Serializable { + + def get(key: K): Option[V] = underlying.get(key) + + override def default(key: K): V = defaultValue(key) + + override def iterableFactory: IterableFactory[Iterable] = underlying.iterableFactory + + def iterator: Iterator[(K, V)] = underlying.iterator + + override def isEmpty: Boolean = underlying.isEmpty + + override def mapFactory: MapFactory[Map] = underlying.mapFactory + + override def concat [V2 >: V](xs: collection.IterableOnce[(K, V2)]): WithDefault[K, V2] = + new WithDefault(underlying.concat(xs), defaultValue) + + def removed(key: K): WithDefault[K, V] = new WithDefault[K, V](underlying.removed(key), defaultValue) + + def updated[V1 >: V](key: K, value: V1): WithDefault[K, V1] = + new WithDefault[K, V1](underlying.updated(key, value), defaultValue) + + override def empty: WithDefault[K, V] = new WithDefault[K, V](underlying.empty, defaultValue) + + override protected def fromSpecific(coll: collection.IterableOnce[(K, V)] @uncheckedVariance): WithDefault[K, V] = + new WithDefault[K, V](mapFactory.from(coll), defaultValue) + + override protected def newSpecificBuilder: Builder[(K, V), WithDefault[K, V]] @uncheckedVariance = + Map.newBuilder.mapResult((p: Map[K, V]) => new WithDefault[K, V](p, defaultValue)) + } + + def empty[K, V]: Map[K, V] = EmptyMap.asInstanceOf[Map[K, V]] + + def from[K, V](it: IterableOnce[(K, V)]): Map[K, V] = + it match { + case it: Iterable[_] if it.isEmpty => empty[K, V] + // Since IterableOnce[(K, V)] launders the variance of K, + // identify only our implementations which can be soundly substituted. + // For example, the ordering used by sorted maps would fail on widened key type. (scala/bug#12745) + // The following type test is not sufficient: case m: Map[K, V] => m + case m: HashMap[K, V] => m + case m: Map1[K, V] => m + case m: Map2[K, V] => m + case m: Map3[K, V] => m + case m: Map4[K, V] => m + //case m: WithDefault[K, V] => m // cf SortedMap.WithDefault + //case m: SeqMap[K, V] => SeqMap.from(it) // inlined here to avoid hard dependency + case m: ListMap[K, V] => m + case m: TreeSeqMap[K, V] => m + case m: VectorMap[K, V] => m + case m: SeqMap1[K, V] => m + case m: SeqMap2[K, V] => m + case m: SeqMap3[K, V] => m + case m: SeqMap4[K, V] => m + + // Maps with a reified key type must be rebuilt, such as `SortedMap` and `IntMap`. + case _ => newBuilder[K, V].addAll(it).result() + } + + def newBuilder[K, V]: Builder[(K, V), Map[K, V]] = new MapBuilderImpl + + @SerialVersionUID(3L) + private object EmptyMap extends AbstractMap[Any, Nothing] with Serializable { + override def size: Int = 0 + override def knownSize: Int = 0 + override def isEmpty: Boolean = true + override def apply(key: Any) = throw new NoSuchElementException("key not found: " + key) + override def contains(key: Any) = false + def get(key: Any): Option[Nothing] = None + override def getOrElse [V1](key: Any, default: => V1): V1 = default + def iterator: Iterator[(Any, Nothing)] = Iterator.empty + override def keysIterator: Iterator[Any] = Iterator.empty + override def valuesIterator: Iterator[Nothing] = Iterator.empty + def updated [V1] (key: Any, value: V1): Map[Any, V1] = new Map1(key, value) + def removed(key: Any): Map[Any, Nothing] = this + override def concat[V2 >: Nothing](suffix: IterableOnce[(Any, V2)]): Map[Any, V2] = suffix match { + case m: immutable.Map[Any, V2] => m + case _ => super.concat(suffix) + } + } + + @SerialVersionUID(3L) + final class Map1[K, +V](key1: K, value1: V) extends AbstractMap[K, V] with StrictOptimizedIterableOps[(K, V), Iterable, Map[K, V]] with Serializable { + override def size: Int = 1 + override def knownSize: Int = 1 + override def isEmpty: Boolean = false + override def apply(key: K): V = if (key == key1) value1 else throw new NoSuchElementException("key not found: " + key) + override def contains(key: K): Boolean = key == key1 + def get(key: K): Option[V] = + if (key == key1) Some(value1) else None + override def getOrElse [V1 >: V](key: K, default: => V1): V1 = + if (key == key1) value1 else default + def iterator: Iterator[(K, V)] = Iterator.single((key1, value1)) + override def keysIterator: Iterator[K] = Iterator.single(key1) + override def valuesIterator: Iterator[V] = Iterator.single(value1) + def updated[V1 >: V](key: K, value: V1): Map[K, V1] = + if (key == key1) new Map1(key1, value) + else new Map2(key1, value1, key, value) + def removed(key: K): Map[K, V] = + if (key == key1) Map.empty else this + override def foreach[U](f: ((K, V)) => U): Unit = { + f((key1, value1)) + } + override def exists(p: ((K, V)) => Boolean): Boolean = p((key1, value1)) + override def forall(p: ((K, V)) => Boolean): Boolean = p((key1, value1)) + override protected[collection] def filterImpl(pred: ((K, V)) => Boolean, isFlipped: Boolean): Map[K, V] = + if (pred((key1, value1)) != isFlipped) this else Map.empty + override def transform[W](f: (K, V) => W): Map[K, W] = { + val walue1 = f(key1, value1) + if (walue1.asInstanceOf[AnyRef] eq value1.asInstanceOf[AnyRef]) this.asInstanceOf[Map[K, W]] + else new Map1(key1, walue1) + } + override def hashCode(): Int = { + import scala.util.hashing.MurmurHash3 + var a, b = 0 + val N = 1 + var c = 1 + + var h = MurmurHash3.tuple2Hash(key1, value1) + a += h + b ^= h + c *= h | 1 + + h = MurmurHash3.mapSeed + h = MurmurHash3.mix(h, a) + h = MurmurHash3.mix(h, b) + h = MurmurHash3.mixLast(h, c) + MurmurHash3.finalizeHash(h, N) + } + } + + @SerialVersionUID(3L) + final class Map2[K, +V](key1: K, value1: V, key2: K, value2: V) extends AbstractMap[K, V] with StrictOptimizedIterableOps[(K, V), Iterable, Map[K, V]] with Serializable { + override def size: Int = 2 + override def knownSize: Int = 2 + override def isEmpty: Boolean = false + override def apply(key: K): V = + if (key == key1) value1 + else if (key == key2) value2 + else throw new NoSuchElementException("key not found: " + key) + override def contains(key: K): Boolean = (key == key1) || (key == key2) + def get(key: K): Option[V] = + if (key == key1) Some(value1) + else if (key == key2) Some(value2) + else None + override def getOrElse [V1 >: V](key: K, default: => V1): V1 = + if (key == key1) value1 + else if (key == key2) value2 + else default + def iterator: Iterator[(K, V)] = new Map2Iterator[(K, V)] { + override protected def nextResult(k: K, v: V): (K, V) = (k, v) + } + override def keysIterator: Iterator[K] = new Map2Iterator[K] { + override protected def nextResult(k: K, v: V): K = k + } + override def valuesIterator: Iterator[V] = new Map2Iterator[V] { + override protected def nextResult(k: K, v: V): V = v + } + + private abstract class Map2Iterator[A] extends AbstractIterator[A] { + private[this] var i = 0 + override def hasNext: Boolean = i < 2 + override def next(): A = { + val result = i match { + case 0 => nextResult(key1, value1) + case 1 => nextResult(key2, value2) + case _ => Iterator.empty.next() + } + i += 1 + result + } + override def drop(n: Int): Iterator[A] = { i += n; this } + protected def nextResult(k: K, v: V @uncheckedVariance): A + } + def updated[V1 >: V](key: K, value: V1): Map[K, V1] = + if (key == key1) new Map2(key1, value, key2, value2) + else if (key == key2) new Map2(key1, value1, key2, value) + else new Map3(key1, value1, key2, value2, key, value) + def removed(key: K): Map[K, V] = + if (key == key1) new Map1(key2, value2) + else if (key == key2) new Map1(key1, value1) + else this + override def foreach[U](f: ((K, V)) => U): Unit = { + f((key1, value1)); f((key2, value2)) + } + override def exists(p: ((K, V)) => Boolean): Boolean = p((key1, value1)) || p((key2, value2)) + override def forall(p: ((K, V)) => Boolean): Boolean = p((key1, value1)) && p((key2, value2)) + override protected[collection] def filterImpl(pred: ((K, V)) => Boolean, isFlipped: Boolean): Map[K, V] = { + var k1 = null.asInstanceOf[K] + var v1 = null.asInstanceOf[V] + var n = 0 + if (pred((key1, value1)) != isFlipped) { {k1 = key1; v1 = value1}; n += 1} + if (pred((key2, value2)) != isFlipped) { if (n == 0) {k1 = key2; v1 = value2}; n += 1} + + n match { + case 0 => Map.empty + case 1 => new Map1(k1, v1) + case 2 => this + } + } + override def transform[W](f: (K, V) => W): Map[K, W] = { + val walue1 = f(key1, value1) + val walue2 = f(key2, value2) + if ((walue1.asInstanceOf[AnyRef] eq value1.asInstanceOf[AnyRef]) && + (walue2.asInstanceOf[AnyRef] eq value2.asInstanceOf[AnyRef])) this.asInstanceOf[Map[K, W]] + else new Map2(key1, walue1, key2, walue2) + } + override def hashCode(): Int = { + import scala.util.hashing.MurmurHash3 + var a, b = 0 + val N = 2 + var c = 1 + + var h = MurmurHash3.tuple2Hash(key1, value1) + a += h + b ^= h + c *= h | 1 + + h = MurmurHash3.tuple2Hash(key2, value2) + a += h + b ^= h + c *= h | 1 + + h = MurmurHash3.mapSeed + h = MurmurHash3.mix(h, a) + h = MurmurHash3.mix(h, b) + h = MurmurHash3.mixLast(h, c) + MurmurHash3.finalizeHash(h, N) + } + } + + @SerialVersionUID(3L) + class Map3[K, +V](key1: K, value1: V, key2: K, value2: V, key3: K, value3: V) extends AbstractMap[K, V] with StrictOptimizedIterableOps[(K, V), Iterable, Map[K, V]] with Serializable { + override def size: Int = 3 + override def knownSize: Int = 3 + override def isEmpty: Boolean = false + override def apply(key: K): V = + if (key == key1) value1 + else if (key == key2) value2 + else if (key == key3) value3 + else throw new NoSuchElementException("key not found: " + key) + override def contains(key: K): Boolean = (key == key1) || (key == key2) || (key == key3) + def get(key: K): Option[V] = + if (key == key1) Some(value1) + else if (key == key2) Some(value2) + else if (key == key3) Some(value3) + else None + override def getOrElse [V1 >: V](key: K, default: => V1): V1 = + if (key == key1) value1 + else if (key == key2) value2 + else if (key == key3) value3 + else default + def iterator: Iterator[(K, V)] = new Map3Iterator[(K, V)] { + override protected def nextResult(k: K, v: V): (K, V) = (k, v) + } + override def keysIterator: Iterator[K] = new Map3Iterator[K] { + override protected def nextResult(k: K, v: V): K = k + } + override def valuesIterator: Iterator[V] = new Map3Iterator[V] { + override protected def nextResult(k: K, v: V): V = v + } + + private abstract class Map3Iterator[A] extends AbstractIterator[A] { + private[this] var i = 0 + override def hasNext: Boolean = i < 3 + override def next(): A = { + val result = i match { + case 0 => nextResult(key1, value1) + case 1 => nextResult(key2, value2) + case 2 => nextResult(key3, value3) + case _ => Iterator.empty.next() + } + i += 1 + result + } + override def drop(n: Int): Iterator[A] = { i += n; this } + protected def nextResult(k: K, v: V @uncheckedVariance): A + } + def updated[V1 >: V](key: K, value: V1): Map[K, V1] = + if (key == key1) new Map3(key1, value, key2, value2, key3, value3) + else if (key == key2) new Map3(key1, value1, key2, value, key3, value3) + else if (key == key3) new Map3(key1, value1, key2, value2, key3, value) + else new Map4(key1, value1, key2, value2, key3, value3, key, value) + def removed(key: K): Map[K, V] = + if (key == key1) new Map2(key2, value2, key3, value3) + else if (key == key2) new Map2(key1, value1, key3, value3) + else if (key == key3) new Map2(key1, value1, key2, value2) + else this + override def foreach[U](f: ((K, V)) => U): Unit = { + f((key1, value1)); f((key2, value2)); f((key3, value3)) + } + override def exists(p: ((K, V)) => Boolean): Boolean = p((key1, value1)) || p((key2, value2)) || p((key3, value3)) + override def forall(p: ((K, V)) => Boolean): Boolean = p((key1, value1)) && p((key2, value2)) && p((key3, value3)) + override protected[collection] def filterImpl(pred: ((K, V)) => Boolean, isFlipped: Boolean): Map[K, V] = { + var k1, k2 = null.asInstanceOf[K] + var v1, v2 = null.asInstanceOf[V] + var n = 0 + if (pred((key1, value1)) != isFlipped) { { k1 = key1; v1 = value1 }; n += 1} + if (pred((key2, value2)) != isFlipped) { if (n == 0) { k1 = key2; v1 = value2 } else { k2 = key2; v2 = value2 }; n += 1} + if (pred((key3, value3)) != isFlipped) { if (n == 0) { k1 = key3; v1 = value3 } else if (n == 1) { k2 = key3; v2 = value3 }; n += 1} + + n match { + case 0 => Map.empty + case 1 => new Map1(k1, v1) + case 2 => new Map2(k1, v1, k2, v2) + case 3 => this + } + } + override def transform[W](f: (K, V) => W): Map[K, W] = { + val walue1 = f(key1, value1) + val walue2 = f(key2, value2) + val walue3 = f(key3, value3) + if ((walue1.asInstanceOf[AnyRef] eq value1.asInstanceOf[AnyRef]) && + (walue2.asInstanceOf[AnyRef] eq value2.asInstanceOf[AnyRef]) && + (walue3.asInstanceOf[AnyRef] eq value3.asInstanceOf[AnyRef])) this.asInstanceOf[Map[K, W]] + else new Map3(key1, walue1, key2, walue2, key3, walue3) + } + override def hashCode(): Int = { + import scala.util.hashing.MurmurHash3 + var a, b = 0 + val N = 3 + var c = 1 + + var h = MurmurHash3.tuple2Hash(key1, value1) + a += h + b ^= h + c *= h | 1 + + h = MurmurHash3.tuple2Hash(key2, value2) + a += h + b ^= h + c *= h | 1 + + h = MurmurHash3.tuple2Hash(key3, value3) + a += h + b ^= h + c *= h | 1 + + h = MurmurHash3.mapSeed + h = MurmurHash3.mix(h, a) + h = MurmurHash3.mix(h, b) + h = MurmurHash3.mixLast(h, c) + MurmurHash3.finalizeHash(h, N) + } + } + + @SerialVersionUID(3L) + final class Map4[K, +V](key1: K, value1: V, key2: K, value2: V, key3: K, value3: V, key4: K, value4: V) + extends AbstractMap[K, V] with StrictOptimizedIterableOps[(K, V), Iterable, Map[K, V]] with Serializable { + + override def size: Int = 4 + override def knownSize: Int = 4 + override def isEmpty: Boolean = false + override def apply(key: K): V = + if (key == key1) value1 + else if (key == key2) value2 + else if (key == key3) value3 + else if (key == key4) value4 + else throw new NoSuchElementException("key not found: " + key) + override def contains(key: K): Boolean = (key == key1) || (key == key2) || (key == key3) || (key == key4) + def get(key: K): Option[V] = + if (key == key1) Some(value1) + else if (key == key2) Some(value2) + else if (key == key3) Some(value3) + else if (key == key4) Some(value4) + else None + override def getOrElse [V1 >: V](key: K, default: => V1): V1 = + if (key == key1) value1 + else if (key == key2) value2 + else if (key == key3) value3 + else if (key == key4) value4 + else default + def iterator: Iterator[(K, V)] = new Map4Iterator[(K, V)] { + override protected def nextResult(k: K, v: V): (K, V) = (k, v) + } + override def keysIterator: Iterator[K] = new Map4Iterator[K] { + override protected def nextResult(k: K, v: V): K = k + } + override def valuesIterator: Iterator[V] = new Map4Iterator[V] { + override protected def nextResult(k: K, v: V): V = v + } + + private abstract class Map4Iterator[A] extends AbstractIterator[A] { + private[this] var i = 0 + override def hasNext: Boolean = i < 4 + override def next(): A = { + val result = i match { + case 0 => nextResult(key1, value1) + case 1 => nextResult(key2, value2) + case 2 => nextResult(key3, value3) + case 3 => nextResult(key4, value4) + case _ => Iterator.empty.next() + } + i += 1 + result + } + override def drop(n: Int): Iterator[A] = { i += n; this } + protected def nextResult(k: K, v: V @uncheckedVariance): A + } + def updated[V1 >: V](key: K, value: V1): Map[K, V1] = + if (key == key1) new Map4(key1, value, key2, value2, key3, value3, key4, value4) + else if (key == key2) new Map4(key1, value1, key2, value, key3, value3, key4, value4) + else if (key == key3) new Map4(key1, value1, key2, value2, key3, value, key4, value4) + else if (key == key4) new Map4(key1, value1, key2, value2, key3, value3, key4, value) + else HashMap.empty[K, V1].updated(key1,value1).updated(key2, value2).updated(key3, value3).updated(key4, value4).updated(key, value) + def removed(key: K): Map[K, V] = + if (key == key1) new Map3(key2, value2, key3, value3, key4, value4) + else if (key == key2) new Map3(key1, value1, key3, value3, key4, value4) + else if (key == key3) new Map3(key1, value1, key2, value2, key4, value4) + else if (key == key4) new Map3(key1, value1, key2, value2, key3, value3) + else this + override def foreach[U](f: ((K, V)) => U): Unit = { + f((key1, value1)); f((key2, value2)); f((key3, value3)); f((key4, value4)) + } + override def exists(p: ((K, V)) => Boolean): Boolean = p((key1, value1)) || p((key2, value2)) || p((key3, value3)) || p((key4, value4)) + override def forall(p: ((K, V)) => Boolean): Boolean = p((key1, value1)) && p((key2, value2)) && p((key3, value3)) && p((key4, value4)) + override protected[collection] def filterImpl(pred: ((K, V)) => Boolean, isFlipped: Boolean): Map[K, V] = { + var k1, k2, k3 = null.asInstanceOf[K] + var v1, v2, v3 = null.asInstanceOf[V] + var n = 0 + if (pred((key1, value1)) != isFlipped) { { k1 = key1; v1 = value1 }; n += 1} + if (pred((key2, value2)) != isFlipped) { if (n == 0) { k1 = key2; v1 = value2 } else { k2 = key2; v2 = value2 }; n += 1} + if (pred((key3, value3)) != isFlipped) { if (n == 0) { k1 = key3; v1 = value3 } else if (n == 1) { k2 = key3; v2 = value3 } else { k3 = key3; v3 = value3}; n += 1} + if (pred((key4, value4)) != isFlipped) { if (n == 0) { k1 = key4; v1 = value4 } else if (n == 1) { k2 = key4; v2 = value4 } else if (n == 2) { k3 = key4; v3 = value4 }; n += 1} + + n match { + case 0 => Map.empty + case 1 => new Map1(k1, v1) + case 2 => new Map2(k1, v1, k2, v2) + case 3 => new Map3(k1, v1, k2, v2, k3, v3) + case 4 => this + } + } + override def transform[W](f: (K, V) => W): Map[K, W] = { + val walue1 = f(key1, value1) + val walue2 = f(key2, value2) + val walue3 = f(key3, value3) + val walue4 = f(key4, value4) + if ((walue1.asInstanceOf[AnyRef] eq value1.asInstanceOf[AnyRef]) && + (walue2.asInstanceOf[AnyRef] eq value2.asInstanceOf[AnyRef]) && + (walue3.asInstanceOf[AnyRef] eq value3.asInstanceOf[AnyRef]) && + (walue4.asInstanceOf[AnyRef] eq value4.asInstanceOf[AnyRef])) this.asInstanceOf[Map[K, W]] + else new Map4(key1, walue1, key2, walue2, key3, walue3, key4, walue4) + } + private[immutable] def buildTo[V1 >: V](builder: HashMapBuilder[K, V1]): builder.type = + builder.addOne(key1, value1).addOne(key2, value2).addOne(key3, value3).addOne(key4, value4) + override def hashCode(): Int = { + import scala.util.hashing.MurmurHash3 + var a, b = 0 + val N = 4 + var c = 1 + + var h = MurmurHash3.tuple2Hash(key1, value1) + a += h + b ^= h + c *= h | 1 + + h = MurmurHash3.tuple2Hash(key2, value2) + a += h + b ^= h + c *= h | 1 + + h = MurmurHash3.tuple2Hash(key3, value3) + a += h + b ^= h + c *= h | 1 + + h = MurmurHash3.tuple2Hash(key4, value4) + a += h + b ^= h + c *= h | 1 + + h = MurmurHash3.mapSeed + h = MurmurHash3.mix(h, a) + h = MurmurHash3.mix(h, b) + h = MurmurHash3.mixLast(h, c) + MurmurHash3.finalizeHash(h, N) + } + } +} + +/** Explicit instantiation of the `Map` trait to reduce class file size in subclasses. */ +abstract class AbstractMap[K, +V] extends scala.collection.AbstractMap[K, V] with Map[K, V] + +private[immutable] final class MapBuilderImpl[K, V] extends ReusableBuilder[(K, V), Map[K, V]] { + private[this] var elems: Map[K, V] = Map.empty + private[this] var switchedToHashMapBuilder: Boolean = false + private[this] var hashMapBuilder: HashMapBuilder[K, V] = _ + + private[immutable] def getOrElse[V0 >: V](key: K, value: V0): V0 = + if (hashMapBuilder ne null) hashMapBuilder.getOrElse(key, value) + else elems.getOrElse(key, value) + + override def clear(): Unit = { + elems = Map.empty + if (hashMapBuilder != null) { + hashMapBuilder.clear() + } + switchedToHashMapBuilder = false + } + + override def result(): Map[K, V] = + if (switchedToHashMapBuilder) hashMapBuilder.result() else elems + + def addOne(key: K, value: V): this.type = { + if (switchedToHashMapBuilder) { + hashMapBuilder.addOne(key, value) + } else if (elems.size < 4) { + elems = elems.updated(key, value) + } else { + // assert(elems.size == 4) + if (elems.contains(key)) { + elems = elems.updated(key, value) + } else { + switchedToHashMapBuilder = true + if (hashMapBuilder == null) { + hashMapBuilder = new HashMapBuilder + } + elems.asInstanceOf[Map4[K, V]].buildTo(hashMapBuilder) + hashMapBuilder.addOne(key, value) + } + } + + this + } + + def addOne(elem: (K, V)) = addOne(elem._1, elem._2) + + override def addAll(xs: IterableOnce[(K, V)]): this.type = + if (switchedToHashMapBuilder) { + hashMapBuilder.addAll(xs) + this + } else { + super.addAll(xs) + } +} diff --git a/library/src/scala/collection/immutable/NumericRange.scala b/library/src/scala/collection/immutable/NumericRange.scala new file mode 100644 index 000000000000..6a10bef7171f --- /dev/null +++ b/library/src/scala/collection/immutable/NumericRange.scala @@ -0,0 +1,542 @@ +/* + * 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.collection.immutable + +import scala.language.`2.13` +import scala.collection.Stepper.EfficientSplit +import scala.collection.{AbstractIterator, AnyStepper, IterableFactoryDefaults, Iterator, Stepper, StepperShape} +import scala.collection.generic.CommonErrors + +/** `NumericRange` is a more generic version of the + * `Range` class which works with arbitrary types. + * It must be supplied with an `Integral` implementation of the + * range type. + * + * Factories for likely types include `Range.BigInt`, `Range.Long`, + * and `Range.BigDecimal`. `Range.Int` exists for completeness, but + * the `Int`-based `scala.Range` should be more performant. + * + * {{{ + * val r1 = Range(0, 100, 1) + * val veryBig = Int.MaxValue.toLong + 1 + * val r2 = Range.Long(veryBig, veryBig + 100, 1) + * assert(r1 sameElements r2.map(_ - veryBig)) + * }}} + * + * @define Coll `NumericRange` + * @define coll numeric range + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +@SerialVersionUID(3L) +sealed class NumericRange[T]( + val start: T, + val end: T, + val step: T, + val isInclusive: Boolean +)(implicit + num: Integral[T] +) + extends AbstractSeq[T] + with IndexedSeq[T] + with IndexedSeqOps[T, IndexedSeq, IndexedSeq[T]] + with StrictOptimizedSeqOps[T, IndexedSeq, IndexedSeq[T]] + with IterableFactoryDefaults[T, IndexedSeq] + with Serializable { self => + + override def iterator: Iterator[T] = new NumericRange.NumericRangeIterator(this, num) + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[T, S]): S with EfficientSplit = { + import scala.collection.convert._ + import impl._ + val s = shape.shape match { + case StepperShape.IntShape => new IntNumericRangeStepper (this.asInstanceOf[NumericRange[Int]], 0, length) + case StepperShape.LongShape => new LongNumericRangeStepper (this.asInstanceOf[NumericRange[Long]], 0, length) + case _ => shape.parUnbox(new AnyNumericRangeStepper[T](this, 0, length).asInstanceOf[AnyStepper[T] with EfficientSplit]) + } + s.asInstanceOf[S with EfficientSplit] + } + + + /** Note that NumericRange must be invariant so that constructs + * such as "1L to 10 by 5" do not infer the range type as AnyVal. + */ + import num._ + + // See comment in Range for why this must be lazy. + override lazy val length: Int = NumericRange.count(start, end, step, isInclusive) + override lazy val isEmpty: Boolean = ( + (num.gt(start, end) && num.gt(step, num.zero)) + || (num.lt(start, end) && num.lt(step, num.zero)) + || (num.equiv(start, end) && !isInclusive) + ) + override def last: T = + if (isEmpty) Nil.head + else locationAfterN(length - 1) + override def init: NumericRange[T] = + if (isEmpty) Nil.init + else new NumericRange(start, end - step, step, isInclusive) + + override def head: T = if (isEmpty) Nil.head else start + override def tail: NumericRange[T] = + if (isEmpty) Nil.tail + else if(isInclusive) new NumericRange.Inclusive(start + step, end, step) + else new NumericRange.Exclusive(start + step, end, step) + + /** Create a new range with the start and end values of this range and + * a new `step`. + */ + def by(newStep: T): NumericRange[T] = copy(start, end, newStep) + + + /** Create a copy of this range. + */ + def copy(start: T, end: T, step: T): NumericRange[T] = + new NumericRange(start, end, step, isInclusive) + + @throws[IndexOutOfBoundsException] + def apply(idx: Int): T = { + if (idx < 0 || idx >= length) + throw CommonErrors.indexOutOfBounds(index = idx, max = length - 1) + else locationAfterN(idx) + } + + override def foreach[@specialized(Specializable.Unit) U](f: T => U): Unit = { + var count = 0 + var current = start + while (count < length) { + f(current) + current += step + count += 1 + } + } + + private[this] def indexOfTyped(elem: T, from: Int): Int = + posOf(elem) match { + case pos if pos >= from => pos + case _ => -1 + } + + final override def indexOf[B >: T](elem: B, from: Int): Int = + try indexOfTyped(elem.asInstanceOf[T], from) + catch { case _: ClassCastException => super.indexOf(elem, from) } + + private[this] def lastIndexOfTyped(elem: T, end: Int): Int = + posOf(elem) match { + case pos if pos <= end => pos + case _ => -1 + } + + final override def lastIndexOf[B >: T](elem: B, end: Int = length - 1): Int = + try lastIndexOfTyped(elem.asInstanceOf[T], end) + catch { case _: ClassCastException => super.lastIndexOf(elem, end) } + + private[this] def posOf(i: T): Int = + /* + If i is in this NumericRange, its position can simply be calculated by taking the amount of values up till i. + NumericRange.count does this in an most efficient manner. + Note that the contains() method throws an exception if the range has more than Int.MaxValue elements, but so did + the original indexOf / lastIndexOf functions, so no functionality changed. */ + if (contains(i)) { + /* Because of zero indexing, the count is always one higher than the index. This can be simply solved by setting + isInclusive = false. */ + NumericRange.count(this.start, i, this.step, isInclusive = false) + } else -1 + + // TODO: these private methods are straight copies from Range, duplicated + // to guard against any (most likely illusory) performance drop. They should + // be eliminated one way or another. + + // Tests whether a number is within the endpoints, without testing + // whether it is a member of the sequence (i.e. when step > 1.) + private def isWithinBoundaries(elem: T) = !isEmpty && ( + (step > zero && start <= elem && elem <= last ) || + (step < zero && last <= elem && elem <= start) + ) + // Methods like apply throw exceptions on invalid n, but methods like take/drop + // are forgiving: therefore the checks are with the methods. + private def locationAfterN(n: Int): T = start + (step * fromInt(n)) + + private def crossesTheEndAfterN(n: Int): Boolean = { + // if we're sure that subtraction in the context of T won't overflow, we use this function + // to calculate the length of the range + def unsafeRangeLength(r: NumericRange[T]): T = { + val diff = num.minus(r.end, r.start) + val quotient = num.quot(diff, r.step) + val remainder = num.rem(diff, r.step) + if (!r.isInclusive && num.equiv(remainder, num.zero)) + num.max(quotient, num.zero) + else + num.max(num.plus(quotient, num.one), num.zero) + } + + // detects whether value can survive a bidirectional trip to -and then from- Int. + def fitsInInteger(value: T): Boolean = num.equiv(num.fromInt(num.toInt(value)), value) + + val stepIsInTheSameDirectionAsStartToEndVector = + (num.gt(end, start) && num.gt(step, num.zero)) || (num.lt(end, start) && num.sign(step) == -num.one) + + if (num.equiv(start, end) || n <= 0 || !stepIsInTheSameDirectionAsStartToEndVector) return n >= 1 + + val sameSign = num.equiv(num.sign(start), num.sign(end)) + + if (sameSign) { // subtraction is safe + val len = unsafeRangeLength(this) + if (fitsInInteger(len)) n >= num.toInt(len) else num.gteq(num.fromInt(n), len) + } else { + // split to two ranges, which subtraction is safe in both of them (around zero) + val stepsRemainderToZero = num.rem(start, step) + val walksOnZero = num.equiv(stepsRemainderToZero, num.zero) + val closestToZero = if (walksOnZero) -step else stepsRemainderToZero + + /* + When splitting into two ranges, we should be super-careful about one of the sides hitting MinValue of T, + so we take two steps smaller than zero to ensure unsafeRangeLength won't overflow (taking one step may overflow depending on the step). + Same thing happens for MaxValue from zero, so we take one step further to ensure the safety of unsafeRangeLength. + After performing such operation, there are some elements remaining in between and around zero, + which their length is represented by carry. + */ + val (l: NumericRange[T], r: NumericRange[T], carry: Int) = + if (num.lt(start, num.zero)) { + if (walksOnZero) { + val twoStepsAfterLargestNegativeNumber = num.plus(closestToZero, num.times(step, num.fromInt(2))) + (NumericRange(start, closestToZero, step), copy(twoStepsAfterLargestNegativeNumber, end, step), 2) + } else { + (NumericRange(start, closestToZero, step), copy(num.plus(closestToZero, step), end, step), 1) + } + } else { + if (walksOnZero) { + val twoStepsAfterZero = num.times(step, num.fromInt(2)) + (copy(twoStepsAfterZero, end, step), NumericRange.inclusive(start, -step, step), 2) + } else { + val twoStepsAfterSmallestPositiveNumber = num.plus(closestToZero, num.times(step, num.fromInt(2))) + (copy(twoStepsAfterSmallestPositiveNumber, end, step), NumericRange.inclusive(start, closestToZero, step), 2) + } + } + + val leftLength = unsafeRangeLength(l) + val rightLength = unsafeRangeLength(r) + + // instead of `n >= rightLength + leftLength + curry` which may cause addition overflow, + // this can be used `(n - leftLength - curry) >= rightLength` (Both in Int and T, depends on whether the lengths fit in Int) + if (fitsInInteger(leftLength) && fitsInInteger(rightLength)) + n - num.toInt(leftLength) - carry >= num.toInt(rightLength) + else + num.gteq(num.minus(num.minus(num.fromInt(n), leftLength), num.fromInt(carry)), rightLength) + } + } + + // When one drops everything. Can't ever have unchecked operations + // like "end + 1" or "end - 1" because ranges involving Int.{ MinValue, MaxValue } + // will overflow. This creates an exclusive range where start == end + // based on the given value. + private def newEmptyRange(value: T) = NumericRange(value, value, step) + + override def take(n: Int): NumericRange[T] = { + if (n <= 0 || isEmpty) newEmptyRange(start) + else if (crossesTheEndAfterN(n)) this + else new NumericRange.Inclusive(start, locationAfterN(n - 1), step) + } + + override def drop(n: Int): NumericRange[T] = { + if (n <= 0 || isEmpty) this + else if (crossesTheEndAfterN(n)) newEmptyRange(end) + else copy(locationAfterN(n), end, step) + } + + override def splitAt(n: Int): (NumericRange[T], NumericRange[T]) = (take(n), drop(n)) + + override def reverse: NumericRange[T] = + if (isEmpty) this + else { + val newStep = -step + if (num.sign(newStep) == num.sign(step)) { + throw new ArithmeticException("number type is unsigned, and .reverse requires a negative step") + } else new NumericRange.Inclusive(last, start, newStep) + } + + import NumericRange.defaultOrdering + + override def min[T1 >: T](implicit ord: Ordering[T1]): T = + // We can take the fast path: + // - If the Integral of this NumericRange is also the requested Ordering + // (Integral <: Ordering). This can happen for custom Integral types. + // - The Ordering is the default Ordering of a well-known Integral type. + if ((ord eq num) || defaultOrdering.get(num).exists(ord eq _)) { + if (num.sign(step) > zero) head + else last + } else super.min(ord) + + override def max[T1 >: T](implicit ord: Ordering[T1]): T = + // See comment for fast path in min(). + if ((ord eq num) || defaultOrdering.get(num).exists(ord eq _)) { + if (num.sign(step) > zero) last + else head + } else super.max(ord) + + // a well-typed contains method. + def containsTyped(x: T): Boolean = + isWithinBoundaries(x) && (((x - start) % step) == zero) + + override def contains[A1 >: T](x: A1): Boolean = + try containsTyped(x.asInstanceOf[T]) + catch { case _: ClassCastException => false } + + override def sum[B >: T](implicit num: Numeric[B]): B = { + if (isEmpty) num.zero + else if (size == 1) head + else { + // If there is no overflow, use arithmetic series formula + // a + ... (n terms total) ... + b = n*(a+b)/2 + if ((num eq scala.math.Numeric.IntIsIntegral)|| + (num eq scala.math.Numeric.ShortIsIntegral)|| + (num eq scala.math.Numeric.ByteIsIntegral)|| + (num eq scala.math.Numeric.CharIsIntegral)) { + // We can do math with no overflow in a Long--easy + val exact = (size * ((num toLong head) + (num toInt last))) / 2 + num fromInt exact.toInt + } + else if (num eq scala.math.Numeric.LongIsIntegral) { + // Uh-oh, might be overflow, so we have to divide before we overflow. + // Either numRangeElements or (head + last) must be even, so divide the even one before multiplying + val a = head.toLong + val b = last.toLong + val ans = + if ((size & 1) == 0) (size / 2) * (a + b) + else size * { + // Sum is even, but we might overflow it, so divide in pieces and add back remainder + val ha = a/2 + val hb = b/2 + ha + hb + ((a - 2*ha) + (b - 2*hb)) / 2 + } + ans.asInstanceOf[B] + } + else if ((num eq scala.math.Numeric.BigIntIsIntegral) || + (num eq scala.math.Numeric.BigDecimalAsIfIntegral)) { + // No overflow, so we can use arithmetic series formula directly + // (not going to worry about running out of memory) + val numAsIntegral = num.asInstanceOf[Integral[B]] + import numAsIntegral._ + ((num fromInt size) * (head + last)) / (num fromInt 2) + } + else { + // User provided custom Numeric, so we cannot rely on arithmetic series formula (e.g. won't work on something like Z_6) + if (isEmpty) num.zero + else { + var acc = num.zero + var i = head + var idx = 0 + while(idx < length) { + acc = num.plus(acc, i) + i = i + step + idx = idx + 1 + } + acc + } + } + } + } + + override lazy val hashCode: Int = super.hashCode() + override protected final def applyPreferredMaxLength: Int = Int.MaxValue + + override def equals(other: Any): Boolean = other match { + case x: NumericRange[_] => + (x canEqual this) && (length == x.length) && ( + (isEmpty) || // all empty sequences are equal + (start == x.start && last == x.last) // same length and same endpoints implies equality + ) + case _ => + super.equals(other) + } + + override def toString: String = { + val empty = if (isEmpty) "empty " else "" + val preposition = if (isInclusive) "to" else "until" + val stepped = if (step == 1) "" else s" by $step" + s"${empty}NumericRange $start $preposition $end$stepped" + } + + override protected[this] def className = "NumericRange" +} + +/** A companion object for numeric ranges. + * @define Coll `NumericRange` + * @define coll numeric range + */ +object NumericRange { + private def bigDecimalCheckUnderflow[T](start: T, end: T, step: T)(implicit num: Integral[T]): Unit = { + def FAIL(boundary: T, step: T): Unit = { + val msg = boundary match { + case bd: BigDecimal => s"Precision ${bd.mc.getPrecision}" + case _ => "Precision" + } + throw new IllegalArgumentException( + s"$msg inadequate to represent steps of size $step near $boundary" + ) + } + if (num.minus(num.plus(start, step), start) != step) FAIL(start, step) + if (num.minus(end, num.minus(end, step)) != step) FAIL(end, step) + } + + /** Calculates the number of elements in a range given start, end, step, and + * whether or not it is inclusive. Throws an exception if step == 0 or + * the number of elements exceeds the maximum Int. + */ + def count[T](start: T, end: T, step: T, isInclusive: Boolean)(implicit num: Integral[T]): Int = { + val zero = num.zero + val upward = num.lt(start, end) + val posStep = num.gt(step, zero) + + if (step == zero) throw new IllegalArgumentException("step cannot be 0.") + else if (start == end) if (isInclusive) 1 else 0 + else if (upward != posStep) 0 + else { + /* We have to be frightfully paranoid about running out of range. + * We also can't assume that the numbers will fit in a Long. + * We will assume that if a > 0, -a can be represented, and if + * a < 0, -a+1 can be represented. We also assume that if we + * can't fit in Int, we can represent 2*Int.MaxValue+3 (at least). + * And we assume that numbers wrap rather than cap when they overflow. + */ + // Check whether we can short-circuit by deferring to Int range. + val startint = num.toInt(start) + if (start == num.fromInt(startint)) { + val endint = num.toInt(end) + if (end == num.fromInt(endint)) { + val stepint = num.toInt(step) + if (step == num.fromInt(stepint)) { + return { + if (isInclusive) Range.inclusive(startint, endint, stepint).length + else Range (startint, endint, stepint).length + } + } + } + } + // If we reach this point, deferring to Int failed. + // Numbers may be big. + if (num.isInstanceOf[Numeric.BigDecimalAsIfIntegral]) { + bigDecimalCheckUnderflow(start, end, step) // Throw exception if math is inaccurate (including no progress at all) + } + val one = num.one + val limit = num.fromInt(Int.MaxValue) + def check(t: T): T = + if (num.gt(t, limit)) throw new IllegalArgumentException("More than Int.MaxValue elements.") + else t + // If the range crosses zero, it might overflow when subtracted + val startside = num.sign(start) + val endside = num.sign(end) + num.toInt{ + if (num.gteq(num.times(startside, endside), zero)) { + // We're sure we can subtract these numbers. + // Note that we do not use .rem because of different conventions for Long and BigInt + val diff = num.minus(end, start) + val quotient = check(num.quot(diff, step)) + val remainder = num.minus(diff, num.times(quotient, step)) + if (!isInclusive && zero == remainder) quotient else check(num.plus(quotient, one)) + } + else { + // We might not even be able to subtract these numbers. + // Jump in three pieces: + // * start to -1 or 1, whichever is closer (waypointA) + // * one step, which will take us at least to 0 (ends at waypointB) + // * (except with really small numbers) + // * there to the end + val negone = num.fromInt(-1) + val startlim = if (posStep) negone else one + //Use start value if the start value is closer to zero than startlim + // * e.g. .5 is closer to zero than 1 and -.5 is closer to zero than -1 + val startdiff = { + if ((posStep && num.lt(startlim, start)) || (!posStep && num.gt(startlim, start))) + start + else + num.minus(startlim, start) + } + val startq = check(num.quot(startdiff, step)) + val waypointA = if (startq == zero) start else num.plus(start, num.times(startq, step)) + val waypointB = num.plus(waypointA, step) + check { + if (num.lt(waypointB, end) != upward) { + // No last piece + if (isInclusive && waypointB == end) num.plus(startq, num.fromInt(2)) + else num.plus(startq, one) + } + else { + // There is a last piece + val enddiff = num.minus(end,waypointB) + val endq = check(num.quot(enddiff, step)) + val last = if (endq == zero) waypointB else num.plus(waypointB, num.times(endq, step)) + // Now we have to tally up all the pieces + // 1 for the initial value + // startq steps to waypointA + // 1 step to waypointB + // endq steps to the end (one less if !isInclusive and last==end) + num.plus(startq, num.plus(endq, if (!isInclusive && last==end) one else num.fromInt(2))) + } + } + } + } + } + } + + @SerialVersionUID(3L) + class Inclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]) + extends NumericRange(start, end, step, isInclusive = true) { + override def copy(start: T, end: T, step: T): Inclusive[T] = + NumericRange.inclusive(start, end, step) + + def exclusive: Exclusive[T] = NumericRange(start, end, step) + } + + @SerialVersionUID(3L) + class Exclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]) + extends NumericRange(start, end, step, isInclusive = false) { + override def copy(start: T, end: T, step: T): Exclusive[T] = + NumericRange(start, end, step) + + def inclusive: Inclusive[T] = NumericRange.inclusive(start, end, step) + } + + def apply[T](start: T, end: T, step: T)(implicit num: Integral[T]): Exclusive[T] = + new Exclusive(start, end, step) + def inclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]): Inclusive[T] = + new Inclusive(start, end, step) + + private[collection] val defaultOrdering = Map[Numeric[_], Ordering[_]]( + Numeric.BigIntIsIntegral -> Ordering.BigInt, + Numeric.IntIsIntegral -> Ordering.Int, + Numeric.ShortIsIntegral -> Ordering.Short, + Numeric.ByteIsIntegral -> Ordering.Byte, + Numeric.CharIsIntegral -> Ordering.Char, + Numeric.LongIsIntegral -> Ordering.Long, + Numeric.BigDecimalAsIfIntegral -> Ordering.BigDecimal + ) + + @SerialVersionUID(3L) + private final class NumericRangeIterator[T](self: NumericRange[T], num: Integral[T]) extends AbstractIterator[T] with Serializable { + import num.mkNumericOps + + private[this] var _hasNext = !self.isEmpty + private[this] var _next: T = self.start + private[this] val lastElement: T = if (_hasNext) self.last else self.start + override def knownSize: Int = if (_hasNext) num.toInt((lastElement - _next) / self.step) + 1 else 0 + def hasNext: Boolean = _hasNext + def next(): T = { + if (!_hasNext) Iterator.empty.next() + val value = _next + _hasNext = value != lastElement + _next = num.plus(value, self.step) + value + } + } +} diff --git a/library/src/scala/collection/immutable/Queue.scala b/library/src/scala/collection/immutable/Queue.scala new file mode 100644 index 000000000000..6deaabc5c7f7 --- /dev/null +++ b/library/src/scala/collection/immutable/Queue.scala @@ -0,0 +1,218 @@ +/* + * 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.collection +package immutable + +import scala.language.`2.13` +import scala.collection.generic.DefaultSerializable +import scala.collection.mutable.{Builder, ListBuffer} + +/** `Queue` objects implement data structures that allow to + * insert and retrieve elements in a first-in-first-out (FIFO) manner. + * + * `Queue` is implemented as a pair of `List`s, one containing the ''in'' elements and the other the ''out'' elements. + * Elements are added to the ''in'' list and removed from the ''out'' list. When the ''out'' list runs dry, the + * queue is pivoted by replacing the ''out'' list by ''in.reverse'', and ''in'' by ''Nil''. + * + * Adding items to the queue always has cost `O(1)`. Removing items has cost `O(1)`, except in the case + * where a pivot is required, in which case, a cost of `O(n)` is incurred, where `n` is the number of elements in the queue. When this happens, + * `n` remove operations with `O(1)` cost are guaranteed. Removing an item is on average `O(1)`. + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html#immutable-queues "Scala's Collection Library overview"]] + * section on `Immutable Queues` for more information. + * + * @define Coll `immutable.Queue` + * @define coll immutable queue + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ + +sealed class Queue[+A] protected(protected val in: List[A], protected val out: List[A]) + extends AbstractSeq[A] + with LinearSeq[A] + with LinearSeqOps[A, Queue, Queue[A]] + with StrictOptimizedLinearSeqOps[A, Queue, Queue[A]] + with StrictOptimizedSeqOps[A, Queue, Queue[A]] + with IterableFactoryDefaults[A, Queue] + with DefaultSerializable { + + override def iterableFactory: SeqFactory[Queue] = Queue + + /** Returns the `n`-th element of this queue. + * The first element is at position `0`. + * + * @param n index of the element to return + * @return the element at position `n` in this queue. + * @throws NoSuchElementException if the queue is too short. + */ + override def apply(n: Int): A = { + def indexOutOfRange(): Nothing = throw new IndexOutOfBoundsException(n.toString) + + var index = 0 + var curr = out + + while (index < n && curr.nonEmpty) { + index += 1 + curr = curr.tail + } + + if (index == n) { + if (curr.nonEmpty) curr.head + else if (in.nonEmpty) in.last + else indexOutOfRange() + } else { + val indexFromBack = n - index + val inLength = in.length + if (indexFromBack >= inLength) indexOutOfRange() + else in(inLength - indexFromBack - 1) + } + } + + /** Returns the elements in the list as an iterator + */ + override def iterator: Iterator[A] = out.iterator.concat(in.reverse) + + /** Checks if the queue is empty. + * + * @return true, iff there is no element in the queue. + */ + override def isEmpty: Boolean = in.isEmpty && out.isEmpty + + override def head: A = + if (out.nonEmpty) out.head + else if (in.nonEmpty) in.last + else throw new NoSuchElementException("head on empty queue") + + override def tail: Queue[A] = + if (out.nonEmpty) new Queue(in, out.tail) + else if (in.nonEmpty) new Queue(Nil, in.reverse.tail) + else throw new NoSuchElementException("tail on empty queue") + + override def last: A = + if (in.nonEmpty) in.head + else if (out.nonEmpty) out.last + else throw new NoSuchElementException("last on empty queue") + + /* This is made to avoid inefficient implementation of iterator. */ + override def forall(p: A => Boolean): Boolean = + in.forall(p) && out.forall(p) + + /* This is made to avoid inefficient implementation of iterator. */ + override def exists(p: A => Boolean): Boolean = + in.exists(p) || out.exists(p) + + override protected[this] def className = "Queue" + + /** Returns the length of the queue. */ + override def length: Int = in.length + out.length + + override def prepended[B >: A](elem: B): Queue[B] = new Queue(in, elem :: out) + + override def appended[B >: A](elem: B): Queue[B] = enqueue(elem) + + override def appendedAll[B >: A](that: scala.collection.IterableOnce[B]): Queue[B] = { + val newIn = that match { + case that: Queue[B] => that.in ++ (that.out reverse_::: this.in) + case that: List[B] => that reverse_::: this.in + case _ => + var result: List[B] = this.in + val iter = that.iterator + while (iter.hasNext) { + result = iter.next() :: result + } + result + } + if (newIn eq this.in) this else new Queue[B](newIn, this.out) + } + + /** Creates a new queue with element added at the end + * of the old queue. + * + * @param elem the element to insert + */ + def enqueue[B >: A](elem: B): Queue[B] = new Queue(elem :: in, out) + + /** Creates a new queue with all elements provided by an `Iterable` object + * added at the end of the old queue. + * + * The elements are appended in the order they are given out by the + * iterator. + * + * @param iter an iterable object + */ + @deprecated("Use `enqueueAll` instead of `enqueue` to enqueue a collection of elements", "2.13.0") + @`inline` final def enqueue[B >: A](iter: scala.collection.Iterable[B]) = enqueueAll(iter) + + /** Creates a new queue with all elements provided by an `Iterable` object + * added at the end of the old queue. + * + * The elements are appended in the order they are given out by the + * iterator. + * + * @param iter an iterable object + */ + def enqueueAll[B >: A](iter: scala.collection.Iterable[B]): Queue[B] = appendedAll(iter) + + /** Returns a tuple with the first element in the queue, + * and a new queue with this element removed. + * + * @return the first element of the queue. + * @throws NoSuchElementException if the queue is empty + */ + def dequeue: (A, Queue[A]) = out match { + case Nil if !in.isEmpty => val rev = in.reverse ; (rev.head, new Queue(Nil, rev.tail)) + case x :: xs => (x, new Queue(in, xs)) + case _ => throw new NoSuchElementException("dequeue on empty queue") + } + + /** Optionally retrieves the first element and a queue of the remaining elements. + * + * @return A tuple of the first element of the queue, and a new queue with this element removed. + * If the queue is empty, `None` is returned. + */ + def dequeueOption: Option[(A, Queue[A])] = if(isEmpty) None else Some(dequeue) + + /** Returns the first element in the queue, or throws an error if there + * is no element contained in the queue. + * + * @throws NoSuchElementException if the queue is empty + * @return the first element. + */ + def front: A = head + + /** Returns a string representation of this queue. + */ + override def toString(): String = mkString("Queue(", ", ", ")") +} + +/** $factoryInfo + * @define Coll `immutable.Queue` + * @define coll immutable queue + */ +@SerialVersionUID(3L) +object Queue extends StrictOptimizedSeqFactory[Queue] { + def newBuilder[A]: Builder[A, Queue[A]] = new ListBuffer[A] mapResult (x => new Queue[A](Nil, x)) + + def from[A](source: IterableOnce[A]): Queue[A] = source match { + case q: Queue[A] => q + case _ => + val list = List.from(source) + if (list.isEmpty) empty + else new Queue(Nil, list) + } + + def empty[A]: Queue[A] = EmptyQueue + override def apply[A](xs: A*): Queue[A] = new Queue[A](Nil, xs.toList) + + private object EmptyQueue extends Queue[Nothing](Nil, Nil) { } +} diff --git a/library/src/scala/collection/immutable/Range.scala b/library/src/scala/collection/immutable/Range.scala new file mode 100644 index 000000000000..5fd0490596d7 --- /dev/null +++ b/library/src/scala/collection/immutable/Range.scala @@ -0,0 +1,672 @@ +/* + * 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 collection.immutable + +import scala.language.`2.13` +import scala.collection.Stepper.EfficientSplit +import scala.collection.convert.impl.RangeStepper +import scala.collection.generic.CommonErrors +import scala.collection.{AbstractIterator, AnyStepper, IterableFactoryDefaults, Iterator, Stepper, StepperShape} +import scala.util.hashing.MurmurHash3 + +/** The `Range` class represents integer values in range + * ''[start;end)'' with non-zero step value `step`. + * It's a special case of an indexed sequence. + * For example: + * + * {{{ + * val r1 = 0 until 10 + * val r2 = r1.start until r1.end by r1.step + 1 + * println(r2.length) // = 5 + * }}} + * + * Ranges that contain more than `Int.MaxValue` elements can be created, but + * these overfull ranges have only limited capabilities. Any method that + * could require a collection of over `Int.MaxValue` length to be created, or + * could be asked to index beyond `Int.MaxValue` elements will throw an + * exception. Overfull ranges can safely be reduced in size by changing + * the step size (e.g. `by 3`) or taking/dropping elements. `contains`, + * `equals`, and access to the ends of the range (`head`, `last`, `tail`, + * `init`) are also permitted on overfull ranges. + * + * @param start the start of this range. + * @param end the end of the range. For exclusive ranges, e.g. + * `Range(0,3)` or `(0 until 3)`, this is one + * step past the last one in the range. For inclusive + * ranges, e.g. `Range.inclusive(0,3)` or `(0 to 3)`, + * it may be in the range if it is not skipped by the step size. + * To find the last element inside a non-empty range, + * use `last` instead. + * @param step the step for the range. + * + * @define coll range + * @define ccoll indexed sequence + * @define mayNotTerminateInf + * @define willNotTerminateInf + * @define doesNotUseBuilders + * '''Note:''' this method does not use builders to construct a new range, + * and its complexity is O(1). + */ +@SerialVersionUID(3L) +sealed abstract class Range( + val start: Int, + val end: Int, + val step: Int +) + extends AbstractSeq[Int] + with IndexedSeq[Int] + with IndexedSeqOps[Int, IndexedSeq, IndexedSeq[Int]] + with StrictOptimizedSeqOps[Int, IndexedSeq, IndexedSeq[Int]] + with IterableFactoryDefaults[Int, IndexedSeq] + with Serializable { range => + + final override def iterator: Iterator[Int] = new RangeIterator(start, step, lastElement, isEmpty) + + override final def stepper[S <: Stepper[_]](implicit shape: StepperShape[Int, S]): S with EfficientSplit = { + val st = new RangeStepper(start, step, 0, length) + val r = + if (shape.shape == StepperShape.IntShape) st + else { + assert(shape.shape == StepperShape.ReferenceShape, s"unexpected StepperShape: $shape") + AnyStepper.ofParIntStepper(st) + } + r.asInstanceOf[S with EfficientSplit] + } + + private[this] def gap = end.toLong - start.toLong + private[this] def isExact = gap % step == 0 + private[this] def hasStub = isInclusive || !isExact + private[this] def longLength = gap / step + ( if (hasStub) 1 else 0 ) + + def isInclusive: Boolean + + final override val isEmpty: Boolean = ( + (start > end && step > 0) + || (start < end && step < 0) + || (start == end && !isInclusive) + ) + + private[this] val numRangeElements: Int = { + if (step == 0) throw new IllegalArgumentException("step cannot be 0.") + else if (isEmpty) 0 + else { + val len = longLength + if (len > scala.Int.MaxValue) -1 + else len.toInt + } + } + + final def length = if (numRangeElements < 0) fail() else numRangeElements + + // This field has a sensible value only for non-empty ranges + private[this] val lastElement = step match { + case 1 => if (isInclusive) end else end-1 + case -1 => if (isInclusive) end else end+1 + case _ => + val remainder = (gap % step).toInt + if (remainder != 0) end - remainder + else if (isInclusive) end + else end - step + } + + /** The last element of this range. This method will return the correct value + * even if there are too many elements to iterate over. + */ + final override def last: Int = + if (isEmpty) throw Range.emptyRangeError("last") else lastElement + final override def head: Int = + if (isEmpty) throw Range.emptyRangeError("head") else start + + /** Creates a new range containing all the elements of this range except the last one. + * + * $doesNotUseBuilders + * + * @return a new range consisting of all the elements of this range except the last one. + */ + final override def init: Range = + if (isEmpty) throw Range.emptyRangeError("init") else dropRight(1) + + /** Creates a new range containing all the elements of this range except the first one. + * + * $doesNotUseBuilders + * + * @return a new range consisting of all the elements of this range except the first one. + */ + final override def tail: Range = { + if (isEmpty) throw Range.emptyRangeError("tail") + if (numRangeElements == 1) newEmptyRange(end) + else if(isInclusive) new Range.Inclusive(start + step, end, step) + else new Range.Exclusive(start + step, end, step) + } + + override def map[B](f: Int => B): IndexedSeq[B] = { + validateMaxLength() + super.map(f) + } + + final protected def copy(start: Int = start, end: Int = end, step: Int = step, isInclusive: Boolean = isInclusive): Range = + if(isInclusive) new Range.Inclusive(start, end, step) else new Range.Exclusive(start, end, step) + + /** Create a new range with the `start` and `end` values of this range and + * a new `step`. + * + * @return a new range with a different step + */ + final def by(step: Int): Range = copy(start, end, step) + + // Check cannot be evaluated eagerly because we have a pattern where + // ranges are constructed like: "x to y by z" The "x to y" piece + // should not trigger an exception. So the calculation is delayed, + // which means it will not fail fast for those cases where failing was + // correct. + private[this] def validateMaxLength(): Unit = { + if (numRangeElements < 0) + fail() + } + private[this] def description = "%d %s %d by %s".format(start, if (isInclusive) "to" else "until", end, step) + private[this] def fail() = throw new IllegalArgumentException(description + ": seqs cannot contain more than Int.MaxValue elements.") + + @throws[IndexOutOfBoundsException] + final def apply(idx: Int): Int = { + validateMaxLength() + if (idx < 0 || idx >= numRangeElements) + throw CommonErrors.indexOutOfBounds(index = idx, max = numRangeElements - 1) + else start + (step * idx) + } + + /*@`inline`*/ final override def foreach[@specialized(Unit) U](f: Int => U): Unit = { + // Implementation chosen on the basis of favorable microbenchmarks + // Note--initialization catches step == 0 so we don't need to here + if (!isEmpty) { + var i = start + while (true) { + f(i) + if (i == lastElement) return + i += step + } + } + } + + override final def indexOf[@specialized(Int) B >: Int](elem: B, from: Int = 0): Int = + elem match { + case i: Int => + val pos = posOf(i) + if (pos >= from) pos else -1 + case _ => super.indexOf(elem, from) + } + + override final def lastIndexOf[@specialized(Int) B >: Int](elem: B, end: Int = length - 1): Int = + elem match { + case i: Int => + val pos = posOf(i) + if (pos <= end) pos else -1 + case _ => super.lastIndexOf(elem, end) + } + + private[this] def posOf(i: Int): Int = + if (contains(i)) (i - start) / step else -1 + + override def sameElements[B >: Int](that: IterableOnce[B]): Boolean = that match { + case other: Range => + (this.length : @annotation.switch) match { + case 0 => other.isEmpty + case 1 => other.length == 1 && this.start == other.start + case n => other.length == n && ( + (this.start == other.start) + && (this.step == other.step) + ) + } + case _ => super.sameElements(that) + } + + /** Creates a new range containing the first `n` elements of this range. + * + * @param n the number of elements to take. + * @return a new range consisting of `n` first elements. + */ + final override def take(n: Int): Range = + if (n <= 0 || isEmpty) newEmptyRange(start) + else if (n >= numRangeElements && numRangeElements >= 0) this + else { + // May have more than Int.MaxValue elements in range (numRangeElements < 0) + // but the logic is the same either way: take the first n + new Range.Inclusive(start, locationAfterN(n - 1), step) + } + + /** Creates a new range containing all the elements of this range except the first `n` elements. + * + * @param n the number of elements to drop. + * @return a new range consisting of all the elements of this range except `n` first elements. + */ + final override def drop(n: Int): Range = + if (n <= 0 || isEmpty) this + else if (n >= numRangeElements && numRangeElements >= 0) newEmptyRange(end) + else { + // May have more than Int.MaxValue elements (numRangeElements < 0) + // but the logic is the same either way: go forwards n steps, keep the rest + copy(locationAfterN(n), end, step) + } + + /** Creates a new range consisting of the last `n` elements of the range. + * + * $doesNotUseBuilders + */ + final override def takeRight(n: Int): Range = { + if (n <= 0) newEmptyRange(start) + else if (numRangeElements >= 0) drop(numRangeElements - n) + else { + // Need to handle over-full range separately + val y = last + val x = y - step.toLong*(n-1) + if ((step > 0 && x < start) || (step < 0 && x > start)) this + else Range.inclusive(x.toInt, y, step) + } + } + + /** Creates a new range consisting of the initial `length - n` elements of the range. + * + * $doesNotUseBuilders + */ + final override def dropRight(n: Int): Range = { + if (n <= 0) this + else if (numRangeElements >= 0) take(numRangeElements - n) + else { + // Need to handle over-full range separately + val y = last - step.toInt*n + if ((step > 0 && y < start) || (step < 0 && y > start)) newEmptyRange(start) + else Range.inclusive(start, y.toInt, step) + } + } + + // Advance from the start while we meet the given test + private[this] def argTakeWhile(p: Int => Boolean): Long = { + if (isEmpty) start + else { + var current = start + val stop = last + while (current != stop && p(current)) current += step + if (current != stop || !p(current)) current + else current.toLong + step + } + } + + final override def takeWhile(p: Int => Boolean): Range = { + val stop = argTakeWhile(p) + if (stop==start) newEmptyRange(start) + else { + val x = (stop - step).toInt + if (x == last) this + else Range.inclusive(start, x, step) + } + } + + final override def dropWhile(p: Int => Boolean): Range = { + val stop = argTakeWhile(p) + if (stop == start) this + else { + val x = (stop - step).toInt + if (x == last) newEmptyRange(last) + else Range.inclusive(x + step, last, step) + } + } + + final override def span(p: Int => Boolean): (Range, Range) = { + val border = argTakeWhile(p) + if (border == start) (newEmptyRange(start), this) + else { + val x = (border - step).toInt + if (x == last) (this, newEmptyRange(last)) + else (Range.inclusive(start, x, step), Range.inclusive(x+step, last, step)) + } + } + + /** Creates a new range containing the elements starting at `from` up to but not including `until`. + * + * $doesNotUseBuilders + * + * @param from the element at which to start + * @param until the element at which to end (not included in the range) + * @return a new range consisting of a contiguous interval of values in the old range + */ + final override def slice(from: Int, until: Int): Range = + if (from <= 0) take(until) + else if (until >= numRangeElements && numRangeElements >= 0) drop(from) + else { + val fromValue = locationAfterN(from) + if (from >= until) newEmptyRange(fromValue) + else Range.inclusive(fromValue, locationAfterN(until-1), step) + } + + // Overridden only to refine the return type + final override def splitAt(n: Int): (Range, Range) = (take(n), drop(n)) + + // Methods like apply throw exceptions on invalid n, but methods like take/drop + // are forgiving: therefore the checks are with the methods. + private[this] def locationAfterN(n: Int) = start + (step * n) + + // When one drops everything. Can't ever have unchecked operations + // like "end + 1" or "end - 1" because ranges involving Int.{ MinValue, MaxValue } + // will overflow. This creates an exclusive range where start == end + // based on the given value. + private[this] def newEmptyRange(value: Int) = new Range.Exclusive(value, value, step) + + /** Returns the reverse of this range. + */ + final override def reverse: Range = + if (isEmpty) this + else new Range.Inclusive(last, start, -step) + + /** Make range inclusive. + */ + final def inclusive: Range = + if (isInclusive) this + else new Range.Inclusive(start, end, step) + + final def contains(x: Int): Boolean = { + if (x == end && !isInclusive) false + else if (step > 0) { + if (x < start || x > end) false + else (step == 1) || (Integer.remainderUnsigned(x - start, step) == 0) + } + else { + if (x < end || x > start) false + else (step == -1) || (Integer.remainderUnsigned(start - x, -step) == 0) + } + } + /* Seq#contains has a type parameter so the optimised contains above doesn't override it */ + override final def contains[B >: Int](elem: B): Boolean = elem match { + case i: Int => this.contains(i) + case _ => super.contains(elem) + } + + final override def sum[B >: Int](implicit num: Numeric[B]): Int = { + if (num eq scala.math.Numeric.IntIsIntegral) { + // this is normal integer range with usual addition. arithmetic series formula can be used + if (isEmpty) 0 + else if (size == 1) head + else ((size * (head.toLong + last)) / 2).toInt + } else { + // user provided custom Numeric, we cannot rely on arithmetic series formula + if (isEmpty) num.toInt(num.zero) + else { + var acc = num.zero + var i = head + while (true) { + acc = num.plus(acc, i) + if (i == lastElement) return num.toInt(acc) + i = i + step + } + 0 // Never hit this--just to satisfy compiler since it doesn't know while(true) has type Nothing + } + } + } + + final override def min[A1 >: Int](implicit ord: Ordering[A1]): Int = + if (ord eq Ordering.Int) { + if (step > 0) head + else last + } else if (Ordering.Int isReverseOf ord) { + if (step > 0) last + else head + } else super.min(ord) + + final override def max[A1 >: Int](implicit ord: Ordering[A1]): Int = + if (ord eq Ordering.Int) { + if (step > 0) last + else head + } else if (Ordering.Int isReverseOf ord) { + if (step > 0) head + else last + } else super.max(ord) + + override def tails: Iterator[Range] = + new AbstractIterator[Range] { + private[this] var i = 0 + override def hasNext = i <= Range.this.length + override def next() = { + if (hasNext) { + val res = Range.this.drop(i) + i += 1 + res + } else { + Iterator.empty.next() + } + } + } + + override def inits: Iterator[Range] = + new AbstractIterator[Range] { + private[this] var i = 0 + override def hasNext = i <= Range.this.length + override def next() = { + if (hasNext) { + val res = Range.this.dropRight(i) + i += 1 + res + } else { + Iterator.empty.next() + } + } + } + override protected final def applyPreferredMaxLength: Int = Int.MaxValue + + final override def equals(other: Any): Boolean = other match { + case x: Range => + // Note: this must succeed for overfull ranges (length > Int.MaxValue) + if (isEmpty) x.isEmpty // empty sequences are equal + else // this is non-empty... + x.nonEmpty && start == x.start && { // ...so other must contain something and have same start + val l0 = last + (l0 == x.last && ( // And same end + start == l0 || step == x.step // And either the same step, or not take any steps + )) + } + case _ => + super.equals(other) + } + + final override def hashCode: Int = + if(length >= 2) MurmurHash3.rangeHash(start, step, lastElement) + else super.hashCode + + final override def toString: String = { + val preposition = if (isInclusive) "to" else "until" + val stepped = if (step == 1) "" else s" by $step" + val prefix = if (isEmpty) "empty " else if (!isExact) "inexact " else "" + s"${prefix}Range $start $preposition $end$stepped" + } + + override protected[this] def className = "Range" + + override def distinct: Range = this + + override def grouped(size: Int): Iterator[Range] = { + require(size >= 1, f"size=$size%d, but size must be positive") + if (isEmpty) { + Iterator.empty + } else { + val s = size + new AbstractIterator[Range] { + private[this] var i = 0 + override def hasNext = Range.this.length > i + override def next() = + if (hasNext) { + val x = Range.this.slice(i, i + s) + i += s + x + } else { + Iterator.empty.next() + } + } + } + } + + override def sorted[B >: Int](implicit ord: Ordering[B]): IndexedSeq[Int] = + if (ord eq Ordering.Int) { + if (step > 0) { + this + } else { + reverse + } + } else { + super.sorted(ord) + } +} + +/** Companion object for ranges. */ +object Range { + + /** Counts the number of range elements. + * precondition: step != 0 + * If the size of the range exceeds Int.MaxValue, the + * result will be negative. + */ + def count(start: Int, end: Int, step: Int, isInclusive: Boolean): Int = { + if (step == 0) + throw new IllegalArgumentException("step cannot be 0.") + + val isEmpty = + if (start == end) !isInclusive + else if (start < end) step < 0 + else step > 0 + + if (isEmpty) 0 + else { + // Counts with Longs so we can recognize too-large ranges. + val gap: Long = end.toLong - start.toLong + val jumps: Long = gap / step + // Whether the size of this range is one larger than the + // number of full-sized jumps. + val hasStub = isInclusive || (gap % step != 0) + val result: Long = jumps + ( if (hasStub) 1 else 0 ) + + if (result > scala.Int.MaxValue) -1 + else result.toInt + } + } + def count(start: Int, end: Int, step: Int): Int = + count(start, end, step, isInclusive = false) + + /** Make a range from `start` until `end` (exclusive) with given step value. + * @note step != 0 + */ + def apply(start: Int, end: Int, step: Int): Range.Exclusive = new Range.Exclusive(start, end, step) + + /** Make a range from `start` until `end` (exclusive) with step value 1. + */ + def apply(start: Int, end: Int): Range.Exclusive = new Range.Exclusive(start, end, 1) + + /** Make an inclusive range from `start` to `end` with given step value. + * @note step != 0 + */ + def inclusive(start: Int, end: Int, step: Int): Range.Inclusive = new Range.Inclusive(start, end, step) + + /** Make an inclusive range from `start` to `end` with step value 1. + */ + def inclusive(start: Int, end: Int): Range.Inclusive = new Range.Inclusive(start, end, 1) + + @SerialVersionUID(3L) + final class Inclusive(start: Int, end: Int, step: Int) extends Range(start, end, step) { + def isInclusive: Boolean = true + } + + @SerialVersionUID(3L) + final class Exclusive(start: Int, end: Int, step: Int) extends Range(start, end, step) { + def isInclusive: Boolean = false + } + + // BigInt and Long are straightforward generic ranges. + object BigInt { + def apply(start: BigInt, end: BigInt, step: BigInt): NumericRange.Exclusive[BigInt] = NumericRange(start, end, step) + def inclusive(start: BigInt, end: BigInt, step: BigInt): NumericRange.Inclusive[BigInt] = NumericRange.inclusive(start, end, step) + } + + object Long { + def apply(start: Long, end: Long, step: Long): NumericRange.Exclusive[Long] = NumericRange(start, end, step) + def inclusive(start: Long, end: Long, step: Long): NumericRange.Inclusive[Long] = NumericRange.inclusive(start, end, step) + } + + // BigDecimal uses an alternative implementation of Numeric in which + // it pretends to be Integral[T] instead of Fractional[T]. See Numeric for + // details. The intention is for it to throw an exception anytime + // imprecision or surprises might result from anything, although this may + // not yet be fully implemented. + object BigDecimal { + implicit val bigDecAsIntegral: Numeric.BigDecimalAsIfIntegral = Numeric.BigDecimalAsIfIntegral + + def apply(start: BigDecimal, end: BigDecimal, step: BigDecimal): NumericRange.Exclusive[BigDecimal] = + NumericRange(start, end, step) + def inclusive(start: BigDecimal, end: BigDecimal, step: BigDecimal): NumericRange.Inclusive[BigDecimal] = + NumericRange.inclusive(start, end, step) + } + + // As there is no appealing default step size for not-really-integral ranges, + // we offer a partially constructed object. + class Partial[T, U](private val f: T => U) extends AnyVal { + def by(x: T): U = f(x) + override def toString = "Range requires step" + } + + // Illustrating genericity with Int Range, which should have the same behavior + // as the original Range class. However we leave the original Range + // indefinitely, for performance and because the compiler seems to bootstrap + // off it and won't do so with our parameterized version without modifications. + object Int { + def apply(start: Int, end: Int, step: Int): NumericRange.Exclusive[Int] = NumericRange(start, end, step) + def inclusive(start: Int, end: Int, step: Int): NumericRange.Inclusive[Int] = NumericRange.inclusive(start, end, step) + } + + private def emptyRangeError(what: String): Throwable = + new NoSuchElementException(what + " on empty Range") +} + +/** + * @param lastElement The last element included in the Range + * @param initiallyEmpty Whether the Range was initially empty or not + */ +@SerialVersionUID(3L) +private class RangeIterator( + start: Int, + step: Int, + lastElement: Int, + initiallyEmpty: Boolean +) extends AbstractIterator[Int] with Serializable { + private[this] var _hasNext: Boolean = !initiallyEmpty + private[this] var _next: Int = start + override def knownSize: Int = if (_hasNext) (lastElement - _next) / step + 1 else 0 + def hasNext: Boolean = _hasNext + @throws[NoSuchElementException] + def next(): Int = { + if (!_hasNext) Iterator.empty.next() + val value = _next + _hasNext = value != lastElement + _next = value + step + value + } + + override def drop(n: Int): Iterator[Int] = { + if (n > 0) { + val longPos = _next.toLong + step * n + if (step > 0) { + _next = Math.min(lastElement, longPos).toInt + _hasNext = longPos <= lastElement + } + else if (step < 0) { + _next = Math.max(lastElement, longPos).toInt + _hasNext = longPos >= lastElement + } + } + this + } +} diff --git a/library/src/scala/collection/immutable/RedBlackTree.scala b/library/src/scala/collection/immutable/RedBlackTree.scala new file mode 100644 index 000000000000..a57785bbf741 --- /dev/null +++ b/library/src/scala/collection/immutable/RedBlackTree.scala @@ -0,0 +1,1245 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import scala.annotation.meta.{getter, setter} +import scala.annotation.tailrec +import scala.runtime.Statics.releaseFence + +/** An object containing the RedBlack tree implementation used by for `TreeMaps` and `TreeSets`. + * + * Implementation note: since efficiency is important for data structures this implementation + * uses `null` to represent empty trees. This also means pattern matching cannot + * easily be used. The API represented by the RedBlackTree object tries to hide these + * optimizations behind a reasonably clean API. + */ +private[collection] object RedBlackTree { + def validate[A](tree: Tree[A, _])(implicit ordering: Ordering[A]): tree.type = { + def impl(tree: Tree[A, _], keyProp: A => Boolean): Int = { + assert(keyProp(tree.key), s"key check failed: $tree") + if (tree.isRed) { + assert(tree.left == null || tree.left.isBlack, s"red-red left $tree") + assert(tree.right == null || tree.right.isBlack, s"red-red right $tree") + } + val leftBlacks = if (tree.left == null) 0 else impl(tree.left, k => keyProp(k) && ordering.compare(k, tree.key) < 0) + val rightBlacks = if (tree.right == null) 0 else impl(tree.right, k => keyProp(k) && ordering.compare(k, tree.key) > 0) + assert(leftBlacks == rightBlacks, s"not balanced: $tree") + leftBlacks + (if (tree.isBlack) 1 else 0) + } + if (tree != null) impl(tree, _ => true) + tree + } + + def isEmpty(tree: Tree[_, _]): Boolean = tree eq null + + def contains[A: Ordering](tree: Tree[A, _], x: A): Boolean = lookup(tree, x) ne null + def get[A: Ordering, B](tree: Tree[A, B], x: A): Option[B] = lookup(tree, x) match { + case null => None + case found => Some(found.value) + } + + @tailrec + def lookup[A, B](tree: Tree[A, B], x: A)(implicit ordering: Ordering[A]): Tree[A, B] = if (tree eq null) null else { + val cmp = ordering.compare(x, tree.key) + if (cmp < 0) lookup(tree.left, x) + else if (cmp > 0) lookup(tree.right, x) + else tree + } + private[immutable] abstract class Helper[A](implicit val ordering: Ordering[A]) { + def beforePublish[B](tree: Tree[A, B]): Tree[A, B] = { + if (tree eq null) tree + else if (tree.isMutable) { + val res = tree.mutableBlack.makeImmutable + releaseFence() + res + } else tree.black + } + /** Create a new balanced tree where `newLeft` replaces `tree.left`. + * tree and newLeft are never null */ + protected[this] final def mutableBalanceLeft[A1, B, B1 >: B](tree: Tree[A1, B], newLeft: Tree[A1, B1]): Tree[A1, B1] = { + // Parameter trees + // tree | newLeft + // -- KV R | nl.L nl.KV nl.R + // | nl.R.L nl.R.KV nl.R.R + //Note - unlike the immutable trees we can't consider tree.left eq newLeft + //as the balance operations may mutate the same object + //but that check was mostly to avoid the object creation + if (newLeft.isRed) { + val newLeft_left = newLeft.left + val newLeft_right = newLeft.right + if (isRedTree(newLeft_left)) { + // RED + // black(nl.L) nl.KV black + // nl.R KV R + val resultLeft = newLeft_left.mutableBlack + val resultRight = tree.mutableBlackWithLeft(newLeft_right) + + newLeft.mutableWithLeftRight(resultLeft, resultRight) + } else if (isRedTree(newLeft_right)) { + // RED + // black nl.R.KV black + // nl.L nl.KV nl.R.L nl.R.R KV R + + val newLeft_right_right = newLeft_right.right + + val resultLeft = newLeft.mutableBlackWithRight(newLeft_right.left) + val resultRight = tree.mutableBlackWithLeft(newLeft_right_right) + + newLeft_right.mutableWithLeftRight(resultLeft, resultRight) + } else { + // tree + // newLeft KV R + tree.mutableWithLeft(newLeft) + } + } else { + // tree + // newLeft KV R + tree.mutableWithLeft(newLeft) + } + } + /** Create a new balanced tree where `newRight` replaces `tree.right`. + * tree and newRight are never null */ + protected[this] final def mutableBalanceRight[A1, B, B1 >: B](tree: Tree[A1, B], newRight: Tree[A1, B1]): Tree[A1, B1] = { + // Parameter trees + // tree | newRight + // L KV -- | nr.L nr.KV nr.R + // | nr.L.L nr.L.KV nr.L.R + //Note - unlike the immutable trees we can't consider tree.right eq newRight + //as the balance operations may mutate the same object + //but that check was mostly to avoid the object creation + if (newRight.isRed) { + val newRight_left = newRight.left + if (isRedTree(newRight_left)) { + // RED + // black nr.L.KV black + // L KV nr.L.L nr.L.R nr.KV nr.R + + val resultLeft = tree.mutableBlackWithRight(newRight_left.left) + val resultRight = newRight.mutableBlackWithLeft(newRight_left.right) + + newRight_left.mutableWithLeftRight(resultLeft, resultRight) + + } else { + val newRight_right = newRight.right + if (isRedTree(newRight_right)) { + // RED + // black nr.KV black(nr.R) + // L KV nr.L + + val resultLeft = tree.mutableBlackWithRight(newRight_left) + val resultRight = newRight_right.mutableBlack + + newRight.mutableWithLeftRight(resultLeft, resultRight) + } else { + // tree + // L KV newRight + tree.mutableWithRight(newRight) + } + } + } else { + // tree + // L KV newRight + tree.mutableWithRight(newRight) + } + } + } + private[immutable] class SetHelper[A](implicit ordering: Ordering[A]) extends Helper[A] { + protected[this] final def mutableUpd(tree: Tree[A, Any], k: A): Tree[A, Any] = + if (tree eq null) { + mutableRedTree(k, (), null, null) + } else if (k.asInstanceOf[AnyRef] eq tree.key.asInstanceOf[AnyRef]) { + tree + } else { + val cmp = ordering.compare(k, tree.key) + if (cmp < 0) + mutableBalanceLeft(tree, mutableUpd(tree.left, k)) + else if (cmp > 0) + mutableBalanceRight(tree, mutableUpd(tree.right, k)) + else tree + } + } + private[immutable] class MapHelper[A, B](implicit ordering: Ordering[A]) extends Helper[A] { + protected[this] final def mutableUpd[B1 >: B](tree: Tree[A, B], k: A, v: B1): Tree[A, B1] = + if (tree eq null) { + mutableRedTree(k, v, null, null) + } else if (k.asInstanceOf[AnyRef] eq tree.key.asInstanceOf[AnyRef]) { + tree.mutableWithV(v) + } else { + val cmp = ordering.compare(k, tree.key) + if (cmp < 0) + mutableBalanceLeft(tree, mutableUpd(tree.left, k, v)) + else if (cmp > 0) + mutableBalanceRight(tree, mutableUpd(tree.right, k, v)) + else tree.mutableWithV(v) + } + } + + def count(tree: Tree[_, _]) = if (tree eq null) 0 else tree.count + def update[A: Ordering, B, B1 >: B](tree: Tree[A, B], k: A, v: B1, overwrite: Boolean): Tree[A, B1] = blacken(upd(tree, k, v, overwrite)) + def delete[A: Ordering, B](tree: Tree[A, B], k: A): Tree[A, B] = blacken(del(tree, k)) + def rangeImpl[A: Ordering, B](tree: Tree[A, B], from: Option[A], until: Option[A]): Tree[A, B] = (from, until) match { + case (Some(from), Some(until)) => this.range(tree, from, until) + case (Some(from), None) => this.from(tree, from) + case (None, Some(until)) => this.until(tree, until) + case (None, None) => tree + } + def range[A: Ordering, B](tree: Tree[A, B], from: A, until: A): Tree[A, B] = blacken(doRange(tree, from, until)) + def from[A: Ordering, B](tree: Tree[A, B], from: A): Tree[A, B] = blacken(doFrom(tree, from)) + def to[A: Ordering, B](tree: Tree[A, B], to: A): Tree[A, B] = blacken(doTo(tree, to)) + def until[A: Ordering, B](tree: Tree[A, B], key: A): Tree[A, B] = blacken(doUntil(tree, key)) + + def drop[A: Ordering, B](tree: Tree[A, B], n: Int): Tree[A, B] = blacken(doDrop(tree, n)) + def take[A: Ordering, B](tree: Tree[A, B], n: Int): Tree[A, B] = blacken(doTake(tree, n)) + def slice[A: Ordering, B](tree: Tree[A, B], from: Int, until: Int): Tree[A, B] = blacken(doSlice(tree, from, until)) + + def smallest[A, B](tree: Tree[A, B]): Tree[A, B] = { + if (tree eq null) throw new NoSuchElementException("empty tree") + var result = tree + while (result.left ne null) result = result.left + result + } + def greatest[A, B](tree: Tree[A, B]): Tree[A, B] = { + if (tree eq null) throw new NoSuchElementException("empty tree") + var result = tree + while (result.right ne null) result = result.right + result + } + + def tail[A, B](tree: Tree[A, B]): Tree[A, B] = { + def _tail(tree: Tree[A, B]): Tree[A, B] = + if (tree eq null) throw new NoSuchElementException("empty tree") + else { + val tl = tree.left + if (tl eq null) tree.right + else if (tl.isBlack) balLeft(tree, _tail(tl), tree.right) + else tree.redWithLeft(_tail(tree.left)) + } + blacken(_tail(tree)) + } + + def init[A, B](tree: Tree[A, B]): Tree[A, B] = { + def _init(tree: Tree[A, B]): Tree[A, B] = + if (tree eq null) throw new NoSuchElementException("empty tree") + else { + val tr = tree.right + if (tr eq null) tree.left + else if (tr.isBlack) balRight(tree, tree.left, _init(tr)) + else tree.redWithRight(_init(tr)) + } + blacken(_init(tree)) + } + + /** + * Returns the smallest node with a key larger than or equal to `x`. Returns `null` if there is no such node. + */ + def minAfter[A, B](tree: Tree[A, B], x: A)(implicit ordering: Ordering[A]): Tree[A, B] = if (tree eq null) null else { + val cmp = ordering.compare(x, tree.key) + if (cmp == 0) tree + else if (cmp < 0) { + val l = minAfter(tree.left, x) + if (l != null) l else tree + } else minAfter(tree.right, x) + } + + /** + * Returns the largest node with a key smaller than `x`. Returns `null` if there is no such node. + */ + def maxBefore[A, B](tree: Tree[A, B], x: A)(implicit ordering: Ordering[A]): Tree[A, B] = if (tree eq null) null else { + val cmp = ordering.compare(x, tree.key) + if (cmp <= 0) maxBefore(tree.left, x) + else { + val r = maxBefore(tree.right, x) + if (r != null) r else tree + } + } + + def foreach[A,B,U](tree:Tree[A,B], f:((A,B)) => U):Unit = if (tree ne null) _foreach(tree,f) + + def keysEqual[A: Ordering, X, Y](a: Tree[A, X], b: Tree[A, Y]): Boolean = { + if (a eq b) true + else if (a eq null) false + else if (b eq null) false + else a.count == b.count && (new EqualsIterator(a)).sameKeys(new EqualsIterator(b)) + } + def valuesEqual[A: Ordering, X, Y](a: Tree[A, X], b: Tree[A, Y]): Boolean = { + if (a eq b) true + else if (a eq null) false + else if (b eq null) false + else a.count == b.count && (new EqualsIterator(a)).sameValues(new EqualsIterator(b)) + } + def entriesEqual[A: Ordering, X, Y](a: Tree[A, X], b: Tree[A, Y]): Boolean = { + if (a eq b) true + else if (a eq null) false + else if (b eq null) false + else a.count == b.count && (new EqualsIterator(a)).sameEntries(new EqualsIterator(b)) + } + + private[this] def _foreach[A, B, U](tree: Tree[A, B], f: ((A, B)) => U): Unit = { + if (tree.left ne null) _foreach(tree.left, f) + f((tree.key, tree.value)) + if (tree.right ne null) _foreach(tree.right, f) + } + + def foreachKey[A, U](tree:Tree[A,_], f: A => U):Unit = if (tree ne null) _foreachKey(tree,f) + + private[this] def _foreachKey[A, U](tree: Tree[A, _], f: A => U): Unit = { + if (tree.left ne null) _foreachKey(tree.left, f) + f((tree.key)) + if (tree.right ne null) _foreachKey(tree.right, f) + } + + def foreachEntry[A, B, U](tree:Tree[A,B], f: (A, B) => U):Unit = if (tree ne null) _foreachEntry(tree,f) + + private[this] def _foreachEntry[A, B, U](tree: Tree[A, B], f: (A, B) => U): Unit = { + if (tree.left ne null) _foreachEntry(tree.left, f) + f(tree.key, tree.value) + if (tree.right ne null) _foreachEntry(tree.right, f) + } + + def iterator[A: Ordering, B](tree: Tree[A, B], start: Option[A] = None): Iterator[(A, B)] = new EntriesIterator(tree, start) + def keysIterator[A: Ordering](tree: Tree[A, _], start: Option[A] = None): Iterator[A] = new KeysIterator(tree, start) + def valuesIterator[A: Ordering, B](tree: Tree[A, B], start: Option[A] = None): Iterator[B] = new ValuesIterator(tree, start) + + @tailrec + def nth[A, B](tree: Tree[A, B], n: Int): Tree[A, B] = { + val count = this.count(tree.left) + if (n < count) nth(tree.left, n) + else if (n > count) nth(tree.right, n - count - 1) + else tree + } + + def isBlack(tree: Tree[_, _]) = (tree eq null) || tree.isBlack + + @`inline` private[this] def isRedTree(tree: Tree[_, _]) = (tree ne null) && tree.isRed + @`inline` private[this] def isBlackTree(tree: Tree[_, _]) = (tree ne null) && tree.isBlack + + private[this] def blacken[A, B](t: Tree[A, B]): Tree[A, B] = if (t eq null) null else t.black + + // Blacken if the tree is red and has a red child. This is necessary when using methods such as `upd` or `updNth` + // for building subtrees. Use `blacken` instead when building top-level trees. + private[this] def maybeBlacken[A, B](t: Tree[A, B]): Tree[A, B] = + if(isBlack(t)) t else if(isRedTree(t.left) || isRedTree(t.right)) t.black else t + + private[this] def mkTree[A, B](isBlack: Boolean, key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = { + val sizeAndColour = sizeOf(left) + sizeOf(right) + 1 | (if(isBlack) initialBlackCount else initialRedCount) + new Tree(key, value.asInstanceOf[AnyRef], left, right, sizeAndColour) + } + + /** Create a new balanced tree where `newLeft` replaces `tree.left`. */ + private[this] def balanceLeft[A, B1](tree: Tree[A, B1], newLeft: Tree[A, B1]): Tree[A, B1] = { + // Parameter trees + // tree | newLeft + // -- KV R | nl.L nl.KV nl.R + // | nl.R.L nl.R.KV nl.R.R + if (tree.left eq newLeft) tree + else { + if (newLeft.isRed) { + val newLeft_left = newLeft.left + val newLeft_right = newLeft.right + if (isRedTree(newLeft_left)) { + // RED + // black(nl.L) nl.KV black + // nl.R KV R + val resultLeft = newLeft_left.black + val resultRight = tree.blackWithLeft(newLeft_right) + + newLeft.withLeftRight(resultLeft, resultRight) + } else if (isRedTree(newLeft_right)) { + // RED + // black nl.R.KV black + // nl.L nl.KV nl.R.L nl.R.R KV R + val newLeft_right_right = newLeft_right.right + + val resultLeft = newLeft.blackWithRight(newLeft_right.left) + val resultRight = tree.blackWithLeft(newLeft_right_right) + + newLeft_right.withLeftRight(resultLeft, resultRight) + } else { + // tree + // newLeft KV R + tree.withLeft(newLeft) + } + } else { + // tree + // newLeft KV R + tree.withLeft(newLeft) + } + } + } + /** Create a new balanced tree where `newRight` replaces `tree.right`. */ + private[this] def balanceRight[A, B1](tree: Tree[A, B1], newRight: Tree[A, B1]): Tree[A, B1] = { + // Parameter trees + // tree | newRight + // L KV -- | nr.L nr.KV nr.R + // | nr.L.L nr.L.KV nr.L.R + if (tree.right eq newRight) tree + else { + if (newRight.isRed) { + val newRight_left = newRight.left + if (isRedTree(newRight_left)) { + // RED + // black nr.L.KV black + // L KV nr.L.L nr.L.R nr.KV nr.R + val resultLeft = tree.blackWithRight(newRight_left.left) + val resultRight = newRight.blackWithLeft(newRight_left.right) + + newRight_left.withLeftRight(resultLeft, resultRight) + } else { + val newRight_right = newRight.right + if (isRedTree(newRight_right)) { + // RED + // black nr.KV black(nr.R) + // L KV nr.L + val resultLeft = tree.blackWithRight(newRight_left) + val resultRight = newRight_right.black + + newRight.withLeftRight(resultLeft, resultRight) + } else { + // tree + // L KV newRight + tree.withRight(newRight) + } + } + } else { + // tree + // L KV newRight + tree.withRight(newRight) + } + } + } + + private[this] def upd[A, B, B1 >: B](tree: Tree[A, B], k: A, v: B1, overwrite: Boolean)(implicit ordering: Ordering[A]): Tree[A, B1] = if (tree eq null) { + RedTree(k, v, null, null) + } else if (k.asInstanceOf[AnyRef] eq tree.key.asInstanceOf[AnyRef]) { + if (overwrite) + tree.withV(v) + else tree + } else { + val cmp = ordering.compare(k, tree.key) + if (cmp < 0) + balanceLeft(tree, upd(tree.left, k, v, overwrite)) + else if (cmp > 0) + balanceRight(tree, upd(tree.right, k, v, overwrite)) + else if (overwrite && (v.asInstanceOf[AnyRef] ne tree.value.asInstanceOf[AnyRef])) + tree.withV(v) + else tree + } + private[this] def updNth[A, B, B1 >: B](tree: Tree[A, B], idx: Int, k: A, v: B1): Tree[A, B1] = if (tree eq null) { + RedTree(k, v, null, null) + } else { + val rank = count(tree.left) + 1 + if (idx < rank) + balanceLeft(tree, updNth(tree.left, idx, k, v)) + else if (idx > rank) + balanceRight(tree, updNth(tree.right, idx - rank, k, v)) + else tree + } + + private[this] def doFrom[A, B](tree: Tree[A, B], from: A)(implicit ordering: Ordering[A]): Tree[A, B] = { + if (tree eq null) return null + if (ordering.lt(tree.key, from)) return doFrom(tree.right, from) + val newLeft = doFrom(tree.left, from) + if (newLeft eq tree.left) tree + else if (newLeft eq null) maybeBlacken(upd(tree.right, tree.key, tree.value, overwrite = false)) + else join(newLeft, tree.key, tree.value, tree.right) + } + private[this] def doTo[A, B](tree: Tree[A, B], to: A)(implicit ordering: Ordering[A]): Tree[A, B] = { + if (tree eq null) return null + if (ordering.lt(to, tree.key)) return doTo(tree.left, to) + val newRight = doTo(tree.right, to) + if (newRight eq tree.right) tree + else if (newRight eq null) maybeBlacken(upd(tree.left, tree.key, tree.value, overwrite = false)) + else join(tree.left, tree.key, tree.value, newRight) + } + private[this] def doUntil[A, B](tree: Tree[A, B], until: A)(implicit ordering: Ordering[A]): Tree[A, B] = { + if (tree eq null) return null + if (ordering.lteq(until, tree.key)) return doUntil(tree.left, until) + val newRight = doUntil(tree.right, until) + if (newRight eq tree.right) tree + else if (newRight eq null) maybeBlacken(upd(tree.left, tree.key, tree.value, overwrite = false)) + else join(tree.left, tree.key, tree.value, newRight) + } + + private[this] def doRange[A, B](tree: Tree[A, B], from: A, until: A)(implicit ordering: Ordering[A]): Tree[A, B] = { + if (tree eq null) return null + if (ordering.lt(tree.key, from)) return doRange(tree.right, from, until) + if (ordering.lteq(until, tree.key)) return doRange(tree.left, from, until) + val newLeft = doFrom(tree.left, from) + val newRight = doUntil(tree.right, until) + if ((newLeft eq tree.left) && (newRight eq tree.right)) tree + else if (newLeft eq null) upd(newRight, tree.key, tree.value, overwrite = false) + else if (newRight eq null) upd(newLeft, tree.key, tree.value, overwrite = false) + else join(newLeft, tree.key, tree.value, newRight) + } + + private[this] def doDrop[A, B](tree: Tree[A, B], n: Int): Tree[A, B] = + if((tree eq null) || (n <= 0)) tree + else if(n >= tree.count) null + else { + val l = count(tree.left) + if(n > l) doDrop(tree.right, n-l-1) + else if(n == l) join(null, tree.key, tree.value, tree.right) + else join(doDrop(tree.left, n), tree.key, tree.value, tree.right) + } + + private[this] def doTake[A, B](tree: Tree[A, B], n: Int): Tree[A, B] = + if((tree eq null) || (n <= 0)) null + else if(n >= tree.count) tree + else { + val l = count(tree.left) + if(n <= l) doTake(tree.left, n) + else if(n == l+1) maybeBlacken(updNth(tree.left, n, tree.key, tree.value)) + else join(tree.left, tree.key, tree.value, doTake(tree.right, n-l-1)) + } + + private[this] def doSlice[A, B](tree: Tree[A, B], from: Int, until: Int): Tree[A, B] = + if((tree eq null) || (from >= until) || (from >= tree.count) || (until <= 0)) null + else if((from <= 0) && (until >= tree.count)) tree + else { + val l = count(tree.left) + if(until <= l) doSlice(tree.left, from, until) + else if(from > l) doSlice(tree.right, from-l-1, until-l-1) + else join(doDrop(tree.left, from), tree.key, tree.value, doTake(tree.right, until-l-1)) + } + + /* + * Forcing direct fields access using the @`inline` annotation helps speed up + * various operations (especially smallest/greatest and update/delete). + * + * Unfortunately the direct field access is not guaranteed to work (but + * works on the current implementation of the Scala compiler). + * + * An alternative is to implement the these classes using plain old Java code... + * + * Mutability + * This implementation encodes both mutable and immutable trees. + * Mutable trees are never exposed to the user code but we get significant reductions in both CPU and allocations + * by maintaining a mutable tree during internal operations, e.g. a builder building a Tree, and the other bulk + * API such as filter or ++ + * + * Mutable trees are only used within the confines of this bulk operation and not shared + * Mutable trees may transition to become immutable by calling beforePublish + * Mutable trees may have child nodes (left and right) which are immutable Trees (this promotes structural sharing) + * + * Immutable trees may only child nodes (left and right) which are immutable Trees, and as such the immutable + * trees the entire transitive subtree is immutable + * + * Colour, mutablity and size encoding + * The colour of the Tree, its mutablity and size are all encoded in the _count field + * The colour is encoded in the top bit (31) of the _count. This allows a mutable tree to change colour without + * additional allocation + * The mutable trees always have bits 0 .. 30 (inclusive) set to 0 + * The immutable trees always have bits 0 .. 30 containing the size of the transitive subtree + * + * Naming + * All of the methods that can yield a mutable result have "mutable" on their name, and generally there + * is another method similarly named with doesn't. This is to aid safety and to reduce the cognitive load when + * reviewing changes. e.g. + * def upd(...) will update an immutable Tree, producing an immutable Tree + * def mutableUpd(...) will update a mutable or immutable Tree and may return a mutable or immutable Tree + * a method that has mutable in its name may return a immutable tree if the operation can reuse the existing tree + * + */ + private[immutable] final class Tree[A, +B]( + @(`inline` @getter @setter) private var _key: A, + @(`inline` @getter @setter) private var _value: AnyRef, + @(`inline` @getter @setter) private var _left: Tree[A, _], + @(`inline` @getter @setter) private var _right: Tree[A, _], + @(`inline` @getter @setter) private var _count: Int) + { + @`inline` private[RedBlackTree] def isMutable: Boolean = (_count & colourMask) == 0 + // read only APIs + @`inline` private[RedBlackTree] final def count = { + //devTimeAssert((_count & 0x7FFFFFFF) != 0) + _count & colourMask + } + //retain the colour, and mark as mutable + @`inline` private def mutableRetainingColour = _count & colourBit + + //inlined here to avoid outer object null checks + @`inline` private[RedBlackTree] final def sizeOf(tree:Tree[_,_]) = if (tree eq null) 0 else tree.count + @`inline` private[immutable] final def key = _key + @`inline` private[immutable] final def value = _value.asInstanceOf[B] + @`inline` private[immutable] final def left = _left.asInstanceOf[Tree[A, B]] + @`inline` private[immutable] final def right = _right.asInstanceOf[Tree[A, B]] + //Note - only used in tests outside RedBlackTree + @`inline` private[immutable] final def isBlack = _count < 0 + //Note - only used in tests outside RedBlackTree + @`inline` private[immutable] final def isRed = _count >= 0 + + override def toString: String = s"${if(isRed) "RedTree" else "BlackTree"}($key, $value, $left, $right)" + + //mutable APIs + private[RedBlackTree] def makeImmutable: this.type = { + def makeImmutableImpl(): Unit = { + if (isMutable) { + var size = 1 + if (_left ne null) { + _left.makeImmutable + size += _left.count + } + if (_right ne null) { + _right.makeImmutable + size += _right.count + } + _count |= size //retains colour + } + } + makeImmutableImpl() + this + } + + private[RedBlackTree] def mutableBlack: Tree[A, B] = { + if (isBlack) this + else if (isMutable) { + _count = initialBlackCount + this + } + else new Tree(_key, _value, _left, _right, initialBlackCount) + } +// private[RedBlackTree] def mutableRed: Tree[A, B] = { +// if (isRed) this +// else if (mutable) { +// _count = initialRedCount +// this +// } +// else new Tree(_key, _value, _left, _right, initialRedCount) +// } + + private[RedBlackTree] def mutableWithV[B1 >: B](newValue: B1): Tree[A, B1] = { + if (newValue.asInstanceOf[AnyRef] eq _value.asInstanceOf[AnyRef]) this + else if (isMutable) { + _value = newValue.asInstanceOf[AnyRef] + this + } else new Tree(_key, newValue.asInstanceOf[AnyRef], _left, _right, mutableRetainingColour) + } + + private[RedBlackTree] def mutableWithLeft[B1 >: B](newLeft: Tree[A, B1]): Tree[A, B1] = { + if (_left eq newLeft) this + else if (isMutable) { + _left = newLeft + this + } else new Tree(_key, _value, newLeft, _right, mutableRetainingColour) + } + private[RedBlackTree] def mutableWithRight[B1 >: B](newRight: Tree[A, B1]): Tree[A, B1] = { + if (_right eq newRight) this + else if (isMutable) { + _right = newRight + this + } else new Tree(_key, _value, _left, newRight, mutableRetainingColour) + } + private[RedBlackTree] def mutableWithLeftRight[B1 >: B](newLeft: Tree[A, B1], newRight: Tree[A, B1]): Tree[A, B1] = { + if ((_left eq newLeft) && (_right eq newRight)) this + else if (isMutable) { + _left = newLeft + _right = newRight + this + } else new Tree(_key, _value, newLeft, newRight, mutableRetainingColour) + } + private[RedBlackTree] def mutableBlackWithLeft[B1 >: B](newLeft: Tree[A, B1]): Tree[A, B1] = { + if ((_left eq newLeft) && isBlack) this + else if (isMutable) { + _count = initialBlackCount + _left = newLeft + this + } else new Tree(_key, _value, newLeft, _right, initialBlackCount) + } + private[RedBlackTree] def mutableBlackWithRight[B1 >: B](newRight: Tree[A, B1]): Tree[A, B1] = { + if ((_right eq newRight) && isBlack) this + else if (isMutable) { + _count = initialBlackCount + _right = newRight + this + } else new Tree(_key, _value, _left, newRight, initialBlackCount) + } + + private[RedBlackTree] def black: Tree[A, B] = { + //assertNotMutable(this) + if (isBlack) this + else new Tree(_key, _value, _left, _right, _count ^ colourBit) + } + private[RedBlackTree] def red: Tree[A, B] = { + //assertNotMutable(this) + if (isRed) this + else new Tree(_key, _value, _left, _right, _count ^ colourBit) + } + private[RedBlackTree] def withKV[B1 >: B](newKey: A, newValue: B1): Tree[A, B1] = { + //assertNotMutable(this) + if ((newKey.asInstanceOf[AnyRef] eq _key.asInstanceOf[AnyRef]) && + (newValue.asInstanceOf[AnyRef] eq _value.asInstanceOf[AnyRef])) this + else new Tree(newKey, newValue.asInstanceOf[AnyRef], _left, _right, _count) + } + private[RedBlackTree] def withV[B1 >: B](newValue: B1): Tree[A, B1] = { + //assertNotMutable(this) + if (newValue.asInstanceOf[AnyRef] eq _value.asInstanceOf[AnyRef]) this + else new Tree(_key, newValue.asInstanceOf[AnyRef], _left, _right, _count) + } + + private[RedBlackTree] def withLeft[B1 >: B](newLeft: Tree[A, B1]): Tree[A, B1] = { + //assertNotMutable(this) + //assertNotMutable(newLeft) + if (newLeft eq _left) this + else { + val size = sizeOf(newLeft) + sizeOf(_right) + 1 + new Tree(key, value.asInstanceOf[AnyRef], newLeft, _right, (_count & colourBit) | size) + } + } + private[RedBlackTree] def withRight[B1 >: B](newRight: Tree[A, B1]): Tree[A, B1] = { + //assertNotMutable(this) + //assertNotMutable(newRight) + if (newRight eq _right) this + else { + val size = sizeOf(_left) + sizeOf(newRight) + 1 + new Tree(key, value.asInstanceOf[AnyRef], _left, newRight, (_count & colourBit) | size) + } + } + private[RedBlackTree] def blackWithLeft[B1 >: B](newLeft: Tree[A, B1]): Tree[A, B1] = { + //assertNotMutable(this) + //assertNotMutable(newLeft) + if ((newLeft eq _left) && isBlack) this + else { + val size = sizeOf(newLeft) + sizeOf(_right) + 1 + new Tree(key, value.asInstanceOf[AnyRef], newLeft, _right, initialBlackCount | size) + } + } + private[RedBlackTree] def redWithLeft[B1 >: B](newLeft: Tree[A, B1]): Tree[A, B1] = { + //assertNotMutable(this) + //assertNotMutable(newLeft) + if ((newLeft eq _left) && isRed) this + else { + val size = sizeOf(newLeft) + sizeOf(_right) + 1 + new Tree(key, value.asInstanceOf[AnyRef], newLeft, _right, initialRedCount | size) + } + } + private[RedBlackTree] def blackWithRight[B1 >: B](newRight: Tree[A, B1]): Tree[A, B1] = { + //assertNotMutable(this) + //assertNotMutable(newRight) + if ((newRight eq _right) && isBlack) this + else { + val size = sizeOf(_left) + sizeOf(newRight) + 1 + new Tree(key, value.asInstanceOf[AnyRef], _left, newRight, initialBlackCount | size) + } + } + private[RedBlackTree] def redWithRight[B1 >: B](newRight: Tree[A, B1]): Tree[A, B1] = { + //assertNotMutable(this) + //assertNotMutable(newLeft) + if ((newRight eq _right) && isRed) this + else { + val size = sizeOf(_left) + sizeOf(newRight) + 1 + new Tree(key, value.asInstanceOf[AnyRef], _left, newRight, initialRedCount | size) + } + } + private[RedBlackTree] def withLeftRight[B1 >: B](newLeft: Tree[A, B1], newRight: Tree[A, B1]): Tree[A, B1] = { + //assertNotMutable(this) + //assertNotMutable(newLeft) + //assertNotMutable(newRight) + if ((newLeft eq _left) && (newRight eq _right)) this + else { + val size = sizeOf(newLeft) + sizeOf(newRight) + 1 + new Tree(key, value.asInstanceOf[AnyRef], newLeft, newRight, (_count & colourBit) | size) + } + } + private[RedBlackTree] def redWithLeftRight[B1 >: B](newLeft: Tree[A, B1], newRight: Tree[A, B1]): Tree[A, B1] = { + //assertNotMutable(this) + //assertNotMutable(newLeft) + //assertNotMutable(newRight) + if ((newLeft eq _left) && (newRight eq _right) && isRed) this + else { + val size = sizeOf(newLeft) + sizeOf(newRight) + 1 + new Tree(key, value.asInstanceOf[AnyRef], newLeft, newRight, initialRedCount | size) + } + } + private[RedBlackTree] def blackWithLeftRight[B1 >: B](newLeft: Tree[A, B1], newRight: Tree[A, B1]): Tree[A, B1] = { + //assertNotMutable(this) + //assertNotMutable(newLeft) + //assertNotMutable(newRight) + if ((newLeft eq _left) && (newRight eq _right) && isBlack) this + else { + val size = sizeOf(newLeft) + sizeOf(newRight) + 1 + new Tree(key, value.asInstanceOf[AnyRef], newLeft, newRight, initialBlackCount | size) + } + } + } + //see #Tree docs "Colour, mutablity and size encoding" + //we make these final vals because the optimiser inlines them, without reference to the enclosing module + private[RedBlackTree] final val colourBit = 0x80000000 + private[RedBlackTree] final val colourMask = ~colourBit + private[RedBlackTree] final val initialBlackCount = colourBit + private[RedBlackTree] final val initialRedCount = 0 + + @`inline` private[RedBlackTree] def mutableRedTree[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new Tree[A,B](key, value.asInstanceOf[AnyRef], left, right, initialRedCount) + @`inline` private[RedBlackTree] def mutableBlackTree[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new Tree[A,B](key, value.asInstanceOf[AnyRef], left, right, initialBlackCount) + + /** create a new immutable red tree. + * left and right may be null + */ + private[immutable] def RedTree[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]): Tree[A, B] = { + //assertNotMutable(left) + //assertNotMutable(right) + val size = sizeOf(left) + sizeOf(right) + 1 + new Tree(key, value.asInstanceOf[AnyRef], left, right, initialRedCount | size) + } + private[immutable] def BlackTree[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]): Tree[A, B] = { + //assertNotMutable(left) + //assertNotMutable(right) + val size = sizeOf(left) + sizeOf(right) + 1 + new Tree(key, value.asInstanceOf[AnyRef], left, right, initialBlackCount | size) + } + @`inline` private def sizeOf(tree:Tree[_,_]) = if (tree eq null) 0 else tree.count + //immutable APIs + //assertions - uncomment decls and callers when changing functionality + // private def devTimeAssert(assertion: Boolean) = { + // //uncomment this during development of the functionality + // assert(assertion) + // } + // private def assertNotMutable(t:Tree[_,_]) = { + // devTimeAssert ((t eq null) || t.count > 0) + // } + private[this] abstract class TreeIterator[A, B, R](root: Tree[A, B], start: Option[A])(protected implicit val ordering: Ordering[A]) extends AbstractIterator[R] { + protected[this] def nextResult(tree: Tree[A, B]): R + + override def hasNext: Boolean = lookahead ne null + + @throws[NoSuchElementException] + override def next(): R = { + val tree = lookahead + if(tree ne null) { + lookahead = findLeftMostOrPopOnEmpty(goRight(tree)) + nextResult(tree) + } else Iterator.empty.next() + } + + @tailrec + protected final def findLeftMostOrPopOnEmpty(tree: Tree[A, B]): Tree[A, B] = + if (tree eq null) popNext() + else if (tree.left eq null) tree + else findLeftMostOrPopOnEmpty(goLeft(tree)) + + @`inline` private[this] def pushNext(tree: Tree[A, B]): Unit = { + stackOfNexts(index) = tree + index += 1 + } + @`inline` protected final def popNext(): Tree[A, B] = if (index == 0) null else { + index -= 1 + stackOfNexts(index) + } + + protected[this] val stackOfNexts = if (root eq null) null else { + /* + * According to "Ralf Hinze. Constructing red-black trees" [https://www.cs.ox.ac.uk/ralf.hinze/publications/#P5] + * the maximum height of a red-black tree is 2*log_2(n + 2) - 2. + * + * According to {@see Integer#numberOfLeadingZeros} ceil(log_2(n)) = (32 - Integer.numberOfLeadingZeros(n - 1)) + * + * Although we don't store the deepest nodes in the path during iteration, + * we potentially do so in `startFrom`. + */ + val maximumHeight = 2 * (32 - Integer.numberOfLeadingZeros(root.count + 2 - 1)) - 2 + new Array[Tree[A, B]](maximumHeight) + } + private[this] var index = 0 + protected var lookahead: Tree[A, B] = if (start.isDefined) startFrom(start.get) else findLeftMostOrPopOnEmpty(root) + + /** + * Find the leftmost subtree whose key is equal to the given key, or if no such thing, + * the leftmost subtree with the key that would be "next" after it according + * to the ordering. Along the way build up the iterator's path stack so that "next" + * functionality works. + */ + private[this] def startFrom(key: A) : Tree[A,B] = if (root eq null) null else { + @tailrec def find(tree: Tree[A, B]): Tree[A, B] = + if (tree eq null) popNext() + else find( + if (ordering.lteq(key, tree.key)) goLeft(tree) + else goRight(tree) + ) + find(root) + } + + @`inline` private[this] def goLeft(tree: Tree[A, B]) = { + pushNext(tree) + tree.left + } + + @`inline` protected final def goRight(tree: Tree[A, B]) = tree.right + } + + private[this] class EqualsIterator[A: Ordering, B](tree: Tree[A, B]) extends TreeIterator[A, B, Unit](tree, None) { + override def nextResult(tree: Tree[A, B]): Nothing = ??? + + def sameKeys[X](that:EqualsIterator[A,X]): Boolean = { + var equal = true + while (equal && (this.lookahead ne null) && (that.lookahead ne null)) { + if (this.lookahead eq that.lookahead) { + this.lookahead = this.popNext() + that.lookahead = that.popNext() + } else { + equal = (this.lookahead.key.asInstanceOf[AnyRef] eq that.lookahead.key.asInstanceOf[AnyRef]) || + ordering.equiv(this.lookahead.key, that.lookahead.key) + this.lookahead = this.findLeftMostOrPopOnEmpty(this.goRight(this.lookahead)) + that.lookahead = that.findLeftMostOrPopOnEmpty(that.goRight(that.lookahead)) + } + } + equal && (this.lookahead eq null) && (that.lookahead eq null) + } + def sameValues[X](that:EqualsIterator[A,X]): Boolean = { + var equal = true + while (equal && (this.lookahead ne null) && (that.lookahead ne null)) { + if (this.lookahead eq that.lookahead) { + this.lookahead = this.popNext() + that.lookahead = that.popNext() + } else { + equal = this.lookahead.value == that.lookahead.value + this.lookahead = this.findLeftMostOrPopOnEmpty(this.goRight(this.lookahead)) + that.lookahead = that.findLeftMostOrPopOnEmpty(that.goRight(that.lookahead)) + } + } + equal && (this.lookahead eq null) && (that.lookahead eq null) + } + def sameEntries[X](that:EqualsIterator[A,X]): Boolean = { + var equal = true + while (equal && (this.lookahead ne null) && (that.lookahead ne null)) { + if (this.lookahead eq that.lookahead) { + this.lookahead = this.popNext() + that.lookahead = that.popNext() + } else { + equal = ((this.lookahead.key.asInstanceOf[AnyRef] eq that.lookahead.key.asInstanceOf[AnyRef]) || + ordering.equiv(this.lookahead.key, that.lookahead.key)) && this.lookahead.value == that.lookahead.value + this.lookahead = this.findLeftMostOrPopOnEmpty(this.goRight(this.lookahead)) + that.lookahead = that.findLeftMostOrPopOnEmpty(that.goRight(that.lookahead)) + } + } + equal && (this.lookahead eq null) && (that.lookahead eq null) + } + } + private[this] class EntriesIterator[A: Ordering, B](tree: Tree[A, B], focus: Option[A]) extends TreeIterator[A, B, (A, B)](tree, focus) { + override def nextResult(tree: Tree[A, B]) = (tree.key, tree.value) + } + + private[this] class KeysIterator[A: Ordering, B](tree: Tree[A, B], focus: Option[A]) extends TreeIterator[A, B, A](tree, focus) { + override def nextResult(tree: Tree[A, B]) = tree.key + } + + private[this] class ValuesIterator[A: Ordering, B](tree: Tree[A, B], focus: Option[A]) extends TreeIterator[A, B, B](tree, focus) { + override def nextResult(tree: Tree[A, B]) = tree.value + } + + /** Build a Tree suitable for a TreeSet from an ordered sequence of keys */ + def fromOrderedKeys[A](xs: Iterator[A], size: Int): Tree[A, Null] = { + val maxUsedDepth = 32 - Integer.numberOfLeadingZeros(size) // maximum depth of non-leaf nodes + def f(level: Int, size: Int): Tree[A, Null] = size match { + case 0 => null + case 1 => mkTree(level != maxUsedDepth || level == 1, xs.next(), null, null, null) + case n => + val leftSize = (size-1)/2 + val left = f(level+1, leftSize) + val x = xs.next() + val right = f(level+1, size-1-leftSize) + BlackTree(x, null, left, right) + } + f(1, size) + } + + /** Build a Tree suitable for a TreeMap from an ordered sequence of key/value pairs */ + def fromOrderedEntries[A, B](xs: Iterator[(A, B)], size: Int): Tree[A, B] = { + val maxUsedDepth = 32 - Integer.numberOfLeadingZeros(size) // maximum depth of non-leaf nodes + def f(level: Int, size: Int): Tree[A, B] = size match { + case 0 => null + case 1 => + val (k, v) = xs.next() + mkTree(level != maxUsedDepth || level == 1, k, v, null, null) + case n => + val leftSize = (size-1)/2 + val left = f(level+1, leftSize) + val (k, v) = xs.next() + val right = f(level+1, size-1-leftSize) + BlackTree(k, v, left, right) + } + f(1, size) + } + + def transform[A, B, C](t: Tree[A, B], f: (A, B) => C): Tree[A, C] = + if(t eq null) null + else { + val k = t.key + val v = t.value + val l = t.left + val r = t.right + val l2 = transform(l, f) + val v2 = f(k, v) + val r2 = transform(r, f) + if((v2.asInstanceOf[AnyRef] eq v.asInstanceOf[AnyRef]) + && (l2 eq l) + && (r2 eq r)) t.asInstanceOf[Tree[A, C]] + else mkTree(t.isBlack, k, v2, l2, r2) + } + + def filterEntries[A, B](t: Tree[A, B], f: (A, B) => Boolean): Tree[A, B] = if(t eq null) null else { + def fk(t: Tree[A, B]): Tree[A, B] = { + val k = t.key + val v = t.value + val l = t.left + val r = t.right + val l2 = if(l eq null) null else fk(l) + val keep = f(k, v) + val r2 = if(r eq null) null else fk(r) + if(!keep) join2(l2, r2) + else if((l2 eq l) && (r2 eq r)) t + else join(l2, k, v, r2) + } + blacken(fk(t)) + } + + private[this] val null2 = (null, null) + + def partitionEntries[A, B](t: Tree[A, B], p: (A, B) => Boolean): (Tree[A, B], Tree[A, B]) = if(t eq null) (null, null) else { + if (t eq null) null2 + else { + object partitioner { + var tmpk, tmpd = null: Tree[A, B] // shared vars to avoid returning tuples from fk + def fk(t: Tree[A, B]): Unit = { + val k = t.key + val v = t.value + val l = t.left + val r = t.right + var l2k, l2d, r2k, r2d = null: Tree[A, B] + if (l ne null) { + fk(l) + l2k = tmpk + l2d = tmpd + } + val keep = p(k, v) + if (r ne null) { + fk(r) + r2k = tmpk + r2d = tmpd + } + val jk = + if (!keep) join2(l2k, r2k) + else if ((l2k eq l) && (r2k eq r)) t + else join(l2k, k, v, r2k) + val jd = + if (keep) join2(l2d, r2d) + else if ((l2d eq l) && (r2d eq r)) t + else join(l2d, k, v, r2d) + tmpk = jk + tmpd = jd + } + } + + partitioner.fk(t) + (blacken(partitioner.tmpk), blacken(partitioner.tmpd)) + } + } + + // Based on Stefan Kahrs' Haskell version of Okasaki's Red&Black Trees + // Constructing Red-Black Trees, Ralf Hinze: [[https://www.cs.ox.ac.uk/ralf.hinze/publications/WAAAPL99b.ps.gz]] + // Red-Black Trees in a Functional Setting, Chris Okasaki: [[https://wiki.rice.edu/confluence/download/attachments/2761212/Okasaki-Red-Black.pdf]] */ + + private[this] def del[A, B](tree: Tree[A, B], k: A)(implicit ordering: Ordering[A]): Tree[A, B] = if (tree eq null) null else { + val cmp = ordering.compare(k, tree.key) + if (cmp < 0) { + val newLeft = del(tree.left, k) + if (newLeft eq tree.left) tree + else if (isBlackTree(tree.left)) balLeft(tree, newLeft, tree.right) + else tree.redWithLeft(newLeft) + } else if (cmp > 0) { + val newRight = del(tree.right, k) + if (newRight eq tree.right) tree + else if (isBlackTree(tree.right)) balRight(tree, tree.left, newRight) + else tree.redWithRight(newRight) + } else append(tree.left, tree.right) + } + + private[this] def balance[A, B](tree: Tree[A,B], tl: Tree[A, B], tr: Tree[A, B]): Tree[A, B] = + if (isRedTree(tl)) { + if (isRedTree(tr)) tree.redWithLeftRight(tl.black, tr.black) + else if (isRedTree(tl.left)) tl.withLeftRight(tl.left.black, tree.blackWithLeftRight(tl.right, tr)) + else if (isRedTree(tl.right)) tl.right.withLeftRight(tl.blackWithRight(tl.right.left), tree.blackWithLeftRight(tl.right.right, tr)) + else tree.blackWithLeftRight(tl, tr) + } else if (isRedTree(tr)) { + if (isRedTree(tr.right)) tr.withLeftRight(tree.blackWithLeftRight(tl, tr.left), tr.right.black) + else if (isRedTree(tr.left)) tr.left.withLeftRight(tree.blackWithLeftRight(tl, tr.left.left), tr.blackWithLeftRight(tr.left.right, tr.right)) + else tree.blackWithLeftRight(tl, tr) + } else tree.blackWithLeftRight(tl, tr) + + private[this] def balLeft[A, B](tree: Tree[A,B], tl: Tree[A, B], tr: Tree[A, B]): Tree[A, B] = + if (isRedTree(tl)) tree.redWithLeftRight(tl.black, tr) + else if (isBlackTree(tr)) balance(tree, tl, tr.red) + else if (isRedTree(tr) && isBlackTree(tr.left)) + tr.left.redWithLeftRight(tree.blackWithLeftRight(tl, tr.left.left), balance(tr, tr.left.right, tr.right.red)) + else sys.error("Defect: invariance violation") + + private[this] def balRight[A, B](tree: Tree[A,B], tl: Tree[A, B], tr: Tree[A, B]): Tree[A, B] = + if (isRedTree(tr)) tree.redWithLeftRight(tl, tr.black) + else if (isBlackTree(tl)) balance(tree, tl.red, tr) + else if (isRedTree(tl) && isBlackTree(tl.right)) + tl.right.redWithLeftRight(balance(tl, tl.left.red, tl.right.left), tree.blackWithLeftRight(tl.right.right, tr)) + else sys.error("Defect: invariance violation") + + /** `append` is similar to `join2` but requires that both subtrees have the same black height */ + private[this] def append[A, B](tl: Tree[A, B], tr: Tree[A, B]): Tree[A, B] = { + if (tl eq null) tr + else if (tr eq null) tl + else if (tl.isRed) { + if (tr.isRed) { + //tl is red, tr is red + val bc = append(tl.right, tr.left) + if (isRedTree(bc)) bc.withLeftRight(tl.withRight(bc.left), tr.withLeft(bc.right)) + else tl.withRight(tr.withLeft(bc)) + } else { + //tl is red, tr is black + tl.withRight(append(tl.right, tr)) + } + } else { + if (tr.isBlack) { + //tl is black tr is black + val bc = append(tl.right, tr.left) + if (isRedTree(bc)) bc.withLeftRight(tl.withRight(bc.left), tr.withLeft(bc.right)) + else balLeft(tl, tl.left, tr.withLeft(bc)) + } else { + //tl is black tr is red + tr.withLeft(append(tl, tr.left)) + } + } + } + + + // Bulk operations based on "Just Join for Parallel Ordered Sets" (https://www.cs.cmu.edu/~guyb/papers/BFS16.pdf) + // We don't store the black height in the tree so we pass it down into the join methods and derive the black height + // of child nodes from it. Where possible the black height is used directly instead of deriving the rank from it. + // Our trees are supposed to have a black root so we always blacken as the last step of union/intersect/difference. + + def union[A, B](t1: Tree[A, B], t2: Tree[A, B])(implicit ordering: Ordering[A]): Tree[A, B] = blacken(_union(t1, t2)) + + def intersect[A, B](t1: Tree[A, B], t2: Tree[A, B])(implicit ordering: Ordering[A]): Tree[A, B] = blacken(_intersect(t1, t2)) + + def difference[A, B](t1: Tree[A, B], t2: Tree[A, _])(implicit ordering: Ordering[A]): Tree[A, B] = + blacken(_difference(t1, t2.asInstanceOf[Tree[A, B]])) + + /** Compute the rank from a tree and its black height */ + @`inline` private[this] def rank(t: Tree[_, _], bh: Int): Int = { + if(t eq null) 0 + else if(t.isBlack) 2*(bh-1) + else 2*bh-1 + } + + private[this] def joinRight[A, B](tl: Tree[A, B], k: A, v: B, tr: Tree[A, B], bhtl: Int, rtr: Int): Tree[A, B] = { + val rtl = rank(tl, bhtl) + if(rtl == (rtr/2)*2) RedTree(k, v, tl, tr) + else { + val tlBlack = isBlackTree(tl) + val bhtlr = if(tlBlack) bhtl-1 else bhtl + val ttr = joinRight(tl.right, k, v, tr, bhtlr, rtr) + if(tlBlack && isRedTree(ttr) && isRedTree(ttr.right)) + RedTree(ttr.key, ttr.value, + BlackTree(tl.key, tl.value, tl.left, ttr.left), + ttr.right.black) + else mkTree(tlBlack, tl.key, tl.value, tl.left, ttr) + } + } + + private[this] def joinLeft[A, B](tl: Tree[A, B], k: A, v: B, tr: Tree[A, B], rtl: Int, bhtr: Int): Tree[A, B] = { + val rtr = rank(tr, bhtr) + if(rtr == (rtl/2)*2) RedTree(k, v, tl, tr) + else { + val trBlack = isBlackTree(tr) + val bhtrl = if(trBlack) bhtr-1 else bhtr + val ttl = joinLeft(tl, k, v, tr.left, rtl, bhtrl) + if(trBlack && isRedTree(ttl) && isRedTree(ttl.left)) + RedTree(ttl.key, ttl.value, + ttl.left.black, + BlackTree(tr.key, tr.value, ttl.right, tr.right)) + else mkTree(trBlack, tr.key, tr.value, ttl, tr.right) + } + } + + private[this] def join[A, B](tl: Tree[A, B], k: A, v: B, tr: Tree[A, B]): Tree[A, B] = { + @tailrec def h(t: Tree[_, _], i: Int): Int = + if(t eq null) i+1 else h(t.left, if(t.isBlack) i+1 else i) + val bhtl = h(tl, 0) + val bhtr = h(tr, 0) + if(bhtl > bhtr) { + val tt = joinRight(tl, k, v, tr, bhtl, rank(tr, bhtr)) + if(isRedTree(tt) && isRedTree(tt.right)) tt.black + else tt + } else if(bhtr > bhtl) { + val tt = joinLeft(tl, k, v, tr, rank(tl, bhtl), bhtr) + if(isRedTree(tt) && isRedTree(tt.left)) tt.black + else tt + } else mkTree(isRedTree(tl) || isRedTree(tr), k, v, tl, tr) + } + + private[this] def split[A, B](t: Tree[A, B], k2: A)(implicit ordering: Ordering[A]): (Tree[A, B], Tree[A, B], Tree[A, B], A) = + if(t eq null) (null, null, null, k2) + else { + val cmp = ordering.compare(k2, t.key) + if(cmp == 0) (t.left, t, t.right, t.key) + else if(cmp < 0) { + val (ll, b, lr, k1) = split(t.left, k2) + (ll, b, join(lr, t.key, t.value, t.right), k1) + } else { + val (rl, b, rr, k1) = split(t.right, k2) + (join(t.left, t.key, t.value, rl), b, rr, k1) + } + } + + private[this] def splitLast[A, B](t: Tree[A, B]): (Tree[A, B], A, B) = + if(t.right eq null) (t.left, t.key, t.value) + else { + val (tt, kk, vv) = splitLast(t.right) + (join(t.left, t.key, t.value, tt), kk, vv) + } + + private[this] def join2[A, B](tl: Tree[A, B], tr: Tree[A, B]): Tree[A, B] = + if(tl eq null) tr + else if(tr eq null) tl + else { + val (ttl, k, v) = splitLast(tl) + join(ttl, k, v, tr) + } + + private[this] def _union[A, B](t1: Tree[A, B], t2: Tree[A, B])(implicit ordering: Ordering[A]): Tree[A, B] = + if((t1 eq null) || (t1 eq t2)) t2 + else if(t2 eq null) t1 + else { + val (l1, _, r1, k1) = split(t1, t2.key) + val tl = _union(l1, t2.left) + val tr = _union(r1, t2.right) + join(tl, k1, t2.value, tr) + } + + private[this] def _intersect[A, B](t1: Tree[A, B], t2: Tree[A, B])(implicit ordering: Ordering[A]): Tree[A, B] = + if((t1 eq null) || (t2 eq null)) null + else if (t1 eq t2) t1 + else { + val (l1, b, r1, k1) = split(t1, t2.key) + val tl = _intersect(l1, t2.left) + val tr = _intersect(r1, t2.right) + if(b ne null) join(tl, k1, t2.value, tr) + else join2(tl, tr) + } + + private[this] def _difference[A, B](t1: Tree[A, B], t2: Tree[A, B])(implicit ordering: Ordering[A]): Tree[A, B] = + if((t1 eq null) || (t2 eq null)) t1 + else if (t1 eq t2) null + else { + val (l1, _, r1, _) = split(t1, t2.key) + val tl = _difference(l1, t2.left) + val tr = _difference(r1, t2.right) + join2(tl, tr) + } +} diff --git a/library/src/scala/collection/immutable/Seq.scala b/library/src/scala/collection/immutable/Seq.scala new file mode 100644 index 000000000000..fc11b697dc0c --- /dev/null +++ b/library/src/scala/collection/immutable/Seq.scala @@ -0,0 +1,157 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` + +trait Seq[+A] extends Iterable[A] + with collection.Seq[A] + with SeqOps[A, Seq, Seq[A]] + with IterableFactoryDefaults[A, Seq] { + + override final def toSeq: this.type = this + + override def iterableFactory: SeqFactory[Seq] = Seq +} + +/** + * @define coll immutable sequence + * @define Coll `immutable.Seq` + */ +transparent trait SeqOps[+A, +CC[_], +C] extends Any with collection.SeqOps[A, CC, C] + +/** + * $factoryInfo + * @define coll immutable sequence + * @define Coll `immutable.Seq` + */ +@SerialVersionUID(3L) +object Seq extends SeqFactory.Delegate[Seq](List) { + override def from[E](it: IterableOnce[E]): Seq[E] = it match { + case s: Seq[E] => s + case _ => super.from(it) + } +} + +/** Base trait for immutable indexed sequences that have efficient `apply` and `length` */ +trait IndexedSeq[+A] extends Seq[A] + with collection.IndexedSeq[A] + with IndexedSeqOps[A, IndexedSeq, IndexedSeq[A]] + with IterableFactoryDefaults[A, IndexedSeq] { + + final override def toIndexedSeq: IndexedSeq[A] = this + + override def canEqual(that: Any): Boolean = that match { + case otherIndexedSeq: IndexedSeq[_] => length == otherIndexedSeq.length && super.canEqual(that) + case _ => super.canEqual(that) + } + + + override def sameElements[B >: A](o: IterableOnce[B]): Boolean = o match { + case that: IndexedSeq[_] => + (this eq that) || { + val length = this.length + var equal = length == that.length + if (equal) { + var index = 0 + // some IndexedSeq apply is less efficient than using Iterators + // e.g. Vector so we can compare the first few with apply and the rest with an iterator + // but if apply is more efficient than Iterators then we can use the apply for all the comparison + // we default to the minimum preferred length + val maxApplyCompare = { + val preferredLength = Math.min(applyPreferredMaxLength, that.applyPreferredMaxLength) + if (length > (preferredLength.toLong << 1)) preferredLength else length + } + while (index < maxApplyCompare && equal) { + equal = this (index) == that(index) + index += 1 + } + if ((index < length) && equal) { + val thisIt = this.iterator.drop(index) + val thatIt = that.iterator.drop(index) + while (equal && thisIt.hasNext) { + equal = thisIt.next() == thatIt.next() + } + } + } + equal + } + case _ => super.sameElements(o) + } + + /** a hint to the runtime when scanning values + * [[apply]] is preferred for scan with a max index less than this value + * [[iterator]] is preferred for scans above this range + * @return a hint about when to use [[apply]] or [[iterator]] + */ + protected def applyPreferredMaxLength: Int = IndexedSeqDefaults.defaultApplyPreferredMaxLength + + override def iterableFactory: SeqFactory[IndexedSeq] = IndexedSeq +} + +object IndexedSeqDefaults { + val defaultApplyPreferredMaxLength: Int = + try System.getProperty( + "scala.collection.immutable.IndexedSeq.defaultApplyPreferredMaxLength", "64").toInt + catch { + case _: SecurityException => 64 + } +} + +@SerialVersionUID(3L) +object IndexedSeq extends SeqFactory.Delegate[IndexedSeq](Vector) { + override def from[E](it: IterableOnce[E]): IndexedSeq[E] = it match { + case is: IndexedSeq[E] => is + case _ => super.from(it) + } +} + +/** Base trait for immutable indexed Seq operations */ +transparent trait IndexedSeqOps[+A, +CC[_], +C] + extends SeqOps[A, CC, C] + with collection.IndexedSeqOps[A, CC, C] { + + override def slice(from: Int, until: Int): C = { + // since we are immutable we can just share the same collection + if (from <= 0 && until >= length) coll + else super.slice(from, until) + } + +} + +/** Base trait for immutable linear sequences that have efficient `head` and `tail` */ +trait LinearSeq[+A] + extends Seq[A] + with collection.LinearSeq[A] + with LinearSeqOps[A, LinearSeq, LinearSeq[A]] + with IterableFactoryDefaults[A, LinearSeq] { + + override def iterableFactory: SeqFactory[LinearSeq] = LinearSeq +} + +@SerialVersionUID(3L) +object LinearSeq extends SeqFactory.Delegate[LinearSeq](List) { + override def from[E](it: IterableOnce[E]): LinearSeq[E] = it match { + case ls: LinearSeq[E] => ls + case _ => super.from(it) + } +} + +transparent trait LinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] with LinearSeqOps[A, CC, C]] + extends Any with SeqOps[A, CC, C] + with collection.LinearSeqOps[A, CC, C] + +/** Explicit instantiation of the `Seq` trait to reduce class file size in subclasses. */ +abstract class AbstractSeq[+A] extends scala.collection.AbstractSeq[A] with Seq[A] diff --git a/library/src/scala/collection/immutable/SeqMap.scala b/library/src/scala/collection/immutable/SeqMap.scala new file mode 100644 index 000000000000..50a5dcfce382 --- /dev/null +++ b/library/src/scala/collection/immutable/SeqMap.scala @@ -0,0 +1,285 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import scala.collection.mutable.{Builder, ReusableBuilder} + +/** A base trait for ordered, immutable maps. + * + * Note that the [[equals]] method for [[SeqMap]] compares key-value pairs + * without regard to ordering. + * + * All behavior is defined in terms of the abstract methods in `SeqMap`. + * It is sufficient for concrete subclasses to implement those methods. + * Methods that return a new map, in particular [[removed]] and [[updated]], must preserve ordering. + * + * @tparam K the type of the keys contained in this linked map. + * @tparam V the type of the values associated with the keys in this linked map. + * + * @define coll immutable seq map + * @define Coll `immutable.SeqMap` + */ + +trait SeqMap[K, +V] + extends Map[K, V] + with collection.SeqMap[K, V] + with MapOps[K, V, SeqMap, SeqMap[K, V]] + with MapFactoryDefaults[K, V, SeqMap, Iterable] { + override def mapFactory: MapFactory[SeqMap] = SeqMap +} + + +object SeqMap extends MapFactory[SeqMap] { + def empty[K, V]: SeqMap[K, V] = EmptySeqMap.asInstanceOf[SeqMap[K, V]] + + def from[K, V](it: collection.IterableOnce[(K, V)]): SeqMap[K, V] = + it match { + //case sm: SeqMap[K, V] => sm + case m: ListMap[K, V] => m + case m: TreeSeqMap[K, V] => m + case m: VectorMap[K, V] => m + case m: SeqMap1[K, V] => m + case m: SeqMap2[K, V] => m + case m: SeqMap3[K, V] => m + case m: SeqMap4[K, V] => m + case it: Iterable[_] if it.isEmpty => empty[K, V] + case _ => (newBuilder[K, V] ++= it).result() + } + + def newBuilder[K, V]: Builder[(K, V), SeqMap[K, V]] = new SeqMapBuilderImpl + + @SerialVersionUID(3L) + private object EmptySeqMap extends SeqMap[Any, Nothing] with Serializable { + override def size: Int = 0 + override def knownSize: Int = 0 + override def apply(key: Any) = throw new NoSuchElementException("key not found: " + key) + override def contains(key: Any) = false + def get(key: Any): Option[Nothing] = None + override def getOrElse [V1](key: Any, default: => V1): V1 = default + def iterator: Iterator[(Any, Nothing)] = Iterator.empty + def updated [V1] (key: Any, value: V1): SeqMap[Any, V1] = new SeqMap1(key, value) + def removed(key: Any): SeqMap[Any, Nothing] = this + } + + @SerialVersionUID(3L) + private[immutable] final class SeqMap1[K, +V](key1: K, value1: V) extends SeqMap[K,V] with Serializable { + override def size: Int = 1 + override def knownSize: Int = 1 + override def apply(key: K) = if (key == key1) value1 else throw new NoSuchElementException("key not found: " + key) + override def contains(key: K) = key == key1 + def get(key: K): Option[V] = + if (key == key1) Some(value1) else None + override def getOrElse [V1 >: V](key: K, default: => V1): V1 = + if (key == key1) value1 else default + def iterator = Iterator.single((key1, value1)) + def updated[V1 >: V](key: K, value: V1): SeqMap[K, V1] = + if (key == key1) new SeqMap1(key1, value) + else new SeqMap2(key1, value1, key, value) + def removed(key: K): SeqMap[K, V] = + if (key == key1) SeqMap.empty else this + override def foreach[U](f: ((K, V)) => U): Unit = { + f((key1, value1)) + } + override def foreachEntry[U](f: (K, V) => U): Unit = { + f(key1, value1) + } + } + + @SerialVersionUID(3L) + private[immutable] final class SeqMap2[K, +V](key1: K, value1: V, key2: K, value2: V) extends SeqMap[K,V] with Serializable { + override def size: Int = 2 + override def knownSize: Int = 2 + override def apply(key: K) = + if (key == key1) value1 + else if (key == key2) value2 + else throw new NoSuchElementException("key not found: " + key) + override def contains(key: K) = (key == key1) || (key == key2) + def get(key: K): Option[V] = + if (key == key1) Some(value1) + else if (key == key2) Some(value2) + else None + override def getOrElse [V1 >: V](key: K, default: => V1): V1 = + if (key == key1) value1 + else if (key == key2) value2 + else default + def iterator = ((key1, value1) :: (key2, value2) :: Nil).iterator + def updated[V1 >: V](key: K, value: V1): SeqMap[K, V1] = + if (key == key1) new SeqMap2(key1, value, key2, value2) + else if (key == key2) new SeqMap2(key1, value1, key2, value) + else new SeqMap3(key1, value1, key2, value2, key, value) + def removed(key: K): SeqMap[K, V] = + if (key == key1) new SeqMap1(key2, value2) + else if (key == key2) new SeqMap1(key1, value1) + else this + override def foreach[U](f: ((K, V)) => U): Unit = { + f((key1, value1)); f((key2, value2)) + } + override def foreachEntry[U](f: (K, V) => U): Unit = { + f(key1, value1) + f(key2, value2) + } + } + + @SerialVersionUID(3L) + private[immutable] class SeqMap3[K, +V](key1: K, value1: V, key2: K, value2: V, key3: K, value3: V) extends SeqMap[K,V] with Serializable { + override def size: Int = 3 + override def knownSize: Int = 3 + override def apply(key: K) = + if (key == key1) value1 + else if (key == key2) value2 + else if (key == key3) value3 + else throw new NoSuchElementException("key not found: " + key) + override def contains(key: K) = (key == key1) || (key == key2) || (key == key3) + def get(key: K): Option[V] = + if (key == key1) Some(value1) + else if (key == key2) Some(value2) + else if (key == key3) Some(value3) + else None + override def getOrElse [V1 >: V](key: K, default: => V1): V1 = + if (key == key1) value1 + else if (key == key2) value2 + else if (key == key3) value3 + else default + def iterator = ((key1, value1) :: (key2, value2) :: (key3, value3) :: Nil).iterator + def updated[V1 >: V](key: K, value: V1): SeqMap[K, V1] = + if (key == key1) new SeqMap3(key1, value, key2, value2, key3, value3) + else if (key == key2) new SeqMap3(key1, value1, key2, value, key3, value3) + else if (key == key3) new SeqMap3(key1, value1, key2, value2, key3, value) + else new SeqMap4(key1, value1, key2, value2, key3, value3, key, value) + def removed(key: K): SeqMap[K, V] = + if (key == key1) new SeqMap2(key2, value2, key3, value3) + else if (key == key2) new SeqMap2(key1, value1, key3, value3) + else if (key == key3) new SeqMap2(key1, value1, key2, value2) + else this + override def foreach[U](f: ((K, V)) => U): Unit = { + f((key1, value1)); f((key2, value2)); f((key3, value3)) + } + override def foreachEntry[U](f: (K, V) => U): Unit = { + f(key1, value1) + f(key2, value2) + f(key3, value3) + } + } + + @SerialVersionUID(3L) + private[immutable] final class SeqMap4[K, +V](key1: K, value1: V, key2: K, value2: V, key3: K, value3: V, key4: K, value4: V) extends SeqMap[K,V] with Serializable { + override def size: Int = 4 + override def knownSize: Int = 4 + override def apply(key: K) = + if (key == key1) value1 + else if (key == key2) value2 + else if (key == key3) value3 + else if (key == key4) value4 + else throw new NoSuchElementException("key not found: " + key) + override def contains(key: K) = (key == key1) || (key == key2) || (key == key3) || (key == key4) + def get(key: K): Option[V] = + if (key == key1) Some(value1) + else if (key == key2) Some(value2) + else if (key == key3) Some(value3) + else if (key == key4) Some(value4) + else None + override def getOrElse [V1 >: V](key: K, default: => V1): V1 = + if (key == key1) value1 + else if (key == key2) value2 + else if (key == key3) value3 + else if (key == key4) value4 + else default + def iterator = ((key1, value1) :: (key2, value2) :: (key3, value3) :: (key4, value4) :: Nil).iterator + def updated[V1 >: V](key: K, value: V1): SeqMap[K, V1] = + if (key == key1) new SeqMap4(key1, value, key2, value2, key3, value3, key4, value4) + else if (key == key2) new SeqMap4(key1, value1, key2, value, key3, value3, key4, value4) + else if (key == key3) new SeqMap4(key1, value1, key2, value2, key3, value, key4, value4) + else if (key == key4) new SeqMap4(key1, value1, key2, value2, key3, value3, key4, value) + else { + // Directly create the elements for performance reasons + val fields = Vector(key1, key2, key3, key4, key) + val underlying: Map[K, (Int, V1)] = + HashMap( + (key1, (0, value1)), + (key2, (1, value2)), + (key3, (2, value3)), + (key4, (3, value4)), + (key, (4, value)) + ) + new VectorMap(fields, underlying) + } + def removed(key: K): SeqMap[K, V] = + if (key == key1) new SeqMap3(key2, value2, key3, value3, key4, value4) + else if (key == key2) new SeqMap3(key1, value1, key3, value3, key4, value4) + else if (key == key3) new SeqMap3(key1, value1, key2, value2, key4, value4) + else if (key == key4) new SeqMap3(key1, value1, key2, value2, key3, value3) + else this + override def foreach[U](f: ((K, V)) => U): Unit = { + f((key1, value1)); f((key2, value2)); f((key3, value3)); f((key4, value4)) + } + override def foreachEntry[U](f: (K, V) => U): Unit = { + f(key1, value1) + f(key2, value2) + f(key3, value3) + f(key4, value4) + } + + private[SeqMap] def buildTo[V1 >: V](builder: Builder[(K, V1), SeqMap[K, V1]]): builder.type = + builder.addOne((key1, value1)).addOne((key2, value2)).addOne((key3, value3)).addOne((key4, value4)) + } + + private final class SeqMapBuilderImpl[K, V] extends ReusableBuilder[(K, V), SeqMap[K, V]] { + private[this] var elems: SeqMap[K, V] = SeqMap.empty + private[this] var switchedToVectorMapBuilder: Boolean = false + private[this] var vectorMapBuilder: VectorMapBuilder[K, V] = _ + + override def clear(): Unit = { + elems = SeqMap.empty + if (vectorMapBuilder != null) { + vectorMapBuilder.clear() + } + switchedToVectorMapBuilder = false + } + + override def result(): SeqMap[K, V] = + if (switchedToVectorMapBuilder) vectorMapBuilder.result() else elems + + def addOne(elem: (K, V)) = { + if (switchedToVectorMapBuilder) { + vectorMapBuilder.addOne(elem) + } else if (elems.size < 4) { + elems = elems + elem + } else { + // assert(elems.size == 4) + if (elems.contains(elem._1)) { + elems = elems + elem // will not increase the size of the map + } else { + switchedToVectorMapBuilder = true + if (vectorMapBuilder == null) { + vectorMapBuilder = new VectorMapBuilder + } + elems.asInstanceOf[SeqMap4[K, V]].buildTo(vectorMapBuilder) + vectorMapBuilder.addOne(elem) + } + } + + this + } + + override def addAll(xs: IterableOnce[(K, V)]): this.type = + if (switchedToVectorMapBuilder) { + vectorMapBuilder.addAll(xs) + this + } else { + super.addAll(xs) + } + } +} diff --git a/library/src/scala/collection/immutable/Set.scala b/library/src/scala/collection/immutable/Set.scala new file mode 100644 index 000000000000..08fd4a13e9cd --- /dev/null +++ b/library/src/scala/collection/immutable/Set.scala @@ -0,0 +1,408 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import scala.collection.immutable.Set.Set4 +import scala.collection.mutable.{Builder, ReusableBuilder} + +/** Base trait for immutable set collections */ +trait Set[A] extends Iterable[A] + with collection.Set[A] + with SetOps[A, Set, Set[A]] + with IterableFactoryDefaults[A, Set] { + override def iterableFactory: IterableFactory[Set] = Set +} + +/** Base trait for immutable set operations + * + * @define coll immutable set + * @define Coll `immutable.Set` + */ +transparent trait SetOps[A, +CC[X], +C <: SetOps[A, CC, C]] + extends collection.SetOps[A, CC, C] { + + /** Creates a new set with an additional element, unless the element is + * already present. + * + * @param elem the element to be added + * @return a new set that contains all elements of this set and that also + * contains `elem`. + */ + def incl(elem: A): C + + /** Alias for `incl` */ + override final def + (elem: A): C = incl(elem) // like in collection.Set but not deprecated + + /** Creates a new set with a given element removed from this set. + * + * @param elem the element to be removed + * @return a new set that contains all elements of this set but that does not + * contain `elem`. + */ + def excl(elem: A): C + + /** Alias for `excl` */ + @`inline` final override def - (elem: A): C = excl(elem) + + def diff(that: collection.Set[A]): C = + foldLeft(empty)((result, elem) => if (that contains elem) result else result + elem) + + /** Creates a new $coll from this $coll by removing all elements of another + * collection. + * + * @param that the collection containing the elements to remove. + * @return a new $coll with the given elements removed, omitting duplicates. + */ + def removedAll(that: IterableOnce[A]): C = that.iterator.foldLeft[C](coll)(_ - _) + + /** Alias for removedAll */ + override final def -- (that: IterableOnce[A]): C = removedAll(that) +} + +transparent trait StrictOptimizedSetOps[A, +CC[X], +C <: SetOps[A, CC, C]] + extends SetOps[A, CC, C] + with collection.StrictOptimizedSetOps[A, CC, C] + with StrictOptimizedIterableOps[A, CC, C] { + + override def concat(that: collection.IterableOnce[A]): C = { + var result: C = coll + val it = that.iterator + while (it.hasNext) result = result + it.next() + result + } +} + +/** + * $factoryInfo + * @define coll immutable set + * @define Coll `immutable.Set` + */ +@SerialVersionUID(3L) +object Set extends IterableFactory[Set] { + + def empty[A]: Set[A] = EmptySet.asInstanceOf[Set[A]] + + def from[E](it: collection.IterableOnce[E]): Set[E] = + it match { + case _ if it.knownSize == 0 => empty[E] + // Since IterableOnce[E] launders the variance of E, + // identify only our implementations which can be soundly substituted. + // It's not sufficient to match `SortedSet[E]` to rebuild and `Set[E]` to retain. + case s: HashSet[E] => s + case s: ListSet[E] => s + case s: Set1[E] => s + case s: Set2[E] => s + case s: Set3[E] => s + case s: Set4[E] => s + case s: HashMap[E @unchecked, _]#HashKeySet => s + case s: MapOps[E, Any, Map, Map[E, Any]]#ImmutableKeySet @unchecked => s + // We also want `SortedSet` (and subclasses, such as `BitSet`) + // to rebuild themselves, to avoid element type widening issues. + case _ => newBuilder[E].addAll(it).result() + } + + def newBuilder[A]: Builder[A, Set[A]] = new SetBuilderImpl[A] + + /** An optimized representation for immutable empty sets */ + @SerialVersionUID(3L) + private object EmptySet extends AbstractSet[Any] with Serializable { + override def size: Int = 0 + override def isEmpty = true + override def knownSize: Int = size + override def filter(pred: Any => Boolean): Set[Any] = this + override def filterNot(pred: Any => Boolean): Set[Any] = this + override def removedAll(that: IterableOnce[Any]): Set[Any] = this + override def diff(that: collection.Set[Any]): Set[Any] = this + override def subsetOf(that: collection.Set[Any]): Boolean = true + override def intersect(that: collection.Set[Any]): Set[Any] = this + override def view: View[Any] = View.empty + def contains(elem: Any): Boolean = false + def incl(elem: Any): Set[Any] = new Set1(elem) + def excl(elem: Any): Set[Any] = this + def iterator: Iterator[Any] = Iterator.empty + override def foreach[U](f: Any => U): Unit = () + } + private[collection] def emptyInstance: Set[Any] = EmptySet + + @SerialVersionUID(3L) + private abstract class SetNIterator[A](n: Int) extends AbstractIterator[A] with Serializable { + private[this] var current = 0 + private[this] var remainder = n + override def knownSize: Int = remainder + def hasNext = remainder > 0 + def apply(i: Int): A + def next(): A = + if (hasNext) { + val r = apply(current) + current += 1 + remainder -= 1 + r + } else Iterator.empty.next() + + override def drop(n: Int): Iterator[A] = { + if (n > 0) { + current += n + remainder = Math.max(0, remainder - n) + } + this + } + } + + /** An optimized representation for immutable sets of size 1 */ + @SerialVersionUID(3L) + final class Set1[A] private[collection] (elem1: A) extends AbstractSet[A] with StrictOptimizedIterableOps[A, Set, Set[A]] with Serializable { + override def size: Int = 1 + override def isEmpty = false + override def knownSize: Int = size + def contains(elem: A): Boolean = elem == elem1 + def incl(elem: A): Set[A] = + if (contains(elem)) this + else new Set2(elem1, elem) + def excl(elem: A): Set[A] = + if (elem == elem1) Set.empty + else this + def iterator: Iterator[A] = Iterator.single(elem1) + override def foreach[U](f: A => U): Unit = f(elem1) + override def exists(p: A => Boolean): Boolean = p(elem1) + override def forall(p: A => Boolean): Boolean = p(elem1) + override protected[collection] def filterImpl(pred: A => Boolean, isFlipped: Boolean): Set[A] = + if (pred(elem1) != isFlipped) this else Set.empty + + override def find(p: A => Boolean): Option[A] = + if (p(elem1)) Some(elem1) + else None + override def head: A = elem1 + override def tail: Set[A] = Set.empty + } + + /** An optimized representation for immutable sets of size 2 */ + @SerialVersionUID(3L) + final class Set2[A] private[collection] (elem1: A, elem2: A) extends AbstractSet[A] with StrictOptimizedIterableOps[A, Set, Set[A]] with Serializable { + override def size: Int = 2 + override def isEmpty = false + override def knownSize: Int = size + def contains(elem: A): Boolean = elem == elem1 || elem == elem2 + def incl(elem: A): Set[A] = + if (contains(elem)) this + else new Set3(elem1, elem2, elem) + def excl(elem: A): Set[A] = + if (elem == elem1) new Set1(elem2) + else if (elem == elem2) new Set1(elem1) + else this + def iterator: Iterator[A] = new SetNIterator[A](size) { + def apply(i: Int) = getElem(i) + } + private def getElem(i: Int) = i match { case 0 => elem1 case 1 => elem2 } + + override def foreach[U](f: A => U): Unit = { + f(elem1); f(elem2) + } + override def exists(p: A => Boolean): Boolean = { + p(elem1) || p(elem2) + } + override def forall(p: A => Boolean): Boolean = { + p(elem1) && p(elem2) + } + override protected[collection] def filterImpl(pred: A => Boolean, isFlipped: Boolean): Set[A] = { + var r1: A = null.asInstanceOf[A] + var n = 0 + if (pred(elem1) != isFlipped) { r1 = elem1; n += 1} + if (pred(elem2) != isFlipped) { if (n == 0) r1 = elem2; n += 1} + + n match { + case 0 => Set.empty + case 1 => new Set1(r1) + case 2 => this + } + } + override def find(p: A => Boolean): Option[A] = { + if (p(elem1)) Some(elem1) + else if (p(elem2)) Some(elem2) + else None + } + override def head: A = elem1 + override def tail: Set[A] = new Set1(elem2) + } + + /** An optimized representation for immutable sets of size 3 */ + @SerialVersionUID(3L) + final class Set3[A] private[collection] (elem1: A, elem2: A, elem3: A) extends AbstractSet[A] with StrictOptimizedIterableOps[A, Set, Set[A]] with Serializable { + override def size: Int = 3 + override def isEmpty = false + override def knownSize: Int = size + def contains(elem: A): Boolean = + elem == elem1 || elem == elem2 || elem == elem3 + def incl(elem: A): Set[A] = + if (contains(elem)) this + else new Set4(elem1, elem2, elem3, elem) + def excl(elem: A): Set[A] = + if (elem == elem1) new Set2(elem2, elem3) + else if (elem == elem2) new Set2(elem1, elem3) + else if (elem == elem3) new Set2(elem1, elem2) + else this + def iterator: Iterator[A] = new SetNIterator[A](size) { + def apply(i: Int) = getElem(i) + } + private def getElem(i: Int) = i match { case 0 => elem1 case 1 => elem2 case 2 => elem3 } + + override def foreach[U](f: A => U): Unit = { + f(elem1); f(elem2); f(elem3) + } + override def exists(p: A => Boolean): Boolean = { + p(elem1) || p(elem2) || p(elem3) + } + override def forall(p: A => Boolean): Boolean = { + p(elem1) && p(elem2) && p(elem3) + } + override protected[collection] def filterImpl(pred: A => Boolean, isFlipped: Boolean): Set[A] = { + var r1, r2: A = null.asInstanceOf[A] + var n = 0 + if (pred(elem1) != isFlipped) { r1 = elem1; n += 1} + if (pred(elem2) != isFlipped) { if (n == 0) r1 = elem2 else r2 = elem2; n += 1} + if (pred(elem3) != isFlipped) { if (n == 0) r1 = elem3 else if (n == 1) r2 = elem3; n += 1} + + n match { + case 0 => Set.empty + case 1 => new Set1(r1) + case 2 => new Set2(r1, r2) + case 3 => this + } + } + override def find(p: A => Boolean): Option[A] = { + if (p(elem1)) Some(elem1) + else if (p(elem2)) Some(elem2) + else if (p(elem3)) Some(elem3) + else None + } + override def head: A = elem1 + override def tail: Set[A] = new Set2(elem2, elem3) + } + + /** An optimized representation for immutable sets of size 4 */ + @SerialVersionUID(3L) + final class Set4[A] private[collection] (elem1: A, elem2: A, elem3: A, elem4: A) extends AbstractSet[A] with StrictOptimizedIterableOps[A, Set, Set[A]] with Serializable { + override def size: Int = 4 + override def isEmpty = false + override def knownSize: Int = size + def contains(elem: A): Boolean = + elem == elem1 || elem == elem2 || elem == elem3 || elem == elem4 + def incl(elem: A): Set[A] = + if (contains(elem)) this + else HashSet.empty[A] + elem1 + elem2 + elem3 + elem4 + elem + def excl(elem: A): Set[A] = + if (elem == elem1) new Set3(elem2, elem3, elem4) + else if (elem == elem2) new Set3(elem1, elem3, elem4) + else if (elem == elem3) new Set3(elem1, elem2, elem4) + else if (elem == elem4) new Set3(elem1, elem2, elem3) + else this + def iterator: Iterator[A] = new SetNIterator[A](size) { + def apply(i: Int) = getElem(i) + } + private def getElem(i: Int) = i match { case 0 => elem1 case 1 => elem2 case 2 => elem3 case 3 => elem4 } + + override def foreach[U](f: A => U): Unit = { + f(elem1); f(elem2); f(elem3); f(elem4) + } + override def exists(p: A => Boolean): Boolean = { + p(elem1) || p(elem2) || p(elem3) || p(elem4) + } + override def forall(p: A => Boolean): Boolean = { + p(elem1) && p(elem2) && p(elem3) && p(elem4) + } + override protected[collection] def filterImpl(pred: A => Boolean, isFlipped: Boolean): Set[A] = { + var r1, r2, r3: A = null.asInstanceOf[A] + var n = 0 + if (pred(elem1) != isFlipped) { r1 = elem1; n += 1} + if (pred(elem2) != isFlipped) { if (n == 0) r1 = elem2 else r2 = elem2; n += 1} + if (pred(elem3) != isFlipped) { if (n == 0) r1 = elem3 else if (n == 1) r2 = elem3 else r3 = elem3; n += 1} + if (pred(elem4) != isFlipped) { if (n == 0) r1 = elem4 else if (n == 1) r2 = elem4 else if (n == 2) r3 = elem4; n += 1} + + n match { + case 0 => Set.empty + case 1 => new Set1(r1) + case 2 => new Set2(r1, r2) + case 3 => new Set3(r1, r2, r3) + case 4 => this + } + } + + override def find(p: A => Boolean): Option[A] = { + if (p(elem1)) Some(elem1) + else if (p(elem2)) Some(elem2) + else if (p(elem3)) Some(elem3) + else if (p(elem4)) Some(elem4) + else None + } + override def head: A = elem1 + override def tail: Set[A] = new Set3(elem2, elem3, elem4) + + private[immutable] def buildTo(builder: Builder[A, Set[A]]): builder.type = + builder.addOne(elem1).addOne(elem2).addOne(elem3).addOne(elem4) + } +} + +/** Explicit instantiation of the `Set` trait to reduce class file size in subclasses. */ +abstract class AbstractSet[A] extends scala.collection.AbstractSet[A] with Set[A] + +/** Builder for Set. + * $multipleResults + */ +private final class SetBuilderImpl[A] extends ReusableBuilder[A, Set[A]] { + private[this] var elems: Set[A] = Set.empty + private[this] var switchedToHashSetBuilder: Boolean = false + private[this] var hashSetBuilder: HashSetBuilder[A] = _ + + override def clear(): Unit = { + elems = Set.empty + if (hashSetBuilder != null) { + hashSetBuilder.clear() + } + switchedToHashSetBuilder = false + } + + override def result(): Set[A] = + if (switchedToHashSetBuilder) hashSetBuilder.result() else elems + + def addOne(elem: A) = { + if (switchedToHashSetBuilder) { + hashSetBuilder.addOne(elem) + } else if (elems.size < 4) { + elems = elems + elem + } else { + // assert(elems.size == 4) + if (elems.contains(elem)) { + () // do nothing + } else { + switchedToHashSetBuilder = true + if (hashSetBuilder == null) { + hashSetBuilder = new HashSetBuilder + } + elems.asInstanceOf[Set4[A]].buildTo(hashSetBuilder) + hashSetBuilder.addOne(elem) + } + } + + this + } + + override def addAll(xs: IterableOnce[A]): this.type = + if (switchedToHashSetBuilder) { + hashSetBuilder.addAll(xs) + this + } else { + super.addAll(xs) + } +} diff --git a/library/src/scala/collection/immutable/SortedMap.scala b/library/src/scala/collection/immutable/SortedMap.scala new file mode 100644 index 000000000000..a25321235f89 --- /dev/null +++ b/library/src/scala/collection/immutable/SortedMap.scala @@ -0,0 +1,178 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import scala.annotation.unchecked.uncheckedVariance +import scala.collection.mutable.Builder + +/** An immutable map whose key-value pairs are sorted according to an [[scala.math.Ordering]] on the keys. + * + * Allows for range queries to be performed on its keys, and implementations must guarantee that traversal happens in + * sorted order, according to the map's [[scala.math.Ordering]]. + * + * @example {{{ + * import scala.collection.immutable.SortedMap + * + * // Make a SortedMap via the companion object factory + * val weekdays = SortedMap( + * 2 -> "Monday", + * 3 -> "Tuesday", + * 4 -> "Wednesday", + * 5 -> "Thursday", + * 6 -> "Friday" + * ) + * // TreeMap(2 -> Monday, 3 -> Tuesday, 4 -> Wednesday, 5 -> Thursday, 6 -> Friday) + * + * val days = weekdays ++ List(1 -> "Sunday", 7 -> "Saturday") + * // TreeMap(1 -> Sunday, 2 -> Monday, 3 -> Tuesday, 4 -> Wednesday, 5 -> Thursday, 6 -> Friday, 7 -> Saturday) + * + * val day3 = days.get(3) // Some("Tuesday") + * + * val rangeOfDays = days.range(2, 5) // TreeMap(2 -> Monday, 3 -> Tuesday, 4 -> Wednesday) + * + * val daysUntil2 = days.rangeUntil(2) // TreeMap(1 -> Sunday) + * val daysTo2 = days.rangeTo(2) // TreeMap(1 -> Sunday, 2 -> Monday) + * val daysAfter5 = days.rangeFrom(5) // TreeMap(5 -> Thursday, 6 -> Friday, 7 -> Saturday) + * }}} + * + * @tparam K the type of the keys contained in this tree map. + * @tparam V the type of the values associated with the keys. + */ +trait SortedMap[K, +V] + extends Map[K, V] + with collection.SortedMap[K, V] + with SortedMapOps[K, V, SortedMap, SortedMap[K, V]] + with SortedMapFactoryDefaults[K, V, SortedMap, Iterable, Map] { + + override def unsorted: Map[K, V] = this + + override def sortedMapFactory: SortedMapFactory[SortedMap] = SortedMap + + /** The same map with a given default function. + * Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc. + * are not affected by `withDefault`. + * + * Invoking transformer methods (e.g. `map`) will not preserve the default value. + * + * @param d the function mapping keys to values, used for non-present keys + * @return a wrapper of the map with a default value + */ + override def withDefault[V1 >: V](d: K => V1): SortedMap[K, V1] = new SortedMap.WithDefault[K, V1](this, d) + + /** The same map with a given default value. + * Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc. + * are not affected by `withDefaultValue`. + * + * Invoking transformer methods (e.g. `map`) will not preserve the default value. + * + * @param d default value used for non-present keys + * @return a wrapper of the map with a default value + */ + override def withDefaultValue[V1 >: V](d: V1): SortedMap[K, V1] = new SortedMap.WithDefault[K, V1](this, _ => d) +} + +transparent trait SortedMapOps[K, +V, +CC[X, +Y] <: Map[X, Y] with SortedMapOps[X, Y, CC, _], +C <: SortedMapOps[K, V, CC, C]] + extends MapOps[K, V, Map, C] with collection.SortedMapOps[K, V, CC, C] { self => + + protected def coll: C with CC[K, V] + + def unsorted: Map[K, V] + + override def keySet: SortedSet[K] = new ImmutableKeySortedSet + + /** The implementation class of the set returned by `keySet` */ + protected class ImmutableKeySortedSet extends AbstractSet[K] with SortedSet[K] with GenKeySet with GenKeySortedSet { + def rangeImpl(from: Option[K], until: Option[K]): SortedSet[K] = { + val map = self.rangeImpl(from, until) + new map.ImmutableKeySortedSet + } + def incl(elem: K): SortedSet[K] = fromSpecific(this).incl(elem) + def excl(elem: K): SortedSet[K] = fromSpecific(this).excl(elem) + } + + // We override these methods to fix their return type (which would be `Map` otherwise) + def updated[V1 >: V](key: K, value: V1): CC[K, V1] + @`inline` final override def +[V1 >: V](kv: (K, V1)): CC[K, V1] = updated(kv._1, kv._2) + override def updatedWith[V1 >: V](key: K)(remappingFunction: Option[V] => Option[V1]): CC[K, V1] = { + // Implementation has been copied from `MapOps` + val previousValue = this.get(key) + remappingFunction(previousValue) match { + case None => previousValue.fold(coll)(_ => this.removed(key).coll) + case Some(nextValue) => + if (previousValue.exists(_.asInstanceOf[AnyRef] eq nextValue.asInstanceOf[AnyRef])) coll + else coll.updated(key, nextValue) + } + } + override def transform[W](f: (K, V) => W): CC[K, W] = map({ case (k, v) => (k, f(k, v)) })(ordering) +} + +transparent trait StrictOptimizedSortedMapOps[K, +V, +CC[X, +Y] <: Map[X, Y] with SortedMapOps[X, Y, CC, _], +C <: SortedMapOps[K, V, CC, C]] + extends SortedMapOps[K, V, CC, C] + with collection.StrictOptimizedSortedMapOps[K, V, CC, C] + with StrictOptimizedMapOps[K, V, Map, C] { + + override def concat[V2 >: V](xs: collection.IterableOnce[(K, V2)]): CC[K, V2] = { + var result: CC[K, V2] = coll + val it = xs.iterator + while (it.hasNext) result = result + it.next() + result + } +} + +@SerialVersionUID(3L) +object SortedMap extends SortedMapFactory.Delegate[SortedMap](TreeMap) { + + override def from[K: Ordering, V](it: IterableOnce[(K, V)]): SortedMap[K, V] = it match { + case sm: SortedMap[K, V] if Ordering[K] == sm.ordering => sm + case _ => super.from(it) + } + + final class WithDefault[K, +V](underlying: SortedMap[K, V], defaultValue: K => V) + extends Map.WithDefault[K, V](underlying, defaultValue) + with SortedMap[K, V] + with SortedMapOps[K, V, SortedMap, WithDefault[K, V]] with Serializable { + + implicit def ordering: Ordering[K] = underlying.ordering + + override def sortedMapFactory: SortedMapFactory[SortedMap] = underlying.sortedMapFactory + + def iteratorFrom(start: K): scala.collection.Iterator[(K, V)] = underlying.iteratorFrom(start) + + def keysIteratorFrom(start: K): scala.collection.Iterator[K] = underlying.keysIteratorFrom(start) + + def rangeImpl(from: Option[K], until: Option[K]): WithDefault[K, V] = + new WithDefault[K, V](underlying.rangeImpl(from, until), defaultValue) + + // Need to override following methods to match type signatures of `SortedMap.WithDefault` + // for operations preserving default value + + override def updated[V1 >: V](key: K, value: V1): WithDefault[K, V1] = + new WithDefault[K, V1](underlying.updated(key, value), defaultValue) + + override def concat [V2 >: V](xs: collection.IterableOnce[(K, V2)]): WithDefault[K, V2] = + new WithDefault( underlying.concat(xs) , defaultValue) + + override def removed(key: K): WithDefault[K, V] = new WithDefault[K, V](underlying.removed(key), defaultValue) + + override def empty: WithDefault[K, V] = new WithDefault[K, V](underlying.empty, defaultValue) + + override protected def fromSpecific(coll: scala.collection.IterableOnce[(K, V)] @uncheckedVariance): WithDefault[K, V] = + new WithDefault[K, V](sortedMapFactory.from(coll), defaultValue) + + override protected def newSpecificBuilder: Builder[(K, V), WithDefault[K, V]] @uncheckedVariance = + SortedMap.newBuilder.mapResult((p: SortedMap[K, V]) => new WithDefault[K, V](p, defaultValue)) + } +} diff --git a/library/src/scala/collection/immutable/SortedSet.scala b/library/src/scala/collection/immutable/SortedSet.scala new file mode 100644 index 000000000000..e8bbf1810d48 --- /dev/null +++ b/library/src/scala/collection/immutable/SortedSet.scala @@ -0,0 +1,59 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` + +/** Base trait for sorted sets */ +trait SortedSet[A] + extends Set[A] + with collection.SortedSet[A] + with SortedSetOps[A, SortedSet, SortedSet[A]] + with SortedSetFactoryDefaults[A, SortedSet, Set] { + + override def unsorted: Set[A] = this + + override def sortedIterableFactory: SortedIterableFactory[SortedSet] = SortedSet +} + +/** + * @define coll immutable sorted set + * @define Coll `immutable.SortedSet` + */ +transparent trait SortedSetOps[A, +CC[X] <: SortedSet[X], +C <: SortedSetOps[A, CC, C]] + extends SetOps[A, Set, C] + with collection.SortedSetOps[A, CC, C] { + + def unsorted: Set[A] +} + +transparent trait StrictOptimizedSortedSetOps[A, +CC[X] <: SortedSet[X], +C <: SortedSetOps[A, CC, C]] + extends SortedSetOps[A, CC, C] + with collection.StrictOptimizedSortedSetOps[A, CC, C] + with StrictOptimizedSetOps[A, Set, C] { +} + +/** + * $factoryInfo + * @define coll immutable sorted set + * @define Coll `immutable.SortedSet` + */ +@SerialVersionUID(3L) +object SortedSet extends SortedIterableFactory.Delegate[SortedSet](TreeSet) { + override def from[E: Ordering](it: IterableOnce[E]): SortedSet[E] = it match { + case ss: SortedSet[E] if Ordering[E] == ss.ordering => ss + case _ => super.from(it) + } +} diff --git a/library/src/scala/collection/immutable/Stream.scala b/library/src/scala/collection/immutable/Stream.scala new file mode 100644 index 000000000000..a7017a23a16d --- /dev/null +++ b/library/src/scala/collection/immutable/Stream.scala @@ -0,0 +1,570 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import java.io.{ObjectInputStream, ObjectOutputStream} +import java.lang.{StringBuilder => JStringBuilder} + +import scala.annotation.tailrec +import scala.annotation.unchecked.uncheckedVariance +import scala.collection.generic.SerializeEnd +import scala.collection.mutable.{ArrayBuffer, StringBuilder} +import scala.language.implicitConversions +import Stream.cons + +@deprecated("Use LazyList (which is fully lazy) instead of Stream (which has a lazy tail only)", "2.13.0") +@SerialVersionUID(3L) +sealed abstract class Stream[+A] extends AbstractSeq[A] + with LinearSeq[A] + with LinearSeqOps[A, Stream, Stream[A]] + with IterableFactoryDefaults[A, Stream] + with Serializable { + def tail: Stream[A] + + /** Forces evaluation of the whole `Stream` and returns it. + * + * @note Often we use `Stream`s to represent an infinite set or series. If + * that's the case for your particular `Stream` then this function will never + * return and will probably crash the VM with an `OutOfMemory` exception. + * This function will not hang on a finite cycle, however. + * + * @return The fully realized `Stream`. + */ + def force: this.type + + override def iterableFactory: SeqFactory[Stream] = Stream + + override protected[this] def className: String = "Stream" + + /** Apply the given function `f` to each element of this linear sequence + * (while respecting the order of the elements). + * + * @param f The treatment to apply to each element. + * @note Overridden here as final to trigger tail-call optimization, which + * replaces 'this' with 'tail' at each iteration. This is absolutely + * necessary for allowing the GC to collect the underlying Stream as elements + * are consumed. + * @note This function will force the realization of the entire Stream + * unless the `f` throws an exception. + */ + @tailrec + override final def foreach[U](f: A => U): Unit = { + if (!this.isEmpty) { + f(head) + tail.foreach(f) + } + } + + @tailrec + override final def find(p: A => Boolean): Option[A] = { + if(isEmpty) None + else if(p(head)) Some(head) + else tail.find(p) + } + + override def take(n: Int): Stream[A] = { + if (n <= 0 || isEmpty) Stream.empty + else if (n == 1) new Stream.Cons(head, Stream.empty) + else new Stream.Cons(head, tail.take(n - 1)) + } + + /** Stream specialization of foldLeft which allows GC to collect along the + * way. + * + * @tparam B The type of value being accumulated. + * @param z The initial value seeded into the function `op`. + * @param op The operation to perform on successive elements of the `Stream`. + * @return The accumulated value from successive applications of `op`. + */ + @tailrec + override final def foldLeft[B](z: B)(op: (B, A) => B): B = { + if (this.isEmpty) z + else tail.foldLeft(op(z, head))(op) + } + + /** The stream resulting from the concatenation of this stream with the argument stream. + * @param rest The collection that gets appended to this stream + * @return The stream containing elements of this stream and the iterable object. + */ + @deprecated("The `append` operation has been renamed `lazyAppendedAll`", "2.13.0") + @inline final def append[B >: A](rest: => IterableOnce[B]): Stream[B] = lazyAppendedAll(rest) + + protected[this] def writeReplace(): AnyRef = + if(nonEmpty && tailDefined) new Stream.SerializationProxy[A](this) else this + + /** Prints elements of this stream one by one, separated by commas. */ + @deprecated(message = """Use print(stream.force.mkString(", ")) instead""", since = "2.13.0") + @inline def print(): Unit = Console.print(this.force.mkString(", ")) + + /** Prints elements of this stream one by one, separated by `sep`. + * @param sep The separator string printed between consecutive elements. + */ + @deprecated(message = "Use print(stream.force.mkString(sep)) instead", since = "2.13.0") + @inline def print(sep: String): Unit = Console.print(this.force.mkString(sep)) + + /** The stream resulting from the concatenation of this stream with the argument stream. + * + * @param suffix The collection that gets appended to this stream + * @return The stream containing elements of this stream and the iterable object. + */ + def lazyAppendedAll[B >: A](suffix: => collection.IterableOnce[B]): Stream[B] = + if (isEmpty) iterableFactory.from(suffix) else cons[B](head, tail.lazyAppendedAll(suffix)) + + override def scanLeft[B](z: B)(op: (B, A) => B): Stream[B] = + if (isEmpty) z +: iterableFactory.empty + else cons(z, tail.scanLeft(op(z, head))(op)) + + /** Stream specialization of reduceLeft which allows GC to collect + * along the way. + * + * @tparam B The type of value being accumulated. + * @param f The operation to perform on successive elements of the `Stream`. + * @return The accumulated value from successive applications of `f`. + */ + override final def reduceLeft[B >: A](f: (B, A) => B): B = { + if (this.isEmpty) throw new UnsupportedOperationException("empty.reduceLeft") + else { + var reducedRes: B = this.head + var left: Stream[A] = this.tail + while (!left.isEmpty) { + reducedRes = f(reducedRes, left.head) + left = left.tail + } + reducedRes + } + } + + override def partition(p: A => Boolean): (Stream[A], Stream[A]) = (filter(p(_)), filterNot(p(_))) + + override def filter(pred: A => Boolean): Stream[A] = filterImpl(pred, isFlipped = false) + + override def filterNot(pred: A => Boolean): Stream[A] = filterImpl(pred, isFlipped = true) + + private[immutable] def filterImpl(p: A => Boolean, isFlipped: Boolean): Stream[A] = { + // optimization: drop leading prefix of elems for which f returns false + // var rest = this dropWhile (!p(_)) - forget DRY principle - GC can't collect otherwise + var rest: Stream[A] = coll + while (rest.nonEmpty && p(rest.head) == isFlipped) rest = rest.tail + // private utility func to avoid `this` on stack (would be needed for the lazy arg) + if (rest.nonEmpty) Stream.filteredTail(rest, p, isFlipped) + else iterableFactory.empty + } + + /** A `collection.WithFilter` which allows GC of the head of stream during processing */ + override final def withFilter(p: A => Boolean): collection.WithFilter[A, Stream] = + Stream.withFilter(coll, p) + + override final def prepended[B >: A](elem: B): Stream[B] = cons(elem, coll) + + override final def map[B](f: A => B): Stream[B] = + if (isEmpty) iterableFactory.empty + else cons(f(head), tail.map(f)) + + @tailrec override final def collect[B](pf: PartialFunction[A, B]): Stream[B] = + if(isEmpty) Stream.empty + else { + var newHead: B = null.asInstanceOf[B] + val runWith = pf.runWith((b: B) => newHead = b) + if(runWith(head)) Stream.collectedTail(newHead, this, pf) + else tail.collect(pf) + } + + @tailrec override final def collectFirst[B](pf: PartialFunction[A, B]): Option[B] = + if(isEmpty) None + else { + var newHead: B = null.asInstanceOf[B] + val runWith = pf.runWith((b: B) => newHead = b) + if(runWith(head)) Some(newHead) + else tail.collectFirst(pf) + } + + // optimisations are not for speed, but for functionality + // see tickets #153, #498, #2147, and corresponding tests in run/ (as well as run/stream_flatmap_odds.scala) + override final def flatMap[B](f: A => IterableOnce[B]): Stream[B] = + if (isEmpty) iterableFactory.empty + else { + // establish !prefix.isEmpty || nonEmptyPrefix.isEmpty + var nonEmptyPrefix: Stream[A] = coll + var prefix = iterableFactory.from(f(nonEmptyPrefix.head)) + while (!nonEmptyPrefix.isEmpty && prefix.isEmpty) { + nonEmptyPrefix = nonEmptyPrefix.tail + if(!nonEmptyPrefix.isEmpty) + prefix = iterableFactory.from(f(nonEmptyPrefix.head)) + } + + if (nonEmptyPrefix.isEmpty) iterableFactory.empty + else prefix.lazyAppendedAll(nonEmptyPrefix.tail.flatMap(f)) + } + + override final def zip[B](that: collection.IterableOnce[B]): Stream[(A, B)] = + if (this.isEmpty || that.isEmpty) iterableFactory.empty + else { + val thatIterable = that match { + case that: collection.Iterable[B] => that + case _ => LazyList.from(that) + } + cons[(A, B)]((this.head, thatIterable.head), this.tail.zip(thatIterable.tail)) + } + + override final def zipWithIndex: Stream[(A, Int)] = this.zip(LazyList.from(0)) + + protected def tailDefined: Boolean + + /** Appends all elements of this $coll to a string builder using start, end, and separator strings. + * The written text begins with the string `start` and ends with the string `end`. + * Inside, the string representations (w.r.t. the method `toString`) + * of all elements of this $coll are separated by the string `sep`. + * + * Undefined elements are represented with `"_"`, an undefined tail is represented with `"<not computed>"`, + * and cycles are represented with `"<cycle>"`. + * + * @param sb the string builder to which elements are appended. + * @param start the starting string. + * @param sep the separator string. + * @param end the ending string. + * @return the string builder `b` to which elements were appended. + */ + override def addString(sb: StringBuilder, start: String, sep: String, end: String): sb.type = { + force + addStringNoForce(sb.underlying, start, sep, end) + sb + } + + private[this] def addStringNoForce(b: JStringBuilder, start: String, sep: String, end: String): b.type = { + b.append(start) + if (nonEmpty) { + b.append(head) + var cursor = this + def appendCursorElement(): Unit = b.append(sep).append(cursor.head) + if (tailDefined) { // If tailDefined, also !isEmpty + var scout = tail + if (cursor ne scout) { + cursor = scout + if (scout.tailDefined) { + scout = scout.tail + // Use 2x 1x iterator trick for cycle detection; slow iterator can add strings + while ((cursor ne scout) && scout.tailDefined) { + appendCursorElement() + cursor = cursor.tail + scout = scout.tail + if (scout.tailDefined) scout = scout.tail + } + } + } + if (!scout.tailDefined) { // Not a cycle, scout hit an end + while (cursor ne scout) { + appendCursorElement() + cursor = cursor.tail + } + if (cursor.nonEmpty) { + appendCursorElement() + } + } + else { + // Cycle. + // If we have a prefix of length P followed by a cycle of length C, + // the scout will be at position (P%C) in the cycle when the cursor + // enters it at P. They'll then collide when the scout advances another + // C - (P%C) ahead of the cursor. + // If we run the scout P farther, then it will be at the start of + // the cycle: (C - (P%C) + (P%C)) == C == 0. So if another runner + // starts at the beginning of the prefix, they'll collide exactly at + // the start of the loop. + var runner = this + var k = 0 + while (runner ne scout) { + runner = runner.tail + scout = scout.tail + k += 1 + } + // Now runner and scout are at the beginning of the cycle. Advance + // cursor, adding to string, until it hits; then we'll have covered + // everything once. If cursor is already at beginning, we'd better + // advance one first unless runner didn't go anywhere (in which case + // we've already looped once). + if ((cursor eq scout) && (k > 0)) { + appendCursorElement() + cursor = cursor.tail + } + while (cursor ne scout) { + appendCursorElement() + cursor = cursor.tail + } + } + } + if (cursor.nonEmpty) { + // Either undefined or cyclic; we can check with tailDefined + if (!cursor.tailDefined) b.append(sep).append("") + else b.append(sep).append("") + } + } + b.append(end) + b + } + + /** + * @return a string representation of this collection. Undefined elements are + * represented with `"_"`, an undefined tail is represented with `"<not computed>"`, + * and cycles are represented with `"<cycle>"` + * + * Examples: + * + * - `"Stream(_, <not computed>)"`, a non-empty stream, whose head has not been + * evaluated ; + * - `"Stream(_, 1, _, <not computed>)"`, a stream with at least three elements, + * the second one has been evaluated ; + * - `"Stream(1, 2, 3, <cycle>)"`, an infinite stream that contains + * a cycle at the fourth element. + */ + override def toString = addStringNoForce(new JStringBuilder(className), "(", ", ", ")").toString + + @deprecated("Check .knownSize instead of .hasDefiniteSize for more actionable information (see scaladoc for details)", "2.13.0") + override def hasDefiniteSize: Boolean = isEmpty || { + if (!tailDefined) false + else { + // Two-iterator trick (2x & 1x speed) for cycle detection. + var those = this + var these = tail + while (those ne these) { + if (these.isEmpty) return true + if (!these.tailDefined) return false + these = these.tail + if (these.isEmpty) return true + if (!these.tailDefined) return false + these = these.tail + if (those eq these) return false + those = those.tail + } + false // Cycle detected + } + } +} + +@deprecated("Use LazyList (which is fully lazy) instead of Stream (which has a lazy tail only)", "2.13.0") +@SerialVersionUID(3L) +object Stream extends SeqFactory[Stream] { + + /* !!! #11997 This `object cons` must be defined lexically *before* `class Cons` below. + * Otherwise it prevents Scala.js from building on Windows. + */ + /** An alternative way of building and matching Streams using Stream.cons(hd, tl). + */ + object cons { + /** A stream consisting of a given first element and remaining elements + * @param hd The first element of the result stream + * @param tl The remaining elements of the result stream + */ + def apply[A](hd: A, tl: => Stream[A]): Stream[A] = new Cons(hd, tl) + + /** Maps a stream to its head and tail */ + def unapply[A](xs: Stream[A]): Option[(A, Stream[A])] = #::.unapply(xs) + } + + //@SerialVersionUID(3L) //TODO Putting an annotation on Stream.empty causes a cyclic dependency in unpickling + object Empty extends Stream[Nothing] { + override def isEmpty: Boolean = true + override def head: Nothing = throw new NoSuchElementException("head of empty stream") + override def tail: Stream[Nothing] = throw new UnsupportedOperationException("tail of empty stream") + /** Forces evaluation of the whole `Stream` and returns it. + * + * @note Often we use `Stream`s to represent an infinite set or series. If + * that's the case for your particular `Stream` then this function will never + * return and will probably crash the VM with an `OutOfMemory` exception. + * This function will not hang on a finite cycle, however. + * + * @return The fully realized `Stream`. + */ + def force: this.type = this + override def knownSize: Int = 0 + protected def tailDefined: Boolean = false + } + + @SerialVersionUID(3L) + final class Cons[A](override val head: A, tl: => Stream[A]) extends Stream[A] { + override def isEmpty: Boolean = false + @volatile private[this] var tlVal: Stream[A] = _ + @volatile private[this] var tlGen = () => tl + protected def tailDefined: Boolean = tlGen eq null + override def tail: Stream[A] = { + if (!tailDefined) + synchronized { + if (!tailDefined) { + tlVal = tlGen() + tlGen = null + } + } + tlVal + } + + /** Forces evaluation of the whole `Stream` and returns it. + * + * @note Often we use `Stream`s to represent an infinite set or series. If + * that's the case for your particular `Stream` then this function will never + * return and will probably crash the VM with an `OutOfMemory` exception. + * This function will not hang on a finite cycle, however. + * + * @return The fully realized `Stream`. + */ + def force: this.type = { + // Use standard 2x 1x iterator trick for cycle detection ("those" is slow one) + var these, those: Stream[A] = this + if (!these.isEmpty) these = these.tail + while (those ne these) { + if (these.isEmpty) return this + these = these.tail + if (these.isEmpty) return this + these = these.tail + if (these eq those) return this + those = those.tail + } + this + } + + } + + implicit def toDeferrer[A](l: => Stream[A]): Deferrer[A] = new Deferrer[A](() => l) + + final class Deferrer[A] private[Stream] (private val l: () => Stream[A]) extends AnyVal { + /** Construct a Stream consisting of a given first element followed by elements + * from another Stream. + */ + def #:: [B >: A](elem: B): Stream[B] = new Cons(elem, l()) + /** Construct a Stream consisting of the concatenation of the given Stream and + * another Stream. + */ + def #:::[B >: A](prefix: Stream[B]): Stream[B] = prefix lazyAppendedAll l() + } + + object #:: { + def unapply[A](s: Stream[A]): Option[(A, Stream[A])] = + if (s.nonEmpty) Some((s.head, s.tail)) else None + } + + def from[A](coll: collection.IterableOnce[A]): Stream[A] = coll match { + case coll: Stream[A] => coll + case _ => fromIterator(coll.iterator) + } + + /** + * @return A `Stream[A]` that gets its elements from the given `Iterator`. + * + * @param it Source iterator + * @tparam A type of elements + */ + // Note that the resulting `Stream` will be effectively iterable more than once because + // `Stream` memoizes its elements + def fromIterator[A](it: Iterator[A]): Stream[A] = + if (it.hasNext) { + new Stream.Cons(it.next(), fromIterator(it)) + } else Stream.Empty + + def empty[A]: Stream[A] = Empty + + override def newBuilder[A]: mutable.Builder[A, Stream[A]] = ArrayBuffer.newBuilder[A].mapResult(array => from(array)) + + private[immutable] def withFilter[A](l: Stream[A] @uncheckedVariance, p: A => Boolean): collection.WithFilter[A, Stream] = + new WithFilter[A](l, p) + + private[this] final class WithFilter[A](l: Stream[A] @uncheckedVariance, p: A => Boolean) extends collection.WithFilter[A, Stream] { + private[this] var s = l // set to null to allow GC after filtered + private[this] lazy val filtered: Stream[A] = { val f = s.filter(p); s = null.asInstanceOf[Stream[A]]; f } // don't set to null if throw during filter + def map[B](f: A => B): Stream[B] = filtered.map(f) + def flatMap[B](f: A => IterableOnce[B]): Stream[B] = filtered.flatMap(f) + def foreach[U](f: A => U): Unit = filtered.foreach(f) + def withFilter(q: A => Boolean): collection.WithFilter[A, Stream] = new WithFilter(filtered, q) + } + + /** An infinite Stream that repeatedly applies a given function to a start value. + * + * @param start the start value of the Stream + * @param f the function that's repeatedly applied + * @return the Stream returning the infinite sequence of values `start, f(start), f(f(start)), ...` + */ + def iterate[A](start: A)(f: A => A): Stream[A] = { + cons(start, iterate(f(start))(f)) + } + + /** + * Create an infinite Stream starting at `start` and incrementing by + * step `step`. + * + * @param start the start value of the Stream + * @param step the increment value of the Stream + * @return the Stream starting at value `start`. + */ + def from(start: Int, step: Int): Stream[Int] = + cons(start, from(start + step, step)) + + /** + * Create an infinite Stream starting at `start` and incrementing by `1`. + * + * @param start the start value of the Stream + * @return the Stream starting at value `start`. + */ + def from(start: Int): Stream[Int] = from(start, 1) + + /** + * Create an infinite Stream containing the given element expression (which + * is computed for each occurrence). + * + * @param elem the element composing the resulting Stream + * @return the Stream containing an infinite number of elem + */ + def continually[A](elem: => A): Stream[A] = cons(elem, continually(elem)) + + + private[Stream] def filteredTail[A](stream: Stream[A] @uncheckedVariance, p: A => Boolean, isFlipped: Boolean) = { + cons(stream.head, stream.tail.filterImpl(p, isFlipped)) + } + + private[Stream] def collectedTail[A, B](head: B, stream: Stream[A] @uncheckedVariance, pf: PartialFunction[A, B]) = { + cons(head, stream.tail.collect(pf)) + } + + /** This serialization proxy is used for Streams which start with a sequence of evaluated cons cells. + * The forced sequence is serialized in a compact, sequential format, followed by the unevaluated tail, which uses + * standard Java serialization to store the complete structure of unevaluated thunks. This allows the serialization + * of long evaluated streams without exhausting the stack through recursive serialization of cons cells. + */ + @SerialVersionUID(3L) + class SerializationProxy[A](@transient protected var coll: Stream[A]) extends Serializable { + + private[this] def writeObject(out: ObjectOutputStream): Unit = { + out.defaultWriteObject() + var these = coll + while(these.nonEmpty && these.tailDefined) { + out.writeObject(these.head) + these = these.tail + } + out.writeObject(SerializeEnd) + out.writeObject(these) + } + + private[this] def readObject(in: ObjectInputStream): Unit = { + in.defaultReadObject() + val init = new ArrayBuffer[A] + var initRead = false + while (!initRead) in.readObject match { + case SerializeEnd => initRead = true + case a => init += a.asInstanceOf[A] + } + val tail = in.readObject().asInstanceOf[Stream[A]] + coll = (init ++: tail) + } + + protected[this] def readResolve(): Any = coll + } +} diff --git a/library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala b/library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala new file mode 100644 index 000000000000..51be923eb719 --- /dev/null +++ b/library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala @@ -0,0 +1,85 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import scala.collection.generic.CommonErrors + +/** Trait that overrides operations to take advantage of strict builders. + */ +transparent trait StrictOptimizedSeqOps[+A, +CC[_], +C] + extends Any + with SeqOps[A, CC, C] + with collection.StrictOptimizedSeqOps[A, CC, C] + with StrictOptimizedIterableOps[A, CC, C] { + + override def distinctBy[B](f: A => B): C = { + if (lengthCompare(1) <= 0) coll + else { + val builder = newSpecificBuilder + val seen = mutable.HashSet.empty[B] + val it = this.iterator + var different = false + while (it.hasNext) { + val next = it.next() + if (seen.add(f(next))) builder += next else different = true + } + if (different) builder.result() else coll + } + } + + override def updated[B >: A](index: Int, elem: B): CC[B] = { + if (index < 0) + throw ( + if (knownSize >= 0) CommonErrors.indexOutOfBounds(index = index, max = knownSize) + else CommonErrors.indexOutOfBounds(index = index) + ) + val b = iterableFactory.newBuilder[B] + b.sizeHint(this) + var i = 0 + val it = iterator + while (i < index && it.hasNext) { + b += it.next() + i += 1 + } + if (!it.hasNext) + throw CommonErrors.indexOutOfBounds(index = index, max = i - 1) + b += elem + it.next() + while (it.hasNext) b += it.next() + b.result() + } + + override def patch[B >: A](from: Int, other: IterableOnce[B], replaced: Int): CC[B] = { + val b = iterableFactory.newBuilder[B] + var i = 0 + val it = iterator + while (i < from && it.hasNext) { + b += it.next() + i += 1 + } + b ++= other + i = replaced + while (i > 0 && it.hasNext) { + it.next() + i -= 1 + } + while (it.hasNext) b += it.next() + b.result() + } + + override def sorted[B >: A](implicit ord: Ordering[B]): C = super.sorted(ord) + +} diff --git a/library/src/scala/collection/immutable/TreeMap.scala b/library/src/scala/collection/immutable/TreeMap.scala new file mode 100644 index 000000000000..b4a7e4b6605d --- /dev/null +++ b/library/src/scala/collection/immutable/TreeMap.scala @@ -0,0 +1,381 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import scala.annotation.tailrec +import scala.collection.Stepper.EfficientSplit +import scala.collection.generic.DefaultSerializable +import scala.collection.immutable.{RedBlackTree => RB} +import scala.collection.mutable.ReusableBuilder +import scala.runtime.AbstractFunction2 + +/** An immutable SortedMap whose values are stored in a red-black tree. + * + * This class is optimal when range queries will be performed, + * or when traversal in order of an ordering is desired. + * If you only need key lookups, and don't care in which order key-values + * are traversed in, consider using * [[scala.collection.immutable.HashMap]], + * which will generally have better performance. If you need insertion order, + * consider a * [[scala.collection.immutable.SeqMap]], which does not need to + * have an ordering supplied. + * + * @example {{{ + * import scala.collection.immutable.TreeMap + * + * // Make a TreeMap via the companion object factory + * val weekdays = TreeMap( + * 2 -> "Monday", + * 3 -> "Tuesday", + * 4 -> "Wednesday", + * 5 -> "Thursday", + * 6 -> "Friday" + * ) + * // TreeMap(2 -> Monday, 3 -> Tuesday, 4 -> Wednesday, 5 -> Thursday, 6 -> Friday) + * + * val days = weekdays ++ List(1 -> "Sunday", 7 -> "Saturday") + * // TreeMap(1 -> Sunday, 2 -> Monday, 3 -> Tuesday, 4 -> Wednesday, 5 -> Thursday, 6 -> Friday, 7 -> Saturday) + * + * val day3 = days.get(3) // Some("Tuesday") + * + * val rangeOfDays = days.range(2, 5) // TreeMap(2 -> Monday, 3 -> Tuesday, 4 -> Wednesday) + * + * val daysUntil2 = days.rangeUntil(2) // TreeMap(1 -> Sunday) + * val daysTo2 = days.rangeTo(2) // TreeMap(1 -> Sunday, 2 -> Monday) + * val daysAfter5 = days.rangeFrom(5) // TreeMap(5 -> Thursday, 6 -> Friday, 7 -> Saturday) + * }}} + * + * @tparam K the type of the keys contained in this tree map. + * @tparam V the type of the values associated with the keys. + * @param ordering the implicit ordering used to compare objects of type `A`. + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html#red-black-trees "Scala's Collection Library overview"]] + * section on `Red-Black Trees` for more information. + * + * @define Coll immutable.TreeMap + * @define coll immutable tree map + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +final class TreeMap[K, +V] private (private val tree: RB.Tree[K, V])(implicit val ordering: Ordering[K]) + extends AbstractMap[K, V] + with SortedMap[K, V] + with StrictOptimizedSortedMapOps[K, V, TreeMap, TreeMap[K, V]] + with SortedMapFactoryDefaults[K, V, TreeMap, Iterable, Map] + with DefaultSerializable { + + def this()(implicit ordering: Ordering[K]) = this(null)(ordering) + private[immutable] def tree0: RB.Tree[K, V] = tree + + private[this] def newMapOrSelf[V1 >: V](t: RB.Tree[K, V1]): TreeMap[K, V1] = if(t eq tree) this else new TreeMap[K, V1](t) + + override def sortedMapFactory: SortedMapFactory[TreeMap] = TreeMap + + def iterator: Iterator[(K, V)] = RB.iterator(tree) + + def keysIteratorFrom(start: K): Iterator[K] = RB.keysIterator(tree, Some(start)) + + override def keySet: TreeSet[K] = new TreeSet(tree)(ordering) + + def iteratorFrom(start: K): Iterator[(K, V)] = RB.iterator(tree, Some(start)) + + override def valuesIteratorFrom(start: K): Iterator[V] = RB.valuesIterator(tree, Some(start)) + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[(K, V), S]): S with EfficientSplit = + shape.parUnbox( + scala.collection.convert.impl.AnyBinaryTreeStepper.from[(K, V), RB.Tree[K, V]]( + size, tree, _.left, _.right, x => (x.key, x.value) + ) + ) + + override def keyStepper[S <: Stepper[_]](implicit shape: StepperShape[K, S]): S with EfficientSplit = { + import scala.collection.convert.impl._ + type T = RB.Tree[K, V] + val s = shape.shape match { + case StepperShape.IntShape => IntBinaryTreeStepper.from[T] (size, tree, _.left, _.right, _.key.asInstanceOf[Int]) + case StepperShape.LongShape => LongBinaryTreeStepper.from[T] (size, tree, _.left, _.right, _.key.asInstanceOf[Long]) + case StepperShape.DoubleShape => DoubleBinaryTreeStepper.from[T](size, tree, _.left, _.right, _.key.asInstanceOf[Double]) + case _ => shape.parUnbox(AnyBinaryTreeStepper.from[K, T](size, tree, _.left, _.right, _.key)) + } + s.asInstanceOf[S with EfficientSplit] + } + + override def valueStepper[S <: Stepper[_]](implicit shape: StepperShape[V, S]): S with EfficientSplit = { + import scala.collection.convert.impl._ + type T = RB.Tree[K, V] + val s = shape.shape match { + case StepperShape.IntShape => IntBinaryTreeStepper.from[T] (size, tree, _.left, _.right, _.value.asInstanceOf[Int]) + case StepperShape.LongShape => LongBinaryTreeStepper.from[T] (size, tree, _.left, _.right, _.value.asInstanceOf[Long]) + case StepperShape.DoubleShape => DoubleBinaryTreeStepper.from[T] (size, tree, _.left, _.right, _.value.asInstanceOf[Double]) + case _ => shape.parUnbox(AnyBinaryTreeStepper.from[V, T] (size, tree, _.left, _.right, _.value.asInstanceOf[V])) + } + s.asInstanceOf[S with EfficientSplit] + } + + def get(key: K): Option[V] = RB.get(tree, key) + override def getOrElse[V1 >: V](key: K, default: => V1): V1 = { + val resultOrNull = RB.lookup(tree, key) + if (resultOrNull eq null) default + else resultOrNull.value + } + + // override for performance -- no Some allocation + override def apply(key: K): V = { + val resultOrNull = RB.lookup(tree, key) + if (resultOrNull eq null) default(key) + else resultOrNull.value + } + + // override for performance -- no Some allocation + override def contains(key: K): Boolean = RB.contains(tree, key) + + def removed(key: K): TreeMap[K,V] = + newMapOrSelf(RB.delete(tree, key)) + + def updated[V1 >: V](key: K, value: V1): TreeMap[K, V1] = + newMapOrSelf(RB.update(tree, key, value, overwrite = true)) + + override def concat[V1 >: V](that: collection.IterableOnce[(K, V1)]): TreeMap[K, V1] = + newMapOrSelf(that match { + case tm: TreeMap[K, V] @unchecked if ordering == tm.ordering => + RB.union(tree, tm.tree) + case ls: LinearSeq[(K,V1)] => + if (ls.isEmpty) tree //to avoid the creation of the adder + else { + val adder = new Adder[V1] + adder.addAll(ls) + adder.finalTree + } + case _ => + val adder = new Adder[V1] + val it = that.iterator + while (it.hasNext) { + adder.apply(it.next()) + } + adder.finalTree + }) + + override def removedAll(keys: IterableOnce[K]): TreeMap[K, V] = keys match { + case ts: TreeSet[K] if ordering == ts.ordering => + newMapOrSelf(RB.difference(tree, ts.tree)) + case _ => super.removedAll(keys) + } + + /** A new TreeMap with the entry added is returned, + * assuming that key is not in the TreeMap. + * + * @tparam V1 type of the values of the new bindings, a supertype of `V` + * @param key the key to be inserted + * @param value the value to be associated with `key` + * @return a new $coll with the inserted binding, if it wasn't present in the map + */ + @deprecated("Use `updated` instead", "2.13.0") + def insert[V1 >: V](key: K, value: V1): TreeMap[K, V1] = { + assert(!RB.contains(tree, key)) + updated(key, value) + } + + def rangeImpl(from: Option[K], until: Option[K]): TreeMap[K, V] = newMapOrSelf(RB.rangeImpl(tree, from, until)) + + override def minAfter(key: K): Option[(K, V)] = RB.minAfter(tree, key) match { + case null => Option.empty + case x => Some((x.key, x.value)) + } + + override def maxBefore(key: K): Option[(K, V)] = RB.maxBefore(tree, key) match { + case null => Option.empty + case x => Some((x.key, x.value)) + } + + override def range(from: K, until: K): TreeMap[K,V] = newMapOrSelf(RB.range(tree, from, until)) + + override def foreach[U](f: ((K, V)) => U): Unit = RB.foreach(tree, f) + override def foreachEntry[U](f: (K, V) => U): Unit = RB.foreachEntry(tree, f) + override def size: Int = RB.count(tree) + override def knownSize: Int = size + + override def isEmpty = size == 0 + + override def firstKey: K = RB.smallest(tree).key + + override def lastKey: K = RB.greatest(tree).key + + override def head: (K, V) = { + val smallest = RB.smallest(tree) + (smallest.key, smallest.value) + } + + override def last: (K, V) = { + val greatest = RB.greatest(tree) + (greatest.key, greatest.value) + } + + override def tail: TreeMap[K, V] = new TreeMap(RB.tail(tree)) + + override def init: TreeMap[K, V] = new TreeMap(RB.init(tree)) + + override def drop(n: Int): TreeMap[K, V] = { + if (n <= 0) this + else if (n >= size) empty + else new TreeMap(RB.drop(tree, n)) + } + + override def take(n: Int): TreeMap[K, V] = { + if (n <= 0) empty + else if (n >= size) this + else new TreeMap(RB.take(tree, n)) + } + + override def slice(from: Int, until: Int) = { + if (until <= from) empty + else if (from <= 0) take(until) + else if (until >= size) drop(from) + else new TreeMap(RB.slice(tree, from, until)) + } + + override def dropRight(n: Int): TreeMap[K, V] = take(size - math.max(n, 0)) + + override def takeRight(n: Int): TreeMap[K, V] = drop(size - math.max(n, 0)) + + private[this] def countWhile(p: ((K, V)) => Boolean): Int = { + var result = 0 + val it = iterator + while (it.hasNext && p(it.next())) result += 1 + result + } + + override def dropWhile(p: ((K, V)) => Boolean): TreeMap[K, V] = drop(countWhile(p)) + + override def takeWhile(p: ((K, V)) => Boolean): TreeMap[K, V] = take(countWhile(p)) + + override def span(p: ((K, V)) => Boolean): (TreeMap[K, V], TreeMap[K, V]) = splitAt(countWhile(p)) + + override def filter(f: ((K, V)) => Boolean): TreeMap[K, V] = + newMapOrSelf(RB.filterEntries[K, V](tree, (k, v) => f((k, v)))) + + override def partition(p: ((K, V)) => Boolean): (TreeMap[K, V], TreeMap[K, V]) = { + val (l, r) = RB.partitionEntries[K, V](tree, (k, v) => p((k, v))) + (newMapOrSelf(l), newMapOrSelf(r)) + } + + override def transform[W](f: (K, V) => W): TreeMap[K, W] = { + val t2 = RB.transform[K, V, W](tree, f) + if(t2 eq tree) this.asInstanceOf[TreeMap[K, W]] + else new TreeMap(t2) + } + + private final class Adder[B1 >: V] + extends RB.MapHelper[K, B1] with Function1[(K, B1), Unit] { + private var currentMutableTree: RB.Tree[K,B1] = tree0 + def finalTree = beforePublish(currentMutableTree) + override def apply(kv: (K, B1)): Unit = { + currentMutableTree = mutableUpd(currentMutableTree, kv._1, kv._2) + } + @tailrec def addAll(ls: LinearSeq[(K, B1)]): Unit = { + if (!ls.isEmpty) { + val kv = ls.head + currentMutableTree = mutableUpd(currentMutableTree, kv._1, kv._2) + addAll(ls.tail) + } + } + } + override def equals(obj: Any): Boolean = obj match { + case that: TreeMap[K @unchecked, _] if ordering == that.ordering => RB.entriesEqual(tree, that.tree) + case _ => super.equals(obj) + } + + override protected[this] def className = "TreeMap" +} + +/** $factoryInfo + * @define Coll immutable.TreeMap + * @define coll immutable tree map + */ +@SerialVersionUID(3L) +object TreeMap extends SortedMapFactory[TreeMap] { + + def empty[K : Ordering, V]: TreeMap[K, V] = new TreeMap() + + def from[K, V](it: IterableOnce[(K, V)])(implicit ordering: Ordering[K]): TreeMap[K, V] = + it match { + case tm: TreeMap[K, V] if ordering == tm.ordering => tm + case sm: scala.collection.SortedMap[K, V] if ordering == sm.ordering => + new TreeMap[K, V](RB.fromOrderedEntries(sm.iterator, sm.size)) + case _ => + var t: RB.Tree[K, V] = null + val i = it.iterator + while (i.hasNext) { + val (k, v) = i.next() + t = RB.update(t, k, v, overwrite = true) + } + new TreeMap[K, V](t) + } + + def newBuilder[K, V](implicit ordering: Ordering[K]): ReusableBuilder[(K, V), TreeMap[K, V]] = new TreeMapBuilder[K, V] + + private class TreeMapBuilder[K, V](implicit ordering: Ordering[K]) + extends RB.MapHelper[K, V] + with ReusableBuilder[(K, V), TreeMap[K, V]] { + type Tree = RB.Tree[K, V] + private var tree:Tree = null + + def addOne(elem: (K, V)): this.type = { + tree = mutableUpd(tree, elem._1, elem._2) + this + } + private object adder extends AbstractFunction2[K, V, Unit] { + // we cache tree to avoid the outer access to tree + // in the hot path (apply) + private[this] var accumulator :Tree = null + def addForEach(hasForEach: collection.Map[K, V]): Unit = { + accumulator = tree + hasForEach.foreachEntry(this) + tree = accumulator + // be friendly to GC + accumulator = null + } + + override def apply(key: K, value: V): Unit = { + accumulator = mutableUpd(accumulator, key, value) + } + } + + override def addAll(xs: IterableOnce[(K, V)]): this.type = { + xs match { + // TODO consider writing a mutable-safe union for TreeSet/TreeMap builder ++= + // for the moment we have to force immutability before the union + // which will waste some time and space + // calling `beforePublish` makes `tree` immutable + case ts: TreeMap[K, V] if ts.ordering == ordering => + if (tree eq null) tree = ts.tree0 + else tree = RB.union(beforePublish(tree), ts.tree0) + case that: collection.Map[K, V] => + //add avoiding creation of tuples + adder.addForEach(that) + case _ => + super.addAll(xs) + } + this + } + + override def clear(): Unit = { + tree = null + } + + override def result(): TreeMap[K, V] = new TreeMap[K, V](beforePublish(tree)) + } +} diff --git a/library/src/scala/collection/immutable/TreeSeqMap.scala b/library/src/scala/collection/immutable/TreeSeqMap.scala new file mode 100644 index 000000000000..e3a7552c5934 --- /dev/null +++ b/library/src/scala/collection/immutable/TreeSeqMap.scala @@ -0,0 +1,651 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import scala.annotation.tailrec + +/** This class implements an immutable map that preserves order using + * a hash map for the key to value mapping to provide efficient lookup, + * and a tree for the ordering of the keys to provide efficient + * insertion/modification order traversal and destructuring. + * + * By default insertion order (`TreeSeqMap.OrderBy.Insertion`) + * is used, but modification order (`TreeSeqMap.OrderBy.Modification`) + * can be used instead if so specified at creation. + * + * The `orderingBy(orderBy: TreeSeqMap.OrderBy): TreeSeqMap[K, V]` method + * can be used to switch to the specified ordering for the returned map. + * + * A key can be manually refreshed (i.e. placed at the end) via the + * `refresh(key: K): TreeSeqMap[K, V]` method (regardless of the ordering in + * use). + * + * Internally, an ordinal counter is increased for each insertion/modification + * and then the current ordinal is used as key in the tree map. After 2^32^ + * insertions/modifications the entire map is copied (thus resetting the ordinal + * counter). + * + * @tparam K the type of the keys contained in this map. + * @tparam V the type of the values associated with the keys in this map. + * @define coll immutable tree seq map + * @define Coll `immutable.TreeSeqMap` + */ +final class TreeSeqMap[K, +V] private ( + private val ordering: TreeSeqMap.Ordering[K], + private val mapping: TreeSeqMap.Mapping[K, V], + private val ordinal: Int, + val orderedBy: TreeSeqMap.OrderBy) + extends AbstractMap[K, V] + with SeqMap[K, V] + with MapOps[K, V, TreeSeqMap, TreeSeqMap[K, V]] + with StrictOptimizedIterableOps[(K, V), Iterable, TreeSeqMap[K, V]] + with StrictOptimizedMapOps[K, V, TreeSeqMap, TreeSeqMap[K, V]] + with MapFactoryDefaults[K, V, TreeSeqMap, Iterable] { + + import TreeSeqMap._ + + override protected[this] def className: String = "TreeSeqMap" + + override def mapFactory: MapFactory[TreeSeqMap] = TreeSeqMap + + override val size = mapping.size + + override def knownSize: Int = size + + override def isEmpty = size == 0 + + /* + // This should have been overridden in 2.13.0 but wasn't so it will have to wait since it is not forwards compatible + // Now handled in inherited method from scala.collection.MapFactoryDefaults instead. + override def empty = TreeSeqMap.empty[K, V](orderedBy) + */ + + def orderingBy(orderBy: OrderBy): TreeSeqMap[K, V] = { + if (orderBy == this.orderedBy) this + else if (isEmpty) TreeSeqMap.empty(orderBy) + else new TreeSeqMap(ordering, mapping, ordinal, orderBy) + } + + def updated[V1 >: V](key: K, value: V1): TreeSeqMap[K, V1] = { + mapping.get(key) match { + case e if ordinal == -1 && (orderedBy == OrderBy.Modification || e.isEmpty) => + // Reinsert into fresh instance to restart ordinal counting, expensive but only done after 2^32 updates. + TreeSeqMap.empty[K, V1](orderedBy) ++ this + (key -> value) + case Some((o, _)) if orderedBy == OrderBy.Insertion => + new TreeSeqMap( + ordering.include(o, key), + mapping.updated[(Int, V1)](key, (o, value)), + ordinal, // Do not increment the ordinal since the key is already present, i.e. o <= ordinal. + orderedBy) + case Some((o, _)) => + val o1 = increment(ordinal) + new TreeSeqMap( + ordering.exclude(o).append(o1, key), + mapping.updated[(Int, V1)](key, (o1, value)), + o1, + orderedBy) + case None => + val o1 = increment(ordinal) + new TreeSeqMap( + ordering.append(o1, key), + mapping.updated[(Int, V1)](key, (o1, value)), + o1, + orderedBy) + } + } + + def removed(key: K): TreeSeqMap[K, V] = { + mapping.get(key) match { + case Some((o, _)) => + new TreeSeqMap( + ordering.exclude(o), + mapping.removed(key), + ordinal, + orderedBy) + case None => + this + } + } + + def refresh(key: K): TreeSeqMap[K, V] = { + mapping.get(key) match { + case Some((o, _)) => + val o1 = increment(ordinal) + new TreeSeqMap( + ordering.exclude(o).append(o1, key), + mapping, + o1, + orderedBy) + case None => + this + } + } + + def get(key: K): Option[V] = mapping.get(key).map(value) + + def iterator: Iterator[(K, V)] = new AbstractIterator[(K, V)] { + private[this] val iter = ordering.iterator + + override def hasNext: Boolean = iter.hasNext + + override def next(): (K, V) = binding(iter.next()) + } + + override def keysIterator: Iterator[K] = new AbstractIterator[K] { + private[this] val iter = ordering.iterator + + override def hasNext: Boolean = iter.hasNext + + override def next(): K = iter.next() + } + + override def valuesIterator: Iterator[V] = new AbstractIterator[V] { + private[this] val iter = ordering.iterator + + override def hasNext: Boolean = iter.hasNext + + override def next(): V = value(binding(iter.next())) + } + + override def contains(key: K): Boolean = mapping.contains(key) + + override def head: (K, V) = binding(ordering.head) + + override def headOption = ordering.headOption.map(binding) + + override def last: (K, V) = binding(ordering.last) + + override def lastOption: Option[(K, V)] = ordering.lastOption.map(binding) + + override def tail: TreeSeqMap[K, V] = { + val (head, tail) = ordering.headTail + new TreeSeqMap(tail, mapping.removed(head), ordinal, orderedBy) + } + + override def init: TreeSeqMap[K, V] = { + val (init, last) = ordering.initLast + new TreeSeqMap(init, mapping.removed(last), ordinal, orderedBy) + } + + override def slice(from: Int, until: Int): TreeSeqMap[K, V] = { + val sz = size + if (sz == 0 || from >= until) TreeSeqMap.empty[K, V](orderedBy) + else { + val sz = size + val f = if (from >= 0) from else 0 + val u = if (until <= sz) until else sz + val l = u - f + if (l <= 0) TreeSeqMap.empty[K, V](orderedBy) + else if (l > sz / 2) { + // Remove front and rear incrementally if majority of elements are to be kept + val (front, rest) = ordering.splitAt(f) + val (ong, rear) = rest.splitAt(l) + var mng = this.mapping + val frontIter = front.iterator + while (frontIter.hasNext) { + mng = mng - frontIter.next() + } + val rearIter = rear.iterator + while (rearIter.hasNext) { + mng = mng - rearIter.next() + } + new TreeSeqMap(ong, mng, ordinal, orderedBy) + } else { + // Populate with builder otherwise + val bdr = newBuilder[K, V](orderedBy) + val iter = ordering.iterator + var i = 0 + while (i < f) { + iter.next() + i += 1 + } + while (i < u) { + val k = iter.next() + bdr.addOne((k, mapping(k)._2)) + i += 1 + } + bdr.result() + } + } + } + + override def map[K2, V2](f: ((K, V)) => (K2, V2)): TreeSeqMap[K2, V2] = { + val bdr = newBuilder[K2, V2](orderedBy) + val iter = ordering.iterator + while (iter.hasNext) { + val k = iter.next() + val (_, v) = mapping(k) + val (k2, v2) = f((k, v)) + bdr.addOne((k2, v2)) + } + bdr.result() + } + + override def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]): TreeSeqMap[K2, V2] = { + val bdr = newBuilder[K2, V2](orderedBy) + val iter = ordering.iterator + while (iter.hasNext) { + val k = iter.next() + val (_, v) = mapping(k) + val jter = f((k, v)).iterator + while (jter.hasNext) { + val (k2, v2) = jter.next() + bdr.addOne((k2, v2)) + } + } + bdr.result() + } + + override def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]): TreeSeqMap[K2, V2] = { + val bdr = newBuilder[K2, V2](orderedBy) + val iter = ordering.iterator + while (iter.hasNext) { + val k = iter.next() + val (_, v) = mapping(k) + pf.runWith({ case (k2, v2) => bdr.addOne((k2, v2)) })((k, v)) + } + bdr.result() + } + + override def concat[V2 >: V](suffix: IterableOnce[(K, V2)]): TreeSeqMap[K, V2] = { + var ong: Ordering[K] = ordering + var mng: Mapping[K, V2] = mapping + var ord = increment(ordinal) + val iter = suffix.iterator + while (iter.hasNext) { + val (k, v2) = iter.next() + mng.get(k) match { + case Some((o, v)) => + if (orderedBy == OrderBy.Insertion && v != v2) mng = mng.updated(k, (o, v2)) + else if (orderedBy == OrderBy.Modification) { + mng = mng.updated(k, (ord, v2)) + ong = ong.exclude(o).append(ord, k) + ord = increment(ord) + } + case None => + mng = mng.updated(k, (ord, v2)) + ong = ong.append(ord, k) + ord = increment(ord) + } + } + new TreeSeqMap[K, V2](ong, mng, ord, orderedBy) + } + + @`inline` private[this] def value(p: (_, V)) = p._2 + @`inline` private[this] def binding(k: K) = mapping(k).copy(_1 = k) +} +object TreeSeqMap extends MapFactory[TreeSeqMap] { + sealed trait OrderBy + object OrderBy { + case object Insertion extends OrderBy + case object Modification extends OrderBy + } + + private val EmptyByInsertion = new TreeSeqMap[Nothing, Nothing](Ordering.empty, HashMap.empty, 0, OrderBy.Insertion) + private val EmptyByModification = new TreeSeqMap[Nothing, Nothing](Ordering.empty, HashMap.empty, 0, OrderBy.Modification) + val Empty = EmptyByInsertion + def empty[K, V]: TreeSeqMap[K, V] = empty(OrderBy.Insertion) + def empty[K, V](orderBy: OrderBy): TreeSeqMap[K, V] = { + if (orderBy == OrderBy.Modification) EmptyByModification + else EmptyByInsertion + }.asInstanceOf[TreeSeqMap[K, V]] + + def from[K, V](it: collection.IterableOnce[(K, V)]): TreeSeqMap[K, V] = + it match { + case om: TreeSeqMap[K, V] => om + case _ => (newBuilder[K, V] ++= it).result() + } + + @inline private def increment(ord: Int) = if (ord == Int.MaxValue) Int.MinValue else ord + 1 + + def newBuilder[K, V]: mutable.Builder[(K, V), TreeSeqMap[K, V]] = newBuilder(OrderBy.Insertion) + def newBuilder[K, V](orderedBy: OrderBy): mutable.Builder[(K, V), TreeSeqMap[K, V]] = new Builder[K, V](orderedBy) + + final class Builder[K, V](orderedBy: OrderBy) extends mutable.Builder[(K, V), TreeSeqMap[K, V]] { + private[this] val bdr = new MapBuilderImpl[K, (Int, V)] + private[this] var ong = Ordering.empty[K] + private[this] var ord = 0 + private[this] var aliased: TreeSeqMap[K, V] = _ + + override def addOne(elem: (K, V)): this.type = addOne(elem._1, elem._2) + def addOne(key: K, value: V): this.type = { + if (aliased ne null) { + aliased = aliased.updated(key, value) + } else { + bdr.getOrElse(key, null) match { + case (o, v) => + if (orderedBy == OrderBy.Insertion && v != value) bdr.addOne(key, (o, value)) + else if (orderedBy == OrderBy.Modification) { + bdr.addOne(key, (ord, value)) + ong = ong.exclude(o).appendInPlace(ord, key) + ord = increment(ord) + } + case null => + bdr.addOne(key, (ord, value)) + ong = ong.appendInPlace(ord, key) + ord = increment(ord) + } + } + this + } + + override def clear(): Unit = { + ong = Ordering.empty + ord = 0 + bdr.clear() + aliased = null + } + + override def result(): TreeSeqMap[K, V] = { + if (aliased eq null) { + aliased = new TreeSeqMap(ong, bdr.result(), ord, orderedBy) + } + aliased + } + } + + private type Mapping[K, +V] = Map[K, (Int, V)] + @annotation.unused + private val Mapping = Map + + /* The ordering implementation below is an adapted version of immutable.IntMap. */ + private[immutable] object Ordering { + import scala.collection.generic.BitOperations.Int.{Int => _, _} + + @inline private[immutable] def toBinaryString(i: Int): String = s"$i/${i.toBinaryString}" + + def empty[T] : Ordering[T] = Zero + + def apply[T](elems: (Int, T)*): Ordering[T] = + elems.foldLeft(empty[T])((x, y) => x.include(y._1, y._2)) + + // Iterator over a non-empty Ordering. + final class Iterator[+V](it: Ordering[V]) { + // Basically this uses a simple stack to emulate conversion over the tree. However + // because we know that Ints are at least 32 bits we can have at most 32 Bins and + // one Tip sitting on the tree at any point. Therefore we know the maximum stack + // depth is 33 + private[this] var index = 0 + private[this] val buffer = new Array[AnyRef](33) + + private[this] def pop = { + index -= 1 + buffer(index).asInstanceOf[Ordering[V]] + } + + private[this] def push[V2 >: V](x: Ordering[V2]): Unit = { + buffer(index) = x.asInstanceOf[AnyRef] + index += 1 + } + + if (it != Zero) push(it) + + def hasNext = index > 0 + @tailrec + def next(): V = + if (!hasNext) scala.collection.Iterator.empty.next() + else pop match { + case Bin(_,_, Tip(_, v), right) => + push(right) + v + case Bin(_, _, left, right) => + push(right) + push(left) + next() + case Tip(_, v) => v + // This should never happen. We don't allow Ordering.Zero in subtrees of the Ordering + // and don't return an Ordering.Iterator for Ordering.Zero. + case Zero => throw new IllegalStateException("empty subtree not allowed") + } + } + + object Iterator { + val Empty = new Iterator[Nothing](Ordering.empty[Nothing]) + def empty[V]: Iterator[V] = Empty.asInstanceOf[Iterator[V]] + } + + case object Zero extends Ordering[Nothing] { + // Important! Without this equals method in place, an infinite + // loop from Map.equals => size => pattern-match-on-Nil => equals + // develops. Case objects and custom equality don't mix without + // careful handling. + override def equals(that : Any): Boolean = that match { + case _: this.type => true + case _: Ordering[_] => false // The only empty Orderings are eq Nil + case _ => super.equals(that) + } + protected def format(sb: StringBuilder, prefix: String, subPrefix: String): Unit = sb ++= s"${prefix}Ø" + } + + final case class Tip[+T](ord: Int, value: T) extends Ordering[T] { + def withValue[S](s: S) = + if (s.asInstanceOf[AnyRef] eq value.asInstanceOf[AnyRef]) this.asInstanceOf[Tip[S]] + else Tip(ord, s) + protected def format(sb: StringBuilder, prefix: String, subPrefix: String): Unit = sb ++= s"${prefix}Tip(${toBinaryString(ord)} -> $value)\n" + } + + final case class Bin[+T](prefix: Int, mask: Int, left: Ordering[T], var right: Ordering[T] @scala.annotation.unchecked.uncheckedVariance) extends Ordering[T] { + def bin[S](left: Ordering[S], right: Ordering[S]): Ordering[S] = { + if ((this.left eq left) && (this.right eq right)) this.asInstanceOf[Bin[S]] + else Bin[S](prefix, mask, left, right) + } + protected def format(sb: StringBuilder, prefix: String, subPrefix: String): Unit = { + sb ++= s"${prefix}Bin(${toBinaryString(this.prefix)}:${toBinaryString(mask)})\n" + left.format(sb, subPrefix + "├── ", subPrefix + "│ ") + right.format(sb, subPrefix + "└── ", subPrefix + " ") + } + } + + private def branchMask(i: Int, j: Int) = highestOneBit(i ^ j) + + private def join[T](p1: Int, t1: Ordering[T], p2: Int, t2: Ordering[T]): Ordering[T] = { + val m = branchMask(p1, p2) + val p = mask(p1, m) + if (zero(p1, m)) Bin(p, m, t1, t2) + else Bin(p, m, t2, t1) + } + + private def bin[T](prefix: Int, mask: Int, left: Ordering[T], right: Ordering[T]): Ordering[T] = (left, right) match { + case (l, Zero) => l + case (Zero, r) => r + case (l, r) => Bin(prefix, mask, l, r) + } + } + + sealed abstract class Ordering[+T] { + import Ordering._ + import scala.annotation.tailrec + import scala.collection.generic.BitOperations.Int._ + + override final def toString: String = format + final def format: String = { + val sb = new StringBuilder + format(sb, "", "") + sb.toString() + } + protected def format(sb: StringBuilder, prefix: String, subPrefix: String): Unit + + @tailrec + final def head: T = this match { + case Zero => throw new NoSuchElementException("head of empty map") + case Tip(k, v) => v + case Bin(_, _, l, _) => l.head + } + + @tailrec + final def headOption: Option[T] = this match { + case Zero => None + case Tip(_, v) => Some(v) + case Bin(_, _, l, _) => l.headOption + } + + @tailrec + final def last: T = this match { + case Zero => throw new NoSuchElementException("last of empty map") + case Tip(_, v) => v + case Bin(_, _, _, r) => r.last + } + + @tailrec + final def lastOption: Option[T] = this match { + case Zero => None + case Tip(_, v) => Some(v) + case Bin(_, _, _, r) => r.lastOption + } + + @tailrec + final def ordinal: Int = this match { + case Zero => 0 + case Tip(o, _) => o + case Bin(_, _, _, r) => r.ordinal + } + + final def tail: Ordering[T] = this match { + case Zero => throw new NoSuchElementException("tail of empty map") + case Tip(_, _) => Zero + case Bin(p, m, l, r) => bin(p, m, l.tail, r) + } + + final def headTail: (T, Ordering[T]) = this match { + case Zero => throw new NoSuchElementException("init of empty map") + case Tip(_, v) => (v, Zero) + case Bin(p, m, l, r) => + val (head, tail) = l.headTail + (head, bin(p, m, tail, r)) + } + + final def init: Ordering[T] = this match { + case Zero => throw new NoSuchElementException("init of empty map") + case Tip(_, _) => Zero + case Bin(p, m, l, r) => + bin(p, m, l, r.init) + } + + final def initLast: (Ordering[T], T) = this match { + case Zero => throw new NoSuchElementException("init of empty map") + case Tip(_, v) => (Zero, v) + case Bin(p, m, l, r) => + val (init, last) = r.initLast + (bin(p, m, l, init), last) + } + + final def iterator: Iterator[T] = this match { + case Zero => Iterator.empty + case _ => new Iterator(this) + } + + final def include[S >: T](ordinal: Int, value: S): Ordering[S] = this match { + case Zero => + Tip(ordinal, value) + case Tip(o, _) => + if (ordinal == o) Tip(ordinal, value) + else join(ordinal, Tip(ordinal, value), o, this) + case Bin(p, m, l, r) => + if (!hasMatch(ordinal, p, m)) join(ordinal, Tip(ordinal, value), p, this) + else if (zero(ordinal, m)) Bin(p, m, l.include(ordinal, value), r) + else Bin(p, m, l, r.include(ordinal, value)) + } + + final def append[S >: T](ordinal: Int, value: S): Ordering[S] = this match { + case Zero => + Tip(ordinal, value) + case Tip(o, _) => + if (ordinal == o) Tip(ordinal, value) + else join(ordinal, Tip(ordinal, value), o, this) + case Bin(p, m, l, r) => + if (!hasMatch(ordinal, p, m)) join(ordinal, Tip(ordinal, value), p, this) + else if (zero(ordinal, m)) throw new IllegalArgumentException(s"Append called with ordinal out of range: $ordinal is not greater than current max ordinal ${this.ordinal}") + else Bin(p, m, l, r.append(ordinal, value)) + } + + @inline private[collection] final def appendInPlace[S >: T](ordinal: Int, value: S): Ordering[S] = appendInPlace1(null, ordinal, value) + private[collection] final def appendInPlace1[S >: T](parent: Bin[S], ordinal: Int, value: S): Ordering[S] = this match { + case Zero => + Tip(ordinal, value) + case Tip(o, _) if o >= ordinal => + throw new IllegalArgumentException(s"Append called with ordinal out of range: $o is not greater than current max ordinal ${this.ordinal}") + case Tip(o, _) if parent == null => + join(ordinal, Tip(ordinal, value), o, this) + case Tip(o, _) => + parent.right = join(ordinal, Tip(ordinal, value), o, this) + parent + case b @ Bin(p, m, _, r) => + if (!hasMatch(ordinal, p, m)) { + val b2 = join(ordinal, Tip(ordinal, value), p, this) + if (parent != null) { + parent.right = b2 + parent + } else b2 + } else if (zero(ordinal, m)) throw new IllegalArgumentException(s"Append called with ordinal out of range: $ordinal is not greater than current max ordinal ${this.ordinal}") + else { + r.appendInPlace1(b, ordinal, value) + this + } + } + + final def exclude(ordinal: Int): Ordering[T] = this match { + case Zero => + Zero + case Tip(o, _) => + if (ordinal == o) Zero + else this + case Bin(p, m, l, r) => + if (!hasMatch(ordinal, p, m)) this + else if (zero(ordinal, m)) bin(p, m, l.exclude(ordinal), r) + else bin(p, m, l, r.exclude(ordinal)) + } + + final def splitAt(n: Int): (Ordering[T], Ordering[T]) = { + var rear = Ordering.empty[T] + var i = n + (modifyOrRemove { (o, v) => + i -= 1 + if (i >= 0) Some(v) + else { + rear = rear.appendInPlace(o, v) + None + } + }, rear) + } + + /** + * A combined transform and filter function. Returns an `Ordering` such that + * for each `(key, value)` mapping in this map, if `f(key, value) == None` + * the map contains no mapping for key, and if `f(key, value) == Some(x)` the + * map contains `(key, x)`. + * + * @tparam S The type of the values in the resulting `LongMap`. + * @param f The transforming function. + * @return The modified map. + */ + final def modifyOrRemove[S](f: (Int, T) => Option[S]): Ordering[S] = this match { + case Zero => Zero + case Tip(key, value) => + f(key, value) match { + case None => Zero + case Some(value2) => + // hack to preserve sharing + if (value.asInstanceOf[AnyRef] eq value2.asInstanceOf[AnyRef]) this.asInstanceOf[Ordering[S]] + else Tip(key, value2) + } + case Bin(prefix, mask, left, right) => + val l = left.modifyOrRemove(f) + val r = right.modifyOrRemove(f) + if ((left eq l) && (right eq r)) this.asInstanceOf[Ordering[S]] + else bin(prefix, mask, l, r) + } + } +} diff --git a/library/src/scala/collection/immutable/TreeSet.scala b/library/src/scala/collection/immutable/TreeSet.scala new file mode 100644 index 000000000000..a9bf1979700a --- /dev/null +++ b/library/src/scala/collection/immutable/TreeSet.scala @@ -0,0 +1,297 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import scala.collection.Stepper.EfficientSplit +import scala.collection.generic.DefaultSerializable +import scala.collection.mutable.ReusableBuilder +import scala.collection.immutable.{RedBlackTree => RB} +import scala.runtime.AbstractFunction1 + + +/** This class implements immutable sorted sets using a tree. + * + * @tparam A the type of the elements contained in this tree set + * @param ordering the implicit ordering used to compare objects of type `A` + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html#red-black-trees "Scala's Collection Library overview"]] + * section on `Red-Black Trees` for more information. + * + * @define Coll `immutable.TreeSet` + * @define coll immutable tree set + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +final class TreeSet[A] private[immutable] (private[immutable] val tree: RB.Tree[A, Any])(implicit val ordering: Ordering[A]) + extends AbstractSet[A] + with SortedSet[A] + with SortedSetOps[A, TreeSet, TreeSet[A]] + with StrictOptimizedSortedSetOps[A, TreeSet, TreeSet[A]] + with SortedSetFactoryDefaults[A, TreeSet, Set] + with DefaultSerializable { + + if (ordering eq null) throw new NullPointerException("ordering must not be null") + + def this()(implicit ordering: Ordering[A]) = this(null)(ordering) + + override def sortedIterableFactory: TreeSet.type = TreeSet + + private[this] def newSetOrSelf(t: RB.Tree[A, Any]) = if(t eq tree) this else new TreeSet[A](t) + + override def size: Int = RB.count(tree) + + override def isEmpty = size == 0 + + override def head: A = RB.smallest(tree).key + + override def last: A = RB.greatest(tree).key + + override def tail: TreeSet[A] = new TreeSet(RB.tail(tree)) + + override def init: TreeSet[A] = new TreeSet(RB.init(tree)) + + override def min[A1 >: A](implicit ord: Ordering[A1]): A = { + if ((ord eq ordering) && nonEmpty) { + head + } else { + super.min(ord) + } + } + + override def max[A1 >: A](implicit ord: Ordering[A1]): A = { + if ((ord eq ordering) && nonEmpty) { + last + } else { + super.max(ord) + } + } + + override def drop(n: Int): TreeSet[A] = { + if (n <= 0) this + else if (n >= size) empty + else new TreeSet(RB.drop(tree, n)) + } + + override def take(n: Int): TreeSet[A] = { + if (n <= 0) empty + else if (n >= size) this + else new TreeSet(RB.take(tree, n)) + } + + override def slice(from: Int, until: Int): TreeSet[A] = { + if (until <= from) empty + else if (from <= 0) take(until) + else if (until >= size) drop(from) + else new TreeSet(RB.slice(tree, from, until)) + } + + override def dropRight(n: Int): TreeSet[A] = take(size - math.max(n, 0)) + + override def takeRight(n: Int): TreeSet[A] = drop(size - math.max(n, 0)) + + private[this] def countWhile(p: A => Boolean): Int = { + var result = 0 + val it = iterator + while (it.hasNext && p(it.next())) result += 1 + result + } + override def dropWhile(p: A => Boolean): TreeSet[A] = drop(countWhile(p)) + + override def takeWhile(p: A => Boolean): TreeSet[A] = take(countWhile(p)) + + override def span(p: A => Boolean): (TreeSet[A], TreeSet[A]) = splitAt(countWhile(p)) + + override def foreach[U](f: A => U): Unit = RB.foreachKey(tree, f) + + override def minAfter(key: A): Option[A] = { + val v = RB.minAfter(tree, key) + if (v eq null) Option.empty else Some(v.key) + } + + override def maxBefore(key: A): Option[A] = { + val v = RB.maxBefore(tree, key) + if (v eq null) Option.empty else Some(v.key) + } + + def iterator: Iterator[A] = RB.keysIterator(tree) + + def iteratorFrom(start: A): Iterator[A] = RB.keysIterator(tree, Some(start)) + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit = { + import scala.collection.convert.impl._ + type T = RB.Tree[A, Any] + val s = shape.shape match { + case StepperShape.IntShape => IntBinaryTreeStepper.from[T] (size, tree, _.left, _.right, _.key.asInstanceOf[Int]) + case StepperShape.LongShape => LongBinaryTreeStepper.from[T] (size, tree, _.left, _.right, _.key.asInstanceOf[Long]) + case StepperShape.DoubleShape => DoubleBinaryTreeStepper.from[T](size, tree, _.left, _.right, _.key.asInstanceOf[Double]) + case _ => shape.parUnbox(AnyBinaryTreeStepper.from[A, T](size, tree, _.left, _.right, _.key)) + } + s.asInstanceOf[S with EfficientSplit] + } + + /** Checks if this set contains element `elem`. + * + * @param elem the element to check for membership. + * @return true, iff `elem` is contained in this set. + */ + def contains(elem: A): Boolean = RB.contains(tree, elem) + + override def range(from: A, until: A): TreeSet[A] = newSetOrSelf(RB.range(tree, from, until)) + + def rangeImpl(from: Option[A], until: Option[A]): TreeSet[A] = newSetOrSelf(RB.rangeImpl(tree, from, until)) + + /** Creates a new `TreeSet` with the entry added. + * + * @param elem a new element to add. + * @return a new $coll containing `elem` and all the elements of this $coll. + */ + def incl(elem: A): TreeSet[A] = + newSetOrSelf(RB.update(tree, elem, null, overwrite = false)) + + /** Creates a new `TreeSet` with the entry removed. + * + * @param elem a new element to add. + * @return a new $coll containing all the elements of this $coll except `elem`. + */ + def excl(elem: A): TreeSet[A] = + newSetOrSelf(RB.delete(tree, elem)) + + override def concat(that: collection.IterableOnce[A]): TreeSet[A] = { + val t = that match { + case ts: TreeSet[A] if ordering == ts.ordering => + RB.union(tree, ts.tree) + case _ => + val it = that.iterator + var t = tree + while (it.hasNext) t = RB.update(t, it.next(), null, overwrite = false) + t + } + newSetOrSelf(t) + } + + override def removedAll(that: IterableOnce[A]): TreeSet[A] = that match { + case ts: TreeSet[A] if ordering == ts.ordering => + newSetOrSelf(RB.difference(tree, ts.tree)) + case _ => + //TODO add an implementation of a mutable subtractor similar to TreeMap + //but at least this doesn't create a TreeSet for each iteration + object sub extends AbstractFunction1[A, Unit] { + var currentTree = tree + override def apply(k: A): Unit = { + currentTree = RB.delete(currentTree, k) + } + } + that.iterator.foreach(sub) + newSetOrSelf(sub.currentTree) + } + + override def intersect(that: collection.Set[A]): TreeSet[A] = that match { + case ts: TreeSet[A] if ordering == ts.ordering => + newSetOrSelf(RB.intersect(tree, ts.tree)) + case _ => + super.intersect(that) + } + + override def diff(that: collection.Set[A]): TreeSet[A] = that match { + case ts: TreeSet[A] if ordering == ts.ordering => + newSetOrSelf(RB.difference(tree, ts.tree)) + case _ => + super.diff(that) + } + + override def filter(f: A => Boolean): TreeSet[A] = newSetOrSelf(RB.filterEntries[A, Any](tree, {(k, _) => f(k)})) + + override def partition(p: A => Boolean): (TreeSet[A], TreeSet[A]) = { + val (l, r) = RB.partitionEntries(tree, {(a:A, _: Any) => p(a)}) + (newSetOrSelf(l), newSetOrSelf(r)) + } + + override def equals(obj: Any): Boolean = obj match { + case that: TreeSet[A @unchecked] if ordering == that.ordering => RB.keysEqual(tree, that.tree) + case _ => super.equals(obj) + } + + override protected[this] def className = "TreeSet" +} + +/** + * $factoryInfo + * + * @define Coll `immutable.TreeSet` + * @define coll immutable tree set + */ +@SerialVersionUID(3L) +object TreeSet extends SortedIterableFactory[TreeSet] { + + def empty[A: Ordering]: TreeSet[A] = new TreeSet[A] + + def from[E](it: scala.collection.IterableOnce[E])(implicit ordering: Ordering[E]): TreeSet[E] = + it match { + case ts: TreeSet[E] if ordering == ts.ordering => ts + case ss: scala.collection.SortedSet[E] if ordering == ss.ordering => + new TreeSet[E](RB.fromOrderedKeys(ss.iterator, ss.size)) + case r: Range if (ordering eq Ordering.Int) || (Ordering.Int isReverseOf ordering) => + val it = if((ordering eq Ordering.Int) == (r.step > 0)) r.iterator else r.reverseIterator + val tree = RB.fromOrderedKeys(it.asInstanceOf[Iterator[E]], r.size) + // The cast is needed to compile with Dotty: + // Dotty doesn't infer that E =:= Int, since instantiation of covariant GADTs is unsound + new TreeSet[E](tree) + case _ => + var t: RB.Tree[E, Null] = null + val i = it.iterator + while (i.hasNext) t = RB.update(t, i.next(), null, overwrite = false) + new TreeSet[E](t) + } + + def newBuilder[A](implicit ordering: Ordering[A]): ReusableBuilder[A, TreeSet[A]] = new TreeSetBuilder[A] + private class TreeSetBuilder[A](implicit ordering: Ordering[A]) + extends RB.SetHelper[A] + with ReusableBuilder[A, TreeSet[A]] { + type Tree = RB.Tree[A, Any] + private [this] var tree:RB.Tree[A, Any] = null + + override def addOne(elem: A): this.type = { + tree = mutableUpd(tree, elem) + this + } + + override def addAll(xs: IterableOnce[A]): this.type = { + xs match { + // TODO consider writing a mutable-safe union for TreeSet/TreeMap builder ++= + // for the moment we have to force immutability before the union + // which will waste some time and space + // calling `beforePublish` makes `tree` immutable + case ts: TreeSet[A] if ts.ordering == ordering => + if (tree eq null) tree = ts.tree + else tree = RB.union(beforePublish(tree), ts.tree)(ordering) + case ts: TreeMap[A @unchecked, _] if ts.ordering == ordering => + if (tree eq null) tree = ts.tree0 + else tree = RB.union(beforePublish(tree), ts.tree0)(ordering) + case _ => + super.addAll(xs) + } + this + } + + override def clear(): Unit = { + tree = null + } + + override def result(): TreeSet[A] = new TreeSet[A](beforePublish(tree))(ordering) + } +} diff --git a/library/src/scala/collection/immutable/Vector.scala b/library/src/scala/collection/immutable/Vector.scala new file mode 100644 index 000000000000..4ea962fc8fa9 --- /dev/null +++ b/library/src/scala/collection/immutable/Vector.scala @@ -0,0 +1,2484 @@ +/* + * 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.collection +package immutable + +import scala.language.`2.13` +import java.lang.Math.{abs, max => mmax, min => mmin} +import java.util.Arrays.{copyOf, copyOfRange} +import java.util.{Arrays, Spliterator} + +import scala.annotation.switch +import scala.annotation.unchecked.uncheckedVariance +import scala.collection.Stepper.EfficientSplit +import scala.collection.generic.{CommonErrors, DefaultSerializable} +import scala.collection.immutable.VectorInline._ +import scala.collection.immutable.VectorStatics._ +import scala.collection.mutable.ReusableBuilder + + +/** $factoryInfo + * @define Coll `Vector` + * @define coll vector + */ +@SerialVersionUID(3L) +object Vector extends StrictOptimizedSeqFactory[Vector] { + + def empty[A]: Vector[A] = Vector0 + + def from[E](it: collection.IterableOnce[E]): Vector[E] = + it match { + case v: Vector[E] => v + case _ => + val knownSize = it.knownSize + if (knownSize == 0) empty[E] + else if (knownSize > 0 && knownSize <= WIDTH) { + val a1: Arr1 = it match { + case as: ArraySeq.ofRef[_] if as.elemTag.runtimeClass == classOf[AnyRef] => + as.unsafeArray.asInstanceOf[Arr1] + case it: Iterable[E] => + val a1 = new Arr1(knownSize) + @annotation.unused val copied = it.copyToArray(a1.asInstanceOf[Array[Any]]) + //assert(copied == knownSize) + a1 + case _ => + val a1 = new Arr1(knownSize) + @annotation.unused val copied = it.iterator.copyToArray(a1.asInstanceOf[Array[Any]]) + //assert(copied == knownSize) + a1.asInstanceOf[Arr1] + } + new Vector1[E](a1) + } else { + (newBuilder ++= it).result() + } + } + + def newBuilder[A]: ReusableBuilder[A, Vector[A]] = new VectorBuilder[A] + + /** Create a Vector with the same element at each index. + * + * Unlike `fill`, which takes a by-name argument for the value and can thereby + * compute different values for each index, this method guarantees that all + * elements are identical. This allows sparse allocation in O(log n) time and space. + */ + private[collection] def fillSparse[A](n: Int)(elem: A): Vector[A] = { + //TODO Make public; this method is private for now because it is not forward binary compatible + if(n <= 0) Vector0 + else { + val b = new VectorBuilder[A] + b.initSparse(n, elem) + b.result() + } + } + + private val defaultApplyPreferredMaxLength: Int = + // explicit StringOps to avoid initialization cycle with Predef (scala/bug#13009) + try new StringOps(System.getProperty("scala.collection.immutable.Vector.defaultApplyPreferredMaxLength", + "250")).toInt + catch { + case _: SecurityException => 250 + } + + private val emptyIterator = new NewVectorIterator(Vector0, 0, 0) +} + + +/** Vector is a general-purpose, immutable data structure. It provides random access and updates + * in O(log n) time, as well as very fast append/prepend/tail/init (amortized O(1), worst case O(log n)). + * Because vectors strike a good balance between fast random selections and fast random functional updates, + * they are currently the default implementation of immutable indexed sequences. + * + * Vectors are implemented by radix-balanced finger trees of width 32. There is a separate subclass + * for each level (0 to 6, with 0 being the empty vector and 6 a tree with a maximum width of 64 at the + * top level). + * + * Tree balancing: + * - Only the first dimension of an array may have a size < WIDTH + * - In a `data` (central) array the first dimension may be up to WIDTH-2 long, in `prefix1` and `suffix1` up + * to WIDTH, and in other `prefix` and `suffix` arrays up to WIDTH-1 + * - `prefix1` and `suffix1` are never empty + * - Balancing does not cross the main data array (i.e. prepending never touches the suffix and appending never touches + * the prefix). The level is increased/decreased when the affected side plus main data is already full/empty + * - All arrays are left-aligned and truncated + * + * In addition to the data slices (`prefix1`, `prefix2`, ..., `dataN`, ..., `suffix2`, `suffix1`) we store a running + * count of elements after each prefix for more efficient indexing without having to dereference all prefix arrays. + */ +sealed abstract class Vector[+A] private[immutable] (private[immutable] final val prefix1: Arr1) + extends AbstractSeq[A] + with IndexedSeq[A] + with IndexedSeqOps[A, Vector, Vector[A]] + with StrictOptimizedSeqOps[A, Vector, Vector[A]] + with IterableFactoryDefaults[A, Vector] + with DefaultSerializable { + + override def iterableFactory: SeqFactory[Vector] = Vector + + override final def length: Int = + if(this.isInstanceOf[BigVector[_]]) this.asInstanceOf[BigVector[_]].length0 + else prefix1.length + + override final def iterator: Iterator[A] = + if(this.isInstanceOf[Vector0.type]) Vector.emptyIterator + else new NewVectorIterator(this, length, vectorSliceCount) + + override final protected[collection] def filterImpl(pred: A => Boolean, isFlipped: Boolean): Vector[A] = { + var i = 0 + val len = prefix1.length + while (i != len) { + if (pred(prefix1(i).asInstanceOf[A]) == isFlipped) { + // each 1 bit indicates that index passes the filter. + // all indices < i are also assumed to pass the filter + var bitmap = 0 + var j = i + 1 + while (j < len) { + if (pred(prefix1(j).asInstanceOf[A]) != isFlipped) { + bitmap |= (1 << j) + } + j += 1 + } + val newLen = i + java.lang.Integer.bitCount(bitmap) + + if(this.isInstanceOf[BigVector[_]]) { + val b = new VectorBuilder[A] + var k = 0 + while(k < i) { + b.addOne(prefix1(k).asInstanceOf[A]) + k += 1 + } + k = i + 1 + while (i != newLen) { + if (((1 << k) & bitmap) != 0) { + b.addOne(prefix1(k).asInstanceOf[A]) + i += 1 + } + k += 1 + } + this.asInstanceOf[BigVector[A]].foreachRest { v => if(pred(v) != isFlipped) b.addOne(v) } + return b.result() + } else { + if (newLen == 0) return Vector0 + val newData = new Array[AnyRef](newLen) + System.arraycopy(prefix1, 0, newData, 0, i) + var k = i + 1 + while (i != newLen) { + if (((1 << k) & bitmap) != 0) { + newData(i) = prefix1(k) + i += 1 + } + k += 1 + } + return new Vector1[A](newData) + } + } + i += 1 + } + if(this.isInstanceOf[BigVector[_]]) { + val b = new VectorBuilder[A] + b.initFrom(prefix1) + this.asInstanceOf[BigVector[A]].foreachRest { v => if(pred(v) != isFlipped) b.addOne(v) } + b.result() + } else this + } + + // Dummy overrides to refine result types for binary compatibility: + override def updated[B >: A](index: Int, elem: B): Vector[B] = super.updated(index, elem) + override def appended[B >: A](elem: B): Vector[B] = super.appended(elem) + override def prepended[B >: A](elem: B): Vector[B] = super.prepended(elem) + override def prependedAll[B >: A](prefix: collection.IterableOnce[B]): Vector[B] = { + val k = prefix.knownSize + if (k == 0) this + else if (k < 0) super.prependedAll(prefix) + else prependedAll0(prefix, k) + } + + override final def appendedAll[B >: A](suffix: collection.IterableOnce[B]): Vector[B] = { + val k = suffix.knownSize + if (k == 0) this + else if (k < 0) super.appendedAll(suffix) + else appendedAll0(suffix, k) + } + + protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B], k: Int): Vector[B] = { + // k >= 0, k = prefix.knownSize + val tinyAppendLimit = 4 + vectorSliceCount + if (k < tinyAppendLimit /*|| k < (this.size >>> Log2ConcatFaster)*/) { + var v: Vector[B] = this + val it = IndexedSeq.from(prefix).reverseIterator + while (it.hasNext) v = it.next() +: v + v + } else if (this.size < (k >>> Log2ConcatFaster) && prefix.isInstanceOf[Vector[_]]) { + var v = prefix.asInstanceOf[Vector[B]] + val it = this.iterator + while (it.hasNext) v = v :+ it.next() + v + } else if (k < this.size - AlignToFaster) { + new VectorBuilder[B].alignTo(k, this).addAll(prefix).addAll(this).result() + } else super.prependedAll(prefix) + } + + protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B], k: Int): Vector[B] = { + // k >= 0, k = suffix.knownSize + val tinyAppendLimit = 4 + vectorSliceCount + if (k < tinyAppendLimit) { + var v: Vector[B] = this + suffix match { + case it: Iterable[_] => it.asInstanceOf[Iterable[B]].foreach(x => v = v.appended(x)) + case _ => suffix.iterator.foreach(x => v = v.appended(x)) + } + v + } else if (this.size < (k >>> Log2ConcatFaster) && suffix.isInstanceOf[Vector[_]]) { + var v = suffix.asInstanceOf[Vector[B]] + val ri = this.reverseIterator + while (ri.hasNext) v = v.prepended(ri.next()) + v + } else if (this.size < k - AlignToFaster && suffix.isInstanceOf[Vector[_]]) { + val v = suffix.asInstanceOf[Vector[B]] + new VectorBuilder[B].alignTo(this.size, v).addAll(this).addAll(v).result() + } else new VectorBuilder[B].initFrom(this).addAll(suffix).result() + } + + override def className = "Vector" + + @inline override final def take(n: Int): Vector[A] = slice(0, n) + @inline override final def drop(n: Int): Vector[A] = slice(n, length) + @inline override final def takeRight(n: Int): Vector[A] = slice(length - mmax(n, 0), length) + @inline override final def dropRight(n: Int): Vector[A] = slice(0, length - mmax(n, 0)) + override def tail: Vector[A] = slice(1, length) + override def init: Vector[A] = slice(0, length-1) + + /** Like slice but parameters must be 0 <= lo < hi < length */ + protected[this] def slice0(lo: Int, hi: Int): Vector[A] + + /** Number of slices */ + protected[immutable] def vectorSliceCount: Int + /** Slice at index */ + protected[immutable] def vectorSlice(idx: Int): Array[_ <: AnyRef] + /** Length of all slices up to and including index */ + protected[immutable] def vectorSlicePrefixLength(idx: Int): Int + + override def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): Int = iterator.copyToArray(xs, start, len) + + override def toVector: Vector[A] = this + + override protected def applyPreferredMaxLength: Int = Vector.defaultApplyPreferredMaxLength + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit = { + val s = shape.shape match { + case StepperShape.IntShape => new IntVectorStepper(iterator.asInstanceOf[NewVectorIterator[Int]]) + case StepperShape.LongShape => new LongVectorStepper(iterator.asInstanceOf[NewVectorIterator[Long]]) + case StepperShape.DoubleShape => new DoubleVectorStepper(iterator.asInstanceOf[NewVectorIterator[Double]]) + case _ => shape.parUnbox(new AnyVectorStepper[A](iterator.asInstanceOf[NewVectorIterator[A]])) + } + s.asInstanceOf[S with EfficientSplit] + } + + protected[this] def ioob(index: Int): IndexOutOfBoundsException = + CommonErrors.indexOutOfBounds(index = index, max = length - 1) + + override final def head: A = + if (prefix1.length == 0) throw new NoSuchElementException("empty.head") + else prefix1(0).asInstanceOf[A] + + override final def last: A = { + if(this.isInstanceOf[BigVector[_]]) { + val suffix = this.asInstanceOf[BigVector[_]].suffix1 + if(suffix.length == 0) throw new NoSuchElementException("empty.tail") + else suffix(suffix.length-1) + } else prefix1(prefix1.length-1) + }.asInstanceOf[A] + + override final def foreach[U](f: A => U): Unit = { + val c = vectorSliceCount + var i = 0 + while (i < c) { + foreachRec(vectorSliceDim(c, i) - 1, vectorSlice(i), f) + i += 1 + } + } + + // The following definitions are needed for binary compatibility with ParVector + private[collection] def startIndex: Int = 0 + private[collection] def endIndex: Int = length + private[collection] def initIterator[B >: A](s: VectorIterator[B]): Unit = + s.it = iterator.asInstanceOf[NewVectorIterator[B]] +} + + +/** This class only exists because we cannot override `slice` in `Vector` in a binary-compatible way */ +private sealed abstract class VectorImpl[+A](_prefix1: Arr1) extends Vector[A](_prefix1) { + + override final def slice(from: Int, until: Int): Vector[A] = { + val lo = mmax(from, 0) + val hi = mmin(until, length) + if (hi <= lo) Vector0 + else if (hi - lo == length) this + else slice0(lo, hi) + } +} + + +/** Vector with suffix and length fields; all Vector subclasses except Vector1 extend this */ +private sealed abstract class BigVector[+A](_prefix1: Arr1, private[immutable] val suffix1: Arr1, private[immutable] val length0: Int) extends VectorImpl[A](_prefix1) { + + protected[immutable] final def foreachRest[U](f: A => U): Unit = { + val c = vectorSliceCount + var i = 1 + while(i < c) { + foreachRec(vectorSliceDim(c, i)-1, vectorSlice(i), f) + i += 1 + } + } +} + + +/** Empty vector */ +private object Vector0 extends BigVector[Nothing](empty1, empty1, 0) { + + def apply(index: Int): Nothing = throw ioob(index) + + override def updated[B >: Nothing](index: Int, elem: B): Vector[B] = throw ioob(index) + + override def appended[B >: Nothing](elem: B): Vector[B] = new Vector1(wrap1(elem)) + + override def prepended[B >: Nothing](elem: B): Vector[B] = new Vector1(wrap1(elem)) + + override def map[B](f: Nothing => B): Vector[B] = this + + override def tail: Vector[Nothing] = throw new UnsupportedOperationException("empty.tail") + + override def init: Vector[Nothing] = throw new UnsupportedOperationException("empty.init") + + protected[this] def slice0(lo: Int, hi: Int): Vector[Nothing] = this + + protected[immutable] def vectorSliceCount: Int = 0 + protected[immutable] def vectorSlice(idx: Int): Array[_ <: AnyRef] = null + protected[immutable] def vectorSlicePrefixLength(idx: Int): Int = 0 + + override def equals(o: Any): Boolean = { + if(this eq o.asInstanceOf[AnyRef]) true + else o match { + case that: Vector[_] => false + case o => super.equals(o) + } + } + + override protected[this]def prependedAll0[B >: Nothing](prefix: collection.IterableOnce[B], k: Int): Vector[B] = + Vector.from(prefix) + + override protected[this]def appendedAll0[B >: Nothing](suffix: collection.IterableOnce[B], k: Int): Vector[B] = + Vector.from(suffix) + + override protected[this] def ioob(index: Int): IndexOutOfBoundsException = + new IndexOutOfBoundsException(s"$index is out of bounds (empty vector)") +} + +/** Flat ArraySeq-like structure */ +private final class Vector1[+A](_data1: Arr1) extends VectorImpl[A](_data1) { + + @inline def apply(index: Int): A = { + if(index >= 0 && index < prefix1.length) + prefix1(index).asInstanceOf[A] + else throw ioob(index) + } + + override def updated[B >: A](index: Int, elem: B): Vector[B] = { + if(index >= 0 && index < prefix1.length) + new Vector1(copyUpdate(prefix1, index, elem)) + else throw ioob(index) + } + + override def appended[B >: A](elem: B): Vector[B] = { + val len1 = prefix1.length + if(len1 < WIDTH) new Vector1(copyAppend1(prefix1, elem)) + else new Vector2(prefix1, WIDTH, empty2, wrap1(elem), WIDTH+1) + } + + override def prepended[B >: A](elem: B): Vector[B] = { + val len1 = prefix1.length + if(len1 < WIDTH) new Vector1(copyPrepend1(elem, prefix1)) + else new Vector2(wrap1(elem), 1, empty2, prefix1, len1+1) + } + + override def map[B](f: A => B): Vector[B] = new Vector1(mapElems1(prefix1, f)) + + protected[this] def slice0(lo: Int, hi: Int): Vector[A] = + new Vector1(copyOfRange(prefix1, lo, hi)) + + override def tail: Vector[A] = + if(prefix1.length == 1) Vector0 + else new Vector1(copyTail(prefix1)) + + override def init: Vector[A] = + if(prefix1.length == 1) Vector0 + else new Vector1(copyInit(prefix1)) + + protected[immutable] def vectorSliceCount: Int = 1 + protected[immutable] def vectorSlice(idx: Int): Array[_ <: AnyRef] = prefix1 + protected[immutable] def vectorSlicePrefixLength(idx: Int): Int = prefix1.length + + override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B], k: Int): Vector[B] = + prepend1IfSpace(prefix1, prefix) match { + case null => super.prependedAll0(prefix, k) + case data1b => new Vector1(data1b) + } + + override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B], k: Int): Vector[B] = { + val data1b = append1IfSpace(prefix1, suffix) + if(data1b ne null) new Vector1(data1b) + else super.appendedAll0(suffix, k) + } +} + + +/** 2-dimensional radix-balanced finger tree */ +private final class Vector2[+A](_prefix1: Arr1, private[immutable] val len1: Int, + private[immutable] val data2: Arr2, + _suffix1: Arr1, + _length0: Int) extends BigVector[A](_prefix1, _suffix1, _length0) { + + @inline private[this] def copy(prefix1: Arr1 = prefix1, len1: Int = len1, + data2: Arr2 = data2, + suffix1: Arr1 = suffix1, + length0: Int = length0) = + new Vector2(prefix1, len1, data2, suffix1, length0) + + @inline def apply(index: Int): A = { + if(index >= 0 && index < length0) { + val io = index - len1 + if(io >= 0) { + val i2 = io >>> BITS + val i1 = io & MASK + if(i2 < data2.length) data2(i2)(i1) + else suffix1(io & MASK) + } else prefix1(index) + }.asInstanceOf[A] else throw ioob(index) + } + + override def updated[B >: A](index: Int, elem: B): Vector[B] = { + if(index >= 0 && index < length0) { + if(index >= len1) { + val io = index - len1 + val i2 = io >>> BITS + val i1 = io & MASK + if(i2 < data2.length) copy(data2 = copyUpdate(data2, i2, i1, elem)) + else copy(suffix1 = copyUpdate(suffix1, i1, elem)) + } else { + copy(prefix1 = copyUpdate(prefix1, index, elem)) + } + } else throw ioob(index) + } + + override def appended[B >: A](elem: B): Vector[B] = { + if (suffix1.length < WIDTH ) copy(suffix1 = copyAppend1(suffix1, elem), length0 = length0+1) + else if(data2.length < WIDTH-2) copy(data2 = copyAppend(data2, suffix1), suffix1 = wrap1(elem), length0 = length0+1) + else new Vector3(prefix1, len1, data2, WIDTH*(WIDTH-2) + len1, empty3, wrap2(suffix1), wrap1(elem), length0+1) + } + + override def prepended[B >: A](elem: B): Vector[B] = { + if (len1 < WIDTH ) copy(copyPrepend1(elem, prefix1), len1+1, length0 = length0+1) + else if(data2.length < WIDTH-2) copy(wrap1(elem), 1, copyPrepend(prefix1, data2), length0 = length0+1) + else new Vector3(wrap1(elem), 1, wrap2(prefix1), len1+1, empty3, data2, suffix1, length0+1) + } + + override def map[B](f: A => B): Vector[B] = + copy(prefix1 = mapElems1(prefix1, f), data2 = mapElems(2, data2, f), suffix1 = mapElems1(suffix1, f)) + + protected[this] def slice0(lo: Int, hi: Int): Vector[A] = { + val b = new VectorSliceBuilder(lo, hi) + b.consider(1, prefix1) + b.consider(2, data2) + b.consider(1, suffix1) + b.result() + } + + override def tail: Vector[A] = + if(len1 > 1) copy(copyTail(prefix1), len1-1, length0 = length0-1) + else slice0(1, length0) + + override def init: Vector[A] = + if(suffix1.length > 1) copy(suffix1 = copyInit(suffix1), length0 = length0-1) + else slice0(0, length0-1) + + protected[immutable] def vectorSliceCount: Int = 3 + protected[immutable] def vectorSlice(idx: Int): Array[_ <: AnyRef] = (idx: @switch) match { + case 0 => prefix1 + case 1 => data2 + case 2 => suffix1 + } + protected[immutable] def vectorSlicePrefixLength(idx: Int): Int = (idx: @switch) match { + case 0 => len1 + case 1 => length0 - suffix1.length + case 2 => length0 + } + + override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B], k: Int): Vector[B] = + prepend1IfSpace(prefix1, prefix) match { + case null => super.prependedAll0(prefix, k) + case prefix1b => + val diff = prefix1b.length - prefix1.length + copy(prefix1 = prefix1b, + len1 = len1 + diff, + length0 = length0 + diff, + ) + } + + override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B], k: Int): Vector[B] = { + val suffix1b = append1IfSpace(suffix1, suffix) + if(suffix1b ne null) copy(suffix1 = suffix1b, length0 = length0-suffix1.length+suffix1b.length) + else super.appendedAll0(suffix, k) + } +} + + +/** 3-dimensional radix-balanced finger tree */ +private final class Vector3[+A](_prefix1: Arr1, private[immutable] val len1: Int, + private[immutable] val prefix2: Arr2, private[immutable] val len12: Int, + private[immutable] val data3: Arr3, + private[immutable] val suffix2: Arr2, _suffix1: Arr1, + _length0: Int) extends BigVector[A](_prefix1, _suffix1, _length0) { + + @inline private[this] def copy(prefix1: Arr1 = prefix1, len1: Int = len1, + prefix2: Arr2 = prefix2, len12: Int = len12, + data3: Arr3 = data3, + suffix2: Arr2 = suffix2, suffix1: Arr1 = suffix1, + length0: Int = length0) = + new Vector3(prefix1, len1, prefix2, len12, data3, suffix2, suffix1, length0) + + @inline def apply(index: Int): A = { + if(index >= 0 && index < length0) { + val io = index - len12 + if(io >= 0) { + val i3 = io >>> BITS2 + val i2 = (io >>> BITS) & MASK + val i1 = io & MASK + if(i3 < data3.length) data3(i3)(i2)(i1) + else if(i2 < suffix2.length) suffix2(i2)(i1) + else suffix1(i1) + } else if(index >= len1) { + val io = index - len1 + prefix2(io >>> BITS)(io & MASK) + } else prefix1(index) + }.asInstanceOf[A] else throw ioob(index) + } + + override def updated[B >: A](index: Int, elem: B): Vector[B] = { + if(index >= 0 && index < length0) { + if(index >= len12) { + val io = index - len12 + val i3 = io >>> BITS2 + val i2 = (io >>> BITS) & MASK + val i1 = io & MASK + if (i3 < data3.length ) copy(data3 = copyUpdate(data3, i3, i2, i1, elem)) + else if(i2 < suffix2.length) copy(suffix2 = copyUpdate(suffix2, i2, i1, elem)) + else copy(suffix1 = copyUpdate(suffix1, i1, elem)) + } else if(index >= len1) { + val io = index - len1 + copy(prefix2 = copyUpdate(prefix2, io >>> BITS, io & MASK, elem)) + } else { + copy(prefix1 = copyUpdate(prefix1, index, elem)) + } + } else throw ioob(index) + } + + override def appended[B >: A](elem: B): Vector[B] = { + if (suffix1.length < WIDTH ) copy(suffix1 = copyAppend1(suffix1, elem), length0 = length0+1) + else if(suffix2.length < WIDTH-1) copy(suffix2 = copyAppend(suffix2, suffix1), suffix1 = wrap1(elem), length0 = length0+1) + else if(data3.length < WIDTH-2) copy(data3 = copyAppend(data3, copyAppend(suffix2, suffix1)), suffix2 = empty2, suffix1 = wrap1(elem), length0 = length0+1) + else new Vector4(prefix1, len1, prefix2, len12, data3, (WIDTH-2)*WIDTH2 + len12, empty4, wrap3(copyAppend(suffix2, suffix1)), empty2, wrap1(elem), length0+1) + } + + override def prepended[B >: A](elem: B): Vector[B] = { + if (len1 < WIDTH ) copy(prefix1 = copyPrepend1(elem, prefix1), len1 = len1+1, len12 = len12+1, length0 = length0+1) + else if(len12 < WIDTH2 ) copy(prefix1 = wrap1(elem), len1 = 1, prefix2 = copyPrepend(prefix1, prefix2), len12 = len12+1, length0 = length0+1) + else if(data3.length < WIDTH-2) copy(prefix1 = wrap1(elem), len1 = 1, prefix2 = empty2, len12 = 1, data3 = copyPrepend(copyPrepend(prefix1, prefix2), data3), length0 = length0+1) + else new Vector4(wrap1(elem), 1, empty2, 1, wrap3(copyPrepend(prefix1, prefix2)), len12+1, empty4, data3, suffix2, suffix1, length0+1) + } + + override def map[B](f: A => B): Vector[B] = + copy(prefix1 = mapElems1(prefix1, f), prefix2 = mapElems(2, prefix2, f), + data3 = mapElems(3, data3, f), + suffix2 = mapElems(2, suffix2, f), suffix1 = mapElems1(suffix1, f)) + + protected[this] def slice0(lo: Int, hi: Int): Vector[A] = { + val b = new VectorSliceBuilder(lo, hi) + b.consider(1, prefix1) + b.consider(2, prefix2) + b.consider(3, data3) + b.consider(2, suffix2) + b.consider(1, suffix1) + b.result() + } + + override def tail: Vector[A] = + if(len1 > 1) copy(prefix1 = copyTail(prefix1), len1 = len1-1, len12 = len12-1, length0 = length0-1) + else slice0(1, length0) + + override def init: Vector[A] = + if(suffix1.length > 1) copy(suffix1 = copyInit(suffix1), length0 = length0-1) + else slice0(0, length0-1) + + protected[immutable] def vectorSliceCount: Int = 5 + protected[immutable] def vectorSlice(idx: Int): Array[_ <: AnyRef] = (idx: @switch) match { + case 0 => prefix1 + case 1 => prefix2 + case 2 => data3 + case 3 => suffix2 + case 4 => suffix1 + } + protected[immutable] def vectorSlicePrefixLength(idx: Int): Int = (idx: @switch) match { + case 0 => len1 + case 1 => len12 + case 2 => len12 + data3.length*WIDTH2 + case 3 => length0 - suffix1.length + case 4 => length0 + } + + override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B], k: Int): Vector[B] = + prepend1IfSpace(prefix1, prefix) match { + case null => super.prependedAll0(prefix, k) + case prefix1b => + val diff = prefix1b.length - prefix1.length + copy(prefix1 = prefix1b, + len1 = len1 + diff, + len12 = len12 + diff, + length0 = length0 + diff, + ) + } + + override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B], k: Int): Vector[B] = { + val suffix1b = append1IfSpace(suffix1, suffix) + if(suffix1b ne null) copy(suffix1 = suffix1b, length0 = length0-suffix1.length+suffix1b.length) + else super.appendedAll0(suffix, k) + } +} + + +/** 4-dimensional radix-balanced finger tree */ +private final class Vector4[+A](_prefix1: Arr1, private[immutable] val len1: Int, + private[immutable] val prefix2: Arr2, private[immutable] val len12: Int, + private[immutable] val prefix3: Arr3, private[immutable] val len123: Int, + private[immutable] val data4: Arr4, + private[immutable] val suffix3: Arr3, private[immutable] val suffix2: Arr2, _suffix1: Arr1, + _length0: Int) extends BigVector[A](_prefix1, _suffix1, _length0) { + + @inline private[this] def copy(prefix1: Arr1 = prefix1, len1: Int = len1, + prefix2: Arr2 = prefix2, len12: Int = len12, + prefix3: Arr3 = prefix3, len123: Int = len123, + data4: Arr4 = data4, + suffix3: Arr3 = suffix3, suffix2: Arr2 = suffix2, suffix1: Arr1 = suffix1, + length0: Int = length0) = + new Vector4(prefix1, len1, prefix2, len12, prefix3, len123, data4, suffix3, suffix2, suffix1, length0) + + @inline def apply(index: Int): A = { + if(index >= 0 && index < length0) { + val io = index - len123 + if(io >= 0) { + val i4 = io >>> BITS3 + val i3 = (io >>> BITS2) & MASK + val i2 = (io >>> BITS) & MASK + val i1 = io & MASK + if(i4 < data4.length) data4(i4)(i3)(i2)(i1) + else if(i3 < suffix3.length) suffix3(i3)(i2)(i1) + else if(i2 < suffix2.length) suffix2(i2)(i1) + else suffix1(i1) + } else if(index >= len12) { + val io = index - len12 + prefix3(io >>> BITS2)((io >>> BITS) & MASK)(io & MASK) + } else if(index >= len1) { + val io = index - len1 + prefix2(io >>> BITS)(io & MASK) + } else prefix1(index) + }.asInstanceOf[A] else throw ioob(index) + } + + override def updated[B >: A](index: Int, elem: B): Vector[B] = { + if(index >= 0 && index < length0) { + if(index >= len123) { + val io = index - len123 + val i4 = io >>> BITS3 + val i3 = (io >>> BITS2) & MASK + val i2 = (io >>> BITS) & MASK + val i1 = io & MASK + if (i4 < data4.length ) copy(data4 = copyUpdate(data4, i4, i3, i2, i1, elem)) + else if(i3 < suffix3.length) copy(suffix3 = copyUpdate(suffix3, i3, i2, i1, elem)) + else if(i2 < suffix2.length) copy(suffix2 = copyUpdate(suffix2, i2, i1, elem)) + else copy(suffix1 = copyUpdate(suffix1, i1, elem)) + } else if(index >= len12) { + val io = index - len12 + copy(prefix3 = copyUpdate(prefix3, io >>> BITS2, (io >>> BITS) & MASK, io & MASK, elem)) + } else if(index >= len1) { + val io = index - len1 + copy(prefix2 = copyUpdate(prefix2, io >>> BITS, io & MASK, elem)) + } else { + copy(prefix1 = copyUpdate(prefix1, index, elem)) + } + } else throw ioob(index) + } + + override def appended[B >: A](elem: B): Vector[B] = { + if (suffix1.length < WIDTH ) copy(suffix1 = copyAppend1(suffix1, elem), length0 = length0+1) + else if(suffix2.length < WIDTH-1) copy(suffix2 = copyAppend(suffix2, suffix1), suffix1 = wrap1(elem), length0 = length0+1) + else if(suffix3.length < WIDTH-1) copy(suffix3 = copyAppend(suffix3, copyAppend(suffix2, suffix1)), suffix2 = empty2, suffix1 = wrap1(elem), length0 = length0+1) + else if(data4.length < WIDTH-2) copy(data4 = copyAppend(data4, copyAppend(suffix3, copyAppend(suffix2, suffix1))), suffix3 = empty3, suffix2 = empty2, suffix1 = wrap1(elem), length0 = length0+1) + else new Vector5(prefix1, len1, prefix2, len12, prefix3, len123, data4, (WIDTH-2)*WIDTH3 + len123, empty5, wrap4(copyAppend(suffix3, copyAppend(suffix2, suffix1))), empty3, empty2, wrap1(elem), length0+1) + } + + override def prepended[B >: A](elem: B): Vector[B] = { + if (len1 < WIDTH ) copy(copyPrepend1(elem, prefix1), len1+1, len12 = len12+1, len123 = len123+1, length0 = length0+1) + else if(len12 < WIDTH2 ) copy(wrap1(elem), 1, copyPrepend(prefix1, prefix2), len12+1, len123 = len123+1, length0 = length0+1) + else if(len123 < WIDTH3 ) copy(wrap1(elem), 1, empty2, 1, copyPrepend(copyPrepend(prefix1, prefix2), prefix3), len123+1, length0 = length0+1) + else if(data4.length < WIDTH-2) copy(wrap1(elem), 1, empty2, 1, empty3, 1, copyPrepend(copyPrepend(copyPrepend(prefix1, prefix2), prefix3), data4), length0 = length0+1) + else new Vector5(wrap1(elem), 1, empty2, 1, empty3, 1, wrap4(copyPrepend(copyPrepend(prefix1, prefix2), prefix3)), len123+1, empty5, data4, suffix3, suffix2, suffix1, length0+1) + } + + override def map[B](f: A => B): Vector[B] = + copy(prefix1 = mapElems1(prefix1, f), prefix2 = mapElems(2, prefix2, f), prefix3 = mapElems(3, prefix3, f), + data4 = mapElems(4, data4, f), + suffix3 = mapElems(3, suffix3, f), suffix2 = mapElems(2, suffix2, f), suffix1 = mapElems1(suffix1, f)) + + protected[this] def slice0(lo: Int, hi: Int): Vector[A] = { + val b = new VectorSliceBuilder(lo, hi) + b.consider(1, prefix1) + b.consider(2, prefix2) + b.consider(3, prefix3) + b.consider(4, data4) + b.consider(3, suffix3) + b.consider(2, suffix2) + b.consider(1, suffix1) + b.result() + } + + override def tail: Vector[A] = + if(len1 > 1) copy(copyTail(prefix1), len1-1, len12 = len12-1, len123 = len123-1, length0 = length0-1) + else slice0(1, length0) + + override def init: Vector[A] = + if(suffix1.length > 1) copy(suffix1 = copyInit(suffix1), length0 = length0-1) + else slice0(0, length0-1) + + protected[immutable] def vectorSliceCount: Int = 7 + protected[immutable] def vectorSlice(idx: Int): Array[_ <: AnyRef] = (idx: @switch) match { + case 0 => prefix1 + case 1 => prefix2 + case 2 => prefix3 + case 3 => data4 + case 4 => suffix3 + case 5 => suffix2 + case 6 => suffix1 + } + protected[immutable] def vectorSlicePrefixLength(idx: Int): Int = (idx: @switch) match { + case 0 => len1 + case 1 => len12 + case 2 => len123 + case 3 => len123 + data4.length*WIDTH3 + case 4 => len123 + data4.length*WIDTH3 + suffix3.length*WIDTH2 + case 5 => length0 - suffix1.length + case 6 => length0 + } + + override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B], k: Int): Vector[B] = + prepend1IfSpace(prefix1, prefix) match { + case null => super.prependedAll0(prefix, k) + case prefix1b => + val diff = prefix1b.length - prefix1.length + copy(prefix1 = prefix1b, + len1 = len1 + diff, + len12 = len12 + diff, + len123 = len123 + diff, + length0 = length0 + diff, + ) + } + + override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B], k: Int): Vector[B] = { + val suffix1b = append1IfSpace(suffix1, suffix) + if(suffix1b ne null) copy(suffix1 = suffix1b, length0 = length0-suffix1.length+suffix1b.length) + else super.appendedAll0(suffix, k) + } +} + + +/** 5-dimensional radix-balanced finger tree */ +private final class Vector5[+A](_prefix1: Arr1, private[immutable] val len1: Int, + private[immutable] val prefix2: Arr2, private[immutable] val len12: Int, + private[immutable] val prefix3: Arr3, private[immutable] val len123: Int, + private[immutable] val prefix4: Arr4, private[immutable] val len1234: Int, + private[immutable] val data5: Arr5, + private[immutable] val suffix4: Arr4, private[immutable] val suffix3: Arr3, private[immutable] val suffix2: Arr2, _suffix1: Arr1, + _length0: Int) extends BigVector[A](_prefix1, _suffix1, _length0) { + + @inline private[this] def copy(prefix1: Arr1 = prefix1, len1: Int = len1, + prefix2: Arr2 = prefix2, len12: Int = len12, + prefix3: Arr3 = prefix3, len123: Int = len123, + prefix4: Arr4 = prefix4, len1234: Int = len1234, + data5: Arr5 = data5, + suffix4: Arr4 = suffix4, suffix3: Arr3 = suffix3, suffix2: Arr2 = suffix2, suffix1: Arr1 = suffix1, + length0: Int = length0) = + new Vector5(prefix1, len1, prefix2, len12, prefix3, len123, prefix4, len1234, data5, suffix4, suffix3, suffix2, suffix1, length0) + + @inline def apply(index: Int): A = { + if(index >= 0 && index < length0) { + val io = index - len1234 + if(io >= 0) { + val i5 = io >>> BITS4 + val i4 = (io >>> BITS3) & MASK + val i3 = (io >>> BITS2) & MASK + val i2 = (io >>> BITS) & MASK + val i1 = io & MASK + if(i5 < data5.length) data5(i5)(i4)(i3)(i2)(i1) + else if(i4 < suffix4.length) suffix4(i4)(i3)(i2)(i1) + else if(i3 < suffix3.length) suffix3(i3)(i2)(i1) + else if(i2 < suffix2.length) suffix2(i2)(i1) + else suffix1(i1) + } else if(index >= len123) { + val io = index - len123 + prefix4(io >>> BITS3)((io >>> BITS2) & MASK)((io >>> BITS) & MASK)(io & MASK) + } else if(index >= len12) { + val io = index - len12 + prefix3(io >>> BITS2)((io >>> BITS) & MASK)(io & MASK) + } else if(index >= len1) { + val io = index - len1 + prefix2(io >>> BITS)(io & MASK) + } else prefix1(index) + }.asInstanceOf[A] else throw ioob(index) + } + + override def updated[B >: A](index: Int, elem: B): Vector[B] = { + if(index >= 0 && index < length0) { + if(index >= len1234) { + val io = index - len1234 + val i5 = io >>> BITS4 + val i4 = (io >>> BITS3) & MASK + val i3 = (io >>> BITS2) & MASK + val i2 = (io >>> BITS) & MASK + val i1 = io & MASK + if (i5 < data5.length ) copy(data5 = copyUpdate(data5, i5, i4, i3, i2, i1, elem)) + else if(i4 < suffix4.length) copy(suffix4 = copyUpdate(suffix4, i4, i3, i2, i1, elem)) + else if(i3 < suffix3.length) copy(suffix3 = copyUpdate(suffix3, i3, i2, i1, elem)) + else if(i2 < suffix2.length) copy(suffix2 = copyUpdate(suffix2, i2, i1, elem)) + else copy(suffix1 = copyUpdate(suffix1, i1, elem)) + } else if(index >= len123) { + val io = index - len123 + copy(prefix4 = copyUpdate(prefix4, io >>> BITS3, (io >>> BITS2) & MASK, (io >>> BITS) & MASK, io & MASK, elem)) + } else if(index >= len12) { + val io = index - len12 + copy(prefix3 = copyUpdate(prefix3, io >>> BITS2, (io >>> BITS) & MASK, io & MASK, elem)) + } else if(index >= len1) { + val io = index - len1 + copy(prefix2 = copyUpdate(prefix2, io >>> BITS, io & MASK, elem)) + } else { + copy(prefix1 = copyUpdate(prefix1, index, elem)) + } + } else throw ioob(index) + } + + override def appended[B >: A](elem: B): Vector[B] = { + if (suffix1.length < WIDTH ) copy(suffix1 = copyAppend1(suffix1, elem), length0 = length0+1) + else if(suffix2.length < WIDTH-1) copy(suffix2 = copyAppend(suffix2, suffix1), suffix1 = wrap1(elem), length0 = length0+1) + else if(suffix3.length < WIDTH-1) copy(suffix3 = copyAppend(suffix3, copyAppend(suffix2, suffix1)), suffix2 = empty2, suffix1 = wrap1(elem), length0 = length0+1) + else if(suffix4.length < WIDTH-1) copy(suffix4 = copyAppend(suffix4, copyAppend(suffix3, copyAppend(suffix2, suffix1))), suffix3 = empty3, suffix2 = empty2, suffix1 = wrap1(elem), length0 = length0+1) + else if(data5.length < WIDTH-2) copy(data5 = copyAppend(data5, copyAppend(suffix4, copyAppend(suffix3, copyAppend(suffix2, suffix1)))), suffix4 = empty4, suffix3 = empty3, suffix2 = empty2, suffix1 = wrap1(elem), length0 = length0+1) + else new Vector6(prefix1, len1, prefix2, len12, prefix3, len123, prefix4, len1234, data5, (WIDTH-2)*WIDTH4 + len1234, empty6, wrap5(copyAppend(suffix4, copyAppend(suffix3, copyAppend(suffix2, suffix1)))), empty4, empty3, empty2, wrap1(elem), length0+1) + } + + override def prepended[B >: A](elem: B): Vector[B] = { + if (len1 < WIDTH ) copy(copyPrepend1(elem, prefix1), len1+1, len12 = len12+1, len123 = len123+1, len1234 = len1234+1, length0 = length0+1) + else if(len12 < WIDTH2 ) copy(wrap1(elem), 1, copyPrepend(prefix1, prefix2), len12+1, len123 = len123+1, len1234 = len1234+1, length0 = length0+1) + else if(len123 < WIDTH3 ) copy(wrap1(elem), 1, empty2, 1, copyPrepend(copyPrepend(prefix1, prefix2), prefix3), len123+1, len1234 = len1234+1, length0 = length0+1) + else if(len1234 < WIDTH4 ) copy(wrap1(elem), 1, empty2, 1, empty3, 1, copyPrepend(copyPrepend(copyPrepend(prefix1, prefix2), prefix3), prefix4), len1234+1, length0 = length0+1) + else if(data5.length < WIDTH-2) copy(wrap1(elem), 1, empty2, 1, empty3, 1, empty4, 1, copyPrepend(copyPrepend(copyPrepend(copyPrepend(prefix1, prefix2), prefix3), prefix4), data5), length0 = length0+1) + else new Vector6(wrap1(elem), 1, empty2, 1, empty3, 1, empty4, 1, wrap5(copyPrepend(copyPrepend(copyPrepend(prefix1, prefix2), prefix3), prefix4)), len1234+1, empty6, data5, suffix4, suffix3, suffix2, suffix1, length0+1) + } + + override def map[B](f: A => B): Vector[B] = + copy(prefix1 = mapElems1(prefix1, f), prefix2 = mapElems(2, prefix2, f), prefix3 = mapElems(3, prefix3, f), prefix4 = mapElems(4, prefix4, f), + data5 = mapElems(5, data5, f), + suffix4 = mapElems(4, suffix4, f), suffix3 = mapElems(3, suffix3, f), suffix2 = mapElems(2, suffix2, f), suffix1 = mapElems1(suffix1, f)) + + protected[this] def slice0(lo: Int, hi: Int): Vector[A] = { + val b = new VectorSliceBuilder(lo, hi) + b.consider(1, prefix1) + b.consider(2, prefix2) + b.consider(3, prefix3) + b.consider(4, prefix4) + b.consider(5, data5) + b.consider(4, suffix4) + b.consider(3, suffix3) + b.consider(2, suffix2) + b.consider(1, suffix1) + b.result() + } + + override def tail: Vector[A] = + if(len1 > 1) copy(copyTail(prefix1), len1-1, len12 = len12-1, len123 = len123-1, len1234 = len1234-1, length0 = length0-1) + else slice0(1, length0) + + override def init: Vector[A] = + if(suffix1.length > 1) copy(suffix1 = copyInit(suffix1), length0 = length0-1) + else slice0(0, length0-1) + + protected[immutable] def vectorSliceCount: Int = 9 + protected[immutable] def vectorSlice(idx: Int): Array[_ <: AnyRef] = (idx: @switch) match { + case 0 => prefix1 + case 1 => prefix2 + case 2 => prefix3 + case 3 => prefix4 + case 4 => data5 + case 5 => suffix4 + case 6 => suffix3 + case 7 => suffix2 + case 8 => suffix1 + } + protected[immutable] def vectorSlicePrefixLength(idx: Int): Int = (idx: @switch) match { + case 0 => len1 + case 1 => len12 + case 2 => len123 + case 3 => len1234 + case 4 => len1234 + data5.length*WIDTH4 + case 5 => len1234 + data5.length*WIDTH4 + suffix4.length*WIDTH3 + case 6 => len1234 + data5.length*WIDTH4 + suffix4.length*WIDTH3 + suffix3.length*WIDTH2 + case 7 => length0 - suffix1.length + case 8 => length0 + } + + override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B], k: Int): Vector[B] = + prepend1IfSpace(prefix1, prefix) match { + case null => super.prependedAll0(prefix, k) + case prefix1b => + val diff = prefix1b.length - prefix1.length + copy(prefix1 = prefix1b, + len1 = len1 + diff, + len12 = len12 + diff, + len123 = len123 + diff, + len1234 = len1234 + diff, + length0 = length0 + diff, + ) + } + + override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B], k: Int): Vector[B] = { + val suffix1b = append1IfSpace(suffix1, suffix) + if(suffix1b ne null) copy(suffix1 = suffix1b, length0 = length0-suffix1.length+suffix1b.length) + else super.appendedAll0(suffix, k) + } +} + + +/** 6-dimensional radix-balanced finger tree */ +private final class Vector6[+A](_prefix1: Arr1, private[immutable] val len1: Int, + private[immutable] val prefix2: Arr2, private[immutable] val len12: Int, + private[immutable] val prefix3: Arr3, private[immutable] val len123: Int, + private[immutable] val prefix4: Arr4, private[immutable] val len1234: Int, + private[immutable] val prefix5: Arr5, private[immutable] val len12345: Int, + private[immutable] val data6: Arr6, + private[immutable] val suffix5: Arr5, private[immutable] val suffix4: Arr4, private[immutable] val suffix3: Arr3, private[immutable] val suffix2: Arr2, _suffix1: Arr1, + _length0: Int) extends BigVector[A](_prefix1, _suffix1, _length0) { + + @inline private[this] def copy(prefix1: Arr1 = prefix1, len1: Int = len1, + prefix2: Arr2 = prefix2, len12: Int = len12, + prefix3: Arr3 = prefix3, len123: Int = len123, + prefix4: Arr4 = prefix4, len1234: Int = len1234, + prefix5: Arr5 = prefix5, len12345: Int = len12345, + data6: Arr6 = data6, + suffix5: Arr5 = suffix5, suffix4: Arr4 = suffix4, suffix3: Arr3 = suffix3, suffix2: Arr2 = suffix2, suffix1: Arr1 = suffix1, + length0: Int = length0) = + new Vector6(prefix1, len1, prefix2, len12, prefix3, len123, prefix4, len1234, prefix5, len12345, data6, suffix5, suffix4, suffix3, suffix2, suffix1, length0) + + @inline def apply(index: Int): A = { + if(index >= 0 && index < length0) { + val io = index - len12345 + if(io >= 0) { + val i6 = io >>> BITS5 + val i5 = (io >>> BITS4) & MASK + val i4 = (io >>> BITS3) & MASK + val i3 = (io >>> BITS2) & MASK + val i2 = (io >>> BITS) & MASK + val i1 = io & MASK + if(i6 < data6.length) data6(i6)(i5)(i4)(i3)(i2)(i1) + else if(i5 < suffix5.length) suffix5(i5)(i4)(i3)(i2)(i1) + else if(i4 < suffix4.length) suffix4(i4)(i3)(i2)(i1) + else if(i3 < suffix3.length) suffix3(i3)(i2)(i1) + else if(i2 < suffix2.length) suffix2(i2)(i1) + else suffix1(i1) + } else if(index >= len1234) { + val io = index - len1234 + prefix5(io >>> BITS4)((io >>> BITS3) & MASK)((io >>> BITS2) & MASK)((io >>> BITS) & MASK)(io & MASK) + } else if(index >= len123) { + val io = index - len123 + prefix4(io >>> BITS3)((io >>> BITS2) & MASK)((io >>> BITS) & MASK)(io & MASK) + } else if(index >= len12) { + val io = index - len12 + prefix3(io >>> BITS2)((io >>> BITS) & MASK)(io & MASK) + } else if(index >= len1) { + val io = index - len1 + prefix2(io >>> BITS)(io & MASK) + } else prefix1(index) + }.asInstanceOf[A] else throw ioob(index) + } + + override def updated[B >: A](index: Int, elem: B): Vector[B] = { + if(index >= 0 && index < length0) { + if(index >= len12345) { + val io = index - len12345 + val i6 = io >>> BITS5 + val i5 = (io >>> BITS4) & MASK + val i4 = (io >>> BITS3) & MASK + val i3 = (io >>> BITS2) & MASK + val i2 = (io >>> BITS) & MASK + val i1 = io & MASK + if (i6 < data6.length ) copy(data6 = copyUpdate(data6, i6, i5, i4, i3, i2, i1, elem)) + else if(i5 < suffix5.length) copy(suffix5 = copyUpdate(suffix5, i5, i4, i3, i2, i1, elem)) + else if(i4 < suffix4.length) copy(suffix4 = copyUpdate(suffix4, i4, i3, i2, i1, elem)) + else if(i3 < suffix3.length) copy(suffix3 = copyUpdate(suffix3, i3, i2, i1, elem)) + else if(i2 < suffix2.length) copy(suffix2 = copyUpdate(suffix2, i2, i1, elem)) + else copy(suffix1 = copyUpdate(suffix1, i1, elem)) + } else if(index >= len1234) { + val io = index - len1234 + copy(prefix5 = copyUpdate(prefix5, io >>> BITS4, (io >>> BITS3) & MASK, (io >>> BITS2) & MASK, (io >>> BITS) & MASK, io & MASK, elem)) + } else if(index >= len123) { + val io = index - len123 + copy(prefix4 = copyUpdate(prefix4, io >>> BITS3, (io >>> BITS2) & MASK, (io >>> BITS) & MASK, io & MASK, elem)) + } else if(index >= len12) { + val io = index - len12 + copy(prefix3 = copyUpdate(prefix3, io >>> BITS2, (io >>> BITS) & MASK, io & MASK, elem)) + } else if(index >= len1) { + val io = index - len1 + copy(prefix2 = copyUpdate(prefix2, io >>> BITS, io & MASK, elem)) + } else { + copy(prefix1 = copyUpdate(prefix1, index, elem)) + } + } else throw ioob(index) + } + + override def appended[B >: A](elem: B): Vector[B] = { + if (suffix1.length < WIDTH ) copy(suffix1 = copyAppend1(suffix1, elem), length0 = length0+1) + else if(suffix2.length < WIDTH-1 ) copy(suffix2 = copyAppend(suffix2, suffix1), suffix1 = wrap1(elem), length0 = length0+1) + else if(suffix3.length < WIDTH-1 ) copy(suffix3 = copyAppend(suffix3, copyAppend(suffix2, suffix1)), suffix2 = empty2, suffix1 = wrap1(elem), length0 = length0+1) + else if(suffix4.length < WIDTH-1 ) copy(suffix4 = copyAppend(suffix4, copyAppend(suffix3, copyAppend(suffix2, suffix1))), suffix3 = empty3, suffix2 = empty2, suffix1 = wrap1(elem), length0 = length0+1) + else if(suffix5.length < WIDTH-1 ) copy(suffix5 = copyAppend(suffix5, copyAppend(suffix4, copyAppend(suffix3, copyAppend(suffix2, suffix1)))), suffix4 = empty4, suffix3 = empty3, suffix2 = empty2, suffix1 = wrap1(elem), length0 = length0+1) + else if(data6.length < LASTWIDTH-2) copy(data6 = copyAppend(data6, copyAppend(suffix5, copyAppend(suffix4, copyAppend(suffix3, copyAppend(suffix2, suffix1))))), suffix5 = empty5, suffix4 = empty4, suffix3 = empty3, suffix2 = empty2, suffix1 = wrap1(elem), length0 = length0+1) + else throw new IllegalArgumentException + } + + override def prepended[B >: A](elem: B): Vector[B] = { + if (len1 < WIDTH ) copy(copyPrepend1(elem, prefix1), len1+1, len12 = len12+1, len123 = len123+1, len1234 = len1234+1, len12345 = len12345+1, length0 = length0+1) + else if(len12 < WIDTH2 ) copy(wrap1(elem), 1, copyPrepend(prefix1, prefix2), len12+1, len123 = len123+1, len1234 = len1234+1, len12345 = len12345+1, length0 = length0+1) + else if(len123 < WIDTH3 ) copy(wrap1(elem), 1, empty2, 1, copyPrepend(copyPrepend(prefix1, prefix2), prefix3), len123+1, len1234 = len1234+1, len12345 = len12345+1, length0 = length0+1) + else if(len1234 < WIDTH4 ) copy(wrap1(elem), 1, empty2, 1, empty3, 1, copyPrepend(copyPrepend(copyPrepend(prefix1, prefix2), prefix3), prefix4), len1234+1, len12345 = len12345+1, length0 = length0+1) + else if(len12345 < WIDTH5 ) copy(wrap1(elem), 1, empty2, 1, empty3, 1, empty4, 1, copyPrepend(copyPrepend(copyPrepend(copyPrepend(prefix1, prefix2), prefix3), prefix4), prefix5), len12345+1, length0 = length0+1) + else if(data6.length < LASTWIDTH-2) copy(wrap1(elem), 1, empty2, 1, empty3, 1, empty4, 1, empty5, 1, copyPrepend(copyPrepend(copyPrepend(copyPrepend(copyPrepend(prefix1, prefix2), prefix3), prefix4), prefix5), data6), length0 = length0+1) + else throw new IllegalArgumentException + } + + override def map[B](f: A => B): Vector[B] = + copy(prefix1 = mapElems1(prefix1, f), prefix2 = mapElems(2, prefix2, f), prefix3 = mapElems(3, prefix3, f), prefix4 = mapElems(4, prefix4, f), prefix5 = mapElems(5, prefix5, f), + data6 = mapElems(6, data6, f), + suffix5 = mapElems(5, suffix5, f), suffix4 = mapElems(4, suffix4, f), suffix3 = mapElems(3, suffix3, f), suffix2 = mapElems(2, suffix2, f), suffix1 = mapElems1(suffix1, f)) + + protected[this] def slice0(lo: Int, hi: Int): Vector[A] = { + val b = new VectorSliceBuilder(lo, hi) + b.consider(1, prefix1) + b.consider(2, prefix2) + b.consider(3, prefix3) + b.consider(4, prefix4) + b.consider(5, prefix5) + b.consider(6, data6) + b.consider(5, suffix5) + b.consider(4, suffix4) + b.consider(3, suffix3) + b.consider(2, suffix2) + b.consider(1, suffix1) + b.result() + } + + override def tail: Vector[A] = + if(len1 > 1) copy(copyTail(prefix1), len1-1, len12 = len12-1, len123 = len123-1, len1234 = len1234-1, len12345 = len12345-1, length0 = length0-1) + else slice0(1, length0) + + override def init: Vector[A] = + if(suffix1.length > 1) copy(suffix1 = copyInit(suffix1), length0 = length0-1) + else slice0(0, length0-1) + + protected[immutable] def vectorSliceCount: Int = 11 + protected[immutable] def vectorSlice(idx: Int): Array[_ <: AnyRef] = (idx: @switch) match { + case 0 => prefix1 + case 1 => prefix2 + case 2 => prefix3 + case 3 => prefix4 + case 4 => prefix5 + case 5 => data6 + case 6 => suffix5 + case 7 => suffix4 + case 8 => suffix3 + case 9 => suffix2 + case 10 => suffix1 + } + protected[immutable] def vectorSlicePrefixLength(idx: Int): Int = (idx: @switch) match { + case 0 => len1 + case 1 => len12 + case 2 => len123 + case 3 => len1234 + case 4 => len12345 + case 5 => len12345 + data6.length*WIDTH5 + case 6 => len12345 + data6.length*WIDTH5 + suffix5.length*WIDTH4 + case 7 => len12345 + data6.length*WIDTH5 + suffix5.length*WIDTH4 + suffix4.length*WIDTH3 + case 8 => len12345 + data6.length*WIDTH5 + suffix5.length*WIDTH4 + suffix4.length*WIDTH3 + suffix3.length*WIDTH2 + case 9 => length0 - suffix1.length + case 10 => length0 + } + + override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B], k: Int): Vector[B] = + prepend1IfSpace(prefix1, prefix) match { + case null => super.prependedAll0(prefix, k) + case prefix1b => + val diff = prefix1b.length - prefix1.length + copy(prefix1 = prefix1b, + len1 = len1 + diff, + len12 = len12 + diff, + len123 = len123 + diff, + len1234 = len1234 + diff, + len12345 = len12345 + diff, + length0 = length0 + diff, + ) + } + + override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B], k: Int): Vector[B] = { + val suffix1b = append1IfSpace(suffix1, suffix) + if(suffix1b ne null) copy(suffix1 = suffix1b, length0 = length0-suffix1.length+suffix1b.length) + else super.appendedAll0(suffix, k) + } +} + + +/** Helper class for vector slicing. It is initialized with the validated start and end index, + * then the vector slices are added in succession with `consider`. No matter what the dimension + * of the originating vector is or where the cut is performed, this always results in a + * structure with the highest-dimensional data in the middle and fingers of decreasing dimension + * at both ends, which can be turned into a new vector with very little rebalancing. + */ +private final class VectorSliceBuilder(lo: Int, hi: Int) { + //println(s"***** VectorSliceBuilder($lo, $hi)") + + private[this] val slices = new Array[Array[AnyRef]](11) + private[this] var len, pos, maxDim = 0 + + @inline private[this] def prefixIdx(n: Int) = n-1 + @inline private[this] def suffixIdx(n: Int) = 11-n + + def consider[T <: AnyRef](n: Int, a: Array[T]): Unit = { + //println(s"***** consider($n, /${a.length})") + val count = a.length * (1 << (BITS*(n-1))) + val lo0 = mmax(lo-pos, 0) + val hi0 = mmin(hi-pos, count) + if(hi0 > lo0) { + addSlice(n, a, lo0, hi0) + len += (hi0 - lo0) + } + pos += count + } + + private[this] def addSlice[T <: AnyRef](n: Int, a: Array[T], lo: Int, hi: Int): Unit = { + //println(s"***** addSlice($n, /${a.length}, $lo, $hi)") + if(n == 1) { + add(1, copyOrUse(a, lo, hi)) + } else { + val bitsN = BITS * (n-1) + val widthN = 1 << bitsN + val loN = lo >>> bitsN + val hiN = hi >>> bitsN + val loRest = lo & (widthN - 1) + val hiRest = hi & (widthN - 1) + //println(s"***** bitsN=$bitsN, loN=$loN, hiN=$hiN, loRest=$loRest, hiRest=$hiRest") + if(loRest == 0) { + if(hiRest == 0) { + add(n, copyOrUse(a, loN, hiN)) + } else { + if(hiN > loN) add(n, copyOrUse(a, loN, hiN)) + addSlice(n-1, a(hiN).asInstanceOf[Array[AnyRef]], 0, hiRest) + } + } else { + if(hiN == loN) { + addSlice(n-1, a(loN).asInstanceOf[Array[AnyRef]], loRest, hiRest) + } else { + addSlice(n-1, a(loN).asInstanceOf[Array[AnyRef]], loRest, widthN) + if(hiRest == 0) { + if(hiN > loN+1) add(n, copyOrUse(a, loN+1, hiN)) + } else { + if(hiN > loN+1) add(n, copyOrUse(a, loN+1, hiN)) + addSlice(n-1, a(hiN).asInstanceOf[Array[AnyRef]], 0, hiRest) + } + } + } + } + } + + private[this] def add[T <: AnyRef](n: Int, a: Array[T]): Unit = { + //println(s"***** add($n, /${a.length})") + val idx = + if(n <= maxDim) suffixIdx(n) + else { maxDim = n; prefixIdx(n) } + slices(idx) = a.asInstanceOf[Array[AnyRef]] + } + + def result[A](): Vector[A] = { + //println(s"***** result: $len, $maxDim") + if(len <= 32) { + if(len == 0) Vector0 + else { + val prefix1 = slices(prefixIdx(1)) + val suffix1 = slices(suffixIdx(1)) + //println(s"***** prefix1: ${if(prefix1 == null) "null" else prefix1.mkString("[", ",", "]")}, suffix1: ${if(suffix1 == null) "null" else suffix1.mkString("[", ",", "]")}") + val a: Arr1 = + if(prefix1 ne null) { + if(suffix1 ne null) concatArrays(prefix1, suffix1) + else prefix1 + } else if(suffix1 ne null) suffix1 + else { + val prefix2 = slices(prefixIdx(2)).asInstanceOf[Arr2] + if(prefix2 ne null) prefix2(0) + else { + val suffix2 = slices(suffixIdx(2)).asInstanceOf[Arr2] + suffix2(0) + } + } + new Vector1(a) + } + } else { + balancePrefix(1) + balanceSuffix(1) + var resultDim = maxDim + if(resultDim < 6) { + val pre = slices(prefixIdx(maxDim)) + val suf = slices(suffixIdx(maxDim)) + if((pre ne null) && (suf ne null)) { + // The highest-dimensional data consists of two slices: concatenate if they fit into the main data array, + // otherwise increase the dimension + if(pre.length + suf.length <= WIDTH-2) { + slices(prefixIdx(maxDim)) = concatArrays(pre, suf) + slices(suffixIdx(maxDim)) = null + } else resultDim += 1 + } else { + // A single highest-dimensional slice could have length WIDTH-1 if it came from a prefix or suffix but we + // only allow WIDTH-2 for the main data, so increase the dimension in this case + val one = if(pre ne null) pre else suf + if(one.length > WIDTH-2) resultDim += 1 + } + } + val prefix1 = slices(prefixIdx(1)) + val suffix1 = slices(suffixIdx(1)) + val len1 = prefix1.length + val res = (resultDim: @switch) match { + case 2 => + val data2 = dataOr(2, empty2) + new Vector2[A](prefix1, len1, data2, suffix1, len) + case 3 => + val prefix2 = prefixOr(2, empty2) + val data3 = dataOr(3, empty3) + val suffix2 = suffixOr(2, empty2) + val len12 = len1 + (prefix2.length * WIDTH) + new Vector3[A](prefix1, len1, prefix2, len12, data3, suffix2, suffix1, len) + case 4 => + val prefix2 = prefixOr(2, empty2) + val prefix3 = prefixOr(3, empty3) + val data4 = dataOr(4, empty4) + val suffix3 = suffixOr(3, empty3) + val suffix2 = suffixOr(2, empty2) + val len12 = len1 + (prefix2.length * WIDTH) + val len123 = len12 + (prefix3.length * WIDTH2) + new Vector4[A](prefix1, len1, prefix2, len12, prefix3, len123, data4, suffix3, suffix2, suffix1, len) + case 5 => + val prefix2 = prefixOr(2, empty2) + val prefix3 = prefixOr(3, empty3) + val prefix4 = prefixOr(4, empty4) + val data5 = dataOr(5, empty5) + val suffix4 = suffixOr(4, empty4) + val suffix3 = suffixOr(3, empty3) + val suffix2 = suffixOr(2, empty2) + val len12 = len1 + (prefix2.length * WIDTH) + val len123 = len12 + (prefix3.length * WIDTH2) + val len1234 = len123 + (prefix4.length * WIDTH3) + new Vector5[A](prefix1, len1, prefix2, len12, prefix3, len123, prefix4, len1234, data5, suffix4, suffix3, suffix2, suffix1, len) + case 6 => + val prefix2 = prefixOr(2, empty2) + val prefix3 = prefixOr(3, empty3) + val prefix4 = prefixOr(4, empty4) + val prefix5 = prefixOr(5, empty5) + val data6 = dataOr(6, empty6) + val suffix5 = suffixOr(5, empty5) + val suffix4 = suffixOr(4, empty4) + val suffix3 = suffixOr(3, empty3) + val suffix2 = suffixOr(2, empty2) + val len12 = len1 + (prefix2.length * WIDTH) + val len123 = len12 + (prefix3.length * WIDTH2) + val len1234 = len123 + (prefix4.length * WIDTH3) + val len12345 = len1234 + (prefix5.length * WIDTH4) + new Vector6[A](prefix1, len1, prefix2, len12, prefix3, len123, prefix4, len1234, prefix5, len12345, data6, suffix5, suffix4, suffix3, suffix2, suffix1, len) + } + res + } + } + + @inline private[this] def prefixOr[T <: AnyRef](n: Int, a: Array[T]): Array[T] = { + val p = slices(prefixIdx(n)) + if(p ne null) p.asInstanceOf[Array[T]] else a + } + + @inline private[this] def suffixOr[T <: AnyRef](n: Int, a: Array[T]): Array[T] = { + val s = slices(suffixIdx(n)) + if(s ne null) s.asInstanceOf[Array[T]] else a + } + + @inline private[this] def dataOr[T <: AnyRef](n: Int, a: Array[T]): Array[T] = { + val p = slices(prefixIdx(n)) + if(p ne null) p.asInstanceOf[Array[T]] + else { + val s = slices(suffixIdx(n)) + if(s ne null) s.asInstanceOf[Array[T]] else a + } + } + + /** Ensure prefix is not empty */ + private[this] def balancePrefix(n: Int): Unit = { + if(slices(prefixIdx(n)) eq null) { + if(n == maxDim) { + slices(prefixIdx(n)) = slices(suffixIdx(n)) + slices(suffixIdx(n)) = null + } else { + balancePrefix(n+1) + val preN1 = slices(prefixIdx(n+1)).asInstanceOf[Array[Array[AnyRef]]] + //assert(preN1 ne null) + slices(prefixIdx(n)) = preN1(0) + if(preN1.length == 1) { + slices(prefixIdx(n+1)) = null + if((maxDim == n+1) && (slices(suffixIdx(n+1)) eq null)) maxDim = n + } else { + slices(prefixIdx(n+1)) = copyOfRange(preN1, 1, preN1.length).asInstanceOf[Array[AnyRef]] + } + } + } + } + + /** Ensure suffix is not empty */ + private[this] def balanceSuffix(n: Int): Unit = { + if(slices(suffixIdx(n)) eq null) { + if(n == maxDim) { + slices(suffixIdx(n)) = slices(prefixIdx(n)) + slices(prefixIdx(n)) = null + } else { + balanceSuffix(n+1) + val sufN1 = slices(suffixIdx(n+1)).asInstanceOf[Array[Array[AnyRef]]] + //assert(sufN1 ne null, s"n=$n, maxDim=$maxDim, slices=${slices.mkString(",")}") + slices(suffixIdx(n)) = sufN1(sufN1.length-1) + if(sufN1.length == 1) { + slices(suffixIdx(n+1)) = null + if((maxDim == n+1) && (slices(prefixIdx(n+1)) eq null)) maxDim = n + } else { + slices(suffixIdx(n+1)) = copyOfRange(sufN1, 0, sufN1.length-1).asInstanceOf[Array[AnyRef]] + } + } + } + } + + override def toString: String = + s"VectorSliceBuilder(lo=$lo, hi=$hi, len=$len, pos=$pos, maxDim=$maxDim)" + + private[immutable] def getSlices: Array[Array[AnyRef]] = slices +} + + +final class VectorBuilder[A] extends ReusableBuilder[A, Vector[A]] { + + private[this] var a6: Arr6 = _ + private[this] var a5: Arr5 = _ + private[this] var a4: Arr4 = _ + private[this] var a3: Arr3 = _ + private[this] var a2: Arr2 = _ + private[this] var a1: Arr1 = new Arr1(WIDTH) + private[this] var len1, lenRest, offset = 0 + private[this] var prefixIsRightAligned = false + private[this] var depth = 1 + + @inline private[this] final def setLen(i: Int): Unit = { + len1 = i & MASK + lenRest = i - len1 + } + + override def knownSize: Int = len1 + lenRest - offset + + @inline def size: Int = knownSize + @inline def isEmpty: Boolean = knownSize == 0 + @inline def nonEmpty: Boolean = knownSize != 0 + + def clear(): Unit = { + a6 = null + a5 = null + a4 = null + a3 = null + a2 = null + a1 = new Arr1(WIDTH) + len1 = 0 + lenRest = 0 + offset = 0 + prefixIsRightAligned = false + depth = 1 + } + + private[immutable] def initSparse(size: Int, elem: A): Unit = { + setLen(size) + Arrays.fill(a1, elem) + if(size > WIDTH) { + a2 = new Array(WIDTH) + Arrays.fill(a2.asInstanceOf[Array[AnyRef]], a1) + if(size > WIDTH2) { + a3 = new Array(WIDTH) + Arrays.fill(a3.asInstanceOf[Array[AnyRef]], a2) + if(size > WIDTH3) { + a4 = new Array(WIDTH) + Arrays.fill(a4.asInstanceOf[Array[AnyRef]], a3) + if(size > WIDTH4) { + a5 = new Array(WIDTH) + Arrays.fill(a5.asInstanceOf[Array[AnyRef]], a4) + if(size > WIDTH5) { + a6 = new Array(LASTWIDTH) + Arrays.fill(a6.asInstanceOf[Array[AnyRef]], a5) + depth = 6 + } else depth = 5 + } else depth = 4 + } else depth = 3 + } else depth = 2 + } else depth = 1 + } + + private[immutable] def initFrom(prefix1: Arr1): Unit = { + depth = 1 + setLen(prefix1.length) + a1 = copyOrUse(prefix1, 0, WIDTH) + if(len1 == 0 && lenRest > 0) { + // force advance() on next addition: + len1 = WIDTH + lenRest -= WIDTH + } + } + + private[immutable] def initFrom(v: Vector[_]): this.type = { + (v.vectorSliceCount: @switch) match { + case 0 => + case 1 => + val v1 = v.asInstanceOf[Vector1[_]] + depth = 1 + setLen(v1.prefix1.length) + a1 = copyOrUse(v1.prefix1, 0, WIDTH) + case 3 => + val v2 = v.asInstanceOf[Vector2[_]] + val d2 = v2.data2 + a1 = copyOrUse(v2.suffix1, 0, WIDTH) + depth = 2 + offset = WIDTH - v2.len1 + setLen(v2.length0 + offset) + a2 = new Arr2(WIDTH) + a2(0) = v2.prefix1 + System.arraycopy(d2, 0, a2, 1, d2.length) + a2(d2.length+1) = a1 + case 5 => + val v3 = v.asInstanceOf[Vector3[_]] + val d3 = v3.data3 + val s2 = v3.suffix2 + a1 = copyOrUse(v3.suffix1, 0, WIDTH) + depth = 3 + offset = WIDTH2 - v3.len12 + setLen(v3.length0 + offset) + a3 = new Arr3(WIDTH) + a3(0) = copyPrepend(v3.prefix1, v3.prefix2) + System.arraycopy(d3, 0, a3, 1, d3.length) + a2 = copyOf(s2, WIDTH) + a3(d3.length+1) = a2 + a2(s2.length) = a1 + case 7 => + val v4 = v.asInstanceOf[Vector4[_]] + val d4 = v4.data4 + val s3 = v4.suffix3 + val s2 = v4.suffix2 + a1 = copyOrUse(v4.suffix1, 0, WIDTH) + depth = 4 + offset = WIDTH3 - v4.len123 + setLen(v4.length0 + offset) + a4 = new Arr4(WIDTH) + a4(0) = copyPrepend(copyPrepend(v4.prefix1, v4.prefix2), v4.prefix3) + System.arraycopy(d4, 0, a4, 1, d4.length) + a3 = copyOf(s3, WIDTH) + a2 = copyOf(s2, WIDTH) + a4(d4.length+1) = a3 + a3(s3.length) = a2 + a2(s2.length) = a1 + case 9 => + val v5 = v.asInstanceOf[Vector5[_]] + val d5 = v5.data5 + val s4 = v5.suffix4 + val s3 = v5.suffix3 + val s2 = v5.suffix2 + a1 = copyOrUse(v5.suffix1, 0, WIDTH) + depth = 5 + offset = WIDTH4 - v5.len1234 + setLen(v5.length0 + offset) + a5 = new Arr5(WIDTH) + a5(0) = copyPrepend(copyPrepend(copyPrepend(v5.prefix1, v5.prefix2), v5.prefix3), v5.prefix4) + System.arraycopy(d5, 0, a5, 1, d5.length) + a4 = copyOf(s4, WIDTH) + a3 = copyOf(s3, WIDTH) + a2 = copyOf(s2, WIDTH) + a5(d5.length+1) = a4 + a4(s4.length) = a3 + a3(s3.length) = a2 + a2(s2.length) = a1 + case 11 => + val v6 = v.asInstanceOf[Vector6[_]] + val d6 = v6.data6 + val s5 = v6.suffix5 + val s4 = v6.suffix4 + val s3 = v6.suffix3 + val s2 = v6.suffix2 + a1 = copyOrUse(v6.suffix1, 0, WIDTH) + depth = 6 + offset = WIDTH5 - v6.len12345 + setLen(v6.length0 + offset) + a6 = new Arr6(LASTWIDTH) + a6(0) = copyPrepend(copyPrepend(copyPrepend(copyPrepend(v6.prefix1, v6.prefix2), v6.prefix3), v6.prefix4), v6.prefix5) + System.arraycopy(d6, 0, a6, 1, d6.length) + a5 = copyOf(s5, WIDTH) + a4 = copyOf(s4, WIDTH) + a3 = copyOf(s3, WIDTH) + a2 = copyOf(s2, WIDTH) + a6(d6.length+1) = a5 + a5(s5.length) = a4 + a4(s4.length) = a3 + a3(s3.length) = a2 + a2(s2.length) = a1 + } + if(len1 == 0 && lenRest > 0) { + // force advance() on next addition: + len1 = WIDTH + lenRest -= WIDTH + } + this + } + + //TODO Make public; this method is only private for binary compatibility + private[collection] def alignTo(before: Int, bigVector: Vector[A]): this.type = { + if (len1 != 0 || lenRest != 0) + throw new UnsupportedOperationException("A non-empty VectorBuilder cannot be aligned retrospectively. Please call .reset() or use a new VectorBuilder.") + val (prefixLength, maxPrefixLength) = bigVector match { + case Vector0 => (0, 1) + case v1: Vector1[_] => (0, 1) + case v2: Vector2[_] => (v2.len1, WIDTH) + case v3: Vector3[_] => (v3.len12, WIDTH2) + case v4: Vector4[_] => (v4.len123, WIDTH3) + case v5: Vector5[_] => (v5.len1234, WIDTH4) + case v6: Vector6[_] => (v6.len12345, WIDTH5) + } + if (maxPrefixLength == 1) return this // does not really make sense to align for <= 32 element-vector + val overallPrefixLength = (before + prefixLength) % maxPrefixLength + offset = (maxPrefixLength - overallPrefixLength) % maxPrefixLength + // pretend there are already `offset` elements added + advanceN(offset & ~MASK) + len1 = offset & MASK + prefixIsRightAligned = true + this + } + + /** + * Removes `offset` leading `null`s in the prefix. + * This is needed after calling `alignTo` and subsequent additions, + * directly before the result is used for creating a new Vector. + * Note that the outermost array keeps its length to keep the + * Builder re-usable. + * + * example: + * a2 = Array(null, ..., null, Array(null, .., null, 0, 1, .., x), Array(x+1, .., x+32), ...) + * becomes + * a2 = Array(Array(0, 1, .., x), Array(x+1, .., x+32), ..., ?, ..., ?) + */ + private[this] def leftAlignPrefix(): Unit = { + @inline def shrinkOffsetIfToLarge(width: Int): Unit = { + val newOffset = offset % width + lenRest -= offset - newOffset + offset = newOffset + } + var a: Array[AnyRef] = null // the array we modify + var aParent: Array[AnyRef] = null // a's parent, so aParent(0) == a + if (depth >= 6) { + a = a6.asInstanceOf[Array[AnyRef]] + val i = offset >>> BITS5 + if (i > 0) System.arraycopy(a, i, a, 0, LASTWIDTH - i) + shrinkOffsetIfToLarge(WIDTH5) + if ((lenRest >>> BITS5) == 0) depth = 5 + aParent = a + a = a(0).asInstanceOf[Array[AnyRef]] + } + if (depth >= 5) { + if (a == null) a = a5.asInstanceOf[Array[AnyRef]] + val i = (offset >>> BITS4) & MASK + if (depth == 5) { + if (i > 0) System.arraycopy(a, i, a, 0, WIDTH - i) + a5 = a.asInstanceOf[Arr5] + shrinkOffsetIfToLarge(WIDTH4) + if ((lenRest >>> BITS4) == 0) depth = 4 + } else { + if (i > 0) a = copyOfRange(a, i, WIDTH) + aParent(0) = a + } + aParent = a + a = a(0).asInstanceOf[Array[AnyRef]] + } + if (depth >= 4) { + if (a == null) a = a4.asInstanceOf[Array[AnyRef]] + val i = (offset >>> BITS3) & MASK + if (depth == 4) { + if (i > 0) System.arraycopy(a, i, a, 0, WIDTH - i) + a4 = a.asInstanceOf[Arr4] + shrinkOffsetIfToLarge(WIDTH3) + if ((lenRest >>> BITS3) == 0) depth = 3 + } else { + if (i > 0) a = copyOfRange(a, i, WIDTH) + aParent(0) = a + } + aParent = a + a = a(0).asInstanceOf[Array[AnyRef]] + } + if (depth >= 3) { + if (a == null) a = a3.asInstanceOf[Array[AnyRef]] + val i = (offset >>> BITS2) & MASK + if (depth == 3) { + if (i > 0) System.arraycopy(a, i, a, 0, WIDTH - i) + a3 = a.asInstanceOf[Arr3] + shrinkOffsetIfToLarge(WIDTH2) + if ((lenRest >>> BITS2) == 0) depth = 2 + } else { + if (i > 0) a = copyOfRange(a, i, WIDTH) + aParent(0) = a + } + aParent = a + a = a(0).asInstanceOf[Array[AnyRef]] + } + if (depth >= 2) { + if (a == null) a = a2.asInstanceOf[Array[AnyRef]] + val i = (offset >>> BITS) & MASK + if (depth == 2) { + if (i > 0) System.arraycopy(a, i, a, 0, WIDTH - i) + a2 = a.asInstanceOf[Arr2] + shrinkOffsetIfToLarge(WIDTH) + if ((lenRest >>> BITS) == 0) depth = 1 + } else { + if (i > 0) a = copyOfRange(a, i, WIDTH) + aParent(0) = a + } + aParent = a + a = a(0).asInstanceOf[Array[AnyRef]] + } + if (depth >= 1) { + if (a == null) a = a1.asInstanceOf[Array[AnyRef]] + val i = offset & MASK + if (depth == 1) { + if (i > 0) System.arraycopy(a, i, a, 0, WIDTH - i) + a1 = a.asInstanceOf[Arr1] + len1 -= offset + offset = 0 + } else { + if (i > 0) a = copyOfRange(a, i, WIDTH) + aParent(0) = a + } + } + prefixIsRightAligned = false + } + + def addOne(elem: A): this.type = { + if(len1 == WIDTH) advance() + a1(len1) = elem.asInstanceOf[AnyRef] + len1 += 1 + this + } + + private[this] def addArr1(data: Arr1): Unit = { + val dl = data.length + if(dl > 0) { + if(len1 == WIDTH) advance() + val copy1 = mmin(WIDTH-len1, dl) + val copy2 = dl - copy1 + System.arraycopy(data, 0, a1, len1, copy1) + len1 += copy1 + if(copy2 > 0) { + advance() + System.arraycopy(data, copy1, a1, 0, copy2) + len1 += copy2 + } + } + } + + private[this] def addArrN(slice: Array[AnyRef], dim: Int): Unit = { +// assert(dim >= 2) +// assert(lenRest % WIDTH == 0) +// assert(len1 == 0 || len1 == WIDTH) + if (slice.isEmpty) return + if (len1 == WIDTH) advance() + val sl = slice.length + (dim: @switch) match { + case 2 => + // lenRest is always a multiple of WIDTH + val copy1 = mmin(((WIDTH2 - lenRest) >>> BITS) & MASK, sl) + val copy2 = sl - copy1 + val destPos = (lenRest >>> BITS) & MASK + System.arraycopy(slice, 0, a2, destPos, copy1) + advanceN(WIDTH * copy1) + if (copy2 > 0) { + System.arraycopy(slice, copy1, a2, 0, copy2) + advanceN(WIDTH * copy2) + } + case 3 => + if (lenRest % WIDTH2 != 0) { + // lenRest is not multiple of WIDTH2, so this slice does not align, need to try lower dimension + slice.foreach(e => addArrN(e.asInstanceOf[Array[AnyRef]], 2)) + return + } + val copy1 = mmin(((WIDTH3 - lenRest) >>> BITS2) & MASK, sl) + val copy2 = sl - copy1 + val destPos = (lenRest >>> BITS2) & MASK + System.arraycopy(slice, 0, a3, destPos, copy1) + advanceN(WIDTH2 * copy1) + if (copy2 > 0) { + System.arraycopy(slice, copy1, a3, 0, copy2) + advanceN(WIDTH2 * copy2) + } + case 4 => + if (lenRest % WIDTH3 != 0) { + // lenRest is not multiple of WIDTH3, so this slice does not align, need to try lower dimensions + slice.foreach(e => addArrN(e.asInstanceOf[Array[AnyRef]], 3)) + return + } + val copy1 = mmin(((WIDTH4 - lenRest) >>> BITS3) & MASK, sl) + val copy2 = sl - copy1 + val destPos = (lenRest >>> BITS3) & MASK + System.arraycopy(slice, 0, a4, destPos, copy1) + advanceN(WIDTH3 * copy1) + if (copy2 > 0) { + System.arraycopy(slice, copy1, a4, 0, copy2) + advanceN(WIDTH3 * copy2) + } + case 5 => + if (lenRest % WIDTH4 != 0) { + // lenRest is not multiple of WIDTH4, so this slice does not align, need to try lower dimensions + slice.foreach(e => addArrN(e.asInstanceOf[Array[AnyRef]], 4)) + return + } + val copy1 = mmin(((WIDTH5 - lenRest) >>> BITS4) & MASK, sl) + val copy2 = sl - copy1 + val destPos = (lenRest >>> BITS4) & MASK + System.arraycopy(slice, 0, a5, destPos, copy1) + advanceN(WIDTH4 * copy1) + if (copy2 > 0) { + System.arraycopy(slice, copy1, a5, 0, copy2) + advanceN(WIDTH4 * copy2) + } + case 6 => // note width is now LASTWIDTH + if (lenRest % WIDTH5 != 0) { + // lenRest is not multiple of WIDTH5, so this slice does not align, need to try lower dimensions + slice.foreach(e => addArrN(e.asInstanceOf[Array[AnyRef]], 5)) + return + } + val copy1 = sl + // there is no copy2 because there can't be another a6 to copy to + val destPos = lenRest >>> BITS5 + if (destPos + copy1 > LASTWIDTH) + throw new IllegalArgumentException("exceeding 2^31 elements") + System.arraycopy(slice, 0, a6, destPos, copy1) + advanceN(WIDTH5 * copy1) + } + } + + private[this] def addVector(xs: Vector[A]): this.type = { + val sliceCount = xs.vectorSliceCount + var sliceIdx = 0 + while(sliceIdx < sliceCount) { + val slice = xs.vectorSlice(sliceIdx) + vectorSliceDim(sliceCount, sliceIdx) match { + case 1 => addArr1(slice.asInstanceOf[Arr1]) + case n if len1 == WIDTH || len1 == 0 => + addArrN(slice.asInstanceOf[Array[AnyRef]], n) + case n => foreachRec(n-2, slice, addArr1) + } + sliceIdx += 1 + } + this + } + + override def addAll(xs: IterableOnce[A]): this.type = xs match { + case v: Vector[_] => + if(len1 == 0 && lenRest == 0 && !prefixIsRightAligned) initFrom(v) + else addVector(v.asInstanceOf[Vector[A]]) + case _ => + super.addAll(xs) + } + + private[this] def advance(): Unit = { + val idx = lenRest + WIDTH + val xor = idx ^ lenRest + lenRest = idx + len1 = 0 + advance1(idx, xor) + } + + private[this] def advanceN(n: Int): Unit = if (n > 0) { + // assert(n % 32 == 0) + val idx = lenRest + n + val xor = idx ^ lenRest + lenRest = idx + len1 = 0 + advance1(idx, xor) + } + + private[this] def advance1(idx: Int, xor: Int): Unit = { + if (xor <= 0) { // level = 6 or something very unexpected happened + throw new IllegalArgumentException(s"advance1($idx, $xor): a1=$a1, a2=$a2, a3=$a3, a4=$a4, a5=$a5, a6=$a6, depth=$depth") + } else if (xor < WIDTH2) { // level = 1 + if (depth <= 1) { a2 = new Array(WIDTH); a2(0) = a1; depth = 2 } + a1 = new Array(WIDTH) + a2((idx >>> BITS) & MASK) = a1 + } else if (xor < WIDTH3) { // level = 2 + if (depth <= 2) { a3 = new Array(WIDTH); a3(0) = a2; depth = 3 } + a1 = new Array(WIDTH) + a2 = new Array(WIDTH) + a2((idx >>> BITS) & MASK) = a1 + a3((idx >>> BITS2) & MASK) = a2 + } else if (xor < WIDTH4) { // level = 3 + if (depth <= 3) { a4 = new Array(WIDTH); a4(0) = a3; depth = 4 } + a1 = new Array(WIDTH) + a2 = new Array(WIDTH) + a3 = new Array(WIDTH) + a2((idx >>> BITS) & MASK) = a1 + a3((idx >>> BITS2) & MASK) = a2 + a4((idx >>> BITS3) & MASK) = a3 + } else if (xor < WIDTH5) { // level = 4 + if (depth <= 4) { a5 = new Array(WIDTH); a5(0) = a4; depth = 5 } + a1 = new Array(WIDTH) + a2 = new Array(WIDTH) + a3 = new Array(WIDTH) + a4 = new Array(WIDTH) + a2((idx >>> BITS) & MASK) = a1 + a3((idx >>> BITS2) & MASK) = a2 + a4((idx >>> BITS3) & MASK) = a3 + a5((idx >>> BITS4) & MASK) = a4 + } else { // level = 5 + if (depth <= 5) { a6 = new Array(LASTWIDTH); a6(0) = a5; depth = 6 } + a1 = new Array(WIDTH) + a2 = new Array(WIDTH) + a3 = new Array(WIDTH) + a4 = new Array(WIDTH) + a5 = new Array(WIDTH) + a2((idx >>> BITS) & MASK) = a1 + a3((idx >>> BITS2) & MASK) = a2 + a4((idx >>> BITS3) & MASK) = a3 + a5((idx >>> BITS4) & MASK) = a4 + a6(idx >>> BITS5) = a5 + } + } + + def result(): Vector[A] = { + if (prefixIsRightAligned) leftAlignPrefix() + val len = len1 + lenRest + val realLen = len - offset + if(realLen == 0) Vector.empty + else if(len < 0) throw new IndexOutOfBoundsException(s"Vector cannot have negative size $len") + else if(len <= WIDTH) { + new Vector1(copyIfDifferentSize(a1, realLen)) + } else if(len <= WIDTH2) { + val i1 = (len-1) & MASK + val i2 = (len-1) >>> BITS + val data = copyOfRange(a2, 1, i2) + val prefix1 = a2(0) + val suffix1 = copyIfDifferentSize(a2(i2), i1+1) + new Vector2(prefix1, WIDTH-offset, data, suffix1, realLen) + } else if(len <= WIDTH3) { + val i1 = (len-1) & MASK + val i2 = ((len-1) >>> BITS) & MASK + val i3 = ((len-1) >>> BITS2) + val data = copyOfRange(a3, 1, i3) + val prefix2 = copyTail(a3(0)) + val prefix1 = a3(0)(0) + val suffix2 = copyOf(a3(i3), i2) + val suffix1 = copyIfDifferentSize(a3(i3)(i2), i1+1) + val len1 = prefix1.length + val len12 = len1 + prefix2.length*WIDTH + new Vector3(prefix1, len1, prefix2, len12, data, suffix2, suffix1, realLen) + } else if(len <= WIDTH4) { + val i1 = (len-1) & MASK + val i2 = ((len-1) >>> BITS) & MASK + val i3 = ((len-1) >>> BITS2) & MASK + val i4 = ((len-1) >>> BITS3) + val data = copyOfRange(a4, 1, i4) + val prefix3 = copyTail(a4(0)) + val prefix2 = copyTail(a4(0)(0)) + val prefix1 = a4(0)(0)(0) + val suffix3 = copyOf(a4(i4), i3) + val suffix2 = copyOf(a4(i4)(i3), i2) + val suffix1 = copyIfDifferentSize(a4(i4)(i3)(i2), i1+1) + val len1 = prefix1.length + val len12 = len1 + prefix2.length*WIDTH + val len123 = len12 + prefix3.length*WIDTH2 + new Vector4(prefix1, len1, prefix2, len12, prefix3, len123, data, suffix3, suffix2, suffix1, realLen) + } else if(len <= WIDTH5) { + val i1 = (len-1) & MASK + val i2 = ((len-1) >>> BITS) & MASK + val i3 = ((len-1) >>> BITS2) & MASK + val i4 = ((len-1) >>> BITS3) & MASK + val i5 = ((len-1) >>> BITS4) + val data = copyOfRange(a5, 1, i5) + val prefix4 = copyTail(a5(0)) + val prefix3 = copyTail(a5(0)(0)) + val prefix2 = copyTail(a5(0)(0)(0)) + val prefix1 = a5(0)(0)(0)(0) + val suffix4 = copyOf(a5(i5), i4) + val suffix3 = copyOf(a5(i5)(i4), i3) + val suffix2 = copyOf(a5(i5)(i4)(i3), i2) + val suffix1 = copyIfDifferentSize(a5(i5)(i4)(i3)(i2), i1+1) + val len1 = prefix1.length + val len12 = len1 + prefix2.length*WIDTH + val len123 = len12 + prefix3.length*WIDTH2 + val len1234 = len123 + prefix4.length*WIDTH3 + new Vector5(prefix1, len1, prefix2, len12, prefix3, len123, prefix4, len1234, data, suffix4, suffix3, suffix2, suffix1, realLen) + } else { + val i1 = (len-1) & MASK + val i2 = ((len-1) >>> BITS) & MASK + val i3 = ((len-1) >>> BITS2) & MASK + val i4 = ((len-1) >>> BITS3) & MASK + val i5 = ((len-1) >>> BITS4) & MASK + val i6 = ((len-1) >>> BITS5) + val data = copyOfRange(a6, 1, i6) + val prefix5 = copyTail(a6(0)) + val prefix4 = copyTail(a6(0)(0)) + val prefix3 = copyTail(a6(0)(0)(0)) + val prefix2 = copyTail(a6(0)(0)(0)(0)) + val prefix1 = a6(0)(0)(0)(0)(0) + val suffix5 = copyOf(a6(i6), i5) + val suffix4 = copyOf(a6(i6)(i5), i4) + val suffix3 = copyOf(a6(i6)(i5)(i4), i3) + val suffix2 = copyOf(a6(i6)(i5)(i4)(i3), i2) + val suffix1 = copyIfDifferentSize(a6(i6)(i5)(i4)(i3)(i2), i1+1) + val len1 = prefix1.length + val len12 = len1 + prefix2.length*WIDTH + val len123 = len12 + prefix3.length*WIDTH2 + val len1234 = len123 + prefix4.length*WIDTH3 + val len12345 = len1234 + prefix5.length*WIDTH4 + new Vector6(prefix1, len1, prefix2, len12, prefix3, len123, prefix4, len1234, prefix5, len12345, data, suffix5, suffix4, suffix3, suffix2, suffix1, realLen) + } + } + + override def toString: String = + s"VectorBuilder(len1=$len1, lenRest=$lenRest, offset=$offset, depth=$depth)" + + private[immutable] def getData: Array[Array[_]] = Array[Array[AnyRef]]( + a1, a2.asInstanceOf[Array[AnyRef]], a3.asInstanceOf[Array[AnyRef]], a4.asInstanceOf[Array[AnyRef]], + a5.asInstanceOf[Array[AnyRef]], a6.asInstanceOf[Array[AnyRef]] + ).asInstanceOf[Array[Array[_]]] +} + + +/** Compile-time definitions for Vector. No references to this object should appear in bytecode. */ +private[immutable] object VectorInline { + // compile-time numeric constants + final val BITS = 5 + final val WIDTH = 1 << BITS + final val MASK = WIDTH - 1 + final val BITS2 = BITS * 2 + final val WIDTH2 = 1 << BITS2 + final val BITS3 = BITS * 3 + final val WIDTH3 = 1 << BITS3 + final val BITS4 = BITS * 4 + final val WIDTH4 = 1 << BITS4 + final val BITS5 = BITS * 5 + final val WIDTH5 = 1 << BITS5 + final val LASTWIDTH = WIDTH << 1 // 1 extra bit in the last level to go up to Int.MaxValue (2^31-1) instead of 2^30: + final val Log2ConcatFaster = 5 + final val AlignToFaster = 64 + + type Arr1 = Array[AnyRef] + type Arr2 = Array[Array[AnyRef]] + type Arr3 = Array[Array[Array[AnyRef]]] + type Arr4 = Array[Array[Array[Array[AnyRef]]]] + type Arr5 = Array[Array[Array[Array[Array[AnyRef]]]]] + type Arr6 = Array[Array[Array[Array[Array[Array[AnyRef]]]]]] + + /** Dimension of the slice at index */ + @inline def vectorSliceDim(count: Int, idx: Int): Int = { + val c = count/2 + c+1-abs(idx-c) + } + + @inline def copyOrUse[T <: AnyRef](a: Array[T], start: Int, end: Int): Array[T] = + if(start == 0 && end == a.length) a else copyOfRange[T](a, start, end) + + @inline final def copyTail[T <: AnyRef](a: Array[T]): Array[T] = copyOfRange[T](a, 1, a.length) + + @inline final def copyInit[T <: AnyRef](a: Array[T]): Array[T] = copyOfRange[T](a, 0, a.length-1) + + @inline final def copyIfDifferentSize[T <: AnyRef](a: Array[T], len: Int): Array[T] = + if(a.length == len) a else copyOf[T](a, len) + + @inline final def wrap1(x: Any ): Arr1 = { val a = new Arr1(1); a(0) = x.asInstanceOf[AnyRef]; a } + @inline final def wrap2(x: Arr1): Arr2 = { val a = new Arr2(1); a(0) = x; a } + @inline final def wrap3(x: Arr2): Arr3 = { val a = new Arr3(1); a(0) = x; a } + @inline final def wrap4(x: Arr3): Arr4 = { val a = new Arr4(1); a(0) = x; a } + @inline final def wrap5(x: Arr4): Arr5 = { val a = new Arr5(1); a(0) = x; a } + + @inline final def copyUpdate(a1: Arr1, idx1: Int, elem: Any): Arr1 = { + val a1c = a1.clone() + a1c(idx1) = elem.asInstanceOf[AnyRef] + a1c + } + + @inline final def copyUpdate(a2: Arr2, idx2: Int, idx1: Int, elem: Any): Arr2 = { + val a2c = a2.clone() + a2c(idx2) = copyUpdate(a2c(idx2), idx1, elem) + a2c + } + + @inline final def copyUpdate(a3: Arr3, idx3: Int, idx2: Int, idx1: Int, elem: Any): Arr3 = { + val a3c = a3.clone() + a3c(idx3) = copyUpdate(a3c(idx3), idx2, idx1, elem) + a3c + } + + @inline final def copyUpdate(a4: Arr4, idx4: Int, idx3: Int, idx2: Int, idx1: Int, elem: Any): Arr4 = { + val a4c = a4.clone() + a4c(idx4) = copyUpdate(a4c(idx4), idx3, idx2, idx1, elem) + a4c + } + + @inline final def copyUpdate(a5: Arr5, idx5: Int, idx4: Int, idx3: Int, idx2: Int, idx1: Int, elem: Any): Arr5 = { + val a5c = a5.clone() + a5c(idx5) = copyUpdate(a5c(idx5), idx4, idx3, idx2, idx1, elem) + a5c + } + + @inline final def copyUpdate(a6: Arr6, idx6: Int, idx5: Int, idx4: Int, idx3: Int, idx2: Int, idx1: Int, elem: Any): Arr6 = { + val a6c = a6.clone() + a6c(idx6) = copyUpdate(a6c(idx6), idx5, idx4, idx3, idx2, idx1, elem) + a6c + } + + @inline final def concatArrays[T <: AnyRef](a: Array[T], b: Array[T]): Array[T] = { + val dest = copyOf[T](a, a.length+b.length) + System.arraycopy(b, 0, dest, a.length, b.length) + dest + } +} + + +/** Helper methods and constants for Vector. */ +private object VectorStatics { + + final def copyAppend1(a: Arr1, elem: Any): Arr1 = { + val alen = a.length + val ac = new Arr1(alen+1) + System.arraycopy(a, 0, ac, 0, alen) + ac(alen) = elem.asInstanceOf[AnyRef] + ac + } + + final def copyAppend[T <: AnyRef](a: Array[T], elem: T): Array[T] = { + val ac = copyOf(a, a.length+1) + ac(ac.length-1) = elem + ac + } + + final def copyPrepend1(elem: Any, a: Arr1): Arr1 = { + val ac = new Arr1(a.length+1) + System.arraycopy(a, 0, ac, 1, a.length) + ac(0) = elem.asInstanceOf[AnyRef] + ac + } + + final def copyPrepend[T <: AnyRef](elem: T, a: Array[T]): Array[T] = { + val ac = java.lang.reflect.Array.newInstance(a.getClass.getComponentType, a.length+1).asInstanceOf[Array[T]] + System.arraycopy(a, 0, ac, 1, a.length) + ac(0) = elem + ac + } + + final val empty1: Arr1 = new Array(0) + final val empty2: Arr2 = new Array(0) + final val empty3: Arr3 = new Array(0) + final val empty4: Arr4 = new Array(0) + final val empty5: Arr5 = new Array(0) + final val empty6: Arr6 = new Array(0) + + final def foreachRec[T <: AnyRef, A, U](level: Int, a: Array[T], f: A => U): Unit = { + var i = 0 + val len = a.length + if(level == 0) { + while(i < len) { + f(a(i).asInstanceOf[A]) + i += 1 + } + } else { + val l = level-1 + while(i < len) { + foreachRec(l, a(i).asInstanceOf[Array[AnyRef]], f) + i += 1 + } + } + } + + final def mapElems1[A, B](a: Arr1, f: A => B): Arr1 = { + var i = 0 + while(i < a.length) { + val v1 = a(i).asInstanceOf[AnyRef] + val v2 = f(v1.asInstanceOf[A]).asInstanceOf[AnyRef] + if(v1 ne v2) + return mapElems1Rest(a, f, i, v2) + i += 1 + } + a + } + + final def mapElems1Rest[A, B](a: Arr1, f: A => B, at: Int, v2: AnyRef): Arr1 = { + val ac = new Arr1(a.length) + if(at > 0) System.arraycopy(a, 0, ac, 0, at) + ac(at) = v2 + var i = at+1 + while(i < a.length) { + ac(i) = f(a(i).asInstanceOf[A]).asInstanceOf[AnyRef] + i += 1 + } + ac + } + + final def mapElems[A, B, T <: AnyRef](n: Int, a: Array[T], f: A => B): Array[T] = { + if(n == 1) + mapElems1[A, B](a.asInstanceOf[Arr1], f).asInstanceOf[Array[T]] + else { + var i = 0 + while(i < a.length) { + val v1 = a(i) + val v2 = mapElems(n-1, v1.asInstanceOf[Array[AnyRef]], f) + if(v1 ne v2) + return mapElemsRest(n, a, f, i, v2) + i += 1 + } + a + } + } + + final def mapElemsRest[A, B, T <: AnyRef](n: Int, a: Array[T], f: A => B, at: Int, v2: AnyRef): Array[T] = { + val ac = java.lang.reflect.Array.newInstance(a.getClass.getComponentType, a.length).asInstanceOf[Array[AnyRef]] + if(at > 0) System.arraycopy(a, 0, ac, 0, at) + ac(at) = v2 + var i = at+1 + while(i < a.length) { + ac(i) = mapElems(n-1, a(i).asInstanceOf[Array[AnyRef]], f) + i += 1 + } + ac.asInstanceOf[Array[T]] + } + + final def prepend1IfSpace(prefix1: Arr1, xs: IterableOnce[_]): Arr1 = xs match { + case it: Iterable[_] => + if(it.sizeCompare(WIDTH-prefix1.length) <= 0) { + it.size match { + case 0 => null + case 1 => copyPrepend(it.head.asInstanceOf[AnyRef], prefix1) + case s => + val prefix1b = new Arr1(prefix1.length + s) + System.arraycopy(prefix1, 0, prefix1b, s, prefix1.length) + @annotation.unused val copied = it.copyToArray(prefix1b.asInstanceOf[Array[Any]], 0) + //assert(copied == s) + prefix1b + } + } else null + case it => + val s = it.knownSize + if(s > 0 && s <= WIDTH-prefix1.length) { + val prefix1b = new Arr1(prefix1.length + s) + System.arraycopy(prefix1, 0, prefix1b, s, prefix1.length) + @annotation.unused val copied = it.iterator.copyToArray(prefix1b.asInstanceOf[Array[Any]], 0) + //assert(copied == s) + prefix1b + } else null + } + + final def append1IfSpace(suffix1: Arr1, xs: IterableOnce[_]): Arr1 = xs match { + case it: Iterable[_] => + if(it.sizeCompare(WIDTH-suffix1.length) <= 0) { + it.size match { + case 0 => null + case 1 => copyAppend(suffix1, it.head.asInstanceOf[AnyRef]) + case s => + val suffix1b = copyOf(suffix1, suffix1.length + s) + @annotation.unused val copied = it.copyToArray(suffix1b.asInstanceOf[Array[Any]], suffix1.length) + //assert(copied == s) + suffix1b + } + } else null + case it => + val s = it.knownSize + if(s > 0 && s <= WIDTH-suffix1.length) { + val suffix1b = copyOf(suffix1, suffix1.length + s) + @annotation.unused val copied = it.iterator.copyToArray(suffix1b.asInstanceOf[Array[Any]], suffix1.length) + //assert(copied == s) + suffix1b + } else null + } +} + + +private final class NewVectorIterator[A](v: Vector[A], private[this] var totalLength: Int, private[this] val sliceCount: Int) extends AbstractIterator[A] with java.lang.Cloneable { + + private[this] var a1: Arr1 = v.prefix1 + private[this] var a2: Arr2 = _ + private[this] var a3: Arr3 = _ + private[this] var a4: Arr4 = _ + private[this] var a5: Arr5 = _ + private[this] var a6: Arr6 = _ + private[this] var a1len = a1.length + private[this] var i1 = 0 // current index in a1 + private[this] var oldPos = 0 + private[this] var len1 = totalLength // remaining length relative to a1 + + private[this] var sliceIdx = 0 + private[this] var sliceDim = 1 + private[this] var sliceStart = 0 // absolute position + private[this] var sliceEnd = a1len // absolute position + + //override def toString: String = + // s"NewVectorIterator(v=$v, totalLength=$totalLength, sliceCount=$sliceCount): a1len=$a1len, len1=$len1, i1=$i1, sliceEnd=$sliceEnd" + + @inline override def knownSize = len1 - i1 + + @inline def hasNext: Boolean = len1 > i1 + + def next(): A = { + if(i1 == a1len) advance() + val r = a1(i1) + i1 += 1 + r.asInstanceOf[A] + } + + private[this] def advanceSlice(): Unit = { + if(!hasNext) Iterator.empty.next() + sliceIdx += 1 + var slice: Array[_ <: AnyRef] = v.vectorSlice(sliceIdx) + while(slice.length == 0) { + sliceIdx += 1 + slice = v.vectorSlice(sliceIdx) + } + sliceStart = sliceEnd + sliceDim = vectorSliceDim(sliceCount, sliceIdx) + (sliceDim: @switch) match { + case 1 => a1 = slice.asInstanceOf[Arr1] + case 2 => a2 = slice.asInstanceOf[Arr2] + case 3 => a3 = slice.asInstanceOf[Arr3] + case 4 => a4 = slice.asInstanceOf[Arr4] + case 5 => a5 = slice.asInstanceOf[Arr5] + case 6 => a6 = slice.asInstanceOf[Arr6] + } + sliceEnd = sliceStart + slice.length * (1 << (BITS*(sliceDim-1))) + if(sliceEnd > totalLength) sliceEnd = totalLength + if(sliceDim > 1) oldPos = (1 << (BITS*sliceDim))-1 + } + + private[this] def advance(): Unit = { + val pos = i1-len1+totalLength + if(pos == sliceEnd) advanceSlice() + if(sliceDim > 1) { + val io = pos - sliceStart + val xor = oldPos ^ io + advanceA(io, xor) + oldPos = io + } + len1 -= i1 + a1len = mmin(a1.length, len1) + i1 = 0 + } + + private[this] def advanceA(io: Int, xor: Int): Unit = { + if(xor < WIDTH2) { + a1 = a2((io >>> BITS) & MASK) + } else if(xor < WIDTH3) { + a2 = a3((io >>> BITS2) & MASK) + a1 = a2(0) + } else if(xor < WIDTH4) { + a3 = a4((io >>> BITS3) & MASK) + a2 = a3(0) + a1 = a2(0) + } else if(xor < WIDTH5) { + a4 = a5((io >>> BITS4) & MASK) + a3 = a4(0) + a2 = a3(0) + a1 = a2(0) + } else { + a5 = a6(io >>> BITS5) + a4 = a5(0) + a3 = a4(0) + a2 = a3(0) + a1 = a2(0) + } + } + + private[this] def setA(io: Int, xor: Int): Unit = { + if(xor < WIDTH2) { + a1 = a2((io >>> BITS) & MASK) + } else if(xor < WIDTH3) { + a2 = a3((io >>> BITS2) & MASK) + a1 = a2((io >>> BITS) & MASK) + } else if(xor < WIDTH4) { + a3 = a4((io >>> BITS3) & MASK) + a2 = a3((io >>> BITS2) & MASK) + a1 = a2((io >>> BITS) & MASK) + } else if(xor < WIDTH5) { + a4 = a5((io >>> BITS4) & MASK) + a3 = a4((io >>> BITS3) & MASK) + a2 = a3((io >>> BITS2) & MASK) + a1 = a2((io >>> BITS) & MASK) + } else { + a5 = a6(io >>> BITS5) + a4 = a5((io >>> BITS4) & MASK) + a3 = a4((io >>> BITS3) & MASK) + a2 = a3((io >>> BITS2) & MASK) + a1 = a2((io >>> BITS) & MASK) + } + } + + override def drop(n: Int): Iterator[A] = { + if(n > 0) { + val oldpos = i1-len1+totalLength + val newpos = mmin(oldpos + n, totalLength) + if(newpos == totalLength) { + i1 = 0 + len1 = 0 + a1len = 0 + } else { + while(newpos >= sliceEnd) advanceSlice() + val io = newpos - sliceStart + if(sliceDim > 1) { + val xor = oldPos ^ io + setA(io, xor) + oldPos = io + } + a1len = a1.length + i1 = io & MASK + len1 = i1 + (totalLength-newpos) + if(a1len > len1) a1len = len1 + } + } + this + } + + override def take(n: Int): Iterator[A] = { + if(n < knownSize) { + val trunc = knownSize - mmax(0, n) + totalLength -= trunc + len1 -= trunc + if(len1 < a1len) a1len = len1 + if(totalLength < sliceEnd) sliceEnd = totalLength + } + this + } + + override def slice(from: Int, until: Int): Iterator[A] = { + val _until = mmax(until, 0) + + val n = + if(from > 0) { + drop(from) + _until - from + } else _until + take(n) + } + + override def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): Int = { + val xsLen = xs.length + val total = IterableOnce.elemsToCopyToArray(knownSize, xsLen, start, len) + var copied = 0 + val isBoxed = xs.isInstanceOf[Array[AnyRef]] + while(copied < total) { + if(i1 == a1len) advance() + val count = mmin(total-copied, a1.length-i1) + if(isBoxed) System.arraycopy(a1, i1, xs, start+copied, count) + else Array.copy(a1, i1, xs, start+copied, count) + i1 += count + copied += count + } + total + } + + override def toVector: Vector[A] = + v.slice(i1-len1+totalLength, totalLength) + + protected[immutable] def split(at: Int): NewVectorIterator[A] = { + val it2 = clone().asInstanceOf[NewVectorIterator[A]] + it2.take(at) + drop(at) + it2 + } +} + + +private abstract class VectorStepperBase[A, Sub >: Null <: Stepper[A], Semi <: Sub](it: NewVectorIterator[A]) + extends Stepper[A] with EfficientSplit { + + protected[this] def build(it: NewVectorIterator[A]): Semi + + final def hasStep: Boolean = it.hasNext + + final def characteristics: Int = Spliterator.ORDERED + Spliterator.SIZED + Spliterator.SUBSIZED + + final def estimateSize: Long = it.knownSize + + def trySplit(): Sub = { + val len = it.knownSize + if(len > 1) build(it.split(len >>> 1)) + else null + } + + override final def iterator: Iterator[A] = it +} + +private class AnyVectorStepper[A](it: NewVectorIterator[A]) + extends VectorStepperBase[A, AnyStepper[A], AnyVectorStepper[A]](it) with AnyStepper[A] { + protected[this] def build(it: NewVectorIterator[A]) = new AnyVectorStepper(it) + def nextStep(): A = it.next() +} + +private class DoubleVectorStepper(it: NewVectorIterator[Double]) + extends VectorStepperBase[Double, DoubleStepper, DoubleVectorStepper](it) with DoubleStepper { + protected[this] def build(it: NewVectorIterator[Double]) = new DoubleVectorStepper(it) + def nextStep(): Double = it.next() +} + +private class IntVectorStepper(it: NewVectorIterator[Int]) + extends VectorStepperBase[Int, IntStepper, IntVectorStepper](it) with IntStepper { + protected[this] def build(it: NewVectorIterator[Int]) = new IntVectorStepper(it) + def nextStep(): Int = it.next() +} + +private class LongVectorStepper(it: NewVectorIterator[Long]) + extends VectorStepperBase[Long, LongStepper, LongVectorStepper](it) with LongStepper { + protected[this] def build(it: NewVectorIterator[Long]) = new LongVectorStepper(it) + def nextStep(): Long = it.next() +} + + +// The following definitions are needed for binary compatibility with ParVector +private[collection] class VectorIterator[+A](_startIndex: Int, private[this] var endIndex: Int) extends AbstractIterator[A] { + private[immutable] var it: NewVectorIterator[A @uncheckedVariance] = _ + def hasNext: Boolean = it.hasNext + def next(): A = it.next() + private[collection] def remainingElementCount: Int = it.size + private[collection] def remainingVector: Vector[A] = it.toVector +} diff --git a/library/src/scala/collection/immutable/VectorMap.scala b/library/src/scala/collection/immutable/VectorMap.scala new file mode 100644 index 000000000000..316d02037ec0 --- /dev/null +++ b/library/src/scala/collection/immutable/VectorMap.scala @@ -0,0 +1,279 @@ +/* + * 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 collection +package immutable + +import scala.language.`2.13` +import scala.annotation.{nowarn, tailrec} + +/** This class implements immutable maps using a vector/map-based data structure, which preserves insertion order. + * + * Unlike `ListMap`, `VectorMap` has amortized effectively constant lookup at the expense + * of using extra memory and generally lower performance for other operations + * + * @tparam K the type of the keys contained in this vector map. + * @tparam V the type of the values associated with the keys in this vector map. + * + * @define coll immutable vector map + * @define Coll `immutable.VectorMap` + */ +final class VectorMap[K, +V] private ( + private[immutable] val fields: Vector[Any], // K | Tombstone | Null + private[immutable] val underlying: Map[K, (Int, V)], + dropped: Int) + extends AbstractMap[K, V] + with SeqMap[K, V] + with StrictOptimizedMapOps[K, V, VectorMap, VectorMap[K, V]] + with MapFactoryDefaults[K, V, VectorMap, Iterable] { + + import VectorMap._ + + override protected[this] def className: String = "VectorMap" + + private[immutable] def this(fields: Vector[K], underlying: Map[K, (Int, V)]) = this(fields, underlying, 0) + + override val size = underlying.size + + override def knownSize: Int = size + + override def isEmpty: Boolean = size == 0 + + def updated[V1 >: V](key: K, value: V1): VectorMap[K, V1] = { + underlying.get(key) match { + case Some((slot, _)) => + new VectorMap(fields, underlying.updated[(Int, V1)](key, (slot, value)), dropped) + case None => + new VectorMap(fields :+ key, underlying.updated[(Int, V1)](key, (fields.length + dropped, value)), dropped) + } + } + + override def withDefault[V1 >: V](d: K => V1): Map[K, V1] = + new Map.WithDefault(this, d) + + override def withDefaultValue[V1 >: V](d: V1): Map[K, V1] = + new Map.WithDefault[K, V1](this, _ => d) + + def get(key: K): Option[V] = underlying.get(key) match { + case Some(v) => Some(v._2) + case None => None + } + + @tailrec + private def nextValidField(slot: Int): (Int, K) = { + if (slot >= fields.size) (-1, null.asInstanceOf[K]) + else fields(slot) match { + case Tombstone(distance) => nextValidField(slot + distance) + case k /*: K | Null */ => (slot, k.asInstanceOf[K]) + } + } + + def iterator: Iterator[(K, V)] = new AbstractIterator[(K, V)] { + private[this] val fieldsLength = fields.length + private[this] var slot = -1 + private[this] var key: K = null.asInstanceOf[K] + + private[this] def advance(): Unit = { + val nextSlot = slot + 1 + if (nextSlot >= fieldsLength) { + slot = fieldsLength + key = null.asInstanceOf[K] + } else { + nextValidField(nextSlot) match { + case (-1, _) => + slot = fieldsLength + key = null.asInstanceOf[K] + case (s, k) => + slot = s + key = k + } + } + } + + advance() + + override def hasNext: Boolean = slot < fieldsLength + + override def next(): (K, V) = + if (!hasNext) Iterator.empty.next() + else { + val result = (key, underlying(key)._2) + advance() + result + } + } + + // No-Op overrides to allow for more efficient steppers in a minor release. + // Refining the return type to `S with EfficientSplit` is binary compatible. + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[(K, V), S]): S = super.stepper(shape) + + override def keyStepper[S <: Stepper[_]](implicit shape: StepperShape[K, S]): S = super.keyStepper(shape) + + override def valueStepper[S <: Stepper[_]](implicit shape: StepperShape[V, S]): S = super.valueStepper(shape) + + + def removed(key: K): VectorMap[K, V] = { + if (isEmpty) empty + else { + var fs = fields + val sz = fs.size + underlying.get(key) match { + case Some(_) if size == 1 => empty + case Some((slot, _)) => + val s = slot - dropped + + // Calculate next of kin + val next = + if (s < sz - 1) fs(s + 1) match { + case Tombstone(d) => s + d + 1 + case _ => s + 1 + } else s + 1 + + fs = fs.updated(s, Tombstone(next - s)) + + // Calculate first index of preceding tombstone sequence + val first = + if (s > 0) { + fs(s - 1) match { + case Tombstone(d) if d < 0 => if (s + d >= 0) s + d else 0 + case Tombstone(d) if d == 1 => s - 1 + case Tombstone(d) => throw new IllegalStateException("tombstone indicate wrong position: " + d) + case _ => s + } + }else s + fs = fs.updated(first, Tombstone(next - first)) + + // Calculate last index of succeeding tombstone sequence + val last = next - 1 + if (last != first) { + fs = fs.updated(last, Tombstone(first - 1 - last)) + } + new VectorMap(fs, underlying - key, dropped) + case _ => + this + } + } + } + + override def mapFactory: MapFactory[VectorMap] = VectorMap + + override def contains(key: K): Boolean = underlying.contains(key) + + override def head: (K, V) = iterator.next() + + override def last: (K, V) = { + if (isEmpty) throw new UnsupportedOperationException("empty.last") + val lastSlot = fields.length - 1 + val last = fields.last match { + case Tombstone(d) if d < 0 => fields(lastSlot + d).asInstanceOf[K] + case Tombstone(d) if d == 1 => fields(lastSlot - 1).asInstanceOf[K] + case Tombstone(d) => throw new IllegalStateException("tombstone indicate wrong position: " + d) + case k => k.asInstanceOf[K] + } + (last, underlying(last)._2) + } + + override def lastOption: Option[(K, V)] = { + if (isEmpty) None + else Some(last) + } + + override def tail: VectorMap[K, V] = { + if (isEmpty) throw new UnsupportedOperationException("empty.tail") + val (slot, key) = nextValidField(0) + new VectorMap(fields.drop(slot + 1), underlying - key, dropped + slot + 1) + } + + override def init: VectorMap[K, V] = { + if (isEmpty) throw new UnsupportedOperationException("empty.init") + val lastSlot = fields.size - 1 + val (slot, key) = fields.last match { + case Tombstone(d) if d < 0 => (lastSlot + d, fields(lastSlot + d).asInstanceOf[K]) + case Tombstone(d) if d == 1 => (lastSlot - 1, fields(lastSlot - 1).asInstanceOf[K]) + case Tombstone(d) => throw new IllegalStateException("tombstone indicate wrong position: " + d) + case k => (lastSlot, k.asInstanceOf[K]) + } + new VectorMap(fields.dropRight(fields.size - slot), underlying - key, dropped) + } + + /** A [[Vector]] of the keys contained by this map. + * + * @return a [[Vector]] of the keys contained by this map. + */ + @nowarn("msg=overriding method keys") + override def keys: Vector[K] = keysIterator.toVector + + override def values: Iterable[V] = new Iterable[V] with IterableFactoryDefaults[V, Iterable] { + override def iterator: Iterator[V] = keysIterator.map(underlying(_)._2) + } +} + +object VectorMap extends MapFactory[VectorMap] { + //Class to mark deleted slots in 'fields'. + //When one or more consecutive slots are deleted, the 'distance' of the first 'Tombstone' + // represents the distance to the location of the next undeleted slot (or the last slot in 'fields' +1 if it does not exist). + //When two or more consecutive slots are deleted, the 'distance' of the trailing 'Tombstone' + // represents the distance to the location of the previous undeleted slot ( or -1 if it does not exist) multiplied by -1. + //For other deleted slots, it simply indicates that they have been deleted. + private[VectorMap] final case class Tombstone(distance: Int) + + private[this] final val EmptyMap: VectorMap[Nothing, Nothing] = + new VectorMap[Nothing, Nothing](Vector.empty[Nothing], HashMap.empty[Nothing, (Int, Nothing)]) + + def empty[K, V]: VectorMap[K, V] = EmptyMap.asInstanceOf[VectorMap[K, V]] + + def from[K, V](it: collection.IterableOnce[(K, V)]): VectorMap[K, V] = + it match { + case vm: VectorMap[K, V] => vm + case _ => (newBuilder[K, V] ++= it).result() + } + + def newBuilder[K, V]: mutable.Builder[(K, V), VectorMap[K, V]] = new VectorMapBuilder[K, V] +} + +private[immutable] final class VectorMapBuilder[K, V] extends mutable.Builder[(K, V), VectorMap[K, V]] { + private[this] val vectorBuilder = new VectorBuilder[K] + private[this] val mapBuilder = new MapBuilderImpl[K, (Int, V)] + private[this] var aliased: VectorMap[K, V] = _ + + override def clear(): Unit = { + vectorBuilder.clear() + mapBuilder.clear() + aliased = null + } + + override def result(): VectorMap[K, V] = { + if (aliased eq null) { + aliased = new VectorMap(vectorBuilder.result(), mapBuilder.result()) + } + aliased + } + def addOne(key: K, value: V): this.type = { + if (aliased ne null) { + aliased = aliased.updated(key, value) + } else { + mapBuilder.getOrElse(key, null) match { + case (slot, _) => + mapBuilder.addOne(key, (slot, value)) + case null => + val vectorSize = vectorBuilder.size + vectorBuilder.addOne(key) + mapBuilder.addOne(key, (vectorSize, value)) + } + } + this + } + + override def addOne(elem: (K, V)): this.type = addOne(elem._1, elem._2) +} diff --git a/library/src/scala/collection/immutable/WrappedString.scala b/library/src/scala/collection/immutable/WrappedString.scala new file mode 100644 index 000000000000..76d69c092673 --- /dev/null +++ b/library/src/scala/collection/immutable/WrappedString.scala @@ -0,0 +1,140 @@ +/* + * 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.collection +package immutable + +import scala.language.`2.13` +import scala.Predef.{wrapString => _, assert} +import scala.collection.Stepper.EfficientSplit +import scala.collection.convert.impl.CharStringStepper +import scala.collection.mutable.{Builder, StringBuilder} + +/** + * This class serves as a wrapper augmenting `String`s with all the operations + * found in indexed sequences. + * + * The difference between this class and `StringOps` is that calling transformer + * methods such as `filter` and `map` will yield an object of type `WrappedString` + * rather than a `String`. + * + * @param self a string contained within this wrapped string + * + * @define Coll `WrappedString` + * @define coll wrapped string + */ +@SerialVersionUID(3L) +final class WrappedString(private val self: String) extends AbstractSeq[Char] with IndexedSeq[Char] + with IndexedSeqOps[Char, IndexedSeq, WrappedString] + with Serializable { + + def apply(i: Int): Char = self.charAt(i) + + override protected def fromSpecific(coll: scala.collection.IterableOnce[Char]): WrappedString = WrappedString.fromSpecific(coll) + override protected def newSpecificBuilder: Builder[Char, WrappedString] = WrappedString.newBuilder + override def empty: WrappedString = WrappedString.empty + + override def slice(from: Int, until: Int): WrappedString = { + val start = if (from < 0) 0 else from + if (until <= start || start >= self.length) + return WrappedString.empty + + val end = if (until > length) length else until + new WrappedString(self.substring(start, end)) + } + override def length = self.length + override def toString = self + override def view: StringView = new StringView(self) + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Char, S]): S with EfficientSplit = { + val st = new CharStringStepper(self, 0, self.length) + val r = + if (shape.shape == StepperShape.CharShape) st + else { + assert(shape.shape == StepperShape.ReferenceShape, s"unexpected StepperShape: $shape") + AnyStepper.ofParIntStepper(st) + } + r.asInstanceOf[S with EfficientSplit] + } + + override def startsWith[B >: Char](that: IterableOnce[B], offset: Int = 0): Boolean = + that match { + case s: WrappedString => self.startsWith(s.self, offset) + case _ => super.startsWith(that, offset) + } + + override def endsWith[B >: Char](that: collection.Iterable[B]): Boolean = + that match { + case s: WrappedString => self.endsWith(s.self) + case _ => super.endsWith(that) + } + + override def indexOf[B >: Char](elem: B, from: Int = 0): Int = elem match { + case c: Char => self.indexOf(c, from) + case _ => super.indexOf(elem, from) + } + + override def lastIndexOf[B >: Char](elem: B, end: Int = length - 1): Int = + elem match { + case c: Char => self.lastIndexOf(c, end) + case _ => super.lastIndexOf(elem, end) + } + + override def copyToArray[B >: Char](xs: Array[B], start: Int, len: Int): Int = + (xs: Any) match { + case chs: Array[Char] => + val copied = IterableOnce.elemsToCopyToArray(length, chs.length, start, len) + self.getChars(0, copied, chs, start) + copied + case _ => super.copyToArray(xs, start, len) + } + + override def appendedAll[B >: Char](suffix: IterableOnce[B]): IndexedSeq[B] = + suffix match { + case s: WrappedString => new WrappedString(self concat s.self) + case _ => super.appendedAll(suffix) + } + + override def sameElements[B >: Char](o: IterableOnce[B]) = o match { + case s: WrappedString => self == s.self + case _ => super.sameElements(o) + } + + override protected[this] def className = "WrappedString" + + override protected final def applyPreferredMaxLength: Int = Int.MaxValue + override def equals(other: Any): Boolean = other match { + case that: WrappedString => + this.self == that.self + case _ => + super.equals(other) + } +} + +/** A companion object for wrapped strings. + */ +@SerialVersionUID(3L) +object WrappedString extends SpecificIterableFactory[Char, WrappedString] { + def fromSpecific(it: IterableOnce[Char]): WrappedString = { + val b = newBuilder + b.sizeHint(it) + b ++= it + b.result() + } + val empty: WrappedString = new WrappedString("") + def newBuilder: Builder[Char, WrappedString] = + new StringBuilder().mapResult(x => new WrappedString(x)) + + implicit class UnwrapOp(private val value: WrappedString) extends AnyVal { + def unwrap: String = value.self + } +} diff --git a/library/src/scala/collection/immutable/package.scala b/library/src/scala/collection/immutable/package.scala new file mode 100644 index 000000000000..061619cb8b86 --- /dev/null +++ b/library/src/scala/collection/immutable/package.scala @@ -0,0 +1,30 @@ +/* + * 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.collection + +import scala.language.`2.13` + +package object immutable { + type StringOps = scala.collection.StringOps + val StringOps = scala.collection.StringOps + type StringView = scala.collection.StringView + val StringView = scala.collection.StringView + + @deprecated("Use Iterable instead of Traversable", "2.13.0") + type Traversable[+X] = Iterable[X] + @deprecated("Use Iterable instead of Traversable", "2.13.0") + val Traversable = Iterable + + @deprecated("Use Map instead of DefaultMap", "2.13.0") + type DefaultMap[K, +V] = scala.collection.immutable.Map[K, V] +} diff --git a/library/src/scala/collection/mutable/AnyRefMap.scala b/library/src/scala/collection/mutable/AnyRefMap.scala new file mode 100644 index 000000000000..3caccdbc194d --- /dev/null +++ b/library/src/scala/collection/mutable/AnyRefMap.scala @@ -0,0 +1,628 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` +import scala.annotation.meta.companionClass +import scala.annotation.nowarn +import scala.collection.generic.DefaultSerializationProxy +import scala.language.implicitConversions + +/** This class implements mutable maps with `AnyRef` keys based on a hash table with open addressing. + * + * Basic map operations on single entries, including `contains` and `get`, + * are typically significantly faster with `AnyRefMap` than [[HashMap]]. + * Note that numbers and characters are not handled specially in AnyRefMap; + * only plain `equals` and `hashCode` are used in comparisons. + * + * Methods that traverse or regenerate the map, including `foreach` and `map`, + * are not in general faster than with `HashMap`. The methods `foreachKey`, + * `foreachValue`, `mapValuesNow`, and `transformValues` are, however, faster + * than alternative ways to achieve the same functionality. + * + * Maps with open addressing may become less efficient at lookup after + * repeated addition/removal of elements. Although `AnyRefMap` makes a + * decent attempt to remain efficient regardless, calling `repack` + * on a map that will no longer have elements removed but will be + * used heavily may save both time and storage space. + * + * This map is not intended to contain more than 2^29^ entries (approximately + * 500 million). The maximum capacity is 2^30^, but performance will degrade + * rapidly as 2^30^ is approached. + * + */ +@(deprecated @companionClass)("Use `scala.collection.mutable.HashMap` instead for better performance.", since = "2.13.16") +class AnyRefMap[K <: AnyRef, V] private[collection] (defaultEntry: K => V, initialBufferSize: Int, initBlank: Boolean) + extends AbstractMap[K, V] + with MapOps[K, V, Map, AnyRefMap[K, V]] + with StrictOptimizedIterableOps[(K, V), Iterable, AnyRefMap[K, V]] + with Serializable { + + import AnyRefMap._ + def this() = this(AnyRefMap.exceptionDefault, 16, initBlank = true) + + /** Creates a new `AnyRefMap` that returns default values according to a supplied key-value mapping. */ + def this(defaultEntry: K => V) = this(defaultEntry, 16, initBlank = true) + + /** Creates a new `AnyRefMap` with an initial buffer of specified size. + * + * An `AnyRefMap` can typically contain half as many elements as its buffer size + * before it requires resizing. + */ + def this(initialBufferSize: Int) = this(AnyRefMap.exceptionDefault, initialBufferSize, initBlank = true) + + /** Creates a new `AnyRefMap` with specified default values and initial buffer size. */ + def this(defaultEntry: K => V, initialBufferSize: Int) = this(defaultEntry, initialBufferSize, initBlank = true) + + private[this] var mask = 0 + private[this] var _size = 0 + private[this] var _vacant = 0 + private[this] var _hashes: Array[Int] = null + private[this] var _keys: Array[AnyRef] = null + private[this] var _values: Array[AnyRef] = null + + if (initBlank) defaultInitialize(initialBufferSize) + + private[this] def defaultInitialize(n: Int): Unit = { + mask = + if (n<0) 0x7 + else (((1 << (32 - java.lang.Integer.numberOfLeadingZeros(n-1))) - 1) & 0x3FFFFFFF) | 0x7 + _hashes = new Array[Int](mask+1) + _keys = new Array[AnyRef](mask+1) + _values = new Array[AnyRef](mask+1) + } + + private[collection] def initializeTo( + m: Int, sz: Int, vc: Int, hz: Array[Int], kz: Array[AnyRef], vz: Array[AnyRef] + ): Unit = { + mask = m; _size = sz; _vacant = vc; _hashes = hz; _keys = kz; _values = vz + } + + override protected def fromSpecific(coll: scala.collection.IterableOnce[(K, V)]): AnyRefMap[K,V] = { + var sz = coll.knownSize + if(sz < 0) sz = 4 + val arm = new AnyRefMap[K, V](sz * 2) + coll.iterator.foreach{ case (k,v) => arm(k) = v } + if (arm.size < (sz>>3)) arm.repack() + arm + } + override protected def newSpecificBuilder: Builder[(K, V), AnyRefMap[K,V]] = new AnyRefMapBuilder + + override def size: Int = _size + override def knownSize: Int = size + override def isEmpty: Boolean = _size == 0 + override def empty: AnyRefMap[K,V] = new AnyRefMap(defaultEntry) + + private def imbalanced: Boolean = + (_size + _vacant) > 0.5*mask || _vacant > _size + + private def hashOf(key: K): Int = { + // Note: this method must not return 0 or Int.MinValue, as these indicate no element + if (key eq null) 0x41081989 + else { + val h = key.hashCode + // Part of the MurmurHash3 32 bit finalizer + val i = (h ^ (h >>> 16)) * 0x85EBCA6B + val j = (i ^ (i >>> 13)) & 0x7FFFFFFF + if (j==0) 0x41081989 else j + } + } + + private def seekEntry(h: Int, k: AnyRef): Int = { + var e = h & mask + var x = 0 + var g = 0 + val hashes = _hashes + val keys = _keys + while ({ g = hashes(e); g != 0}) { + if (g == h && { val q = keys(e); (q eq k) || ((q ne null) && (q equals k)) }) return e + x += 1 + e = (e + 2*(x+1)*x - 3) & mask + } + e | MissingBit + } + + @`inline` private def seekEntryOrOpen(h: Int, k: AnyRef): Int = { + var e = h & mask + var x = 0 + var g = 0 + var o = -1 + while ({ g = _hashes(e); g != 0}) { + if (g == h && { val q = _keys(e); (q eq k) || ((q ne null) && (q equals k)) }) return e + else if (o == -1 && g+g == 0) o = e + x += 1 + e = (e + 2*(x+1)*x - 3) & mask + } + if (o >= 0) o | MissVacant else e | MissingBit + } + + override def contains(key: K): Boolean = seekEntry(hashOf(key), key) >= 0 + + override def get(key: K): Option[V] = { + val i = seekEntry(hashOf(key), key) + if (i < 0) None else Some(_values(i).asInstanceOf[V]) + } + + override def getOrElse[V1 >: V](key: K, default: => V1): V1 = { + val i = seekEntry(hashOf(key), key) + if (i < 0) default else _values(i).asInstanceOf[V] + } + + override def getOrElseUpdate(key: K, defaultValue: => V): V = { + val h = hashOf(key) + var i = seekEntryOrOpen(h, key) + if (i < 0) { + val value = { + val ohs = _hashes + val j = i & IndexMask + val oh = ohs(j) + val ans = defaultValue + // Evaluating `defaultValue` may change the map + // - repack: the array is different + // - element added at `j`: since `i < 0`, the key was missing and `oh` is either 0 or MinValue. + // If `defaultValue` added an element at `j` then `_hashes(j)` must be different now. + // (`hashOf` never returns 0 or MinValue.) + if (ohs.ne(_hashes) || oh != _hashes(j)) { + i = seekEntryOrOpen(h, key) + if (i >= 0) _size -= 1 + } + ans + } + _size += 1 + val j = i & IndexMask + _hashes(j) = h + _keys(j) = key.asInstanceOf[AnyRef] + _values(j) = value.asInstanceOf[AnyRef] + if ((i & VacantBit) != 0) _vacant -= 1 + else if (imbalanced) repack() + value + } + else _values(i).asInstanceOf[V] + } + + /** Retrieves the value associated with a key, or the default for that type if none exists + * (null for AnyRef, 0 for floats and integers). + * + * Note: this is the fastest way to retrieve a value that may or + * may not exist, if the default null/zero is acceptable. For key/value + * pairs that do exist, `apply` (i.e. `map(key)`) is equally fast. + */ + def getOrNull(key: K): V = { + val i = seekEntry(hashOf(key), key) + (if (i < 0) null else _values(i)).asInstanceOf[V] + } + + /** Retrieves the value associated with a key. + * If the key does not exist in the map, the `defaultEntry` for that key + * will be returned instead; an exception will be thrown if no + * `defaultEntry` was supplied. + */ + override def apply(key: K): V = { + val i = seekEntry(hashOf(key), key) + if (i < 0) defaultEntry(key) else _values(i).asInstanceOf[V] + } + + /** Defers to defaultEntry to find a default value for the key. Throws an + * exception if no other default behavior was specified. + */ + override def default(key: K): V = defaultEntry(key) + + private def repack(newMask: Int): Unit = { + val oh = _hashes + val ok = _keys + val ov = _values + mask = newMask + _hashes = new Array[Int](mask+1) + _keys = new Array[AnyRef](mask+1) + _values = new Array[AnyRef](mask+1) + _vacant = 0 + var i = 0 + while (i < oh.length) { + val h = oh(i) + if (h+h != 0) { + var e = h & mask + var x = 0 + while (_hashes(e) != 0) { x += 1; e = (e + 2*(x+1)*x - 3) & mask } + _hashes(e) = h + _keys(e) = ok(i) + _values(e) = ov(i) + } + i += 1 + } + } + + /** Repacks the contents of this `AnyRefMap` for maximum efficiency of lookup. + * + * For maps that undergo a complex creation process with both addition and + * removal of keys, and then are used heavily with no further removal of + * elements, calling `repack` after the end of the creation can result in + * improved performance. Repacking takes time proportional to the number + * of entries in the map. + */ + def repack(): Unit = { + var m = mask + if (_size + _vacant >= 0.5*mask && !(_vacant > 0.2*mask)) m = ((m << 1) + 1) & IndexMask + while (m > 8 && 8*_size < m) m = m >>> 1 + repack(m) + } + + override def put(key: K, value: V): Option[V] = { + val h = hashOf(key) + val i = seekEntryOrOpen(h, key) + if (i < 0) { + val j = i & IndexMask + _hashes(j) = h + _keys(j) = key + _values(j) = value.asInstanceOf[AnyRef] + _size += 1 + if ((i & VacantBit) != 0) _vacant -= 1 + else if (imbalanced) repack() + None + } + else { + val ans = Some(_values(i).asInstanceOf[V]) + _hashes(i) = h + _values(i) = value.asInstanceOf[AnyRef] + ans + } + } + + /** Updates the map to include a new key-value pair. + * + * This is the fastest way to add an entry to an `AnyRefMap`. + */ + override def update(key: K, value: V): Unit = { + val h = hashOf(key) + val i = seekEntryOrOpen(h, key) + if (i < 0) { + val j = i & IndexMask + _hashes(j) = h + _keys(j) = key + _values(j) = value.asInstanceOf[AnyRef] + _size += 1 + if ((i & VacantBit) != 0) _vacant -= 1 + else if (imbalanced) repack() + } + else { + _hashes(i) = h + _values(i) = value.asInstanceOf[AnyRef] + } + } + + /** Adds a new key/value pair to this map and returns the map. */ + @deprecated("Use `addOne` or `update` instead; infix operations with an operand of multiple args will be deprecated", "2.13.3") + def +=(key: K, value: V): this.type = { update(key, value); this } + + /** Adds a new key/value pair to this map and returns the map. */ + @inline final def addOne(key: K, value: V): this.type = { update(key, value); this } + + @inline override final def addOne(kv: (K, V)): this.type = { update(kv._1, kv._2); this } + + def subtractOne(key: K): this.type = { + val i = seekEntry(hashOf(key), key) + if (i >= 0) { + _size -= 1 + _vacant += 1 + _hashes(i) = Int.MinValue + _keys(i) = null + _values(i) = null + } + this + } + + def iterator: Iterator[(K, V)] = new AnyRefMapIterator[(K, V)] { + protected def nextResult(k: K, v: V) = (k, v) + } + override def keysIterator: Iterator[K] = new AnyRefMapIterator[K] { + protected def nextResult(k: K, v: V) = k + } + override def valuesIterator: Iterator[V] = new AnyRefMapIterator[V] { + protected def nextResult(k: K, v: V) = v + } + + private abstract class AnyRefMapIterator[A] extends AbstractIterator[A] { + private[this] val hz = _hashes + private[this] val kz = _keys + private[this] val vz = _values + + private[this] var index = 0 + + def hasNext: Boolean = index= hz.length) return false + h = hz(index) + } + true + } + + def next(): A = { + if (hasNext) { + val ans = nextResult(kz(index).asInstanceOf[K], vz(index).asInstanceOf[V]) + index += 1 + ans + } + else throw new NoSuchElementException("next") + } + + protected def nextResult(k: K, v: V): A + } + + + override def foreach[U](f: ((K,V)) => U): Unit = { + var i = 0 + var e = _size + while (e > 0) { + while(i < _hashes.length && { val h = _hashes(i); h+h == 0 && i < _hashes.length}) i += 1 + if (i < _hashes.length) { + f((_keys(i).asInstanceOf[K], _values(i).asInstanceOf[V])) + i += 1 + e -= 1 + } + else return + } + } + + override def foreachEntry[U](f: (K,V) => U): Unit = { + var i = 0 + var e = _size + while (e > 0) { + while(i < _hashes.length && { val h = _hashes(i); h+h == 0 && i < _hashes.length}) i += 1 + if (i < _hashes.length) { + f(_keys(i).asInstanceOf[K], _values(i).asInstanceOf[V]) + i += 1 + e -= 1 + } + else return + } + } + + override def clone(): AnyRefMap[K, V] = { + val hz = java.util.Arrays.copyOf(_hashes, _hashes.length) + val kz = java.util.Arrays.copyOf(_keys, _keys.length) + val vz = java.util.Arrays.copyOf(_values, _values.length) + val arm = new AnyRefMap[K, V](defaultEntry, 1, initBlank = false) + arm.initializeTo(mask, _size, _vacant, hz, kz, vz) + arm + } + + @deprecated("Consider requiring an immutable Map or fall back to Map.concat", "2.13.0") + override def + [V1 >: V](kv: (K, V1)): AnyRefMap[K, V1] = AnyRefMap.from(new View.Appended(this, kv)) + + @deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0") + override def + [V1 >: V](elem1: (K, V1), elem2: (K, V1), elems: (K, V1)*): AnyRefMap[K, V1] = { + val m = this + elem1 + elem2 + if(elems.isEmpty) m else m.concat(elems) + } + + override def concat[V2 >: V](xs: scala.collection.IterableOnce[(K, V2)]): AnyRefMap[K, V2] = { + val arm = clone().asInstanceOf[AnyRefMap[K, V2]] + xs.iterator.foreach(kv => arm += kv) + arm + } + + override def ++[V2 >: V](xs: scala.collection.IterableOnce[(K, V2)]): AnyRefMap[K, V2] = concat(xs) + + @deprecated("Use m.clone().addOne(k,v) instead of m.updated(k, v)", "2.13.0") + override def updated[V1 >: V](key: K, value: V1): AnyRefMap[K, V1] = + clone().asInstanceOf[AnyRefMap[K, V1]].addOne(key, value) + + private[this] def foreachElement[A,B](elems: Array[AnyRef], f: A => B): Unit = { + var i,j = 0 + while (i < _hashes.length & j < _size) { + val h = _hashes(i) + if (h+h != 0) { + j += 1 + f(elems(i).asInstanceOf[A]) + } + i += 1 + } + } + + /** Applies a function to all keys of this map. */ + def foreachKey[A](f: K => A): Unit = foreachElement[K,A](_keys, f) + + /** Applies a function to all values of this map. */ + def foreachValue[A](f: V => A): Unit = foreachElement[V,A](_values, f) + + /** Creates a new `AnyRefMap` with different values. + * Unlike `mapValues`, this method generates a new + * collection immediately. + */ + def mapValuesNow[V1](f: V => V1): AnyRefMap[K, V1] = { + val arm = new AnyRefMap[K,V1](AnyRefMap.exceptionDefault, 1, initBlank = false) + val hz = java.util.Arrays.copyOf(_hashes, _hashes.length) + val kz = java.util.Arrays.copyOf(_keys, _keys.length) + val vz = new Array[AnyRef](_values.length) + var i,j = 0 + while (i < _hashes.length & j < _size) { + val h = _hashes(i) + if (h+h != 0) { + j += 1 + vz(i) = f(_values(i).asInstanceOf[V]).asInstanceOf[AnyRef] + } + i += 1 + } + arm.initializeTo(mask, _size, _vacant, hz, kz, vz) + arm + } + + /** Applies a transformation function to all values stored in this map. + * Note: the default, if any, is not transformed. + */ + @deprecated("Use transformValuesInPlace instead of transformValues", "2.13.0") + @`inline` final def transformValues(f: V => V): this.type = transformValuesInPlace(f) + + /** Applies a transformation function to all values stored in this map. + * Note: the default, if any, is not transformed. + */ + def transformValuesInPlace(f: V => V): this.type = { + var i,j = 0 + while (i < _hashes.length & j < _size) { + val h = _hashes(i) + if (h+h != 0) { + j += 1 + _values(i) = f(_values(i).asInstanceOf[V]).asInstanceOf[AnyRef] + } + i += 1 + } + this + } + + // The implicit dummy parameter is necessary to distinguish these methods from the base methods they overload (not override). + // Previously, in Scala 2, f took `K with AnyRef` scala/bug#11035 + /** + * An overload of `map` which produces an `AnyRefMap`. + * + * @param f the mapping function must produce a key-value pair where the key is an `AnyRef` + * @param dummy an implicit placeholder for purposes of distinguishing the (erased) signature of this method + */ + def map[K2 <: AnyRef, V2](f: ((K, V)) => (K2, V2))(implicit dummy: DummyImplicit): AnyRefMap[K2, V2] = + AnyRefMap.from(new View.Map(this, f)) + /** + * An overload of `flatMap` which produces an `AnyRefMap`. + * + * @param f the mapping function must produce key-value pairs where the key is an `AnyRef` + * @param dummy an implicit placeholder for purposes of distinguishing the (erased) signature of this method + */ + def flatMap[K2 <: AnyRef, V2](f: ((K, V)) => IterableOnce[(K2, V2)])(implicit dummy: DummyImplicit): AnyRefMap[K2, V2] = + AnyRefMap.from(new View.FlatMap(this, f)) + /** + * An overload of `collect` which produces an `AnyRefMap`. + * + * @param pf the mapping function must produce a key-value pair where the key is an `AnyRef` + * @param dummy an implicit placeholder for purposes of distinguishing the (erased) signature of this method + */ + def collect[K2 <: AnyRef, V2](pf: PartialFunction[(K, V), (K2, V2)])(implicit dummy: DummyImplicit): AnyRefMap[K2, V2] = + strictOptimizedCollect(AnyRefMap.newBuilder[K2, V2], pf) + + override def clear(): Unit = { + import java.util.Arrays.fill + fill(_keys, null) + fill(_values, null) + fill(_hashes, 0) + _size = 0 + _vacant = 0 + } + + protected[this] def writeReplace(): AnyRef = new DefaultSerializationProxy(AnyRefMap.toFactory[K, V](AnyRefMap), this) + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix = "AnyRefMap" +} + +@deprecated("Use `scala.collection.mutable.HashMap` instead for better performance.", since = "2.13.16") +object AnyRefMap { + private final val IndexMask = 0x3FFFFFFF + private final val MissingBit = 0x80000000 + private final val VacantBit = 0x40000000 + private final val MissVacant = 0xC0000000 + + private class ExceptionDefault extends (Any => Nothing) with Serializable { + def apply(k: Any): Nothing = throw new NoSuchElementException(if (k == null) "(null)" else k.toString) + } + private val exceptionDefault = new ExceptionDefault + + /** A builder for instances of `AnyRefMap`. + * + * This builder can be reused to create multiple instances. + */ + final class AnyRefMapBuilder[K <: AnyRef, V] extends ReusableBuilder[(K, V), AnyRefMap[K, V]] { + private[collection] var elems: AnyRefMap[K, V] = new AnyRefMap[K, V] + def addOne(entry: (K, V)): this.type = { + elems += entry + this + } + def clear(): Unit = elems = new AnyRefMap[K, V] + def result(): AnyRefMap[K, V] = elems + override def knownSize: Int = elems.knownSize + } + + /** Creates a new `AnyRefMap` with zero or more key/value pairs. */ + def apply[K <: AnyRef, V](elems: (K, V)*): AnyRefMap[K, V] = buildFromIterableOnce(elems) + + def newBuilder[K <: AnyRef, V]: ReusableBuilder[(K, V), AnyRefMap[K, V]] = new AnyRefMapBuilder[K, V] + + private def buildFromIterableOnce[K <: AnyRef, V](elems: IterableOnce[(K, V)]): AnyRefMap[K, V] = { + var sz = elems.knownSize + if(sz < 0) sz = 4 + val arm = new AnyRefMap[K, V](sz * 2) + elems.iterator.foreach{ case (k,v) => arm(k) = v } + if (arm.size < (sz>>3)) arm.repack() + arm + } + + /** Creates a new empty `AnyRefMap`. */ + def empty[K <: AnyRef, V]: AnyRefMap[K, V] = new AnyRefMap[K, V] + + /** Creates a new empty `AnyRefMap` with the supplied default */ + def withDefault[K <: AnyRef, V](default: K => V): AnyRefMap[K, V] = new AnyRefMap[K, V](default) + + /** Creates a new `AnyRefMap` from an existing source collection. A source collection + * which is already an `AnyRefMap` gets cloned. + * + * @param source Source collection + * @tparam K the type of the keys + * @tparam V the type of the values + * @return a new `AnyRefMap` with the elements of `source` + */ + def from[K <: AnyRef, V](source: IterableOnce[(K, V)]): AnyRefMap[K, V] = source match { + case source: AnyRefMap[_, _] => source.clone().asInstanceOf[AnyRefMap[K, V]] + case _ => buildFromIterableOnce(source) + } + + /** Creates a new `AnyRefMap` from arrays of keys and values. + * Equivalent to but more efficient than `AnyRefMap((keys zip values): _*)`. + */ + def fromZip[K <: AnyRef, V](keys: Array[K], values: Array[V]): AnyRefMap[K, V] = { + val sz = math.min(keys.length, values.length) + val arm = new AnyRefMap[K, V](sz * 2) + var i = 0 + while (i < sz) { arm(keys(i)) = values(i); i += 1 } + if (arm.size < (sz>>3)) arm.repack() + arm + } + + /** Creates a new `AnyRefMap` from keys and values. + * Equivalent to but more efficient than `AnyRefMap((keys zip values): _*)`. + */ + def fromZip[K <: AnyRef, V](keys: Iterable[K], values: Iterable[V]): AnyRefMap[K, V] = { + val sz = math.min(keys.size, values.size) + val arm = new AnyRefMap[K, V](sz * 2) + val ki = keys.iterator + val vi = values.iterator + while (ki.hasNext && vi.hasNext) arm(ki.next()) = vi.next() + if (arm.size < (sz >> 3)) arm.repack() + arm + } + + implicit def toFactory[K <: AnyRef, V](dummy: AnyRefMap.type): Factory[(K, V), AnyRefMap[K, V]] = ToFactory.asInstanceOf[Factory[(K, V), AnyRefMap[K, V]]] + + @SerialVersionUID(3L) + private[this] object ToFactory extends Factory[(AnyRef, AnyRef), AnyRefMap[AnyRef, AnyRef]] with Serializable { + def fromSpecific(it: IterableOnce[(AnyRef, AnyRef)]): AnyRefMap[AnyRef, AnyRef] = AnyRefMap.from[AnyRef, AnyRef](it) + def newBuilder: Builder[(AnyRef, AnyRef), AnyRefMap[AnyRef, AnyRef]] = AnyRefMap.newBuilder[AnyRef, AnyRef] + } + + implicit def toBuildFrom[K <: AnyRef, V](factory: AnyRefMap.type): BuildFrom[Any, (K, V), AnyRefMap[K, V]] = ToBuildFrom.asInstanceOf[BuildFrom[Any, (K, V), AnyRefMap[K, V]]] + private[this] object ToBuildFrom extends BuildFrom[Any, (AnyRef, AnyRef), AnyRefMap[AnyRef, AnyRef]] { + def fromSpecific(from: Any)(it: IterableOnce[(AnyRef, AnyRef)]): AnyRefMap[AnyRef, AnyRef] = AnyRefMap.from(it) + def newBuilder(from: Any): ReusableBuilder[(AnyRef, AnyRef), AnyRefMap[AnyRef, AnyRef]] = AnyRefMap.newBuilder[AnyRef, AnyRef] + } + + implicit def iterableFactory[K <: AnyRef, V]: Factory[(K, V), AnyRefMap[K, V]] = toFactory[K, V](this) + implicit def buildFromAnyRefMap[K <: AnyRef, V]: BuildFrom[AnyRefMap[_, _], (K, V), AnyRefMap[K, V]] = toBuildFrom(this) +} diff --git a/library/src/scala/collection/mutable/ArrayBuffer.scala b/library/src/scala/collection/mutable/ArrayBuffer.scala new file mode 100644 index 000000000000..72384e3f65a2 --- /dev/null +++ b/library/src/scala/collection/mutable/ArrayBuffer.scala @@ -0,0 +1,408 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` +import java.util.Arrays +import scala.annotation.{nowarn, tailrec} +import scala.collection.Stepper.EfficientSplit +import scala.collection.generic.{CommonErrors, DefaultSerializable} +import scala.runtime.PStatics.VM_MaxArraySize + +/** An implementation of the `Buffer` class using an array to + * represent the assembled sequence internally. Append, update and random + * access take constant time (amortized time). Prepends and removes are + * linear in the buffer size. + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html#array-buffers "Scala's Collection Library overview"]] + * section on `Array Buffers` for more information. + * + * @tparam A the type of this arraybuffer's elements. + * + * @define Coll `mutable.ArrayBuffer` + * @define coll array buffer + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +@SerialVersionUID(-1582447879429021880L) +class ArrayBuffer[A] private (initialElements: Array[AnyRef], initialSize: Int) + extends AbstractBuffer[A] + with IndexedBuffer[A] + with IndexedSeqOps[A, ArrayBuffer, ArrayBuffer[A]] + with StrictOptimizedSeqOps[A, ArrayBuffer, ArrayBuffer[A]] + with IterableFactoryDefaults[A, ArrayBuffer] + with DefaultSerializable { + + def this() = this(new Array[AnyRef](ArrayBuffer.DefaultInitialSize), 0) + + def this(initialSize: Int) = this(new Array[AnyRef](initialSize max 1), 0) + + @transient private[this] var mutationCount: Int = 0 + + // needs to be `private[collection]` or `protected[collection]` for parallel-collections + protected[collection] var array: Array[AnyRef] = initialElements + protected var size0 = initialSize + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit = { + import scala.collection.convert.impl._ + shape.parUnbox(new ObjectArrayStepper(array, 0, length).asInstanceOf[AnyStepper[A] with EfficientSplit]) + } + + override def knownSize: Int = super[IndexedSeqOps].knownSize + + /** Ensure that the internal array has at least `n` cells. */ + protected def ensureSize(n: Int): Unit = { + array = ArrayBuffer.ensureSize(array, size0, n) + } + + /** Uses the given size to resize internal storage, if necessary. + * + * @param size Expected maximum number of elements. + */ + def sizeHint(size: Int): Unit = + if(size > length && size >= 1) ensureSize(size) + + /** Reduce length to `n`, nulling out all dropped elements */ + private def reduceToSize(n: Int): Unit = { + mutationCount += 1 + Arrays.fill(array, n, size0, null) + size0 = n + } + + /** Trims the ArrayBuffer to an appropriate size for the current + * number of elements (rounding up to the next natural size), + * which may replace the array by a shorter one. + * This allows releasing some unused memory. + */ + def trimToSize(): Unit = { + resize(length) + } + + /** Trims the `array` buffer size down to either a power of 2 + * or Int.MaxValue while keeping first `requiredLength` elements. + */ + private def resize(requiredLength: Int): Unit = + array = ArrayBuffer.downsize(array, requiredLength) + + @inline private def checkWithinBounds(lo: Int, hi: Int) = { + if (lo < 0) throw CommonErrors.indexOutOfBounds(index = lo, max = size0 - 1) + if (hi > size0) throw CommonErrors.indexOutOfBounds(index = hi - 1, max = size0 - 1) + } + + def apply(n: Int): A = { + checkWithinBounds(n, n + 1) + array(n).asInstanceOf[A] + } + + def update(@deprecatedName("n", "2.13.0") index: Int, elem: A): Unit = { + checkWithinBounds(index, index + 1) + mutationCount += 1 + array(index) = elem.asInstanceOf[AnyRef] + } + + def length = size0 + + // TODO: return `IndexedSeqView` rather than `ArrayBufferView` + override def view: ArrayBufferView[A] = new ArrayBufferView(this, () => mutationCount) + + override def iterableFactory: SeqFactory[ArrayBuffer] = ArrayBuffer + + /** Note: This does not actually resize the internal representation. + * See clearAndShrink if you want to also resize internally + */ + def clear(): Unit = reduceToSize(0) + + /** + * Clears this buffer and shrinks to @param size (rounding up to the next + * natural size) + * @param size + */ + def clearAndShrink(size: Int = ArrayBuffer.DefaultInitialSize): this.type = { + clear() + resize(size) + this + } + + def addOne(elem: A): this.type = { + mutationCount += 1 + val newSize = size0 + 1 + if(array.length <= newSize - 1) ensureSize(newSize) + size0 = newSize + array(newSize - 1) = elem.asInstanceOf[AnyRef] + this + } + + // Overridden to use array copying for efficiency where possible. + override def addAll(elems: IterableOnce[A]): this.type = { + elems match { + case elems: ArrayBuffer[_] => + val elemsLength = elems.size0 + if (elemsLength > 0) { + mutationCount += 1 + ensureSize(size0 + elemsLength) + Array.copy(elems.array, 0, array, length, elemsLength) + size0 = length + elemsLength + } + case _ => super.addAll(elems) + } + this + } + + def insert(@deprecatedName("n", "2.13.0") index: Int, elem: A): Unit = { + checkWithinBounds(index, index) + mutationCount += 1 + ensureSize(size0 + 1) + Array.copy(array, index, array, index + 1, size0 - index) + size0 += 1 + this(index) = elem + } + + def prepend(elem: A): this.type = { + insert(0, elem) + this + } + + def insertAll(@deprecatedName("n", "2.13.0") index: Int, elems: IterableOnce[A]): Unit = { + checkWithinBounds(index, index) + elems match { + case elems: collection.Iterable[A] => + val elemsLength = elems.size + if (elemsLength > 0) { + mutationCount += 1 + ensureSize(size0 + elemsLength) + val len = size0 + Array.copy(array, index, array, index + elemsLength, len - index) + // if `elems eq this`, this copy is safe because + // - `elems.array eq this.array` + // - we didn't overwrite the values being inserted after moving them in + // the previous line + // - `copyElemsToArray` will call `System.arraycopy` + // - `System.arraycopy` will effectively "read" all the values before + // overwriting any of them when two arrays are the same reference + val actual = IterableOnce.copyElemsToArray(elems, array.asInstanceOf[Array[Any]], index, elemsLength) + if (actual != elemsLength) throw new IllegalStateException(s"Copied $actual of $elemsLength") + size0 = len + elemsLength // update size AFTER the copy, in case we're inserting a proxy + } + case _ => insertAll(index, ArrayBuffer.from(elems)) + } + } + + /** Note: This does not actually resize the internal representation. + * See trimToSize if you want to also resize internally + */ + def remove(@deprecatedName("n", "2.13.0") index: Int): A = { + checkWithinBounds(index, index + 1) + val res = this(index) + Array.copy(array, index + 1, array, index, size0 - (index + 1)) + reduceToSize(size0 - 1) + res + } + + /** Note: This does not actually resize the internal representation. + * See trimToSize if you want to also resize internally + */ + def remove(@deprecatedName("n", "2.13.0") index: Int, count: Int): Unit = + if (count > 0) { + checkWithinBounds(index, index + count) + Array.copy(array, index + count, array, index, size0 - (index + count)) + reduceToSize(size0 - count) + } else if (count < 0) { + throw new IllegalArgumentException("removing negative number of elements: " + count) + } + + @deprecated("Use 'this' instance instead", "2.13.0") + @deprecatedOverriding("ArrayBuffer[A] no longer extends Builder[A, ArrayBuffer[A]]", "2.13.0") + @inline def result(): this.type = this + + @deprecated("Use 'new GrowableBuilder(this).mapResult(f)' instead", "2.13.0") + @deprecatedOverriding("ArrayBuffer[A] no longer extends Builder[A, ArrayBuffer[A]]", "2.13.0") + @inline def mapResult[NewTo](f: (ArrayBuffer[A]) => NewTo): Builder[A, NewTo] = new GrowableBuilder[A, ArrayBuffer[A]](this).mapResult(f) + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix = "ArrayBuffer" + + override def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): Int = { + val copied = IterableOnce.elemsToCopyToArray(length, xs.length, start, len) + if(copied > 0) { + Array.copy(array, 0, xs, start, copied) + } + copied + } + + /** Sorts this $coll in place according to an Ordering. + * + * @see [[scala.collection.mutable.IndexedSeqOps.sortInPlace]] + * @param ord the ordering to be used to compare elements. + * @return modified input $coll sorted according to the ordering `ord`. + */ + override def sortInPlace[B >: A]()(implicit ord: Ordering[B]): this.type = { + if (length > 1) { + mutationCount += 1 + scala.util.Sorting.stableSort(array.asInstanceOf[Array[B]], 0, length) + } + this + } + + @tailrec private def foldl[B](start: Int, end: Int, z: B, op: (B, A) => B): B = + if (start == end) z + else foldl(start + 1, end, op(z, array(start).asInstanceOf[A]), op) + + @tailrec private def foldr[B](start: Int, end: Int, z: B, op: (A, B) => B): B = + if (start == end) z + else foldr(start, end - 1, op(array(end - 1).asInstanceOf[A], z), op) + + override def foldLeft[B](z: B)(op: (B, A) => B): B = foldl(0, length, z, op) + + override def foldRight[B](z: B)(op: (A, B) => B): B = foldr(0, length, z, op) + + override def reduceLeft[B >: A](op: (B, A) => B): B = + if (length > 0) foldl(1, length, array(0).asInstanceOf[B], op) + else super.reduceLeft(op) + + override def reduceRight[B >: A](op: (A, B) => B): B = + if (length > 0) foldr(0, length - 1, array(length - 1).asInstanceOf[B], op) + else super.reduceRight(op) + + override def sliding(size: Int, step: Int): Iterator[ArrayBuffer[A]] = + new MutationTracker.CheckedIterator(super.sliding(size = size, step = step), mutationCount) +} + +/** + * Factory object for the `ArrayBuffer` class. + * + * $factoryInfo + * + * @define coll array buffer + * @define Coll `mutable.ArrayBuffer` + */ +@SerialVersionUID(3L) +object ArrayBuffer extends StrictOptimizedSeqFactory[ArrayBuffer] { + final val DefaultInitialSize = 16 + private[this] val emptyArray = new Array[AnyRef](0) + + def from[B](coll: collection.IterableOnce[B]): ArrayBuffer[B] = { + val k = coll.knownSize + if (k >= 0) { + // Avoid reallocation of buffer if length is known + val array = ensureSize(emptyArray, 0, k) // don't duplicate sizing logic, and check VM array size limit + val actual = IterableOnce.copyElemsToArray(coll, array.asInstanceOf[Array[Any]]) + if (actual != k) throw new IllegalStateException(s"Copied $actual of $k") + new ArrayBuffer[B](array, k) + } + else new ArrayBuffer[B] ++= coll + } + + def newBuilder[A]: Builder[A, ArrayBuffer[A]] = new GrowableBuilder[A, ArrayBuffer[A]](empty[A]) { + override def sizeHint(size: Int): Unit = elems.sizeHint(size) + } + + def empty[A]: ArrayBuffer[A] = new ArrayBuffer[A]() + + /** + * The increased size for an array-backed collection. + * + * @param arrayLen the length of the backing array + * @param targetLen the minimum length to resize up to + * @return + * - `-1` if no resizing is needed, else + * - `VM_MaxArraySize` if `arrayLen` is too large to be doubled, else + * - `max(targetLen, arrayLen * 2, DefaultInitialSize)`. + * - Throws an exception if `targetLen` exceeds `VM_MaxArraySize` or is negative (overflow). + */ + private[mutable] def resizeUp(arrayLen: Int, targetLen: Int): Int = + if (targetLen < 0) throw new RuntimeException(s"Overflow while resizing array of array-backed collection. Requested length: $targetLen; current length: $arrayLen; increase: ${targetLen - arrayLen}") + else if (targetLen <= arrayLen) -1 + else if (targetLen > VM_MaxArraySize) throw new RuntimeException(s"Array of array-backed collection exceeds VM length limit of $VM_MaxArraySize. Requested length: $targetLen; current length: $arrayLen") + else if (arrayLen > VM_MaxArraySize / 2) VM_MaxArraySize + else math.max(targetLen, math.max(arrayLen * 2, DefaultInitialSize)) + + // if necessary, copy (curSize elements of) the array to a new array of capacity n. + // Should use Array.copyOf(array, resizeEnsuring(array.length))? + private def ensureSize(array: Array[AnyRef], curSize: Int, targetSize: Int): Array[AnyRef] = { + val newLen = resizeUp(array.length, targetSize) + if (newLen < 0) array + else { + val res = new Array[AnyRef](newLen) + System.arraycopy(array, 0, res, 0, curSize) + res + } + } + + /** + * @param arrayLen the length of the backing array + * @param targetLen the length to resize down to, if smaller than `arrayLen` + * @return -1 if no resizing is needed, or the size for the new array otherwise + */ + private def resizeDown(arrayLen: Int, targetLen: Int): Int = + if (targetLen >= arrayLen) -1 else math.max(targetLen, 0) + private def downsize(array: Array[AnyRef], targetSize: Int): Array[AnyRef] = { + val newLen = resizeDown(array.length, targetSize) + if (newLen < 0) array + else if (newLen == 0) emptyArray + else { + val res = new Array[AnyRef](newLen) + System.arraycopy(array, 0, res, 0, targetSize) + res + } + } +} + +// TODO: use `CheckedIndexedSeqView.Id` once we can change the return type of `ArrayBuffer#view` +final class ArrayBufferView[A] private[mutable](underlying: ArrayBuffer[A], mutationCount: () => Int) + extends AbstractIndexedSeqView[A] { + @deprecated("never intended to be public; call ArrayBuffer#view instead", since = "2.13.7") + def this(array: Array[AnyRef], length: Int) = { + // this won't actually track mutation, but it would be a pain to have the implementation + // check if we have a method to get the current mutation count or not on every method and + // change what it does based on that. hopefully no one ever calls this. + this({ + val _array = array + val _length = length + new ArrayBuffer[A](0) { + this.array = _array + this.size0 = _length + } + }, () => 0) + } + + @deprecated("never intended to be public", since = "2.13.7") + def array: Array[AnyRef] = underlying.toArray[Any].asInstanceOf[Array[AnyRef]] + + @throws[IndexOutOfBoundsException] + def apply(n: Int): A = underlying(n) + def length: Int = underlying.length + override protected[this] def className = "ArrayBufferView" + + // we could inherit all these from `CheckedIndexedSeqView`, except this class is public + override def iterator: Iterator[A] = new CheckedIndexedSeqView.CheckedIterator(this, mutationCount()) + override def reverseIterator: Iterator[A] = new CheckedIndexedSeqView.CheckedReverseIterator(this, mutationCount()) + + override def appended[B >: A](elem: B): IndexedSeqView[B] = new CheckedIndexedSeqView.Appended(this, elem)(mutationCount) + override def prepended[B >: A](elem: B): IndexedSeqView[B] = new CheckedIndexedSeqView.Prepended(elem, this)(mutationCount) + override def take(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.Take(this, n)(mutationCount) + override def takeRight(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.TakeRight(this, n)(mutationCount) + override def drop(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.Drop(this, n)(mutationCount) + override def dropRight(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.DropRight(this, n)(mutationCount) + override def map[B](f: A => B): IndexedSeqView[B] = new CheckedIndexedSeqView.Map(this, f)(mutationCount) + override def reverse: IndexedSeqView[A] = new CheckedIndexedSeqView.Reverse(this)(mutationCount) + override def slice(from: Int, until: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.Slice(this, from, until)(mutationCount) + override def tapEach[U](f: A => U): IndexedSeqView[A] = new CheckedIndexedSeqView.Map(this, { (a: A) => f(a); a})(mutationCount) + + override def concat[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new CheckedIndexedSeqView.Concat(this, suffix)(mutationCount) + override def appendedAll[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new CheckedIndexedSeqView.Concat(this, suffix)(mutationCount) + override def prependedAll[B >: A](prefix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new CheckedIndexedSeqView.Concat(prefix, this)(mutationCount) +} diff --git a/library/src/scala/collection/mutable/ArrayBuilder.scala b/library/src/scala/collection/mutable/ArrayBuilder.scala new file mode 100644 index 000000000000..c6bd34a4001c --- /dev/null +++ b/library/src/scala/collection/mutable/ArrayBuilder.scala @@ -0,0 +1,537 @@ +/* + * 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.collection +package mutable + +import scala.language.`2.13` +import scala.collection.mutable.ArrayBuffer.resizeUp +import scala.reflect.ClassTag + +/** A builder class for arrays. + * + * @tparam T the type of the elements for the builder. + */ +@SerialVersionUID(3L) +sealed abstract class ArrayBuilder[T] + extends ReusableBuilder[T, Array[T]] + with Serializable { + protected[this] var capacity: Int = 0 + protected[this] def elems: Array[T] // may not be allocated at size = capacity = 0 + protected var size: Int = 0 + + /** Current number of elements. */ + def length: Int = size + + /** Current number of elements. */ + override def knownSize: Int = size + + protected[this] final def ensureSize(size: Int): Unit = { + val newLen = resizeUp(capacity, size) + if (newLen > 0) resize(newLen) + } + + override final def sizeHint(size: Int): Unit = if (capacity < size) resize(size) + + def clear(): Unit = size = 0 + + protected[this] def resize(size: Int): Unit + + /** Add all elements of an array. */ + def addAll(xs: Array[_ <: T]): this.type = addAll(xs, 0, xs.length) + + /** Add a slice of an array. */ + def addAll(xs: Array[_ <: T], offset: Int, length: Int): this.type = { + val offset1 = offset.max(0) + val length1 = length.max(0) + val effectiveLength = length1.min(xs.length - offset1) + doAddAll(xs, offset1, effectiveLength) + } + + private def doAddAll(xs: Array[_ <: T], offset: Int, length: Int): this.type = { + if (length > 0) { + ensureSize(this.size + length) + Array.copy(xs, offset, elems, this.size, length) + size += length + } + this + } + + override def addAll(xs: IterableOnce[T]): this.type = { + val k = xs.knownSize + if (k > 0) { + ensureSize(this.size + k) + val actual = IterableOnce.copyElemsToArray(xs, elems, this.size) + if (actual != k) throw new IllegalStateException(s"Copied $actual of $k") + size += k + } else if (k < 0) super.addAll(xs) + this + } +} + +/** A companion object for array builders. + */ +object ArrayBuilder { + + /** Creates a new arraybuilder of type `T`. + * + * @tparam T type of the elements for the array builder, with a `ClassTag` context bound. + * @return a new empty array builder. + */ + @inline def make[T: ClassTag]: ArrayBuilder[T] = { + val tag = implicitly[ClassTag[T]] + tag.runtimeClass match { + case java.lang.Byte.TYPE => new ArrayBuilder.ofByte().asInstanceOf[ArrayBuilder[T]] + case java.lang.Short.TYPE => new ArrayBuilder.ofShort().asInstanceOf[ArrayBuilder[T]] + case java.lang.Character.TYPE => new ArrayBuilder.ofChar().asInstanceOf[ArrayBuilder[T]] + case java.lang.Integer.TYPE => new ArrayBuilder.ofInt().asInstanceOf[ArrayBuilder[T]] + case java.lang.Long.TYPE => new ArrayBuilder.ofLong().asInstanceOf[ArrayBuilder[T]] + case java.lang.Float.TYPE => new ArrayBuilder.ofFloat().asInstanceOf[ArrayBuilder[T]] + case java.lang.Double.TYPE => new ArrayBuilder.ofDouble().asInstanceOf[ArrayBuilder[T]] + case java.lang.Boolean.TYPE => new ArrayBuilder.ofBoolean().asInstanceOf[ArrayBuilder[T]] + case java.lang.Void.TYPE => new ArrayBuilder.ofUnit().asInstanceOf[ArrayBuilder[T]] + case _ => new ArrayBuilder.ofRef[T with AnyRef]()(tag.asInstanceOf[ClassTag[T with AnyRef]]).asInstanceOf[ArrayBuilder[T]] + } + } + + /** A class for array builders for arrays of reference types. + * + * This builder can be reused. + * + * @tparam T type of elements for the array builder, subtype of `AnyRef` with a `ClassTag` context bound. + */ + @SerialVersionUID(3L) + final class ofRef[T <: AnyRef](implicit ct: ClassTag[T]) extends ArrayBuilder[T] { + + protected var elems: Array[T] = _ + + private def mkArray(size: Int): Array[T] = { + if (capacity == size && capacity > 0) elems + else if (elems eq null) new Array[T](size) + else java.util.Arrays.copyOf[T](elems, size) + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def addOne(elem: T): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + def result(): Array[T] = { + if (capacity != 0 && capacity == size) { + capacity = 0 + val res = elems + elems = null + res + } + else mkArray(size) + } + + override def clear(): Unit = { + super.clear() + if(elems ne null) java.util.Arrays.fill(elems.asInstanceOf[Array[AnyRef]], null) + } + + override def equals(other: Any): Boolean = other match { + case x: ofRef[_] => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofRef" + } + + /** A class for array builders for arrays of `byte`s. It can be reused. */ + @SerialVersionUID(3L) + final class ofByte extends ArrayBuilder[Byte] { + + protected var elems: Array[Byte] = _ + + private def mkArray(size: Int): Array[Byte] = { + val newelems = new Array[Byte](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def addOne(elem: Byte): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + def result(): Array[Byte] = { + if (capacity != 0 && capacity == size) { + capacity = 0 + val res = elems + elems = null + res + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofByte => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofByte" + } + + /** A class for array builders for arrays of `short`s. It can be reused. */ + @SerialVersionUID(3L) + final class ofShort extends ArrayBuilder[Short] { + + protected var elems: Array[Short] = _ + + private def mkArray(size: Int): Array[Short] = { + val newelems = new Array[Short](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def addOne(elem: Short): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + def result(): Array[Short] = { + if (capacity != 0 && capacity == size) { + capacity = 0 + val res = elems + elems = null + res + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofShort => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofShort" + } + + /** A class for array builders for arrays of `char`s. It can be reused. */ + @SerialVersionUID(3L) + final class ofChar extends ArrayBuilder[Char] { + + protected var elems: Array[Char] = _ + + private def mkArray(size: Int): Array[Char] = { + val newelems = new Array[Char](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def addOne(elem: Char): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + def result(): Array[Char] = { + if (capacity != 0 && capacity == size) { + capacity = 0 + val res = elems + elems = null + res + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofChar => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofChar" + } + + /** A class for array builders for arrays of `int`s. It can be reused. */ + @SerialVersionUID(3L) + final class ofInt extends ArrayBuilder[Int] { + + protected var elems: Array[Int] = _ + + private def mkArray(size: Int): Array[Int] = { + val newelems = new Array[Int](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def addOne(elem: Int): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + def result(): Array[Int] = { + if (capacity != 0 && capacity == size) { + capacity = 0 + val res = elems + elems = null + res + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofInt => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofInt" + } + + /** A class for array builders for arrays of `long`s. It can be reused. */ + @SerialVersionUID(3L) + final class ofLong extends ArrayBuilder[Long] { + + protected var elems: Array[Long] = _ + + private def mkArray(size: Int): Array[Long] = { + val newelems = new Array[Long](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def addOne(elem: Long): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + def result(): Array[Long] = { + if (capacity != 0 && capacity == size) { + capacity = 0 + val res = elems + elems = null + res + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofLong => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofLong" + } + + /** A class for array builders for arrays of `float`s. It can be reused. */ + @SerialVersionUID(3L) + final class ofFloat extends ArrayBuilder[Float] { + + protected var elems: Array[Float] = _ + + private def mkArray(size: Int): Array[Float] = { + val newelems = new Array[Float](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def addOne(elem: Float): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + def result(): Array[Float] = { + if (capacity != 0 && capacity == size) { + capacity = 0 + val res = elems + elems = null + res + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofFloat => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofFloat" + } + + /** A class for array builders for arrays of `double`s. It can be reused. */ + @SerialVersionUID(3L) + final class ofDouble extends ArrayBuilder[Double] { + + protected var elems: Array[Double] = _ + + private def mkArray(size: Int): Array[Double] = { + val newelems = new Array[Double](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def addOne(elem: Double): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + def result(): Array[Double] = { + if (capacity != 0 && capacity == size) { + capacity = 0 + val res = elems + elems = null + res + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofDouble => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofDouble" + } + + /** A class for array builders for arrays of `boolean`s. It can be reused. */ + @SerialVersionUID(3L) + class ofBoolean extends ArrayBuilder[Boolean] { + + protected var elems: Array[Boolean] = _ + + private def mkArray(size: Int): Array[Boolean] = { + val newelems = new Array[Boolean](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def addOne(elem: Boolean): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + def result(): Array[Boolean] = { + if (capacity != 0 && capacity == size) { + capacity = 0 + val res = elems + elems = null + res + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofBoolean => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofBoolean" + } + + /** A class for array builders for arrays of `Unit` type. It can be reused. */ + @SerialVersionUID(3L) + final class ofUnit extends ArrayBuilder[Unit] { + + protected def elems: Array[Unit] = throw new UnsupportedOperationException() + + def addOne(elem: Unit): this.type = { + val newSize = size + 1 + ensureSize(newSize) + size = newSize + this + } + + override def addAll(xs: IterableOnce[Unit]): this.type = { + val newSize = size + xs.iterator.size + ensureSize(newSize) + size = newSize + this + } + + override def addAll(xs: Array[_ <: Unit], offset: Int, length: Int): this.type = { + val newSize = size + length + ensureSize(newSize) + size = newSize + this + } + + def result() = { + val ans = new Array[Unit](size) + var i = 0 + while (i < size) { ans(i) = (); i += 1 } + ans + } + + override def equals(other: Any): Boolean = other match { + case x: ofUnit => (size == x.size) + case _ => false + } + + protected[this] def resize(size: Int): Unit = capacity = size + + override def toString = "ArrayBuilder.ofUnit" + } +} diff --git a/library/src/scala/collection/mutable/ArrayDeque.scala b/library/src/scala/collection/mutable/ArrayDeque.scala new file mode 100644 index 000000000000..186df5b21c74 --- /dev/null +++ b/library/src/scala/collection/mutable/ArrayDeque.scala @@ -0,0 +1,641 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` +import scala.annotation.nowarn +import scala.collection.Stepper.EfficientSplit +import scala.collection.generic.{CommonErrors, DefaultSerializable} +import scala.reflect.ClassTag + +/** An implementation of a double-ended queue that internally uses a resizable circular buffer. + * + * Append, prepend, removeHead, removeLast and random-access (indexed-lookup and indexed-replacement) + * take amortized constant time. In general, removals and insertions at i-th index are O(min(i, n-i)) + * and thus insertions and removals from end/beginning are fast. + * + * @note Subclasses ''must'' override the `ofArray` protected method to return a more specific type. + * + * @tparam A the type of this ArrayDeque's elements. + * + * @define Coll `mutable.ArrayDeque` + * @define coll array deque + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +class ArrayDeque[A] protected ( + protected var array: Array[AnyRef], + private[ArrayDeque] var start: Int, + private[ArrayDeque] var end: Int +) extends AbstractBuffer[A] + with IndexedBuffer[A] + with IndexedSeqOps[A, ArrayDeque, ArrayDeque[A]] + with StrictOptimizedSeqOps[A, ArrayDeque, ArrayDeque[A]] + with IterableFactoryDefaults[A, ArrayDeque] + with ArrayDequeOps[A, ArrayDeque, ArrayDeque[A]] + with Cloneable[ArrayDeque[A]] + with DefaultSerializable { + + reset(array, start, end) + + private[this] def reset(array: Array[AnyRef], start: Int, end: Int) = { + assert((array.length & (array.length - 1)) == 0, s"Array.length must be power of 2") + requireBounds(idx = start, until = array.length) + requireBounds(idx = end, until = array.length) + this.array = array + this.start = start + this.end = end + } + + def this(initialSize: Int = ArrayDeque.DefaultInitialSize) = this(ArrayDeque.alloc(initialSize), start = 0, end = 0) + + override def knownSize: Int = super[IndexedSeqOps].knownSize + + // No-Op override to allow for more efficient stepper in a minor release. + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit = super.stepper(shape) + + def apply(idx: Int): A = { + requireBounds(idx) + _get(idx) + } + + def update(idx: Int, elem: A): Unit = { + requireBounds(idx) + _set(idx, elem) + } + + def addOne(elem: A): this.type = { + ensureSize(length + 1) + appendAssumingCapacity(elem) + } + + def prepend(elem: A): this.type = { + ensureSize(length + 1) + prependAssumingCapacity(elem) + } + + @inline private[ArrayDeque] def appendAssumingCapacity(elem: A): this.type = { + array(end) = elem.asInstanceOf[AnyRef] + end = end_+(1) + this + } + + @inline private[ArrayDeque] def prependAssumingCapacity(elem: A): this.type = { + start = start_-(1) + array(start) = elem.asInstanceOf[AnyRef] + this + } + + override def prependAll(elems: IterableOnce[A]): this.type = { + val it = elems.iterator + if (it.nonEmpty) { + val n = length + // The following code resizes the current collection at most once and traverses elems at most twice + elems.knownSize match { + // Size is too expensive to compute AND we can traverse it only once - can't do much but retry with an IndexedSeq + case srcLength if srcLength < 0 => prependAll(it.to(IndexedSeq: Factory[A, IndexedSeq[A]] /* type ascription needed by Dotty */)) + + // We know for sure we need to resize to hold everything, might as well resize and memcopy upfront + case srcLength if mustGrow(srcLength + n) => + val finalLength = srcLength + n + val array2 = ArrayDeque.alloc(finalLength) + @annotation.unused val copied = it.copyToArray(array2.asInstanceOf[Array[A]]) + //assert(copied == srcLength) + copySliceToArray(srcStart = 0, dest = array2, destStart = srcLength, maxItems = n) + reset(array = array2, start = 0, end = finalLength) + + // Just fill up from (start - srcLength) to (start - 1) and move back start + case srcLength => + // Optimized version of `elems.zipWithIndex.foreach((elem, i) => _set(i - srcLength, elem))` + var i = 0 + while(i < srcLength) { + _set(i - srcLength, it.next()) + i += 1 + } + start = start_-(srcLength) + } + } + this + } + + override def addAll(elems: IterableOnce[A]): this.type = { + elems.knownSize match { + case srcLength if srcLength > 0 => + ensureSize(srcLength + length) + elems.iterator.foreach(appendAssumingCapacity) + case _ => elems.iterator.foreach(+=) + } + this + } + + def insert(idx: Int, elem: A): Unit = { + requireBounds(idx, length+1) + val n = length + if (idx == 0) { + prepend(elem) + } else if (idx == n) { + addOne(elem) + } else { + val finalLength = n + 1 + if (mustGrow(finalLength)) { + val array2 = ArrayDeque.alloc(finalLength) + copySliceToArray(srcStart = 0, dest = array2, destStart = 0, maxItems = idx) + array2(idx) = elem.asInstanceOf[AnyRef] + copySliceToArray(srcStart = idx, dest = array2, destStart = idx + 1, maxItems = n) + reset(array = array2, start = 0, end = finalLength) + } else if (n <= idx * 2) { + var i = n - 1 + while(i >= idx) { + _set(i + 1, _get(i)) + i -= 1 + } + end = end_+(1) + i += 1 + _set(i, elem) + } else { + var i = 0 + while(i < idx) { + _set(i - 1, _get(i)) + i += 1 + } + start = start_-(1) + _set(i, elem) + } + } + } + + def insertAll(idx: Int, elems: IterableOnce[A]): Unit = { + requireBounds(idx, length+1) + val n = length + if (idx == 0) { + prependAll(elems) + } else if (idx == n) { + addAll(elems) + } else { + // Get both an iterator and the length of the source (by copying the source to an IndexedSeq if needed) + val (it, srcLength) = { + val _srcLength = elems.knownSize + if (_srcLength >= 0) (elems.iterator, _srcLength) + else { + val indexed = IndexedSeq.from(elems) + (indexed.iterator, indexed.size) + } + } + if (it.nonEmpty) { + val finalLength = srcLength + n + // Either we resize right away or move prefix left or suffix right + if (mustGrow(finalLength)) { + val array2 = ArrayDeque.alloc(finalLength) + copySliceToArray(srcStart = 0, dest = array2, destStart = 0, maxItems = idx) + @annotation.unused val copied = it.copyToArray(array2.asInstanceOf[Array[A]], idx) + //assert(copied == srcLength) + copySliceToArray(srcStart = idx, dest = array2, destStart = idx + srcLength, maxItems = n) + reset(array = array2, start = 0, end = finalLength) + } else if (2*idx >= n) { // Cheaper to shift the suffix right + var i = n - 1 + while(i >= idx) { + _set(i + srcLength, _get(i)) + i -= 1 + } + end = end_+(srcLength) + while(it.hasNext) { + i += 1 + _set(i, it.next()) + } + } else { // Cheaper to shift prefix left + var i = 0 + while(i < idx) { + _set(i - srcLength, _get(i)) + i += 1 + } + start = start_-(srcLength) + while(it.hasNext) { + _set(i, it.next()) + i += 1 + } + } + } + } + } + + def remove(idx: Int, count: Int): Unit = { + if (count > 0) { + requireBounds(idx) + val n = length + val removals = Math.min(n - idx, count) + val finalLength = n - removals + val suffixStart = idx + removals + // If we know we can resize after removing, do it right away using arrayCopy + // Else, choose the shorter: either move the prefix (0 until idx) right OR the suffix (idx+removals until n) left + if (shouldShrink(finalLength)) { + val array2 = ArrayDeque.alloc(finalLength) + copySliceToArray(srcStart = 0, dest = array2, destStart = 0, maxItems = idx) + copySliceToArray(srcStart = suffixStart, dest = array2, destStart = idx, maxItems = n) + reset(array = array2, start = 0, end = finalLength) + } else if (2*idx <= finalLength) { // Cheaper to move the prefix right + var i = suffixStart - 1 + while(i >= removals) { + _set(i, _get(i - removals)) + i -= 1 + } + while(i >= 0) { + _set(i, null.asInstanceOf[A]) + i -= 1 + } + start = start_+(removals) + } else { // Cheaper to move the suffix left + var i = idx + while(i < finalLength) { + _set(i, _get(i + removals)) + i += 1 + } + while(i < n) { + _set(i, null.asInstanceOf[A]) + i += 1 + } + end = end_-(removals) + } + } else { + require(count == 0, s"removing negative number of elements: $count") + } + } + + def remove(idx: Int): A = { + val elem = this(idx) + remove(idx, 1) + elem + } + + override def subtractOne(elem: A): this.type = { + val idx = indexOf(elem) + if (idx >= 0) remove(idx, 1) //TODO: SeqOps should be fluent API + this + } + + /** + * + * @param resizeInternalRepr If this is set, resize the internal representation to reclaim space once in a while + * @return + */ + def removeHeadOption(resizeInternalRepr: Boolean = false): Option[A] = + if (isEmpty) None else Some(removeHeadAssumingNonEmpty(resizeInternalRepr)) + + /** + * Unsafely remove the first element (throws exception when empty) + * See also removeHeadOption() + * + * @param resizeInternalRepr If this is set, resize the internal representation to reclaim space once in a while + * @throws NoSuchElementException when empty + * @return + */ + def removeHead(resizeInternalRepr: Boolean = false): A = + if (isEmpty) throw new NoSuchElementException(s"empty collection") else removeHeadAssumingNonEmpty(resizeInternalRepr) + + @inline private[this] def removeHeadAssumingNonEmpty(resizeInternalRepr: Boolean = false): A = { + val elem = array(start) + array(start) = null + start = start_+(1) + if (resizeInternalRepr) resize(length) + elem.asInstanceOf[A] + } + + /** + * + * @param resizeInternalRepr If this is set, resize the internal representation to reclaim space once in a while + * @return + */ + def removeLastOption(resizeInternalRepr: Boolean = false): Option[A] = + if (isEmpty) None else Some(removeLastAssumingNonEmpty(resizeInternalRepr)) + + /** + * Unsafely remove the last element (throws exception when empty) + * See also removeLastOption() + * + * @param resizeInternalRepr If this is set, resize the internal representation to reclaim space once in a while + * @throws NoSuchElementException when empty + * @return + */ + def removeLast(resizeInternalRepr: Boolean = false): A = + if (isEmpty) throw new NoSuchElementException(s"empty collection") else removeLastAssumingNonEmpty(resizeInternalRepr) + + @`inline` private[this] def removeLastAssumingNonEmpty(resizeInternalRepr: Boolean = false): A = { + end = end_-(1) + val elem = array(end) + array(end) = null + if (resizeInternalRepr) resize(length) + elem.asInstanceOf[A] + } + + /** + * Remove all elements from this collection and return the elements while emptying this data structure + * @return + */ + def removeAll(): scala.collection.immutable.Seq[A] = { + val elems = scala.collection.immutable.Seq.newBuilder[A] + elems.sizeHint(length) + while(nonEmpty) { + elems += removeHeadAssumingNonEmpty() + } + elems.result() + } + + /** + * Remove all elements from this collection and return the elements in reverse while emptying this data structure + * @return + */ + def removeAllReverse(): scala.collection.immutable.Seq[A] = { + val elems = scala.collection.immutable.Seq.newBuilder[A] + elems.sizeHint(length) + while(nonEmpty) { + elems += removeLastAssumingNonEmpty() + } + elems.result() + } + + /** + * Returns and removes all elements from the left of this queue which satisfy the given predicate + * + * @param f the predicate used for choosing elements + * @return + */ + def removeHeadWhile(f: A => Boolean): scala.collection.immutable.Seq[A] = { + val elems = scala.collection.immutable.Seq.newBuilder[A] + while(headOption.exists(f)) { + elems += removeHeadAssumingNonEmpty() + } + elems.result() + } + + /** + * Returns and removes all elements from the right of this queue which satisfy the given predicate + * + * @param f the predicate used for choosing elements + * @return + */ + def removeLastWhile(f: A => Boolean): scala.collection.immutable.Seq[A] = { + val elems = scala.collection.immutable.Seq.newBuilder[A] + while(lastOption.exists(f)) { + elems += removeLastAssumingNonEmpty() + } + elems.result() + } + + /** Returns the first element which satisfies the given predicate after or at some start index + * and removes this element from the collections + * + * @param p the predicate used for choosing the first element + * @param from the start index + * @return the first element of the queue for which p yields true + */ + def removeFirst(p: A => Boolean, from: Int = 0): Option[A] = { + val i = indexWhere(p, from) + if (i < 0) None else Some(remove(i)) + } + + /** Returns all elements in this collection which satisfy the given predicate + * and removes those elements from this collections. + * + * @param p the predicate used for choosing elements + * @return a sequence of all elements in the queue for which + * p yields true. + */ + def removeAll(p: A => Boolean): scala.collection.immutable.Seq[A] = { + val res = scala.collection.immutable.Seq.newBuilder[A] + var i, j = 0 + while (i < size) { + if (p(this(i))) { + res += this(i) + } else { + if (i != j) { + this(j) = this(i) + } + j += 1 + } + i += 1 + } + if (i != j) takeInPlace(j) + res.result() + } + + @inline def ensureSize(hint: Int) = if (hint > length && mustGrow(hint)) resize(hint) + + def length = end_-(start) + + override def isEmpty = start == end + + override protected def klone(): ArrayDeque[A] = new ArrayDeque(array.clone(), start = start, end = end) + + override def iterableFactory: SeqFactory[ArrayDeque] = ArrayDeque + + /** + * Note: This does not actually resize the internal representation. + * See clearAndShrink if you want to also resize internally + */ + def clear(): Unit = { + while(nonEmpty) { + removeHeadAssumingNonEmpty() + } + } + + /** + * Clears this buffer and shrinks to @param size + * + * @param size + * @return + */ + def clearAndShrink(size: Int = ArrayDeque.DefaultInitialSize): this.type = { + reset(array = ArrayDeque.alloc(size), start = 0, end = 0) + this + } + + protected def ofArray(array: Array[AnyRef], end: Int): ArrayDeque[A] = + new ArrayDeque[A](array, start = 0, end) + + override def copyToArray[B >: A](dest: Array[B], destStart: Int, len: Int): Int = { + val copied = IterableOnce.elemsToCopyToArray(length, dest.length, destStart, len) + if (copied > 0) { + copySliceToArray(srcStart = 0, dest = dest, destStart = destStart, maxItems = len) + } + copied + } + + override def toArray[B >: A: ClassTag]: Array[B] = + copySliceToArray(srcStart = 0, dest = new Array[B](length), destStart = 0, maxItems = length) + + /** + * Trims the capacity of this ArrayDeque's instance to be the current size + */ + def trimToSize(): Unit = resize(length) + + // Utils for common modular arithmetic: + @inline protected def start_+(idx: Int) = (start + idx) & (array.length - 1) + @inline private[this] def start_-(idx: Int) = (start - idx) & (array.length - 1) + @inline private[this] def end_+(idx: Int) = (end + idx) & (array.length - 1) + @inline private[this] def end_-(idx: Int) = (end - idx) & (array.length - 1) + + // Note: here be overflow dragons! This is used for int overflow + // assumptions in resize(). Use caution changing. + @inline private[this] def mustGrow(len: Int) = { + len >= array.length + } + + // Assumes that 0 <= len < array.length! + @inline private[this] def shouldShrink(len: Int) = { + // To avoid allocation churn, only shrink when array is large + // and less than 2/5 filled. + array.length > ArrayDeque.StableSize && array.length - len - (len >> 1) > len + } + + // Assumes that 0 <= len < array.length! + @inline private[this] def canShrink(len: Int) = { + array.length > ArrayDeque.DefaultInitialSize && array.length - len > len + } + + @inline private[this] def _get(idx: Int): A = array(start_+(idx)).asInstanceOf[A] + + @inline private[this] def _set(idx: Int, elem: A) = array(start_+(idx)) = elem.asInstanceOf[AnyRef] + + // Assumes that 0 <= len. + private[this] def resize(len: Int) = if (mustGrow(len) || canShrink(len)) { + val n = length + val array2 = copySliceToArray(srcStart = 0, dest = ArrayDeque.alloc(len), destStart = 0, maxItems = n) + reset(array = array2, start = 0, end = n) + } + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix = "ArrayDeque" +} + +/** + * $factoryInfo + * @define coll array deque + * @define Coll `ArrayDeque` + */ +@SerialVersionUID(3L) +object ArrayDeque extends StrictOptimizedSeqFactory[ArrayDeque] { + + def from[B](coll: collection.IterableOnce[B]): ArrayDeque[B] = { + val s = coll.knownSize + if (s >= 0) { + val array = alloc(s) + val actual = IterableOnce.copyElemsToArray(coll, array.asInstanceOf[Array[Any]]) + if (actual != s) throw new IllegalStateException(s"Copied $actual of $s") + new ArrayDeque[B](array, start = 0, end = s) + } else new ArrayDeque[B]() ++= coll + } + + def newBuilder[A]: Builder[A, ArrayDeque[A]] = + new GrowableBuilder[A, ArrayDeque[A]](empty) { + override def sizeHint(size: Int): Unit = { + elems.ensureSize(size) + } + } + + def empty[A]: ArrayDeque[A] = new ArrayDeque[A]() + + final val DefaultInitialSize = 16 + + /** + * We try to not repeatedly resize arrays smaller than this + */ + private[ArrayDeque] final val StableSize = 128 + + /** + * Allocates an array whose size is next power of 2 > `len` + * Largest possible len is 1<<30 - 1 + * + * @param len + * @return + */ + private[mutable] def alloc(len: Int) = { + require(len >= 0, s"Non-negative array size required") + val size = (1 << 31) >>> java.lang.Integer.numberOfLeadingZeros(len) << 1 + require(size >= 0, s"ArrayDeque too big - cannot allocate ArrayDeque of length $len") + new Array[AnyRef](Math.max(size, DefaultInitialSize)) + } +} + +transparent trait ArrayDequeOps[A, +CC[_], +C <: AnyRef] extends StrictOptimizedSeqOps[A, CC, C] { + protected def array: Array[AnyRef] + + final override def clone(): C = klone() + + protected def klone(): C + + protected def ofArray(array: Array[AnyRef], end: Int): C + + protected def start_+(idx: Int): Int + + @inline protected final def requireBounds(idx: Int, until: Int = length): Unit = + if (idx < 0 || idx >= until) + throw CommonErrors.indexOutOfBounds(index = idx, max = until - 1) + + /** + * This is a more general version of copyToArray - this also accepts a srcStart unlike copyToArray + * This copies maxItems elements from this collections srcStart to dest's destStart + * If we reach the end of either collections before we could copy maxItems, we simply stop copying + * + * @param dest + * @param srcStart + * @param destStart + * @param maxItems + */ + def copySliceToArray(srcStart: Int, dest: Array[_], destStart: Int, maxItems: Int): dest.type = { + requireBounds(destStart, dest.length+1) + val toCopy = Math.min(maxItems, Math.min(length - srcStart, dest.length - destStart)) + if (toCopy > 0) { + requireBounds(srcStart) + val startIdx = start_+(srcStart) + val block1 = Math.min(toCopy, array.length - startIdx) + Array.copy(src = array, srcPos = startIdx, dest = dest, destPos = destStart, length = block1) + val block2 = toCopy - block1 + if (block2 > 0) Array.copy(src = array, srcPos = 0, dest = dest, destPos = destStart + block1, length = block2) + } + dest + } + + override def reverse: C = { + val n = length + val arr = ArrayDeque.alloc(n) + var i = 0 + while(i < n) { + arr(i) = this(n - i - 1).asInstanceOf[AnyRef] + i += 1 + } + ofArray(arr, n) + } + + override def slice(from: Int, until: Int): C = { + val n = length + val left = Math.max(0, Math.min(n, from)) + val right = Math.max(0, Math.min(n, until)) + val len = right - left + if (len <= 0) { + empty + } else if (len >= n) { + klone() + } else { + val array2 = copySliceToArray(srcStart = left, dest = ArrayDeque.alloc(len), destStart = 0, maxItems = len) + ofArray(array2, len) + } + } + + override def sliding(@deprecatedName("window") size: Int, step: Int): Iterator[C] = + super.sliding(size = size, step = step) + + override def grouped(n: Int): Iterator[C] = sliding(n, n) +} diff --git a/library/src/scala/collection/mutable/ArraySeq.scala b/library/src/scala/collection/mutable/ArraySeq.scala new file mode 100644 index 000000000000..a6af2be04fff --- /dev/null +++ b/library/src/scala/collection/mutable/ArraySeq.scala @@ -0,0 +1,355 @@ +/* + * 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.collection +package mutable + +import scala.language.`2.13` +import java.util.Arrays +import scala.collection.Stepper.EfficientSplit +import scala.collection.convert.impl._ +import scala.reflect.ClassTag +import scala.util.hashing.MurmurHash3 + +/** + * A collection representing `Array[T]`. Unlike `ArrayBuffer` it is always backed by the same + * underlying `Array`, therefore it is not growable or shrinkable. + * + * @tparam T type of the elements in this wrapped array. + * + * @define Coll `ArraySeq` + * @define coll wrapped array + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +@SerialVersionUID(3L) +sealed abstract class ArraySeq[T] + extends AbstractSeq[T] + with IndexedSeq[T] + with IndexedSeqOps[T, ArraySeq, ArraySeq[T]] + with StrictOptimizedSeqOps[T, ArraySeq, ArraySeq[T]] + with Serializable { + + override def iterableFactory: scala.collection.SeqFactory[ArraySeq] = ArraySeq.untagged + + override protected def fromSpecific(coll: scala.collection.IterableOnce[T]): ArraySeq[T] = { + val b = ArrayBuilder.make(using elemTag).asInstanceOf[ArrayBuilder[T]] + b.sizeHint(coll, delta = 0) + b ++= coll + ArraySeq.make(b.result()) + } + override protected def newSpecificBuilder: Builder[T, ArraySeq[T]] = ArraySeq.newBuilder(using elemTag).asInstanceOf[Builder[T, ArraySeq[T]]] + override def empty: ArraySeq[T] = ArraySeq.empty(using elemTag.asInstanceOf[ClassTag[T]]) + + /** The tag of the element type. This does not have to be equal to the element type of this ArraySeq. A primitive + * ArraySeq can be backed by an array of boxed values and a reference ArraySeq can be backed by an array of a supertype + * or subtype of the element type. */ + def elemTag: ClassTag[_] + + /** Update element at given index */ + def update(@deprecatedName("idx", "2.13.0") index: Int, elem: T): Unit + + /** The underlying array. Its element type does not have to be equal to the element type of this ArraySeq. A primitive + * ArraySeq can be backed by an array of boxed values and a reference ArraySeq can be backed by an array of a supertype + * or subtype of the element type. */ + def array: Array[_] + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[T, S]): S with EfficientSplit + + override protected[this] def className = "ArraySeq" + + /** Clones this object, including the underlying Array. */ + override def clone(): ArraySeq[T] = ArraySeq.make(array.clone()).asInstanceOf[ArraySeq[T]] + + override def copyToArray[B >: T](xs: Array[B], start: Int, len: Int): Int = { + val copied = IterableOnce.elemsToCopyToArray(length, xs.length, start, len) + if(copied > 0) { + Array.copy(array, 0, xs, start, copied) + } + copied + } + + override def equals(other: Any): Boolean = other match { + case that: ArraySeq[_] if this.array.length != that.array.length => + false + case _ => + super.equals(other) + } + + override def sorted[B >: T](implicit ord: Ordering[B]): ArraySeq[T] = + ArraySeq.make(array.sorted(ord.asInstanceOf[Ordering[Any]])).asInstanceOf[ArraySeq[T]] + + override def sortInPlace[B >: T]()(implicit ord: Ordering[B]): this.type = { + if (length > 1) scala.util.Sorting.stableSort(array.asInstanceOf[Array[B]]) + this + } +} + +/** A companion object used to create instances of `ArraySeq`. + */ +@SerialVersionUID(3L) +object ArraySeq extends StrictOptimizedClassTagSeqFactory[ArraySeq] { self => + val untagged: SeqFactory[ArraySeq] = new ClassTagSeqFactory.AnySeqDelegate(self) + + // This is reused for all calls to empty. + private[this] val EmptyArraySeq = new ofRef[AnyRef](new Array[AnyRef](0)) + def empty[T : ClassTag]: ArraySeq[T] = EmptyArraySeq.asInstanceOf[ArraySeq[T]] + + def from[A : ClassTag](it: scala.collection.IterableOnce[A]): ArraySeq[A] = make(Array.from[A](it)) + + def newBuilder[A : ClassTag]: Builder[A, ArraySeq[A]] = ArrayBuilder.make[A].mapResult(make) + + /** + * Wrap an existing `Array` into a `ArraySeq` of the proper primitive specialization type + * without copying. + * + * Note that an array containing boxed primitives can be converted to a `ArraySeq` without + * copying. For example, `val a: Array[Any] = Array(1)` is an array of `Object` at runtime, + * containing `Integer`s. An `ArraySeq[Int]` can be obtained with a cast: + * `ArraySeq.make(a).asInstanceOf[ArraySeq[Int]]`. The values are still + * boxed, the resulting instance is an [[ArraySeq.ofRef]]. Writing + * `ArraySeq.make(a.asInstanceOf[Array[Int]])` does not work, it throws a `ClassCastException` + * at runtime. + */ + def make[T](x: Array[T]): ArraySeq[T] = ((x: @unchecked) match { + case null => null + case x: Array[AnyRef] => new ofRef[AnyRef](x) + case x: Array[Int] => new ofInt(x) + case x: Array[Double] => new ofDouble(x) + case x: Array[Long] => new ofLong(x) + case x: Array[Float] => new ofFloat(x) + case x: Array[Char] => new ofChar(x) + case x: Array[Byte] => new ofByte(x) + case x: Array[Short] => new ofShort(x) + case x: Array[Boolean] => new ofBoolean(x) + case x: Array[Unit] => new ofUnit(x) + }).asInstanceOf[ArraySeq[T]] + + @SerialVersionUID(3L) + final class ofRef[T <: AnyRef](val array: Array[T]) extends ArraySeq[T] { + def elemTag: ClassTag[T] = ClassTag[T](array.getClass.getComponentType) + def length: Int = array.length + def apply(index: Int): T = array(index) + def update(index: Int, elem: T): Unit = { array(index) = elem } + override def hashCode = MurmurHash3.arraySeqHash(array) + override def equals(that: Any) = that match { + case that: ofRef[_] => + Array.equals( + this.array.asInstanceOf[Array[AnyRef]], + that.array.asInstanceOf[Array[AnyRef]]) + case _ => super.equals(that) + } + override def iterator: Iterator[T] = new ArrayOps.ArrayIterator[T](array) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[T, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + new ObjectArrayStepper(array, 0, array.length) + else shape.parUnbox(new ObjectArrayStepper(array, 0, array.length).asInstanceOf[AnyStepper[T] with EfficientSplit]) + ).asInstanceOf[S with EfficientSplit] + } + + @SerialVersionUID(3L) + final class ofByte(val array: Array[Byte]) extends ArraySeq[Byte] { + // Type erases to `ManifestFactory.ByteManifest`, but can't annotate that because it's not accessible + def elemTag: ClassTag.Byte.type = ClassTag.Byte + def length: Int = array.length + def apply(index: Int): Byte = array(index) + def update(index: Int, elem: Byte): Unit = { array(index) = elem } + override def hashCode = MurmurHash3.arraySeqHash(array) + override def equals(that: Any) = that match { + case that: ofByte => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + override def iterator: Iterator[Byte] = new ArrayOps.ArrayIterator[Byte](array) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Byte, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + AnyStepper.ofParIntStepper(new WidenedByteArrayStepper(array, 0, array.length)) + else new WidenedByteArrayStepper(array, 0, array.length) + ).asInstanceOf[S with EfficientSplit] + } + + @SerialVersionUID(3L) + final class ofShort(val array: Array[Short]) extends ArraySeq[Short] { + // Type erases to `ManifestFactory.ShortManifest`, but can't annotate that because it's not accessible + def elemTag: ClassTag.Short.type = ClassTag.Short + def length: Int = array.length + def apply(index: Int): Short = array(index) + def update(index: Int, elem: Short): Unit = { array(index) = elem } + override def hashCode = MurmurHash3.arraySeqHash(array) + override def equals(that: Any) = that match { + case that: ofShort => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + override def iterator: Iterator[Short] = new ArrayOps.ArrayIterator[Short](array) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Short, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + AnyStepper.ofParIntStepper(new WidenedShortArrayStepper(array, 0, array.length)) + else new WidenedShortArrayStepper(array, 0, array.length) + ).asInstanceOf[S with EfficientSplit] + } + + @SerialVersionUID(3L) + final class ofChar(val array: Array[Char]) extends ArraySeq[Char] { + // Type erases to `ManifestFactory.CharManifest`, but can't annotate that because it's not accessible + def elemTag: ClassTag.Char.type = ClassTag.Char + def length: Int = array.length + def apply(index: Int): Char = array(index) + def update(index: Int, elem: Char): Unit = { array(index) = elem } + override def hashCode = MurmurHash3.arraySeqHash(array) + override def equals(that: Any) = that match { + case that: ofChar => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + override def iterator: Iterator[Char] = new ArrayOps.ArrayIterator[Char](array) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Char, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + AnyStepper.ofParIntStepper(new WidenedCharArrayStepper(array, 0, array.length)) + else new WidenedCharArrayStepper(array, 0, array.length) + ).asInstanceOf[S with EfficientSplit] + + override def addString(sb: StringBuilder, start: String, sep: String, end: String): sb.type = { + val jsb = sb.underlying + if (start.length != 0) jsb.append(start) + val len = array.length + if (len != 0) { + if (sep.isEmpty) jsb.append(array) + else { + jsb.ensureCapacity(jsb.length + len + end.length + (len - 1) * sep.length) + jsb.append(array(0)) + var i = 1 + while (i < len) { + jsb.append(sep) + jsb.append(array(i)) + i += 1 + } + } + } + if (end.length != 0) jsb.append(end) + sb + } + } + + @SerialVersionUID(3L) + final class ofInt(val array: Array[Int]) extends ArraySeq[Int] { + // Type erases to `ManifestFactory.IntManifest`, but can't annotate that because it's not accessible + def elemTag: ClassTag.Int.type = ClassTag.Int + def length: Int = array.length + def apply(index: Int): Int = array(index) + def update(index: Int, elem: Int): Unit = { array(index) = elem } + override def hashCode = MurmurHash3.arraySeqHash(array) + override def equals(that: Any) = that match { + case that: ofInt => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + override def iterator: Iterator[Int] = new ArrayOps.ArrayIterator[Int](array) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Int, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + AnyStepper.ofParIntStepper(new IntArrayStepper(array, 0, array.length)) + else new IntArrayStepper(array, 0, array.length) + ).asInstanceOf[S with EfficientSplit] + } + + @SerialVersionUID(3L) + final class ofLong(val array: Array[Long]) extends ArraySeq[Long] { + // Type erases to `ManifestFactory.LongManifest`, but can't annotate that because it's not accessible + def elemTag: ClassTag.Long.type = ClassTag.Long + def length: Int = array.length + def apply(index: Int): Long = array(index) + def update(index: Int, elem: Long): Unit = { array(index) = elem } + override def hashCode = MurmurHash3.arraySeqHash(array) + override def equals(that: Any) = that match { + case that: ofLong => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + override def iterator: Iterator[Long] = new ArrayOps.ArrayIterator[Long](array) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Long, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + AnyStepper.ofParLongStepper(new LongArrayStepper(array, 0, array.length)) + else new LongArrayStepper(array, 0, array.length) + ).asInstanceOf[S with EfficientSplit] + } + + @SerialVersionUID(3L) + final class ofFloat(val array: Array[Float]) extends ArraySeq[Float] { + // Type erases to `ManifestFactory.FloatManifest`, but can't annotate that because it's not accessible + def elemTag: ClassTag.Float.type = ClassTag.Float + def length: Int = array.length + def apply(index: Int): Float = array(index) + def update(index: Int, elem: Float): Unit = { array(index) = elem } + override def hashCode = MurmurHash3.arraySeqHash(array) + override def equals(that: Any) = that match { + case that: ofFloat => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + override def iterator: Iterator[Float] = new ArrayOps.ArrayIterator[Float](array) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Float, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + AnyStepper.ofParDoubleStepper(new WidenedFloatArrayStepper(array, 0, array.length)) + else new WidenedFloatArrayStepper(array, 0, array.length) + ).asInstanceOf[S with EfficientSplit] + } + + @SerialVersionUID(3L) + final class ofDouble(val array: Array[Double]) extends ArraySeq[Double] { + // Type erases to `ManifestFactory.DoubleManifest`, but can't annotate that because it's not accessible + def elemTag: ClassTag.Double.type = ClassTag.Double + def length: Int = array.length + def apply(index: Int): Double = array(index) + def update(index: Int, elem: Double): Unit = { array(index) = elem } + override def hashCode = MurmurHash3.arraySeqHash(array) + override def equals(that: Any) = that match { + case that: ofDouble => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + override def iterator: Iterator[Double] = new ArrayOps.ArrayIterator[Double](array) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Double, S]): S with EfficientSplit = ( + if(shape.shape == StepperShape.ReferenceShape) + AnyStepper.ofParDoubleStepper(new DoubleArrayStepper(array, 0, array.length)) + else new DoubleArrayStepper(array, 0, array.length) + ).asInstanceOf[S with EfficientSplit] + } + + @SerialVersionUID(3L) + final class ofBoolean(val array: Array[Boolean]) extends ArraySeq[Boolean] { + // Type erases to `ManifestFactory.BooleanManifest`, but can't annotate that because it's not accessible + def elemTag: ClassTag.Boolean.type = ClassTag.Boolean + def length: Int = array.length + def apply(index: Int): Boolean = array(index) + def update(index: Int, elem: Boolean): Unit = { array(index) = elem } + override def hashCode = MurmurHash3.arraySeqHash(array) + override def equals(that: Any) = that match { + case that: ofBoolean => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + override def iterator: Iterator[Boolean] = new ArrayOps.ArrayIterator[Boolean](array) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Boolean, S]): S with EfficientSplit = + new BoxedBooleanArrayStepper(array, 0, array.length).asInstanceOf[S with EfficientSplit] + } + + @SerialVersionUID(3L) + final class ofUnit(val array: Array[Unit]) extends ArraySeq[Unit] { + // Type erases to `ManifestFactory.UnitManifest`, but can't annotate that because it's not accessible + def elemTag: ClassTag.Unit.type = ClassTag.Unit + def length: Int = array.length + def apply(index: Int): Unit = array(index) + def update(index: Int, elem: Unit): Unit = { array(index) = elem } + override def hashCode = MurmurHash3.arraySeqHash(array) + override def equals(that: Any) = that match { + case that: ofUnit => array.length == that.array.length + case _ => super.equals(that) + } + override def iterator: Iterator[Unit] = new ArrayOps.ArrayIterator[Unit](array) + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[Unit, S]): S with EfficientSplit = + new ObjectArrayStepper[AnyRef](array.asInstanceOf[Array[AnyRef]], 0, array.length).asInstanceOf[S with EfficientSplit] + } +} diff --git a/library/src/scala/collection/mutable/BitSet.scala b/library/src/scala/collection/mutable/BitSet.scala new file mode 100644 index 000000000000..39124537758f --- /dev/null +++ b/library/src/scala/collection/mutable/BitSet.scala @@ -0,0 +1,393 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` +import scala.collection.immutable.Range +import BitSetOps.{LogWL, MaxSize} +import scala.annotation.implicitNotFound + +/** + * A class for mutable bitsets. + * + * $bitsetinfo + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html#mutable-bitsets "Scala's Collection Library overview"]] + * section on `Mutable Bitsets` for more information. + * + * @define Coll `BitSet` + * @define coll bitset + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +class BitSet(protected[collection] final var elems: Array[Long]) + extends AbstractSet[Int] + with SortedSet[Int] + with SortedSetOps[Int, SortedSet, BitSet] + with StrictOptimizedIterableOps[Int, Set, BitSet] + with StrictOptimizedSortedSetOps[Int, SortedSet, BitSet] + with collection.BitSet + with collection.BitSetOps[BitSet] + with Serializable { + + def this(initSize: Int) = this(new Array[Long](math.max((initSize + 63) >> 6, 1))) + + def this() = this(0) + + override protected def fromSpecific(coll: IterableOnce[Int]): BitSet = bitSetFactory.fromSpecific(coll) + override protected def newSpecificBuilder: Builder[Int, BitSet] = bitSetFactory.newBuilder + override def empty: BitSet = bitSetFactory.empty + + def bitSetFactory: BitSet.type = BitSet + + override def unsorted: Set[Int] = this + + protected[collection] final def nwords: Int = elems.length + + protected[collection] final def word(idx: Int): Long = + if (idx < nwords) elems(idx) else 0L + + protected[collection] def fromBitMaskNoCopy(elems: Array[Long]): BitSet = + if (elems.length == 0) empty + else new BitSet(elems) + + def addOne(elem: Int): this.type = { + require(elem >= 0) + if (!contains(elem)) { + val idx = elem >> LogWL + updateWord(idx, word(idx) | (1L << elem)) + } + this + } + + def subtractOne(elem: Int): this.type = { + require(elem >= 0) + if (contains(elem)) { + val idx = elem >> LogWL + updateWord(idx, word(idx) & ~(1L << elem)) + } + this + } + + def clear(): Unit = { + elems = new Array[Long](elems.length) + } + + protected final def updateWord(idx: Int, w: Long): Unit = { + ensureCapacity(idx) + elems(idx) = w + } + + protected final def ensureCapacity(idx: Int): Unit = { + require(idx < MaxSize) + if (idx >= nwords) { + var newlen = nwords + while (idx >= newlen) newlen = math.min(newlen * 2, MaxSize) + val elems1 = new Array[Long](newlen) + Array.copy(elems, 0, elems1, 0, nwords) + elems = elems1 + } + } + + def unconstrained: collection.Set[Int] = this + + /** Updates this bitset to the union with another bitset by performing a bitwise "or". + * + * @param other the bitset to form the union with. + * @return the bitset itself. + */ + def |= (other: collection.BitSet): this.type = { + ensureCapacity(other.nwords - 1) + var i = 0 + val othernwords = other.nwords + while (i < othernwords) { + elems(i) = elems(i) | other.word(i) + i += 1 + } + this + } + /** Updates this bitset to the intersection with another bitset by performing a bitwise "and". + * + * @param other the bitset to form the intersection with. + * @return the bitset itself. + */ + def &= (other: collection.BitSet): this.type = { + // Different from other operations: no need to ensure capacity because + // anything beyond the capacity is 0. Since we use other.word which is 0 + // off the end, we also don't need to make sure we stay in bounds there. + var i = 0 + val thisnwords = nwords + while (i < thisnwords) { + elems(i) = elems(i) & other.word(i) + i += 1 + } + this + } + /** Updates this bitset to the symmetric difference with another bitset by performing a bitwise "xor". + * + * @param other the bitset to form the symmetric difference with. + * @return the bitset itself. + */ + def ^= (other: collection.BitSet): this.type = { + ensureCapacity(other.nwords - 1) + var i = 0 + val othernwords = other.nwords + while (i < othernwords) { + + elems(i) = elems(i) ^ other.word(i) + i += 1 + } + this + } + /** Updates this bitset to the difference with another bitset by performing a bitwise "and-not". + * + * @param other the bitset to form the difference with. + * @return the bitset itself. + */ + def &~= (other: collection.BitSet): this.type = { + var i = 0 + val max = Math.min(nwords, other.nwords) + while (i < max) { + elems(i) = elems(i) & ~other.word(i) + i += 1 + } + this + } + + override def clone(): BitSet = new BitSet(java.util.Arrays.copyOf(elems, elems.length)) + + def toImmutable: immutable.BitSet = immutable.BitSet.fromBitMask(elems) + + override def map(f: Int => Int): BitSet = strictOptimizedMap(newSpecificBuilder, f) + override def map[B](f: Int => B)(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = + super[StrictOptimizedSortedSetOps].map(f) + + override def flatMap(f: Int => IterableOnce[Int]): BitSet = strictOptimizedFlatMap(newSpecificBuilder, f) + override def flatMap[B](f: Int => IterableOnce[B])(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = + super[StrictOptimizedSortedSetOps].flatMap(f) + + override def collect(pf: PartialFunction[Int, Int]): BitSet = strictOptimizedCollect(newSpecificBuilder, pf) + override def collect[B](pf: scala.PartialFunction[Int, B])(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = + super[StrictOptimizedSortedSetOps].collect(pf) + + // necessary for disambiguation + override def zip[B](that: IterableOnce[B])(implicit @implicitNotFound(collection.BitSet.zipOrdMsg) ev: Ordering[(Int, B)]): SortedSet[(Int, B)] = + super.zip(that) + + override def addAll(xs: IterableOnce[Int]): this.type = xs match { + case bs: collection.BitSet => + this |= bs + case range: Range => + if (range.nonEmpty) { + val start = range.min + if (start >= 0) { + val end = range.max + val endIdx = end >> LogWL + ensureCapacity(endIdx) + + if (range.step == 1 || range.step == -1) { + val startIdx = start >> LogWL + val wordStart = startIdx * BitSetOps.WordLength + val wordMask = -1L << (start - wordStart) + + if (endIdx > startIdx) { + elems(startIdx) |= wordMask + java.util.Arrays.fill(elems, startIdx + 1, endIdx, -1L) + elems(endIdx) |= -1L >>> (BitSetOps.WordLength - (end - endIdx * BitSetOps.WordLength) - 1) + } else elems(endIdx) |= (wordMask & (-1L >>> (BitSetOps.WordLength - (end - wordStart) - 1))) + } else super.addAll(range) + } else super.addAll(range) + } + this + + case sorted: collection.SortedSet[Int] => + // if `sorted` is using the regular Int ordering, ensure capacity for the largest + // element up front to avoid multiple resizing allocations + if (sorted.nonEmpty) { + val ord = sorted.ordering + if (ord eq Ordering.Int) { + ensureCapacity(sorted.lastKey >> LogWL) + } else if (ord eq Ordering.Int.reverse) { + ensureCapacity(sorted.firstKey >> LogWL) + } + val iter = sorted.iterator + while (iter.hasNext) { + addOne(iter.next()) + } + } + + this + + case other => + super.addAll(other) + } + + override def subsetOf(that: collection.Set[Int]): Boolean = that match { + case bs: collection.BitSet => + val thisnwords = this.nwords + val bsnwords = bs.nwords + val minWords = Math.min(thisnwords, bsnwords) + + // if any bits are set to `1` in words out of range of `bs`, then this is not a subset. Start there + var i = bsnwords + while (i < thisnwords) { + if (word(i) != 0L) return false + i += 1 + } + + // the higher range of `this` is all `0`s, fall back to lower range + var j = 0 + while (j < minWords) { + if ((word(j) & ~bs.word(j)) != 0L) return false + j += 1 + } + + true + case other => + super.subsetOf(other) + } + + override def subtractAll(xs: IterableOnce[Int]): this.type = xs match { + case bs: collection.BitSet => this &~= bs + case other => super.subtractAll(other) + } + + protected[this] def writeReplace(): AnyRef = new BitSet.SerializationProxy(this) + + override def diff(that: collection.Set[Int]): BitSet = that match { + case bs: collection.BitSet => + /* + * Algorithm: + * + * We iterate, word-by-word, backwards from the shortest of the two bitsets (this, or bs) i.e. the one with + * the fewer words. + * + * Array Shrinking: + * If `this` is not longer than `bs`, then since we must iterate through the full array of words, + * we can track the new highest index word which is non-zero, at little additional cost. At the end, the new + * Array[Long] allocated for the returned BitSet will only be of size `maxNonZeroIndex + 1` + */ + + val bsnwords = bs.nwords + val thisnwords = nwords + if (bsnwords >= thisnwords) { + // here, we may have opportunity to shrink the size of the array + // so, track the highest index which is non-zero. That ( + 1 ) will be our new array length + var i = thisnwords - 1 + var currentWord = 0L + + while (i >= 0 && currentWord == 0L) { + val oldWord = word(i) + currentWord = oldWord & ~bs.word(i) + i -= 1 + } + + if (i < 0) { + fromBitMaskNoCopy(Array(currentWord)) + } else { + val minimumNonZeroIndex: Int = i + 1 + val newArray = elems.take(minimumNonZeroIndex + 1) + newArray(i + 1) = currentWord + while (i >= 0) { + newArray(i) = word(i) & ~bs.word(i) + i -= 1 + } + fromBitMaskNoCopy(newArray) + } + } else { + // here, there is no opportunity to shrink the array size, no use in tracking highest non-zero index + val newElems = elems.clone() + var i = bsnwords - 1 + while (i >= 0) { + newElems(i) = word(i) & ~bs.word(i) + i -= 1 + } + fromBitMaskNoCopy(newElems) + } + case _ => super.diff(that) + } + + override def filterImpl(pred: Int => Boolean, isFlipped: Boolean): BitSet = { + // We filter the BitSet from highest to lowest, so we can determine exactly the highest non-zero word + // index which lets us avoid: + // * over-allocating -- the resulting array will be exactly the right size + // * multiple resizing allocations -- the array is allocated one time, not log(n) times. + var i = nwords - 1 + var newArray: Array[Long] = null + while (i >= 0) { + val w = BitSetOps.computeWordForFilter(pred, isFlipped, word(i), i) + if (w != 0L) { + if (newArray eq null) { + newArray = new Array(i + 1) + } + newArray(i) = w + } + i -= 1 + } + if (newArray eq null) { + empty + } else { + fromBitMaskNoCopy(newArray) + } + } + + override def filterInPlace(p: Int => Boolean): this.type = { + val thisnwords = nwords + var i = 0 + while (i < thisnwords) { + elems(i) = BitSetOps.computeWordForFilter(p, isFlipped = false, elems(i), i) + i += 1 + } + this + } + + override def toBitMask: Array[Long] = elems.clone() +} + +@SerialVersionUID(3L) +object BitSet extends SpecificIterableFactory[Int, BitSet] { + + def fromSpecific(it: scala.collection.IterableOnce[Int]): BitSet = Growable.from(empty, it) + + def empty: BitSet = new BitSet() + + def newBuilder: Builder[Int, BitSet] = new GrowableBuilder(empty) + + /** A bitset containing all the bits in an array */ + def fromBitMask(elems: Array[Long]): BitSet = { + val len = elems.length + if (len == 0) empty + else { + val a = java.util.Arrays.copyOf(elems, len) + new BitSet(a) + } + } + + /** A bitset containing all the bits in an array, wrapping the existing + * array without copying. + */ + def fromBitMaskNoCopy(elems: Array[Long]): BitSet = { + val len = elems.length + if (len == 0) empty + else new BitSet(elems) + } + + @SerialVersionUID(3L) + private final class SerializationProxy(coll: BitSet) extends scala.collection.BitSet.SerializationProxy(coll) { + protected[this] def readResolve(): Any = BitSet.fromBitMaskNoCopy(elems) + } +} diff --git a/library/src/scala/collection/mutable/Buffer.scala b/library/src/scala/collection/mutable/Buffer.scala new file mode 100644 index 000000000000..333d86fce772 --- /dev/null +++ b/library/src/scala/collection/mutable/Buffer.scala @@ -0,0 +1,319 @@ +/* + * 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.collection +package mutable + +import scala.language.`2.13` +import scala.annotation.nowarn + + +/** A `Buffer` is a growable and shrinkable `Seq`. + * + * @define coll buffer + * @define Coll `Buffer` + */ +trait Buffer[A] + extends Seq[A] + with SeqOps[A, Buffer, Buffer[A]] + with Growable[A] + with Shrinkable[A] + with IterableFactoryDefaults[A, Buffer] { + + override def iterableFactory: SeqFactory[Buffer] = Buffer + + override def knownSize: Int = super[Seq].knownSize + + //TODO Prepend is a logical choice for a readable name of `+=:` but it conflicts with the renaming of `append` to `add` + /** Prepends a single element at the front of this $coll. + * + * @param elem the element to $add. + * @return the $coll itself + */ + def prepend(elem: A): this.type + + /** Appends the given elements to this buffer. + * + * @param elem the element to append. + * @return this $coll + */ + @`inline` final def append(elem: A): this.type = addOne(elem) + + @deprecated("Use appendAll instead", "2.13.0") + @`inline` final def append(elems: A*): this.type = addAll(elems) + + /** Appends the elements contained in a iterable object to this buffer. + * @param elems the iterable object containing the elements to append. + * @return this $coll + */ + @`inline` final def appendAll(@deprecatedName("xs") elems: IterableOnce[A]): this.type = addAll(elems) + + /** Alias for `prepend` */ + @`inline` final def +=: (elem: A): this.type = prepend(elem) + + /** Prepends the elements contained in a iterable object to this buffer. + * @param elems the iterable object containing the elements to append. + * @return this $coll + */ + def prependAll(elems: IterableOnce[A]): this.type = { insertAll(0, elems); this } + + @deprecated("Use prependAll instead", "2.13.0") + @`inline` final def prepend(elems: A*): this.type = prependAll(elems) + + /** Alias for `prependAll` */ + @inline final def ++=:(elems: IterableOnce[A]): this.type = prependAll(elems) + + /** Inserts a new element at a given index into this buffer. + * + * @param idx the index where the new elements is inserted. + * @param elem the element to insert. + * @throws IndexOutOfBoundsException if the index `idx` is not in the valid range + * `0 <= idx <= length`. + */ + @throws[IndexOutOfBoundsException] + def insert(idx: Int, elem: A): Unit + + /** Inserts new elements at the index `idx`. Opposed to method + * `update`, this method will not replace an element with a new + * one. Instead, it will insert a new element at index `idx`. + * + * @param idx the index where a new element will be inserted. + * @param elems the iterable object providing all elements to insert. + * @throws IndexOutOfBoundsException if `idx` is out of bounds. + */ + @throws[IndexOutOfBoundsException] + def insertAll(idx: Int, elems: IterableOnce[A]): Unit + + /** Removes the element at a given index position. + * + * @param idx the index which refers to the element to delete. + * @return the element that was formerly at index `idx`. + */ + @throws[IndexOutOfBoundsException] + def remove(idx: Int): A + + /** Removes the element on a given index position. It takes time linear in + * the buffer size. + * + * @param idx the index which refers to the first element to remove. + * @param count the number of elements to remove. + * @throws IndexOutOfBoundsException if the index `idx` is not in the valid range + * `0 <= idx <= length - count` (with `count > 0`). + * @throws IllegalArgumentException if `count < 0`. + */ + @throws[IndexOutOfBoundsException] + @throws[IllegalArgumentException] + def remove(idx: Int, count: Int): Unit + + /** Removes a single element from this buffer, at its first occurrence. + * If the buffer does not contain that element, it is unchanged. + * + * @param x the element to remove. + * @return the buffer itself + */ + def subtractOne (x: A): this.type = { + val i = indexOf(x) + if (i != -1) remove(i) + this + } + + /** Removes the first ''n'' elements of this buffer. + * + * @param n the number of elements to remove from the beginning + * of this buffer. + */ + @deprecated("use dropInPlace instead", since = "2.13.4") + def trimStart(n: Int): Unit = dropInPlace(n) + + /** Removes the last ''n'' elements of this buffer. + * + * @param n the number of elements to remove from the end + * of this buffer. + */ + @deprecated("use dropRightInPlace instead", since = "2.13.4") + def trimEnd(n: Int): Unit = dropRightInPlace(n) + + /** Replaces a slice of elements in this $coll by another sequence of elements. + * + * Patching at negative indices is the same as patching starting at 0. + * Patching at indices at or larger than the length of the original $coll appends the patch to the end. + * If the `replaced` count would exceed the available elements, the difference in excess is ignored. + * + * @param from the index of the first replaced element + * @param patch the replacement sequence + * @param replaced the number of elements to drop in the original $coll + * @return this $coll + */ + def patchInPlace(from: Int, patch: scala.collection.IterableOnce[A], replaced: Int): this.type + + // +=, ++=, clear inherited from Growable + // Per remark of @ichoran, we should preferably not have these: + // + // def +=:(elem: A): this.type = { insert(0, elem); this } + // def +=:(elem1: A, elem2: A, elems: A*): this.type = elem1 +=: elem2 +=: elems ++=: this + // def ++=:(elems: IterableOnce[A]): this.type = { insertAll(0, elems); this } + + /** Removes the first `n` elements from this $coll. + * + * @param n the number of elements to remove + * @return this $coll + * + */ + def dropInPlace(n: Int): this.type = { remove(0, normalized(n)); this } + + /** Removes the last `n` elements from this $coll. + * + * @param n the number of elements to remove + * @return this $coll + * + */ + def dropRightInPlace(n: Int): this.type = { + val norm = normalized(n) + remove(length - norm, norm) + this + } + + /** Retains the first `n` elements from this $coll and removes the rest. + * + * @param n the number of elements to retain + * @return this $coll + * + */ + def takeInPlace(n: Int): this.type = { + val norm = normalized(n) + remove(norm, length - norm) + this + } + + /** Retains the last `n` elements from this $coll and removes the rest. + * + * @param n the number of elements to retain + * @return this $coll + * + */ + def takeRightInPlace(n: Int): this.type = { remove(0, length - normalized(n)); this } + + /** Retains the specified slice from this $coll and removes the rest. + * + * @param start the lowest index to include + * @param end the lowest index to exclude + * @return this $coll + * + */ + def sliceInPlace(start: Int, end: Int): this.type = takeInPlace(end).dropInPlace(start) + + private def normalized(n: Int): Int = math.min(math.max(n, 0), length) + + /** Drops the longest prefix of elements that satisfy a predicate. + * + * @param p The predicate used to test elements. + * @return this $coll + * @see [[dropWhile]] + */ + def dropWhileInPlace(p: A => Boolean): this.type = { + val idx = indexWhere(!p(_)) + if (idx < 0) { clear(); this } else dropInPlace(idx) + } + + /** Retains the longest prefix of elements that satisfy a predicate. + * + * @param p The predicate used to test elements. + * @return this $coll + * @see [[takeWhile]] + */ + def takeWhileInPlace(p: A => Boolean): this.type = { + val idx = indexWhere(!p(_)) + if (idx < 0) this else takeInPlace(idx) + } + + /** Append the given element to this $coll until a target length is reached. + * + * @param len the target length + * @param elem the padding value + * @return this $coll + */ + def padToInPlace(len: Int, elem: A): this.type = { + while (length < len) +=(elem) + this + } + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix = "Buffer" +} + +trait IndexedBuffer[A] extends IndexedSeq[A] + with IndexedSeqOps[A, IndexedBuffer, IndexedBuffer[A]] + with Buffer[A] + with IterableFactoryDefaults[A, IndexedBuffer] { + + override def iterableFactory: SeqFactory[IndexedBuffer] = IndexedBuffer + + /** Replace the contents of this $coll with the flatmapped result. + * + * @param f the mapping function + * @return this $coll + */ + def flatMapInPlace(f: A => IterableOnce[A]): this.type = { + // There's scope for a better implementation which copies elements in place. + var i = 0 + val s = size + val newElems = new Array[IterableOnce[A]](s) + while (i < s) { newElems(i) = f(this(i)); i += 1 } + clear() + i = 0 + while (i < s) { ++=(newElems(i)); i += 1 } + this + } + + /** Replace the contents of this $coll with the filtered result. + * + * @param f the filtering function + * @return this $coll + */ + def filterInPlace(p: A => Boolean): this.type = { + var i, j = 0 + while (i < size) { + if (p(apply(i))) { + if (i != j) { + this(j) = this(i) + } + j += 1 + } + i += 1 + } + + if (i == j) this else takeInPlace(j) + } + + def patchInPlace(from: Int, patch: scala.collection.IterableOnce[A], replaced: Int): this.type = { + val replaced0 = math.min(math.max(replaced, 0), length) + val i = math.min(math.max(from, 0), length) + var j = 0 + val iter = patch.iterator + while (iter.hasNext && j < replaced0 && i + j < length) { + update(i + j, iter.next()) + j += 1 + } + if (iter.hasNext) insertAll(i + j, iter) + else if (j < replaced0) remove(i + j, math.min(replaced0 - j, length - i - j)) + this + } +} + +@SerialVersionUID(3L) +object Buffer extends SeqFactory.Delegate[Buffer](ArrayBuffer) + +@SerialVersionUID(3L) +object IndexedBuffer extends SeqFactory.Delegate[IndexedBuffer](ArrayBuffer) + +/** Explicit instantiation of the `Buffer` trait to reduce class file size in subclasses. */ +abstract class AbstractBuffer[A] extends AbstractSeq[A] with Buffer[A] diff --git a/library/src/scala/collection/mutable/Builder.scala b/library/src/scala/collection/mutable/Builder.scala new file mode 100644 index 000000000000..c545929fa33f --- /dev/null +++ b/library/src/scala/collection/mutable/Builder.scala @@ -0,0 +1,105 @@ +/* + * 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.collection.mutable + +import scala.language.`2.13` + +/** Base trait for collection builders. + * + * After calling `result()` the behavior of a Builder (which is not also a [[scala.collection.mutable.ReusableBuilder]]) + * is undefined. No further methods should be called. It is common for mutable collections to be their own non-reusable + * Builder, in which case `result()` simply returns `this`. + * + * @see [[scala.collection.mutable.ReusableBuilder]] for Builders which can be reused after calling `result()` + */ +trait Builder[-A, +To] extends Growable[A] { self => + + /** Clears the contents of this builder. + * After execution of this method the builder will contain no elements. + */ + def clear(): Unit + + /** Result collection consisting of all elements appended so far. */ + def result(): To + + /** Gives a hint how many elements are expected to be added in total + * by the time `result` is called. + * + * Some builder classes will optimize their representation based on the hint. + * However, builder implementations are required to work correctly even if the hint is + * wrong, e.g., a different number of elements is added, or the hint is out of range. + * + * The default implementation simply ignores the hint. + * + * @param size the hint how many elements will be added. + */ + def sizeHint(size: Int): Unit = () + + /** Gives a hint that the `result` of this builder is expected + * to have the same size as the given collection, plus some delta. + * + * This method provides a hint only if the collection has a known size, + * as specified by the following pseudocode: + * + * {{{ + * if (coll.knownSize != -1) + * if (coll.knownSize + delta <= 0) sizeHint(0) + * else sizeHint(coll.knownSize + delta) + * }}} + * + * If the delta is negative and the result size is known to be negative, + * then the size hint is issued at zero. + * + * Some builder classes will optimize their representation based on the hint. + * However, builder implementations are required to work correctly even if the hint is + * wrong, i.e., if a different number of elements is added. + * + * @param coll the collection which serves as a hint for the result's size. + * @param delta a correction to add to the `coll.size` to produce the size hint (zero if omitted). + */ + final def sizeHint(coll: scala.collection.IterableOnce[_], delta: Int = 0): Unit = + coll.knownSize match { + case -1 => + case sz => sizeHint(0 max sz + delta) + } + + /** Gives a hint how many elements are expected to be added + * when the next `result` is called, together with an upper bound + * given by the size of some other collection. Some builder classes + * will optimize their representation based on the hint. However, + * builder implementations are still required to work correctly even if the hint is + * wrong, i.e. a different number of elements is added. + * + * @param size the hint how many elements will be added. + * @param boundingColl the bounding collection. If it is + * an IndexedSeqLike, then sizes larger + * than collection's size are reduced. + */ + // should probably be `boundingColl: IterableOnce[_]`, but binary compatibility + final def sizeHintBounded(size: Int, boundingColl: scala.collection.Iterable[_]): Unit = { + val s = boundingColl.knownSize + if (s != -1) { + sizeHint(scala.math.min(s, size)) + } + } + + /** A builder resulting from this builder by mapping the result using `f`. */ + def mapResult[NewTo](f: To => NewTo): Builder[A, NewTo] = new Builder[A, NewTo] { + def addOne(x: A): this.type = { self += x; this } + def clear(): Unit = self.clear() + override def addAll(xs: IterableOnce[A]): this.type = { self ++= xs; this } + override def sizeHint(size: Int): Unit = self.sizeHint(size) + def result(): NewTo = f(self.result()) + override def knownSize: Int = self.knownSize + } +} diff --git a/library/src/scala/collection/mutable/CheckedIndexedSeqView.scala b/library/src/scala/collection/mutable/CheckedIndexedSeqView.scala new file mode 100644 index 000000000000..720e8ce90b95 --- /dev/null +++ b/library/src/scala/collection/mutable/CheckedIndexedSeqView.scala @@ -0,0 +1,119 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` + +private[mutable] trait CheckedIndexedSeqView[+A] extends IndexedSeqView[A] { + protected val mutationCount: () => Int + + override def iterator: Iterator[A] = new CheckedIndexedSeqView.CheckedIterator(this, mutationCount()) + override def reverseIterator: Iterator[A] = new CheckedIndexedSeqView.CheckedReverseIterator(this, mutationCount()) + + override def appended[B >: A](elem: B): IndexedSeqView[B] = new CheckedIndexedSeqView.Appended(this, elem)(mutationCount) + override def prepended[B >: A](elem: B): IndexedSeqView[B] = new CheckedIndexedSeqView.Prepended(elem, this)(mutationCount) + override def take(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.Take(this, n)(mutationCount) + override def takeRight(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.TakeRight(this, n)(mutationCount) + override def drop(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.Drop(this, n)(mutationCount) + override def dropRight(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.DropRight(this, n)(mutationCount) + override def map[B](f: A => B): IndexedSeqView[B] = new CheckedIndexedSeqView.Map(this, f)(mutationCount) + override def reverse: IndexedSeqView[A] = new CheckedIndexedSeqView.Reverse(this)(mutationCount) + override def slice(from: Int, until: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.Slice(this, from, until)(mutationCount) + override def tapEach[U](f: A => U): IndexedSeqView[A] = new CheckedIndexedSeqView.Map(this, { (a: A) => f(a); a})(mutationCount) + + override def concat[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new CheckedIndexedSeqView.Concat(this, suffix)(mutationCount) + override def appendedAll[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new CheckedIndexedSeqView.Concat(this, suffix)(mutationCount) + override def prependedAll[B >: A](prefix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new CheckedIndexedSeqView.Concat(prefix, this)(mutationCount) +} + +private[mutable] object CheckedIndexedSeqView { + import IndexedSeqView.SomeIndexedSeqOps + + @SerialVersionUID(3L) + private[mutable] class CheckedIterator[A](self: IndexedSeqView[A], mutationCount: => Int) + extends IndexedSeqView.IndexedSeqViewIterator[A](self) { + private[this] val expectedCount = mutationCount + override def hasNext: Boolean = { + MutationTracker.checkMutationsForIteration(expectedCount, mutationCount) + super.hasNext + } + } + + @SerialVersionUID(3L) + private[mutable] class CheckedReverseIterator[A](self: IndexedSeqView[A], mutationCount: => Int) + extends IndexedSeqView.IndexedSeqViewReverseIterator[A](self) { + private[this] val expectedCount = mutationCount + override def hasNext: Boolean = { + MutationTracker.checkMutationsForIteration(expectedCount, mutationCount) + super.hasNext + } + } + + @SerialVersionUID(3L) + class Id[+A](underlying: SomeIndexedSeqOps[A])(protected val mutationCount: () => Int) + extends IndexedSeqView.Id(underlying) with CheckedIndexedSeqView[A] + + @SerialVersionUID(3L) + class Appended[+A](underlying: SomeIndexedSeqOps[A], elem: A)(protected val mutationCount: () => Int) + extends IndexedSeqView.Appended(underlying, elem) with CheckedIndexedSeqView[A] + + @SerialVersionUID(3L) + class Prepended[+A](elem: A, underlying: SomeIndexedSeqOps[A])(protected val mutationCount: () => Int) + extends IndexedSeqView.Prepended(elem, underlying) with CheckedIndexedSeqView[A] + + @SerialVersionUID(3L) + class Concat[A](prefix: SomeIndexedSeqOps[A], suffix: SomeIndexedSeqOps[A])(protected val mutationCount: () => Int) + extends IndexedSeqView.Concat[A](prefix, suffix) with CheckedIndexedSeqView[A] + + @SerialVersionUID(3L) + class Take[A](underlying: SomeIndexedSeqOps[A], n: Int)(protected val mutationCount: () => Int) + extends IndexedSeqView.Take(underlying, n) with CheckedIndexedSeqView[A] + + @SerialVersionUID(3L) + class TakeRight[A](underlying: SomeIndexedSeqOps[A], n: Int)(protected val mutationCount: () => Int) + extends IndexedSeqView.TakeRight(underlying, n) with CheckedIndexedSeqView[A] + + @SerialVersionUID(3L) + class Drop[A](underlying: SomeIndexedSeqOps[A], n: Int)(protected val mutationCount: () => Int) + extends IndexedSeqView.Drop[A](underlying, n) with CheckedIndexedSeqView[A] + + @SerialVersionUID(3L) + class DropRight[A](underlying: SomeIndexedSeqOps[A], n: Int)(protected val mutationCount: () => Int) + extends IndexedSeqView.DropRight[A](underlying, n) with CheckedIndexedSeqView[A] + + @SerialVersionUID(3L) + class Map[A, B](underlying: SomeIndexedSeqOps[A], f: A => B)(protected val mutationCount: () => Int) + extends IndexedSeqView.Map(underlying, f) with CheckedIndexedSeqView[B] + + @SerialVersionUID(3L) + class Reverse[A](underlying: SomeIndexedSeqOps[A])(protected val mutationCount: () => Int) + extends IndexedSeqView.Reverse[A](underlying) with CheckedIndexedSeqView[A] { + override def reverse: IndexedSeqView[A] = underlying match { + case x: IndexedSeqView[A] => x + case _ => super.reverse + } + } + + @SerialVersionUID(3L) + class Slice[A](underlying: SomeIndexedSeqOps[A], from: Int, until: Int)(protected val mutationCount: () => Int) + extends AbstractIndexedSeqView[A] with CheckedIndexedSeqView[A] { + protected val lo = from max 0 + protected val hi = (until max 0) min underlying.length + protected val len = (hi - lo) max 0 + @throws[IndexOutOfBoundsException] + def apply(i: Int): A = underlying(lo + i) + def length: Int = len + } +} diff --git a/library/src/scala/collection/mutable/Cloneable.scala b/library/src/scala/collection/mutable/Cloneable.scala new file mode 100644 index 000000000000..7480b5bae613 --- /dev/null +++ b/library/src/scala/collection/mutable/Cloneable.scala @@ -0,0 +1,23 @@ +/* + * 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.collection.mutable + +import scala.language.`2.13` + +/** A trait for cloneable collections. + * + * @tparam C Type of the collection, covariant and with reference types as upperbound. + */ +trait Cloneable[+C <: AnyRef] extends scala.Cloneable { + override def clone(): C = super.clone().asInstanceOf[C] +} diff --git a/library/src/scala/collection/mutable/CollisionProofHashMap.scala b/library/src/scala/collection/mutable/CollisionProofHashMap.scala new file mode 100644 index 000000000000..18d469c42900 --- /dev/null +++ b/library/src/scala/collection/mutable/CollisionProofHashMap.scala @@ -0,0 +1,888 @@ +/* + * 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.collection +package mutable + +import scala.language.`2.13` +import scala.{unchecked => uc} +import scala.annotation.{implicitNotFound, tailrec, unused} +import scala.annotation.unchecked.uncheckedVariance +import scala.collection.generic.DefaultSerializationProxy +import scala.runtime.Statics + +/** This class implements mutable maps using a hashtable with red-black trees in the buckets for good + * worst-case performance on hash collisions. An `Ordering` is required for the element type. Equality + * as determined by the `Ordering` has to be consistent with `equals` and `hashCode`. Universal equality + * of numeric types is not supported (similar to `AnyRefMap`). + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html#hash-tables "Scala's Collection Library overview"]] + * section on `Hash Tables` for more information. + * + * @define Coll `mutable.CollisionProofHashMap` + * @define coll mutable collision-proof hash map + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +final class CollisionProofHashMap[K, V](initialCapacity: Int, loadFactor: Double)(implicit ordering: Ordering[K]) + extends AbstractMap[K, V] + with MapOps[K, V, Map, CollisionProofHashMap[K, V]] //-- + with StrictOptimizedIterableOps[(K, V), Iterable, CollisionProofHashMap[K, V]] + with StrictOptimizedMapOps[K, V, Map, CollisionProofHashMap[K, V]] { //-- + + private[this] final def sortedMapFactory: SortedMapFactory[CollisionProofHashMap] = CollisionProofHashMap + + def this()(implicit ordering: Ordering[K]) = this(CollisionProofHashMap.defaultInitialCapacity, CollisionProofHashMap.defaultLoadFactor)(ordering) + + import CollisionProofHashMap.Node + private[this] type RBNode = CollisionProofHashMap.RBNode[K, V] + private[this] type LLNode = CollisionProofHashMap.LLNode[K, V] + + /** The actual hash table. */ + private[this] var table: Array[Node] = new Array[Node](tableSizeFor(initialCapacity)) + + /** The next size value at which to resize (capacity * load factor). */ + private[this] var threshold: Int = newThreshold(table.length) + + private[this] var contentSize = 0 + + override def size: Int = contentSize + + @`inline` private[this] final def computeHash(o: K): Int = { + val h = if(o.asInstanceOf[AnyRef] eq null) 0 else o.hashCode + h ^ (h >>> 16) + } + + @`inline` private[this] final def index(hash: Int) = hash & (table.length - 1) + + override protected def fromSpecific(coll: IterableOnce[(K, V)] @uncheckedVariance): CollisionProofHashMap[K, V] @uncheckedVariance = CollisionProofHashMap.from(coll) + override protected def newSpecificBuilder: Builder[(K, V), CollisionProofHashMap[K, V]] @uncheckedVariance = CollisionProofHashMap.newBuilder[K, V] + + override def empty: CollisionProofHashMap[K, V] = new CollisionProofHashMap[K, V] + + override def contains(key: K): Boolean = findNode(key) ne null + + def get(key: K): Option[V] = findNode(key) match { + case null => None + case nd => Some(nd match { + case nd: LLNode @uc => nd.value + case nd: RBNode @uc => nd.value + }) + } + + @throws[NoSuchElementException] + override def apply(key: K): V = findNode(key) match { + case null => default(key) + case nd => nd match { + case nd: LLNode @uc => nd.value + case nd: RBNode @uc => nd.value + } + } + + override def getOrElse[V1 >: V](key: K, default: => V1): V1 = { + val nd = findNode(key) + if (nd eq null) default else nd match { + case nd: LLNode @uc => nd.value + case n => n.asInstanceOf[RBNode].value + } + } + + @`inline` private[this] def findNode(elem: K): Node = { + val hash = computeHash(elem) + table(index(hash)) match { + case null => null + case n: LLNode @uc => n.getNode(elem, hash) + case n => n.asInstanceOf[RBNode].getNode(elem, hash) + } + } + + override def sizeHint(size: Int): Unit = { + val target = tableSizeFor(((size + 1).toDouble / loadFactor).toInt) + if(target > table.length) { + if(size == 0) reallocTable(target) + else growTable(target) + } + } + + override def update(key: K, value: V): Unit = put0(key, value, getOld = false) + + override def put(key: K, value: V): Option[V] = put0(key, value, getOld = true) match { + case null => None + case sm => sm + } + + def addOne(elem: (K, V)): this.type = { put0(elem._1, elem._2, getOld = false); this } + + @`inline` private[this] def put0(key: K, value: V, getOld: Boolean): Some[V] = { + if(contentSize + 1 >= threshold) growTable(table.length * 2) + val hash = computeHash(key) + val idx = index(hash) + put0(key, value, getOld, hash, idx) + } + + private[this] def put0(key: K, value: V, getOld: Boolean, hash: Int, idx: Int): Some[V] = { + val res = table(idx) match { + case n: RBNode @uc => + insert(n, idx, key, hash, value) + case _old => + val old: LLNode = _old.asInstanceOf[LLNode] + if(old eq null) { + table(idx) = new LLNode(key, hash, value, null) + } else { + var remaining = CollisionProofHashMap.treeifyThreshold + var prev: LLNode = null + var n = old + while((n ne null) && n.hash <= hash && remaining > 0) { + if(n.hash == hash && key == n.key) { + val old = n.value + n.value = value + return (if(getOld) Some(old) else null) + } + prev = n + n = n.next + remaining -= 1 + } + if(remaining == 0) { + treeify(old, idx) + return put0(key, value, getOld, hash, idx) + } + if(prev eq null) table(idx) = new LLNode(key, hash, value, old) + else prev.next = new LLNode(key, hash, value, prev.next) + } + true + } + if(res) contentSize += 1 + if(res) Some(null.asInstanceOf[V]) else null //TODO + } + + private[this] def treeify(old: LLNode, idx: Int): Unit = { + table(idx) = CollisionProofHashMap.leaf(old.key, old.hash, old.value, red = false, null) + var n: LLNode = old.next + while(n ne null) { + val root = table(idx).asInstanceOf[RBNode] + insertIntoExisting(root, idx, n.key, n.hash, n.value, root) + n = n.next + } + } + + override def addAll(xs: IterableOnce[(K, V)]): this.type = { + sizeHint(xs, delta = contentSize) + super.addAll(xs) + } + + // returns the old value or Statics.pfMarker if not found + private[this] def remove0(elem: K) : Any = { + val hash = computeHash(elem) + val idx = index(hash) + table(idx) match { + case null => Statics.pfMarker + case t: RBNode @uc => + val v = delete(t, idx, elem, hash) + if(v.asInstanceOf[AnyRef] ne Statics.pfMarker) contentSize -= 1 + v + case nd: LLNode @uc if nd.hash == hash && nd.key == elem => + // first element matches + table(idx) = nd.next + contentSize -= 1 + nd.value + case nd: LLNode @uc => + // find an element that matches + var prev = nd + var next = nd.next + while((next ne null) && next.hash <= hash) { + if(next.hash == hash && next.key == elem) { + prev.next = next.next + contentSize -= 1 + return next.value + } + prev = next + next = next.next + } + Statics.pfMarker + } + } + + private[this] abstract class MapIterator[R] extends AbstractIterator[R] { + protected[this] def extract(node: LLNode): R + protected[this] def extract(node: RBNode): R + + private[this] var i = 0 + private[this] var node: Node = null + private[this] val len = table.length + + def hasNext: Boolean = { + if(node ne null) true + else { + while(i < len) { + val n = table(i) + i += 1 + n match { + case null => + case n: RBNode @uc => + node = CollisionProofHashMap.minNodeNonNull(n) + return true + case n: LLNode @uc => + node = n + return true + } + } + false + } + } + + def next(): R = + if(!hasNext) Iterator.empty.next() + else node match { + case n: RBNode @uc => + val r = extract(n) + node = CollisionProofHashMap.successor(n ) + r + case n: LLNode @uc => + val r = extract(n) + node = n.next + r + } + } + + override def keysIterator: Iterator[K] = { + if (isEmpty) Iterator.empty + else new MapIterator[K] { + protected[this] def extract(node: LLNode) = node.key + protected[this] def extract(node: RBNode) = node.key + } + } + + override def iterator: Iterator[(K, V)] = { + if (isEmpty) Iterator.empty + else new MapIterator[(K, V)] { + protected[this] def extract(node: LLNode) = (node.key, node.value) + protected[this] def extract(node: RBNode) = (node.key, node.value) + } + } + + private[this] def growTable(newlen: Int) = { + var oldlen = table.length + table = java.util.Arrays.copyOf(table, newlen) + threshold = newThreshold(table.length) + while(oldlen < newlen) { + var i = 0 + while (i < oldlen) { + val old = table(i) + if(old ne null) splitBucket(old, i, i + oldlen, oldlen) + i += 1 + } + oldlen *= 2 + } + } + + @`inline` private[this] def reallocTable(newlen: Int) = { + table = new Array(newlen) + threshold = newThreshold(table.length) + } + + @`inline` private[this] def splitBucket(tree: Node, lowBucket: Int, highBucket: Int, mask: Int): Unit = tree match { + case t: LLNode @uc => splitBucket(t, lowBucket, highBucket, mask) + case t: RBNode @uc => splitBucket(t, lowBucket, highBucket, mask) + } + + private[this] def splitBucket(list: LLNode, lowBucket: Int, highBucket: Int, mask: Int): Unit = { + val preLow: LLNode = new LLNode(null.asInstanceOf[K], 0, null.asInstanceOf[V], null) + val preHigh: LLNode = new LLNode(null.asInstanceOf[K], 0, null.asInstanceOf[V], null) + //preLow.next = null + //preHigh.next = null + var lastLow: LLNode = preLow + var lastHigh: LLNode = preHigh + var n = list + while(n ne null) { + val next = n.next + if((n.hash & mask) == 0) { // keep low + lastLow.next = n + lastLow = n + } else { // move to high + lastHigh.next = n + lastHigh = n + } + n = next + } + lastLow.next = null + if(list ne preLow.next) table(lowBucket) = preLow.next + if(preHigh.next ne null) { + table(highBucket) = preHigh.next + lastHigh.next = null + } + } + + private[this] def splitBucket(tree: RBNode, lowBucket: Int, highBucket: Int, mask: Int): Unit = { + var lowCount, highCount = 0 + tree.foreachNode((n: RBNode) => if((n.hash & mask) != 0) highCount += 1 else lowCount += 1) + if(highCount != 0) { + if(lowCount == 0) { + table(lowBucket) = null + table(highBucket) = tree + } else { + table(lowBucket) = fromNodes(new CollisionProofHashMap.RBNodesIterator(tree).filter(n => (n.hash & mask) == 0), lowCount) + table(highBucket) = fromNodes(new CollisionProofHashMap.RBNodesIterator(tree).filter(n => (n.hash & mask) != 0), highCount) + } + } + } + + private[this] def tableSizeFor(capacity: Int) = + (Integer.highestOneBit((capacity-1).max(4))*2).min(1 << 30) + + private[this] def newThreshold(size: Int) = (size.toDouble * loadFactor).toInt + + override def clear(): Unit = { + java.util.Arrays.fill(table.asInstanceOf[Array[AnyRef]], null) + contentSize = 0 + } + + override def remove(key: K): Option[V] = { + val v = remove0(key) + if(v.asInstanceOf[AnyRef] eq Statics.pfMarker) None else Some(v.asInstanceOf[V]) + } + + def subtractOne(elem: K): this.type = { remove0(elem); this } + + override def knownSize: Int = size + + override def isEmpty: Boolean = size == 0 + + override def foreach[U](f: ((K, V)) => U): Unit = { + val len = table.length + var i = 0 + while(i < len) { + val n = table(i) + if(n ne null) n match { + case n: LLNode @uc => n.foreach(f) + case n: RBNode @uc => n.foreach(f) + } + i += 1 + } + } + + override def foreachEntry[U](f: (K, V) => U): Unit = { + val len = table.length + var i = 0 + while(i < len) { + val n = table(i) + if(n ne null) n match { + case n: LLNode @uc => n.foreachEntry(f) + case n: RBNode @uc => n.foreachEntry(f) + } + i += 1 + } + } + + protected[this] def writeReplace(): AnyRef = new DefaultSerializationProxy(new CollisionProofHashMap.DeserializationFactory[K, V](table.length, loadFactor, ordering), this) + + override protected[this] def className = "CollisionProofHashMap" + + override def getOrElseUpdate(key: K, defaultValue: => V): V = { + val hash = computeHash(key) + val idx = index(hash) + table(idx) match { + case null => () + case n: LLNode @uc => + val nd = n.getNode(key, hash) + if(nd != null) return nd.value + case n => + val nd = n.asInstanceOf[RBNode].getNode(key, hash) + if(nd != null) return nd.value + } + val table0 = table + val default = defaultValue + if(contentSize + 1 >= threshold) growTable(table.length * 2) + // Avoid recomputing index if the `defaultValue()` or new element hasn't triggered a table resize. + val newIdx = if (table0 eq table) idx else index(hash) + put0(key, default, getOld = false, hash, newIdx) + default + } + + ///////////////////// Overrides code from SortedMapOps + + /** Builds a new `CollisionProofHashMap` by applying a function to all elements of this $coll. + * + * @param f the function to apply to each element. + * @return a new $coll resulting from applying the given function + * `f` to each element of this $coll and collecting the results. + */ + def map[K2, V2](f: ((K, V)) => (K2, V2)) + (implicit @implicitNotFound(CollisionProofHashMap.ordMsg) ordering: Ordering[K2]): CollisionProofHashMap[K2, V2] = + sortedMapFactory.from(new View.Map[(K, V), (K2, V2)](this, f)) + + /** Builds a new `CollisionProofHashMap` by applying a function to all elements of this $coll + * and using the elements of the resulting collections. + * + * @param f the function to apply to each element. + * @return a new $coll resulting from applying the given collection-valued function + * `f` to each element of this $coll and concatenating the results. + */ + def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]) + (implicit @implicitNotFound(CollisionProofHashMap.ordMsg) ordering: Ordering[K2]): CollisionProofHashMap[K2, V2] = + sortedMapFactory.from(new View.FlatMap(this, f)) + + /** Builds a new sorted map by applying a partial function to all elements of this $coll + * on which the function is defined. + * + * @param pf the partial function which filters and maps the $coll. + * @return a new $coll resulting from applying the given partial function + * `pf` to each element on which it is defined and collecting the results. + * The order of the elements is preserved. + */ + def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]) + (implicit @implicitNotFound(CollisionProofHashMap.ordMsg) ordering: Ordering[K2]): CollisionProofHashMap[K2, V2] = + sortedMapFactory.from(new View.Collect(this, pf)) + + override def concat[V2 >: V](suffix: IterableOnce[(K, V2)]): CollisionProofHashMap[K, V2] = sortedMapFactory.from(suffix match { + case it: Iterable[(K, V2)] => new View.Concat(this, it) + case _ => iterator.concat(suffix.iterator) + }) + + /** Alias for `concat` */ + @`inline` override final def ++ [V2 >: V](xs: IterableOnce[(K, V2)]): CollisionProofHashMap[K, V2] = concat(xs) + + @deprecated("Consider requiring an immutable Map or fall back to Map.concat", "2.13.0") + override def + [V1 >: V](kv: (K, V1)): CollisionProofHashMap[K, V1] = + sortedMapFactory.from(new View.Appended(this, kv)) + + @deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0") + override def + [V1 >: V](elem1: (K, V1), elem2: (K, V1), elems: (K, V1)*): CollisionProofHashMap[K, V1] = + sortedMapFactory.from(new View.Concat(new View.Appended(new View.Appended(this, elem1), elem2), elems)) + + ///////////////////// RedBlackTree code derived from mutable.RedBlackTree: + + @`inline` private[this] def isRed(node: RBNode) = (node ne null) && node.red + @`inline` private[this] def isBlack(node: RBNode) = (node eq null) || !node.red + + @unused @`inline` private[this] def compare(key: K, hash: Int, node: LLNode): Int = { + val i = hash - node.hash + if(i != 0) i else ordering.compare(key, node.key) + } + + @`inline` private[this] def compare(key: K, hash: Int, node: RBNode): Int = { + /*val i = hash - node.hash + if(i != 0) i else*/ ordering.compare(key, node.key) + } + + // ---- insertion ---- + + @tailrec private[this] final def insertIntoExisting(_root: RBNode, bucket: Int, key: K, hash: Int, value: V, x: RBNode): Boolean = { + val cmp = compare(key, hash, x) + if(cmp == 0) { + x.value = value + false + } else { + val next = if(cmp < 0) x.left else x.right + if(next eq null) { + val z = CollisionProofHashMap.leaf(key, hash, value, red = true, x) + if (cmp < 0) x.left = z else x.right = z + table(bucket) = fixAfterInsert(_root, z) + return true + } + else insertIntoExisting(_root, bucket, key, hash, value, next) + } + } + + private[this] final def insert(tree: RBNode, bucket: Int, key: K, hash: Int, value: V): Boolean = { + if(tree eq null) { + table(bucket) = CollisionProofHashMap.leaf(key, hash, value, red = false, null) + true + } else insertIntoExisting(tree, bucket, key, hash, value, tree) + } + + private[this] def fixAfterInsert(_root: RBNode, node: RBNode): RBNode = { + var root = _root + var z = node + while (isRed(z.parent)) { + if (z.parent eq z.parent.parent.left) { + val y = z.parent.parent.right + if (isRed(y)) { + z.parent.red = false + y.red = false + z.parent.parent.red = true + z = z.parent.parent + } else { + if (z eq z.parent.right) { + z = z.parent + root = rotateLeft(root, z) + } + z.parent.red = false + z.parent.parent.red = true + root = rotateRight(root, z.parent.parent) + } + } else { // symmetric cases + val y = z.parent.parent.left + if (isRed(y)) { + z.parent.red = false + y.red = false + z.parent.parent.red = true + z = z.parent.parent + } else { + if (z eq z.parent.left) { + z = z.parent + root = rotateRight(root, z) + } + z.parent.red = false + z.parent.parent.red = true + root = rotateLeft(root, z.parent.parent) + } + } + } + root.red = false + root + } + + // ---- deletion ---- + + // returns the old value or Statics.pfMarker if not found + private[this] def delete(_root: RBNode, bucket: Int, key: K, hash: Int): Any = { + var root = _root + val z = root.getNode(key, hash: Int) + if (z ne null) { + val oldValue = z.value + var y = z + var yIsRed = y.red + var x: RBNode = null + var xParent: RBNode = null + + if (z.left eq null) { + x = z.right + root = transplant(root, z, z.right) + xParent = z.parent + } + else if (z.right eq null) { + x = z.left + root = transplant(root, z, z.left) + xParent = z.parent + } + else { + y = CollisionProofHashMap.minNodeNonNull(z.right) + yIsRed = y.red + x = y.right + + if (y.parent eq z) xParent = y + else { + xParent = y.parent + root = transplant(root, y, y.right) + y.right = z.right + y.right.parent = y + } + root = transplant(root, z, y) + y.left = z.left + y.left.parent = y + y.red = z.red + } + + if (!yIsRed) root = fixAfterDelete(root, x, xParent) + if(root ne _root) table(bucket) = root + oldValue + } else Statics.pfMarker + } + + private[this] def fixAfterDelete(_root: RBNode, node: RBNode, parent: RBNode): RBNode = { + var root = _root + var x = node + var xParent = parent + while ((x ne root) && isBlack(x)) { + if (x eq xParent.left) { + var w = xParent.right + // assert(w ne null) + + if (w.red) { + w.red = false + xParent.red = true + root = rotateLeft(root, xParent) + w = xParent.right + } + if (isBlack(w.left) && isBlack(w.right)) { + w.red = true + x = xParent + } else { + if (isBlack(w.right)) { + w.left.red = false + w.red = true + root = rotateRight(root, w) + w = xParent.right + } + w.red = xParent.red + xParent.red = false + w.right.red = false + root = rotateLeft(root, xParent) + x = root + } + } else { // symmetric cases + var w = xParent.left + // assert(w ne null) + + if (w.red) { + w.red = false + xParent.red = true + root = rotateRight(root, xParent) + w = xParent.left + } + if (isBlack(w.right) && isBlack(w.left)) { + w.red = true + x = xParent + } else { + if (isBlack(w.left)) { + w.right.red = false + w.red = true + root = rotateLeft(root, w) + w = xParent.left + } + w.red = xParent.red + xParent.red = false + w.left.red = false + root = rotateRight(root, xParent) + x = root + } + } + xParent = x.parent + } + if (x ne null) x.red = false + root + } + + // ---- helpers ---- + + @`inline` private[this] def rotateLeft(_root: RBNode, x: RBNode): RBNode = { + var root = _root + val y = x.right + x.right = y.left + + val xp = x.parent + if (y.left ne null) y.left.parent = x + y.parent = xp + + if (xp eq null) root = y + else if (x eq xp.left) xp.left = y + else xp.right = y + + y.left = x + x.parent = y + root + } + + @`inline` private[this] def rotateRight(_root: RBNode, x: RBNode): RBNode = { + var root = _root + val y = x.left + x.left = y.right + + val xp = x.parent + if (y.right ne null) y.right.parent = x + y.parent = xp + + if (xp eq null) root = y + else if (x eq xp.right) xp.right = y + else xp.left = y + + y.right = x + x.parent = y + root + } + + /** + * Transplant the node `from` to the place of node `to`. This is done by setting `from` as a child of `to`'s previous + * parent and setting `from`'s parent to the `to`'s previous parent. The children of `from` are left unchanged. + */ + private[this] def transplant(_root: RBNode, to: RBNode, from: RBNode): RBNode = { + var root = _root + if (to.parent eq null) root = from + else if (to eq to.parent.left) to.parent.left = from + else to.parent.right = from + if (from ne null) from.parent = to.parent + root + } + + // building + + def fromNodes(xs: Iterator[Node], size: Int): RBNode = { + val maxUsedDepth = 32 - Integer.numberOfLeadingZeros(size) // maximum depth of non-leaf nodes + def f(level: Int, size: Int): RBNode = size match { + case 0 => null + case 1 => + val nn = xs.next() + val (key, hash, value) = nn match { + case nn: LLNode @uc => (nn.key, nn.hash, nn.value) + case nn: RBNode @uc => (nn.key, nn.hash, nn.value) + } + new RBNode(key, hash, value, level == maxUsedDepth && level != 1, null, null, null) + case n => + val leftSize = (size-1)/2 + val left = f(level+1, leftSize) + val nn = xs.next() + val right = f(level+1, size-1-leftSize) + val (key, hash, value) = nn match { + case nn: LLNode @uc => (nn.key, nn.hash, nn.value) + case nn: RBNode @uc => (nn.key, nn.hash, nn.value) + } + val n = new RBNode(key, hash, value, red = false, left, right, null) + if(left ne null) left.parent = n + right.parent = n + n + } + f(1, size) + } +} + +/** + * $factoryInfo + * @define Coll `mutable.CollisionProofHashMap` + * @define coll mutable collision-proof hash map + */ +@SerialVersionUID(3L) +object CollisionProofHashMap extends SortedMapFactory[CollisionProofHashMap] { + private[collection] final val ordMsg = "No implicit Ordering[${K2}] found to build a CollisionProofHashMap[${K2}, ${V2}]. You may want to upcast to a Map[${K}, ${V}] first by calling `unsorted`." + + def from[K : Ordering, V](it: scala.collection.IterableOnce[(K, V)]): CollisionProofHashMap[K, V] = { + val k = it.knownSize + val cap = if(k > 0) ((k + 1).toDouble / defaultLoadFactor).toInt else defaultInitialCapacity + new CollisionProofHashMap[K, V](cap, defaultLoadFactor) ++= it + } + + def empty[K : Ordering, V]: CollisionProofHashMap[K, V] = new CollisionProofHashMap[K, V] + + def newBuilder[K : Ordering, V]: Builder[(K, V), CollisionProofHashMap[K, V]] = newBuilder(defaultInitialCapacity, defaultLoadFactor) + + def newBuilder[K : Ordering, V](initialCapacity: Int, loadFactor: Double): Builder[(K, V), CollisionProofHashMap[K, V]] = + new GrowableBuilder[(K, V), CollisionProofHashMap[K, V]](new CollisionProofHashMap[K, V](initialCapacity, loadFactor)) { + override def sizeHint(size: Int) = elems.sizeHint(size) + } + + /** The default load factor for the hash table */ + final def defaultLoadFactor: Double = 0.75 + + /** The default initial capacity for the hash table */ + final def defaultInitialCapacity: Int = 16 + + @SerialVersionUID(3L) + private final class DeserializationFactory[K, V](val tableLength: Int, val loadFactor: Double, val ordering: Ordering[K]) extends Factory[(K, V), CollisionProofHashMap[K, V]] with Serializable { + def fromSpecific(it: IterableOnce[(K, V)]): CollisionProofHashMap[K, V] = new CollisionProofHashMap[K, V](tableLength, loadFactor)(ordering) ++= it + def newBuilder: Builder[(K, V), CollisionProofHashMap[K, V]] = CollisionProofHashMap.newBuilder(tableLength, loadFactor)(using ordering) + } + + @unused @`inline` private def compare[K, V](key: K, hash: Int, node: LLNode[K, V])(implicit ord: Ordering[K]): Int = { + val i = hash - node.hash + if(i != 0) i else ord.compare(key, node.key) + } + + @`inline` private def compare[K, V](key: K, hash: Int, node: RBNode[K, V])(implicit ord: Ordering[K]): Int = { + /*val i = hash - node.hash + if(i != 0) i else*/ ord.compare(key, node.key) + } + + private final val treeifyThreshold = 8 + + // Superclass for RBNode and LLNode to help the JIT with optimizing instance checks, but no shared common fields. + // Keeping calls monomorphic where possible and dispatching manually where needed is faster. + sealed abstract class Node + + /////////////////////////// Red-Black Tree Node + + final class RBNode[K, V](var key: K, var hash: Int, var value: V, var red: Boolean, var left: RBNode[K, V], var right: RBNode[K, V], var parent: RBNode[K, V]) extends Node { + override def toString: String = "RBNode(" + key + ", " + hash + ", " + value + ", " + red + ", " + left + ", " + right + ")" + + @tailrec def getNode(k: K, h: Int)(implicit ord: Ordering[K]): RBNode[K, V] = { + val cmp = compare(k, h, this) + if (cmp < 0) { + if(left ne null) left.getNode(k, h) else null + } else if (cmp > 0) { + if(right ne null) right.getNode(k, h) else null + } else this + } + + def foreach[U](f: ((K, V)) => U): Unit = { + if(left ne null) left.foreach(f) + f((key, value)) + if(right ne null) right.foreach(f) + } + + def foreachEntry[U](f: (K, V) => U): Unit = { + if(left ne null) left.foreachEntry(f) + f(key, value) + if(right ne null) right.foreachEntry(f) + } + + def foreachNode[U](f: RBNode[K, V] => U): Unit = { + if(left ne null) left.foreachNode(f) + f(this) + if(right ne null) right.foreachNode(f) + } + } + + @`inline` private def leaf[A, B](key: A, hash: Int, value: B, red: Boolean, parent: RBNode[A, B]): RBNode[A, B] = + new RBNode(key, hash, value, red, null, null, parent) + + @tailrec private def minNodeNonNull[A, B](node: RBNode[A, B]): RBNode[A, B] = + if (node.left eq null) node else minNodeNonNull(node.left) + + /** + * Returns the node that follows `node` in an in-order tree traversal. If `node` has the maximum key (and is, + * therefore, the last node), this method returns `null`. + */ + private def successor[A, B](node: RBNode[A, B]): RBNode[A, B] = { + if (node.right ne null) minNodeNonNull(node.right) + else { + var x = node + var y = x.parent + while ((y ne null) && (x eq y.right)) { + x = y + y = y.parent + } + y + } + } + + private final class RBNodesIterator[A, B](tree: RBNode[A, B])(implicit @unused ord: Ordering[A]) extends AbstractIterator[RBNode[A, B]] { + private[this] var nextNode: RBNode[A, B] = if(tree eq null) null else minNodeNonNull(tree) + + def hasNext: Boolean = nextNode ne null + + @throws[NoSuchElementException] + def next(): RBNode[A, B] = nextNode match { + case null => Iterator.empty.next() + case node => + nextNode = successor(node) + node + } + } + + /////////////////////////// Linked List Node + + private final class LLNode[K, V](var key: K, var hash: Int, var value: V, var next: LLNode[K, V]) extends Node { + override def toString = s"LLNode($key, $value, $hash) -> $next" + + private[this] def eq(a: Any, b: Any): Boolean = + if(a.asInstanceOf[AnyRef] eq null) b.asInstanceOf[AnyRef] eq null else a.asInstanceOf[AnyRef].equals(b) + + @tailrec def getNode(k: K, h: Int)(implicit ord: Ordering[K]): LLNode[K, V] = { + if(h == hash && eq(k, key) /*ord.compare(k, key) == 0*/) this + else if((next eq null) || (hash > h)) null + else next.getNode(k, h) + } + + @tailrec def foreach[U](f: ((K, V)) => U): Unit = { + f((key, value)) + if(next ne null) next.foreach(f) + } + + @tailrec def foreachEntry[U](f: (K, V) => U): Unit = { + f(key, value) + if(next ne null) next.foreachEntry(f) + } + + @tailrec def foreachNode[U](f: LLNode[K, V] => U): Unit = { + f(this) + if(next ne null) next.foreachNode(f) + } + } +} diff --git a/library/src/scala/collection/mutable/Growable.scala b/library/src/scala/collection/mutable/Growable.scala new file mode 100644 index 000000000000..9caed94240e2 --- /dev/null +++ b/library/src/scala/collection/mutable/Growable.scala @@ -0,0 +1,103 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` + +/** This trait forms part of collections that can be augmented + * using a `+=` operator and that can be cleared of all elements using + * a `clear` method. + * + * @define coll growable collection + * @define Coll `Growable` + * @define add add + * @define Add Add + */ +trait Growable[-A] extends Clearable { + + /** ${Add}s a single element to this $coll. + * + * @param elem the element to $add. + * @return the $coll itself + */ + def addOne(elem: A): this.type + + /** Alias for `addOne` */ + @inline final def += (elem: A): this.type = addOne(elem) + + //TODO This causes a conflict in StringBuilder; looks like a compiler bug + //@deprecated("Use addOne or += instead of append", "2.13.0") + //@`inline` final def append(elem: A): Unit = addOne(elem) + + /** ${Add}s two or more elements to this $coll. + * + * @param elem1 the first element to $add. + * @param elem2 the second element to $add. + * @param elems the remaining elements to $add. + * @return the $coll itself + */ + @deprecated("Use `++=` aka `addAll` instead of varargs `+=`; infix operations with an operand of multiple args will be deprecated", "2.13.0") + @inline final def += (elem1: A, elem2: A, elems: A*): this.type = this += elem1 += elem2 ++= (elems: IterableOnce[A]) + + /** ${Add}s all elements produced by an IterableOnce to this $coll. + * + * @param elems the IterableOnce producing the elements to $add. + * @return the $coll itself. + */ + def addAll(@deprecatedName("xs") elems: IterableOnce[A]): this.type = { + if (elems.asInstanceOf[AnyRef] eq this) addAll(Buffer.from(elems)) // avoid mutating under our own iterator + else { + val it = elems.iterator + while (it.hasNext) { + addOne(it.next()) + } + } + this + } + + /** Alias for `addAll` */ + @inline final def ++= (@deprecatedName("xs") elems: IterableOnce[A]): this.type = addAll(elems) + + /** The number of elements in the collection under construction, if it can be cheaply computed, -1 otherwise. + * + * @return The number of elements. The default implementation always returns -1. + */ + def knownSize: Int = -1 +} + +object Growable { + + /** + * Fills a `Growable` instance with the elements of a given iterable + * @param empty Instance to fill + * @param it Elements to add + * @tparam A Element type + * @return The filled instance + */ + def from[A](empty: Growable[A], it: collection.IterableOnce[A]): empty.type = empty ++= it + +} + +/** This trait forms part of collections that can be cleared + * with a clear() call. + * + * @define coll collection + */ +trait Clearable { + /** Clears the $coll's contents. After this operation, the + * $coll is empty. + */ + def clear(): Unit +} diff --git a/library/src/scala/collection/mutable/GrowableBuilder.scala b/library/src/scala/collection/mutable/GrowableBuilder.scala new file mode 100644 index 000000000000..e98aa16c2581 --- /dev/null +++ b/library/src/scala/collection/mutable/GrowableBuilder.scala @@ -0,0 +1,38 @@ +/* + * 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 collection.mutable + +import scala.language.`2.13` + +/** The canonical builder for collections that are growable, i.e. that support an + * efficient `+=` method which adds an element to the collection. + * + * GrowableBuilders can produce only a single instance of the collection they are growing. + * + * @define Coll `GrowingBuilder` + * @define coll growing builder + */ +class GrowableBuilder[Elem, To <: Growable[Elem]](protected val elems: To) + extends Builder[Elem, To] { + + def clear(): Unit = elems.clear() + + def result(): To = elems + + def addOne(elem: Elem): this.type = { elems += elem; this } + + override def addAll(xs: IterableOnce[Elem]): this.type = { elems.addAll(xs); this } + + override def knownSize: Int = elems.knownSize +} diff --git a/library/src/scala/collection/mutable/HashMap.scala b/library/src/scala/collection/mutable/HashMap.scala new file mode 100644 index 000000000000..7980ffe08bd7 --- /dev/null +++ b/library/src/scala/collection/mutable/HashMap.scala @@ -0,0 +1,655 @@ +/* + * 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.collection +package mutable + +import scala.language.`2.13` +import scala.annotation.{nowarn, tailrec} +import scala.collection.Stepper.EfficientSplit +import scala.collection.generic.DefaultSerializationProxy +import scala.util.hashing.MurmurHash3 + +/** This class implements mutable maps using a hashtable. + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html#hash-tables "Scala's Collection Library overview"]] + * section on `Hash Tables` for more information. + * + * @tparam K the type of the keys contained in this hash map. + * @tparam V the type of the values assigned to keys in this hash map. + * + * @define Coll `mutable.HashMap` + * @define coll mutable hash map + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +@deprecatedInheritance("HashMap will be made final; use .withDefault for the common use case of computing a default value", "2.13.0") +class HashMap[K, V](initialCapacity: Int, loadFactor: Double) + extends AbstractMap[K, V] + with MapOps[K, V, HashMap, HashMap[K, V]] + with StrictOptimizedIterableOps[(K, V), Iterable, HashMap[K, V]] + with StrictOptimizedMapOps[K, V, HashMap, HashMap[K, V]] + with MapFactoryDefaults[K, V, HashMap, Iterable] + with Serializable { + + /* The HashMap class holds the following invariant: + * - For each i between 0 and table.length, the bucket at table(i) only contains keys whose hash-index is i. + * - Every bucket is sorted in ascendent hash order + * - The sum of the lengths of all buckets is equal to contentSize. + */ + def this() = this(HashMap.defaultInitialCapacity, HashMap.defaultLoadFactor) + + import HashMap.Node + + /** The actual hash table. */ + private[this] var table = new Array[Node[K, V]](tableSizeFor(initialCapacity)) + + /** The next size value at which to resize (capacity * load factor). */ + private[this] var threshold: Int = newThreshold(table.length) + + private[this] var contentSize = 0 + + override def size: Int = contentSize + + /** Performs the inverse operation of improveHash. In this case, it happens to be identical to improveHash*/ + @`inline` private[collection] def unimproveHash(improvedHash: Int): Int = improveHash(improvedHash) + + /** Computes the improved hash of an original (`any.##`) hash. */ + @`inline` private[this] def improveHash(originalHash: Int): Int = { + // Improve the hash by xoring the high 16 bits into the low 16 bits just in case entropy is skewed towards the + // high-value bits. We only use the lowest bits to determine the hash bucket. This is the same improvement + // algorithm as in java.util.HashMap. + // + // This function is also its own inverse. That is, for all ints i, improveHash(improveHash(i)) = i + // this allows us to retrieve the original hash when we need it, for instance when appending to an immutable.HashMap + // and that is why unimproveHash simply forwards to this method + originalHash ^ (originalHash >>> 16) + } + + /** Computes the improved hash of this key */ + @`inline` private[this] def computeHash(o: K): Int = improveHash(o.##) + + @`inline` private[this] def index(hash: Int) = hash & (table.length - 1) + + override def contains(key: K): Boolean = findNode(key) ne null + + @`inline` private[this] def findNode(key: K): Node[K, V] = { + val hash = computeHash(key) + table(index(hash)) match { + case null => null + case nd => nd.findNode(key, hash) + } + } + + override def sizeHint(size: Int): Unit = { + val target = tableSizeFor(((size + 1).toDouble / loadFactor).toInt) + if(target > table.length) growTable(target) + } + + override def addAll(xs: IterableOnce[(K, V)]): this.type = { + sizeHint(xs) + + xs match { + case hm: immutable.HashMap[K, V] => + hm.foreachWithHash((k, v, h) => put0(k, v, improveHash(h), getOld = false)) + this + case hm: mutable.HashMap[K, V] => + val iter = hm.nodeIterator + while (iter.hasNext) { + val next = iter.next() + put0(next.key, next.value, next.hash, getOld = false) + } + this + case lhm: mutable.LinkedHashMap[K, V] => + val iter = lhm.entryIterator + while (iter.hasNext) { + val entry = iter.next() + put0(entry.key, entry.value, entry.hash, getOld = false) + } + this + case thatMap: Map[K, V] => + thatMap.foreachEntry { (key: K, value: V) => + put0(key, value, improveHash(key.##), getOld = false) + } + this + case _ => + super.addAll(xs) + } + } + + // Override updateWith for performance, so we can do the update while hashing + // the input key only once and performing one lookup into the hash table + override def updateWith(key: K)(remappingFunction: Option[V] => Option[V]): Option[V] = { + if (getClass != classOf[HashMap[_, _]]) { + // subclasses of HashMap might customise `get` ... + super.updateWith(key)(remappingFunction) + } else { + val hash = computeHash(key) + val indexedHash = index(hash) + + var foundNode: Node[K, V] = null + var previousNode: Node[K, V] = null + table(indexedHash) match { + case null => + case nd => + @tailrec + def findNode(prev: Node[K, V], nd: Node[K, V], k: K, h: Int): Unit = { + if (h == nd.hash && k == nd.key) { + previousNode = prev + foundNode = nd + } + else if ((nd.next eq null) || (nd.hash > h)) () + else findNode(nd, nd.next, k, h) + } + + findNode(null, nd, key, hash) + } + + val previousValue = foundNode match { + case null => None + case nd => Some(nd.value) + } + + val nextValue = remappingFunction(previousValue) + + (previousValue, nextValue) match { + case (None, None) => // do nothing + + case (Some(_), None) => + if (previousNode != null) previousNode.next = foundNode.next + else table(indexedHash) = foundNode.next + contentSize -= 1 + + case (None, Some(value)) => + val newIndexedHash = + if (contentSize + 1 >= threshold) { + growTable(table.length * 2) + index(hash) + } else indexedHash + put0(key, value, getOld = false, hash, newIndexedHash) + + case (Some(_), Some(newValue)) => foundNode.value = newValue + } + nextValue + } + } + + override def subtractAll(xs: IterableOnce[K]): this.type = { + if (size == 0) { + return this + } + + xs match { + case hs: immutable.HashSet[K] => + hs.foreachWithHashWhile { (k, h) => + remove0(k, improveHash(h)) + size > 0 + } + this + case hs: mutable.HashSet[K] => + val iter = hs.nodeIterator + while (iter.hasNext) { + val next = iter.next() + remove0(next.key, next.hash) + if (size == 0) return this + } + this + case lhs: mutable.LinkedHashSet[K] => + val iter = lhs.entryIterator + while (iter.hasNext) { + val next = iter.next() + remove0(next.key, next.hash) + if (size == 0) return this + } + this + case _ => super.subtractAll(xs) + } + } + + /** Adds a key-value pair to this map + * + * @param key the key to add + * @param value the value to add + * @param hash the **improved** hashcode of `key` (see computeHash) + * @param getOld if true, then the previous value for `key` will be returned, otherwise, false + */ + private[this] def put0(key: K, value: V, hash: Int, getOld: Boolean): Some[V] = { + if(contentSize + 1 >= threshold) growTable(table.length * 2) + val idx = index(hash) + put0(key, value, getOld, hash, idx) + } + + private[this] def put0(key: K, value: V, getOld: Boolean): Some[V] = { + if(contentSize + 1 >= threshold) growTable(table.length * 2) + val hash = computeHash(key) + val idx = index(hash) + put0(key, value, getOld, hash, idx) + } + + + private[this] def put0(key: K, value: V, getOld: Boolean, hash: Int, idx: Int): Some[V] = { + table(idx) match { + case null => + table(idx) = new Node[K, V](key, hash, value, null) + case old => + var prev: Node[K, V] = null + var n = old + while((n ne null) && n.hash <= hash) { + if(n.hash == hash && key == n.key) { + val old = n.value + n.value = value + return if(getOld) Some(old) else null + } + prev = n + n = n.next + } + if(prev eq null) table(idx) = new Node(key, hash, value, old) + else prev.next = new Node(key, hash, value, prev.next) + } + contentSize += 1 + null + } + + private def remove0(elem: K) : Node[K, V] = remove0(elem, computeHash(elem)) + + /** Removes a key from this map if it exists + * + * @param elem the element to remove + * @param hash the **improved** hashcode of `element` (see computeHash) + * @return the node that contained element if it was present, otherwise null + */ + private[this] def remove0(elem: K, hash: Int) : Node[K, V] = { + val idx = index(hash) + table(idx) match { + case null => null + case nd if nd.hash == hash && nd.key == elem => + // first element matches + table(idx) = nd.next + contentSize -= 1 + nd + case nd => + // find an element that matches + var prev = nd + var next = nd.next + while((next ne null) && next.hash <= hash) { + if(next.hash == hash && next.key == elem) { + prev.next = next.next + contentSize -= 1 + return next + } + prev = next + next = next.next + } + null + } + } + + private[this] abstract class HashMapIterator[A] extends AbstractIterator[A] { + private[this] var i = 0 + private[this] var node: Node[K, V] = null + private[this] val len = table.length + + protected[this] def extract(nd: Node[K, V]): A + + def hasNext: Boolean = { + if(node ne null) true + else { + while(i < len) { + val n = table(i) + i += 1 + if(n ne null) { node = n; return true } + } + false + } + } + + def next(): A = + if(!hasNext) Iterator.empty.next() + else { + val r = extract(node) + node = node.next + r + } + } + + override def iterator: Iterator[(K, V)] = + if(size == 0) Iterator.empty + else new HashMapIterator[(K, V)] { + protected[this] def extract(nd: Node[K, V]) = (nd.key, nd.value) + } + + override def keysIterator: Iterator[K] = + if(size == 0) Iterator.empty + else new HashMapIterator[K] { + protected[this] def extract(nd: Node[K, V]) = nd.key + } + + override def valuesIterator: Iterator[V] = + if(size == 0) Iterator.empty + else new HashMapIterator[V] { + protected[this] def extract(nd: Node[K, V]) = nd.value + } + + + /** Returns an iterator over the nodes stored in this HashMap */ + private[collection] def nodeIterator: Iterator[Node[K, V]] = + if(size == 0) Iterator.empty + else new HashMapIterator[Node[K, V]] { + protected[this] def extract(nd: Node[K, V]) = nd + } + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[(K, V), S]): S with EfficientSplit = + shape. + parUnbox(new convert.impl.AnyTableStepper[(K, V), Node[K, V]](size, table, _.next, node => (node.key, node.value), 0, table.length)). + asInstanceOf[S with EfficientSplit] + + override def keyStepper[S <: Stepper[_]](implicit shape: StepperShape[K, S]): S with EfficientSplit = { + import convert.impl._ + val s = shape.shape match { + case StepperShape.IntShape => new IntTableStepper[Node[K, V]] (size, table, _.next, _.key.asInstanceOf[Int], 0, table.length) + case StepperShape.LongShape => new LongTableStepper[Node[K, V]] (size, table, _.next, _.key.asInstanceOf[Long], 0, table.length) + case StepperShape.DoubleShape => new DoubleTableStepper[Node[K, V]](size, table, _.next, _.key.asInstanceOf[Double], 0, table.length) + case _ => shape.parUnbox(new AnyTableStepper[K, Node[K, V]](size, table, _.next, _.key, 0, table.length)) + } + s.asInstanceOf[S with EfficientSplit] + } + + override def valueStepper[S <: Stepper[_]](implicit shape: StepperShape[V, S]): S with EfficientSplit = { + import convert.impl._ + val s = shape.shape match { + case StepperShape.IntShape => new IntTableStepper[Node[K, V]] (size, table, _.next, _.value.asInstanceOf[Int], 0, table.length) + case StepperShape.LongShape => new LongTableStepper[Node[K, V]] (size, table, _.next, _.value.asInstanceOf[Long], 0, table.length) + case StepperShape.DoubleShape => new DoubleTableStepper[Node[K, V]](size, table, _.next, _.value.asInstanceOf[Double], 0, table.length) + case _ => shape.parUnbox(new AnyTableStepper[V, Node[K, V]](size, table, _.next, _.value, 0, table.length)) + } + s.asInstanceOf[S with EfficientSplit] + } + + private[this] def growTable(newlen: Int) = { + if (newlen < 0) + throw new RuntimeException(s"new HashMap table size $newlen exceeds maximum") + var oldlen = table.length + threshold = newThreshold(newlen) + if(size == 0) table = new Array(newlen) + else { + table = java.util.Arrays.copyOf(table, newlen) + val preLow: Node[K, V] = new Node(null.asInstanceOf[K], 0, null.asInstanceOf[V], null) + val preHigh: Node[K, V] = new Node(null.asInstanceOf[K], 0, null.asInstanceOf[V], null) + // Split buckets until the new length has been reached. This could be done more + // efficiently when growing an already filled table to more than double the size. + while(oldlen < newlen) { + var i = 0 + while (i < oldlen) { + val old = table(i) + if(old ne null) { + preLow.next = null + preHigh.next = null + var lastLow: Node[K, V] = preLow + var lastHigh: Node[K, V] = preHigh + var n = old + while(n ne null) { + val next = n.next + if((n.hash & oldlen) == 0) { // keep low + lastLow.next = n + lastLow = n + } else { // move to high + lastHigh.next = n + lastHigh = n + } + n = next + } + lastLow.next = null + if(old ne preLow.next) table(i) = preLow.next + if(preHigh.next ne null) { + table(i + oldlen) = preHigh.next + lastHigh.next = null + } + } + i += 1 + } + oldlen *= 2 + } + } + } + + private[this] def tableSizeFor(capacity: Int) = + (Integer.highestOneBit((capacity-1).max(4))*2).min(1 << 30) + + private[this] def newThreshold(size: Int) = (size.toDouble * loadFactor).toInt + + override def clear(): Unit = { + java.util.Arrays.fill(table.asInstanceOf[Array[AnyRef]], null) + contentSize = 0 + } + + def get(key: K): Option[V] = findNode(key) match { + case null => None + case nd => Some(nd.value) + } + + @throws[NoSuchElementException] + override def apply(key: K): V = findNode(key) match { + case null => default(key) + case nd => nd.value + } + + override def getOrElse[V1 >: V](key: K, default: => V1): V1 = { + if (getClass != classOf[HashMap[_, _]]) { + // subclasses of HashMap might customise `get` ... + super.getOrElse(key, default) + } else { + // .. but in the common case, we can avoid the Option boxing. + val nd = findNode(key) + if (nd eq null) default else nd.value + } + } + + override def getOrElseUpdate(key: K, defaultValue: => V): V = { + if (getClass != classOf[HashMap[_, _]]) { + // subclasses of HashMap might customise `get` ... + super.getOrElseUpdate(key, defaultValue) + } else { + val hash = computeHash(key) + val idx = index(hash) + val nd = table(idx) match { + case null => null + case nd => nd.findNode(key, hash) + } + if(nd != null) nd.value + else { + val table0 = table + val default = defaultValue + if(contentSize + 1 >= threshold) growTable(table.length * 2) + // Avoid recomputing index if the `defaultValue()` or new element hasn't triggered a table resize. + val newIdx = if (table0 eq table) idx else index(hash) + put0(key, default, getOld = false, hash, newIdx) + default + } + } + } + + override def put(key: K, value: V): Option[V] = put0(key, value, getOld = true) match { + case null => None + case sm => sm + } + + override def remove(key: K): Option[V] = remove0(key) match { + case null => None + case nd => Some(nd.value) + } + + override def update(key: K, value: V): Unit = put0(key, value, getOld = false) + + def addOne(elem: (K, V)): this.type = { put0(elem._1, elem._2, getOld = false); this } + + def subtractOne(elem: K): this.type = { remove0(elem); this } + + override def knownSize: Int = size + + override def isEmpty: Boolean = size == 0 + + override def foreach[U](f: ((K, V)) => U): Unit = { + val len = table.length + var i = 0 + while(i < len) { + val n = table(i) + if(n ne null) n.foreach(f) + i += 1 + } + } + + override def foreachEntry[U](f: (K, V) => U): Unit = { + val len = table.length + var i = 0 + while(i < len) { + val n = table(i) + if(n ne null) n.foreachEntry(f) + i += 1 + } + } + + protected[this] def writeReplace(): AnyRef = new DefaultSerializationProxy(new mutable.HashMap.DeserializationFactory[K, V](table.length, loadFactor), this) + + override def filterInPlace(p: (K, V) => Boolean): this.type = { + if (nonEmpty) { + var bucket = 0 + + while (bucket < table.length) { + var head = table(bucket) + + while ((head ne null) && !p(head.key, head.value)) { + head = head.next + contentSize -= 1 + } + + if (head ne null) { + var prev = head + var next = head.next + + while (next ne null) { + if (p(next.key, next.value)) { + prev = next + } else { + prev.next = next.next + contentSize -= 1 + } + next = next.next + } + } + + table(bucket) = head + bucket += 1 + } + } + this + } + + // TODO: rename to `mapValuesInPlace` and override the base version (not binary compatible) + private[mutable] def mapValuesInPlaceImpl(f: (K, V) => V): this.type = { + val len = table.length + var i = 0 + while (i < len) { + var n = table(i) + while (n ne null) { + n.value = f(n.key, n.value) + n = n.next + } + i += 1 + } + this + } + + override def mapFactory: MapFactory[HashMap] = HashMap + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix = "HashMap" + + override def hashCode: Int = { + if (isEmpty) MurmurHash3.emptyMapHash + else { + val tupleHashIterator = new HashMapIterator[Any] { + var hash: Int = 0 + override def hashCode: Int = hash + override protected[this] def extract(nd: Node[K, V]): Any = { + hash = MurmurHash3.tuple2Hash(unimproveHash(nd.hash), nd.value.##) + this + } + } + MurmurHash3.unorderedHash(tupleHashIterator, MurmurHash3.mapSeed) + } + } +} + +/** + * $factoryInfo + * @define Coll `mutable.HashMap` + * @define coll mutable hash map + */ +@SerialVersionUID(3L) +object HashMap extends MapFactory[HashMap] { + + def empty[K, V]: HashMap[K, V] = new HashMap[K, V] + + def from[K, V](it: collection.IterableOnce[(K, V)]): HashMap[K, V] = { + val k = it.knownSize + val cap = if(k > 0) ((k + 1).toDouble / defaultLoadFactor).toInt else defaultInitialCapacity + new HashMap[K, V](cap, defaultLoadFactor).addAll(it) + } + + def newBuilder[K, V]: Builder[(K, V), HashMap[K, V]] = newBuilder(defaultInitialCapacity, defaultLoadFactor) + + def newBuilder[K, V](initialCapacity: Int, loadFactor: Double): Builder[(K, V), HashMap[K, V]] = + new GrowableBuilder[(K, V), HashMap[K, V]](new HashMap[K, V](initialCapacity, loadFactor)) { + override def sizeHint(size: Int) = elems.sizeHint(size) + } + + /** The default load factor for the hash table */ + final def defaultLoadFactor: Double = 0.75 + + /** The default initial capacity for the hash table */ + final def defaultInitialCapacity: Int = 16 + + @SerialVersionUID(3L) + private final class DeserializationFactory[K, V](val tableLength: Int, val loadFactor: Double) extends Factory[(K, V), HashMap[K, V]] with Serializable { + def fromSpecific(it: IterableOnce[(K, V)]): HashMap[K, V] = new HashMap[K, V](tableLength, loadFactor).addAll(it) + def newBuilder: Builder[(K, V), HashMap[K, V]] = HashMap.newBuilder(tableLength, loadFactor) + } + + private[collection] final class Node[K, V](_key: K, _hash: Int, private[this] var _value: V, private[this] var _next: Node[K, V]) { + def key: K = _key + def hash: Int = _hash + def value: V = _value + def value_= (v: V): Unit = _value = v + def next: Node[K, V] = _next + def next_= (n: Node[K, V]): Unit = _next = n + + @tailrec + def findNode(k: K, h: Int): Node[K, V] = + if(h == _hash && k == _key) this + else if((_next eq null) || (_hash > h)) null + else _next.findNode(k, h) + + @tailrec + def foreach[U](f: ((K, V)) => U): Unit = { + f((_key, _value)) + if(_next ne null) _next.foreach(f) + } + + @tailrec + def foreachEntry[U](f: (K, V) => U): Unit = { + f(_key, _value) + if(_next ne null) _next.foreachEntry(f) + } + + override def toString = s"Node($key, $value, $hash) -> $next" + } +} diff --git a/library/src/scala/collection/mutable/HashSet.scala b/library/src/scala/collection/mutable/HashSet.scala new file mode 100644 index 000000000000..df0cb0a9ee33 --- /dev/null +++ b/library/src/scala/collection/mutable/HashSet.scala @@ -0,0 +1,457 @@ +/* + * 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.collection +package mutable + +import scala.language.`2.13` +import scala.annotation.tailrec +import scala.collection.Stepper.EfficientSplit +import scala.collection.generic.DefaultSerializationProxy +import scala.util.hashing.MurmurHash3 + +/** This class implements mutable sets using a hashtable. + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html#hash-tables "Scala's Collection Library overview"]] + * section on `Hash Tables` for more information. + * + * @define Coll `mutable.HashSet` + * @define coll mutable hash set + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +final class HashSet[A](initialCapacity: Int, loadFactor: Double) + extends AbstractSet[A] + with SetOps[A, HashSet, HashSet[A]] + with StrictOptimizedIterableOps[A, HashSet, HashSet[A]] + with IterableFactoryDefaults[A, HashSet] + with Serializable { + + def this() = this(HashSet.defaultInitialCapacity, HashSet.defaultLoadFactor) + + import HashSet.Node + + /* The Hashset class holds the following invariant: + * - For each i between 0 and table.length, the bucket at table(i) only contains elements whose hash-index is i. + * - Every bucket is sorted in ascendent hash order + * - The sum of the lengths of all buckets is equal to contentSize. + */ + /** The actual hash table. */ + private[this] var table = new Array[Node[A]](tableSizeFor(initialCapacity)) + + /** The next size value at which to resize (capacity * load factor). */ + private[this] var threshold: Int = newThreshold(table.length) + + private[this] var contentSize = 0 + + override def size: Int = contentSize + + /** Performs the inverse operation of improveHash. In this case, it happens to be identical to improveHash*/ + @`inline` private[collection] def unimproveHash(improvedHash: Int): Int = improveHash(improvedHash) + + /** Computes the improved hash of an original (`any.##`) hash. */ + private[this] def improveHash(originalHash: Int): Int = { + // Improve the hash by xoring the high 16 bits into the low 16 bits just in case entropy is skewed towards the + // high-value bits. We only use the lowest bits to determine the hash bucket. This is the same improvement + // algorithm as in java.util.HashMap. + originalHash ^ (originalHash >>> 16) + } + + /** Computes the improved hash of this element */ + @`inline` private[this] def computeHash(o: A): Int = improveHash(o.##) + + @`inline` private[this] def index(hash: Int) = hash & (table.length - 1) + + override def contains(elem: A): Boolean = findNode(elem) ne null + + @`inline` private[this] def findNode(elem: A): Node[A] = { + val hash = computeHash(elem) + table(index(hash)) match { + case null => null + case nd => nd.findNode(elem, hash) + } + } + + override def sizeHint(size: Int): Unit = { + val target = tableSizeFor(((size + 1).toDouble / loadFactor).toInt) + if(target > table.length) growTable(target) + } + + override def add(elem: A) : Boolean = { + if(contentSize + 1 >= threshold) growTable(table.length * 2) + addElem(elem, computeHash(elem)) + } + + override def addAll(xs: IterableOnce[A]): this.type = { + sizeHint(xs, delta = 0) + xs match { + case hs: immutable.HashSet[A] => + hs.foreachWithHash((k, h) => addElem(k, improveHash(h))) + this + case hs: mutable.HashSet[A] => + val iter = hs.nodeIterator + while (iter.hasNext) { + val next = iter.next() + addElem(next.key, next.hash) + } + this + case lhs: mutable.LinkedHashSet[A] => + val iter = lhs.entryIterator + while (iter.hasNext) { + val next = iter.next() + addElem(next.key, next.hash) + } + this + case _ => super.addAll(xs) + } + } + + override def subtractAll(xs: IterableOnce[A]): this.type = { + if (size == 0) { + return this + } + + xs match { + case hs: immutable.HashSet[A] => + hs.foreachWithHashWhile { (k, h) => + remove(k, improveHash(h)) + size > 0 + } + this + case hs: mutable.HashSet[A] => + val iter = hs.nodeIterator + while (iter.hasNext) { + val next = iter.next() + remove(next.key, next.hash) + if (size == 0) return this + } + this + case lhs: mutable.LinkedHashSet[A] => + val iter = lhs.entryIterator + while (iter.hasNext) { + val next = iter.next() + remove(next.key, next.hash) + if (size == 0) return this + } + this + case _ => super.subtractAll(xs) + } + } + + /** Adds an element to this set + * @param elem element to add + * @param hash the **improved** hash of `elem` (see computeHash) + */ + private[this] def addElem(elem: A, hash: Int) : Boolean = { + val idx = index(hash) + table(idx) match { + case null => + table(idx) = new Node(elem, hash, null) + case old => + var prev: Node[A] = null + var n = old + while((n ne null) && n.hash <= hash) { + if(n.hash == hash && elem == n.key) return false + prev = n + n = n.next + } + if(prev eq null) + table(idx) = new Node(elem, hash, old) + else + prev.next = new Node(elem, hash, prev.next) + } + contentSize += 1 + true + } + + private[this] def remove(elem: A, hash: Int): Boolean = { + val idx = index(hash) + table(idx) match { + case null => false + case nd if nd.hash == hash && nd.key == elem => + // first element matches + table(idx) = nd.next + contentSize -= 1 + true + case nd => + // find an element that matches + var prev = nd + var next = nd.next + while((next ne null) && next.hash <= hash) { + if(next.hash == hash && next.key == elem) { + prev.next = next.next + contentSize -= 1 + return true + } + prev = next + next = next.next + } + false + } + } + + override def remove(elem: A) : Boolean = remove(elem, computeHash(elem)) + + private[this] abstract class HashSetIterator[B] extends AbstractIterator[B] { + private[this] var i = 0 + private[this] var node: Node[A] = null + private[this] val len = table.length + + protected[this] def extract(nd: Node[A]): B + + def hasNext: Boolean = { + if(node ne null) true + else { + while(i < len) { + val n = table(i) + i += 1 + if(n ne null) { node = n; return true } + } + false + } + } + + def next(): B = + if(!hasNext) Iterator.empty.next() + else { + val r = extract(node) + node = node.next + r + } + } + + override def iterator: Iterator[A] = new HashSetIterator[A] { + override protected[this] def extract(nd: Node[A]): A = nd.key + } + + /** Returns an iterator over the nodes stored in this HashSet */ + private[collection] def nodeIterator: Iterator[Node[A]] = new HashSetIterator[Node[A]] { + override protected[this] def extract(nd: Node[A]): Node[A] = nd + } + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit = { + import convert.impl._ + val s = shape.shape match { + case StepperShape.IntShape => new IntTableStepper[Node[A]] (size, table, _.next, _.key.asInstanceOf[Int], 0, table.length) + case StepperShape.LongShape => new LongTableStepper[Node[A]] (size, table, _.next, _.key.asInstanceOf[Long], 0, table.length) + case StepperShape.DoubleShape => new DoubleTableStepper[Node[A]](size, table, _.next, _.key.asInstanceOf[Double], 0, table.length) + case _ => shape.parUnbox(new AnyTableStepper[A, Node[A]](size, table, _.next, _.key, 0, table.length)) + } + s.asInstanceOf[S with EfficientSplit] + } + + private[this] def growTable(newlen: Int) = { + var oldlen = table.length + threshold = newThreshold(newlen) + if(size == 0) table = new Array(newlen) + else { + table = java.util.Arrays.copyOf(table, newlen) + val preLow: Node[A] = new Node(null.asInstanceOf[A], 0, null) + val preHigh: Node[A] = new Node(null.asInstanceOf[A], 0, null) + // Split buckets until the new length has been reached. This could be done more + // efficiently when growing an already filled table to more than double the size. + while(oldlen < newlen) { + var i = 0 + while (i < oldlen) { + val old = table(i) + if(old ne null) { + preLow.next = null + preHigh.next = null + var lastLow: Node[A] = preLow + var lastHigh: Node[A] = preHigh + var n = old + while(n ne null) { + val next = n.next + if((n.hash & oldlen) == 0) { // keep low + lastLow.next = n + lastLow = n + } else { // move to high + lastHigh.next = n + lastHigh = n + } + n = next + } + lastLow.next = null + if(old ne preLow.next) table(i) = preLow.next + if(preHigh.next ne null) { + table(i + oldlen) = preHigh.next + lastHigh.next = null + } + } + i += 1 + } + oldlen *= 2 + } + } + } + + override def filterInPlace(p: A => Boolean): this.type = { + if (nonEmpty) { + var bucket = 0 + + while (bucket < table.length) { + var head = table(bucket) + + while ((head ne null) && !p(head.key)) { + head = head.next + contentSize -= 1 + } + + if (head ne null) { + var prev = head + var next = head.next + + while (next ne null) { + if (p(next.key)) { + prev = next + } else { + prev.next = next.next + contentSize -= 1 + } + next = next.next + } + } + + table(bucket) = head + bucket += 1 + } + } + this + } + + /* + private[mutable] def checkTable(): Unit = { + var i = 0 + var count = 0 + var prev: Node[A] = null + while(i < table.length) { + var n = table(i) + prev = null + while(n != null) { + count += 1 + assert(index(n.hash) == i) + if(prev ne null) assert(prev.hash <= n.hash) + prev = n + n = n.next + } + i += 1 + } + assert(contentSize == count) + } + */ + + private[this] def tableSizeFor(capacity: Int) = + (Integer.highestOneBit((capacity-1).max(4))*2).min(1 << 30) + + private[this] def newThreshold(size: Int) = (size.toDouble * loadFactor).toInt + + def clear(): Unit = { + java.util.Arrays.fill(table.asInstanceOf[Array[AnyRef]], null) + contentSize = 0 + } + + override def iterableFactory: IterableFactory[HashSet] = HashSet + + @`inline` def addOne(elem: A): this.type = { add(elem); this } + + @`inline` def subtractOne(elem: A): this.type = { remove(elem); this } + + override def knownSize: Int = size + + override def isEmpty: Boolean = size == 0 + + override def foreach[U](f: A => U): Unit = { + val len = table.length + var i = 0 + while(i < len) { + val n = table(i) + if(n ne null) n.foreach(f) + i += 1 + } + } + + protected[this] def writeReplace(): AnyRef = new DefaultSerializationProxy(new HashSet.DeserializationFactory[A](table.length, loadFactor), this) + + override protected[this] def className = "HashSet" + + override def hashCode: Int = { + val setIterator = this.iterator + val hashIterator: Iterator[Any] = + if (setIterator.isEmpty) setIterator + else new HashSetIterator[Any] { + var hash: Int = 0 + override def hashCode: Int = hash + override protected[this] def extract(nd: Node[A]): Any = { + hash = unimproveHash(nd.hash) + this + } + } + MurmurHash3.unorderedHash(hashIterator, MurmurHash3.setSeed) + } +} + +/** + * $factoryInfo + * @define Coll `mutable.HashSet` + * @define coll mutable hash set + */ +@SerialVersionUID(3L) +object HashSet extends IterableFactory[HashSet] { + + def from[B](it: scala.collection.IterableOnce[B]): HashSet[B] = { + val k = it.knownSize + val cap = if(k > 0) ((k + 1).toDouble / defaultLoadFactor).toInt else defaultInitialCapacity + new HashSet[B](cap, defaultLoadFactor) ++= it + } + + def empty[A]: HashSet[A] = new HashSet[A] + + def newBuilder[A]: Builder[A, HashSet[A]] = newBuilder(defaultInitialCapacity, defaultLoadFactor) + + def newBuilder[A](initialCapacity: Int, loadFactor: Double): Builder[A, HashSet[A]] = + new GrowableBuilder[A, HashSet[A]](new HashSet[A](initialCapacity, loadFactor)) { + override def sizeHint(size: Int) = elems.sizeHint(size) + } + + /** The default load factor for the hash table */ + final def defaultLoadFactor: Double = 0.75 + + /** The default initial capacity for the hash table */ + final def defaultInitialCapacity: Int = 16 + + @SerialVersionUID(3L) + private final class DeserializationFactory[A](val tableLength: Int, val loadFactor: Double) extends Factory[A, HashSet[A]] with Serializable { + def fromSpecific(it: IterableOnce[A]): HashSet[A] = new HashSet[A](tableLength, loadFactor) ++= it + def newBuilder: Builder[A, HashSet[A]] = HashSet.newBuilder(tableLength, loadFactor) + } + + private[collection] final class Node[K](_key: K, _hash: Int, private[this] var _next: Node[K]) { + def key: K = _key + def hash: Int = _hash + def next: Node[K] = _next + def next_= (n: Node[K]): Unit = _next = n + + @tailrec + def findNode(k: K, h: Int): Node[K] = + if(h == _hash && k == _key) this + else if((_next eq null) || (_hash > h)) null + else _next.findNode(k, h) + + @tailrec + def foreach[U](f: K => U): Unit = { + f(_key) + if(_next ne null) _next.foreach(f) + } + + override def toString = s"Node($key, $hash) -> $next" + } +} diff --git a/library/src/scala/collection/mutable/HashTable.scala b/library/src/scala/collection/mutable/HashTable.scala new file mode 100644 index 000000000000..08bdd68eece3 --- /dev/null +++ b/library/src/scala/collection/mutable/HashTable.scala @@ -0,0 +1,418 @@ +/* + * 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 collection.mutable + +import scala.language.`2.13` +import collection.{AbstractIterator, Iterator} + +import java.lang.Integer.{numberOfLeadingZeros, rotateRight} +import scala.util.hashing.byteswap32 + +import java.lang.Integer + +/** This class can be used to construct data structures that are based + * on hashtables. Class `HashTable[A]` implements a hashtable + * that maps keys of type `A` to values of the fully abstract + * member type `Entry`. Classes that make use of `HashTable` + * have to provide an implementation for `Entry`. + * + * There are mainly two parameters that affect the performance of a hashtable: + * the initial size and the load factor. The size + * refers to the number of buckets in the hashtable, and the load + * factor is a measure of how full the hashtable is allowed to get before + * its size is automatically doubled. Both parameters may be changed by + * overriding the corresponding values in class `HashTable`. + * + * @tparam A type of the elements contained in this hash table. + */ +// Not used in the standard library, but used in scala-parallel-collections +private[collection] trait HashTable[A, B, Entry >: Null <: HashEntry[A, Entry]] extends HashTable.HashUtils[A] { + // Replacing Entry type parameter by abstract type member here allows to not expose to public + // implementation-specific entry classes such as `DefaultEntry` or `LinkedEntry`. + // However, I'm afraid it's too late now for such breaking change. + import HashTable._ + + protected var _loadFactor = defaultLoadFactor + + /** The actual hash table. + */ + protected[collection] var table: Array[HashEntry[A, Entry]] = new Array(initialCapacity) + + /** The number of mappings contained in this hash table. + */ + protected[collection] var tableSize: Int = 0 + + final def size: Int = tableSize + + /** The next size value at which to resize (capacity * load factor). + */ + protected[collection] var threshold: Int = initialThreshold(_loadFactor) + + /** The array keeping track of the number of elements in 32 element blocks. + */ + protected var sizemap: Array[Int] = null + + protected var seedvalue: Int = tableSizeSeed + + protected def tableSizeSeed = Integer.bitCount(table.length - 1) + + /** The initial size of the hash table. + */ + protected def initialSize: Int = 16 + + /** The initial threshold. + */ + private def initialThreshold(_loadFactor: Int): Int = newThreshold(_loadFactor, initialCapacity) + + private def initialCapacity = capacity(initialSize) + + private def lastPopulatedIndex = { + var idx = table.length - 1 + while (table(idx) == null && idx > 0) + idx -= 1 + + idx + } + + /** + * Initializes the collection from the input stream. `readEntry` will be called for each + * entry to be read from the input stream. + */ + private[collection] def init(in: java.io.ObjectInputStream, readEntry: => Entry): Unit = { + _loadFactor = in.readInt() + assert(_loadFactor > 0) + + val size = in.readInt() + tableSize = 0 + assert(size >= 0) + + seedvalue = in.readInt() + + val smDefined = in.readBoolean() + + table = new Array(capacity(sizeForThreshold(_loadFactor, size))) + threshold = newThreshold(_loadFactor, table.length) + + if (smDefined) sizeMapInit(table.length) else sizemap = null + + var index = 0 + while (index < size) { + addEntry(readEntry) + index += 1 + } + } + + /** + * Serializes the collection to the output stream by saving the load factor, collection + * size and collection entries. `writeEntry` is responsible for writing an entry to the stream. + * + * `foreachEntry` determines the order in which the key/value pairs are saved to the stream. To + * deserialize, `init` should be used. + */ + private[collection] def serializeTo(out: java.io.ObjectOutputStream, writeEntry: Entry => Unit): Unit = { + out.writeInt(_loadFactor) + out.writeInt(tableSize) + out.writeInt(seedvalue) + out.writeBoolean(isSizeMapDefined) + + foreachEntry(writeEntry) + } + + /** Find entry with given key in table, null if not found. + */ + final def findEntry(key: A): Entry = + findEntry0(key, index(elemHashCode(key))) + + protected[collection] final def findEntry0(key: A, h: Int): Entry = { + var e = table(h).asInstanceOf[Entry] + while (e != null && !elemEquals(e.key, key)) e = e.next + e + } + + /** Add entry to table + * pre: no entry with same key exists + */ + protected[collection] final def addEntry(e: Entry): Unit = { + addEntry0(e, index(elemHashCode(e.key))) + } + + protected[collection] final def addEntry0(e: Entry, h: Int): Unit = { + e.next = table(h).asInstanceOf[Entry] + table(h) = e + tableSize = tableSize + 1 + nnSizeMapAdd(h) + if (tableSize > threshold) + resize(2 * table.length) + } + + /** Find entry with given key in table, or add new one if not found. + * May be somewhat faster then `findEntry`/`addEntry` pair as it + * computes entry's hash index only once. + * Returns entry found in table or null. + * New entries are created by calling `createNewEntry` method. + */ + def findOrAddEntry(key: A, value: B): Entry = { + val h = index(elemHashCode(key)) + val e = findEntry0(key, h) + if (e ne null) e else { addEntry0(createNewEntry(key, value), h); null } + } + + /** Creates new entry to be immediately inserted into the hashtable. + * This method is guaranteed to be called only once and in case that the entry + * will be added. In other words, an implementation may be side-effecting. + */ + def createNewEntry(key: A, value: B): Entry + + /** Remove entry from table if present. + */ + final def removeEntry(key: A) : Entry = { + removeEntry0(key, index(elemHashCode(key))) + } + /** Remove entry from table if present. + */ + private[collection] final def removeEntry0(key: A, h: Int) : Entry = { + var e = table(h).asInstanceOf[Entry] + if (e != null) { + if (elemEquals(e.key, key)) { + table(h) = e.next + tableSize = tableSize - 1 + nnSizeMapRemove(h) + e.next = null + return e + } else { + var e1 = e.next + while (e1 != null && !elemEquals(e1.key, key)) { + e = e1 + e1 = e1.next + } + if (e1 != null) { + e.next = e1.next + tableSize = tableSize - 1 + nnSizeMapRemove(h) + e1.next = null + return e1 + } + } + } + null + } + + /** An iterator returning all entries. + */ + def entriesIterator: Iterator[Entry] = new AbstractIterator[Entry] { + val iterTable = table + var idx = lastPopulatedIndex + var es = iterTable(idx) + + def hasNext = es != null + def next() = { + val res = es + es = es.next + while (es == null && idx > 0) { + idx = idx - 1 + es = iterTable(idx) + } + res.asInstanceOf[Entry] + } + } + + /** Avoid iterator for a 2x faster traversal. */ + def foreachEntry[U](f: Entry => U): Unit = { + val iterTable = table + var idx = lastPopulatedIndex + var es = iterTable(idx) + + while (es != null) { + val next = es.next // Cache next in case f removes es. + f(es.asInstanceOf[Entry]) + es = next + + while (es == null && idx > 0) { + idx -= 1 + es = iterTable(idx) + } + } + } + + /** Remove all entries from table + */ + def clearTable(): Unit = { + var i = table.length - 1 + while (i >= 0) { table(i) = null; i = i - 1 } + tableSize = 0 + nnSizeMapReset(0) + } + + private def resize(newSize: Int): Unit = { + val oldTable = table + table = new Array(newSize) + nnSizeMapReset(table.length) + var i = oldTable.length - 1 + while (i >= 0) { + var e = oldTable(i) + while (e != null) { + val h = index(elemHashCode(e.key)) + val e1 = e.next + e.next = table(h).asInstanceOf[Entry] + table(h) = e + e = e1 + nnSizeMapAdd(h) + } + i = i - 1 + } + threshold = newThreshold(_loadFactor, newSize) + } + + /* Size map handling code */ + + /* + * The following three sizeMap* functions (Add, Remove, Reset) + * are used to update the size map of the hash table. + * + * The size map logically divides the hash table into `sizeMapBucketSize` element buckets + * by keeping an integer entry for each such bucket. Each integer entry simply denotes + * the number of elements in the corresponding bucket. + * Best understood through an example, see: + * table = [/, 1, /, 6, 90, /, -3, 5] (8 entries) + * sizemap = [ 2 | 3 ] (2 entries) + * where sizeMapBucketSize == 4. + * + * By default the size map is not initialized, so these methods don't do anything, thus, + * their impact on hash table performance is negligible. However, if the hash table + * is converted into a parallel hash table, the size map is initialized, as it will be needed + * there. + */ + protected final def nnSizeMapAdd(h: Int) = if (sizemap ne null) { + sizemap(h >> sizeMapBucketBitSize) += 1 + } + + protected final def nnSizeMapRemove(h: Int) = if (sizemap ne null) { + sizemap(h >> sizeMapBucketBitSize) -= 1 + } + + protected final def nnSizeMapReset(tableLength: Int) = if (sizemap ne null) { + val nsize = calcSizeMapSize(tableLength) + if (sizemap.length != nsize) sizemap = new Array[Int](nsize) + else java.util.Arrays.fill(sizemap, 0) + } + + private[collection] final def totalSizeMapBuckets = if (sizeMapBucketSize < table.length) 1 else table.length / sizeMapBucketSize + + protected final def calcSizeMapSize(tableLength: Int) = (tableLength >> sizeMapBucketBitSize) + 1 + + // discards the previous sizemap and only allocates a new one + protected def sizeMapInit(tableLength: Int): Unit = { + sizemap = new Array[Int](calcSizeMapSize(tableLength)) + } + + // discards the previous sizemap and populates the new one + protected final def sizeMapInitAndRebuild() = { + sizeMapInit(table.length) + + // go through the buckets, count elements + var tableidx = 0 + var bucketidx = 0 + val tbl = table + var tableuntil = 0 + if (tbl.length < sizeMapBucketSize) tableuntil = tbl.length else tableuntil = sizeMapBucketSize + val totalbuckets = totalSizeMapBuckets + while (bucketidx < totalbuckets) { + var currbucketsize = 0 + while (tableidx < tableuntil) { + var e = tbl(tableidx) + while (e ne null) { + currbucketsize += 1 + e = e.next + } + tableidx += 1 + } + sizemap(bucketidx) = currbucketsize + tableuntil += sizeMapBucketSize + bucketidx += 1 + } + } + + private[collection] def printSizeMap() = { + println(sizemap.to(collection.immutable.List)) + } + + protected final def sizeMapDisable() = sizemap = null + + protected final def isSizeMapDefined = sizemap ne null + + // override to automatically initialize the size map + protected def alwaysInitSizeMap = false + + /* End of size map handling code */ + + protected def elemEquals(key1: A, key2: A): Boolean = (key1 == key2) + + /** + * Note: we take the most significant bits of the hashcode, not the lower ones + * this is of crucial importance when populating the table in parallel + */ + protected[collection] final def index(hcode: Int): Int = { + val ones = table.length - 1 + val exponent = Integer.numberOfLeadingZeros(ones) + (improve(hcode, seedvalue) >>> exponent) & ones + } +} + +private[collection] object HashTable { + /** The load factor for the hash table (in 0.001 step). + */ + private[collection] final def defaultLoadFactor: Int = 750 // corresponds to 75% + private[collection] final def loadFactorDenum = 1000 // should be loadFactorDenom, but changing that isn't binary compatible + + private[collection] final def newThreshold(_loadFactor: Int, size: Int) = ((size.toLong * _loadFactor) / loadFactorDenum).toInt + + private[collection] final def sizeForThreshold(_loadFactor: Int, thr: Int) = ((thr.toLong * loadFactorDenum) / _loadFactor).toInt + + private[collection] final def capacity(expectedSize: Int) = nextPositivePowerOfTwo(expectedSize) + + trait HashUtils[KeyType] { + protected final def sizeMapBucketBitSize = 5 + // so that: + protected final def sizeMapBucketSize = 1 << sizeMapBucketBitSize + + protected[collection] def elemHashCode(key: KeyType) = key.## + + /** + * Defer to a high-quality hash in [[scala.util.hashing]]. + * The goal is to distribute across bins as well as possible even if a hash code has low entropy at some bits. + *

+ * OLD VERSION - quick, but bad for sequence 0-10000 - little entropy in higher bits - since 2003 + * {{{ + * var h: Int = hcode + ~(hcode << 9) + * h = h ^ (h >>> 14) + * h = h + (h << 4) + * h ^ (h >>> 10) + * }}} + * the rest of the computation is due to SI-5293 + */ + protected final def improve(hcode: Int, seed: Int): Int = rotateRight(byteswap32(hcode), seed) + } + + /** + * Returns a power of two >= `target`. + */ + private[collection] def nextPositivePowerOfTwo(target: Int): Int = 1 << -numberOfLeadingZeros(target - 1) +} + +/** Class used internally. + */ +private[collection] trait HashEntry[A, E <: HashEntry[A, E]] { + val key: A + var next: E = _ +} diff --git a/library/src/scala/collection/mutable/ImmutableBuilder.scala b/library/src/scala/collection/mutable/ImmutableBuilder.scala new file mode 100644 index 000000000000..6806367e53a4 --- /dev/null +++ b/library/src/scala/collection/mutable/ImmutableBuilder.scala @@ -0,0 +1,32 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` + +/** + * Reusable builder for immutable collections + */ +abstract class ImmutableBuilder[-A, C <: IterableOnce[_]](empty: C) + extends ReusableBuilder[A, C] { + + protected var elems: C = empty + + def clear(): Unit = { elems = empty } + + def result(): C = elems + + override def knownSize: Int = elems.knownSize +} diff --git a/library/src/scala/collection/mutable/IndexedSeq.scala b/library/src/scala/collection/mutable/IndexedSeq.scala new file mode 100644 index 000000000000..31feb34395f8 --- /dev/null +++ b/library/src/scala/collection/mutable/IndexedSeq.scala @@ -0,0 +1,85 @@ +/* + * 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.collection +package mutable + +import scala.language.`2.13` + +trait IndexedSeq[T] extends Seq[T] + with scala.collection.IndexedSeq[T] + with IndexedSeqOps[T, IndexedSeq, IndexedSeq[T]] + with IterableFactoryDefaults[T, IndexedSeq] { + + override def iterableFactory: SeqFactory[IndexedSeq] = IndexedSeq +} + +@SerialVersionUID(3L) +object IndexedSeq extends SeqFactory.Delegate[IndexedSeq](ArrayBuffer) + +transparent trait IndexedSeqOps[A, +CC[_], +C <: AnyRef] + extends scala.collection.IndexedSeqOps[A, CC, C] + with SeqOps[A, CC, C] { + + /** Modifies this $coll by applying a function to all elements of this $coll. + * + * @param f the function to apply to each element. + * @return this $coll modified by replacing all elements with the + * result of applying the given function `f` to each element + * of this $coll. + */ + def mapInPlace(f: A => A): this.type = { + var i = 0 + val siz = size + while (i < siz) { this(i) = f(this(i)); i += 1 } + this + } + + /** Sorts this $coll in place according to an Ordering. + * + * @see [[scala.collection.SeqOps.sorted]] + * @param ord the ordering to be used to compare elements. + * @return modified input $coll sorted according to the ordering `ord`. + */ + def sortInPlace[B >: A]()(implicit ord: Ordering[B]): this.type = { + val len = this.length + if (len > 1) { + val arr = new Array[AnyRef](len) + var i = 0 + for (x <- this) { + arr(i) = x.asInstanceOf[AnyRef] + i += 1 + } + java.util.Arrays.sort(arr, ord.asInstanceOf[Ordering[Object]]) + i = 0 + while (i < arr.length) { + update(i, arr(i).asInstanceOf[A]) + i += 1 + } + } + this + } + + /** Sorts this $coll in place according to a comparison function. + * + * @see [[scala.collection.SeqOps.sortWith]] + */ + def sortInPlaceWith(lt: (A, A) => Boolean): this.type = sortInPlace()(Ordering.fromLessThan(lt)) + + /** Sorts this $coll in place according to the Ordering which results from transforming + * an implicitly given Ordering with a transformation function. + * + * @see [[scala.collection.SeqOps.sortBy]] + */ + def sortInPlaceBy[B](f: A => B)(implicit ord: Ordering[B]): this.type = sortInPlace()(ord on f) + +} diff --git a/library/src/scala/collection/mutable/Iterable.scala b/library/src/scala/collection/mutable/Iterable.scala new file mode 100644 index 000000000000..7f018b00c17f --- /dev/null +++ b/library/src/scala/collection/mutable/Iterable.scala @@ -0,0 +1,35 @@ +/* + * 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.collection.mutable + +import scala.language.`2.13` +import scala.collection.{IterableFactory, IterableFactoryDefaults} + +trait Iterable[A] + extends collection.Iterable[A] + with collection.IterableOps[A, Iterable, Iterable[A]] + with IterableFactoryDefaults[A, Iterable] { + + override def iterableFactory: IterableFactory[Iterable] = Iterable +} + +/** + * $factoryInfo + * @define coll mutable collection + * @define Coll `mutable.Iterable` + */ +@SerialVersionUID(3L) +object Iterable extends IterableFactory.Delegate[Iterable](ArrayBuffer) + +/** Explicit instantiation of the `Iterable` trait to reduce class file size in subclasses. */ +abstract class AbstractIterable[A] extends scala.collection.AbstractIterable[A] with Iterable[A] diff --git a/library/src/scala/collection/mutable/LinkedHashMap.scala b/library/src/scala/collection/mutable/LinkedHashMap.scala new file mode 100644 index 000000000000..5a4f9285972d --- /dev/null +++ b/library/src/scala/collection/mutable/LinkedHashMap.scala @@ -0,0 +1,510 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` +import scala.annotation.{nowarn, tailrec} +import scala.collection.generic.DefaultSerializable +import scala.util.hashing.MurmurHash3 + + +/** This class implements mutable maps using a hashtable. + * The iterator and all traversal methods of this class visit elements in the order they were inserted. + * + * @tparam K the type of the keys contained in this hash map. + * @tparam V the type of the values assigned to keys in this hash map. + * + * @define Coll `LinkedHashMap` + * @define coll linked hash map + * @define mayNotTerminateInf + * @define willNotTerminateInf + * @define orderDependent + * @define orderDependentFold + */ +@deprecatedInheritance("LinkedHashMap will be made final; use .withDefault for the common use case of computing a default value", "2.13.11") +class LinkedHashMap[K, V] + extends AbstractMap[K, V] + with SeqMap[K, V] + with MapOps[K, V, LinkedHashMap, LinkedHashMap[K, V]] + with StrictOptimizedIterableOps[(K, V), Iterable, LinkedHashMap[K, V]] + with StrictOptimizedMapOps[K, V, LinkedHashMap, LinkedHashMap[K, V]] + with MapFactoryDefaults[K, V, LinkedHashMap, Iterable] + with DefaultSerializable { + + override def mapFactory: MapFactory[LinkedHashMap] = LinkedHashMap + + // stepper / keyStepper / valueStepper are not overridden to use XTableStepper because that stepper + // would not return the elements in insertion order + + private[collection] type Entry = LinkedHashMap.LinkedEntry[K, V] + + private[collection] def _firstEntry: Entry = firstEntry + + protected var firstEntry: Entry = null + + protected var lastEntry: Entry = null + + /* Uses the same implementation as mutable.HashMap. The hashtable holds the following invariant: + * - For each i between 0 and table.length, the bucket at table(i) only contains keys whose hash-index is i. + * - Every bucket is sorted in ascendant hash order + * - The sum of the lengths of all buckets is equal to contentSize. + */ + private[this] var table = new Array[Entry](tableSizeFor(LinkedHashMap.defaultinitialSize)) + + private[this] var threshold: Int = newThreshold(table.length) + + private[this] var contentSize = 0 + + override def last: (K, V) = + if (size > 0) (lastEntry.key, lastEntry.value) + else throw new NoSuchElementException("Cannot call .last on empty LinkedHashMap") + + override def lastOption: Option[(K, V)] = + if (size > 0) Some((lastEntry.key, lastEntry.value)) + else None + + override def head: (K, V) = + if (size > 0) (firstEntry.key, firstEntry.value) + else throw new NoSuchElementException("Cannot call .head on empty LinkedHashMap") + + override def headOption: Option[(K, V)] = + if (size > 0) Some((firstEntry.key, firstEntry.value)) + else None + + override def size = contentSize + override def knownSize: Int = size + override def isEmpty: Boolean = size == 0 + + def get(key: K): Option[V] = { + val e = findEntry(key) + if (e == null) None + else Some(e.value) + } + override def sizeHint(size: Int): Unit = { + val target = tableSizeFor(((size + 1).toDouble / LinkedHashMap.defaultLoadFactor).toInt) + if (target > table.length) growTable(target) + } + + override def contains(key: K): Boolean = { + if (getClass eq classOf[LinkedHashMap[_, _]]) + findEntry(key) != null + else + super.contains(key) // A subclass might override `get`, use the default implementation `contains`. + } + + override def put(key: K, value: V): Option[V] = put0(key, value, getOld = true) match { + case null => None + case sm => sm + } + + override def update(key: K, value: V): Unit = put0(key, value, getOld = false) + + override def remove(key: K): Option[V] = removeEntry0(key) match { + case null => None + case nd => Some(nd.value) + } + + override def getOrElse[V1 >: V](key: K, default: => V1): V1 = { + if (getClass != classOf[LinkedHashMap[_, _]]) { + // subclasses of LinkedHashMap might customise `get` ... + super.getOrElse(key, default) + } else { + // .. but in the common case, we can avoid the Option boxing. + val nd = findEntry(key) + if (nd eq null) default else nd.value + } + } + + override def getOrElseUpdate(key: K, defaultValue: => V): V = { + if (getClass != classOf[LinkedHashMap[_, _]]) { + // subclasses of LinkedHashMap might customise `get` ... + super.getOrElseUpdate(key, defaultValue) + } else { + val hash = computeHash(key) + val idx = index(hash) + val nd = table(idx) match { + case null => null + case nd => nd.findEntry(key, hash) + } + if (nd != null) nd.value + else { + val table0 = table + val default = defaultValue + if (contentSize + 1 >= threshold) growTable(table.length * 2) + // Avoid recomputing index if the `defaultValue()` or new element hasn't triggered a table resize. + val newIdx = if (table0 eq table) idx else index(hash) + put0(key, default, getOld = false, hash, newIdx) + default + } + } + } + + private[this] def removeEntry0(elem: K): Entry = removeEntry0(elem, computeHash(elem)) + + /** Removes a key from this map if it exists + * + * @param elem the element to remove + * @param hash the **improved** hashcode of `element` (see computeHash) + * @return the node that contained element if it was present, otherwise null + */ + private[this] def removeEntry0(elem: K, hash: Int): Entry = { + val idx = index(hash) + table(idx) match { + case null => null + case nd if nd.hash == hash && nd.key == elem => + // first element matches + table(idx) = nd.next + deleteEntry(nd) + contentSize -= 1 + nd + case nd => + // find an element that matches + var prev = nd + var next = nd.next + while ((next ne null) && next.hash <= hash) { + if (next.hash == hash && next.key == elem) { + prev.next = next.next + deleteEntry(next) + contentSize -= 1 + return next + } + prev = next + next = next.next + } + null + } + } + + /** Computes the improved hash of an original (`any.##`) hash. */ + @`inline` private[this] def improveHash(originalHash: Int): Int = { + originalHash ^ (originalHash >>> 16) + } + @`inline` private[collection] def unimproveHash(improvedHash: Int): Int = improveHash(improvedHash) + + /** Computes the improved hash of this key */ + @`inline` private[this] def computeHash(o: K): Int = improveHash(o.##) + + @`inline` private[this] def index(hash: Int) = hash & (table.length - 1) + + @`inline` private[this] def findEntry(key: K): Entry = { + val hash = computeHash(key) + table(index(hash)) match { + case null => null + case nd => nd.findEntry(key, hash) + } + } + + def addOne(kv: (K, V)): this.type = { + put(kv._1, kv._2) + this + } + + def subtractOne(key: K): this.type = { + remove(key) + this + } + + private[this] abstract class LinkedHashMapIterator[T] extends AbstractIterator[T] { + private[this] var cur = firstEntry + def extract(nd: Entry): T + def hasNext: Boolean = cur ne null + def next(): T = + if (hasNext) { val r = extract(cur); cur = cur.later; r } + else Iterator.empty.next() + } + + def iterator: Iterator[(K, V)] = + if (size == 0) Iterator.empty + else new LinkedHashMapIterator[(K, V)] { + def extract(nd: Entry): (K, V) = (nd.key, nd.value) + } + + protected class LinkedKeySet extends KeySet { + override def iterableFactory: IterableFactory[collection.Set] = LinkedHashSet + } + + override def keySet: collection.Set[K] = new LinkedKeySet + + override def keysIterator: Iterator[K] = + if (size == 0) Iterator.empty + else new LinkedHashMapIterator[K] { + def extract(nd: Entry): K = nd.key + } + + private[collection] def entryIterator: Iterator[Entry] = + if (size == 0) Iterator.empty + else new LinkedHashMapIterator[Entry] { + def extract(nd: Entry): Entry = nd + } + + + // Override updateWith for performance, so we can do the update while hashing + // the input key only once and performing one lookup into the hash table + override def updateWith(key: K)(remappingFunction: Option[V] => Option[V]): Option[V] = { + if (getClass != classOf[LinkedHashMap[_, _]]) { + // subclasses of LinkedHashMap might customise `get` ... + super.updateWith(key)(remappingFunction) + } else { + val hash = computeHash(key) + val indexedHash = index(hash) + + var foundEntry: Entry = null + var previousEntry: Entry = null + table(indexedHash) match { + case null => + case nd => + @tailrec + def findEntry(prev: Entry, nd: Entry, k: K, h: Int): Unit = { + if (h == nd.hash && k == nd.key) { + previousEntry = prev + foundEntry = nd + } + else if ((nd.next eq null) || (nd.hash > h)) () + else findEntry(nd, nd.next, k, h) + } + + findEntry(null, nd, key, hash) + } + + val previousValue = foundEntry match { + case null => None + case nd => Some(nd.value) + } + + val nextValue = remappingFunction(previousValue) + + (previousValue, nextValue) match { + case (None, None) => // do nothing + + case (Some(_), None) => + if (previousEntry != null) previousEntry.next = foundEntry.next + else table(indexedHash) = foundEntry.next + deleteEntry(foundEntry) + contentSize -= 1 + + case (None, Some(value)) => + val newIndexedHash = + if (contentSize + 1 >= threshold) { + growTable(table.length * 2) + index(hash) + } else indexedHash + put0(key, value, getOld = false, hash, newIndexedHash) + + case (Some(_), Some(newValue)) => foundEntry.value = newValue + } + nextValue + } + } + + override def valuesIterator: Iterator[V] = + if (size == 0) Iterator.empty + else new LinkedHashMapIterator[V] { + def extract(nd: Entry): V = nd.value + } + + + override def foreach[U](f: ((K, V)) => U): Unit = { + var cur = firstEntry + while (cur ne null) { + f((cur.key, cur.value)) + cur = cur.later + } + } + + override def foreachEntry[U](f: (K, V) => U): Unit = { + var cur = firstEntry + while (cur ne null) { + f(cur.key, cur.value) + cur = cur.later + } + } + + override def clear(): Unit = { + java.util.Arrays.fill(table.asInstanceOf[Array[AnyRef]], null) + contentSize = 0 + firstEntry = null + lastEntry = null + } + + private[this] def tableSizeFor(capacity: Int) = + (Integer.highestOneBit((capacity - 1).max(4)) * 2).min(1 << 30) + + private[this] def newThreshold(size: Int) = (size.toDouble * LinkedHashMap.defaultLoadFactor).toInt + + /*create a new entry. If table is empty(firstEntry is null), then the + * new entry will be the firstEntry. If not, just set the new entry to + * be the lastEntry. + * */ + private[this] def createNewEntry(key: K, hash: Int, value: V): Entry = { + val e = new Entry(key, hash, value) + if (firstEntry eq null) firstEntry = e + else { + lastEntry.later = e + e.earlier = lastEntry + } + lastEntry = e + e + } + + /** Delete the entry from the LinkedHashMap, set the `earlier` and `later` pointers correctly */ + private[this] def deleteEntry(e: Entry): Unit = { + if (e.earlier eq null) firstEntry = e.later + else e.earlier.later = e.later + if (e.later eq null) lastEntry = e.earlier + else e.later.earlier = e.earlier + e.earlier = null + e.later = null + e.next = null + } + + private[this] def put0(key: K, value: V, getOld: Boolean): Some[V] = { + if (contentSize + 1 >= threshold) growTable(table.length * 2) + val hash = computeHash(key) + val idx = index(hash) + put0(key, value, getOld, hash, idx) + } + + private[this] def put0(key: K, value: V, getOld: Boolean, hash: Int, idx: Int): Some[V] = { + table(idx) match { + case null => + table(idx) = createNewEntry(key, hash, value) + case old => + var prev: Entry = null + var n = old + while ((n ne null) && n.hash <= hash) { + if (n.hash == hash && key == n.key) { + val old = n.value + n.value = value + return if (getOld) Some(old) else null + } + prev = n + n = n.next + } + val nnode = createNewEntry(key, hash, value) + if (prev eq null) { + nnode.next = old + table(idx) = nnode + } else { + nnode.next = prev.next + prev.next = nnode + } + } + contentSize += 1 + null + } + + private[this] def growTable(newlen: Int): Unit = { + if (newlen < 0) + throw new RuntimeException(s"new hash table size $newlen exceeds maximum") + var oldlen = table.length + threshold = newThreshold(newlen) + if (size == 0) table = new Array(newlen) + else { + table = java.util.Arrays.copyOf(table, newlen) + val preLow = new Entry(null.asInstanceOf[K], 0, null.asInstanceOf[V]) + val preHigh = new Entry(null.asInstanceOf[K], 0, null.asInstanceOf[V]) + // Split buckets until the new length has been reached. This could be done more + // efficiently when growing an already filled table to more than double the size. + while (oldlen < newlen) { + var i = 0 + while (i < oldlen) { + val old = table(i) + if (old ne null) { + preLow.next = null + preHigh.next = null + var lastLow = preLow + var lastHigh = preHigh + var n = old + while (n ne null) { + val next = n.next + if ((n.hash & oldlen) == 0) { // keep low + lastLow.next = n + lastLow = n + } else { // move to high + lastHigh.next = n + lastHigh = n + } + n = next + } + lastLow.next = null + if (old ne preLow.next) table(i) = preLow.next + if (preHigh.next ne null) { + table(i + oldlen) = preHigh.next + lastHigh.next = null + } + } + i += 1 + } + oldlen *= 2 + } + } + } + + override def hashCode: Int = { + if (isEmpty) MurmurHash3.emptyMapHash + else { + val tupleHashIterator = new LinkedHashMapIterator[Any] { + var hash: Int = 0 + override def hashCode: Int = hash + override def extract(nd: Entry): Any = { + hash = MurmurHash3.tuple2Hash(unimproveHash(nd.hash), nd.value.##) + this + } + } + MurmurHash3.unorderedHash(tupleHashIterator, MurmurHash3.mapSeed) + } + } + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix = "LinkedHashMap" +} + +/** $factoryInfo + * @define Coll `LinkedHashMap` + * @define coll linked hash map + */ +@SerialVersionUID(3L) +object LinkedHashMap extends MapFactory[LinkedHashMap] { + + def empty[K, V] = new LinkedHashMap[K, V] + + def from[K, V](it: collection.IterableOnce[(K, V)]) = { + val newlhm = empty[K, V] + newlhm.sizeHint(it, delta = 0) + newlhm.addAll(it) + newlhm + } + + def newBuilder[K, V]: GrowableBuilder[(K, V), LinkedHashMap[K, V]] = new GrowableBuilder(empty[K, V]) + + /** Class for the linked hash map entry, used internally. + */ + private[mutable] final class LinkedEntry[K, V](val key: K, val hash: Int, var value: V) { + var earlier: LinkedEntry[K, V] = null + var later: LinkedEntry[K, V] = null + var next: LinkedEntry[K, V] = null + + @tailrec + final def findEntry(k: K, h: Int): LinkedEntry[K, V] = + if (h == hash && k == key) this + else if ((next eq null) || (hash > h)) null + else next.findEntry(k, h) + } + + /** The default load factor for the hash table */ + private[collection] final def defaultLoadFactor: Double = 0.75 + + /** The default initial capacity for the hash table */ + private[collection] final def defaultinitialSize: Int = 16 +} diff --git a/library/src/scala/collection/mutable/LinkedHashSet.scala b/library/src/scala/collection/mutable/LinkedHashSet.scala new file mode 100644 index 000000000000..79d3d62a41d1 --- /dev/null +++ b/library/src/scala/collection/mutable/LinkedHashSet.scala @@ -0,0 +1,349 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` +import scala.annotation.{nowarn, tailrec} +import scala.collection.generic.DefaultSerializable +import scala.util.hashing.MurmurHash3 + +/** This class implements mutable sets using a hashtable. + * The iterator and all traversal methods of this class visit elements in the order they were inserted. + * + * @tparam A the type of the elements contained in this set. + * + * @define Coll `LinkedHashSet` + * @define coll linked hash set + * @define mayNotTerminateInf + * @define willNotTerminateInf + * @define orderDependent + * @define orderDependentFold + */ +@deprecatedInheritance("LinkedHashSet will be made final", "2.13.11") +class LinkedHashSet[A] + extends AbstractSet[A] + with SetOps[A, LinkedHashSet, LinkedHashSet[A]] + with StrictOptimizedIterableOps[A, LinkedHashSet, LinkedHashSet[A]] + with IterableFactoryDefaults[A, LinkedHashSet] + with DefaultSerializable { + + override def iterableFactory: IterableFactory[LinkedHashSet] = LinkedHashSet + + // stepper is not overridden to use XTableStepper because that stepper would not return the + // elements in insertion order + + /*private*/ type Entry = LinkedHashSet.Entry[A] + + protected var firstEntry: Entry = null + + protected var lastEntry: Entry = null + + /* Uses the same implementation as mutable.HashSet. The hashtable holds the following invariant: + * - For each i between 0 and table.length, the bucket at table(i) only contains keys whose hash-index is i. + * - Every bucket is sorted in ascendant hash order + * - The sum of the lengths of all buckets is equal to contentSize. + */ + private[this] var table = new Array[Entry](tableSizeFor(LinkedHashSet.defaultinitialSize)) + + private[this] var threshold: Int = newThreshold(table.length) + + private[this] var contentSize = 0 + + override def last: A = + if (size > 0) lastEntry.key + else throw new NoSuchElementException("Cannot call .last on empty LinkedHashSet") + + override def lastOption: Option[A] = + if (size > 0) Some(lastEntry.key) + else None + + override def head: A = + if (size > 0) firstEntry.key + else throw new NoSuchElementException("Cannot call .head on empty LinkedHashSet") + + override def headOption: Option[A] = + if (size > 0) Some(firstEntry.key) + else None + + override def size: Int = contentSize + override def knownSize: Int = size + override def isEmpty: Boolean = size == 0 + + def contains(elem: A): Boolean = findEntry(elem) ne null + + override def sizeHint(size: Int): Unit = { + val target = tableSizeFor(((size + 1).toDouble / LinkedHashSet.defaultLoadFactor).toInt) + if (target > table.length) growTable(target) + } + + override def add(elem: A): Boolean = { + if (contentSize + 1 >= threshold) growTable(table.length * 2) + val hash = computeHash(elem) + put0(elem, hash, index(hash)) + } + + def addOne(elem: A): this.type = { + add(elem) + this + } + + def subtractOne(elem: A): this.type = { + remove(elem) + this + } + + override def remove(elem: A): Boolean = remove0(elem, computeHash(elem)) + + private[this] abstract class LinkedHashSetIterator[T] extends AbstractIterator[T] { + private[this] var cur = firstEntry + def extract(nd: Entry): T + def hasNext: Boolean = cur ne null + def next(): T = + if (hasNext) { val r = extract(cur); cur = cur.later; r } + else Iterator.empty.next() + } + + def iterator: Iterator[A] = new LinkedHashSetIterator[A] { + override def extract(nd: Entry): A = nd.key + } + + private[collection] def entryIterator: Iterator[Entry] = new LinkedHashSetIterator[Entry] { + override def extract(nd: Entry): Entry = nd + } + + override def foreach[U](f: A => U): Unit = { + var cur = firstEntry + while (cur ne null) { + f(cur.key) + cur = cur.later + } + } + + override def clear(): Unit = { + java.util.Arrays.fill(table.asInstanceOf[Array[AnyRef]], null) + contentSize = 0 + firstEntry = null + lastEntry = null + } + + private[this] def tableSizeFor(capacity: Int) = + (Integer.highestOneBit((capacity - 1).max(4)) * 2).min(1 << 30) + + private[this] def newThreshold(size: Int) = (size.toDouble * LinkedHashSet.defaultLoadFactor).toInt + + @`inline` private[this] def improveHash(originalHash: Int): Int = { + originalHash ^ (originalHash >>> 16) + } + + @`inline` private[collection] def unimproveHash(improvedHash: Int): Int = improveHash(improvedHash) + + /** Computes the improved hash of this key */ + @`inline` private[this] def computeHash(o: A): Int = improveHash(o.##) + + @`inline` private[this] def index(hash: Int) = hash & (table.length - 1) + + @`inline` private[this] def findEntry(key: A): Entry = { + val hash = computeHash(key) + table(index(hash)) match { + case null => null + case nd => nd.findEntry(key, hash) + } + } + + /*create a new entry. If table is empty(firstEntry is null), then the + * new entry will be the firstEntry. If not, just set the new entry to + * be the lastEntry. + * */ + private[this] def createNewEntry(key: A, hash: Int): Entry = { + val e = new Entry(key, hash) + if (firstEntry eq null) firstEntry = e + else { + lastEntry.later = e + e.earlier = lastEntry + } + lastEntry = e + e + } + + /** Delete the entry from the LinkedHashSet, set the `earlier` and `later` pointers correctly */ + private[this] def deleteEntry(e: Entry): Unit = { + if (e.earlier eq null) firstEntry = e.later + else e.earlier.later = e.later + if (e.later eq null) lastEntry = e.earlier + else e.later.earlier = e.earlier + e.earlier = null + e.later = null + e.next = null + } + + private[this] def put0(elem: A, hash: Int, idx: Int): Boolean = { + table(idx) match { + case null => + table(idx) = createNewEntry(elem, hash) + case old => + var prev: Entry = null + var n = old + while ((n ne null) && n.hash <= hash) { + if (n.hash == hash && elem == n.key) return false + prev = n + n = n.next + } + val nnode = createNewEntry(elem, hash) + if (prev eq null) { + nnode.next = old + table(idx) = nnode + } else { + nnode.next = prev.next + prev.next = nnode + } + } + contentSize += 1 + true + } + + private[this] def remove0(elem: A, hash: Int): Boolean = { + val idx = index(hash) + table(idx) match { + case null => false + case nd if nd.hash == hash && nd.key == elem => + // first element matches + table(idx) = nd.next + deleteEntry(nd) + contentSize -= 1 + true + case nd => + // find an element that matches + var prev = nd + var next = nd.next + while ((next ne null) && next.hash <= hash) { + if (next.hash == hash && next.key == elem) { + prev.next = next.next + deleteEntry(next) + contentSize -= 1 + return true + } + prev = next + next = next.next + } + false + } + } + + private[this] def growTable(newlen: Int): Unit = { + if (newlen < 0) + throw new RuntimeException(s"new hash table size $newlen exceeds maximum") + var oldlen = table.length + threshold = newThreshold(newlen) + if (size == 0) table = new Array(newlen) + else { + table = java.util.Arrays.copyOf(table, newlen) + val preLow = new Entry(null.asInstanceOf[A], 0) + val preHigh = new Entry(null.asInstanceOf[A], 0) + // Split buckets until the new length has been reached. This could be done more + // efficiently when growing an already filled table to more than double the size. + while (oldlen < newlen) { + var i = 0 + while (i < oldlen) { + val old = table(i) + if (old ne null) { + preLow.next = null + preHigh.next = null + var lastLow = preLow + var lastHigh = preHigh + var n = old + while (n ne null) { + val next = n.next + if ((n.hash & oldlen) == 0) { // keep low + lastLow.next = n + lastLow = n + } else { // move to high + lastHigh.next = n + lastHigh = n + } + n = next + } + lastLow.next = null + if (old ne preLow.next) table(i) = preLow.next + if (preHigh.next ne null) { + table(i + oldlen) = preHigh.next + lastHigh.next = null + } + } + i += 1 + } + oldlen *= 2 + } + } + } + + override def hashCode: Int = { + val setHashIterator = + if (isEmpty) this.iterator + else { + new LinkedHashSetIterator[Any] { + var hash: Int = 0 + override def hashCode: Int = hash + override def extract(nd: Entry): Any = { + hash = unimproveHash(nd.hash) + this + } + } + } + MurmurHash3.unorderedHash(setHashIterator, MurmurHash3.setSeed) + } + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix = "LinkedHashSet" +} + +/** $factoryInfo + * @define Coll `LinkedHashSet` + * @define coll linked hash set + */ +@SerialVersionUID(3L) +object LinkedHashSet extends IterableFactory[LinkedHashSet] { + + override def empty[A]: LinkedHashSet[A] = new LinkedHashSet[A] + + def from[E](it: collection.IterableOnce[E]) = { + val newlhs = empty[E] + newlhs.sizeHint(it, delta = 0) + newlhs.addAll(it) + newlhs + } + + def newBuilder[A]: GrowableBuilder[A, LinkedHashSet[A]] = new GrowableBuilder(empty[A]) + + /** Class for the linked hash set entry, used internally. + */ + private[mutable] final class Entry[A](val key: A, val hash: Int) { + var earlier: Entry[A] = null + var later: Entry[A] = null + var next: Entry[A] = null + + @tailrec + final def findEntry(k: A, h: Int): Entry[A] = + if (h == hash && k == key) this + else if ((next eq null) || (hash > h)) null + else next.findEntry(k, h) + } + + /** The default load factor for the hash table */ + private[collection] final def defaultLoadFactor: Double = 0.75 + + /** The default initial capacity for the hash table */ + private[collection] final def defaultinitialSize: Int = 16 +} + diff --git a/library/src/scala/collection/mutable/ListBuffer.scala b/library/src/scala/collection/mutable/ListBuffer.scala new file mode 100644 index 000000000000..e5858f79eb5e --- /dev/null +++ b/library/src/scala/collection/mutable/ListBuffer.scala @@ -0,0 +1,421 @@ +/* + * 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.collection +package mutable + +import scala.language.`2.13` +import scala.annotation.{nowarn, tailrec} +import scala.collection.generic.CommonErrors +import scala.collection.immutable.{::, List, Nil} +import java.lang.{IllegalArgumentException, IndexOutOfBoundsException} + +import scala.collection.generic.DefaultSerializable +import scala.runtime.Statics.releaseFence + +/** A `Buffer` implementation backed by a list. It provides constant time + * prepend and append. Most other operations are linear. + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html#list-buffers "Scala's Collection Library overview"]] + * section on `List Buffers` for more information. + * + * @tparam A the type of this list buffer's elements. + * + * @define Coll `ListBuffer` + * @define coll list buffer + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +@SerialVersionUID(-8428291952499836345L) +class ListBuffer[A] + extends AbstractBuffer[A] + with SeqOps[A, ListBuffer, ListBuffer[A]] + with StrictOptimizedSeqOps[A, ListBuffer, ListBuffer[A]] + with ReusableBuilder[A, immutable.List[A]] + with IterableFactoryDefaults[A, ListBuffer] + with DefaultSerializable { + @transient private[this] var mutationCount: Int = 0 + + private var first: List[A] = Nil + private var last0: ::[A] = null // last element (`last0` just because the name `last` is already taken) + private[this] var aliased = false + private[this] var len = 0 + + private type Predecessor[A0] = ::[A0] /*| Null*/ + + def iterator: Iterator[A] = new MutationTracker.CheckedIterator(first.iterator, mutationCount) + + override def iterableFactory: SeqFactory[ListBuffer] = ListBuffer + + @throws[IndexOutOfBoundsException] + def apply(i: Int) = first.apply(i) + + def length = len + override def knownSize = len + + override def isEmpty: Boolean = len == 0 + + private def copyElems(): Unit = { + val buf = new ListBuffer[A].freshFrom(this) + first = buf.first + last0 = buf.last0 + aliased = false + } + + // we only call this before mutating things, so it's + // a good place to track mutations for the iterator + private def ensureUnaliased(): Unit = { + mutationCount += 1 + if (aliased) copyElems() + } + + // Avoids copying where possible. + override def toList: List[A] = { + aliased = nonEmpty + // We've accumulated a number of mutations to `List.tail` by this stage. + // Make sure they are visible to threads that the client of this ListBuffer might be about + // to share this List with. + releaseFence() + first + } + + def result(): immutable.List[A] = toList + + /** Prepends the elements of this buffer to a given list + * + * @param xs the list to which elements are prepended + */ + def prependToList(xs: List[A]): List[A] = { + if (isEmpty) xs + else { + ensureUnaliased() + last0.next = xs + toList + } + } + + def clear(): Unit = { + mutationCount += 1 + first = Nil + len = 0 + last0 = null + aliased = false + } + + final def addOne(elem: A): this.type = { + ensureUnaliased() + val last1 = new ::[A](elem, Nil) + if (len == 0) first = last1 else last0.next = last1 + last0 = last1 + len += 1 + this + } + + // MUST only be called on fresh instances + private def freshFrom(xs: IterableOnce[A]): this.type = { + val it = xs.iterator + if (it.hasNext) { + var len = 1 + var last0 = new ::[A](it.next(), Nil) + first = last0 + while (it.hasNext) { + val last1 = new ::[A](it.next(), Nil) + last0.next = last1 + last0 = last1 + len += 1 + } + // copy local vars into instance + this.len = len + this.last0 = last0 + } + this + } + + override final def addAll(xs: IterableOnce[A]): this.type = { + val it = xs.iterator + if (it.hasNext) { + val fresh = new ListBuffer[A].freshFrom(it) + ensureUnaliased() + if (len == 0) first = fresh.first + else last0.next = fresh.first + last0 = fresh.last0 + len += fresh.length + } + this + } + + override def subtractOne(elem: A): this.type = { + ensureUnaliased() + if (isEmpty) {} + else if (first.head == elem) { + first = first.tail + reduceLengthBy(1) + } + else { + var cursor = first + while (!cursor.tail.isEmpty && cursor.tail.head != elem) { + cursor = cursor.tail + } + if (!cursor.tail.isEmpty) { + val z = cursor.asInstanceOf[::[A]] + if (z.next == last0) + last0 = z + z.next = cursor.tail.tail + reduceLengthBy(1) + } + } + this + } + + /** Reduce the length of the buffer, and null out last0 + * if this reduces the length to 0. + */ + private def reduceLengthBy(num: Int): Unit = { + len -= num + if (len <= 0) // obviously shouldn't be < 0, but still better not to leak + last0 = null + } + + private def locate(i: Int): Predecessor[A] = + if (i == 0) null + else if (i == len) last0 + else { + var j = i - 1 + var p = first + while (j > 0) { + p = p.tail + j -= 1 + } + p.asInstanceOf[Predecessor[A]] + } + + private def getNext(p: Predecessor[A]): List[A] = + if (p == null) first else p.next + + def update(idx: Int, elem: A): Unit = { + ensureUnaliased() + if (idx < 0 || idx >= len) throw CommonErrors.indexOutOfBounds(index = idx, max = len - 1) + if (idx == 0) { + val newElem = new :: (elem, first.tail) + if (last0 eq first) { + last0 = newElem + } + first = newElem + } else { + // `p` can not be `null` because the case where `idx == 0` is handled above + val p = locate(idx) + val newElem = new :: (elem, p.tail.tail) + if (last0 eq p.tail) { + last0 = newElem + } + p.asInstanceOf[::[A]].next = newElem + } + } + + def insert(idx: Int, elem: A): Unit = { + ensureUnaliased() + if (idx < 0 || idx > len) throw CommonErrors.indexOutOfBounds(index = idx, max = len - 1) + if (idx == len) addOne(elem) + else { + val p = locate(idx) + val nx = elem :: getNext(p) + if(p eq null) first = nx else p.next = nx + len += 1 + } + } + + def prepend(elem: A): this.type = { + insert(0, elem) + this + } + + // `fresh` must be a `ListBuffer` that only we have access to + private def insertAfter(prev: Predecessor[A], fresh: ListBuffer[A]): Unit = { + if (!fresh.isEmpty) { + val follow = getNext(prev) + if (prev eq null) first = fresh.first else prev.next = fresh.first + fresh.last0.next = follow + if (follow.isEmpty) last0 = fresh.last0 + len += fresh.length + } + } + + def insertAll(idx: Int, elems: IterableOnce[A]): Unit = { + if (idx < 0 || idx > len) throw CommonErrors.indexOutOfBounds(index = idx, max = len - 1) + val it = elems.iterator + if (it.hasNext) { + if (idx == len) addAll(it) + else { + val fresh = new ListBuffer[A].freshFrom(it) + ensureUnaliased() + insertAfter(locate(idx), fresh) + } + } + } + + def remove(idx: Int): A = { + ensureUnaliased() + if (idx < 0 || idx >= len) throw CommonErrors.indexOutOfBounds(index = idx, max = len - 1) + val p = locate(idx) + val nx = getNext(p) + if(p eq null) { + first = nx.tail + if(first.isEmpty) last0 = null + } else { + if(last0 eq nx) last0 = p + p.next = nx.tail + } + len -= 1 + nx.head + } + + def remove(idx: Int, count: Int): Unit = + if (count > 0) { + ensureUnaliased() + if (idx < 0 || idx + count > len) throw new IndexOutOfBoundsException(s"$idx to ${idx + count} is out of bounds (min 0, max ${len - 1})") + removeAfter(locate(idx), count) + } else if (count < 0) { + throw new IllegalArgumentException("removing negative number of elements: " + count) + } + + private def removeAfter(prev: Predecessor[A], n: Int) = { + @tailrec def ahead(p: List[A], n: Int): List[A] = + if (n == 0) p else ahead(p.tail, n - 1) + val nx = ahead(getNext(prev), n) + if(prev eq null) first = nx else prev.next = nx + if(nx.isEmpty) last0 = prev + len -= n + } + + /** Replace the contents of this $coll with the mapped result. + * + * @param f the mapping function + * @return this $coll + */ + def mapInPlace(f: A => A): this.type = { + mutationCount += 1 + val buf = new ListBuffer[A] + for (elem <- this) buf += f(elem) + first = buf.first + last0 = buf.last0 + aliased = false // we just assigned from a new instance + this + } + + /** Replace the contents of this $coll with the flatmapped result. + * + * @param f the mapping function + * @return this $coll + */ + def flatMapInPlace(f: A => IterableOnce[A]): this.type = { + mutationCount += 1 + var src = first + var dst: List[A] = null + last0 = null + len = 0 + while(!src.isEmpty) { + val it = f(src.head).iterator + while(it.hasNext) { + val v = new ::(it.next(), Nil) + if(dst eq null) dst = v else last0.next = v + last0 = v + len += 1 + } + src = src.tail + } + first = if(dst eq null) Nil else dst + aliased = false // we just rebuilt a fresh, unaliased instance + this + } + + /** Replace the contents of this $coll with the filtered result. + * + * @param p the filtering predicate + * @return this $coll + */ + def filterInPlace(p: A => Boolean): this.type = { + ensureUnaliased() + var prev: Predecessor[A] = null + var cur: List[A] = first + while (!cur.isEmpty) { + val follow = cur.tail + if (!p(cur.head)) { + if(prev eq null) first = follow + else prev.next = follow + len -= 1 + } else { + prev = cur.asInstanceOf[Predecessor[A]] + } + cur = follow + } + last0 = prev + this + } + + def patchInPlace(from: Int, patch: collection.IterableOnce[A], replaced: Int): this.type = { + val _len = len + val _from = math.max(from, 0) // normalized + val _replaced = math.max(replaced, 0) // normalized + val it = patch.iterator + + val nonEmptyPatch = it.hasNext + val nonEmptyReplace = (_from < _len) && (_replaced > 0) + + // don't want to add a mutation or check aliasing (potentially expensive) + // if there's no patching to do + if (nonEmptyPatch || nonEmptyReplace) { + val fresh = new ListBuffer[A].freshFrom(it) + ensureUnaliased() + val i = math.min(_from, _len) + val n = math.min(_replaced, _len) + val p = locate(i) + removeAfter(p, math.min(n, _len - i)) + insertAfter(p, fresh) + } + this + } + + /** + * Selects the last element. + * + * Runs in constant time. + * + * @return The last element of this $coll. + * @throws NoSuchElementException If the $coll is empty. + */ + override def last: A = if (last0 eq null) throw new NoSuchElementException("last of empty ListBuffer") else last0.head + + /** + * Optionally selects the last element. + * + * Runs in constant time. + * + * @return the last element of this $coll$ if it is nonempty, `None` if it is empty. + */ + override def lastOption: Option[A] = if (last0 eq null) None else Some(last0.head) + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix = "ListBuffer" + +} + +@SerialVersionUID(3L) +object ListBuffer extends StrictOptimizedSeqFactory[ListBuffer] { + + def from[A](coll: collection.IterableOnce[A]): ListBuffer[A] = new ListBuffer[A].freshFrom(coll) + + def newBuilder[A]: Builder[A, ListBuffer[A]] = new GrowableBuilder(empty[A]) + + def empty[A]: ListBuffer[A] = new ListBuffer[A] +} diff --git a/library/src/scala/collection/mutable/ListMap.scala b/library/src/scala/collection/mutable/ListMap.scala new file mode 100644 index 000000000000..3bb7e9bd54bb --- /dev/null +++ b/library/src/scala/collection/mutable/ListMap.scala @@ -0,0 +1,83 @@ +/* + * 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.collection +package mutable + +import scala.language.`2.13` +import scala.annotation.tailrec +import scala.collection.generic.DefaultSerializable +import scala.collection.immutable.List + +/** A simple mutable map backed by a list, so it preserves insertion order. + * + * @tparam K the type of the keys contained in this list map. + * @tparam V the type of the values assigned to keys in this list map. + * + * @define Coll `mutable.ListMap` + * @define coll mutable list map + * @define mayNotTerminateInf + * @define willNotTerminateInf + * @define orderDependent + * @define orderDependentFold + */ +@deprecated("Use an immutable.ListMap assigned to a var instead of mutable.ListMap", "2.13.0") +class ListMap[K, V] + extends AbstractMap[K, V] + with MapOps[K, V, ListMap, ListMap[K, V]] + with StrictOptimizedIterableOps[(K, V), Iterable, ListMap[K, V]] + with StrictOptimizedMapOps[K, V, ListMap, ListMap[K, V]] + with MapFactoryDefaults[K, V, ListMap, Iterable] + with DefaultSerializable { + + override def mapFactory: MapFactory[ListMap] = ListMap + + private[this] var elems: List[(K, V)] = List() + private[this] var siz: Int = 0 + + def get(key: K): Option[V] = elems find (_._1 == key) map (_._2) + def iterator: Iterator[(K, V)] = elems.iterator + + final override def addOne(kv: (K, V)) = { + val (e, key0) = remove(kv._1, elems, List()) + elems = (key0, kv._2) :: e + siz += 1; this + } + + final override def subtractOne(key: K) = { elems = remove(key, elems, List())._1; this } + + @tailrec + private def remove(key: K, elems: List[(K, V)], acc: List[(K, V)]): (List[(K, V)], K) = { + if (elems.isEmpty) (acc, key) + else if (elems.head._1 == key) { siz -= 1; (acc ::: elems.tail, elems.head._1) } + else remove(key, elems.tail, elems.head :: acc) + } + + final override def clear(): Unit = { elems = List(); siz = 0 } + + final override def size: Int = siz + override def knownSize: Int = size + override def isEmpty: Boolean = size == 0 + override protected[this] def stringPrefix = "ListMap" +} + +/** $factoryInfo + * @define Coll `mutable.ListMap` + * @define coll mutable list map + */ +@SerialVersionUID(3L) +@deprecated("Use an immutable.ListMap assigned to a var instead of mutable.ListMap", "2.13.0") +object ListMap extends MapFactory[ListMap] { + def empty[K, V]: ListMap[K, V] = new ListMap[K, V] + def from[K, V](it: IterableOnce[(K, V)]): ListMap[K,V] = Growable.from(empty[K, V], it) + def newBuilder[K, V]: Builder[(K, V), ListMap[K,V]] = new GrowableBuilder(empty[K, V]) +} diff --git a/library/src/scala/collection/mutable/LongMap.scala b/library/src/scala/collection/mutable/LongMap.scala new file mode 100644 index 000000000000..a56d874a5fc2 --- /dev/null +++ b/library/src/scala/collection/mutable/LongMap.scala @@ -0,0 +1,692 @@ +/* + * 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.collection +package mutable + +import scala.language.`2.13` +import scala.collection.generic.DefaultSerializationProxy +import scala.language.implicitConversions + +/** This class implements mutable maps with `Long` keys based on a hash table with open addressing. + * + * Basic map operations on single entries, including `contains` and `get`, + * are typically substantially faster with `LongMap` than [[HashMap]]. Methods + * that act on the whole map, including `foreach` and `map` are not in + * general expected to be faster than with a generic map, save for those + * that take particular advantage of the internal structure of the map: + * `foreachKey`, `foreachValue`, `mapValuesNow`, and `transformValues`. + * + * Maps with open addressing may become less efficient at lookup after + * repeated addition/removal of elements. Although `LongMap` makes a + * decent attempt to remain efficient regardless, calling `repack` + * on a map that will no longer have elements removed but will be + * used heavily may save both time and storage space. + * + * This map is not intended to contain more than 2^29 entries (approximately + * 500 million). The maximum capacity is 2^30, but performance will degrade + * rapidly as 2^30 is approached. + * + */ +final class LongMap[V] private[collection] (defaultEntry: Long => V, initialBufferSize: Int, initBlank: Boolean) + extends AbstractMap[Long, V] + with MapOps[Long, V, Map, LongMap[V]] + with StrictOptimizedIterableOps[(Long, V), Iterable, LongMap[V]] + with Serializable { + import LongMap._ + + def this() = this(LongMap.exceptionDefault, 16, initBlank = true) + + // TODO: override clear() with an optimization more tailored for efficiency. + override protected def fromSpecific(coll: scala.collection.IterableOnce[(Long, V)]): LongMap[V] = { + //TODO should this be the default implementation of this method in StrictOptimizedIterableOps? + val b = newSpecificBuilder + b.sizeHint(coll) + b.addAll(coll) + b.result() + } + override protected def newSpecificBuilder: Builder[(Long, V),LongMap[V]] = new GrowableBuilder(LongMap.empty[V]) + + /** Creates a new `LongMap` that returns default values according to a supplied key-value mapping. */ + def this(defaultEntry: Long => V) = this(defaultEntry, 16, initBlank = true) + + /** Creates a new `LongMap` with an initial buffer of specified size. + * + * A LongMap can typically contain half as many elements as its buffer size + * before it requires resizing. + */ + def this(initialBufferSize: Int) = this(LongMap.exceptionDefault, initialBufferSize, initBlank = true) + + /** Creates a new `LongMap` with specified default values and initial buffer size. */ + def this(defaultEntry: Long => V, initialBufferSize: Int) = this(defaultEntry, initialBufferSize, initBlank = true) + + private[this] var mask = 0 + private[this] var extraKeys: Int = 0 + private[this] var zeroValue: AnyRef = null + private[this] var minValue: AnyRef = null + private[this] var _size = 0 + private[this] var _vacant = 0 + private[this] var _keys: Array[Long] = null + private[this] var _values: Array[AnyRef] = null + + if (initBlank) defaultInitialize(initialBufferSize) + + private[this] def defaultInitialize(n: Int) = { + mask = + if (n<0) 0x7 + else (((1 << (32 - java.lang.Integer.numberOfLeadingZeros(n-1))) - 1) & 0x3FFFFFFF) | 0x7 + _keys = new Array[Long](mask+1) + _values = new Array[AnyRef](mask+1) + } + + private[collection] def initializeTo( + m: Int, ek: Int, zv: AnyRef, mv: AnyRef, sz: Int, vc: Int, kz: Array[Long], vz: Array[AnyRef] + ): Unit = { + mask = m; extraKeys = ek; zeroValue = zv; minValue = mv; _size = sz; _vacant = vc; _keys = kz; _values = vz + } + + override def size: Int = _size + (extraKeys+1)/2 + override def knownSize: Int = size + override def isEmpty: Boolean = size == 0 + override def empty: LongMap[V] = new LongMap() + + private def imbalanced: Boolean = + (_size + _vacant) > 0.5*mask || _vacant > _size + + private def toIndex(k: Long): Int = { + // Part of the MurmurHash3 32 bit finalizer + val h = ((k ^ (k >>> 32)) & 0xFFFFFFFFL).toInt + val x = (h ^ (h >>> 16)) * 0x85EBCA6B + (x ^ (x >>> 13)) & mask + } + + private def seekEmpty(k: Long): Int = { + var e = toIndex(k) + var x = 0 + while (_keys(e) != 0) { x += 1; e = (e + 2*(x+1)*x - 3) & mask } + e + } + + private def seekEntry(k: Long): Int = { + var e = toIndex(k) + var x = 0 + var q = 0L + while ({ q = _keys(e); if (q==k) return e; q != 0}) { x += 1; e = (e + 2*(x+1)*x - 3) & mask } + e | MissingBit + } + + private def seekEntryOrOpen(k: Long): Int = { + var e = toIndex(k) + var x = 0 + var q = 0L + while ({ q = _keys(e); if (q==k) return e; q+q != 0}) { + x += 1 + e = (e + 2*(x+1)*x - 3) & mask + } + if (q == 0) return e | MissingBit + val o = e | MissVacant + while ({ q = _keys(e); if (q==k) return e; q != 0}) { + x += 1 + e = (e + 2*(x+1)*x - 3) & mask + } + o + } + + override def contains(key: Long): Boolean = { + if (key == -key) (((key>>>63).toInt+1) & extraKeys) != 0 + else seekEntry(key) >= 0 + } + + override def get(key: Long): Option[V] = { + if (key == -key) { + if ((((key>>>63).toInt+1) & extraKeys) == 0) None + else if (key == 0) Some(zeroValue.asInstanceOf[V]) + else Some(minValue.asInstanceOf[V]) + } + else { + val i = seekEntry(key) + if (i < 0) None else Some(_values(i).asInstanceOf[V]) + } + } + + override def getOrElse[V1 >: V](key: Long, default: => V1): V1 = { + if (key == -key) { + if ((((key>>>63).toInt+1) & extraKeys) == 0) default + else if (key == 0) zeroValue.asInstanceOf[V1] + else minValue.asInstanceOf[V1] + } + else { + val i = seekEntry(key) + if (i < 0) default else _values(i).asInstanceOf[V1] + } + } + + override def getOrElseUpdate(key: Long, defaultValue: => V): V = { + if (key == -key) { + val kbits = (key>>>63).toInt + 1 + if ((kbits & extraKeys) == 0) { + val value = defaultValue + extraKeys |= kbits + if (key == 0) zeroValue = value.asInstanceOf[AnyRef] + else minValue = value.asInstanceOf[AnyRef] + value + } + else if (key == 0) zeroValue.asInstanceOf[V] + else minValue.asInstanceOf[V] + } + else { + var i = seekEntryOrOpen(key) + if (i < 0) { + val value = { + val oks = _keys + val j = i & IndexMask + val ok = oks(j) + val ans = defaultValue + // Evaluating `defaultValue` may change the map + // - repack: the array is different + // - element added at `j`: since `i < 0`, the key was missing and `ok` is either 0 or MinValue. + // If `defaultValue` added an element at `j` then `_keys(j)` must be different now. + // (`_keys` never contains 0 or MinValue.) + if (oks.ne(_keys) || ok != _keys(j)) { + i = seekEntryOrOpen(key) + if (i >= 0) _size -= 1 + } + ans + } + _size += 1 + val j = i & IndexMask + _keys(j) = key + _values(j) = value.asInstanceOf[AnyRef] + if ((i & VacantBit) != 0) _vacant -= 1 + else if (imbalanced) repack() + value + } + else _values(i).asInstanceOf[V] + } + } + + /** Retrieves the value associated with a key, or the default for that type if none exists + * (null for AnyRef, 0 for floats and integers). + * + * Note: this is the fastest way to retrieve a value that may or + * may not exist, if the default null/zero is acceptable. For key/value + * pairs that do exist, `apply` (i.e. `map(key)`) is equally fast. + */ + def getOrNull(key: Long): V = { + if (key == -key) { + if ((((key>>>63).toInt+1) & extraKeys) == 0) null.asInstanceOf[V] + else if (key == 0) zeroValue.asInstanceOf[V] + else minValue.asInstanceOf[V] + } + else { + val i = seekEntry(key) + if (i < 0) null.asInstanceOf[V] else _values(i).asInstanceOf[V] + } + } + + /** Retrieves the value associated with a key. + * If the key does not exist in the map, the `defaultEntry` for that key + * will be returned instead. + */ + override def apply(key: Long): V = { + if (key == -key) { + if ((((key>>>63).toInt+1) & extraKeys) == 0) defaultEntry(key) + else if (key == 0) zeroValue.asInstanceOf[V] + else minValue.asInstanceOf[V] + } + else { + val i = seekEntry(key) + if (i < 0) defaultEntry(key) else _values(i).asInstanceOf[V] + } + } + + /** The user-supplied default value for the key. Throws an exception + * if no other default behavior was specified. + */ + override def default(key: Long) = defaultEntry(key) + + private def repack(newMask: Int): Unit = { + val ok = _keys + val ov = _values + mask = newMask + _keys = new Array[Long](mask+1) + _values = new Array[AnyRef](mask+1) + _vacant = 0 + var i = 0 + while (i < ok.length) { + val k = ok(i) + if (k != -k) { + val j = seekEmpty(k) + _keys(j) = k + _values(j) = ov(i) + } + i += 1 + } + } + + /** Repacks the contents of this `LongMap` for maximum efficiency of lookup. + * + * For maps that undergo a complex creation process with both addition and + * removal of keys, and then are used heavily with no further removal of + * elements, calling `repack` after the end of the creation can result in + * improved performance. Repacking takes time proportional to the number + * of entries in the map. + */ + def repack(): Unit = repack(repackMask(mask, _size = _size, _vacant = _vacant)) + + override def put(key: Long, value: V): Option[V] = { + if (key == -key) { + if (key == 0) { + val ans = if ((extraKeys&1) == 1) Some(zeroValue.asInstanceOf[V]) else None + zeroValue = value.asInstanceOf[AnyRef] + extraKeys |= 1 + ans + } + else { + val ans = if ((extraKeys&2) == 1) Some(minValue.asInstanceOf[V]) else None + minValue = value.asInstanceOf[AnyRef] + extraKeys |= 2 + ans + } + } + else { + val i = seekEntryOrOpen(key) + if (i < 0) { + val j = i & IndexMask + _keys(j) = key + _values(j) = value.asInstanceOf[AnyRef] + _size += 1 + if ((i & VacantBit) != 0) _vacant -= 1 + else if (imbalanced) repack() + None + } + else { + val ans = Some(_values(i).asInstanceOf[V]) + _keys(i) = key + _values(i) = value.asInstanceOf[AnyRef] + ans + } + } + } + + /** Updates the map to include a new key-value pair. + * + * This is the fastest way to add an entry to a `LongMap`. + */ + override def update(key: Long, value: V): Unit = { + if (key == -key) { + if (key == 0) { + zeroValue = value.asInstanceOf[AnyRef] + extraKeys |= 1 + } + else { + minValue = value.asInstanceOf[AnyRef] + extraKeys |= 2 + } + } + else { + val i = seekEntryOrOpen(key) + if (i < 0) { + val j = i & IndexMask + _keys(j) = key + _values(j) = value.asInstanceOf[AnyRef] + _size += 1 + if ((i & VacantBit) != 0) _vacant -= 1 + else if (imbalanced) repack() + } + else { + _keys(i) = key + _values(i) = value.asInstanceOf[AnyRef] + } + } + } + + /** Adds a new key/value pair to this map and returns the map. */ + @deprecated("Use `addOne` or `update` instead; infix operations with an operand of multiple args will be deprecated", "2.13.3") + def +=(key: Long, value: V): this.type = { update(key, value); this } + + /** Adds a new key/value pair to this map and returns the map. */ + @inline final def addOne(key: Long, value: V): this.type = { update(key, value); this } + + @inline override final def addOne(kv: (Long, V)): this.type = { update(kv._1, kv._2); this } + + def subtractOne(key: Long): this.type = { + if (key == -key) { + if (key == 0L) { + extraKeys &= 0x2 + zeroValue = null + } + else { + extraKeys &= 0x1 + minValue = null + } + } + else { + val i = seekEntry(key) + if (i >= 0) { + _size -= 1 + _vacant += 1 + _keys(i) = Long.MinValue + _values(i) = null + } + } + this + } + + def iterator: Iterator[(Long, V)] = new AbstractIterator[(Long, V)] { + private[this] val kz = _keys + private[this] val vz = _values + + private[this] var nextPair: (Long, V) = + if (extraKeys==0) null + else if ((extraKeys&1)==1) (0L, zeroValue.asInstanceOf[V]) + else (Long.MinValue, minValue.asInstanceOf[V]) + + private[this] var anotherPair: (Long, V) = + if (extraKeys==3) (Long.MinValue, minValue.asInstanceOf[V]) + else null + + private[this] var index = 0 + + def hasNext: Boolean = nextPair != null || (index < kz.length && { + var q = kz(index) + while (q == -q) { + index += 1 + if (index >= kz.length) return false + q = kz(index) + } + nextPair = (kz(index), vz(index).asInstanceOf[V]) + index += 1 + true + }) + def next() = { + if (nextPair == null && !hasNext) throw new NoSuchElementException("next") + val ans = nextPair + if (anotherPair != null) { + nextPair = anotherPair + anotherPair = null + } + else nextPair = null + ans + } + } + + // TODO PERF override these for efficiency. See immutable.LongMap for how to organize the code. + override def keysIterator: Iterator[Long] = super.keysIterator + override def valuesIterator: Iterator[V] = super.valuesIterator + + override def foreach[U](f: ((Long,V)) => U): Unit = { + if ((extraKeys & 1) == 1) f((0L, zeroValue.asInstanceOf[V])) + if ((extraKeys & 2) == 2) f((Long.MinValue, minValue.asInstanceOf[V])) + var i,j = 0 + while (i < _keys.length & j < _size) { + val k = _keys(i) + if (k != -k) { + j += 1 + f((k, _values(i).asInstanceOf[V])) + } + i += 1 + } + } + + override def foreachEntry[U](f: (Long,V) => U): Unit = { + if ((extraKeys & 1) == 1) f(0L, zeroValue.asInstanceOf[V]) + if ((extraKeys & 2) == 2) f(Long.MinValue, minValue.asInstanceOf[V]) + var i,j = 0 + while (i < _keys.length & j < _size) { + val k = _keys(i) + if (k != -k) { + j += 1 + f(k, _values(i).asInstanceOf[V]) + } + i += 1 + } + } + + override def clone(): LongMap[V] = { + val kz = java.util.Arrays.copyOf(_keys, _keys.length) + val vz = java.util.Arrays.copyOf(_values, _values.length) + val lm = new LongMap[V](defaultEntry, 1, initBlank = false) + lm.initializeTo(mask, extraKeys, zeroValue, minValue, _size, _vacant, kz, vz) + lm + } + + @deprecated("Consider requiring an immutable Map or fall back to Map.concat", "2.13.0") + override def +[V1 >: V](kv: (Long, V1)): LongMap[V1] = { + val lm = clone().asInstanceOf[LongMap[V1]] + lm += kv + lm + } + + @deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0") + override def + [V1 >: V](elem1: (Long, V1), elem2: (Long, V1), elems: (Long, V1)*): LongMap[V1] = { + val m = this + elem1 + elem2 + if(elems.isEmpty) m else m.concat(elems) + } + + override def concat[V1 >: V](xs: scala.collection.IterableOnce[(Long, V1)]): LongMap[V1] = { + val lm = clone().asInstanceOf[LongMap[V1]] + xs.iterator.foreach(kv => lm += kv) + lm + } + + override def ++ [V1 >: V](xs: scala.collection.IterableOnce[(Long, V1)]): LongMap[V1] = concat(xs) + + @deprecated("Use m.clone().addOne(k,v) instead of m.updated(k, v)", "2.13.0") + override def updated[V1 >: V](key: Long, value: V1): LongMap[V1] = + clone().asInstanceOf[LongMap[V1]].addOne(key, value) + + /** Applies a function to all keys of this map. */ + def foreachKey[A](f: Long => A): Unit = { + if ((extraKeys & 1) == 1) f(0L) + if ((extraKeys & 2) == 2) f(Long.MinValue) + var i,j = 0 + while (i < _keys.length & j < _size) { + val k = _keys(i) + if (k != -k) { + j += 1 + f(k) + } + i += 1 + } + } + + /** Applies a function to all values of this map. */ + def foreachValue[A](f: V => A): Unit = { + if ((extraKeys & 1) == 1) f(zeroValue.asInstanceOf[V]) + if ((extraKeys & 2) == 2) f(minValue.asInstanceOf[V]) + var i,j = 0 + while (i < _keys.length & j < _size) { + val k = _keys(i) + if (k != -k) { + j += 1 + f(_values(i).asInstanceOf[V]) + } + i += 1 + } + } + + /** Creates a new `LongMap` with different values. + * Unlike `mapValues`, this method generates a new + * collection immediately. + */ + def mapValuesNow[V1](f: V => V1): LongMap[V1] = { + val zv = if ((extraKeys & 1) == 1) f(zeroValue.asInstanceOf[V]).asInstanceOf[AnyRef] else null + val mv = if ((extraKeys & 2) == 2) f(minValue.asInstanceOf[V]).asInstanceOf[AnyRef] else null + val lm = new LongMap[V1](LongMap.exceptionDefault, 1, initBlank = false) + val kz = java.util.Arrays.copyOf(_keys, _keys.length) + val vz = new Array[AnyRef](_values.length) + var i,j = 0 + while (i < _keys.length & j < _size) { + val k = _keys(i) + if (k != -k) { + j += 1 + vz(i) = f(_values(i).asInstanceOf[V]).asInstanceOf[AnyRef] + } + i += 1 + } + lm.initializeTo(mask, extraKeys, zv, mv, _size, _vacant, kz, vz) + lm + } + + /** Applies a transformation function to all values stored in this map. + * Note: the default, if any, is not transformed. + */ + @deprecated("Use transformValuesInPlace instead of transformValues", "2.13.0") + @`inline` final def transformValues(f: V => V): this.type = transformValuesInPlace(f) + + /** Applies a transformation function to all values stored in this map. + * Note: the default, if any, is not transformed. + */ + def transformValuesInPlace(f: V => V): this.type = { + if ((extraKeys & 1) == 1) zeroValue = f(zeroValue.asInstanceOf[V]).asInstanceOf[AnyRef] + if ((extraKeys & 2) == 2) minValue = f(minValue.asInstanceOf[V]).asInstanceOf[AnyRef] + var i,j = 0 + while (i < _keys.length & j < _size) { + val k = _keys(i) + if (k != -k) { + j += 1 + _values(i) = f(_values(i).asInstanceOf[V]).asInstanceOf[AnyRef] + } + i += 1 + } + this + } + + /** An overload of `map` which produces a `LongMap`. + * + * @param f the mapping function + */ + def map[V2](f: ((Long, V)) => (Long, V2)): LongMap[V2] = LongMap.from(new View.Map(coll, f)) + + /** An overload of `flatMap` which produces a `LongMap`. + * + * @param f the mapping function + */ + def flatMap[V2](f: ((Long, V)) => IterableOnce[(Long, V2)]): LongMap[V2] = LongMap.from(new View.FlatMap(coll, f)) + + /** An overload of `collect` which produces a `LongMap`. + * + * @param pf the mapping function + */ + def collect[V2](pf: PartialFunction[(Long, V), (Long, V2)]): LongMap[V2] = + strictOptimizedCollect(LongMap.newBuilder[V2], pf) + + protected[this] def writeReplace(): AnyRef = new DefaultSerializationProxy(LongMap.toFactory[V](LongMap), this) + + override protected[this] def className = "LongMap" +} + +object LongMap { + private final val IndexMask = 0x3FFF_FFFF + private final val MissingBit = 0x8000_0000 + private final val VacantBit = 0x4000_0000 + private final val MissVacant = 0xC000_0000 + + private val exceptionDefault: Long => Nothing = (k: Long) => throw new NoSuchElementException(k.toString) + + /** A builder for instances of `LongMap`. + * + * This builder can be reused to create multiple instances. + */ + final class LongMapBuilder[V] extends ReusableBuilder[(Long, V), LongMap[V]] { + private[collection] var elems: LongMap[V] = new LongMap[V] + override def addOne(entry: (Long, V)): this.type = { + elems += entry + this + } + def clear(): Unit = elems = new LongMap[V] + def result(): LongMap[V] = elems + override def knownSize: Int = elems.knownSize + } + + /** Creates a new `LongMap` with zero or more key/value pairs. */ + def apply[V](elems: (Long, V)*): LongMap[V] = buildFromIterableOnce(elems) + + private def buildFromIterableOnce[V](elems: IterableOnce[(Long, V)]): LongMap[V] = { + var sz = elems.knownSize + if(sz < 0) sz = 4 + val lm = new LongMap[V](sz * 2) + elems.iterator.foreach{ case (k,v) => lm(k) = v } + if (lm.size < (sz>>3)) lm.repack() + lm + } + + /** Creates a new empty `LongMap`. */ + def empty[V]: LongMap[V] = new LongMap[V] + + /** Creates a new empty `LongMap` with the supplied default */ + def withDefault[V](default: Long => V): LongMap[V] = new LongMap[V](default) + + /** Creates a new `LongMap` from an existing source collection. A source collection + * which is already a `LongMap` gets cloned. + * + * @param source Source collection + * @tparam A the type of the collection’s elements + * @return a new `LongMap` with the elements of `source` + */ + def from[V](source: IterableOnce[(Long, V)]): LongMap[V] = source match { + case source: LongMap[_] => source.clone().asInstanceOf[LongMap[V]] + case _ => buildFromIterableOnce(source) + } + + def newBuilder[V]: ReusableBuilder[(Long, V), LongMap[V]] = new LongMapBuilder[V] + + /** Creates a new `LongMap` from arrays of keys and values. + * Equivalent to but more efficient than `LongMap((keys zip values): _*)`. + */ + def fromZip[V](keys: Array[Long], values: Array[V]): LongMap[V] = { + val sz = math.min(keys.length, values.length) + val lm = new LongMap[V](sz * 2) + var i = 0 + while (i < sz) { lm(keys(i)) = values(i); i += 1 } + if (lm.size < (sz>>3)) lm.repack() + lm + } + + /** Creates a new `LongMap` from keys and values. + * Equivalent to but more efficient than `LongMap((keys zip values): _*)`. + */ + def fromZip[V](keys: scala.collection.Iterable[Long], values: scala.collection.Iterable[V]): LongMap[V] = { + val sz = math.min(keys.size, values.size) + val lm = new LongMap[V](sz * 2) + val ki = keys.iterator + val vi = values.iterator + while (ki.hasNext && vi.hasNext) lm(ki.next()) = vi.next() + if (lm.size < (sz >> 3)) lm.repack() + lm + } + + implicit def toFactory[V](dummy: LongMap.type): Factory[(Long, V), LongMap[V]] = ToFactory.asInstanceOf[Factory[(Long, V), LongMap[V]]] + + @SerialVersionUID(3L) + private[this] object ToFactory extends Factory[(Long, AnyRef), LongMap[AnyRef]] with Serializable { + def fromSpecific(it: IterableOnce[(Long, AnyRef)]): LongMap[AnyRef] = LongMap.from[AnyRef](it) + def newBuilder: Builder[(Long, AnyRef), LongMap[AnyRef]] = LongMap.newBuilder[AnyRef] + } + + implicit def toBuildFrom[V](factory: LongMap.type): BuildFrom[Any, (Long, V), LongMap[V]] = ToBuildFrom.asInstanceOf[BuildFrom[Any, (Long, V), LongMap[V]]] + private object ToBuildFrom extends BuildFrom[Any, (Long, AnyRef), LongMap[AnyRef]] { + def fromSpecific(from: Any)(it: IterableOnce[(Long, AnyRef)]) = LongMap.from(it) + def newBuilder(from: Any): ReusableBuilder[(Long, AnyRef), LongMap[AnyRef]] = LongMap.newBuilder[AnyRef] + } + + implicit def iterableFactory[V]: Factory[(Long, V), LongMap[V]] = toFactory(this) + implicit def buildFromLongMap[V]: BuildFrom[LongMap[_], (Long, V), LongMap[V]] = toBuildFrom(this) + + private def repackMask(mask: Int, _size: Int, _vacant: Int): Int = { + var m = mask + if (_size + _vacant >= 0.5*mask && !(_vacant > 0.2*mask)) m = ((m << 1) + 1) & IndexMask + while (m > 8 && _size < (m >>> 3)) m = m >>> 1 + m /*.ensuring(_size <= _ + 1)*/ + } +} diff --git a/library/src/scala/collection/mutable/Map.scala b/library/src/scala/collection/mutable/Map.scala new file mode 100644 index 000000000000..df6f036b62d9 --- /dev/null +++ b/library/src/scala/collection/mutable/Map.scala @@ -0,0 +1,270 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` + +/** Base type of mutable Maps */ +trait Map[K, V] + extends Iterable[(K, V)] + with collection.Map[K, V] + with MapOps[K, V, Map, Map[K, V]] + with Growable[(K, V)] + with Shrinkable[K] + with MapFactoryDefaults[K, V, Map, Iterable] { + + override def mapFactory: scala.collection.MapFactory[Map] = Map + + /* + //TODO consider keeping `remove` because it returns the removed entry + @deprecated("Use subtract or -= instead of remove", "2.13.0") + def remove(key: K): Option[V] = { + val old = get(key) + if(old.isDefined) subtract(key) + old + } + */ + + /** The same map with a given default function. + * Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc. + * are not affected by `withDefaultValue`. + * + * Invoking transformer methods (e.g. `map`) will not preserve the default value. + * + * @param d the function mapping keys to values, used for non-present keys + * @return a wrapper of the map with a default value + */ + def withDefault(d: K => V): Map[K, V] = new Map.WithDefault[K, V](this, d) + + /** The same map with a given default value. + * Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc. + * are not affected by `withDefaultValue`. + * + * Invoking transformer methods (e.g. `map`) will not preserve the default value. + * + * @param d default value used for non-present keys + * @return a wrapper of the map with a default value + */ + def withDefaultValue(d: V): Map[K, V] = new Map.WithDefault[K, V](this, x => d) +} + +/** + * @define coll mutable map + * @define Coll `mutable.Map` + */ +transparent trait MapOps[K, V, +CC[X, Y] <: MapOps[X, Y, CC, _], +C <: MapOps[K, V, CC, C]] + extends IterableOps[(K, V), Iterable, C] + with collection.MapOps[K, V, CC, C] + with Cloneable[C] + with Builder[(K, V), C] + with Growable[(K, V)] + with Shrinkable[K] { + + def result(): C = coll + + @deprecated("Use - or remove on an immutable Map", "2.13.0") + final def - (key: K): C = clone() -= key + + @deprecated("Use -- or removeAll on an immutable Map", "2.13.0") + final def - (key1: K, key2: K, keys: K*): C = clone() -= key1 -= key2 --= keys + + /** Adds a new key/value pair to this map and optionally returns previously bound value. + * If the map already contains a + * mapping for the key, it will be overridden by the new value. + * + * @param key the key to update + * @param value the new value + * @return an option value containing the value associated with the key + * before the `put` operation was executed, or `None` if `key` + * was not defined in the map before. + */ + def put(key: K, value: V): Option[V] = { + val r = get(key) + update(key, value) + r + } + + /** Adds a new key/value pair to this map. + * If the map already contains a + * mapping for the key, it will be overridden by the new value. + * + * @param key The key to update + * @param value The new value + */ + def update(key: K, value: V): Unit = { coll += ((key, value)) } + + /** + * Update a mapping for the specified key and its current optionally mapped value + * (`Some` if there is current mapping, `None` if not). + * + * If the remapping function returns `Some(v)`, the mapping is updated with the new value `v`. + * If the remapping function returns `None`, the mapping is removed (or remains absent if initially absent). + * If the function itself throws an exception, the exception is rethrown, and the current mapping is left unchanged. + * + * @param key the key value + * @param remappingFunction a function that receives current optionally mapped value and return a new mapping + * @return the new value associated with the specified key + */ + def updateWith(key: K)(remappingFunction: Option[V] => Option[V]): Option[V] = { + val previousValue = this.get(key) + val nextValue = remappingFunction(previousValue) + (previousValue, nextValue) match { + case (None, None) => // do nothing + case (Some(_), None) => this.remove(key) + case (_, Some(v)) => this.update(key,v) + } + nextValue + } + + /** If given key is already in this map, returns associated value. + * + * Otherwise, computes value from given expression `defaultValue`, stores with key + * in map and returns that value. + * + * Concurrent map implementations may evaluate the expression `defaultValue` + * multiple times, or may evaluate `defaultValue` without inserting the result. + * + * @param key the key to test + * @param defaultValue the computation yielding the value to associate with `key`, if + * `key` is previously unbound. + * @return the value associated with key (either previously or as a result + * of executing the method). + */ + def getOrElseUpdate(key: K, @deprecatedName("op", since="2.13.13") defaultValue: => V): V = + get(key) match { + case Some(v) => v + case None => val d = defaultValue; this(key) = d; d + } + + /** Removes a key from this map, returning the value associated previously + * with that key as an option. + * @param key the key to be removed + * @return an option value containing the value associated previously with `key`, + * or `None` if `key` was not defined in the map before. + */ + def remove(key: K): Option[V] = { + val r = get(key) + if (r.isDefined) this -= key + r + } + + def clear(): Unit = { keysIterator foreach -= } + + override def clone(): C = empty ++= this + + @deprecated("Use filterInPlace instead", "2.13.0") + @inline final def retain(p: (K, V) => Boolean): this.type = filterInPlace(p) + + /** Retains only those mappings for which the predicate + * `p` returns `true`. + * + * @param p The test predicate + */ + def filterInPlace(p: (K, V) => Boolean): this.type = { + if (!isEmpty) this match { + case tm: concurrent.Map[_, _] => tm.asInstanceOf[concurrent.Map[K, V]].filterInPlaceImpl(p) + case _ => + val array = this.toArray[Any] // scala/bug#7269 toArray avoids ConcurrentModificationException + val arrayLength = array.length + var i = 0 + while (i < arrayLength) { + val (k, v) = array(i).asInstanceOf[(K, V)] + if (!p(k, v)) { + this -= k + } + i += 1 + } + } + this + } + + @deprecated("Use mapValuesInPlace instead", "2.13.0") + @inline final def transform(f: (K, V) => V): this.type = mapValuesInPlace(f) + + /** Applies a transformation function to all values contained in this map. + * The transformation function produces new values from existing keys + * associated values. + * + * @param f the transformation to apply + * @return the map itself. + */ + def mapValuesInPlace(f: (K, V) => V): this.type = { + if (!isEmpty) this match { + case hm: mutable.HashMap[_, _] => hm.asInstanceOf[mutable.HashMap[K, V]].mapValuesInPlaceImpl(f) + case tm: concurrent.Map[_, _] => tm.asInstanceOf[concurrent.Map[K, V]].mapValuesInPlaceImpl(f) + case _ => + val array = this.toArray[Any] + val arrayLength = array.length + var i = 0 + while (i < arrayLength) { + val (k, v) = array(i).asInstanceOf[(K, V)] + update(k, f(k, v)) + i += 1 + } + } + this + } + + @deprecated("Use m.clone().addOne((k,v)) instead of m.updated(k, v)", "2.13.0") + def updated[V1 >: V](key: K, value: V1): CC[K, V1] = + clone().asInstanceOf[CC[K, V1]].addOne((key, value)) + + override def knownSize: Int = super[IterableOps].knownSize +} + +/** + * $factoryInfo + * @define coll mutable map + * @define Coll `mutable.Map` + */ +@SerialVersionUID(3L) +object Map extends MapFactory.Delegate[Map](HashMap) { + + @SerialVersionUID(3L) + class WithDefault[K, V](val underlying: Map[K, V], val defaultValue: K => V) + extends AbstractMap[K, V] + with MapOps[K, V, Map, WithDefault[K, V]] with Serializable { + + override def default(key: K): V = defaultValue(key) + + def iterator: scala.collection.Iterator[(K, V)] = underlying.iterator + override def isEmpty: Boolean = underlying.isEmpty + override def knownSize: Int = underlying.knownSize + override def mapFactory: MapFactory[Map] = underlying.mapFactory + + override def clear(): Unit = underlying.clear() + + def get(key: K): Option[V] = underlying.get(key) + + def subtractOne(elem: K): WithDefault.this.type = { underlying.subtractOne(elem); this } + + def addOne(elem: (K, V)): WithDefault.this.type = { underlying.addOne(elem); this } + + override def concat[V2 >: V](suffix: collection.IterableOnce[(K, V2)]): Map[K, V2] = + underlying.concat(suffix).withDefault(defaultValue) + + override def empty: WithDefault[K, V] = new WithDefault[K, V](underlying.empty, defaultValue) + + override protected def fromSpecific(coll: scala.collection.IterableOnce[(K, V)]): WithDefault[K, V] = + new WithDefault[K, V](mapFactory.from(coll), defaultValue) + + override protected def newSpecificBuilder: Builder[(K, V), WithDefault[K, V]] = + Map.newBuilder.mapResult((p: Map[K, V]) => new WithDefault[K, V](p, defaultValue)) + } + +} + +/** Explicit instantiation of the `Map` trait to reduce class file size in subclasses. */ +abstract class AbstractMap[K, V] extends scala.collection.AbstractMap[K, V] with Map[K, V] diff --git a/library/src/scala/collection/mutable/MultiMap.scala b/library/src/scala/collection/mutable/MultiMap.scala new file mode 100644 index 000000000000..f96a2ca94754 --- /dev/null +++ b/library/src/scala/collection/mutable/MultiMap.scala @@ -0,0 +1,116 @@ +/* + * 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.collection.mutable + +import scala.language.`2.13` + +/** A trait for mutable maps with multiple values assigned to a key. + * + * This class is typically used as a mixin. It turns maps which map `K` + * to `Set[V]` objects into multimaps that map `K` to `V` objects. + * + * @example {{{ + * // first import all necessary types from package `collection.mutable` + * import collection.mutable.{ HashMap, MultiMap, Set } + * + * // to create a `MultiMap` the easiest way is to mixin it into a normal + * // `Map` instance + * val mm = new HashMap[Int, Set[String]] with MultiMap[Int, String] + * + * // to add key-value pairs to a multimap it is important to use + * // the method `addBinding` because standard methods like `+` will + * // overwrite the complete key-value pair instead of adding the + * // value to the existing key + * mm.addBinding(1, "a") + * mm.addBinding(2, "b") + * mm.addBinding(1, "c") + * + * // mm now contains `Map(2 -> Set(b), 1 -> Set(c, a))` + * + * // to check if the multimap contains a value there is method + * // `entryExists`, which allows to traverse the including set + * mm.entryExists(1, _ == "a") == true + * mm.entryExists(1, _ == "b") == false + * mm.entryExists(2, _ == "b") == true + * + * // to remove a previous added value there is the method `removeBinding` + * mm.removeBinding(1, "a") + * mm.entryExists(1, _ == "a") == false + * }}} + * + * @define coll multimap + * @define Coll `MultiMap` + */ +@deprecated("Use a scala.collection.mutable.MultiDict in the scala-collection-contrib module", "2.13.0") +trait MultiMap[K, V] extends Map[K, Set[V]] { + /** Creates a new set. + * + * Classes that use this trait as a mixin can override this method + * to have the desired implementation of sets assigned to new keys. + * By default this is `HashSet`. + * + * @return An empty set of values of type `V`. + */ + protected def makeSet: Set[V] = new HashSet[V] + + /** Assigns the specified `value` to a specified `key`. If the key + * already has a binding to equal to `value`, nothing is changed; + * otherwise a new binding is added for that `key`. + * + * @param key The key to which to bind the new value. + * @param value The value to bind to the key. + * @return A reference to this multimap. + */ + def addBinding(key: K, value: V): this.type = { + get(key) match { + case None => + val set = makeSet + set += value + this(key) = set + case Some(set) => + set += value + } + this + } + + /** Removes the binding of `value` to `key` if it exists, otherwise this + * operation doesn't have any effect. + * + * If this was the last value assigned to the specified key, the + * set assigned to that key will be removed as well. + * + * @param key The key of the binding. + * @param value The value to remove. + * @return A reference to this multimap. + */ + def removeBinding(key: K, value: V): this.type = { + get(key) match { + case None => + case Some(set) => + set -= value + if (set.isEmpty) this -= key + } + this + } + + /** Checks if there exists a binding to `key` such that it satisfies the predicate `p`. + * + * @param key The key for which the predicate is checked. + * @param p The predicate which a value assigned to the key must satisfy. + * @return A boolean if such a binding exists + */ + def entryExists(key: K, p: V => Boolean): Boolean = get(key) match { + case None => false + case Some(set) => set exists p + } +} diff --git a/library/src/scala/collection/mutable/MutationTracker.scala b/library/src/scala/collection/mutable/MutationTracker.scala new file mode 100644 index 000000000000..6b41ac22db37 --- /dev/null +++ b/library/src/scala/collection/mutable/MutationTracker.scala @@ -0,0 +1,79 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` +import java.util.ConcurrentModificationException + +/** + * Utilities to check that mutations to a client that tracks + * its mutations have not occurred since a given point. + * [[Iterator `Iterator`]]s that perform this check automatically + * during iteration can be created by wrapping an `Iterator` + * in a [[MutationTracker.CheckedIterator `CheckedIterator`]], + * or by manually using the [[MutationTracker.checkMutations() `checkMutations`]] + * and [[MutationTracker.checkMutationsForIteration() `checkMutationsForIteration`]] + * methods. + */ +private object MutationTracker { + + /** + * Checks whether or not the actual mutation count differs from + * the expected one, throwing an exception, if it does. + * + * @param expectedCount the expected mutation count + * @param actualCount the actual mutation count + * @param message the exception message in case of mutations + * @throws ConcurrentModificationException if the expected and actual + * mutation counts differ + */ + @throws[ConcurrentModificationException] + def checkMutations(expectedCount: Int, actualCount: Int, message: String): Unit = { + if (actualCount != expectedCount) throw new ConcurrentModificationException(message) + } + + /** + * Checks whether or not the actual mutation count differs from + * the expected one, throwing an exception, if it does. This method + * produces an exception message saying that it was called because a + * backing collection was mutated during iteration. + * + * @param expectedCount the expected mutation count + * @param actualCount the actual mutation count + * @throws ConcurrentModificationException if the expected and actual + * mutation counts differ + */ + @throws[ConcurrentModificationException] + @inline def checkMutationsForIteration(expectedCount: Int, actualCount: Int): Unit = + checkMutations(expectedCount, actualCount, "mutation occurred during iteration") + + /** + * An iterator wrapper that checks if the underlying collection has + * been mutated. + * + * @param underlying the underlying iterator + * @param mutationCount a by-name provider of the current mutation count + * @tparam A the type of the iterator's elements + */ + final class CheckedIterator[A](underlying: Iterator[A], mutationCount: => Int) extends AbstractIterator[A] { + private[this] val expectedCount = mutationCount + + def hasNext: Boolean = { + checkMutationsForIteration(expectedCount, mutationCount) + underlying.hasNext + } + def next(): A = underlying.next() + } +} diff --git a/library/src/scala/collection/mutable/OpenHashMap.scala b/library/src/scala/collection/mutable/OpenHashMap.scala new file mode 100644 index 000000000000..36d77098512b --- /dev/null +++ b/library/src/scala/collection/mutable/OpenHashMap.scala @@ -0,0 +1,307 @@ +/* + * 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.collection +package mutable + +import scala.language.`2.13` +import java.lang.Integer.numberOfLeadingZeros +import java.util.ConcurrentModificationException +import scala.collection.generic.DefaultSerializable + +/** + * @define Coll `OpenHashMap` + * @define coll open hash map + */ +@deprecated("Use HashMap or one of the specialized versions (LongMap, AnyRefMap) instead of OpenHashMap", "2.13.0") +@SerialVersionUID(3L) +object OpenHashMap extends MapFactory[OpenHashMap] { + + def empty[K, V] = new OpenHashMap[K, V] + def from[K, V](it: IterableOnce[(K, V)]): OpenHashMap[K,V] = empty ++= it + + def newBuilder[K, V]: Builder[(K, V), OpenHashMap[K,V]] = + new GrowableBuilder[(K, V), OpenHashMap[K, V]](empty) + + /** A hash table entry. + * + * The entry is occupied if and only if its `value` is a `Some`; + * deleted if and only if its `value` is `None`. + * If its `key` is not the default value of type `Key`, the entry is occupied. + * If the entry is occupied, `hash` contains the hash value of `key`. + */ + final private class OpenEntry[Key, Value](var key: Key, + var hash: Int, + var value: Option[Value]) + + private[mutable] def nextPositivePowerOfTwo(target: Int): Int = 1 << -numberOfLeadingZeros(target - 1) +} + +/** A mutable hash map based on an open addressing method. The precise scheme is + * undefined, but it should make a reasonable effort to ensure that an insert + * with consecutive hash codes is not unnecessarily penalised. In particular, + * mappings of consecutive integer keys should work without significant + * performance loss. + * + * @tparam Key type of the keys in this map. + * @tparam Value type of the values in this map. + * @param initialSize the initial size of the internal hash table. + * + * @define Coll `OpenHashMap` + * @define coll open hash map + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +@deprecated("Use HashMap or one of the specialized versions (LongMap, AnyRefMap) instead of OpenHashMap", "2.13.0") +class OpenHashMap[Key, Value](initialSize : Int) + extends AbstractMap[Key, Value] + with MapOps[Key, Value, OpenHashMap, OpenHashMap[Key, Value]] + with StrictOptimizedIterableOps[(Key, Value), Iterable, OpenHashMap[Key, Value]] + with MapFactoryDefaults[Key, Value, OpenHashMap, Iterable] + with DefaultSerializable { + + import OpenHashMap.OpenEntry + private type Entry = OpenEntry[Key, Value] + + /** A default constructor creates a hashmap with initial size `8`. + */ + def this() = this(8) + + override def mapFactory: MapFactory[OpenHashMap] = OpenHashMap + + private[this] val actualInitialSize = OpenHashMap.nextPositivePowerOfTwo(initialSize) + + private[this] var mask = actualInitialSize - 1 + + /** The hash table. + * + * The table's entries are initialized to `null`, indication of an empty slot. + * A slot is either deleted or occupied if and only if the entry is non-`null`. + */ + private[this] var table = new Array[Entry](actualInitialSize) + + private[this] var _size = 0 + private[this] var deleted = 0 + + // Used for tracking inserts so that iterators can determine if concurrent modification has occurred. + private[this] var modCount = 0 + + override def size = _size + override def knownSize: Int = size + private[this] def size_=(s : Int): Unit = _size = s + override def isEmpty: Boolean = _size == 0 + /** Returns a mangled hash code of the provided key. */ + protected def hashOf(key: Key) = { + var h = key.## + h ^= ((h >>> 20) ^ (h >>> 12)) + h ^ (h >>> 7) ^ (h >>> 4) + } + + /** Increase the size of the table. + * Copy only the occupied slots, effectively eliminating the deleted slots. + */ + private[this] def growTable() = { + val oldSize = mask + 1 + val newSize = 4 * oldSize + val oldTable = table + table = new Array[Entry](newSize) + mask = newSize - 1 + oldTable.foreach( entry => + if (entry != null && entry.value != None) + table(findIndex(entry.key, entry.hash)) = entry ) + deleted = 0 + } + + /** Return the index of the first slot in the hash table (in probe order) + * that is, in order of preference, either occupied by the given key, deleted, or empty. + * + * @param hash hash value for `key` + */ + private[this] def findIndex(key: Key, hash: Int): Int = { + var index = hash & mask + var j = 0 + + // Index of the first slot containing a deleted entry, or -1 if none found yet + var firstDeletedIndex = -1 + + var entry = table(index) + while (entry != null) { + if (entry.hash == hash && entry.key == key && entry.value != None) + return index + + if (firstDeletedIndex == -1 && entry.value == None) + firstDeletedIndex = index + + j += 1 + index = (index + j) & mask + entry = table(index) + } + + if (firstDeletedIndex == -1) index else firstDeletedIndex + } + + // TODO refactor `put` to extract `findOrAddEntry` and implement this in terms of that to avoid Some boxing. + override def update(key: Key, value: Value): Unit = put(key, value) + + @deprecatedOverriding("addOne should not be overridden in order to maintain consistency with put.", "2.11.0") + def addOne (kv: (Key, Value)): this.type = { put(kv._1, kv._2); this } + + @deprecatedOverriding("subtractOne should not be overridden in order to maintain consistency with remove.", "2.11.0") + def subtractOne (key: Key): this.type = { remove(key); this } + + override def put(key: Key, value: Value): Option[Value] = + put(key, hashOf(key), value) + + private def put(key: Key, hash: Int, value: Value): Option[Value] = { + if (2 * (size + deleted) > mask) growTable() + val index = findIndex(key, hash) + val entry = table(index) + if (entry == null) { + table(index) = new OpenEntry(key, hash, Some(value)) + modCount += 1 + size += 1 + None + } else { + val res = entry.value + if (entry.value == None) { + entry.key = key + entry.hash = hash + size += 1 + deleted -= 1 + modCount += 1 + } + entry.value = Some(value) + res + } + } + + /** Delete the hash table slot contained in the given entry. */ + @`inline` + private[this] def deleteSlot(entry: Entry) = { + entry.key = null.asInstanceOf[Key] + entry.hash = 0 + entry.value = None + + size -= 1 + deleted += 1 + } + + override def remove(key : Key): Option[Value] = { + val entry = table(findIndex(key, hashOf(key))) + if (entry != null && entry.value != None) { + val res = entry.value + deleteSlot(entry) + res + } else None + } + + def get(key : Key) : Option[Value] = { + val hash = hashOf(key) + var index = hash & mask + var entry = table(index) + var j = 0 + while(entry != null){ + if (entry.hash == hash && + entry.key == key){ + return entry.value + } + + j += 1 + index = (index + j) & mask + entry = table(index) + } + None + } + + /** An iterator over the elements of this map. Use of this iterator follows + * the same contract for concurrent modification as the foreach method. + * + * @return the iterator + */ + def iterator: Iterator[(Key, Value)] = new OpenHashMapIterator[(Key, Value)] { + override protected def nextResult(node: Entry): (Key, Value) = (node.key, node.value.get) + } + + override def keysIterator: Iterator[Key] = new OpenHashMapIterator[Key] { + override protected def nextResult(node: Entry): Key = node.key + } + override def valuesIterator: Iterator[Value] = new OpenHashMapIterator[Value] { + override protected def nextResult(node: Entry): Value = node.value.get + } + + private abstract class OpenHashMapIterator[A] extends AbstractIterator[A] { + private[this] var index = 0 + private[this] val initialModCount = modCount + + private[this] def advance(): Unit = { + if (initialModCount != modCount) throw new ConcurrentModificationException + while((index <= mask) && (table(index) == null || table(index).value == None)) index+=1 + } + + def hasNext = {advance(); index <= mask } + + def next() = { + advance() + val result = table(index) + index += 1 + nextResult(result) + } + protected def nextResult(node: Entry): A + } + + override def clone() = { + val it = new OpenHashMap[Key, Value] + foreachUndeletedEntry(entry => it.put(entry.key, entry.hash, entry.value.get)) + it + } + + /** Loop over the key, value mappings of this map. + * + * The behaviour of modifying the map during an iteration is as follows: + * - Deleting a mapping is always permitted. + * - Changing the value of mapping which is already present is permitted. + * - Anything else is not permitted. It will usually, but not always, throw an exception. + * + * @tparam U The return type of the specified function `f`, return result of which is ignored. + * @param f The function to apply to each key, value mapping. + */ + override def foreach[U](f : ((Key, Value)) => U): Unit = { + val startModCount = modCount + foreachUndeletedEntry(entry => { + if (modCount != startModCount) throw new ConcurrentModificationException + f((entry.key, entry.value.get))} + ) + } + override def foreachEntry[U](f : (Key, Value) => U): Unit = { + val startModCount = modCount + foreachUndeletedEntry(entry => { + if (modCount != startModCount) throw new ConcurrentModificationException + f(entry.key, entry.value.get)} + ) + } + + private[this] def foreachUndeletedEntry(f : Entry => Unit): Unit = { + table.foreach(entry => if (entry != null && entry.value != None) f(entry)) + } + + override def mapValuesInPlace(f : (Key, Value) => Value): this.type = { + foreachUndeletedEntry(entry => entry.value = Some(f(entry.key, entry.value.get))) + this + } + + override def filterInPlace(f : (Key, Value) => Boolean): this.type = { + foreachUndeletedEntry(entry => if (!f(entry.key, entry.value.get)) deleteSlot(entry)) + this + } + + override protected[this] def stringPrefix = "OpenHashMap" +} diff --git a/library/src/scala/collection/mutable/PriorityQueue.scala b/library/src/scala/collection/mutable/PriorityQueue.scala new file mode 100644 index 000000000000..f0146450e50f --- /dev/null +++ b/library/src/scala/collection/mutable/PriorityQueue.scala @@ -0,0 +1,414 @@ +/* + * 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.collection +package mutable + +import scala.language.`2.13` +import scala.collection.generic.DefaultSerializationProxy +import scala.math.Ordering + +/** A heap-based priority queue. + * + * To prioritize elements of type `A` there must be an implicit + * `Ordering[A]` available at creation. Elements are retrieved + * in priority order by using [[dequeue]] or [[dequeueAll]]. + * + * If multiple elements have the same priority as determined by the ordering for this + * `PriorityQueue`, no guarantees are made regarding the order in which those elements + * are returned by `dequeue` or `dequeueAll`. In particular, that means this + * class does not guarantee first-in-first-out behavior, as may be + * incorrectly inferred from the fact that this data structure is + * called a "queue". + * + * Only the `dequeue` and `dequeueAll` methods will return elements in priority + * order (while removing elements from the heap). Standard collection methods + * such as `drop`, `iterator`, `toList` and `toString` use an arbitrary + * iteration order: they will traverse the heap or remove elements + * in whichever order seems most convenient. + * + * Therefore, printing a `PriorityQueue` will not show elements in priority order, + * though the highest-priority element will be printed first. + * To print the elements in order, it's necessary to `dequeue` them. + * To do this non-destructively, duplicate the `PriorityQueue` first; + * the `clone` method is a suitable way to obtain a disposable copy. + * + * Client keys are assumed to be immutable. Mutating keys may violate + * the invariant of the underlying heap-ordered tree. Note that [[clone]] + * does not rebuild the underlying tree. + * + * {{{ + * scala> val pq = collection.mutable.PriorityQueue(1, 2, 5, 3, 7) + * val pq: scala.collection.mutable.PriorityQueue[Int] = PriorityQueue(7, 3, 5, 1, 2) + * + * scala> pq.toList // also not in order + * val res0: List[Int] = List(7, 3, 5, 1, 2) + * + * scala> pq.clone.dequeueAll + * val res1: Seq[Int] = ArraySeq(7, 5, 3, 2, 1) + * }}} + * + * @tparam A type of the elements in this priority queue. + * @param ord implicit ordering used to compare the elements of type `A`. + * + * @define Coll PriorityQueue + * @define coll priority queue + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +sealed class PriorityQueue[A](implicit val ord: Ordering[A]) + extends AbstractIterable[A] + with Iterable[A] + with IterableOps[A, Iterable, PriorityQueue[A]] + with StrictOptimizedIterableOps[A, Iterable, PriorityQueue[A]] + with Builder[A, PriorityQueue[A]] + with Cloneable[PriorityQueue[A]] + with Growable[A] + with Serializable +{ + + private class ResizableArrayAccess[A0] extends ArrayBuffer[A0] { + override def mapInPlace(f: A0 => A0): this.type = { + var i = 1 // see "we do not use array(0)" comment below (???) + val siz = this.size + while (i < siz) { this(i) = f(this(i)); i += 1 } + this + } + + def p_size0 = size0 + def p_size0_=(s: Int) = size0 = s + def p_array = array + def p_ensureSize(n: Int) = super.ensureSize(n) + def p_ensureAdditionalSize(n: Int) = super.ensureSize(size0 + n) + def p_swap(a: Int, b: Int): Unit = { + val h = array(a) + array(a) = array(b) + array(b) = h + } + } + + private val resarr = new ResizableArrayAccess[A] + + // we do not use array(0) + // storing the root of the heap at array(1) simplifies the calculations for + // parent and child indices: for a given index k, the parent of k is k / 2, + // the left child is k * 2, and the right child is k * 2 + 1 + resarr.p_size0 += 1 + /** Alias for [[size]]. */ + def length: Int = resarr.length - 1 // adjust length accordingly + override def size: Int = length + override def knownSize: Int = length + override def isEmpty: Boolean = resarr.p_size0 < 2 + + // not eligible for EvidenceIterableFactoryDefaults since C != CC[A] (PriorityQueue[A] != Iterable[A]) + override protected def fromSpecific(coll: scala.collection.IterableOnce[A]): PriorityQueue[A] = PriorityQueue.from(coll) + override protected def newSpecificBuilder: Builder[A, PriorityQueue[A]] = PriorityQueue.newBuilder + override def empty: PriorityQueue[A] = PriorityQueue.empty + + /** Replace the contents of this $coll with the mapped result. + * + * @param f the mapping function + * @return this $coll + */ + def mapInPlace(f: A => A): this.type = { + resarr.mapInPlace(f) + heapify(1) + this + } + + def result() = this + + private def toA(x: AnyRef): A = x.asInstanceOf[A] + protected def fixUp(as: Array[AnyRef], m: Int): Unit = { + var k: Int = m + // use `ord` directly to avoid allocating `OrderingOps` + while (k > 1 && ord.lt(toA(as(k / 2)), toA(as(k)))) { + resarr.p_swap(k, k / 2) + k = k / 2 + } + } + + protected def fixDown(as: Array[AnyRef], m: Int, n: Int): Boolean = { + // returns true if any swaps were done (used in heapify) + var k: Int = m + while (n >= 2 * k) { + var j = 2 * k + // use `ord` directly to avoid allocating `OrderingOps` + if (j < n && ord.lt(toA(as(j)), toA(as(j + 1)))) + j += 1 + if (ord.gteq(toA(as(k)), toA(as(j)))) + return k != m + else { + val h = as(k) + as(k) = as(j) + as(j) = h + k = j + } + } + k != m + } + + /** Inserts a single element into the priority queue. + * + * @param elem the element to insert. + * @return this $coll. + */ + def addOne(elem: A): this.type = { + resarr.p_ensureAdditionalSize(1) + resarr.p_array(resarr.p_size0) = elem.asInstanceOf[AnyRef] + fixUp(resarr.p_array, resarr.p_size0) + resarr.p_size0 += 1 + this + } + + override def addAll(xs: IterableOnce[A]): this.type = { + val from = resarr.p_size0 + for (x <- xs.iterator) unsafeAdd(x) + heapify(from) + this + } + + private def unsafeAdd(elem: A): Unit = { + // like += but skips fixUp, which breaks the ordering invariant + // a series of unsafeAdds MUST be followed by heapify + resarr.p_ensureAdditionalSize(1) + resarr.p_array(resarr.p_size0) = elem.asInstanceOf[AnyRef] + resarr.p_size0 += 1 + } + + private def heapify(from: Int): Unit = { + // elements at indices 1..from-1 were already in heap order before any adds + // elements at indices from..n are newly added, their order must be fixed + val n = length + + if (from <= 2) { + // no pre-existing order to maintain, do the textbook heapify algorithm + for (i <- n/2 to 1 by -1) fixDown(resarr.p_array, i, n) + } + else if (n - from < 4) { + // for very small adds, doing the simplest fix is faster + for (i <- from to n) fixUp(resarr.p_array, i) + } + else { + var min = from/2 // tracks the minimum element in the queue + val queue = scala.collection.mutable.Queue[Int](min) + + // do fixDown on the parents of all the new elements + // except the parent of the first new element, which is in the queue + // (that parent is treated specially because it might be the root) + for (i <- n/2 until min by -1) { + if (fixDown(resarr.p_array, i, n)) { + // there was a swap, so also need to fixDown i's parent + val parent = i/2 + if (parent < min) { // make sure same parent isn't added twice + min = parent + queue += parent + } + } + } + + while (queue.nonEmpty) { + val i = queue.dequeue() + if (fixDown(resarr.p_array, i, n)) { + val parent = i/2 + if (parent < min && parent > 0) { + // the "parent > 0" is to avoid adding the parent of the root + min = parent + queue += parent + } + } + } + } + } + + /** Adds all elements provided by a `IterableOnce` object + * into the priority queue. + * + * @param xs a iterable object. + * @return a new priority queue containing elements of both `xs` and `this`. + */ + def ++(xs: IterableOnce[A]): PriorityQueue[A] = { this.clone() ++= xs } + + /** Adds all elements to the queue. + * + * @param elems the elements to add. + */ + def enqueue(elems: A*): Unit = { this ++= elems } + + /** Returns the element with the highest priority in the queue, + * and removes this element from the queue. + * + * @return the element with the highest priority. + * @throws NoSuchElementException if no element to remove from heap + */ + def dequeue(): A = + if (resarr.p_size0 > 1) { + resarr.p_size0 = resarr.p_size0 - 1 + val result = resarr.p_array(1) + resarr.p_array(1) = resarr.p_array(resarr.p_size0) + resarr.p_array(resarr.p_size0) = null // erase reference from array + fixDown(resarr.p_array, 1, resarr.p_size0 - 1) + toA(result) + } else + throw new NoSuchElementException("no element to remove from heap") + + /** Dequeues all elements and returns them in a sequence, in priority order. */ + def dequeueAll[A1 >: A]: immutable.Seq[A1] = { + val b = ArrayBuilder.make[Any] + b.sizeHint(size) + while (nonEmpty) { + b += dequeue() + } + immutable.ArraySeq.unsafeWrapArray(b.result()).asInstanceOf[immutable.ArraySeq[A1]] + } + + /** Returns the element with the highest priority in the queue, + * or throws an error if there is no element contained in the queue. + * + * @return the element with the highest priority. + */ + override def head: A = if (resarr.p_size0 > 1) toA(resarr.p_array(1)) else throw new NoSuchElementException("queue is empty") + + /** Removes all elements from the queue. After this operation is completed, + * the queue will be empty. + */ + def clear(): Unit = { + resarr.clear() + resarr.p_size0 = 1 + } + + /** Returns an iterator which yields all the elements. + * + * Note: The order of elements returned is undefined. + * If you want to traverse the elements in priority queue + * order, use `clone().dequeueAll.iterator`. + * + * @return an iterator over all the elements. + */ + override def iterator: Iterator[A] = resarr.iterator.drop(1) + + /** Returns the reverse of this priority queue. The new priority queue has + * the same elements as the original, but the opposite ordering. + * + * For example, the element with the highest priority in `pq` has the lowest + * priority in `pq.reverse`, and vice versa. + * + * Ties are handled arbitrarily. Elements with equal priority may or + * may not be reversed with respect to each other. + * + * @return the reversed priority queue. + */ + def reverse: PriorityQueue[A] = { + val revq = new PriorityQueue[A]()(ord.reverse) + // copy the existing data into the new array backwards + // this won't put it exactly into the correct order, + // but will require less fixing than copying it in + // the original order + val n = resarr.p_size0 + revq.resarr.p_ensureSize(n) + revq.resarr.p_size0 = n + val from = resarr.p_array + val to = revq.resarr.p_array + for (i <- 1 until n) to(i) = from(n-i) + revq.heapify(1) + revq + } + + + /** Returns an iterator which yields all the elements in the reverse order + * than that returned by the method `iterator`. + * + * Note: The order of elements returned is undefined. + * + * @return an iterator over all elements sorted in descending order. + */ + def reverseIterator: Iterator[A] = new AbstractIterator[A] { + private[this] var i = resarr.p_size0 - 1 + def hasNext: Boolean = i >= 1 + def next(): A = { + val n = resarr.p_array(i) + i -= 1 + toA(n) + } + } + + /** Returns a regular queue containing the same elements. + * + * Note: the order of elements is undefined. + */ + def toQueue: Queue[A] = new Queue[A] ++= this.iterator + + /** Returns a textual representation of a queue as a string. + * + * @return the string representation of this queue. + */ + override def toString() = toList.mkString("PriorityQueue(", ", ", ")") + + /** Converts this $coll to a list. + * + * Note: the order of elements is undefined. + * + * @return a list containing all elements of this $coll. + */ + override def toList: immutable.List[A] = immutable.List.from(this.iterator) + + /** This method clones the priority queue. + * + * @return a priority queue with the same elements. + */ + override def clone(): PriorityQueue[A] = { + val pq = new PriorityQueue[A] + val n = resarr.p_size0 + pq.resarr.p_ensureSize(n) + java.lang.System.arraycopy(resarr.p_array, 1, pq.resarr.p_array, 1, n-1) + pq.resarr.p_size0 = n + pq + } + + override def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): Int = { + val copied = IterableOnce.elemsToCopyToArray(length, xs.length, start, len) + if (copied > 0) { + Array.copy(resarr.p_array, 1, xs, start, copied) + } + copied + } + + @deprecated("Use `PriorityQueue` instead", "2.13.0") + def orderedCompanion: PriorityQueue.type = PriorityQueue + + protected[this] def writeReplace(): AnyRef = new DefaultSerializationProxy(PriorityQueue.evidenceIterableFactory[A], this) + + override protected[this] def className = "PriorityQueue" +} + + +@SerialVersionUID(3L) +object PriorityQueue extends SortedIterableFactory[PriorityQueue] { + def newBuilder[A : Ordering]: Builder[A, PriorityQueue[A]] = { + new Builder[A, PriorityQueue[A]] { + val pq = new PriorityQueue[A] + def addOne(elem: A): this.type = { pq.unsafeAdd(elem); this } + def result(): PriorityQueue[A] = { pq.heapify(1); pq } + def clear(): Unit = pq.clear() + } + } + + def empty[A : Ordering]: PriorityQueue[A] = new PriorityQueue[A] + + def from[E : Ordering](it: IterableOnce[E]): PriorityQueue[E] = { + val b = newBuilder[E] + b ++= it + b.result() + } +} diff --git a/library/src/scala/collection/mutable/Queue.scala b/library/src/scala/collection/mutable/Queue.scala new file mode 100644 index 000000000000..7a0c25c049b6 --- /dev/null +++ b/library/src/scala/collection/mutable/Queue.scala @@ -0,0 +1,139 @@ +/* + * 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.collection +package mutable + +import scala.language.`2.13` +import scala.annotation.nowarn +import scala.collection.generic.DefaultSerializable + + +/** `Queue` objects implement data structures that allow to + * insert and retrieve elements in a first-in-first-out (FIFO) manner. + * + * @define Coll `mutable.Queue` + * @define coll mutable queue + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +class Queue[A] protected (array: Array[AnyRef], start: Int, end: Int) + extends ArrayDeque[A](array, start, end) + with IndexedSeqOps[A, Queue, Queue[A]] + with StrictOptimizedSeqOps[A, Queue, Queue[A]] + with IterableFactoryDefaults[A, Queue] + with ArrayDequeOps[A, Queue, Queue[A]] + with Cloneable[Queue[A]] + with DefaultSerializable { + + def this(initialSize: Int = ArrayDeque.DefaultInitialSize) = + this(ArrayDeque.alloc(initialSize), start = 0, end = 0) + + override def iterableFactory: SeqFactory[Queue] = Queue + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix = "Queue" + + /** + * Add elements to the end of this queue + * + * @param elem + * @return this + */ + def enqueue(elem: A): this.type = this += elem + + /** Enqueue two or more elements at the end of the queue. The last element + * of the sequence will be on end of the queue. + * + * @param elems the element sequence. + * @return this + */ + def enqueue(elem1: A, elem2: A, elems: A*): this.type = enqueue(elem1).enqueue(elem2).enqueueAll(elems) + + /** Enqueues all elements in the given iterable object into the queue. The + * last element in the iterable object will be on front of the new queue. + * + * @param elems the iterable object. + * @return this + */ + def enqueueAll(elems: scala.collection.IterableOnce[A]): this.type = this ++= elems + + /** + * Removes the first element from this queue and returns it + * + * @return + * @throws NoSuchElementException when queue is empty + */ + def dequeue(): A = removeHead() + + /** Returns the first element in the queue which satisfies the + * given predicate, and removes this element from the queue. + * + * @param p the predicate used for choosing the first element + * @return the first element of the queue for which p yields true + */ + def dequeueFirst(p: A => Boolean): Option[A] = + removeFirst(p) + + /** Returns all elements in the queue which satisfy the + * given predicate, and removes those elements from the queue. + * + * @param p the predicate used for choosing elements + * @return a sequence of all elements in the queue for which + * p yields true. + */ + def dequeueAll(p: A => Boolean): scala.collection.immutable.Seq[A] = + removeAll(p) + + /** + * Returns and dequeues all elements from the queue which satisfy the given predicate + * + * @param f the predicate used for choosing elements + * @return The removed elements + */ + def dequeueWhile(f: A => Boolean): scala.collection.Seq[A] = removeHeadWhile(f) + + /** Returns the first element in the queue, or throws an error if there + * is no element contained in the queue. + * + * @return the first element. + */ + @`inline` final def front: A = head + + override protected def klone(): Queue[A] = { + val bf = newSpecificBuilder + bf ++= this + bf.result() + } + + override protected def ofArray(array: Array[AnyRef], end: Int): Queue[A] = + new Queue(array, start = 0, end) + +} + +/** + * $factoryInfo + * @define coll queue + * @define Coll `Queue` + */ +@SerialVersionUID(3L) +object Queue extends StrictOptimizedSeqFactory[Queue] { + + def from[A](source: IterableOnce[A]): Queue[A] = empty ++= source + + def empty[A]: Queue[A] = new Queue + + def newBuilder[A]: Builder[A, Queue[A]] = new GrowableBuilder[A, Queue[A]](empty) + +} diff --git a/library/src/scala/collection/mutable/RedBlackTree.scala b/library/src/scala/collection/mutable/RedBlackTree.scala new file mode 100644 index 000000000000..e4f2c54017a8 --- /dev/null +++ b/library/src/scala/collection/mutable/RedBlackTree.scala @@ -0,0 +1,653 @@ +/* + * 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 collection.mutable + +import scala.language.`2.13` +import scala.annotation.tailrec +import collection.{AbstractIterator, Iterator} +import java.lang.String + +/** + * An object containing the red-black tree implementation used by mutable `TreeMaps`. + * + * The trees implemented in this object are *not* thread safe. + */ +private[collection] object RedBlackTree { + + // ---- class structure ---- + + // For performance reasons, this implementation uses `null` references to represent leaves instead of a sentinel node. + // Currently, the internal nodes do not store their subtree size - only the tree object keeps track of their size. + // Therefore, while obtaining the size of the whole tree is O(1), knowing the number of entries inside a range is O(n) + // on the size of the range. + + final class Tree[A, B](var root: Node[A, B], var size: Int) { + def treeCopy(): Tree[A, B] = new Tree(copyTree(root), size) + } + + final class Node[A, B](var key: A, var value: B, var red: Boolean, var left: Node[A, B], var right: Node[A, B], var parent: Node[A, B]) { + override def toString: String = "Node(" + key + ", " + value + ", " + red + ", " + left + ", " + right + ")" + } + + object Tree { + def empty[A, B]: Tree[A, B] = new Tree(null, 0) + } + + object Node { + + @`inline` def apply[A, B](key: A, value: B, red: Boolean, + left: Node[A, B], right: Node[A, B], parent: Node[A, B]): Node[A, B] = + new Node(key, value, red, left, right, parent) + + @`inline` def leaf[A, B](key: A, value: B, red: Boolean, parent: Node[A, B]): Node[A, B] = + new Node(key, value, red, null, null, parent) + + def unapply[A, B](t: Node[A, B]) = Some((t.key, t.value, t.left, t.right, t.parent)) + } + + // ---- getters ---- + + def isRed(node: Node[_, _]) = (node ne null) && node.red + def isBlack(node: Node[_, _]) = (node eq null) || !node.red + + // ---- size ---- + + def size(node: Node[_, _]): Int = if (node eq null) 0 else 1 + size(node.left) + size(node.right) + def size(tree: Tree[_, _]): Int = tree.size + def isEmpty(tree: Tree[_, _]) = tree.root eq null + def clear(tree: Tree[_, _]): Unit = { tree.root = null; tree.size = 0 } + + // ---- search ---- + + def get[A: Ordering, B](tree: Tree[A, B], key: A): Option[B] = getNode(tree.root, key) match { + case null => None + case node => Some(node.value) + } + + @tailrec private[this] def getNode[A, B](node: Node[A, B], key: A)(implicit ord: Ordering[A]): Node[A, B] = + if (node eq null) null + else { + val cmp = ord.compare(key, node.key) + if (cmp < 0) getNode(node.left, key) + else if (cmp > 0) getNode(node.right, key) + else node + } + + def contains[A: Ordering](tree: Tree[A, _], key: A): Boolean = getNode(tree.root, key) ne null + + def min[A, B](tree: Tree[A, B]): Option[(A, B)] = minNode(tree.root) match { + case null => None + case node => Some((node.key, node.value)) + } + + def minKey[A](tree: Tree[A, _]): Option[A] = minNode(tree.root) match { + case null => None + case node => Some(node.key) + } + + private def minNode[A, B](node: Node[A, B]): Node[A, B] = + if (node eq null) null else minNodeNonNull(node) + + @tailrec def minNodeNonNull[A, B](node: Node[A, B]): Node[A, B] = + if (node.left eq null) node else minNodeNonNull(node.left) + + def max[A, B](tree: Tree[A, B]): Option[(A, B)] = maxNode(tree.root) match { + case null => None + case node => Some((node.key, node.value)) + } + + def maxKey[A](tree: Tree[A, _]): Option[A] = maxNode(tree.root) match { + case null => None + case node => Some(node.key) + } + + private def maxNode[A, B](node: Node[A, B]): Node[A, B] = + if (node eq null) null else maxNodeNonNull(node) + + @tailrec def maxNodeNonNull[A, B](node: Node[A, B]): Node[A, B] = + if (node.right eq null) node else maxNodeNonNull(node.right) + + /** + * Returns the first (lowest) map entry with a key equal or greater than `key`. Returns `None` if there is no such + * node. + */ + def minAfter[A, B](tree: Tree[A, B], key: A)(implicit ord: Ordering[A]): Option[(A, B)] = + minNodeAfter(tree.root, key) match { + case null => None + case node => Some((node.key, node.value)) + } + + def minKeyAfter[A](tree: Tree[A, _], key: A)(implicit ord: Ordering[A]): Option[A] = + minNodeAfter(tree.root, key) match { + case null => None + case node => Some(node.key) + } + + private[this] def minNodeAfter[A, B](node: Node[A, B], key: A)(implicit ord: Ordering[A]): Node[A, B] = { + if (node eq null) null + else { + var y: Node[A, B] = null + var x = node + var cmp = 1 + while ((x ne null) && cmp != 0) { + y = x + cmp = ord.compare(key, x.key) + x = if (cmp < 0) x.left else x.right + } + if (cmp <= 0) y else successor(y) + } + } + + /** + * Returns the last (highest) map entry with a key smaller than `key`. Returns `None` if there is no such node. + */ + def maxBefore[A, B](tree: Tree[A, B], key: A)(implicit ord: Ordering[A]): Option[(A, B)] = + maxNodeBefore(tree.root, key) match { + case null => None + case node => Some((node.key, node.value)) + } + + def maxKeyBefore[A](tree: Tree[A, _], key: A)(implicit ord: Ordering[A]): Option[A] = + maxNodeBefore(tree.root, key) match { + case null => None + case node => Some(node.key) + } + + private[this] def maxNodeBefore[A, B](node: Node[A, B], key: A)(implicit ord: Ordering[A]): Node[A, B] = { + if (node eq null) null + else { + var y: Node[A, B] = null + var x = node + var cmp = 1 + while ((x ne null) && cmp != 0) { + y = x + cmp = ord.compare(key, x.key) + x = if (cmp < 0) x.left else x.right + } + if (cmp > 0) y else predecessor(y) + } + } + + // ---- insertion ---- + + def insert[A, B](tree: Tree[A, B], key: A, value: B)(implicit ord: Ordering[A]): Unit = { + var y: Node[A, B] = null + var x = tree.root + var cmp = 1 + while ((x ne null) && cmp != 0) { + y = x + cmp = ord.compare(key, x.key) + x = if (cmp < 0) x.left else x.right + } + + if (cmp == 0) y.value = value + else { + val z = Node.leaf(key, value, red = true, y) + + if (y eq null) tree.root = z + else if (cmp < 0) y.left = z + else y.right = z + + fixAfterInsert(tree, z) + tree.size += 1 + } + } + + private[this] def fixAfterInsert[A, B](tree: Tree[A, B], node: Node[A, B]): Unit = { + var z = node + while (isRed(z.parent)) { + if (z.parent eq z.parent.parent.left) { + val y = z.parent.parent.right + if (isRed(y)) { + z.parent.red = false + y.red = false + z.parent.parent.red = true + z = z.parent.parent + } else { + if (z eq z.parent.right) { + z = z.parent + rotateLeft(tree, z) + } + z.parent.red = false + z.parent.parent.red = true + rotateRight(tree, z.parent.parent) + } + } else { // symmetric cases + val y = z.parent.parent.left + if (isRed(y)) { + z.parent.red = false + y.red = false + z.parent.parent.red = true + z = z.parent.parent + } else { + if (z eq z.parent.left) { + z = z.parent + rotateRight(tree, z) + } + z.parent.red = false + z.parent.parent.red = true + rotateLeft(tree, z.parent.parent) + } + } + } + tree.root.red = false + } + + // ---- deletion ---- + + def delete[A, B](tree: Tree[A, B], key: A)(implicit ord: Ordering[A]): Unit = { + val z = getNode(tree.root, key) + if (z ne null) { + var y = z + var yIsRed = y.red + var x: Node[A, B] = null + var xParent: Node[A, B] = null + + if (z.left eq null) { + x = z.right + transplant(tree, z, z.right) + xParent = z.parent + } + else if (z.right eq null) { + x = z.left + transplant(tree, z, z.left) + xParent = z.parent + } + else { + y = minNodeNonNull(z.right) + yIsRed = y.red + x = y.right + + if (y.parent eq z) xParent = y + else { + xParent = y.parent + transplant(tree, y, y.right) + y.right = z.right + y.right.parent = y + } + transplant(tree, z, y) + y.left = z.left + y.left.parent = y + y.red = z.red + } + + if (!yIsRed) fixAfterDelete(tree, x, xParent) + tree.size -= 1 + } + } + + private[this] def fixAfterDelete[A, B](tree: Tree[A, B], node: Node[A, B], parent: Node[A, B]): Unit = { + var x = node + var xParent = parent + while ((x ne tree.root) && isBlack(x)) { + if (x eq xParent.left) { + var w = xParent.right + // assert(w ne null) + + if (w.red) { + w.red = false + xParent.red = true + rotateLeft(tree, xParent) + w = xParent.right + } + if (isBlack(w.left) && isBlack(w.right)) { + w.red = true + x = xParent + } else { + if (isBlack(w.right)) { + w.left.red = false + w.red = true + rotateRight(tree, w) + w = xParent.right + } + w.red = xParent.red + xParent.red = false + w.right.red = false + rotateLeft(tree, xParent) + x = tree.root + } + } else { // symmetric cases + var w = xParent.left + // assert(w ne null) + + if (w.red) { + w.red = false + xParent.red = true + rotateRight(tree, xParent) + w = xParent.left + } + if (isBlack(w.right) && isBlack(w.left)) { + w.red = true + x = xParent + } else { + if (isBlack(w.left)) { + w.right.red = false + w.red = true + rotateLeft(tree, w) + w = xParent.left + } + w.red = xParent.red + xParent.red = false + w.left.red = false + rotateRight(tree, xParent) + x = tree.root + } + } + xParent = x.parent + } + if (x ne null) x.red = false + } + + // ---- helpers ---- + + /** + * Returns the node that follows `node` in an in-order tree traversal. If `node` has the maximum key (and is, + * therefore, the last node), this method returns `null`. + */ + private[this] def successor[A, B](node: Node[A, B]): Node[A, B] = { + if (node.right ne null) minNodeNonNull(node.right) + else { + var x = node + var y = x.parent + while ((y ne null) && (x eq y.right)) { + x = y + y = y.parent + } + y + } + } + + /** + * Returns the node that precedes `node` in an in-order tree traversal. If `node` has the minimum key (and is, + * therefore, the first node), this method returns `null`. + */ + private[this] def predecessor[A, B](node: Node[A, B]): Node[A, B] = { + if (node.left ne null) maxNodeNonNull(node.left) + else { + var x = node + var y = x.parent + while ((y ne null) && (x eq y.left)) { + x = y + y = y.parent + } + y + } + } + + private[this] def rotateLeft[A, B](tree: Tree[A, B], x: Node[A, B]): Unit = if (x ne null) { + // assert(x.right ne null) + val y = x.right + x.right = y.left + + if (y.left ne null) y.left.parent = x + y.parent = x.parent + + if (x.parent eq null) tree.root = y + else if (x eq x.parent.left) x.parent.left = y + else x.parent.right = y + + y.left = x + x.parent = y + } + + private[this] def rotateRight[A, B](tree: Tree[A, B], x: Node[A, B]): Unit = if (x ne null) { + // assert(x.left ne null) + val y = x.left + x.left = y.right + + if (y.right ne null) y.right.parent = x + y.parent = x.parent + + if (x.parent eq null) tree.root = y + else if (x eq x.parent.right) x.parent.right = y + else x.parent.left = y + + y.right = x + x.parent = y + } + + /** + * Transplant the node `from` to the place of node `to`. This is done by setting `from` as a child of `to`'s previous + * parent and setting `from`'s parent to the `to`'s previous parent. The children of `from` are left unchanged. + */ + private[this] def transplant[A, B](tree: Tree[A, B], to: Node[A, B], from: Node[A, B]): Unit = { + if (to.parent eq null) tree.root = from + else if (to eq to.parent.left) to.parent.left = from + else to.parent.right = from + + if (from ne null) from.parent = to.parent + } + + // ---- tree traversal ---- + + def foreach[A, B, U](tree: Tree[A, B], f: ((A, B)) => U): Unit = foreachNode(tree.root, f) + + private[this] def foreachNode[A, B, U](node: Node[A, B], f: ((A, B)) => U): Unit = + if (node ne null) foreachNodeNonNull(node, f) + + private[this] def foreachNodeNonNull[A, B, U](node: Node[A, B], f: ((A, B)) => U): Unit = { + if (node.left ne null) foreachNodeNonNull(node.left, f) + f((node.key, node.value)) + if (node.right ne null) foreachNodeNonNull(node.right, f) + } + + def foreachKey[A, U](tree: Tree[A, _], f: A => U): Unit = { + def g(node: Node[A, _]): Unit = { + val l = node.left + if(l ne null) g(l) + f(node.key) + val r = node.right + if(r ne null) g(r) + } + val r = tree.root + if(r ne null) g(r) + } + + def foreachEntry[A, B, U](tree: Tree[A, B], f: (A, B) => U): Unit = { + def g(node: Node[A, B]): Unit = { + val l = node.left + if(l ne null) g(l) + f(node.key, node.value) + val r = node.right + if(r ne null) g(r) + } + val r = tree.root + if(r ne null) g(r) + } + + def transform[A, B](tree: Tree[A, B], f: (A, B) => B): Unit = transformNode(tree.root, f) + + private[this] def transformNode[A, B, U](node: Node[A, B], f: (A, B) => B): Unit = + if (node ne null) transformNodeNonNull(node, f) + + private[this] def transformNodeNonNull[A, B, U](node: Node[A, B], f: (A, B) => B): Unit = { + if (node.left ne null) transformNodeNonNull(node.left, f) + node.value = f(node.key, node.value) + if (node.right ne null) transformNodeNonNull(node.right, f) + } + + def iterator[A: Ordering, B](tree: Tree[A, B], start: Option[A] = None, end: Option[A] = None): Iterator[(A, B)] = + new EntriesIterator(tree, start, end) + + def keysIterator[A: Ordering](tree: Tree[A, _], start: Option[A] = None, end: Option[A] = None): Iterator[A] = + new KeysIterator(tree, start, end) + + def valuesIterator[A: Ordering, B](tree: Tree[A, B], start: Option[A] = None, end: Option[A] = None): Iterator[B] = + new ValuesIterator(tree, start, end) + + private[this] abstract class TreeIterator[A, B, R](tree: Tree[A, B], start: Option[A], end: Option[A]) + (implicit ord: Ordering[A]) extends AbstractIterator[R] { + + protected def nextResult(node: Node[A, B]): R + + def hasNext: Boolean = nextNode ne null + + @throws[NoSuchElementException] + def next(): R = nextNode match { + case null => throw new NoSuchElementException("next on empty iterator") + case node => + nextNode = successor(node) + setNullIfAfterEnd() + nextResult(node) + } + + private[this] var nextNode: Node[A, B] = start match { + case None => minNode(tree.root) + case Some(from) => minNodeAfter(tree.root, from) + } + + private[this] def setNullIfAfterEnd(): Unit = + if (end.isDefined && (nextNode ne null) && ord.compare(nextNode.key, end.get) >= 0) + nextNode = null + + setNullIfAfterEnd() + } + + private[this] final class EntriesIterator[A: Ordering, B](tree: Tree[A, B], start: Option[A], end: Option[A]) + extends TreeIterator[A, B, (A, B)](tree, start, end) { + + def nextResult(node: Node[A, B]) = (node.key, node.value) + } + + private[this] final class KeysIterator[A: Ordering, B](tree: Tree[A, B], start: Option[A], end: Option[A]) + extends TreeIterator[A, B, A](tree, start, end) { + + def nextResult(node: Node[A, B]) = node.key + } + + private[this] final class ValuesIterator[A: Ordering, B](tree: Tree[A, B], start: Option[A], end: Option[A]) + extends TreeIterator[A, B, B](tree, start, end) { + + def nextResult(node: Node[A, B]) = node.value + } + + // ---- debugging ---- + + /** + * Checks if the tree is in a valid state. That happens if: + * - It is a valid binary search tree; + * - All red-black properties are satisfied; + * - All non-null nodes have their `parent` reference correct; + * - The size variable in `tree` corresponds to the actual size of the tree. + */ + def isValid[A: Ordering, B](tree: Tree[A, B]): Boolean = + isValidBST(tree.root) && hasProperParentRefs(tree) && isValidRedBlackTree(tree) && size(tree.root) == tree.size + + /** + * Returns true if all non-null nodes have their `parent` reference correct. + */ + private[this] def hasProperParentRefs[A, B](tree: Tree[A, B]): Boolean = { + + def hasProperParentRefs(node: Node[A, B]): Boolean = { + if (node eq null) true + else { + if ((node.left ne null) && (node.left.parent ne node) || + (node.right ne null) && (node.right.parent ne node)) false + else hasProperParentRefs(node.left) && hasProperParentRefs(node.right) + } + } + + if(tree.root eq null) true + else (tree.root.parent eq null) && hasProperParentRefs(tree.root) + } + + /** + * Returns true if this node follows the properties of a binary search tree. + */ + private[this] def isValidBST[A, B](node: Node[A, B])(implicit ord: Ordering[A]): Boolean = { + if (node eq null) true + else { + if ((node.left ne null) && (ord.compare(node.key, node.left.key) <= 0) || + (node.right ne null) && (ord.compare(node.key, node.right.key) >= 0)) false + else isValidBST(node.left) && isValidBST(node.right) + } + } + + /** + * Returns true if the tree has all the red-black tree properties: if the root node is black, if all children of red + * nodes are black and if the path from any node to any of its null children has the same number of black nodes. + */ + private[this] def isValidRedBlackTree[A, B](tree: Tree[A, B]): Boolean = { + + def noRedAfterRed(node: Node[A, B]): Boolean = { + if (node eq null) true + else if (node.red && (isRed(node.left) || isRed(node.right))) false + else noRedAfterRed(node.left) && noRedAfterRed(node.right) + } + + def blackHeight(node: Node[A, B]): Int = { + if (node eq null) 1 + else { + val lh = blackHeight(node.left) + val rh = blackHeight(node.right) + + if (lh == -1 || lh != rh) -1 + else if (isRed(node)) lh + else lh + 1 + } + } + + isBlack(tree.root) && noRedAfterRed(tree.root) && blackHeight(tree.root) >= 0 + } + + // building + + /** Build a Tree suitable for a TreeSet from an ordered sequence of keys */ + def fromOrderedKeys[A](xs: Iterator[A], size: Int): Tree[A, Null] = { + val maxUsedDepth = 32 - Integer.numberOfLeadingZeros(size) // maximum depth of non-leaf nodes + def f(level: Int, size: Int): Node[A, Null] = size match { + case 0 => null + case 1 => new Node(xs.next(), null, level == maxUsedDepth && level != 1, null, null, null) + case n => + val leftSize = (size-1)/2 + val left = f(level+1, leftSize) + val x = xs.next() + val right = f(level+1, size-1-leftSize) + val n = new Node(x, null, red = false, left, right, null) + if(left ne null) left.parent = n + right.parent = n + n + } + new Tree(f(1, size), size) + } + + /** Build a Tree suitable for a TreeMap from an ordered sequence of key/value pairs */ + def fromOrderedEntries[A, B](xs: Iterator[(A, B)], size: Int): Tree[A, B] = { + val maxUsedDepth = 32 - Integer.numberOfLeadingZeros(size) // maximum depth of non-leaf nodes + def f(level: Int, size: Int): Node[A, B] = size match { + case 0 => null + case 1 => + val (k, v) = xs.next() + new Node(k, v, level == maxUsedDepth && level != 1, null, null, null) + case n => + val leftSize = (size-1)/2 + val left = f(level+1, leftSize) + val (k, v) = xs.next() + val right = f(level+1, size-1-leftSize) + val n = new Node(k, v, red = false, left, right, null) + if(left ne null) left.parent = n + right.parent = n + n + } + new Tree(f(1, size), size) + } + + def copyTree[A, B](n: Node[A, B]): Node[A, B] = + if(n eq null) null else { + val c = new Node(n.key, n.value, n.red, copyTree(n.left), copyTree(n.right), null) + if(c.left != null) c.left.parent = c + if(c.right != null) c.right.parent = c + c + } +} diff --git a/library/src/scala/collection/mutable/ReusableBuilder.scala b/library/src/scala/collection/mutable/ReusableBuilder.scala new file mode 100644 index 000000000000..1cba786c749a --- /dev/null +++ b/library/src/scala/collection/mutable/ReusableBuilder.scala @@ -0,0 +1,56 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` + +/** `ReusableBuilder` is a marker trait that indicates that a `Builder` + * can be reused to build more than one instance of a collection. In + * particular, calling `result()` followed by `clear()` will produce a + * collection and reset the builder to begin building a new collection + * of the same type. + * + * In general no method other than `clear()` may be called after `result()`. + * It is up to subclasses to implement and to document other allowed sequences + * of operations (e.g. calling other methods after `result()` in order to obtain + * different snapshots of a collection under construction). + * + * @tparam Elem the type of elements that get added to the builder. + * @tparam To the type of collection that it produced. + * + * @define multipleResults + * + * This Builder can be reused after calling `result()` without an + * intermediate call to `clear()` in order to build multiple related results. + */ +trait ReusableBuilder[-Elem, +To] extends Builder[Elem, To] { + /** Clears the contents of this builder. + * After execution of this method, the builder will contain no elements. + * + * If executed immediately after a call to `result()`, this allows a new + * instance of the same type of collection to be built. + */ + override def clear(): Unit // Note: overriding for Scaladoc only! + + /** Produces a collection from the added elements. + * + * After a call to `result`, the behavior of all other methods is undefined + * save for `clear()`. If `clear()` is called, then the builder is reset and + * may be used to build another instance. + * + * @return a collection containing the elements added to this builder. + */ + override def result(): To // Note: overriding for Scaladoc only! +} diff --git a/library/src/scala/collection/mutable/Seq.scala b/library/src/scala/collection/mutable/Seq.scala new file mode 100644 index 000000000000..ed5416ab7fe6 --- /dev/null +++ b/library/src/scala/collection/mutable/Seq.scala @@ -0,0 +1,68 @@ +/* + * 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.collection.mutable + +import scala.language.`2.13` +import scala.collection.{IterableFactoryDefaults, SeqFactory} + +trait Seq[A] + extends Iterable[A] + with collection.Seq[A] + with SeqOps[A, Seq, Seq[A]] + with IterableFactoryDefaults[A, Seq] { + + override def iterableFactory: SeqFactory[Seq] = Seq +} + +/** + * $factoryInfo + * @define coll mutable sequence + * @define Coll `mutable.Seq` + */ +@SerialVersionUID(3L) +object Seq extends SeqFactory.Delegate[Seq](ArrayBuffer) + +/** + * @define coll mutable sequence + * @define Coll `mutable.Seq` + */ +transparent trait SeqOps[A, +CC[_], +C <: AnyRef] + extends collection.SeqOps[A, CC, C] + with Cloneable[C] { + + override def clone(): C = { + val b = newSpecificBuilder + b ++= this + b.result() + } + + /** Replaces element at given index with a new value. + * + * @param idx the index of the element to replace. + * @param elem the new value. + * @throws IndexOutOfBoundsException if the index is not valid. + */ + @throws[IndexOutOfBoundsException] + def update(idx: Int, elem: A): Unit + + @deprecated("Use `mapInPlace` on an `IndexedSeq` instead", "2.13.0") + @`inline`final def transform(f: A => A): this.type = { + var i = 0 + val siz = size + while (i < siz) { this(i) = f(this(i)); i += 1 } + this + } +} + +/** Explicit instantiation of the `Seq` trait to reduce class file size in subclasses. */ +abstract class AbstractSeq[A] extends scala.collection.AbstractSeq[A] with Seq[A] diff --git a/library/src/scala/collection/mutable/SeqMap.scala b/library/src/scala/collection/mutable/SeqMap.scala new file mode 100644 index 000000000000..bad3ea10b243 --- /dev/null +++ b/library/src/scala/collection/mutable/SeqMap.scala @@ -0,0 +1,40 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` + +/** + * A generic trait for ordered mutable maps. Concrete classes have to provide + * functionality for the abstract methods in `SeqMap`. + * + * Note that when checking for equality [[SeqMap]] does not take into account + * ordering. + * + * @tparam K the type of the keys contained in this linked map. + * @tparam V the type of the values associated with the keys in this linked map. + * + * @define coll mutable Seq map + * @define Coll `mutable.SeqMap` + */ + +trait SeqMap[K, V] extends Map[K, V] + with collection.SeqMap[K, V] + with MapOps[K, V, SeqMap, SeqMap[K, V]] + with MapFactoryDefaults[K, V, SeqMap, Iterable] { + override def mapFactory: MapFactory[SeqMap] = SeqMap +} + +object SeqMap extends MapFactory.Delegate[SeqMap](LinkedHashMap) diff --git a/library/src/scala/collection/mutable/Set.scala b/library/src/scala/collection/mutable/Set.scala new file mode 100644 index 000000000000..299cab4ac23c --- /dev/null +++ b/library/src/scala/collection/mutable/Set.scala @@ -0,0 +1,123 @@ +/* + * 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.collection.mutable + +import scala.language.`2.13` +import scala.collection.{IterableFactory, IterableFactoryDefaults, IterableOps} + +/** Base trait for mutable sets */ +trait Set[A] + extends Iterable[A] + with collection.Set[A] + with SetOps[A, Set, Set[A]] + with IterableFactoryDefaults[A, Set] { + + override def iterableFactory: IterableFactory[Set] = Set +} + +/** + * @define coll mutable set + * @define Coll `mutable.Set` + */ +transparent trait SetOps[A, +CC[X], +C <: SetOps[A, CC, C]] + extends collection.SetOps[A, CC, C] + with IterableOps[A, CC, C] // only needed so we can use super[IterableOps] below + with Cloneable[C] + with Builder[A, C] + with Growable[A] + with Shrinkable[A] { + + def result(): C = coll + + /** Check whether the set contains the given element, and add it if not. + * + * @param elem the element to be added + * @return true if the element was added + */ + def add(elem: A): Boolean = + !contains(elem) && { + coll += elem; true + } + + /** Updates the presence of a single element in this set. + * + * This method allows one to add or remove an element `elem` + * from this set depending on the value of parameter `included`. + * Typically, one would use the following syntax: + * {{{ + * set(elem) = true // adds element + * set(elem) = false // removes element + * }}} + * + * @param elem the element to be added or removed + * @param included a flag indicating whether element should be included or excluded. + */ + def update(elem: A, included: Boolean): Unit = { + if (included) add(elem) + else remove(elem) + } + + /** Removes an element from this set. + * + * @param elem the element to be removed + * @return true if this set contained the element before it was removed + */ + def remove(elem: A): Boolean = { + val res = contains(elem) + coll -= elem + res + } + + def diff(that: collection.Set[A]): C = + foldLeft(empty)((result, elem) => if (that contains elem) result else result += elem) + + @deprecated("Use filterInPlace instead", "2.13.0") + @inline final def retain(p: A => Boolean): Unit = filterInPlace(p) + + /** Removes all elements from the set for which do not satisfy a predicate. + * @param p the predicate used to test elements. Only elements for + * which `p` returns `true` are retained in the set; all others + * are removed. + */ + def filterInPlace(p: A => Boolean): this.type = { + if (nonEmpty) { + val array = this.toArray[Any] // scala/bug#7269 toArray avoids ConcurrentModificationException + val arrayLength = array.length + var i = 0 + while (i < arrayLength) { + val elem = array(i).asInstanceOf[A] + if (!p(elem)) { + this -= elem + } + i += 1 + } + } + this + } + + override def clone(): C = empty ++= this + + override def knownSize: Int = super[IterableOps].knownSize +} + +/** + * $factoryInfo + * @define coll mutable set + * @define Coll `mutable.Set` + */ +@SerialVersionUID(3L) +object Set extends IterableFactory.Delegate[Set](HashSet) + + +/** Explicit instantiation of the `Set` trait to reduce class file size in subclasses. */ +abstract class AbstractSet[A] extends scala.collection.AbstractSet[A] with Set[A] diff --git a/library/src/scala/collection/mutable/Shrinkable.scala b/library/src/scala/collection/mutable/Shrinkable.scala new file mode 100644 index 000000000000..96171c6df024 --- /dev/null +++ b/library/src/scala/collection/mutable/Shrinkable.scala @@ -0,0 +1,80 @@ +/* + * 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 collection.mutable + +import scala.language.`2.13` +import scala.annotation.tailrec + +/** This trait forms part of collections that can be reduced + * using a `-=` operator. + * + * @define coll shrinkable collection + * @define Coll `Shrinkable` + */ +trait Shrinkable[-A] { + + /** Removes a single element from this $coll. + * + * @param elem the element to remove. + * @return the $coll itself + */ + def subtractOne(elem: A): this.type + + /** Alias for `subtractOne` */ + @`inline` final def -= (elem: A): this.type = subtractOne(elem) + + /** Removes two or more elements from this $coll. + * + * @param elem1 the first element to remove. + * @param elem2 the second element to remove. + * @param elems the remaining elements to remove. + * @return the $coll itself + */ + @deprecated("Use `--=` aka `subtractAll` instead of varargs `-=`; infix operations with an operand of multiple args will be deprecated", "2.13.3") + def -= (elem1: A, elem2: A, elems: A*): this.type = { + this -= elem1 + this -= elem2 + this --= elems + } + + /** Removes all elements produced by an iterator from this $coll. + * + * @param xs the iterator producing the elements to remove. + * @return the $coll itself + */ + def subtractAll(xs: collection.IterableOnce[A]): this.type = { + @tailrec def loop(xs: collection.LinearSeq[A]): Unit = { + if (xs.nonEmpty) { + subtractOne(xs.head) + loop(xs.tail) + } + } + if (xs.asInstanceOf[AnyRef] eq this) { // avoid mutating under our own iterator + xs match { + case xs: Clearable => xs.clear() + case xs => subtractAll(Buffer.from(xs)) + } + } else { + xs match { + case xs: collection.LinearSeq[A] => loop(xs) + case xs => xs.iterator.foreach(subtractOne) + } + } + this + } + + /** Alias for `subtractAll` */ + @`inline` final def --= (xs: collection.IterableOnce[A]): this.type = subtractAll(xs) + +} diff --git a/library/src/scala/collection/mutable/SortedMap.scala b/library/src/scala/collection/mutable/SortedMap.scala new file mode 100644 index 000000000000..534149aaf053 --- /dev/null +++ b/library/src/scala/collection/mutable/SortedMap.scala @@ -0,0 +1,104 @@ +/* + * 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 collection.mutable + +import scala.language.`2.13` +import scala.collection.{SortedMapFactory, SortedMapFactoryDefaults} + +/** + * Base type for mutable sorted map collections + */ +trait SortedMap[K, V] + extends collection.SortedMap[K, V] + with Map[K, V] + with SortedMapOps[K, V, SortedMap, SortedMap[K, V]] + with SortedMapFactoryDefaults[K, V, SortedMap, Iterable, Map] { + + override def unsorted: Map[K, V] = this + + override def sortedMapFactory: SortedMapFactory[SortedMap] = SortedMap + + /** The same sorted map with a given default function. + * Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc. + * are not affected by `withDefault`. + * + * Invoking transformer methods (e.g. `map`) will not preserve the default value. + * + * @param d the function mapping keys to values, used for non-present keys + * @return a wrapper of the map with a default value + */ + override def withDefault(d: K => V): SortedMap[K, V] = new SortedMap.WithDefault[K, V](this, d) + + /** The same map with a given default value. + * Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc. + * are not affected by `withDefaultValue`. + * + * Invoking transformer methods (e.g. `map`) will not preserve the default value. + * + * @param d default value used for non-present keys + * @return a wrapper of the map with a default value + */ + override def withDefaultValue(d: V): SortedMap[K, V] = new SortedMap.WithDefault[K, V](this, _ => d) +} + +transparent trait SortedMapOps[K, V, +CC[X, Y] <: Map[X, Y] with SortedMapOps[X, Y, CC, _], +C <: SortedMapOps[K, V, CC, C]] + extends collection.SortedMapOps[K, V, CC, C] + with MapOps[K, V, Map, C] { + + def unsorted: Map[K, V] + + @deprecated("Use m.clone().addOne((k,v)) instead of m.updated(k, v)", "2.13.0") + override def updated[V1 >: V](key: K, value: V1): CC[K, V1] = + clone().asInstanceOf[CC[K, V1]].addOne((key, value)) +} + +@SerialVersionUID(3L) +object SortedMap extends SortedMapFactory.Delegate[SortedMap](TreeMap) { + + @SerialVersionUID(3L) + final class WithDefault[K, V](underlying: SortedMap[K, V], defaultValue: K => V) + extends Map.WithDefault[K, V](underlying, defaultValue) + with SortedMap[K, V] + with SortedMapOps[K, V, SortedMap, WithDefault[K, V]] + with Serializable { + + override def sortedMapFactory: SortedMapFactory[SortedMap] = underlying.sortedMapFactory + + def iteratorFrom(start: K): scala.collection.Iterator[(K, V)] = underlying.iteratorFrom(start) + + def keysIteratorFrom(start: K): scala.collection.Iterator[K] = underlying.keysIteratorFrom(start) + + implicit def ordering: Ordering[K] = underlying.ordering + + def rangeImpl(from: Option[K], until: Option[K]): WithDefault[K, V] = + new WithDefault[K, V](underlying.rangeImpl(from, until), defaultValue) + + // Need to override following methods to match type signatures of `SortedMap.WithDefault` + // for operations preserving default value + override def subtractOne(elem: K): WithDefault.this.type = { underlying.subtractOne(elem); this } + + override def addOne(elem: (K, V)): WithDefault.this.type = { underlying.addOne(elem); this } + + override def empty: WithDefault[K, V] = new WithDefault[K, V](underlying.empty, defaultValue) + + override def concat[V2 >: V](suffix: collection.IterableOnce[(K, V2)]): SortedMap[K, V2] = + underlying.concat(suffix).withDefault(defaultValue) + + override protected def fromSpecific(coll: scala.collection.IterableOnce[(K, V)]): WithDefault[K, V] = + new WithDefault[K, V](sortedMapFactory.from(coll), defaultValue) + + override protected def newSpecificBuilder: Builder[(K, V), WithDefault[K, V]] = + SortedMap.newBuilder.mapResult((p: SortedMap[K, V]) => new WithDefault[K, V](p, defaultValue)) + } +} diff --git a/library/src/scala/collection/mutable/SortedSet.scala b/library/src/scala/collection/mutable/SortedSet.scala new file mode 100644 index 000000000000..dbb31ba5a18d --- /dev/null +++ b/library/src/scala/collection/mutable/SortedSet.scala @@ -0,0 +1,47 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` + +/** Base type for mutable sorted set collections + */ +trait SortedSet[A] + extends Set[A] + with collection.SortedSet[A] + with SortedSetOps[A, SortedSet, SortedSet[A]] + with SortedSetFactoryDefaults[A, SortedSet, Set] { + + override def unsorted: Set[A] = this + + override def sortedIterableFactory: SortedIterableFactory[SortedSet] = SortedSet +} + +/** + * @define coll mutable sorted set + * @define Coll `mutable.SortedSet` + */ +transparent trait SortedSetOps[A, +CC[X] <: SortedSet[X], +C <: SortedSetOps[A, CC, C]] + extends SetOps[A, Set, C] + with collection.SortedSetOps[A, CC, C] { + + def unsorted: Set[A] +} + +/** + * $factoryInfo + */ +@SerialVersionUID(3L) +object SortedSet extends SortedIterableFactory.Delegate[SortedSet](TreeSet) diff --git a/library/src/scala/collection/mutable/Stack.scala b/library/src/scala/collection/mutable/Stack.scala new file mode 100644 index 000000000000..f888475c7544 --- /dev/null +++ b/library/src/scala/collection/mutable/Stack.scala @@ -0,0 +1,143 @@ +/* + * 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.collection.mutable + +import scala.language.`2.13` +import scala.annotation.{migration, nowarn} +import scala.collection.generic.DefaultSerializable +import scala.collection.{IterableFactoryDefaults, IterableOnce, SeqFactory, StrictOptimizedSeqFactory, StrictOptimizedSeqOps} + +/** A stack implements a data structure which allows to store and retrieve + * objects in a last-in-first-out (LIFO) fashion. + * + * Note that operations which consume and produce iterables preserve order, + * rather than reversing it (as would be expected from building a new stack + * by pushing an element at a time). + * + * @tparam A type of the elements contained in this stack. + * + * @define Coll `Stack` + * @define coll stack + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +@migration("Stack is now based on an ArrayDeque instead of a linked list", "2.13.0") +class Stack[A] protected (array: Array[AnyRef], start: Int, end: Int) + extends ArrayDeque[A](array, start, end) + with IndexedSeqOps[A, Stack, Stack[A]] + with StrictOptimizedSeqOps[A, Stack, Stack[A]] + with IterableFactoryDefaults[A, Stack] + with ArrayDequeOps[A, Stack, Stack[A]] + with Cloneable[Stack[A]] + with DefaultSerializable { + + def this(initialSize: Int = ArrayDeque.DefaultInitialSize) = + this(ArrayDeque.alloc(initialSize), start = 0, end = 0) + + override def iterableFactory: SeqFactory[Stack] = Stack + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix = "Stack" + + /** + * Add elements to the top of this stack + * + * @param elem + * @return + */ + def push(elem: A): this.type = prepend(elem) + + /** Push two or more elements onto the stack. The last element + * of the sequence will be on top of the new stack. + * + * @param elems the element sequence. + * @return the stack with the new elements on top. + */ + def push(elem1: A, elem2: A, elems: A*): this.type = { + val k = elems.knownSize + ensureSize(length + (if(k >= 0) k + 2 else 3)) + prepend(elem1).prepend(elem2).pushAll(elems) + } + + /** Push all elements in the given iterable object onto the stack. The + * last element in the iterable object will be on top of the new stack. + * + * @param elems the iterable object. + * @return the stack with the new elements on top. + */ + def pushAll(elems: scala.collection.IterableOnce[A]): this.type = + prependAll(elems match { + case it: scala.collection.Seq[A] => it.view.reverse + case it => IndexedSeq.from(it).view.reverse + }) + + /** + * Removes the top element from this stack and return it + * + * @return + * @throws NoSuchElementException when stack is empty + */ + def pop(): A = removeHead() + + /** + * Pop all elements from this stack and return it + * + * @return The removed elements + */ + def popAll(): scala.collection.Seq[A] = removeAll() + + /** + * Returns and removes all elements from the top of this stack which satisfy the given predicate + * + * @param f the predicate used for choosing elements + * @return The removed elements + */ + def popWhile(f: A => Boolean): scala.collection.Seq[A] = removeHeadWhile(f) + + /** Returns the top element of the stack. This method will not remove + * the element from the stack. An error is signaled if there is no + * element on the stack. + * + * @throws NoSuchElementException if the stack is empty + * @return the top element + */ + @`inline` final def top: A = head + + override protected def klone(): Stack[A] = { + val bf = newSpecificBuilder + bf ++= this + bf.result() + } + + override protected def ofArray(array: Array[AnyRef], end: Int): Stack[A] = + new Stack(array, start = 0, end) + +} + +/** + * $factoryInfo + * @define coll stack + * @define Coll `Stack` + */ +@SerialVersionUID(3L) +object Stack extends StrictOptimizedSeqFactory[Stack] { + + def from[A](source: IterableOnce[A]): Stack[A] = empty ++= source + + def empty[A]: Stack[A] = new Stack + + def newBuilder[A]: Builder[A, Stack[A]] = new GrowableBuilder[A, Stack[A]](empty) + +} diff --git a/library/src/scala/collection/mutable/StringBuilder.scala b/library/src/scala/collection/mutable/StringBuilder.scala new file mode 100644 index 000000000000..dfd778b44b34 --- /dev/null +++ b/library/src/scala/collection/mutable/StringBuilder.scala @@ -0,0 +1,496 @@ +/* + * 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.collection.mutable + +import scala.language.`2.13` +import scala.collection.{IterableFactoryDefaults, IterableOnce} +import scala.collection.immutable.WrappedString + +import scala.Predef.{ // unimport char-related implicit conversions to avoid triggering them accidentally + genericArrayOps => _, + charArrayOps => _, + genericWrapArray => _, + wrapCharArray => _, + wrapString => _, + //_ +} + +/** A builder of `String` which is also a mutable sequence of characters. + * + * This class provides an API mostly compatible with `java.lang.StringBuilder`, + * except where there are conflicts with the Scala collections API, such as the `reverse` method: + * [[reverse]] produces a new `StringBuilder`, and [[reverseInPlace]] mutates this builder. + * + * Mutating operations return either `this.type`, i.e., the current builder, or `Unit`. + * + * Other methods extract data or information from the builder without mutating it. + * + * The distinction is also reflected in naming conventions used by collections, + * such as `append`, which mutates, and `appended`, which does not, or `reverse`, + * which does not mutate, and `reverseInPlace`, which does. + * + * The `String` result may be obtained using either `result()` or `toString`. + * + * $multipleResults + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html#stringbuilders "Scala's Collection Library overview"]] + * section on `StringBuilders` for more information. + * + * @define Coll `mutable.IndexedSeq` + * @define coll string builder + */ +@SerialVersionUID(3L) +final class StringBuilder(val underlying: java.lang.StringBuilder) extends AbstractSeq[Char] + with ReusableBuilder[Char, String] + with IndexedSeq[Char] + with IndexedSeqOps[Char, IndexedSeq, StringBuilder] + with IterableFactoryDefaults[Char, IndexedSeq] + with java.lang.CharSequence + with Serializable { + + def this() = this(new java.lang.StringBuilder) + + /** Constructs a string builder with no characters in it and an + * initial capacity specified by the `capacity` argument. + * + * @param capacity the initial capacity. + * @throws java.lang.NegativeArraySizeException if capacity < 0. + */ + def this(capacity: Int) = this(new java.lang.StringBuilder(capacity)) + + /** Constructs a string builder with initial characters + * equal to characters of `str`. + */ + def this(str: String) = this(new java.lang.StringBuilder(str)) + + /** Constructs a string builder initialized with string value `initValue` + * and with additional character capacity `initCapacity`. + */ + def this(initCapacity: Int, initValue: String) = + this(new java.lang.StringBuilder(initValue.length + initCapacity) append initValue) + + // Methods required to make this an IndexedSeq: + def apply(i: Int): Char = underlying.charAt(i) + + override protected def fromSpecific(coll: scala.collection.IterableOnce[Char]): StringBuilder = + new StringBuilder() appendAll coll + + override protected def newSpecificBuilder: Builder[Char, StringBuilder] = + new GrowableBuilder(new StringBuilder()) + + override def empty: StringBuilder = new StringBuilder() + + @inline def length: Int = underlying.length + + def length_=(n: Int): Unit = underlying.setLength(n) + + override def knownSize: Int = super[IndexedSeqOps].knownSize + + def addOne(x: Char): this.type = { underlying.append(x); this } + + def clear(): Unit = underlying.setLength(0) + + /** Overloaded version of `addAll` that takes a string */ + def addAll(s: String): this.type = { underlying.append(s); this } + + /** Alias for `addAll` */ + def ++= (s: String): this.type = addAll(s) + + def result() = underlying.toString + + override def toString: String = result() + + override def toArray[B >: Char](implicit ct: scala.reflect.ClassTag[B]) = + ct.runtimeClass match { + case java.lang.Character.TYPE => toCharArray.asInstanceOf[Array[B]] + case _ => super.toArray + } + + /** Returns the contents of this StringBuilder as an `Array[Char]`. + * + * @return An array with the characters from this builder. + */ + def toCharArray: Array[Char] = { + val len = underlying.length + val arr = new Array[Char](len) + underlying.getChars(0, len, arr, 0) + arr + } + + // append* methods delegate to the underlying java.lang.StringBuilder: + + def appendAll(xs: String): this.type = { + underlying append xs + this + } + + /** Appends the string representation of the given argument, + * which is converted to a String with `String.valueOf`. + * + * @param x an `Any` object. + * @return this StringBuilder. + */ + def append(x: Any): this.type = { + underlying append String.valueOf(x) + this + } + + /** Appends the given String to this sequence. + * + * @param s a String. + * @return this StringBuilder. + */ + def append(s: String): this.type = { + underlying append s + this + } + + /** Appends the given CharSequence to this sequence. + * + * @param cs a CharSequence. + * @return this StringBuilder. + */ + def append(cs: java.lang.CharSequence): this.type = { + underlying.append(cs match { + // Both cases call into append(), but java SB + // looks up type at runtime and has fast path for SB. + case s: StringBuilder => s.underlying + case _ => cs + }) + this + } + + /** Appends the specified string builder to this sequence. + * + * @param s + * @return + */ + def append(s: StringBuilder): this.type = { + underlying append s.underlying + this + } + + /** Appends all the Chars in the given IterableOnce[Char] to this sequence. + * + * @param xs the characters to be appended. + * @return this StringBuilder. + */ + def appendAll(xs: IterableOnce[Char]): this.type = { + xs match { + case x: WrappedString => underlying append x.unwrap + case x: ArraySeq.ofChar => underlying append x.array + case x: StringBuilder => underlying append x.underlying + case _ => + val ks = xs.knownSize + if (ks != 0) { + val b = underlying + if (ks > 0) b.ensureCapacity(b.length + ks) + val it = xs.iterator + while (it.hasNext) { b append it.next() } + } + } + this + } + + /** Appends all the Chars in the given Array[Char] to this sequence. + * + * @param xs the characters to be appended. + * @return a reference to this object. + */ + def appendAll(xs: Array[Char]): this.type = { + underlying append xs + this + } + + /** Appends a portion of the given Array[Char] to this sequence. + * + * @param xs the Array containing Chars to be appended. + * @param offset the index of the first Char to append. + * @param len the numbers of Chars to append. + * @return this StringBuilder. + */ + def appendAll(xs: Array[Char], offset: Int, len: Int): this.type = { + underlying.append(xs, offset, len) + this + } + + /** Append the String representation of the given primitive type + * to this sequence. The argument is converted to a String with + * String.valueOf. + * + * @param x a primitive value + * @return This StringBuilder. + */ + def append(x: Boolean): this.type = { underlying append x ; this } + def append(x: Byte): this.type = append(x.toInt) + def append(x: Short): this.type = append(x.toInt) + def append(x: Int): this.type = { underlying append x ; this } + def append(x: Long): this.type = { underlying append x ; this } + def append(x: Float): this.type = { underlying append x ; this } + def append(x: Double): this.type = { underlying append x ; this } + def append(x: Char): this.type = { underlying append x ; this } + + /** Remove a subsequence of Chars from this sequence, starting at the + * given start index (inclusive) and extending to the end index (exclusive) + * or to the end of the String, whichever comes first. + * + * @param start The beginning index, inclusive. + * @param end The ending index, exclusive. + * @return This StringBuilder. + * @throws StringIndexOutOfBoundsException if start < 0 || start > end + */ + def delete(start: Int, end: Int): this.type = { + underlying.delete(start, end) + this + } + + /** Replaces a subsequence of Chars with the given String. The semantics + * are as in delete, with the String argument then inserted at index 'start'. + * + * @param start The beginning index, inclusive. + * @param end The ending index, exclusive. + * @param str The String to be inserted at the start index. + * @return This StringBuilder. + * @throws StringIndexOutOfBoundsException if start < 0, start > length, or start > end + */ + def replace(start: Int, end: Int, str: String): this.type = { + underlying.replace(start, end, str) + this + } + + /** Inserts a subarray of the given Array[Char] at the given index + * of this sequence. + * + * @param index index at which to insert the subarray. + * @param str the Array from which Chars will be taken. + * @param offset the index of the first Char to insert. + * @param len the number of Chars from 'str' to insert. + * @return This StringBuilder. + * + * @throws StringIndexOutOfBoundsException if index < 0, index > length, + * offset < 0, len < 0, or (offset + len) > str.length. + */ + def insertAll(index: Int, str: Array[Char], offset: Int, len: Int): this.type = { + underlying.insert(index, str, offset, len) + this + } + + /** Inserts the String representation (via String.valueOf) of the given + * argument into this sequence at the given index. + * + * @param index the index at which to insert. + * @param x a value. + * @return this StringBuilder. + * @throws StringIndexOutOfBoundsException if the index is out of bounds. + */ + def insert(index: Int, x: Any): this.type = insert(index, String.valueOf(x)) + + /** Inserts the String into this character sequence. + * + * @param index the index at which to insert. + * @param x a String. + * @return this StringBuilder. + * @throws StringIndexOutOfBoundsException if the index is out of bounds. + */ + def insert(index: Int, x: String): this.type = { + underlying.insert(index, x) + this + } + + /** Inserts the given Seq[Char] into this sequence at the given index. + * + * @param index the index at which to insert. + * @param xs the Seq[Char]. + * @return this StringBuilder. + * @throws StringIndexOutOfBoundsException if the index is out of bounds. + */ + def insertAll(index: Int, xs: IterableOnce[Char]): this.type = + insertAll(index, (ArrayBuilder.make[Char] ++= xs).result()) + + /** Inserts the given Array[Char] into this sequence at the given index. + * + * @param index the index at which to insert. + * @param xs the Array[Char]. + * @return this StringBuilder. + * @throws StringIndexOutOfBoundsException if the index is out of bounds. + */ + def insertAll(index: Int, xs: Array[Char]): this.type = { + underlying.insert(index, xs) + this + } + + /** Calls String.valueOf on the given primitive value, and inserts the + * String at the given index. + * + * @param index the offset position. + * @param x a primitive value. + * @return this StringBuilder. + */ + def insert(index: Int, x: Boolean): this.type = insert(index, String.valueOf(x)) + def insert(index: Int, x: Byte): this.type = insert(index, x.toInt) + def insert(index: Int, x: Short): this.type = insert(index, x.toInt) + def insert(index: Int, x: Int): this.type = insert(index, String.valueOf(x)) + def insert(index: Int, x: Long): this.type = insert(index, String.valueOf(x)) + def insert(index: Int, x: Float): this.type = insert(index, String.valueOf(x)) + def insert(index: Int, x: Double): this.type = insert(index, String.valueOf(x)) + def insert(index: Int, x: Char): this.type = insert(index, String.valueOf(x)) + + /** Sets the length of the character sequence. If the current sequence + * is shorter than the given length, it is padded with nulls; if it is + * longer, it is truncated. + * + * @param len the new length + * @throws IndexOutOfBoundsException if the argument is negative. + */ + def setLength(len: Int): Unit = underlying.setLength(len) + + def update(idx: Int, elem: Char): Unit = underlying.setCharAt(idx, elem) + + + /** Like reverse, but destructively updates the target StringBuilder. + * + * @return the reversed StringBuilder (same as the target StringBuilder) + */ + @deprecated("Use reverseInPlace instead", "2.13.0") + final def reverseContents(): this.type = reverseInPlace() + + /** Like reverse, but destructively updates the target StringBuilder. + * + * @return the reversed StringBuilder (same as the target StringBuilder) + */ + def reverseInPlace(): this.type = { + underlying.reverse() + this + } + + + /** Returns the current capacity, which is the size of the underlying array. + * A new array will be allocated if the current capacity is exceeded. + * + * @return the capacity + */ + def capacity: Int = underlying.capacity + + /** Ensure that the capacity is at least the given argument. + * If the argument is greater than the current capacity, new + * storage will be allocated with size equal to the given + * argument or to `(2 * capacity + 2)`, whichever is larger. + * + * @param newCapacity the minimum desired capacity. + */ + def ensureCapacity(newCapacity: Int): Unit = { underlying.ensureCapacity(newCapacity) } + + /** Returns the Char at the specified index, counting from 0 as in Arrays. + * + * @param index the index to look up + * @return the Char at the given index. + * @throws IndexOutOfBoundsException if the index is out of bounds. + */ + def charAt(index: Int): Char = underlying.charAt(index) + + /** Removes the Char at the specified index. The sequence is + * shortened by one. + * + * @param index The index to remove. + * @return This StringBuilder. + * @throws IndexOutOfBoundsException if the index is out of bounds. + */ + def deleteCharAt(index: Int): this.type = { + underlying.deleteCharAt(index) + this + } + + /** Update the sequence at the given index to hold the specified Char. + * + * @param index the index to modify. + * @param ch the new Char. + * @throws IndexOutOfBoundsException if the index is out of bounds. + */ + def setCharAt(index: Int, ch: Char): this.type = { + underlying.setCharAt(index, ch) + this + } + + /** Returns a new String made up of a subsequence of this sequence, + * beginning at the given index and extending to the end of the sequence. + * + * target.substring(start) is equivalent to target.drop(start) + * + * @param start The starting index, inclusive. + * @return The new String. + * @throws IndexOutOfBoundsException if the index is out of bounds. + */ + def substring(start: Int): String = underlying.substring(start, length) + + /** Returns a new String made up of a subsequence of this sequence, + * beginning at the start index (inclusive) and extending to the + * end index (exclusive). + * + * target.substring(start, end) is equivalent to target.slice(start, end).mkString + * + * @param start The beginning index, inclusive. + * @param end The ending index, exclusive. + * @return The new String. + * @throws StringIndexOutOfBoundsException If either index is out of bounds, + * or if start > end. + */ + def substring(start: Int, end: Int): String = underlying.substring(start, end) + + /** For implementing CharSequence. + */ + def subSequence(start: Int, end: Int): java.lang.CharSequence = + underlying.substring(start, end) + + /** Finds the index of the first occurrence of the specified substring. + * + * @param str the target string to search for + * @return the first applicable index where target occurs, or -1 if not found. + */ + def indexOf(str: String): Int = underlying.indexOf(str) + + /** Finds the index of the first occurrence of the specified substring. + * + * @param str the target string to search for + * @param fromIndex the smallest index in the source string to consider + * @return the first applicable index where target occurs, or -1 if not found. + */ + def indexOf(str: String, fromIndex: Int): Int = underlying.indexOf(str, fromIndex) + + /** Finds the index of the last occurrence of the specified substring. + * + * @param str the target string to search for + * @return the last applicable index where target occurs, or -1 if not found. + */ + def lastIndexOf(str: String): Int = underlying.lastIndexOf(str) + + /** Finds the index of the last occurrence of the specified substring. + * + * @param str the target string to search for + * @param fromIndex the smallest index in the source string to consider + * @return the last applicable index where target occurs, or -1 if not found. + */ + def lastIndexOf(str: String, fromIndex: Int): Int = underlying.lastIndexOf(str, fromIndex) + + /** Tests whether this builder is empty. + * + * This method is required for JDK15+ compatibility + * + * @return `true` if this builder contains nothing, `false` otherwise. + */ + override def isEmpty: Boolean = underlying.length() == 0 +} + +object StringBuilder { + @deprecated("Use `new StringBuilder()` instead of `StringBuilder.newBuilder`", "2.13.0") + def newBuilder = new StringBuilder +} diff --git a/library/src/scala/collection/mutable/TreeMap.scala b/library/src/scala/collection/mutable/TreeMap.scala new file mode 100644 index 000000000000..85b8d897f22a --- /dev/null +++ b/library/src/scala/collection/mutable/TreeMap.scala @@ -0,0 +1,258 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` +import scala.collection.Stepper.EfficientSplit +import scala.collection.generic.DefaultSerializable +import scala.collection.mutable.{RedBlackTree => RB} + +/** + * A mutable sorted map implemented using a mutable red-black tree as underlying data structure. + * + * @param ordering the implicit ordering used to compare objects of type `A`. + * @tparam K the type of the keys contained in this tree map. + * @tparam V the type of the values associated with the keys. + * + * @define Coll mutable.TreeMap + * @define coll mutable tree map + */ +sealed class TreeMap[K, V] private (tree: RB.Tree[K, V])(implicit val ordering: Ordering[K]) + extends AbstractMap[K, V] + with SortedMap[K, V] + with SortedMapOps[K, V, TreeMap, TreeMap[K, V]] + with StrictOptimizedIterableOps[(K, V), Iterable, TreeMap[K, V]] + with StrictOptimizedMapOps[K, V, Map, TreeMap[K, V]] + with StrictOptimizedSortedMapOps[K, V, TreeMap, TreeMap[K, V]] + with SortedMapFactoryDefaults[K, V, TreeMap, Iterable, Map] + with DefaultSerializable { + + override def sortedMapFactory: TreeMap.type = TreeMap + + /** + * Creates an empty `TreeMap`. + * @param ord the implicit ordering used to compare objects of type `K`. + * @return an empty `TreeMap`. + */ + def this()(implicit ord: Ordering[K]) = this(RB.Tree.empty)(ord) + + def iterator: Iterator[(K, V)] = { + if (isEmpty) Iterator.empty + else RB.iterator(tree) + } + + override def keysIterator: Iterator[K] = { + if (isEmpty) Iterator.empty + else RB.keysIterator(tree, None) + } + + override def valuesIterator: Iterator[V] = { + if (isEmpty) Iterator.empty + else RB.valuesIterator(tree, None) + } + + def keysIteratorFrom(start: K): Iterator[K] = { + if (isEmpty) Iterator.empty + else RB.keysIterator(tree, Some(start)) + } + + def iteratorFrom(start: K): Iterator[(K, V)] = { + if (isEmpty) Iterator.empty + else RB.iterator(tree, Some(start)) + } + + override def valuesIteratorFrom(start: K): Iterator[V] = { + if (isEmpty) Iterator.empty + else RB.valuesIterator(tree, Some(start)) + } + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[(K, V), S]): S with EfficientSplit = + shape.parUnbox( + scala.collection.convert.impl.AnyBinaryTreeStepper.from[(K, V), RB.Node[K, V]]( + size, tree.root, _.left, _.right, x => (x.key, x.value) + ) + ) + + override def keyStepper[S <: Stepper[_]](implicit shape: StepperShape[K, S]): S with EfficientSplit = { + import scala.collection.convert.impl._ + type T = RB.Node[K, V] + val s = shape.shape match { + case StepperShape.IntShape => IntBinaryTreeStepper.from[T] (size, tree.root, _.left, _.right, _.key.asInstanceOf[Int]) + case StepperShape.LongShape => LongBinaryTreeStepper.from[T] (size, tree.root, _.left, _.right, _.key.asInstanceOf[Long]) + case StepperShape.DoubleShape => DoubleBinaryTreeStepper.from[T](size, tree.root, _.left, _.right, _.key.asInstanceOf[Double]) + case _ => shape.parUnbox(AnyBinaryTreeStepper.from[K, T](size, tree.root, _.left, _.right, _.key)) + } + s.asInstanceOf[S with EfficientSplit] + } + + override def valueStepper[S <: Stepper[_]](implicit shape: StepperShape[V, S]): S with EfficientSplit = { + import scala.collection.convert.impl._ + type T = RB.Node[K, V] + val s = shape.shape match { + case StepperShape.IntShape => IntBinaryTreeStepper.from[T] (size, tree.root, _.left, _.right, _.value.asInstanceOf[Int]) + case StepperShape.LongShape => LongBinaryTreeStepper.from[T] (size, tree.root, _.left, _.right, _.value.asInstanceOf[Long]) + case StepperShape.DoubleShape => DoubleBinaryTreeStepper.from[T] (size, tree.root, _.left, _.right, _.value.asInstanceOf[Double]) + case _ => shape.parUnbox(AnyBinaryTreeStepper.from[V, T] (size, tree.root, _.left, _.right, _.value)) + } + s.asInstanceOf[S with EfficientSplit] + } + + def addOne(elem: (K, V)): this.type = { RB.insert(tree, elem._1, elem._2); this } + + def subtractOne(elem: K): this.type = { RB.delete(tree, elem); this } + + override def clear(): Unit = RB.clear(tree) + + def get(key: K): Option[V] = RB.get(tree, key) + + /** + * Creates a ranged projection of this map. Any mutations in the ranged projection will update the original map and + * vice versa. + * + * Only entries with keys between this projection's key range will ever appear as elements of this map, independently + * of whether the entries are added through the original map or through this view. That means that if one inserts a + * key-value in a view whose key is outside the view's bounds, calls to `get` or `contains` will _not_ consider the + * newly added entry. Mutations are always reflected in the original map, though. + * + * @param from the lower bound (inclusive) of this projection wrapped in a `Some`, or `None` if there is no lower + * bound. + * @param until the upper bound (exclusive) of this projection wrapped in a `Some`, or `None` if there is no upper + * bound. + */ + def rangeImpl(from: Option[K], until: Option[K]): TreeMap[K, V] = new TreeMapProjection(from, until) + + override def foreach[U](f: ((K, V)) => U): Unit = RB.foreach(tree, f) + override def foreachEntry[U](f: (K, V) => U): Unit = RB.foreachEntry(tree, f) + + override def size: Int = RB.size(tree) + override def knownSize: Int = size + override def isEmpty: Boolean = RB.isEmpty(tree) + + override def contains(key: K): Boolean = RB.contains(tree, key) + + override def head: (K, V) = RB.min(tree).get + + override def last: (K, V) = RB.max(tree).get + + override def minAfter(key: K): Option[(K, V)] = RB.minAfter(tree, key) + + override def maxBefore(key: K): Option[(K, V)] = RB.maxBefore(tree, key) + + override protected[this] def className: String = "TreeMap" + + + /** + * A ranged projection of a [[TreeMap]]. Mutations on this map affect the original map and vice versa. + * + * Only entries with keys between this projection's key range will ever appear as elements of this map, independently + * of whether the entries are added through the original map or through this view. That means that if one inserts a + * key-value in a view whose key is outside the view's bounds, calls to `get` or `contains` will _not_ consider the + * newly added entry. Mutations are always reflected in the original map, though. + * + * @param from the lower bound (inclusive) of this projection wrapped in a `Some`, or `None` if there is no lower + * bound. + * @param until the upper bound (exclusive) of this projection wrapped in a `Some`, or `None` if there is no upper + * bound. + */ + private[this] final class TreeMapProjection(from: Option[K], until: Option[K]) extends TreeMap[K, V](tree) { + + /** + * Given a possible new lower bound, chooses and returns the most constraining one (the maximum). + */ + private[this] def pickLowerBound(newFrom: Option[K]): Option[K] = (from, newFrom) match { + case (Some(fr), Some(newFr)) => Some(ordering.max(fr, newFr)) + case (None, _) => newFrom + case _ => from + } + + /** + * Given a possible new upper bound, chooses and returns the most constraining one (the minimum). + */ + private[this] def pickUpperBound(newUntil: Option[K]): Option[K] = (until, newUntil) match { + case (Some(unt), Some(newUnt)) => Some(ordering.min(unt, newUnt)) + case (None, _) => newUntil + case _ => until + } + + /** + * Returns true if the argument is inside the view bounds (between `from` and `until`). + */ + private[this] def isInsideViewBounds(key: K): Boolean = { + val afterFrom = from.isEmpty || ordering.compare(from.get, key) <= 0 + val beforeUntil = until.isEmpty || ordering.compare(key, until.get) < 0 + afterFrom && beforeUntil + } + + override def rangeImpl(from: Option[K], until: Option[K]): TreeMap[K, V] = + new TreeMapProjection(pickLowerBound(from), pickUpperBound(until)) + + override def get(key: K) = if (isInsideViewBounds(key)) RB.get(tree, key) else None + + override def iterator = if (RB.size(tree) == 0) Iterator.empty else RB.iterator(tree, from, until) + override def keysIterator: Iterator[K] = if (RB.size(tree) == 0) Iterator.empty else RB.keysIterator(tree, from, until) + override def valuesIterator: Iterator[V] = if (RB.size(tree) == 0) Iterator.empty else RB.valuesIterator(tree, from, until) + override def keysIteratorFrom(start: K) = if (RB.size(tree) == 0) Iterator.empty else RB.keysIterator(tree, pickLowerBound(Some(start)), until) + override def iteratorFrom(start: K) = if (RB.size(tree) == 0) Iterator.empty else RB.iterator(tree, pickLowerBound(Some(start)), until) + override def valuesIteratorFrom(start: K) = if (RB.size(tree) == 0) Iterator.empty else RB.valuesIterator(tree, pickLowerBound(Some(start)), until) + override def size = if (RB.size(tree) == 0) 0 else iterator.length + override def knownSize: Int = if (RB.size(tree) == 0) 0 else -1 + override def isEmpty = RB.size(tree) == 0 || !iterator.hasNext + override def contains(key: K) = isInsideViewBounds(key) && RB.contains(tree, key) + + override def head = headOption.get + override def headOption = { + val entry = if (from.isDefined) RB.minAfter(tree, from.get) else RB.min(tree) + (entry, until) match { + case (Some(e), Some(unt)) if ordering.compare(e._1, unt) >= 0 => None + case _ => entry + } + } + + override def last = lastOption.get + override def lastOption = { + val entry = if (until.isDefined) RB.maxBefore(tree, until.get) else RB.max(tree) + (entry, from) match { + case (Some(e), Some(fr)) if ordering.compare(e._1, fr) < 0 => None + case _ => entry + } + } + + // Using the iterator should be efficient enough; if performance is deemed a problem later, specialized + // `foreach(f, from, until)` and `transform(f, from, until)` methods can be created in `RedBlackTree`. See + // https://github.com/scala/scala/pull/4608#discussion_r34307985 for a discussion about this. + override def foreach[U](f: ((K, V)) => U): Unit = iterator.foreach(f) + + override def clone() = super.clone().rangeImpl(from, until) + } + +} + +/** + * $factoryInfo + * + * @define Coll mutable.TreeMap + * @define coll mutable tree map + */ +@SerialVersionUID(3L) +object TreeMap extends SortedMapFactory[TreeMap] { + + def from[K : Ordering, V](it: IterableOnce[(K, V)]): TreeMap[K, V] = + Growable.from(empty[K, V], it) + + def empty[K : Ordering, V]: TreeMap[K, V] = new TreeMap[K, V]() + + def newBuilder[K: Ordering, V]: Builder[(K, V), TreeMap[K, V]] = new GrowableBuilder(empty[K, V]) + +} diff --git a/library/src/scala/collection/mutable/TreeSet.scala b/library/src/scala/collection/mutable/TreeSet.scala new file mode 100644 index 000000000000..4f302d46906e --- /dev/null +++ b/library/src/scala/collection/mutable/TreeSet.scala @@ -0,0 +1,219 @@ +/* + * 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 collection.mutable + +import scala.language.`2.13` +import scala.collection.Stepper.EfficientSplit +import scala.collection.generic.DefaultSerializable +import scala.collection.mutable.{RedBlackTree => RB} +import scala.collection.{SortedIterableFactory, SortedSetFactoryDefaults, Stepper, StepperShape, StrictOptimizedIterableOps, StrictOptimizedSortedSetOps, mutable} + +/** + * A mutable sorted set implemented using a mutable red-black tree as underlying data structure. + * + * @param ordering the implicit ordering used to compare objects of type `A`. + * @tparam A the type of the keys contained in this tree set. + * + * @define Coll mutable.TreeSet + * @define coll mutable tree set + */ +// Original API designed in part by Lucien Pereira +sealed class TreeSet[A] private (private val tree: RB.Tree[A, Null])(implicit val ordering: Ordering[A]) + extends AbstractSet[A] + with SortedSet[A] + with SortedSetOps[A, TreeSet, TreeSet[A]] + with StrictOptimizedIterableOps[A, Set, TreeSet[A]] + with StrictOptimizedSortedSetOps[A, TreeSet, TreeSet[A]] + with SortedSetFactoryDefaults[A, TreeSet, Set] + with DefaultSerializable { + + if (ordering eq null) + throw new NullPointerException("ordering must not be null") + + /** + * Creates an empty `TreeSet`. + * @param ord the implicit ordering used to compare objects of type `A`. + * @return an empty `TreeSet`. + */ + def this()(implicit ord: Ordering[A]) = this(RB.Tree.empty)(ord) + + override def sortedIterableFactory: SortedIterableFactory[TreeSet] = TreeSet + + def iterator: collection.Iterator[A] = RB.keysIterator(tree) + + def iteratorFrom(start: A): collection.Iterator[A] = RB.keysIterator(tree, Some(start)) + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit = { + import scala.collection.convert.impl._ + type T = RB.Node[A, Null] + val s = shape.shape match { + case StepperShape.IntShape => IntBinaryTreeStepper.from[T] (size, tree.root, _.left, _.right, _.key.asInstanceOf[Int]) + case StepperShape.LongShape => LongBinaryTreeStepper.from[T] (size, tree.root, _.left, _.right, _.key.asInstanceOf[Long]) + case StepperShape.DoubleShape => DoubleBinaryTreeStepper.from[T](size, tree.root, _.left, _.right, _.key.asInstanceOf[Double]) + case _ => shape.parUnbox(AnyBinaryTreeStepper.from[A, T](size, tree.root, _.left, _.right, _.key)) + } + s.asInstanceOf[S with EfficientSplit] + } + + def addOne(elem: A): this.type = { + RB.insert(tree, elem, null) + this + } + + def subtractOne(elem: A): this.type = { + RB.delete(tree, elem) + this + } + + def clear(): Unit = RB.clear(tree) + + def contains(elem: A): Boolean = RB.contains(tree, elem) + + def unconstrained: collection.Set[A] = this + + def rangeImpl(from: Option[A], until: Option[A]): TreeSet[A] = new TreeSetProjection(from, until) + + override protected[this] def className: String = "TreeSet" + + override def size: Int = RB.size(tree) + override def knownSize: Int = size + override def isEmpty: Boolean = RB.isEmpty(tree) + + override def head: A = RB.minKey(tree).get + + override def last: A = RB.maxKey(tree).get + + override def minAfter(key: A): Option[A] = RB.minKeyAfter(tree, key) + + override def maxBefore(key: A): Option[A] = RB.maxKeyBefore(tree, key) + + override def foreach[U](f: A => U): Unit = RB.foreachKey(tree, f) + + + /** + * A ranged projection of a [[TreeSet]]. Mutations on this set affect the original set and vice versa. + * + * Only keys between this projection's key range will ever appear as elements of this set, independently of whether + * the elements are added through the original set or through this view. That means that if one inserts an element in + * a view whose key is outside the view's bounds, calls to `contains` will _not_ consider the newly added element. + * Mutations are always reflected in the original set, though. + * + * @param from the lower bound (inclusive) of this projection wrapped in a `Some`, or `None` if there is no lower + * bound. + * @param until the upper bound (exclusive) of this projection wrapped in a `Some`, or `None` if there is no upper + * bound. + */ + private[this] final class TreeSetProjection(from: Option[A], until: Option[A]) extends TreeSet[A](tree) { + + /** + * Given a possible new lower bound, chooses and returns the most constraining one (the maximum). + */ + private[this] def pickLowerBound(newFrom: Option[A]): Option[A] = (from, newFrom) match { + case (Some(fr), Some(newFr)) => Some(ordering.max(fr, newFr)) + case (None, _) => newFrom + case _ => from + } + + /** + * Given a possible new upper bound, chooses and returns the most constraining one (the minimum). + */ + private[this] def pickUpperBound(newUntil: Option[A]): Option[A] = (until, newUntil) match { + case (Some(unt), Some(newUnt)) => Some(ordering.min(unt, newUnt)) + case (None, _) => newUntil + case _ => until + } + + /** + * Returns true if the argument is inside the view bounds (between `from` and `until`). + */ + private[this] def isInsideViewBounds(key: A): Boolean = { + val afterFrom = from.isEmpty || ordering.compare(from.get, key) <= 0 + val beforeUntil = until.isEmpty || ordering.compare(key, until.get) < 0 + afterFrom && beforeUntil + } + + override def rangeImpl(from: Option[A], until: Option[A]): TreeSet[A] = + new TreeSetProjection(pickLowerBound(from), pickUpperBound(until)) + + override def contains(key: A) = isInsideViewBounds(key) && RB.contains(tree, key) + + override def iterator = RB.keysIterator(tree, from, until) + override def iteratorFrom(start: A) = RB.keysIterator(tree, pickLowerBound(Some(start)), until) + + override def size = if (RB.size(tree) == 0) 0 else iterator.length + override def knownSize: Int = if (RB.size(tree) == 0) 0 else -1 + override def isEmpty: Boolean = RB.size(tree) == 0 || !iterator.hasNext + + override def head: A = headOption.get + override def headOption: Option[A] = { + val elem = if (from.isDefined) RB.minKeyAfter(tree, from.get) else RB.minKey(tree) + (elem, until) match { + case (Some(e), Some(unt)) if ordering.compare(e, unt) >= 0 => None + case _ => elem + } + } + + override def last: A = lastOption.get + override def lastOption = { + val elem = if (until.isDefined) RB.maxKeyBefore(tree, until.get) else RB.maxKey(tree) + (elem, from) match { + case (Some(e), Some(fr)) if ordering.compare(e, fr) < 0 => None + case _ => elem + } + } + + // Using the iterator should be efficient enough; if performance is deemed a problem later, a specialized + // `foreachKey(f, from, until)` method can be created in `RedBlackTree`. See + // https://github.com/scala/scala/pull/4608#discussion_r34307985 for a discussion about this. + override def foreach[U](f: A => U): Unit = iterator.foreach(f) + + override def clone(): mutable.TreeSet[A] = super.clone().rangeImpl(from, until) + + } + +} + +/** + * $factoryInfo + * @define Coll `mutable.TreeSet` + * @define coll mutable tree set + */ +@SerialVersionUID(3L) +object TreeSet extends SortedIterableFactory[TreeSet] { + + def empty[A : Ordering]: TreeSet[A] = new TreeSet[A]() + + def from[E](it: IterableOnce[E])(implicit ordering: Ordering[E]): TreeSet[E] = + it match { + case ts: TreeSet[E] if ordering == ts.ordering => + new TreeSet[E](ts.tree.treeCopy()) + case ss: scala.collection.SortedSet[E] if ordering == ss.ordering => + new TreeSet[E](RB.fromOrderedKeys(ss.iterator, ss.size)) + case r: Range if (ordering eq Ordering.Int) || (ordering eq Ordering.Int.reverse) => + val it = if((ordering eq Ordering.Int) == (r.step > 0)) r.iterator else r.reverseIterator + new TreeSet[E](RB.fromOrderedKeys(it.asInstanceOf[Iterator[E]], r.size)) + case _ => + val t: RB.Tree[E, Null] = RB.Tree.empty + val i = it.iterator + while (i.hasNext) RB.insert(t, i.next(), null) + new TreeSet[E](t) + } + + def newBuilder[A](implicit ordering: Ordering[A]): Builder[A, TreeSet[A]] = new ReusableBuilder[A, TreeSet[A]] { + private[this] var tree: RB.Tree[A, Null] = RB.Tree.empty + def addOne(elem: A): this.type = { RB.insert(tree, elem, null); this } + def result(): TreeSet[A] = new TreeSet[A](tree) + def clear(): Unit = { tree = RB.Tree.empty } + } +} diff --git a/library/src/scala/collection/mutable/UnrolledBuffer.scala b/library/src/scala/collection/mutable/UnrolledBuffer.scala new file mode 100644 index 000000000000..4e60b3555a07 --- /dev/null +++ b/library/src/scala/collection/mutable/UnrolledBuffer.scala @@ -0,0 +1,448 @@ +/* + * 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.collection +package mutable + +import scala.language.`2.13` +import scala.annotation.tailrec +import scala.collection.generic.{CommonErrors, DefaultSerializable} +import scala.reflect.ClassTag +import scala.collection.immutable.Nil + +/** A buffer that stores elements in an unrolled linked list. + * + * Unrolled linked lists store elements in linked fixed size + * arrays. + * + * Unrolled buffers retain locality and low memory overhead + * properties of array buffers, but offer much more efficient + * element addition, since they never reallocate and copy the + * internal array. + * + * However, they provide `O(n/m)` complexity random access, + * where `n` is the number of elements, and `m` the size of + * internal array chunks. + * + * Ideal to use when: + * - elements are added to the buffer and then all of the + * elements are traversed sequentially + * - two unrolled buffers need to be concatenated (see `concat`) + * + * Better than singly linked lists for random access, but + * should still be avoided for such a purpose. + * + * @define coll unrolled buffer + * @define Coll `UnrolledBuffer` + * + */ +@SerialVersionUID(3L) +sealed class UnrolledBuffer[T](implicit val tag: ClassTag[T]) + extends AbstractBuffer[T] + with Buffer[T] + with Seq[T] + with SeqOps[T, UnrolledBuffer, UnrolledBuffer[T]] + with StrictOptimizedSeqOps[T, UnrolledBuffer, UnrolledBuffer[T]] + with EvidenceIterableFactoryDefaults[T, UnrolledBuffer, ClassTag] + with Builder[T, UnrolledBuffer[T]] + with DefaultSerializable { + + import UnrolledBuffer.Unrolled + + @transient private var headptr = newUnrolled + @transient private var lastptr = headptr + @transient private var sz = 0 + + private[collection] def headPtr = headptr + private[collection] def headPtr_=(head: Unrolled[T]) = headptr = head + private[collection] def lastPtr = lastptr + private[collection] def lastPtr_=(last: Unrolled[T]) = lastptr = last + private[collection] def size_=(s: Int) = sz = s + + protected def evidenceIterableFactory: UnrolledBuffer.type = UnrolledBuffer + protected def iterableEvidence: ClassTag[T] = tag + + override def iterableFactory: SeqFactory[UnrolledBuffer] = UnrolledBuffer.untagged + + protected def newUnrolled = new Unrolled[T](this) + + // The below would allow more flexible behavior without requiring inheritance + // that is risky because all the important internals are private. + // private var myLengthPolicy: Int => Int = x => x + // + // /** Specifies how the array lengths should vary. + // * + // * By default, `UnrolledBuffer` uses arrays of a fixed size. A length + // * policy can be given that changes this scheme to, for instance, an + // * exponential growth. + // * + // * @param nextLength computes the length of the next array from the length of the latest one + // */ + // def setLengthPolicy(nextLength: Int => Int): Unit = { myLengthPolicy = nextLength } + private[collection] def calcNextLength(sz: Int) = sz // myLengthPolicy(sz) + + def classTagCompanion: UnrolledBuffer.type = UnrolledBuffer + + /** Concatenates the target unrolled buffer to this unrolled buffer. + * + * The specified buffer `that` is cleared after this operation. This is + * an O(1) operation. + * + * @param that the unrolled buffer whose elements are added to this buffer + */ + def concat(that: UnrolledBuffer[T]) = { + // bind the two together + if (!lastptr.bind(that.headptr)) lastptr = that.lastPtr + + // update size + sz += that.sz + + // `that` is no longer usable, so clear it + // here we rely on the fact that `clear` allocates + // new nodes instead of modifying the previous ones + that.clear() + + // return a reference to this + this + } + + def addOne(elem: T) = { + lastptr = lastptr.append(elem) + sz += 1 + this + } + + def clear(): Unit = { + headptr = newUnrolled + lastptr = headptr + sz = 0 + } + + def iterator: Iterator[T] = new AbstractIterator[T] { + var pos: Int = -1 + var node: Unrolled[T] = headptr + scan() + + private def scan(): Unit = { + pos += 1 + while (pos >= node.size) { + pos = 0 + node = node.next + if (node eq null) return + } + } + def hasNext = node ne null + def next() = if (hasNext) { + val r = node.array(pos) + scan() + r + } else Iterator.empty.next() + } + + // this should be faster than the iterator + override def foreach[U](f: T => U) = headptr.foreach(f) + + def result() = this + + def length = sz + + override def knownSize: Int = sz + + def apply(idx: Int) = + if (idx >= 0 && idx < sz) headptr(idx) + else throw CommonErrors.indexOutOfBounds(index = idx, max = sz - 1) + + def update(idx: Int, newelem: T) = + if (idx >= 0 && idx < sz) headptr(idx) = newelem + else throw CommonErrors.indexOutOfBounds(index = idx, max = sz - 1) + + /** Replace the contents of this $coll with the mapped result. + * + * @param f the mapping function + * @return this $coll + */ + def mapInPlace(f: T => T): this.type = { + headptr.mapInPlace(f) + this + } + + def remove(idx: Int) = + if (idx >= 0 && idx < sz) { + sz -= 1 + headptr.remove(idx, this) + } else throw CommonErrors.indexOutOfBounds(index = idx, max = sz - 1) + + @tailrec final def remove(idx: Int, count: Int): Unit = + if (count > 0) { + remove(idx) + remove(idx, count-1) + } + + def prepend(elem: T) = { + headptr = headptr prepend elem + sz += 1 + this + } + + def insert(idx: Int, elem: T): Unit = + insertAll(idx, elem :: Nil) + + def insertAll(idx: Int, elems: IterableOnce[T]): Unit = + if (idx >= 0 && idx <= sz) { + sz += headptr.insertAll(idx, elems, this) + } else throw CommonErrors.indexOutOfBounds(index = idx, max = sz - 1) + + override def subtractOne(elem: T): this.type = { + if (headptr.subtractOne(elem, this)) { + sz -= 1 + } + this + } + + def patchInPlace(from: Int, patch: collection.IterableOnce[T], replaced: Int): this.type = { + remove(from, replaced) + insertAll(from, patch) + this + } + + private def writeObject(out: java.io.ObjectOutputStream): Unit = { + out.defaultWriteObject + out writeInt sz + for (elem <- this) out writeObject elem + } + + private def readObject(in: java.io.ObjectInputStream): Unit = { + in.defaultReadObject + + val num = in.readInt + + headPtr = newUnrolled + lastPtr = headPtr + sz = 0 + var i = 0 + while (i < num) { + this += in.readObject.asInstanceOf[T] + i += 1 + } + } + + override def clone(): UnrolledBuffer[T] = new UnrolledBuffer[T] ++= this + + override protected[this] def className = "UnrolledBuffer" +} + + +@SerialVersionUID(3L) +object UnrolledBuffer extends StrictOptimizedClassTagSeqFactory[UnrolledBuffer] { self => + + val untagged: SeqFactory[UnrolledBuffer] = new ClassTagSeqFactory.AnySeqDelegate(self) + + def empty[A : ClassTag]: UnrolledBuffer[A] = new UnrolledBuffer[A] + + def from[A : ClassTag](source: scala.collection.IterableOnce[A]): UnrolledBuffer[A] = newBuilder[A].addAll(source) + + def newBuilder[A : ClassTag]: UnrolledBuffer[A] = new UnrolledBuffer[A] + + final val waterline: Int = 50 + + final def waterlineDenom: Int = 100 + + @deprecated("Use waterlineDenom instead.", "2.13.0") + final val waterlineDelim: Int = waterlineDenom + + private[collection] val unrolledlength = 32 + + /** 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) { + 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) { + array(size) = elem + size += 1 + this + } else { + next = new Unrolled[T](0, new Array[T](nextlength), null, buff) + next append elem + } + def foreach[U](f: T => U): Unit = { + var unrolled = this + var i = 0 + while (unrolled ne null) { + val chunkarr = unrolled.array + val chunksz = unrolled.size + while (i < chunksz) { + val elem = chunkarr(i) + f(elem) + i += 1 + } + i = 0 + unrolled = unrolled.next + } + } + def mapInPlace(f: T => T): Unit = { + var unrolled = this + var i = 0 + while (unrolled ne null) { + val chunkarr = unrolled.array + val chunksz = unrolled.size + while (i < chunksz) { + val elem = chunkarr(i) + chunkarr(i) = f(elem) + i += 1 + } + i = 0 + unrolled = unrolled.next + } + } + @tailrec final def apply(idx: Int): T = + 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] = + if (idx < size) this else next.locate(idx - size) + def prepend(elem: T) = if (size < array.length) { + // shift the elements of the array right + // then insert the element + shiftright() + array(0) = elem + size += 1 + this + } 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 + } + // shifts right assuming enough space + private def shiftright(): Unit = { + var i = size - 1 + while (i >= 0) { + array(i + 1) = array(i) + i -= 1 + } + } + // returns pointer to new last if changed + @tailrec final def remove(idx: Int, buffer: UnrolledBuffer[T]): T = + if (idx < size) { + // remove the element + // then try to merge with the next bucket + val r = array(idx) + shiftleft(idx) + size -= 1 + if (tryMergeWithNext()) buffer.lastPtr = this + r + } else next.remove(idx - size, buffer) + + @tailrec final def subtractOne(elem: T, buffer: UnrolledBuffer[T]): Boolean = { + var i = 0 + while (i < size) { + if(array(i) == elem) { + remove(i, buffer) + return true + } + i += 1 + } + if(next ne null) next.subtractOne(elem, buffer) else false + } + + // shifts left elements after `leftb` (overwrites `leftb`) + private def shiftleft(leftb: Int): Unit = { + var i = leftb + while (i < (size - 1)) { + array(i) = array(i + 1) + i += 1 + } + nullout(i, i + 1) + } + protected def tryMergeWithNext() = if (next != null && (size + next.size) < (array.length * waterline / waterlineDenom)) { + // copy the next array, then discard the next node + Array.copy(next.array, 0, array, size, next.size) + size = size + next.size + next = next.next + if (next eq null) true else false // checks if last node was thrown out + } else false + + @tailrec final def insertAll(idx: Int, t: scala.collection.IterableOnce[T], buffer: UnrolledBuffer[T]): Int = { + if (idx < size) { + // divide this node at the appropriate position and insert all into head + // update new next + val newnextnode = new Unrolled[T](0, new Array(array.length), null, buff) + Array.copy(array, idx, newnextnode.array, 0, size - idx) + newnextnode.size = size - idx + newnextnode.next = next + + // update this + nullout(idx, size) + size = idx + next = null + + // insert everything from iterable to this + var curr = this + var appended = 0 + for (elem <- t.iterator) { + curr = curr append elem + appended += 1 + } + 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 + else if (newnextnode.next eq null) buffer.lastPtr = newnextnode + appended + } + else if (idx == size || (next eq null)) { + var curr = this + var appended = 0 + for (elem <- t.iterator) { + curr = curr append elem + appended += 1 + } + appended + } + else next.insertAll(idx - size, t, buffer) + } + + private def nullout(from: Int, until: Int): Unit = { + var idx = from + while (idx < until) { + array(idx) = null.asInstanceOf[T] // TODO find a way to assign a default here!! + idx += 1 + } + } + + // assumes this is the last node + // `thathead` and `thatlast` are head and last node + // of the other unrolled list, respectively + def bind(thathead: Unrolled[T]) = { + assert(next eq null) + next = thathead + tryMergeWithNext() + } + + override def toString: String = + array.take(size).mkString("Unrolled@%08x".format(System.identityHashCode(this)) + "[" + size + "/" + array.length + "](", ", ", ")") + " -> " + (if (next ne null) next.toString else "") + } +} + +// This is used by scala.collection.parallel.mutable.UnrolledParArrayCombiner: +// Todo -- revisit whether inheritance is the best way to achieve this functionality +private[collection] class DoublingUnrolledBuffer[T](implicit t: ClassTag[T]) extends UnrolledBuffer[T]()(t) { + override def calcNextLength(sz: Int) = if (sz < 10000) sz * 2 else sz + override protected def newUnrolled = new UnrolledBuffer.Unrolled[T](0, new Array[T](4), null, this) +} diff --git a/library/src/scala/collection/mutable/WeakHashMap.scala b/library/src/scala/collection/mutable/WeakHashMap.scala new file mode 100644 index 000000000000..e0f592faf9f1 --- /dev/null +++ b/library/src/scala/collection/mutable/WeakHashMap.scala @@ -0,0 +1,56 @@ +/* + * 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 collection +package mutable + +import scala.language.`2.13` +import scala.annotation.nowarn +import scala.collection.convert.JavaCollectionWrappers.{JMapWrapper, JMapWrapperLike} + +/** A hash map with references to entries which are weakly reachable. Entries are + * removed from this map when the key is no longer (strongly) referenced. This class wraps + * `java.util.WeakHashMap`. + * + * @tparam K type of keys contained in this map + * @tparam V type of values associated with the keys + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html#weak-hash-maps "Scala's Collection Library overview"]] + * section on `Weak Hash Maps` for more information. + * + * @define Coll `WeakHashMap` + * @define coll weak hash map + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +@SerialVersionUID(3L) +class WeakHashMap[K, V] extends JMapWrapper[K, V](new java.util.WeakHashMap) + with JMapWrapperLike[K, V, WeakHashMap, WeakHashMap[K, V]] + with MapFactoryDefaults[K, V, WeakHashMap, Iterable] { + override def empty = new WeakHashMap[K, V] + override def mapFactory: MapFactory[WeakHashMap] = WeakHashMap + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix = "WeakHashMap" +} + +/** $factoryInfo + * @define Coll `WeakHashMap` + * @define coll weak hash map + */ +@SerialVersionUID(3L) +object WeakHashMap extends MapFactory[WeakHashMap] { + def empty[K, V]: WeakHashMap[K,V] = new WeakHashMap[K, V] + def from[K, V](it: collection.IterableOnce[(K, V)]): WeakHashMap[K,V] = Growable.from(empty[K, V], it) + def newBuilder[K, V]: Builder[(K, V), WeakHashMap[K,V]] = new GrowableBuilder(WeakHashMap.empty[K, V]) +} + diff --git a/library/src/scala/collection/mutable/package.scala b/library/src/scala/collection/mutable/package.scala new file mode 100644 index 000000000000..475aec0afea7 --- /dev/null +++ b/library/src/scala/collection/mutable/package.scala @@ -0,0 +1,42 @@ +/* + * 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.collection + +import scala.language.`2.13` + +package object mutable { + @deprecated("Use ArraySeq instead of WrappedArray; it can represent both, boxed and unboxed arrays", "2.13.0") + type WrappedArray[X] = ArraySeq[X] + @deprecated("Use ArraySeq instead of WrappedArray; it can represent both, boxed and unboxed arrays", "2.13.0") + val WrappedArray = ArraySeq + @deprecated("Use Iterable instead of Traversable", "2.13.0") + type Traversable[X] = Iterable[X] + @deprecated("Use Iterable instead of Traversable", "2.13.0") + val Traversable = Iterable + @deprecated("Use Stack instead of ArrayStack; it now uses an array-based implementation", "2.13.0") + type ArrayStack[X] = Stack[X] + @deprecated("Use Stack instead of ArrayStack; it now uses an array-based implementation", "2.13.0") + val ArrayStack = Stack + + @deprecated("mutable.LinearSeq has been removed; use LinearSeq with mutable.Seq instead", "2.13.0") + type LinearSeq[X] = Seq[X] with scala.collection.LinearSeq[X] + + @deprecated("GrowingBuilder has been renamed to GrowableBuilder", "2.13.0") + type GrowingBuilder[Elem, To <: Growable[Elem]] = GrowableBuilder[Elem, To] + + @deprecated("IndexedOptimizedSeq has been renamed to IndexedSeq", "2.13.0") + type IndexedOptimizedSeq[A] = IndexedSeq[A] + + @deprecated("IndexedOptimizedBuffer has been renamed to IndexedBuffer", "2.13.0") + type IndexedOptimizedBuffer[A] = IndexedBuffer[A] +} diff --git a/library/src/scala/collection/package.scala b/library/src/scala/collection/package.scala new file mode 100644 index 000000000000..aced3cb25e03 --- /dev/null +++ b/library/src/scala/collection/package.scala @@ -0,0 +1,82 @@ +/* + * 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.language.`2.13` + +package object collection { + @deprecated("Use Iterable instead of Traversable", "2.13.0") + type Traversable[+X] = Iterable[X] + @deprecated("Use Iterable instead of Traversable", "2.13.0") + val Traversable = Iterable + @deprecated("Use IterableOnce instead of TraversableOnce", "2.13.0") + type TraversableOnce[+X] = IterableOnce[X] + @deprecated("Use IterableOnce instead of TraversableOnce", "2.13.0") + val TraversableOnce = IterableOnce + @deprecated("Use SeqOps instead of SeqLike", "2.13.0") + type SeqLike[A, T] = SeqOps[A, Seq, T] + @deprecated("Use SeqOps (for the methods) or IndexedSeqOps (for fast indexed access) instead of ArrayLike", "2.13.0") + type ArrayLike[A] = SeqOps[A, Seq, Seq[A]] + + @deprecated("Gen* collection types have been removed", "2.13.0") + type GenTraversableOnce[+X] = IterableOnce[X] + @deprecated("Gen* collection types have been removed", "2.13.0") + val GenTraversableOnce = IterableOnce + @deprecated("Gen* collection types have been removed", "2.13.0") + type GenTraversable[+X] = Iterable[X] + @deprecated("Gen* collection types have been removed", "2.13.0") + val GenTraversable = Iterable + @deprecated("Gen* collection types have been removed", "2.13.0") + type GenIterable[+X] = Iterable[X] + @deprecated("Gen* collection types have been removed", "2.13.0") + val GenIterable = Iterable + @deprecated("Gen* collection types have been removed", "2.13.0") + type GenSeq[+X] = Seq[X] + @deprecated("Gen* collection types have been removed", "2.13.0") + val GenSeq = Seq + @deprecated("Gen* collection types have been removed", "2.13.0") + type GenSet[X] = Set[X] + @deprecated("Gen* collection types have been removed", "2.13.0") + val GenSet = Set + @deprecated("Gen* collection types have been removed", "2.13.0") + type GenMap[K, +V] = Map[K, V] + @deprecated("Gen* collection types have been removed", "2.13.0") + val GenMap = Map + + /** Needed to circumvent a difficulty between dotty and scalac concerning + * the right top type for a type parameter of kind * -> *. + * In Scalac, we can provide `Any`, as `Any` is kind-polymorphic. In dotty this is not allowed. + * In dotty, we can provide `[X] => Any`. But Scalac does not know lambda syntax. + */ + private[scala] type AnyConstr[X] = Any + + /** An extractor used to head/tail deconstruct sequences. */ + object +: { + /** Splits a sequence into head +: tail. + * @return Some((head, tail)) if sequence is non-empty. None otherwise. + */ + def unapply[A, CC[_] <: Seq[_], C <: SeqOps[A, CC, C]](t: C with SeqOps[A, CC, C]): Option[(A, C)] = + if(t.isEmpty) None + else Some(t.head -> t.tail) + } + + /** An extractor used to init/last deconstruct sequences. */ + object :+ { + /** Splits a sequence into init :+ last. + * @return Some((init, last)) if sequence is non-empty. None otherwise. + */ + def unapply[A, CC[_] <: Seq[_], C <: SeqOps[A, CC, C]](t: C with SeqOps[A, CC, C]): Option[(C, A)] = + if(t.isEmpty) None + else Some(t.init -> t.last) + } +} diff --git a/library/src/scala/compat/Platform.scala b/library/src/scala/compat/Platform.scala new file mode 100644 index 000000000000..b6140e34afba --- /dev/null +++ b/library/src/scala/compat/Platform.scala @@ -0,0 +1,148 @@ +/* + * 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 compat + +import scala.language.`2.13` + +@deprecated("Will be removed in the future.", since = "2.13.0") +object Platform { + + /** Thrown when a stack overflow occurs because a method or function recurses too deeply. + * + * On the JVM, this is a type alias for `java.lang.StackOverflowError`, which itself extends `java.lang.Error`. + * The same rules apply to catching a `java.lang.Error` as for Java, that it indicates a serious problem that a reasonable application should not try and catch. + */ + @deprecated("Use `java.lang.StackOverflowError` instead.", since = "2.13.0") + type StackOverflowError = java.lang.StackOverflowError + + /** This is a type alias for `java.util.ConcurrentModificationException`, + * which may be thrown by methods that detect an invalid modification of an object. + * For example, many common collection types do not allow modifying a collection + * while it is being iterated over. + */ + @deprecated("Use `java.util.ConcurrentModificationException` instead.", since = "2.13.0") + type ConcurrentModificationException = java.util.ConcurrentModificationException + + /** Copies `length` elements of array `src` starting at position `srcPos` to the + * array `dest` starting at position `destPos`. If `src`==`dest`, the copying will + * behave as if the elements copied from `src` were first copied to a temporary + * array before being copied back into the array at the destination positions. + * + * @param src A non-null array as source for the copy. + * @param srcPos The starting index in the source array. + * @param dest A non-null array as destination for the copy. + * @param destPos The starting index in the destination array. + * @param length The number of elements to be copied. + * @throws java.lang.NullPointerException If either `src` or `dest` are `null`. + * @throws java.lang.ArrayStoreException If either `src` or `dest` are not of type + * [java.lang.Array]; or if the element type of `src` is not + * compatible with that of `dest`. + * @throws java.lang.IndexOutOfBoundsException If either `srcPos` or `destPos` are + * outside of the bounds of their respective arrays; or if `length` + * is negative; or if there are less than `length` elements available + * after `srcPos` or `destPos` in `src` and `dest` respectively. + */ + @inline + @deprecated("Use `java.lang.System#arraycopy` instead.", since = "2.13.0") + def arraycopy(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int): Unit = { + System.arraycopy(src, srcPos, dest, destPos, length) + } + + /** Creates a new array of the specified type and given length. + * + * Note that if `elemClass` is a subclass of [[scala.AnyVal]] then the returned value is an Array of the corresponding java primitive type. + * For example, the following code `scala.compat.Platform.createArray(classOf[Int], 4)` returns an array of the java primitive type `int`. + * + * For a [[scala.AnyVal]] array, the values of the array are set to 0 for ''numeric value types'' ([[scala.Double]], [[scala.Float]], [[scala.Long]], [[scala.Int]], [[scala.Char]], + * [[scala.Short]], and [[scala.Byte]]), and `false` for [[scala.Boolean]]. Creation of an array of type [[scala.Unit]] is not possible. + * + * For subclasses of [[scala.AnyRef]], the values of the array are set to `null`. + * + * The caller must cast the returned value to the correct type. + * + * @example {{{ + * val a = scala.compat.Platform.createArray(classOf[Int], 4).asInstanceOf[Array[Int]] // returns Array[Int](0, 0, 0, 0) + * }}} + * + * @param elemClass the `Class` object of the component type of the array + * @param length the length of the new array. + * @return an array of the given component type as an `AnyRef`. + * @throws java.lang.NullPointerException If `elemClass` is `null`. + * @throws java.lang.IllegalArgumentException if componentType is [[scala.Unit]] or `java.lang.Void.TYPE` + * @throws java.lang.NegativeArraySizeException if the specified length is negative + */ + @inline + @deprecated("Use `java.lang.reflect.Array#newInstance` instead.", since = "2.13.0") + def createArray(elemClass: Class[_], length: Int): AnyRef = + java.lang.reflect.Array.newInstance(elemClass, length) + + /** Assigns the value of 0 to each element in the array. + * @param arr A non-null Array[Int]. + * @throws java.lang.NullPointerException If `arr` is `null`. + */ + @inline + @deprecated("Use `java.util.Arrays#fill` instead.", since = "2.13.0") + def arrayclear(arr: Array[Int]): Unit = { java.util.Arrays.fill(arr, 0) } + + /** Returns the `Class` object associated with the class or interface with the given string name using the current `ClassLoader`. + * On the JVM, invoking this method is equivalent to: `java.lang.Class.forName(name)` + * + * For more information, please see the Java documentation for [[java.lang.Class]]. + * + * @param name the fully qualified name of the desired class. + * @return the `Class` object for the class with the specified name. + * @throws java.lang.LinkageError if the linkage fails + * @throws java.lang.ExceptionInInitializerError if the initialization provoked by this method fails + * @throws java.lang.ClassNotFoundException if the class cannot be located + * @example {{{ + * val a = scala.compat.Platform.getClassForName("java.lang.Integer") // returns the Class[_] for java.lang.Integer + * }}} + */ + @inline + @deprecated("Use `java.lang.Class#forName` instead.", since = "2.13.0") + def getClassForName(name: String): Class[_] = java.lang.Class.forName(name) + + /** The default line separator. + * + * On the JVM, this is equivalent to calling the method: + * `java.lang.System.lineSeparator` + */ + @deprecated("Use `java.lang.System#lineSeparator` instead.", since = "2.13.0") + val EOL: String = System.lineSeparator + + /** The current time in milliseconds. The time is counted since 1 January 1970 + * UTC. + * + * Note that the operating system timer used to obtain this value may be less + * precise than a millisecond. + */ + @inline + @deprecated("Use `java.lang.System#currentTimeMillis` instead.", since = "2.13.0") + def currentTime: Long = System.currentTimeMillis() + + /** Runs the garbage collector. + * + * This is a request that the underlying JVM runs the garbage collector. + * The results of this call depends heavily on the JVM used. + * The underlying JVM is free to ignore this request. + */ + @inline + @deprecated("Use `java.lang.System#gc` instead.", since = "2.13.0") + def collectGarbage(): Unit = System.gc() + + /** The name of the default character set encoding as a string */ + @inline + @deprecated("Use `java.nio.charset.Charset.defaultCharset#name` instead.", since = "2.13.0") + def defaultCharsetName: String = java.nio.charset.Charset.defaultCharset.name +} diff --git a/library/src/scala/compiletime/Erased.scala b/library/src/scala/compiletime/Erased.scala new file mode 100644 index 000000000000..665639322122 --- /dev/null +++ b/library/src/scala/compiletime/Erased.scala @@ -0,0 +1,7 @@ +package scala.compiletime +import annotation.experimental + +/** A marker trait for erased values. vals or parameters whose type extends + * `Erased` get an implicit `erased` modifier. + */ +@experimental trait Erased \ No newline at end of file diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index 8215ae2452a3..1a161ebd4a03 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -23,7 +23,6 @@ import annotation.{compileTimeOnly, experimental} * the branches. * @syntax markdown */ -// TODO add `erased` once it is not an experimental feature anymore def erasedValue[T]: T = erasedValue[T] /** Used as the initializer of a mutable class or object field, like this: diff --git a/library/src/scala/concurrent/Awaitable.scala b/library/src/scala/concurrent/Awaitable.scala new file mode 100644 index 000000000000..ded8bd307987 --- /dev/null +++ b/library/src/scala/concurrent/Awaitable.scala @@ -0,0 +1,69 @@ +/* + * 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.concurrent + + +import scala.language.`2.13` +import scala.concurrent.duration.Duration + + + +/** + * An object that may eventually be completed with a result value of type `T` which may be + * awaited using blocking methods. + * + * The [[Await]] object provides methods that allow accessing the result of an `Awaitable` + * by blocking the current thread until the `Awaitable` has been completed or a timeout has + * occurred. + */ +trait Awaitable[+T] { + + /** + * Await the "completed" state of this `Awaitable`. + * + * '''''This method should not be called directly; use [[Await.ready]] instead.''''' + * + * @param atMost + * maximum wait time, which may be negative (no waiting is done), + * [[scala.concurrent.duration.Duration.Inf Duration.Inf]] for unbounded waiting, or a finite positive + * duration + * @return this `Awaitable` + * @throws InterruptedException if the current thread is interrupted while waiting + * @throws TimeoutException if after waiting for the specified time this `Awaitable` is still not ready + * @throws IllegalArgumentException if `atMost` is [[scala.concurrent.duration.Duration.Undefined Duration.Undefined]] + */ + @throws(classOf[TimeoutException]) + @throws(classOf[InterruptedException]) + def ready(atMost: Duration)(implicit permit: CanAwait): this.type + + /** + * Await and return the result (of type `T`) of this `Awaitable`. + * + * '''''This method should not be called directly; use [[Await.result]] instead.''''' + * + * @param atMost + * maximum wait time, which may be negative (no waiting is done), + * [[scala.concurrent.duration.Duration.Inf Duration.Inf]] for unbounded waiting, or a finite positive + * duration + * @return the result value if the `Awaitable` is completed within the specific maximum wait time + * @throws InterruptedException if the current thread is interrupted while waiting + * @throws TimeoutException if after waiting for the specified time this `Awaitable` is still not ready + * @throws IllegalArgumentException if `atMost` is [[scala.concurrent.duration.Duration.Undefined Duration.Undefined]] + */ + @throws(classOf[TimeoutException]) + @throws(classOf[InterruptedException]) + def result(atMost: Duration)(implicit permit: CanAwait): T +} + + + diff --git a/library/src/scala/concurrent/BatchingExecutor.scala b/library/src/scala/concurrent/BatchingExecutor.scala new file mode 100644 index 000000000000..b108af7c3eca --- /dev/null +++ b/library/src/scala/concurrent/BatchingExecutor.scala @@ -0,0 +1,271 @@ +/* + * 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.concurrent + +import scala.language.`2.13` +import java.util.concurrent.Executor +import java.util.Objects +import scala.util.control.NonFatal +import scala.annotation.{switch, tailrec} + +/** + * Marker trait to indicate that a Runnable is Batchable by BatchingExecutors + */ +trait Batchable { + self: Runnable => +} + +private[concurrent] object BatchingExecutorStatics { + final val emptyBatchArray: Array[Runnable] = new Array[Runnable](0) + + // Max number of Runnables executed nested before starting to batch (to prevent stack exhaustion) + final val syncPreBatchDepth = 16 + + // Max number of Runnables processed in one go (to prevent starvation of other tasks on the pool) + final val runLimit = 1024 + + object MissingParentBlockContext extends BlockContext { + override def blockOn[T](thunk: => T)(implicit permission: CanAwait): T = + try thunk finally throw new IllegalStateException("BUG in BatchingExecutor.Batch: parentBlockContext is null") + } +} + +/** + * Mixin trait for an Executor + * which groups multiple nested `Runnable.run()` calls + * into a single Runnable passed to the original + * Executor. This can be a useful optimization + * because it bypasses the original context's task + * queue and keeps related (nested) code on a single + * thread which may improve CPU affinity. However, + * if tasks passed to the Executor are blocking + * or expensive, this optimization can prevent work-stealing + * and make performance worse. + * A batching executor can create deadlocks if code does + * not use `scala.concurrent.blocking` when it should, + * because tasks created within other tasks will block + * on the outer task completing. + * This executor may run tasks in any order, including LIFO order. + * There are no ordering guarantees. + * + * WARNING: Only use *EITHER* `submitAsyncBatched` OR `submitSyncBatched`!! + * + * When you implement this trait for async executors like thread pools, + * you're going to need to implement it something like the following: + * + * {{{ + * final override def submitAsync(runnable: Runnable): Unit = + * super[SuperClass].execute(runnable) // To prevent reentrancy into `execute` + * + * final override def execute(runnable: Runnable): Unit = + * if (runnable.isInstanceOf[Batchable]) // Or other logic + * submitAsyncBatched(runnable) + * else + * submitAsync(runnable) + * + * final override def reportFailure(cause: Throwable): Unit = … + * }}} + * + * And if you want to implement if for a sync, trampolining, executor you're + * going to implement it something like this: + * + * {{{ + * final override def submitAsync(runnable: Runnable): Unit = () + * + * final override def execute(runnable: Runnable): Unit = + * submitSyncBatched(runnable) // You typically will want to batch everything + * + * final override def reportFailure(cause: Throwable): Unit = + * ExecutionContext.defaultReporter(cause) // Or choose something more fitting + * }}} + * + */ +private[concurrent] trait BatchingExecutor extends Executor { + private[this] final val _tasksLocal = new ThreadLocal[AnyRef]() + + /* + * Batch implements a LIFO queue (stack) and is used as a trampolining Runnable. + * In order to conserve allocations, the first element in the batch is stored "unboxed" in + * the `first` field. Subsequent Runnables are stored in the array called `other`. + */ + private[this] sealed abstract class AbstractBatch protected (protected final var first: Runnable, protected final var other: Array[Runnable], protected final var size: Int) { + + private[this] final def ensureCapacity(curSize: Int): Array[Runnable] = { + val curOther = this.other + val curLen = curOther.length + if (curSize <= curLen) curOther + else { + val newLen = if (curLen == 0) 4 else curLen << 1 + + if (newLen <= curLen) throw new StackOverflowError("Space limit of asynchronous stack reached: " + curLen) + val newOther = new Array[Runnable](newLen) + System.arraycopy(curOther, 0, newOther, 0, curLen) + this.other = newOther + newOther + } + } + + final def push(r: Runnable): Unit = { + val sz = this.size + if(sz == 0) + this.first = r + else + ensureCapacity(sz)(sz - 1) = r + this.size = sz + 1 + } + + @tailrec protected final def runN(n: Int): Unit = + if (n > 0) + (this.size: @switch) match { + case 0 => + case 1 => + val next = this.first + this.first = null + this.size = 0 + next.run() + runN(n - 1) + case sz => + val o = this.other + val next = o(sz - 2) + o(sz - 2) = null + this.size = sz - 1 + next.run() + runN(n - 1) + } + } + + private[this] final class AsyncBatch private(_first: Runnable, _other: Array[Runnable], _size: Int) extends AbstractBatch(_first, _other, _size) with Runnable with BlockContext with (BlockContext => Throwable) { + private[this] final var parentBlockContext: BlockContext = BatchingExecutorStatics.MissingParentBlockContext + + final def this(runnable: Runnable) = this(runnable, BatchingExecutorStatics.emptyBatchArray, 1) + + override final def run(): Unit = { + _tasksLocal.set(this) // This is later cleared in `apply` or `runWithoutResubmit` + + val f = resubmit(BlockContext.usingBlockContext(this)(this)) + + if (f != null) + throw f + } + + /* LOGIC FOR ASYNCHRONOUS BATCHES */ + override final def apply(prevBlockContext: BlockContext): Throwable = try { + parentBlockContext = prevBlockContext + runN(BatchingExecutorStatics.runLimit) + null + } catch { + case t: Throwable => t // We are handling exceptions on the outside of this method + } finally { + parentBlockContext = BatchingExecutorStatics.MissingParentBlockContext + _tasksLocal.remove() + } + + /* Attempts to resubmit this Batch to the underlying ExecutionContext, + * this only happens for Batches where `resubmitOnBlock` is `true`. + * Only attempt to resubmit when there are `Runnables` left to process. + * Note that `cause` can be `null`. + */ + private[this] final def resubmit(cause: Throwable): Throwable = + if (this.size > 0) { + try { submitForExecution(this); cause } catch { + case inner: Throwable => + if (NonFatal(inner)) { + val e = new ExecutionException("Non-fatal error occurred and resubmission failed, see suppressed exception.", cause) + e.addSuppressed(inner) + e + } else inner + } + } else cause // TODO: consider if NonFatals should simply be `reportFailure`:ed rather than rethrown + + private[this] final def cloneAndClear(): AsyncBatch = { + val newBatch = new AsyncBatch(this.first, this.other, this.size) + this.first = null + this.other = BatchingExecutorStatics.emptyBatchArray + this.size = 0 + newBatch + } + + override final def blockOn[T](thunk: => T)(implicit permission: CanAwait): T = { + // If we know there will be blocking, we don't want to keep tasks queued up because it could deadlock. + if(this.size > 0) + submitForExecution(cloneAndClear()) // If this throws then we have bigger problems + + parentBlockContext.blockOn(thunk) // Now delegate the blocking to the previous BC + } + } + + private[this] final class SyncBatch(runnable: Runnable) extends AbstractBatch(runnable, BatchingExecutorStatics.emptyBatchArray, 1) with Runnable { + @tailrec override final def run(): Unit = { + try runN(BatchingExecutorStatics.runLimit) catch { + case ie: InterruptedException => + reportFailure(ie) // TODO: Handle InterruptedException differently? + case f if NonFatal(f) => + reportFailure(f) + } + + if (this.size > 0) + run() + } + } + + /** MUST throw a NullPointerException when `runnable` is null + * When implementing a sync BatchingExecutor, it is RECOMMENDED + * to implement this method as `runnable.run()` + */ + protected def submitForExecution(runnable: Runnable): Unit + + /** Reports that an asynchronous computation failed. + * See `ExecutionContext.reportFailure(throwable: Throwable)` + */ + protected def reportFailure(throwable: Throwable): Unit + + /** + * WARNING: Never use both `submitAsyncBatched` and `submitSyncBatched` in the same + * implementation of `BatchingExecutor` + */ + protected final def submitAsyncBatched(runnable: Runnable): Unit = { + val b = _tasksLocal.get + if (b.isInstanceOf[AsyncBatch]) b.asInstanceOf[AsyncBatch].push(runnable) + else submitForExecution(new AsyncBatch(runnable)) + } + + /** + * WARNING: Never use both `submitAsyncBatched` and `submitSyncBatched` in the same + * implementation of `BatchingExecutor` + */ + protected final def submitSyncBatched(runnable: Runnable): Unit = { + Objects.requireNonNull(runnable, "runnable is null") + val tl = _tasksLocal + val b = tl.get + if (b.isInstanceOf[SyncBatch]) b.asInstanceOf[SyncBatch].push(runnable) + else { + val i = if (b ne null) b.asInstanceOf[java.lang.Integer].intValue else 0 + if (i < BatchingExecutorStatics.syncPreBatchDepth) { + tl.set(java.lang.Integer.valueOf(i + 1)) + try submitForExecution(runnable) // User code so needs to be try-finally guarded here + catch { + case ie: InterruptedException => + reportFailure(ie) // TODO: Handle InterruptedException differently? + case f if NonFatal(f) => + reportFailure(f) + } + finally tl.set(b) + } else { + val batch = new SyncBatch(runnable) + tl.set(batch) + submitForExecution(batch) + tl.set(b) // Batch only throws fatals so no need for try-finally here + } + } + } +} diff --git a/library/src/scala/concurrent/BlockContext.scala b/library/src/scala/concurrent/BlockContext.scala new file mode 100644 index 000000000000..bec31c270038 --- /dev/null +++ b/library/src/scala/concurrent/BlockContext.scala @@ -0,0 +1,112 @@ +/* + * 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.concurrent + +import scala.language.`2.13` + +/** + * A context to be notified by [[scala.concurrent.blocking]] when + * a thread is about to block. In effect this trait provides + * the implementation for [[scala.concurrent.Await]]. + * [[scala.concurrent.Await.result]] and [[scala.concurrent.Await.ready]] + * locates an instance of `BlockContext` by first looking for one + * provided through [[BlockContext.withBlockContext]] and failing that, + * checking whether `Thread.currentThread` is an instance of `BlockContext`. + * So a thread pool can have its `java.lang.Thread` instances implement + * `BlockContext`. There's a default `BlockContext` used if the thread + * doesn't implement `BlockContext`. + * + * Typically, you'll want to chain to the previous `BlockContext`, + * like this: + * {{{ + * val oldContext = BlockContext.current + * val myContext = new BlockContext { + * override def blockOn[T](thunk: => T)(implicit permission: CanAwait): T = { + * // you'd have code here doing whatever you need to do + * // when the thread is about to block. + * // Then you'd chain to the previous context: + * oldContext.blockOn(thunk) + * } + * } + * BlockContext.withBlockContext(myContext) { + * // then this block runs with myContext as the handler + * // for scala.concurrent.blocking + * } + * }}} + */ +trait BlockContext { + + /** Used internally by the framework; + * Designates (and eventually executes) a thunk which potentially blocks the calling `java.lang.Thread`. + * + * Clients must use `scala.concurrent.blocking` or `scala.concurrent.Await` instead. + * + * In implementations of this method it is RECOMMENDED to first check if `permission` is `null` and + * if it is, throw an `IllegalArgumentException`. + * + * @throws IllegalArgumentException if the `permission` is `null` + */ + def blockOn[T](thunk: => T)(implicit permission: CanAwait): T +} + +object BlockContext { + private[this] object DefaultBlockContext extends BlockContext { + override final def blockOn[T](thunk: => T)(implicit permission: CanAwait): T = thunk + } + + /** + * The default block context will execute the supplied thunk immediately. + * @return the `BlockContext` that will be used if no other is found. + **/ + final def defaultBlockContext: BlockContext = DefaultBlockContext + + private[this] final val contextLocal = new ThreadLocal[BlockContext]() + + private[this] final def prefer(candidate: BlockContext): BlockContext = + if (candidate ne null) candidate + else { + val t = Thread.currentThread + if (t.isInstanceOf[BlockContext]) t.asInstanceOf[BlockContext] + else DefaultBlockContext + } + + /** + * @return the `BlockContext` that would be used for the current `java.lang.Thread` at this point + **/ + final def current: BlockContext = prefer(contextLocal.get) + + /** + * Installs a current `BlockContext` around executing `body`. + **/ + final def withBlockContext[T](blockContext: BlockContext)(body: => T): T = { + val old = contextLocal.get // can be null + if (old eq blockContext) body + else { + contextLocal.set(blockContext) + try body finally contextLocal.set(old) + } + } + + /** + * Installs the BlockContext `blockContext` around the invocation to `f` and passes in the previously installed BlockContext to `f`. + * @return the value produced by applying `f` + **/ + final def usingBlockContext[I, T](blockContext: BlockContext)(f: BlockContext => T): T = { + val old = contextLocal.get // can be null + if (old eq blockContext) f(prefer(old)) + else { + contextLocal.set(blockContext) + try f(prefer(old)) finally contextLocal.set(old) + } + } +} diff --git a/library/src/scala/concurrent/Channel.scala b/library/src/scala/concurrent/Channel.scala new file mode 100644 index 000000000000..ebb3b324ef5a --- /dev/null +++ b/library/src/scala/concurrent/Channel.scala @@ -0,0 +1,61 @@ +/* + * 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.concurrent + +import scala.language.`2.13` + +/** This class provides a simple FIFO queue of data objects, + * which are read by one or more reader threads. + * + * @tparam A type of data exchanged + */ +@deprecated("Use `java.util.concurrent.LinkedTransferQueue` instead.", since = "2.13.0") +class Channel[A] { + private class LinkedList { + var elem: A = _ + var next: LinkedList = _ + } + private[this] var written = new LinkedList // FIFO queue, realized through + private[this] var lastWritten = written // aliasing of a linked list + private[this] var nreaders = 0 + + /** Append a value to the FIFO queue to be read by `read`. + * This operation is nonblocking and can be executed by any thread. + * + * @param x object to enqueue to this channel + */ + def write(x: A): Unit = synchronized { + lastWritten.elem = x + lastWritten.next = new LinkedList + lastWritten = lastWritten.next + if (nreaders > 0) notify() + } + + /** Retrieve the next waiting object from the FIFO queue, + * blocking if necessary until an object is available. + * + * @return next object dequeued from this channel + */ + def read: A = synchronized { + while (written.next == null) { + try { + nreaders += 1 + wait() + } + finally nreaders -= 1 + } + val x = written.elem + written = written.next + x + } +} diff --git a/library/src/scala/concurrent/DelayedLazyVal.scala b/library/src/scala/concurrent/DelayedLazyVal.scala new file mode 100644 index 000000000000..0406e7c0d7f7 --- /dev/null +++ b/library/src/scala/concurrent/DelayedLazyVal.scala @@ -0,0 +1,48 @@ +/* + * 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.concurrent + +import scala.language.`2.13` + +/** A `DelayedLazyVal` is a wrapper for lengthy computations which have a + * valid partially computed result. + * + * The first argument is a function for obtaining the result at any given + * point in time, and the second is the lengthy computation. Once the + * computation is complete, the `apply` method will stop recalculating it + * and return a fixed value from that point forward. + * + * @param f the function to obtain the current value at any point in time + * @param body the computation to run to completion in another thread + */ +@deprecated("`DelayedLazyVal` Will be removed in the future.", since = "2.13.0") +class DelayedLazyVal[T](f: () => T, body: => Unit)(implicit exec: ExecutionContext){ + @volatile private[this] var _isDone = false + private[this] lazy val complete = f() + + /** Whether the computation is complete. + * + * @return true if the computation is complete. + */ + def isDone: Boolean = _isDone + + /** The current result of f(), or the final result if complete. + * + * @return the current value + */ + def apply(): T = if (isDone) complete else f() + + exec.execute(() => { + body; _isDone = true + }) +} diff --git a/library/src/scala/concurrent/ExecutionContext.scala b/library/src/scala/concurrent/ExecutionContext.scala new file mode 100644 index 000000000000..703b962a0f17 --- /dev/null +++ b/library/src/scala/concurrent/ExecutionContext.scala @@ -0,0 +1,295 @@ +/* + * 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.concurrent + +import scala.language.`2.13` +import java.util.concurrent.{ ExecutorService, Executor } +import scala.annotation.implicitNotFound + +/** + * An `ExecutionContext` can execute program logic asynchronously, + * typically but not necessarily on a thread pool. + * + * A general purpose `ExecutionContext` must be asynchronous in executing + * any `Runnable` that is passed into its `execute`-method. A special purpose + * `ExecutionContext` may be synchronous but must only be passed to code that + * is explicitly safe to be run using a synchronously executing `ExecutionContext`. + * + * APIs such as `Future.onComplete` require you to provide a callback + * and an implicit `ExecutionContext`. The implicit `ExecutionContext` + * will be used to execute the callback. + * + * While it is possible to simply import + * `scala.concurrent.ExecutionContext.Implicits.global` to obtain an + * implicit `ExecutionContext`, application developers should carefully + * consider where they want to define the execution policy; + * ideally, one place per application — or per logically related section of code — + * will make a decision about which `ExecutionContext` to use. + * That is, you will mostly want to avoid hardcoding, especially via an import, + * `scala.concurrent.ExecutionContext.Implicits.global`. + * The recommended approach is to add `(implicit ec: ExecutionContext)` to methods, + * or class constructor parameters, which need an `ExecutionContext`. + * + * Then locally import a specific `ExecutionContext` in one place for the entire + * application or module, passing it implicitly to individual methods. + * Alternatively define a local implicit val with the required `ExecutionContext`. + * + * A custom `ExecutionContext` may be appropriate to execute code + * which blocks on IO or performs long-running computations. + * `ExecutionContext.fromExecutorService` and `ExecutionContext.fromExecutor` + * are good ways to create a custom `ExecutionContext`. + * + * The intent of `ExecutionContext` is to lexically scope code execution. + * That is, each method, class, file, package, or application determines + * how to run its own code. This avoids issues such as running + * application callbacks on a thread pool belonging to a networking library. + * The size of a networking library's thread pool can be safely configured, + * knowing that only that library's network operations will be affected. + * Application callback execution can be configured separately. + */ +@implicitNotFound("""Cannot find an implicit ExecutionContext. You might add +an (implicit ec: ExecutionContext) parameter to your method. + +The ExecutionContext is used to configure how and on which +thread pools asynchronous tasks (such as Futures) will run, +so the specific ExecutionContext that is selected is important. + +If your application does not define an ExecutionContext elsewhere, +consider using Scala's global ExecutionContext by defining +the following: + +implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global""") +trait ExecutionContext { + + /** Runs a block of code on this execution context. + * + * @param runnable the task to execute + */ + def execute(runnable: Runnable): Unit + + /** Reports that an asynchronous computation failed. + * + * @param cause the cause of the failure + */ + def reportFailure(@deprecatedName("t") cause: Throwable): Unit + + /** Prepares for the execution of a task. Returns the prepared + * execution context. The recommended implementation of + * `prepare` is to return `this`. + * + * This method should no longer be overridden or called. It was + * originally expected that `prepare` would be called by + * all libraries that consume ExecutionContexts, in order to + * capture thread local context. However, this usage has proven + * difficult to implement in practice and instead it is + * now better to avoid using `prepare` entirely. + * + * Instead, if an `ExecutionContext` needs to capture thread + * local context, it should capture that context when it is + * constructed, so that it doesn't need any additional + * preparation later. + */ + @deprecated("preparation of ExecutionContexts will be removed", "2.12.0") + // This cannot be removed until there is a suitable replacement + def prepare(): ExecutionContext = this +} + +/** + * An [[ExecutionContext]] that is also a + * Java [[java.util.concurrent.Executor Executor]]. + */ +trait ExecutionContextExecutor extends ExecutionContext with Executor + +/** + * An [[ExecutionContext]] that is also a + * Java [[java.util.concurrent.ExecutorService ExecutorService]]. + */ +trait ExecutionContextExecutorService extends ExecutionContextExecutor with ExecutorService + + +/** Contains factory methods for creating execution contexts. + */ +object ExecutionContext { + /** + * The global [[ExecutionContext]]. This default `ExecutionContext` implementation is backed by a work-stealing thread + * pool. It can be configured via the following system properties: + * + * - `scala.concurrent.context.minThreads` = defaults to "1" + * - `scala.concurrent.context.numThreads` = defaults to "x1" (i.e. the current number of available processors * 1) + * - `scala.concurrent.context.maxThreads` = defaults to "x1" (i.e. the current number of available processors * 1) + * - `scala.concurrent.context.maxExtraThreads` = defaults to "256" + * + * The pool size of threads is then `numThreads` bounded by `minThreads` on the lower end and `maxThreads` on the high end. + * + * The `maxExtraThreads` is the maximum number of extra threads to have at any given time to evade deadlock, + * see [[scala.concurrent.blocking]]. + * + * The `global` execution context can be used explicitly, by defining an + * `implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global`, or by importing + * [[ExecutionContext.Implicits.global]]. + * + * == Batching short-lived nested tasks == + * + * Asynchronous code with short-lived nested tasks is executed more efficiently when using + * `ExecutionContext.opportunistic` (continue reading to learn why it is `private[scala]` and how to access it). + * + * `ExecutionContext.opportunistic` uses the same thread pool as `ExecutionContext.global`. It attempts to batch + * nested task and execute them on the same thread as the enclosing task. This is ideally suited to execute + * short-lived tasks as it reduces the overhead of context switching. + * + * WARNING: long-running and/or blocking tasks should be demarcated within [[scala.concurrent.blocking]]-blocks + * to ensure that any pending tasks in the current batch can be executed by another thread on `global`. + * + * === How to use === + * + * This field is `private[scala]` to maintain binary compatibility. It was added in 2.13.4, code that references it + * directly fails to run with a 2.13.0-3 Scala library. + * + * Libraries should not reference this field directly because users of the library might be using an earlier Scala + * version. In order to use the batching `ExecutionContext` in a library, the code needs to fall back to `global` + * in case the `opportunistic` field is missing (example below). The resulting `ExecutionContext` has batching + * behavior in all Scala 2.13 versions (`global` is batching in 2.13.0-3). + * + * {{{ + * implicit val ec: scala.concurrent.ExecutionContext = try { + * scala.concurrent.ExecutionContext.getClass + * .getDeclaredMethod("opportunistic") + * .invoke(scala.concurrent.ExecutionContext) + * .asInstanceOf[scala.concurrent.ExecutionContext] + * } catch { + * case _: NoSuchMethodException => + * scala.concurrent.ExecutionContext.global + * } + * }}} + * + * Application authors can safely use the field because the Scala version at run time is the same as at compile time. + * Options to bypass the access restriction include: + * + * 1. Using a structural type (example below). This uses reflection at run time. + * 1. Writing a Scala `object` in the `scala` package (example below). + * 1. Writing a Java source file. This works because `private[scala]` is emitted as `public` in Java bytecode. + * + * {{{ + * // Option 1 + * implicit val ec: scala.concurrent.ExecutionContext = + * (scala.concurrent.ExecutionContext: + * {def opportunistic: scala.concurrent.ExecutionContextExecutor} + * ).opportunistic + * + * // Option 2 + * package scala { + * object OpportunisticEC { + * implicit val ec: scala.concurrent.ExecutionContext = + * scala.concurrent.ExecutionContext.opportunistic + * } + * } + * }}} + * + * @return the global [[ExecutionContext]] + */ + final lazy val global: ExecutionContextExecutor = impl.ExecutionContextImpl.fromExecutor(null: Executor) + + /** + * WARNING: Only ever execute logic which will quickly return control to the caller. + * + * This `ExecutionContext` steals execution time from other threads by having its + * `Runnable`s run on the `Thread` which calls `execute` and then yielding back control + * to the caller after *all* its `Runnable`s have been executed. + * Nested invocations of `execute` will be trampolined to prevent uncontrolled stack space growth. + * + * When using `parasitic` with abstractions such as `Future` it will in many cases be non-deterministic + * as to which `Thread` will be executing the logic, as it depends on when/if that `Future` is completed. + * + * Do *not* call any blocking code in the `Runnable`s submitted to this `ExecutionContext` + * as it will prevent progress by other enqueued `Runnable`s and the calling `Thread`. + * + * Symptoms of misuse of this `ExecutionContext` include, but are not limited to, deadlocks + * and severe performance problems. + * + * Any `NonFatal` or `InterruptedException`s will be reported to the `defaultReporter`. + */ + object parasitic extends ExecutionContextExecutor with BatchingExecutor { + override final def submitForExecution(runnable: Runnable): Unit = runnable.run() + override final def execute(runnable: Runnable): Unit = submitSyncBatched(runnable) + override final def reportFailure(t: Throwable): Unit = defaultReporter(t) + } + + /** + * See [[ExecutionContext.global]]. + */ + private[scala] lazy val opportunistic: ExecutionContextExecutor = new ExecutionContextExecutor with BatchingExecutor { + final override def submitForExecution(runnable: Runnable): Unit = global.execute(runnable) + + final override def execute(runnable: Runnable): Unit = + if ((!runnable.isInstanceOf[impl.Promise.Transformation[_,_]] || runnable.asInstanceOf[impl.Promise.Transformation[_,_]].benefitsFromBatching) && runnable.isInstanceOf[Batchable]) + submitAsyncBatched(runnable) + else + submitForExecution(runnable) + + override final def reportFailure(t: Throwable): Unit = global.reportFailure(t) + } + + object Implicits { + /** + * An accessor that can be used to import the global `ExecutionContext` into the implicit scope, + * see [[ExecutionContext.global]]. + */ + implicit final def global: ExecutionContext = ExecutionContext.global + } + + /** Creates an `ExecutionContext` from the given `ExecutorService`. + * + * @param e the `ExecutorService` to use. If `null`, a new `ExecutorService` is created with [[scala.concurrent.ExecutionContext$.global default configuration]]. + * @param reporter a function for error reporting + * @return the `ExecutionContext` using the given `ExecutorService` + */ + def fromExecutorService(e: ExecutorService, reporter: Throwable => Unit): ExecutionContextExecutorService = + impl.ExecutionContextImpl.fromExecutorService(e, reporter) + + /** Creates an `ExecutionContext` from the given `ExecutorService` with the [[scala.concurrent.ExecutionContext$.defaultReporter default reporter]]. + * + * If it is guaranteed that none of the executed tasks are blocking, a single-threaded `ExecutorService` + * can be used to create an `ExecutionContext` as follows: + * + * {{{ + * import java.util.concurrent.Executors + * val ec = ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor()) + * }}} + * + * @param e the `ExecutorService` to use. If `null`, a new `ExecutorService` is created with [[scala.concurrent.ExecutionContext$.global default configuration]]. + * @return the `ExecutionContext` using the given `ExecutorService` + */ + def fromExecutorService(e: ExecutorService): ExecutionContextExecutorService = fromExecutorService(e, defaultReporter) + + /** Creates an `ExecutionContext` from the given `Executor`. + * + * @param e the `Executor` to use. If `null`, a new `Executor` is created with [[scala.concurrent.ExecutionContext$.global default configuration]]. + * @param reporter a function for error reporting + * @return the `ExecutionContext` using the given `Executor` + */ + def fromExecutor(e: Executor, reporter: Throwable => Unit): ExecutionContextExecutor = + impl.ExecutionContextImpl.fromExecutor(e, reporter) + + /** Creates an `ExecutionContext` from the given `Executor` with the [[scala.concurrent.ExecutionContext$.defaultReporter default reporter]]. + * + * @param e the `Executor` to use. If `null`, a new `Executor` is created with [[scala.concurrent.ExecutionContext$.global default configuration]]. + * @return the `ExecutionContext` using the given `Executor` + */ + def fromExecutor(e: Executor): ExecutionContextExecutor = fromExecutor(e, defaultReporter) + + /** The default reporter simply prints the stack trace of the `Throwable` to [[java.lang.System#err System.err]]. + * + * @return the function for error reporting + */ + final val defaultReporter: Throwable => Unit = _.printStackTrace() +} diff --git a/library/src/scala/concurrent/Future.scala b/library/src/scala/concurrent/Future.scala new file mode 100644 index 000000000000..05fcadf28e45 --- /dev/null +++ b/library/src/scala/concurrent/Future.scala @@ -0,0 +1,890 @@ +/* + * 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.concurrent + +import scala.language.`2.13` +import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.locks.LockSupport +import scala.util.control.{NoStackTrace, NonFatal} +import scala.util.{Failure, Success, Try} +import scala.concurrent.duration._ +import scala.collection.BuildFrom +import scala.collection.mutable.{ArrayBuffer, Builder} +import scala.reflect.ClassTag +import scala.concurrent.ExecutionContext.parasitic +import scala.concurrent.impl.Promise.DefaultPromise + +/** A `Future` represents a value which may or may not be currently available, + * but will be available at some point, or an exception if that value could not be made available. + * + * Asynchronous computations are created by calling `Future.apply`, which yields instances of `Future`. + * Computations are executed using an `ExecutionContext`, which is usually supplied implicitly, + * and which is commonly backed by a thread pool. + * + * {{{ + * import ExecutionContext.Implicits.global + * val s = "Hello" + * val f: Future[String] = Future { + * s + " future!" + * } + * f foreach { + * msg => println(msg) + * } + * }}} + * + * Note that the `global` context is convenient but restricted: + * "fatal" exceptions are reported only by printing a stack trace, + * and the underlying thread pool may be shared by a mix of jobs. + * For any nontrivial application, see the caveats explained at [[ExecutionContext]] + * and also the overview linked below, which explains + * [[https://docs.scala-lang.org/overviews/core/futures.html#exceptions exception handling]] + * in depth. + * + * + * @see [[https://docs.scala-lang.org/overviews/core/futures.html Futures and Promises]] + * + * @define multipleCallbacks + * Multiple callbacks may be registered; there is no guarantee that they will be + * executed in a particular order. + * + * @define caughtThrowables + * This future may contain a throwable object and this means that the future failed. + * Futures obtained through combinators have the same exception as the future they were obtained from. + * The following throwable objects are not contained in the future: + * - `Error` - fatal errors are not contained within futures + * - `InterruptedException` - not contained within futures + * - all `scala.util.control.ControlThrowable` except `NonLocalReturnControl` - not contained within futures + * + * Instead, the future is completed with an ExecutionException that has one of the exceptions above as its cause. + * If a future is failed with a `scala.runtime.NonLocalReturnControl`, + * it is completed with a value from that throwable instead. + * + * @define swallowsExceptions + * Since this method executes asynchronously and does not produce a return value, + * any non-fatal exceptions thrown will be reported to the `ExecutionContext`. + * + * @define nonDeterministic + * Note: using this method yields nondeterministic dataflow programs. + * + * @define forComprehensionExamples + * Example: + * + * {{{ + * val f = Future { 5 } + * val g = Future { 3 } + * val h = for { + * x: Int <- f // returns Future(5) + * y: Int <- g // returns Future(3) + * } yield x + y + * }}} + * + * is translated to: + * + * {{{ + * f flatMap { (x: Int) => g map { (y: Int) => x + y } } + * }}} + * + * @define callbackInContext + * The provided callback always runs in the provided implicit + *`ExecutionContext`, though there is no guarantee that the + * `execute()` method on the `ExecutionContext` will be called once + * per callback or that `execute()` will be called in the current + * thread. That is, the implementation may run multiple callbacks + * in a batch within a single `execute()` and it may run + * `execute()` either immediately or asynchronously. + * Completion of the Future must *happen-before* the invocation of the callback. + */ +trait Future[+T] extends Awaitable[T] { + + /* Callbacks */ + + /** When this future is completed, either through an exception, or a value, + * apply the provided function. + * + * If the future has already been completed, + * this will either be applied immediately or be scheduled asynchronously. + * + * Note that the returned value of `f` will be discarded. + * + * $swallowsExceptions + * $multipleCallbacks + * $callbackInContext + * + * @tparam U only used to accept any return type of the given callback function + * @param f the function to be executed when this `Future` completes + * @group Callbacks + */ + def onComplete[U](f: Try[T] => U)(implicit executor: ExecutionContext): Unit + + /* Miscellaneous */ + + /** Returns whether the future had already been completed with + * a value or an exception. + * + * $nonDeterministic + * + * @return `true` if the future was completed, `false` otherwise + * @group Polling + */ + def isCompleted: Boolean + + /** The current value of this `Future`. + * + * $nonDeterministic + * + * If the future was not completed the returned value will be `None`. + * If the future was completed the value will be `Some(Success(t))` + * if it contained a valid result, or `Some(Failure(error))` if it contained + * an exception. + * + * @return `None` if the `Future` wasn't completed, `Some` if it was. + * @group Polling + */ + def value: Option[Try[T]] + + + /* Projections */ + + /** The returned `Future` will be successfully completed with the `Throwable` of the original `Future` + * if the original `Future` fails. + * + * If the original `Future` is successful, the returned `Future` is failed with a `NoSuchElementException`. + * + * $caughtThrowables + * + * @return a failed projection of this `Future`. + * @group Transformations + */ + def failed: Future[Throwable] = transform(Future.failedFun)(parasitic) + + + /* Monadic operations */ + + /** Asynchronously processes the value in the future once the value becomes available. + * + * WARNING: Will not be called if this future is never completed or if it is completed with a failure. + * + * $swallowsExceptions + * + * @tparam U only used to accept any return type of the given callback function + * @param f the function which will be executed if this `Future` completes with a result, + * the return value of `f` will be discarded. + * @group Callbacks + */ + def foreach[U](f: T => U)(implicit executor: ExecutionContext): Unit = onComplete { _ foreach f } + + /** Creates a new future by applying the 's' function to the successful result of + * this future, or the 'f' function to the failed result. If there is any non-fatal + * exception thrown when 's' or 'f' is applied, that exception will be propagated + * to the resulting future. + * + * @tparam S the type of the returned `Future` + * @param s function that transforms a successful result of the receiver into a successful result of the returned future + * @param f function that transforms a failure of the receiver into a failure of the returned future + * @return a `Future` that will be completed with the transformed value + * @group Transformations + */ + def transform[S](s: T => S, f: Throwable => Throwable)(implicit executor: ExecutionContext): Future[S] = + transform { + t => + if (t.isInstanceOf[Success[T]]) t map s + else throw f(t.asInstanceOf[Failure[T]].exception) // will throw fatal errors! + } + + /** Creates a new Future by applying the specified function to the result + * of this Future. If there is any non-fatal exception thrown when 'f' + * is applied then that exception will be propagated to the resulting future. + * + * @tparam S the type of the returned `Future` + * @param f function that transforms the result of this future + * @return a `Future` that will be completed with the transformed value + * @group Transformations + */ + def transform[S](f: Try[T] => Try[S])(implicit executor: ExecutionContext): Future[S] + + /** Creates a new Future by applying the specified function, which produces a Future, to the result + * of this Future. If there is any non-fatal exception thrown when 'f' + * is applied then that exception will be propagated to the resulting future. + * + * @tparam S the type of the returned `Future` + * @param f function that transforms the result of this future + * @return a `Future` that will be completed with the transformed value + * @group Transformations + */ + def transformWith[S](f: Try[T] => Future[S])(implicit executor: ExecutionContext): Future[S] + + + /** Creates a new future by applying a function to the successful result of + * this future. If this future is completed with an exception then the new + * future will also contain this exception. + * + * Example: + * + * {{{ + * val f = Future { "The future" } + * val g = f map { x: String => x + " is now!" } + * }}} + * + * Note that a for comprehension involving a `Future` + * may expand to include a call to `map` and or `flatMap` + * and `withFilter`. See [[scala.concurrent.Future#flatMap]] for an example of such a comprehension. + * + * + * @tparam S the type of the returned `Future` + * @param f the function which will be applied to the successful result of this `Future` + * @return a `Future` which will be completed with the result of the application of the function + * @group Transformations + */ + def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S] = transform(_ map f) + + /** Creates a new future by applying a function to the successful result of + * this future, and returns the result of the function as the new future. + * If this future is completed with an exception then the new future will + * also contain this exception. + * + * $forComprehensionExamples + * + * @tparam S the type of the returned `Future` + * @param f the function which will be applied to the successful result of this `Future` + * @return a `Future` which will be completed with the result of the application of the function + * @group Transformations + */ + def flatMap[S](f: T => Future[S])(implicit executor: ExecutionContext): Future[S] = transformWith { + t => + if(t.isInstanceOf[Success[T]]) f(t.asInstanceOf[Success[T]].value) + else this.asInstanceOf[Future[S]] // Safe cast + } + + /** Creates a new future with one level of nesting flattened, this method is equivalent + * to `flatMap(identity)`. + * + * @tparam S the type of the returned `Future` + * @group Transformations + */ + def flatten[S](implicit ev: T <:< Future[S]): Future[S] = flatMap(ev)(parasitic) + + /** Creates a new future by filtering the value of the current future with a predicate. + * + * If the current future contains a value which satisfies the predicate, the new future will also hold that value. + * Otherwise, the resulting future will fail with a `NoSuchElementException`. + * + * If the current future fails, then the resulting future also fails. + * + * Example: + * {{{ + * val f = Future { 5 } + * val g = f filter { _ % 2 == 1 } + * val h = f filter { _ % 2 == 0 } + * g foreach println // Eventually prints 5 + * Await.result(h, Duration.Zero) // throw a NoSuchElementException + * }}} + * + * @param p the predicate to apply to the successful result of this `Future` + * @return a `Future` which will hold the successful result of this `Future` if it matches the predicate or a `NoSuchElementException` + * @group Transformations + */ + def filter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = + transform { + t => + if (t.isInstanceOf[Success[T]]) { + if (p(t.asInstanceOf[Success[T]].value)) t + else Future.filterFailure + } else t + } + + /** Used by for-comprehensions. + * @group Transformations + */ + final def withFilter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = filter(p)(executor) + + /** Creates a new future by mapping the value of the current future, if the given partial function is defined at that value. + * + * If the current future contains a value for which the partial function is defined, the new future will also hold that value. + * Otherwise, the resulting future will fail with a `NoSuchElementException`. + * + * If the current future fails, then the resulting future also fails. + * + * Example: + * {{{ + * val f = Future { -5 } + * val g = f collect { + * case x if x < 0 => -x + * } + * val h = f collect { + * case x if x > 0 => x * 2 + * } + * g foreach println // Eventually prints 5 + * Await.result(h, Duration.Zero) // throw a NoSuchElementException + * }}} + * + * @tparam S the type of the returned `Future` + * @param pf the `PartialFunction` to apply to the successful result of this `Future` + * @return a `Future` holding the result of application of the `PartialFunction` or a `NoSuchElementException` + * @group Transformations + */ + def collect[S](pf: PartialFunction[T, S])(implicit executor: ExecutionContext): Future[S] = + transform { + t => + if (t.isInstanceOf[Success[T]]) + Success(pf.applyOrElse(t.asInstanceOf[Success[T]].value, Future.collectFailed)) + else t.asInstanceOf[Failure[S]] + } + + /** Creates a new future that will handle any matching throwable that this + * future might contain. If there is no match, or if this future contains + * a valid result then the new future will contain the same. + * + * Example: + * + * {{{ + * Future (6 / 0) recover { case e: ArithmeticException => 0 } // result: 0 + * Future (6 / 0) recover { case e: NotFoundException => 0 } // result: exception + * Future (6 / 2) recover { case e: ArithmeticException => 0 } // result: 3 + * }}} + * + * @tparam U the type of the returned `Future` + * @param pf the `PartialFunction` to apply if this `Future` fails + * @return a `Future` with the successful value of this `Future` or the result of the `PartialFunction` + * @group Transformations + */ + def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U] = + transform { _ recover pf } + + /** Creates a new future that will handle any matching throwable that this + * future might contain by assigning it a value of another future. + * + * If there is no match, or if this future contains + * a valid result then the new future will contain the same result. + * + * Example: + * + * {{{ + * val f = Future { Int.MaxValue } + * Future (6 / 0) recoverWith { case e: ArithmeticException => f } // result: Int.MaxValue + * }}} + * + * @tparam U the type of the returned `Future` + * @param pf the `PartialFunction` to apply if this `Future` fails + * @return a `Future` with the successful value of this `Future` or the outcome of the `Future` returned by the `PartialFunction` + * @group Transformations + */ + def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U] = + transformWith { + t => + if (t.isInstanceOf[Failure[T]]) { + val result = pf.applyOrElse(t.asInstanceOf[Failure[T]].exception, Future.recoverWithFailed) + if (result ne Future.recoverWithFailedMarker) result + else this + } else this + } + + /** Zips the values of `this` and `that` future, and creates + * a new future holding the tuple of their results. + * + * If either input future fails, the resulting future is failed with the same + * throwable, without waiting for the other input future to complete. + * + * If the application of `f` throws a non-fatal throwable, the resulting future + * is failed with that throwable. + * + * @tparam U the type of the other `Future` + * @param that the other `Future` + * @return a `Future` with the results of both futures or the failure of the first of them that failed + * @group Transformations + */ + def zip[U](that: Future[U]): Future[(T, U)] = + zipWith(that)(Future.zipWithTuple2Fun)(parasitic) + + /** Zips the values of `this` and `that` future using a function `f`, + * and creates a new future holding the result. + * + * If either input future fails, the resulting future is failed with the same + * throwable, without waiting for the other input future to complete. + * + * If the application of `f` throws a non-fatal throwable, the resulting future + * is failed with that throwable. + * + * @tparam U the type of the other `Future` + * @tparam R the type of the resulting `Future` + * @param that the other `Future` + * @param f the function to apply to the results of `this` and `that` + * @return a `Future` with the result of the application of `f` to the results of `this` and `that` + * @group Transformations + */ + def zipWith[U, R](that: Future[U])(f: (T, U) => R)(implicit executor: ExecutionContext): Future[R] = { + // This is typically overriden by the implementation in DefaultPromise, which provides + // symmetric fail-fast behavior regardless of which future fails first. + // + // TODO: remove this implementation and make Future#zipWith abstract + // when we're next willing to make a binary incompatible change + flatMap(r1 => that.map(r2 => f(r1, r2)))(if (executor.isInstanceOf[BatchingExecutor]) executor else parasitic) + } + + /** Creates a new future which holds the result of this future if it was completed successfully, or, if not, + * the result of the `that` future if `that` is completed successfully. + * If both futures are failed, the resulting future holds the throwable object of the first future. + * + * Using this method will not cause concurrent programs to become nondeterministic. + * + * Example: + * {{{ + * val f = Future { throw new RuntimeException("failed") } + * val g = Future { 5 } + * val h = f fallbackTo g + * h foreach println // Eventually prints 5 + * }}} + * + * @tparam U the type of the other `Future` and the resulting `Future` + * @param that the `Future` whose result we want to use if this `Future` fails. + * @return a `Future` with the successful result of this or that `Future` or the failure of this `Future` if both fail + * @group Transformations + */ + def fallbackTo[U >: T](that: Future[U]): Future[U] = + if (this eq that) this + else { + implicit val ec = parasitic + transformWith { + t => + if (t.isInstanceOf[Success[T]]) this + else that transform { tt => if (tt.isInstanceOf[Success[U]]) tt else t } + } + } + + /** Creates a new `Future[S]` which is completed with this `Future`'s result if + * that conforms to `S`'s erased type or a `ClassCastException` otherwise. + * + * @tparam S the type of the returned `Future` + * @param tag the `ClassTag` which will be used to cast the result of this `Future` + * @return a `Future` holding the casted result of this `Future` or a `ClassCastException` otherwise + * @group Transformations + */ + def mapTo[S](implicit tag: ClassTag[S]): Future[S] = { + implicit val ec = parasitic + val boxedClass = { + val c = tag.runtimeClass + if (c.isPrimitive) Future.toBoxed(c) else c + } + require(boxedClass ne null) + map(s => boxedClass.cast(s).asInstanceOf[S]) + } + + /** Applies the side-effecting function to the result of this future, and returns + * a new future with the result of this future. + * + * This method allows one to enforce that the callbacks are executed in a + * specified order. + * + * Note that if one of the chained `andThen` callbacks throws + * an exception, that exception is not propagated to the subsequent `andThen` + * callbacks. Instead, the subsequent `andThen` callbacks are given the original + * value of this future. + * + * The following example prints out `5`: + * + * {{{ + * val f = Future { 5 } + * f andThen { + * case r => throw new RuntimeException("runtime exception") + * } andThen { + * case Failure(t) => println(t) + * case Success(v) => println(v) + * } + * }}} + * + * $swallowsExceptions + * + * @tparam U only used to accept any return type of the given `PartialFunction` + * @param pf a `PartialFunction` which will be conditionally applied to the outcome of this `Future` + * @return a `Future` which will be completed with the exact same outcome as this `Future` but after the `PartialFunction` has been executed. + * @group Callbacks + */ + def andThen[U](pf: PartialFunction[Try[T], U])(implicit executor: ExecutionContext): Future[T] = + transform { + result => + try pf.applyOrElse[Try[T], Any](result, Future.id[Try[T]]) + catch { case t if NonFatal(t) => executor.reportFailure(t) } + // TODO: use `finally`? + result + } +} + + + +/** Future companion object. + * + * @define nonDeterministic + * Note: using this method yields nondeterministic dataflow programs. + */ +object Future { + + /** + * Utilities, hoisted functions, etc. + */ + + private[concurrent] final val toBoxed = Map[Class[_], Class[_]]( + classOf[Boolean] -> classOf[java.lang.Boolean], + classOf[Byte] -> classOf[java.lang.Byte], + classOf[Char] -> classOf[java.lang.Character], + classOf[Short] -> classOf[java.lang.Short], + classOf[Int] -> classOf[java.lang.Integer], + classOf[Long] -> classOf[java.lang.Long], + classOf[Float] -> classOf[java.lang.Float], + classOf[Double] -> classOf[java.lang.Double], + classOf[Unit] -> classOf[scala.runtime.BoxedUnit] + ) + + private[this] final val _cachedId: AnyRef => AnyRef = Predef.identity _ + + private[concurrent] final def id[T]: T => T = _cachedId.asInstanceOf[T => T] + + private[concurrent] final val collectFailed = + (t: Any) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t) with NoStackTrace + + private[concurrent] final val filterFailure = + Failure[Nothing](new NoSuchElementException("Future.filter predicate is not satisfied") with NoStackTrace) + + private[this] final val failedFailure = + Failure[Nothing](new NoSuchElementException("Future.failed not completed with a throwable.") with NoStackTrace) + + private[concurrent] final val failedFailureFuture: Future[Nothing] = + scala.concurrent.Future.fromTry(failedFailure) + + private[this] final val _failedFun: Try[Any] => Try[Throwable] = + v => if (v.isInstanceOf[Failure[Any]]) Success(v.asInstanceOf[Failure[Any]].exception) else failedFailure + + private[concurrent] final def failedFun[T]: Try[T] => Try[Throwable] = _failedFun.asInstanceOf[Try[T] => Try[Throwable]] + + private[concurrent] final val recoverWithFailedMarker: Future[Nothing] = + scala.concurrent.Future.failed(new Throwable with NoStackTrace) + + private[concurrent] final val recoverWithFailed = (t: Throwable) => recoverWithFailedMarker + + private[this] final val _zipWithTuple2: (Any, Any) => (Any, Any) = Tuple2.apply _ + private[concurrent] final def zipWithTuple2Fun[T,U] = _zipWithTuple2.asInstanceOf[(T,U) => (T,U)] + + private[this] final val _addToBuilderFun: (Builder[Any, Nothing], Any) => Builder[Any, Nothing] = (b: Builder[Any, Nothing], e: Any) => b += e + private[concurrent] final def addToBuilderFun[A, M] = _addToBuilderFun.asInstanceOf[Function2[Builder[A, M], A, Builder[A, M]]] + + /** A Future which is never completed. + */ + object never extends Future[Nothing] { + + @throws[TimeoutException] + @throws[InterruptedException] + override final def ready(atMost: Duration)(implicit permit: CanAwait): this.type = { + import Duration.{Undefined, Inf, MinusInf} + atMost match { + case u if u eq Undefined => throw new IllegalArgumentException("cannot wait for Undefined period") + case `Inf` => + while(!Thread.interrupted()) { + LockSupport.park(this) + } + throw new InterruptedException + case `MinusInf` => // Drop out + case f: FiniteDuration if f > Duration.Zero => + var now = System.nanoTime() + val deadline = now + f.toNanos + while((deadline - now) > 0) { + LockSupport.parkNanos(this, deadline - now) + if (Thread.interrupted()) + throw new InterruptedException + now = System.nanoTime() + } + // Done waiting, drop out + case _: FiniteDuration => // Drop out if 0 or less + case x: Duration.Infinite => throw new MatchError(x) + } + throw new TimeoutException(s"Future timed out after [$atMost]") + } + + @throws[TimeoutException] + @throws[InterruptedException] + override final def result(atMost: Duration)(implicit permit: CanAwait): Nothing = { + ready(atMost) + throw new TimeoutException(s"Future timed out after [$atMost]") + } + + override final def onComplete[U](f: Try[Nothing] => U)(implicit executor: ExecutionContext): Unit = () + override final def isCompleted: Boolean = false + override final def value: Option[Try[Nothing]] = None + override final def failed: Future[Throwable] = this + override final def foreach[U](f: Nothing => U)(implicit executor: ExecutionContext): Unit = () + override final def transform[S](s: Nothing => S, f: Throwable => Throwable)(implicit executor: ExecutionContext): Future[S] = this + override final def transform[S](f: Try[Nothing] => Try[S])(implicit executor: ExecutionContext): Future[S] = this + override final def transformWith[S](f: Try[Nothing] => Future[S])(implicit executor: ExecutionContext): Future[S] = this + override final def map[S](f: Nothing => S)(implicit executor: ExecutionContext): Future[S] = this + override final def flatMap[S](f: Nothing => Future[S])(implicit executor: ExecutionContext): Future[S] = this + override final def flatten[S](implicit ev: Nothing <:< Future[S]): Future[S] = this + override final def filter(p: Nothing => Boolean)(implicit executor: ExecutionContext): Future[Nothing] = this + override final def collect[S](pf: PartialFunction[Nothing, S])(implicit executor: ExecutionContext): Future[S] = this + override final def recover[U >: Nothing](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U] = this + override final def recoverWith[U >: Nothing](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U] = this + override final def zip[U](that: Future[U]): Future[(Nothing, U)] = this + override final def zipWith[U, R](that: Future[U])(f: (Nothing, U) => R)(implicit executor: ExecutionContext): Future[R] = this + override final def fallbackTo[U >: Nothing](that: Future[U]): Future[U] = this + override final def mapTo[S](implicit tag: ClassTag[S]): Future[S] = this + override final def andThen[U](pf: PartialFunction[Try[Nothing], U])(implicit executor: ExecutionContext): Future[Nothing] = this + override final def toString: String = "Future()" + } + + /** A Future which is completed with the Unit value. + */ + final val unit: Future[Unit] = fromTry(Success(())) + + /** Creates an already completed Future with the specified exception. + * + * @tparam T the type of the value in the future + * @param exception the non-null instance of `Throwable` + * @return the newly created `Future` instance + */ + final def failed[T](exception: Throwable): Future[T] = Promise.failed(exception).future + + /** Creates an already completed Future with the specified result. + * + * @tparam T the type of the value in the future + * @param result the given successful value + * @return the newly created `Future` instance + */ + final def successful[T](result: T): Future[T] = Promise.successful(result).future + + /** Creates an already completed Future with the specified result or exception. + * + * @tparam T the type of the value in the `Future` + * @param result the result of the returned `Future` instance + * @return the newly created `Future` instance + */ + final def fromTry[T](result: Try[T]): Future[T] = Promise.fromTry(result).future + + /** Starts an asynchronous computation and returns a `Future` instance with the result of that computation. + * + * The following expressions are equivalent: + * + * {{{ + * val f1 = Future(expr) + * val f2 = Future.unit.map(_ => expr) + * val f3 = Future.unit.transform(_ => Success(expr)) + * }}} + * + * The result becomes available once the asynchronous computation is completed. + * + * @tparam T the type of the result + * @param body the asynchronous computation + * @param executor the execution context on which the future is run + * @return the `Future` holding the result of the computation + */ + final def apply[T](body: => T)(implicit executor: ExecutionContext): Future[T] = + unit.map(_ => body) + + /** Starts an asynchronous computation and returns a `Future` instance with the result of that computation once it completes. + * + * The following expressions are semantically equivalent: + * + * {{{ + * val f1 = Future(expr).flatten + * val f2 = Future.delegate(expr) + * val f3 = Future.unit.flatMap(_ => expr) + * }}} + * + * The result becomes available once the resulting Future of the asynchronous computation is completed. + * + * @tparam T the type of the result + * @param body the asynchronous computation, returning a Future + * @param executor the execution context on which the `body` is evaluated in + * @return the `Future` holding the result of the computation + */ + final def delegate[T](body: => Future[T])(implicit executor: ExecutionContext): Future[T] = + unit.flatMap(_ => body) + + /** Simple version of `Future.traverse`. Asynchronously and non-blockingly transforms, in essence, a `IterableOnce[Future[A]]` + * into a `Future[IterableOnce[A]]`. Useful for reducing many `Future`s into a single `Future`. + * + * @tparam A the type of the value inside the Futures + * @tparam CC the type of the `IterableOnce` of Futures + * @tparam To the type of the resulting collection + * @param in the `IterableOnce` of Futures which will be sequenced + * @return the `Future` of the resulting collection + */ + final def sequence[A, CC[X] <: IterableOnce[X], To](in: CC[Future[A]])(implicit bf: BuildFrom[CC[Future[A]], A, To], executor: ExecutionContext): Future[To] = + in.iterator.foldLeft(successful(bf.newBuilder(in))) { + (fr, fa) => fr.zipWith(fa)(Future.addToBuilderFun) + }.map(_.result())(if (executor.isInstanceOf[BatchingExecutor]) executor else parasitic) + + /** Asynchronously and non-blockingly returns a new `Future` to the result of the first future + * in the list that is completed. This means no matter if it is completed as a success or as a failure. + * + * @tparam T the type of the value in the future + * @param futures the `IterableOnce` of Futures in which to find the first completed + * @return the `Future` holding the result of the future that is first to be completed + */ + final def firstCompletedOf[T](futures: IterableOnce[Future[T]])(implicit executor: ExecutionContext): Future[T] = { + val i = futures.iterator + if (!i.hasNext) Future.never + else { + val p = Promise[T]() + val firstCompleteHandler = new AtomicReference(List.empty[() => Unit]) with (Try[T] => Unit) { + final def apply(res: Try[T]): Unit = { + val deregs = getAndSet(null) + if (deregs != null) { + p.tryComplete(res) // tryComplete is likely to be cheaper than complete + deregs.foreach(_.apply()) + } + } + } + var completed = false + while (i.hasNext && !completed) { + val deregs = firstCompleteHandler.get + if (deregs == null) completed = true + else i.next() match { + case dp: DefaultPromise[T @unchecked] => + val d = dp.onCompleteWithUnregister(firstCompleteHandler) + if (!firstCompleteHandler.compareAndSet(deregs, d :: deregs)) + d.apply() + case f => + f.onComplete(firstCompleteHandler) + } + } + p.future + } + } + + /** Asynchronously and non-blockingly returns a `Future` that will hold the optional result + * of the first `Future` with a result that matches the predicate, failed `Future`s will be ignored. + * + * @tparam T the type of the value in the future + * @param futures the `scala.collection.immutable.Iterable` of Futures to search + * @param p the predicate which indicates if it's a match + * @return the `Future` holding the optional result of the search + */ + final def find[T](futures: scala.collection.immutable.Iterable[Future[T]])(p: T => Boolean)(implicit executor: ExecutionContext): Future[Option[T]] = { + def searchNext(i: Iterator[Future[T]]): Future[Option[T]] = + if (!i.hasNext) successful(None) + else i.next().transformWith { + case Success(r) if p(r) => successful(Some(r)) + case _ => searchNext(i) + } + + searchNext(futures.iterator) + } + + /** A non-blocking, asynchronous left fold over the specified futures, + * with the start value of the given zero. + * The fold is performed asynchronously in left-to-right order as the futures become completed. + * The result will be the first failure of any of the futures, or any failure in the actual fold, + * or the result of the fold. + * + * Example: + * {{{ + * val futureSum = Future.foldLeft(futures)(0)(_ + _) + * }}} + * + * @tparam T the type of the value of the input Futures + * @tparam R the type of the value of the returned `Future` + * @param futures the `scala.collection.immutable.Iterable` of Futures to be folded + * @param zero the start value of the fold + * @param op the fold operation to be applied to the zero and futures + * @return the `Future` holding the result of the fold + */ + final def foldLeft[T, R](futures: scala.collection.immutable.Iterable[Future[T]])(zero: R)(op: (R, T) => R)(implicit executor: ExecutionContext): Future[R] = + foldNext(futures.iterator, zero, op) + + private[this] final def foldNext[T, R](i: Iterator[Future[T]], prevValue: R, op: (R, T) => R)(implicit executor: ExecutionContext): Future[R] = + if (!i.hasNext) successful(prevValue) + else i.next().flatMap { value => foldNext(i, op(prevValue, value), op) } + + /** A non-blocking, asynchronous fold over the specified futures, with the start value of the given zero. + * The fold is performed on the thread where the last future is completed, + * the result will be the first failure of any of the futures, or any failure in the actual fold, + * or the result of the fold. + * + * Example: + * {{{ + * val futureSum = Future.fold(futures)(0)(_ + _) + * }}} + * + * @tparam T the type of the value of the input Futures + * @tparam R the type of the value of the returned `Future` + * @param futures the `IterableOnce` of Futures to be folded + * @param zero the start value of the fold + * @param op the fold operation to be applied to the zero and futures + * @return the `Future` holding the result of the fold + */ + @deprecated("use Future.foldLeft instead", "2.12.0") + // not removed in 2.13, to facilitate 2.11/2.12/2.13 cross-building; remove further down the line (see scala/scala#6319) + def fold[T, R](futures: IterableOnce[Future[T]])(zero: R)(@deprecatedName("foldFun") op: (R, T) => R)(implicit executor: ExecutionContext): Future[R] = + if (futures.isEmpty) successful(zero) + else sequence(futures)(ArrayBuffer, executor).map(_.foldLeft(zero)(op)) + + /** Initiates a non-blocking, asynchronous, fold over the supplied futures + * where the fold-zero is the result value of the first `Future` in the collection. + * + * Example: + * {{{ + * val futureSum = Future.reduce(futures)(_ + _) + * }}} + * @tparam T the type of the value of the input Futures + * @tparam R the type of the value of the returned `Future` + * @param futures the `IterableOnce` of Futures to be reduced + * @param op the reduce operation which is applied to the results of the futures + * @return the `Future` holding the result of the reduce + */ + @deprecated("use Future.reduceLeft instead", "2.12.0") + // not removed in 2.13, to facilitate 2.11/2.12/2.13 cross-building; remove further down the line (see scala/scala#6319) + final def reduce[T, R >: T](futures: IterableOnce[Future[T]])(op: (R, T) => R)(implicit executor: ExecutionContext): Future[R] = + if (futures.isEmpty) failed(new NoSuchElementException("reduce attempted on empty collection")) + else sequence(futures)(ArrayBuffer, executor).map(_ reduceLeft op) + + /** Initiates a non-blocking, asynchronous, left reduction over the supplied futures + * where the zero is the result value of the first `Future`. + * + * Example: + * {{{ + * val futureSum = Future.reduceLeft(futures)(_ + _) + * }}} + * @tparam T the type of the value of the input Futures + * @tparam R the type of the value of the returned `Future` + * @param futures the `scala.collection.immutable.Iterable` of Futures to be reduced + * @param op the reduce operation which is applied to the results of the futures + * @return the `Future` holding the result of the reduce + */ + final def reduceLeft[T, R >: T](futures: scala.collection.immutable.Iterable[Future[T]])(op: (R, T) => R)(implicit executor: ExecutionContext): Future[R] = { + val i = futures.iterator + if (!i.hasNext) failed(new NoSuchElementException("reduceLeft attempted on empty collection")) + else i.next() flatMap { v => foldNext(i, v, op) } + } + + /** Asynchronously and non-blockingly transforms a `IterableOnce[A]` into a `Future[IterableOnce[B]]` + * using the provided function `A => Future[B]`. + * This is useful for performing a parallel map. For example, to apply a function to all items of a list + * in parallel: + * + * {{{ + * val myFutureList = Future.traverse(myList)(x => Future(myFunc(x))) + * }}} + * @tparam A the type of the value inside the Futures in the collection + * @tparam B the type of the value of the returned `Future` + * @tparam M the type of the collection of Futures + * @param in the collection to be mapped over with the provided function to produce a collection of Futures that is then sequenced into a Future collection + * @param fn the function to be mapped over the collection to produce a collection of Futures + * @return the `Future` of the collection of results + */ + final def traverse[A, B, M[X] <: IterableOnce[X]](in: M[A])(fn: A => Future[B])(implicit bf: BuildFrom[M[A], B, M[B]], executor: ExecutionContext): Future[M[B]] = + in.iterator.foldLeft(successful(bf.newBuilder(in))) { + (fr, a) => fr.zipWith(fn(a))(Future.addToBuilderFun) + }.map(_.result())(if (executor.isInstanceOf[BatchingExecutor]) executor else parasitic) +} + +@deprecated("Superseded by `scala.concurrent.Batchable`", "2.13.0") +trait OnCompleteRunnable extends Batchable { + self: Runnable => +} + diff --git a/library/src/scala/concurrent/JavaConversions.scala b/library/src/scala/concurrent/JavaConversions.scala new file mode 100644 index 000000000000..eab15c2610b9 --- /dev/null +++ b/library/src/scala/concurrent/JavaConversions.scala @@ -0,0 +1,39 @@ +/* + * 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.concurrent + +import scala.language.`2.13` +import java.util.concurrent.{ExecutorService, Executor} +import scala.language.implicitConversions + +/** The `JavaConversions` object provides implicit conversions supporting + * interoperability between Scala and Java concurrency classes. + */ +@deprecated("Use the factory methods in `ExecutionContext` instead", "2.13.0") +object JavaConversions { + + /** + * Creates a new `ExecutionContext` which uses the provided `ExecutorService`. + */ + @deprecated("Use `ExecutionContext.fromExecutorService` instead", "2.13.0") + implicit def asExecutionContext(exec: ExecutorService): ExecutionContextExecutorService = + ExecutionContext.fromExecutorService(exec) + + /** + * Creates a new `ExecutionContext` which uses the provided `Executor`. + */ + @deprecated("Use `ExecutionContext.fromExecutor` instead", "2.13.0") + implicit def asExecutionContext(exec: Executor): ExecutionContextExecutor = + ExecutionContext.fromExecutor(exec) + +} diff --git a/library/src/scala/concurrent/Promise.scala b/library/src/scala/concurrent/Promise.scala new file mode 100644 index 000000000000..3c82b9e5920f --- /dev/null +++ b/library/src/scala/concurrent/Promise.scala @@ -0,0 +1,149 @@ +/* + * 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.concurrent + +import scala.language.`2.13` +import scala.util.{ Try, Success, Failure } + +/** Promise is an object which can be completed with a value or failed + * with an exception. + * + * A promise should always eventually be completed, whether for success or failure, + * in order to avoid unintended resource retention for any associated Futures' + * callbacks or transformations. + * + * @define promiseCompletion + * If the promise has already been fulfilled, failed or has timed out, + * calling this method will throw an IllegalStateException. + * + * @define allowedThrowables + * If the throwable used to fail this promise is an error, a control exception + * or an interrupted exception, it will be wrapped as a cause within an + * `ExecutionException` which will fail the promise. + * + * @define nonDeterministic + * Note: Using this method may result in non-deterministic concurrent programs. + */ +trait Promise[T] { + /** Future containing the value of this promise. + */ + def future: Future[T] + + /** Returns whether the promise has already been completed with + * a value or an exception. + * + * $nonDeterministic + * + * @return `true` if the promise is already completed, `false` otherwise + */ + def isCompleted: Boolean + + /** Completes the promise with either an exception or a value. + * + * @param result Either the value or the exception to complete the promise with. + * + * $promiseCompletion + */ + def complete(result: Try[T]): this.type = + if (tryComplete(result)) this else throw new IllegalStateException("Promise already completed.") + + /** Tries to complete the promise with either a value or the exception. + * + * $nonDeterministic + * + * @return If the promise has already been completed returns `false`, or `true` otherwise. + */ + def tryComplete(result: Try[T]): Boolean + + /** Completes this promise with the specified future, once that future is completed. + * + * @return This promise + */ + def completeWith(other: Future[T]): this.type = { + if (other ne this.future) // this tryCompleteWith this doesn't make much sense + other.onComplete(this tryComplete _)(ExecutionContext.parasitic) + + this + } + + /** Attempts to complete this promise with the specified future, once that future is completed. + * + * @return This promise + */ + @deprecated("Since this method is semantically equivalent to `completeWith`, use that instead.", "2.13.0") + final def tryCompleteWith(other: Future[T]): this.type = completeWith(other) + + /** Completes the promise with a value. + * + * @param value The value to complete the promise with. + * + * $promiseCompletion + */ + def success(value: T): this.type = complete(Success(value)) + + /** Tries to complete the promise with a value. + * + * $nonDeterministic + * + * @return If the promise has already been completed returns `false`, or `true` otherwise. + */ + def trySuccess(value: T): Boolean = tryComplete(Success(value)) + + /** Completes the promise with an exception. + * + * @param cause The throwable to complete the promise with. + * + * $allowedThrowables + * + * $promiseCompletion + */ + def failure(cause: Throwable): this.type = complete(Failure(cause)) + + /** Tries to complete the promise with an exception. + * + * $nonDeterministic + * + * @return If the promise has already been completed returns `false`, or `true` otherwise. + */ + def tryFailure(cause: Throwable): Boolean = tryComplete(Failure(cause)) +} + +object Promise { + /** Creates a promise object which can be completed with a value. + * + * @tparam T the type of the value in the promise + * @return the newly created `Promise` instance + */ + final def apply[T](): Promise[T] = new impl.Promise.DefaultPromise[T]() + + /** Creates an already completed Promise with the specified exception. + * + * @tparam T the type of the value in the promise + * @return the newly created `Promise` instance + */ + final def failed[T](exception: Throwable): Promise[T] = fromTry(Failure(exception)) + + /** Creates an already completed Promise with the specified result. + * + * @tparam T the type of the value in the promise + * @return the newly created `Promise` instance + */ + final def successful[T](result: T): Promise[T] = fromTry(Success(result)) + + /** Creates an already completed Promise with the specified result or exception. + * + * @tparam T the type of the value in the promise + * @return the newly created `Promise` instance + */ + final def fromTry[T](result: Try[T]): Promise[T] = new impl.Promise.DefaultPromise[T](result) +} diff --git a/library/src/scala/concurrent/SyncChannel.scala b/library/src/scala/concurrent/SyncChannel.scala new file mode 100644 index 000000000000..53fe27deb4fd --- /dev/null +++ b/library/src/scala/concurrent/SyncChannel.scala @@ -0,0 +1,79 @@ +/* + * 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.concurrent + +import scala.language.`2.13` + +/** A `SyncChannel` allows one to exchange data synchronously between + * a reader and a writer thread. The writer thread is blocked until the + * data to be written has been read by a corresponding reader thread. + */ +@deprecated("Use `java.util.concurrent.Exchanger` instead.", since = "2.13.0") +class SyncChannel[A] { + + private final val Signal = () + private type Signal = Unit + private[this] var pendingWrites = List[(A, SyncVar[Signal])]() + private[this] var pendingReads = List[SyncVar[A]]() + + def write(data: A): Unit = { + // create write request + val writeReq = new SyncVar[Signal] + + this.synchronized { + // check whether there is a reader waiting + if (pendingReads.nonEmpty) { + val readReq = pendingReads.head + pendingReads = pendingReads.tail + + // let reader continue + readReq.put(data) + + // resolve write request + writeReq.put(Signal) + } + else { + // enqueue write request + pendingWrites = pendingWrites ::: List((data, writeReq)) + } + } + + writeReq.get + } + + def read: A = { + // create read request + val readReq = new SyncVar[A] + + this.synchronized { + // check whether there is a writer waiting + if (pendingWrites.nonEmpty) { + // read data + val (data, writeReq) = pendingWrites.head + pendingWrites = pendingWrites.tail + + // let writer continue + writeReq.put(Signal) + + // resolve read request + readReq.put(data) + } + else { + // enqueue read request + pendingReads = pendingReads ::: List(readReq) + } + } + + readReq.get + } +} diff --git a/library/src/scala/concurrent/SyncVar.scala b/library/src/scala/concurrent/SyncVar.scala new file mode 100644 index 000000000000..ccb7d19f3bc7 --- /dev/null +++ b/library/src/scala/concurrent/SyncVar.scala @@ -0,0 +1,123 @@ +/* + * 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.concurrent + +import scala.language.`2.13` +import java.util.concurrent.TimeUnit + +/** A class to provide safe concurrent access to a mutable cell. + * All methods are synchronized. + * + * @tparam A type of the contained value + */ +@deprecated("Use `java.util.concurrent.LinkedBlockingQueue with capacity 1` instead.", since = "2.13.0") +class SyncVar[A] { + private[this] var isDefined: Boolean = false + private[this] var value: A = _ + + /** + * Wait for this SyncVar to become defined and then get + * the stored value without modifying it. + * + * @return value that is held in this container + */ + def get: A = synchronized { + while (!isDefined) wait() + value + } + + /** Waits `timeout` millis. If `timeout <= 0` just returns 0. + * It never returns negative results. + */ + private def waitMeasuringElapsed(timeout: Long): Long = if (timeout <= 0) 0 else { + val start = System.nanoTime() + wait(timeout) + val elapsed = System.nanoTime() - start + // nanoTime should be monotonic, but it's not possible to rely on that. + // See https://bugs.java.com/view_bug.do?bug_id=6458294 + if (elapsed < 0) 0 else TimeUnit.NANOSECONDS.toMillis(elapsed) + } + + /** Wait at least `timeout` milliseconds (possibly more) for this `SyncVar` + * to become defined and then get its value. + * + * @param timeout time in milliseconds to wait + * @return `None` if variable is undefined after `timeout`, `Some(value)` otherwise + */ + def get(timeout: Long): Option[A] = synchronized { + /* Defending against the system clock going backward + * by counting time elapsed directly. Loop required + * to deal with spurious wakeups. + */ + var rest = timeout + while (!isDefined && rest > 0) { + val elapsed = waitMeasuringElapsed(rest) + rest -= elapsed + } + if (isDefined) Some(value) else None + } + + /** + * Wait for this SyncVar to become defined and then get + * the stored value, unsetting it as a side effect. + * + * @return value that was held in this container + */ + def take(): A = synchronized { + try get + finally unsetVal() + } + + /** Wait at least `timeout` milliseconds (possibly more) for this `SyncVar` + * to become defined and then get the stored value, unsetting it + * as a side effect. + * + * @param timeout the amount of milliseconds to wait + * @return the value or a throws an exception if the timeout occurs + * @throws NoSuchElementException on timeout + */ + def take(timeout: Long): A = synchronized { + try get(timeout).get + finally unsetVal() + } + + /** Place a value in the SyncVar. If the SyncVar already has a stored value, + * wait until another thread takes it. */ + def put(x: A): Unit = synchronized { + while (isDefined) wait() + setVal(x) + } + + /** Check whether a value is stored in the synchronized variable. */ + def isSet: Boolean = synchronized { + isDefined + } + + // `setVal` exists so as to retroactively deprecate `set` without + // deprecation warnings where we use `set` internally. The + // implementation of `set` was moved to `setVal` to achieve this + private def setVal(x: A): Unit = synchronized { + isDefined = true + value = x + notifyAll() + } + + // `unsetVal` exists so as to retroactively deprecate `unset` without + // deprecation warnings where we use `unset` internally. The + // implementation of `unset` was moved to `unsetVal` to achieve this + private def unsetVal(): Unit = synchronized { + isDefined = false + value = null.asInstanceOf[A] + notifyAll() + } +} diff --git a/library/src/scala/concurrent/duration/Deadline.scala b/library/src/scala/concurrent/duration/Deadline.scala new file mode 100644 index 000000000000..9270083b351c --- /dev/null +++ b/library/src/scala/concurrent/duration/Deadline.scala @@ -0,0 +1,87 @@ +/* + * 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.concurrent.duration + +import scala.language.`2.13` + +/** + * This class stores a deadline, as obtained via `Deadline.now` or the + * duration DSL: + * + * {{{ + * import scala.concurrent.duration._ + * 3.seconds.fromNow + * }}} + * + * Its main purpose is to manage repeated attempts to achieve something (like + * awaiting a condition) by offering the methods `hasTimeLeft` and `timeLeft`. All + * durations are measured according to `System.nanoTime`; this + * does not take into account changes to the system clock (such as leap + * seconds). + */ +case class Deadline private (time: FiniteDuration) extends Ordered[Deadline] { + /** + * Return a deadline advanced (i.e., moved into the future) by the given duration. + */ + def +(other: FiniteDuration): Deadline = copy(time = time + other) + /** + * Return a deadline moved backwards (i.e., towards the past) by the given duration. + */ + def -(other: FiniteDuration): Deadline = copy(time = time - other) + /** + * Calculate time difference between this and the other deadline, where the result is directed (i.e., may be negative). + */ + def -(other: Deadline): FiniteDuration = time - other.time + /** + * Calculate time difference between this duration and now; the result is negative if the deadline has passed. + * + * '''''Note that on some systems this operation is costly because it entails a system call.''''' + * Check `System.nanoTime` for your platform. + */ + def timeLeft: FiniteDuration = this - Deadline.now + /** + * Determine whether the deadline still lies in the future at the point where this method is called. + * + * '''''Note that on some systems this operation is costly because it entails a system call.''''' + * Check `System.nanoTime` for your platform. + */ + def hasTimeLeft(): Boolean = !isOverdue() + /** + * Determine whether the deadline lies in the past at the point where this method is called. + * + * '''''Note that on some systems this operation is costly because it entails a system call.''''' + * Check `System.nanoTime` for your platform. + */ + def isOverdue(): Boolean = (time.toNanos - System.nanoTime()) < 0 + /** + * The natural ordering for deadline is determined by the natural order of the underlying (finite) duration. + */ + def compare(other: Deadline): Int = time compare other.time +} + +object Deadline { + /** + * Construct a deadline due exactly at the point where this method is called. Useful for then + * advancing it to obtain a future deadline, or for sampling the current time exactly once and + * then comparing it to multiple deadlines (using subtraction). + */ + def now: Deadline = Deadline(Duration(System.nanoTime, NANOSECONDS)) + + /** + * The natural ordering for deadline is determined by the natural order of the underlying (finite) duration. + */ + implicit object DeadlineIsOrdered extends Ordering[Deadline] { + def compare(a: Deadline, b: Deadline): Int = a compare b + } + +} diff --git a/library/src/scala/concurrent/duration/Duration.scala b/library/src/scala/concurrent/duration/Duration.scala new file mode 100644 index 000000000000..4fa495c8989c --- /dev/null +++ b/library/src/scala/concurrent/duration/Duration.scala @@ -0,0 +1,743 @@ +/* + * 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.concurrent.duration + +import scala.language.`2.13` +import java.lang.{ Double => JDouble } +import scala.collection.StringParsers + +object Duration { + + /** + * Construct a Duration from the given length and unit. Observe that nanosecond precision may be lost if + * + * - the unit is NANOSECONDS + * - and the length has an absolute value greater than `2^53` + * + * Infinite inputs (and NaN) are converted into [[Duration.Inf]], [[Duration.MinusInf]] and [[Duration.Undefined]], respectively. + * + * @throws IllegalArgumentException if the length was finite but the resulting duration cannot be expressed as a [[FiniteDuration]] + */ + def apply(length: Double, unit: TimeUnit): Duration = fromNanos(unit.toNanos(1) * length) + + /** + * Construct a finite duration from the given length and time unit. The unit given is retained + * throughout calculations as long as possible, so that it can be retrieved later. + */ + def apply(length: Long, unit: TimeUnit): FiniteDuration = new FiniteDuration(length, unit) + + /** + * Construct a finite duration from the given length and time unit, where the latter is + * looked up in a list of string representation. Valid choices are: + * + * `d, day, h, hr, hour, m, min, minute, s, sec, second, ms, milli, millisecond, µs, micro, microsecond, ns, nano, nanosecond` + * and their pluralized forms (for every but the first mentioned form of each unit, i.e. no "ds", but "days"). + */ + def apply(length: Long, unit: String): FiniteDuration = new FiniteDuration(length, Duration.timeUnit(unit)) + + // Double stores 52 bits mantissa, but there is an implied '1' in front, making the limit 2^53 + // private[this] final val maxPreciseDouble = 9007199254740992d // not used after https://github.com/scala/scala/pull/9233 + + /** + * Parse String into Duration. Format is `""`, where + * whitespace is allowed before, between and after the parts. Infinities are + * designated by `"Inf"`, `"PlusInf"`, `"+Inf"`, `"Duration.Inf"` and `"-Inf"`, `"MinusInf"` or `"Duration.MinusInf"`. + * Undefined is designated by `"Duration.Undefined"`. + * + * @throws NumberFormatException if format is not parsable + */ + def apply(s: String): Duration = { + val s1: String = s filterNot (_.isWhitespace) + s1 match { + case "Inf" | "PlusInf" | "+Inf" | "Duration.Inf" => Inf + case "MinusInf" | "-Inf" | "Duration.MinusInf" => MinusInf + case "Duration.Undefined" => Undefined + case _ => + val unitName = s1.reverse.takeWhile(_.isLetter).reverse + timeUnit get unitName match { + case Some(unit) => + val valueStr = s1 dropRight unitName.length + StringParsers.parseLong(valueStr).map(Duration(_, unit)) + .getOrElse(Duration(JDouble.parseDouble(valueStr), unit)) + case _ => throw new NumberFormatException("format error " + s) + } + } + } + + // "ms milli millisecond" -> List("ms", "milli", "millis", "millisecond", "milliseconds") + private[this] def words(s: String) = (s.trim split "\\s+").toList + private[this] def expandLabels(labels: String): List[String] = { + val hd :: rest = words(labels): @unchecked + hd :: rest.flatMap(s => List(s, s + "s")) + } + private[this] val timeUnitLabels = List( + DAYS -> "d day", + HOURS -> "h hr hour", + MINUTES -> "m min minute", + SECONDS -> "s sec second", + MILLISECONDS -> "ms milli millisecond", + MICROSECONDS -> "µs micro microsecond", + NANOSECONDS -> "ns nano nanosecond" + ) + + // TimeUnit => standard label + protected[duration] val timeUnitName: Map[TimeUnit, String] = + timeUnitLabels.toMap.view.mapValues(s => words(s).last).toMap + + // Label => TimeUnit + protected[duration] val timeUnit: Map[String, TimeUnit] = + timeUnitLabels.flatMap{ case (unit, names) => expandLabels(names) map (_ -> unit) }.toMap + + /** + * Extract length and time unit out of a string, where the format must match the description for [[Duration$.apply(s:String)* apply(String)]]. + * The extractor will not match for malformed strings or non-finite durations. + */ + def unapply(s: String): Option[(Long, TimeUnit)] = + ( try Some(apply(s)) catch { case _: RuntimeException => None } ) flatMap unapply + + /** + * Extract length and time unit out of a duration, if it is finite. + */ + def unapply(d: Duration): Option[(Long, TimeUnit)] = + if (d.isFinite) Some((d.length, d.unit)) else None + + /** + * Construct a possibly infinite or undefined Duration from the given number of nanoseconds. + * + * - `Double.PositiveInfinity` is mapped to [[Duration.Inf]] + * - `Double.NegativeInfinity` is mapped to [[Duration.MinusInf]] + * - `Double.NaN` is mapped to [[Duration.Undefined]] + * - `-0d` is mapped to [[Duration.Zero]] (exactly like `0d`) + * + * The semantics of the resulting Duration objects matches the semantics of their Double + * counterparts with respect to arithmetic operations. + * + * @throws IllegalArgumentException if the length was finite but the resulting duration cannot be expressed as a [[FiniteDuration]] + */ + def fromNanos(nanos: Double): Duration = { + if (nanos.isInfinite) + if (nanos > 0) Inf else MinusInf + else if (JDouble.isNaN(nanos)) + Undefined + else if (nanos > Long.MaxValue || nanos < Long.MinValue) + throw new IllegalArgumentException("trying to construct too large duration with " + nanos + "ns") + else + fromNanos(nanos.round) + } + + private[this] final val ns_per_µs = 1000L + private[this] final val ns_per_ms = ns_per_µs * 1000 + private[this] final val ns_per_s = ns_per_ms * 1000 + private[this] final val ns_per_min = ns_per_s * 60 + private[this] final val ns_per_h = ns_per_min * 60 + private[this] final val ns_per_d = ns_per_h * 24 + + /** + * Construct a finite duration from the given number of nanoseconds. The + * result will have the coarsest possible time unit which can exactly express + * this duration. + * + * @throws IllegalArgumentException for `Long.MinValue` since that would lead to inconsistent behavior afterwards (cannot be negated) + */ + def fromNanos(nanos: Long): FiniteDuration = { + if (nanos % ns_per_d == 0) Duration(nanos / ns_per_d , DAYS) + else if (nanos % ns_per_h == 0) Duration(nanos / ns_per_h , HOURS) + else if (nanos % ns_per_min == 0) Duration(nanos / ns_per_min, MINUTES) + else if (nanos % ns_per_s == 0) Duration(nanos / ns_per_s , SECONDS) + else if (nanos % ns_per_ms == 0) Duration(nanos / ns_per_ms , MILLISECONDS) + else if (nanos % ns_per_µs == 0) Duration(nanos / ns_per_µs , MICROSECONDS) + else Duration(nanos, NANOSECONDS) + } + + /** + * Preconstructed value of `0.days`. + */ + // unit as coarse as possible to keep (_ + Zero) sane unit-wise + val Zero: FiniteDuration = new FiniteDuration(0, DAYS) + + /** + * The Undefined value corresponds closely to Double.NaN: + * + * - it is the result of otherwise invalid operations + * - it does not equal itself (according to `equals()`) + * - it compares greater than any other Duration apart from itself (for which `compare` returns 0) + * + * The particular comparison semantics mirror those of Double.NaN. + * + * '''''Use [[eq]] when checking an input of a method against this value.''''' + */ + val Undefined: Infinite = new Infinite { + override def toString = "Duration.Undefined" + override def equals(other: Any): Boolean = false + override def +(other: Duration): Duration = this + override def -(other: Duration): Duration = this + override def *(factor: Double): Duration = this + override def /(factor: Double): Duration = this + override def /(other: Duration): Double = Double.NaN + def compare(other: Duration): Int = if (other eq this) 0 else 1 + def unary_- : Duration = this + def toUnit(unit: TimeUnit): Double = Double.NaN + private def readResolve(): AnyRef = Undefined // Instructs deserialization to use this same instance + } + + sealed abstract class Infinite extends Duration { + def +(other: Duration): Duration = other match { + case x if x eq Undefined => Undefined + case x: Infinite if x ne this => Undefined + case _ => this + } + def -(other: Duration): Duration = other match { + case x if x eq Undefined => Undefined + case x: Infinite if x eq this => Undefined + case _ => this + } + + def *(factor: Double): Duration = + if (factor == 0d || JDouble.isNaN(factor)) Undefined + else if (factor < 0d) -this + else this + def /(divisor: Double): Duration = + if (JDouble.isNaN(divisor) || divisor.isInfinite) Undefined + else if ((divisor compare 0d) < 0) -this + else this + def /(divisor: Duration): Double = divisor match { + case _: Infinite => Double.NaN + case x => Double.PositiveInfinity * (if ((this > Zero) ^ (divisor >= Zero)) -1 else 1) + } + + final def isFinite = false + + private[this] def fail(what: String) = throw new IllegalArgumentException(s"$what not allowed on infinite Durations") + final def length: Long = fail("length") + final def unit: TimeUnit = fail("unit") + final def toNanos: Long = fail("toNanos") + final def toMicros: Long = fail("toMicros") + final def toMillis: Long = fail("toMillis") + final def toSeconds: Long = fail("toSeconds") + final def toMinutes: Long = fail("toMinutes") + final def toHours: Long = fail("toHours") + final def toDays: Long = fail("toDays") + + final def toCoarsest: Duration = this + } + + /** + * Infinite duration: greater than any other (apart from Undefined) and not equal to any other + * but itself. This value closely corresponds to Double.PositiveInfinity, + * matching its semantics in arithmetic operations. + */ + val Inf: Infinite = new Infinite { + override def toString: String = "Duration.Inf" + def compare(other: Duration): Int = other match { + case x if x eq Undefined => -1 // Undefined != Undefined + case x if x eq this => 0 // `case Inf` will include null checks in the byte code + case _ => 1 + } + def unary_- : Duration = MinusInf + def toUnit(unit: TimeUnit): Double = Double.PositiveInfinity + private def readResolve(): AnyRef = Inf // Instructs deserialization to use this same instance + } + + /** + * Infinite duration: less than any other and not equal to any other + * but itself. This value closely corresponds to Double.NegativeInfinity, + * matching its semantics in arithmetic operations. + */ + val MinusInf: Infinite = new Infinite { + override def toString: String = "Duration.MinusInf" + def compare(other: Duration): Int = if (other eq this) 0 else -1 + def unary_- : Duration = Inf + def toUnit(unit: TimeUnit): Double = Double.NegativeInfinity + private def readResolve(): AnyRef = MinusInf // Instructs deserialization to use this same instance + } + + // Java Factories + + /** + * Construct a finite duration from the given length and time unit. The unit given is retained + * throughout calculations as long as possible, so that it can be retrieved later. + */ + def create(length: Long, unit: TimeUnit): FiniteDuration = apply(length, unit) + /** + * Construct a Duration from the given length and unit. Observe that nanosecond precision may be lost if + * + * - the unit is NANOSECONDS + * - and the length has an absolute value greater than `2^53` + * + * Infinite inputs (and NaN) are converted into [[Duration.Inf]], [[Duration.MinusInf]] and [[Duration.Undefined]], respectively. + * + * @throws IllegalArgumentException if the length was finite but the resulting duration cannot be expressed as a [[FiniteDuration]] + */ + def create(length: Double, unit: TimeUnit): Duration = apply(length, unit) + /** + * Construct a finite duration from the given length and time unit, where the latter is + * looked up in a list of string representation. Valid choices are: + * + * `d, day, h, hour, min, minute, s, sec, second, ms, milli, millisecond, µs, micro, microsecond, ns, nano, nanosecond` + * and their pluralized forms (for every but the first mentioned form of each unit, i.e. no "ds", but "days"). + */ + def create(length: Long, unit: String): FiniteDuration = apply(length, unit) + /** + * Parse String into Duration. Format is `""`, where + * whitespace is allowed before, between and after the parts. Infinities are + * designated by `"Inf"`, `"PlusInf"`, `"+Inf"` and `"-Inf"` or `"MinusInf"`. + * + * @throws NumberFormatException if format is not parsable + */ + def create(s: String): Duration = apply(s) + + /** + * The natural ordering of durations matches the natural ordering for Double, including non-finite values. + */ + implicit object DurationIsOrdered extends Ordering[Duration] { + def compare(a: Duration, b: Duration): Int = a compare b + } +} + +/** + *

Utility for working with java.util.concurrent.TimeUnit durations.

+ * + * '''''This class is not meant as a general purpose representation of time, it is + * optimized for the needs of `scala.concurrent`.''''' + * + *

Basic Usage

+ * + *

+ * Examples: + * {{{ + * import scala.concurrent.duration._ + * + * val duration = Duration(100, MILLISECONDS) + * val duration = Duration(100, "millis") + * + * duration.toNanos + * duration < 1.second + * duration <= Duration.Inf + * }}} + * + * '''''Invoking inexpressible conversions (like calling `toSeconds` on an infinite duration) will throw an IllegalArgumentException.''''' + * + *

+ * Implicits are also provided for Int, Long and Double. Example usage: + * {{{ + * import scala.concurrent.duration._ + * + * val duration = 100.millis + * }}} + * + * '''''The DSL provided by the implicit conversions always allows construction of finite durations, even for infinite Double inputs; use Duration.Inf instead.''''' + * + * Extractors, parsing and arithmetic are also included: + * {{{ + * val d = Duration("1.2 µs") + * val Duration(length, unit) = 5 millis + * val d2 = d * 2.5 + * val d3 = d2 + 1.millisecond + * }}} + * + *

Handling of Time Units

+ * + * Calculations performed on finite durations always retain the more precise unit of either operand, no matter + * whether a coarser unit would be able to exactly express the same duration. This means that Duration can be + * used as a lossless container for a (length, unit) pair if it is constructed using the corresponding methods + * and no arithmetic is performed on it; adding/subtracting durations should in that case be done with care. + * + *

Correspondence to Double Semantics

+ * + * The semantics of arithmetic operations on Duration are two-fold: + * + * - exact addition/subtraction with nanosecond resolution for finite durations, independent of the summands' magnitude + * - isomorphic to `java.lang.Double` when it comes to infinite or undefined values + * + * The conversion between Duration and Double is done using [[Duration.toUnit]] (with unit NANOSECONDS) + * and [[Duration$.fromNanos(nanos:Double)* Duration.fromNanos(Double)]] + * + *

Ordering

+ * + * The default ordering is consistent with the ordering of Double numbers, which means that Undefined is + * considered greater than all other durations, including [[Duration.Inf]]. + * + * @define exc @throws IllegalArgumentException when invoked on a non-finite duration + * + * @define ovf @throws IllegalArgumentException in case of a finite overflow: the range of a finite duration is `+-(2^63-1)`ns, and no conversion to infinite durations takes place. + */ +sealed abstract class Duration extends Serializable with Ordered[Duration] { + /** + * Obtain the length of this Duration measured in the unit obtained by the `unit` method. + * + * $exc + */ + def length: Long + /** + * Obtain the time unit in which the length of this duration is measured. + * + * $exc + */ + def unit: TimeUnit + /** + * Return the length of this duration measured in whole nanoseconds, rounding towards zero. + * + * $exc + */ + def toNanos: Long + /** + * Return the length of this duration measured in whole microseconds, rounding towards zero. + * + * $exc + */ + def toMicros: Long + /** + * Return the length of this duration measured in whole milliseconds, rounding towards zero. + * + * $exc + */ + def toMillis: Long + /** + * Return the length of this duration measured in whole seconds, rounding towards zero. + * + * $exc + */ + def toSeconds: Long + /** + * Return the length of this duration measured in whole minutes, rounding towards zero. + * + * $exc + */ + def toMinutes: Long + /** + * Return the length of this duration measured in whole hours, rounding towards zero. + * + * $exc + */ + def toHours: Long + /** + * Return the length of this duration measured in whole days, rounding towards zero. + * + * $exc + */ + def toDays: Long + /** + * Return the number of nanoseconds as floating point number, scaled down to the given unit. + * The result may not precisely represent this duration due to the Double datatype's inherent + * limitations (mantissa size effectively 53 bits). Non-finite durations are represented as + * - [[Duration.Undefined]] is mapped to Double.NaN + * - [[Duration.Inf]] is mapped to Double.PositiveInfinity + * - [[Duration.MinusInf]] is mapped to Double.NegativeInfinity + */ + def toUnit(unit: TimeUnit): Double + + /** + * Return the sum of that duration and this. When involving non-finite summands the semantics match those + * of Double. + * + * $ovf + */ + def +(other: Duration): Duration + /** + * Return the difference of that duration and this. When involving non-finite summands the semantics match those + * of Double. + * + * $ovf + */ + def -(other: Duration): Duration + /** + * Return this duration multiplied by the scalar factor. When involving non-finite factors the semantics match those + * of Double. + * + * $ovf + */ + def *(factor: Double): Duration + /** + * Return this duration divided by the scalar factor. When involving non-finite factors the semantics match those + * of Double. + * + * $ovf + */ + def /(divisor: Double): Duration + /** + * Return the quotient of this and that duration as floating-point number. The semantics are + * determined by Double as if calculating the quotient of the nanosecond lengths of both factors. + */ + def /(divisor: Duration): Double + /** + * Negate this duration. The only two values which are mapped to themselves are [[Duration.Zero]] and [[Duration.Undefined]]. + */ + def unary_- : Duration + /** + * This method returns whether this duration is finite, which is not the same as + * `!isInfinite` for Double because this method also returns `false` for [[Duration.Undefined]]. + */ + def isFinite: Boolean + /** + * Return the smaller of this and that duration as determined by the natural ordering. + */ + def min(other: Duration): Duration = if (this < other) this else other + /** + * Return the larger of this and that duration as determined by the natural ordering. + */ + def max(other: Duration): Duration = if (this > other) this else other + + // Java API + + /** + * Return this duration divided by the scalar factor. When involving non-finite factors the semantics match those + * of Double. + * + * $ovf + */ + def div(divisor: Double): Duration = this / divisor + /** + * Return the quotient of this and that duration as floating-point number. The semantics are + * determined by Double as if calculating the quotient of the nanosecond lengths of both factors. + */ + def div(other: Duration): Double = this / other + def gt(other: Duration): Boolean = this > other + def gteq(other: Duration): Boolean = this >= other + def lt(other: Duration): Boolean = this < other + def lteq(other: Duration): Boolean = this <= other + /** + * Return the difference of that duration and this. When involving non-finite summands the semantics match those + * of Double. + * + * $ovf + */ + def minus(other: Duration): Duration = this - other + /** + * Return this duration multiplied by the scalar factor. When involving non-finite factors the semantics match those + * of Double. + * + * $ovf + */ + def mul(factor: Double): Duration = this * factor + /** + * Negate this duration. The only two values which are mapped to themselves are [[Duration.Zero]] and [[Duration.Undefined]]. + */ + def neg(): Duration = -this + /** + * Return the sum of that duration and this. When involving non-finite summands the semantics match those + * of Double. + * + * $ovf + */ + def plus(other: Duration): Duration = this + other + /** + * Return duration which is equal to this duration but with a coarsest Unit, or self in case it is already the coarsest Unit + *

+ * Examples: + * {{{ + * Duration(60, MINUTES).toCoarsest // Duration(1, HOURS) + * Duration(1000, MILLISECONDS).toCoarsest // Duration(1, SECONDS) + * Duration(48, HOURS).toCoarsest // Duration(2, DAYS) + * Duration(5, SECONDS).toCoarsest // Duration(5, SECONDS) + * }}} + */ + def toCoarsest: Duration +} + +object FiniteDuration { + + implicit object FiniteDurationIsOrdered extends Ordering[FiniteDuration] { + def compare(a: FiniteDuration, b: FiniteDuration): Int = a compare b + } + + def apply(length: Long, unit: TimeUnit): FiniteDuration = new FiniteDuration(length, unit) + def apply(length: Long, unit: String): FiniteDuration = new FiniteDuration(length, Duration.timeUnit(unit)) + + // limit on abs. value of durations in their units + private final val max_ns = Long.MaxValue + private final val max_µs = max_ns / 1000 + private final val max_ms = max_µs / 1000 + private final val max_s = max_ms / 1000 + private final val max_min= max_s / 60 + private final val max_h = max_min / 60 + private final val max_d = max_h / 24 +} + +/** + * This class represents a finite duration. Its addition and subtraction operators are overloaded to retain + * this guarantee statically. The range of this class is limited to `+-(2^63-1)`ns, which is roughly 292 years. + */ +final class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration { + import FiniteDuration._ + import Duration._ + + private[this] def bounded(max: Long) = -max <= length && length <= max + + require(unit match { + /* + * enforce the 2^63-1 ns limit, must be pos/neg symmetrical because of unary_- + */ + case NANOSECONDS => bounded(max_ns) + case MICROSECONDS => bounded(max_µs) + case MILLISECONDS => bounded(max_ms) + case SECONDS => bounded(max_s) + case MINUTES => bounded(max_min) + case HOURS => bounded(max_h) + case DAYS => bounded(max_d) + case _ => + val v = DAYS.convert(length, unit) + -max_d <= v && v <= max_d + }, "Duration is limited to +-(2^63-1)ns (ca. 292 years)") + + def toNanos: Long = unit.toNanos(length) + def toMicros: Long = unit.toMicros(length) + def toMillis: Long = unit.toMillis(length) + def toSeconds: Long = unit.toSeconds(length) + def toMinutes: Long = unit.toMinutes(length) + def toHours: Long = unit.toHours(length) + def toDays: Long = unit.toDays(length) + def toUnit(u: TimeUnit): Double = toNanos.toDouble / NANOSECONDS.convert(1, u) + + /** + * Construct a [[Deadline]] from this duration by adding it to the current instant `Deadline.now`. + */ + def fromNow: Deadline = Deadline.now + this + + private[this] def unitString = timeUnitName(unit) + ( if (length == 1) "" else "s" ) + override def toString: String = "" + length + " " + unitString + + def compare(other: Duration): Int = other match { + case x: FiniteDuration => toNanos compare x.toNanos + case _ => -(other compare this) + } + + // see https://www.securecoding.cert.org/confluence/display/java/NUM00-J.+Detect+or+prevent+integer+overflow + private[this] def safeAdd(a: Long, b: Long): Long = { + if ((b > 0) && (a > Long.MaxValue - b) || + (b < 0) && (a < Long.MinValue - b)) throw new IllegalArgumentException("integer overflow") + a + b + } + private[this] def add(otherLength: Long, otherUnit: TimeUnit): FiniteDuration = { + val commonUnit = if (otherUnit.convert(1, unit) == 0) unit else otherUnit + val totalLength = safeAdd(commonUnit.convert(length, unit), commonUnit.convert(otherLength, otherUnit)) + new FiniteDuration(totalLength, commonUnit) + } + + def +(other: Duration): Duration = other match { + case x: FiniteDuration => add(x.length, x.unit) + case _ => other + } + def -(other: Duration): Duration = other match { + case x: FiniteDuration => add(-x.length, x.unit) + case _ => -other + } + + def *(factor: Double): Duration = + if (!factor.isInfinite) fromNanos(toNanos * factor) + else if (JDouble.isNaN(factor)) Undefined + else if ((factor > 0) ^ (this < Zero)) Inf + else MinusInf + + def /(divisor: Double): Duration = + if (!divisor.isInfinite) fromNanos(toNanos / divisor) + else if (JDouble.isNaN(divisor)) Undefined + else Zero + + // if this is made a constant, then scalac will elide the conditional and always return +0.0, scala/bug#6331 + private[this] def minusZero = -0d + def /(divisor: Duration): Double = + if (divisor.isFinite) toNanos.toDouble / divisor.toNanos + else if (divisor eq Undefined) Double.NaN + else if ((length < 0) ^ (divisor > Zero)) 0d + else minusZero + + // overloaded methods taking FiniteDurations, so that you can calculate while statically staying finite + def +(other: FiniteDuration): FiniteDuration = add(other.length, other.unit) + def -(other: FiniteDuration): FiniteDuration = add(-other.length, other.unit) + def plus(other: FiniteDuration): FiniteDuration = this + other + def minus(other: FiniteDuration): FiniteDuration = this - other + def min(other: FiniteDuration): FiniteDuration = if (this < other) this else other + def max(other: FiniteDuration): FiniteDuration = if (this > other) this else other + + // overloaded methods taking Long so that you can calculate while statically staying finite + + /** + * Return the quotient of this duration and the given integer factor. + * + * @throws java.lang.ArithmeticException if the factor is 0 + */ + def /(divisor: Long): FiniteDuration = fromNanos(toNanos / divisor) + + /** + * Return the product of this duration and the given integer factor. + * + * @throws IllegalArgumentException if the result would overflow the range of FiniteDuration + */ + def *(factor: Long): FiniteDuration = new FiniteDuration(safeMul(length, factor), unit) + + /* + * This method avoids the use of Long division, which saves 95% of the time spent, + * by checking that there are enough leading zeros so that the result has a chance + * to fit into a Long again; the remaining edge cases are caught by using the sign + * of the product for overflow detection. + * + * This method is not general purpose because it disallows the (otherwise legal) + * case of Long.MinValue * 1, but that is okay for use in FiniteDuration, since + * Long.MinValue is not a legal `length` anyway. + */ + private def safeMul(_a: Long, _b: Long): Long = { + val a = scala.math.abs(_a) + val b = scala.math.abs(_b) + import java.lang.Long.{ numberOfLeadingZeros => leading } + if (leading(a) + leading(b) < 64) throw new IllegalArgumentException("multiplication overflow") + val product = a * b + if (product < 0) throw new IllegalArgumentException("multiplication overflow") + if (a == _a ^ b == _b) -product else product + } + + /** + * Return the quotient of this duration and the given integer factor. + * + * @throws java.lang.ArithmeticException if the factor is 0 + */ + def div(divisor: Long): FiniteDuration = this / divisor + + /** + * Return the product of this duration and the given integer factor. + * + * @throws IllegalArgumentException if the result would overflow the range of FiniteDuration + */ + def mul(factor: Long): FiniteDuration = this * factor + + def unary_- : FiniteDuration = Duration(-length, unit) + + final def isFinite = true + + final override def toCoarsest: FiniteDuration = { + def loop(length: Long, unit: TimeUnit): FiniteDuration = { + def coarserOrThis(coarser: TimeUnit, divider: Int): FiniteDuration = + if (length % divider == 0) loop(length / divider, coarser) + else if (unit == this.unit) this + else FiniteDuration(length, unit) + + unit match { + case DAYS => FiniteDuration(length, unit) + case HOURS => coarserOrThis(DAYS, 24) + case MINUTES => coarserOrThis(HOURS, 60) + case SECONDS => coarserOrThis(MINUTES, 60) + case MILLISECONDS => coarserOrThis(SECONDS, 1000) + case MICROSECONDS => coarserOrThis(MILLISECONDS, 1000) + case NANOSECONDS => coarserOrThis(MICROSECONDS, 1000) + } + } + + if (unit == DAYS || length == 0) this + else loop(length, unit) + } + + override def equals(other: Any): Boolean = other match { + case x: FiniteDuration => toNanos == x.toNanos + case _ => super.equals(other) + } + override def hashCode: Int = toNanos.toInt +} diff --git a/library/src/scala/concurrent/duration/DurationConversions.scala b/library/src/scala/concurrent/duration/DurationConversions.scala new file mode 100644 index 000000000000..b90d93e02ffb --- /dev/null +++ b/library/src/scala/concurrent/duration/DurationConversions.scala @@ -0,0 +1,97 @@ +/* + * 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.concurrent.duration + +import scala.language.`2.13` +import DurationConversions._ + +// Would be nice to limit the visibility of this trait a little bit, +// but it crashes scalac to do so. +trait DurationConversions extends Any { + protected def durationIn(unit: TimeUnit): FiniteDuration + + def nanoseconds: FiniteDuration = durationIn(NANOSECONDS) + def nanos: FiniteDuration = nanoseconds + def nanosecond: FiniteDuration = nanoseconds + def nano: FiniteDuration = nanoseconds + + def microseconds: FiniteDuration = durationIn(MICROSECONDS) + def micros: FiniteDuration = microseconds + def microsecond: FiniteDuration = microseconds + def micro: FiniteDuration = microseconds + + def milliseconds: FiniteDuration = durationIn(MILLISECONDS) + def millis: FiniteDuration = milliseconds + def millisecond: FiniteDuration = milliseconds + def milli: FiniteDuration = milliseconds + + def seconds: FiniteDuration = durationIn(SECONDS) + def second: FiniteDuration = seconds + + def minutes: FiniteDuration = durationIn(MINUTES) + def minute: FiniteDuration = minutes + + def hours: FiniteDuration = durationIn(HOURS) + def hour: FiniteDuration = hours + + def days: FiniteDuration = durationIn(DAYS) + def day: FiniteDuration = days + + def nanoseconds[C](c: C)(implicit ev: Classifier[C]): ev.R = ev.convert(nanoseconds) + def nanos[C](c: C)(implicit ev: Classifier[C]): ev.R = nanoseconds(c) + def nanosecond[C](c: C)(implicit ev: Classifier[C]): ev.R = nanoseconds(c) + def nano[C](c: C)(implicit ev: Classifier[C]): ev.R = nanoseconds(c) + + def microseconds[C](c: C)(implicit ev: Classifier[C]): ev.R = ev.convert(microseconds) + def micros[C](c: C)(implicit ev: Classifier[C]): ev.R = microseconds(c) + def microsecond[C](c: C)(implicit ev: Classifier[C]): ev.R = microseconds(c) + def micro[C](c: C)(implicit ev: Classifier[C]): ev.R = microseconds(c) + + def milliseconds[C](c: C)(implicit ev: Classifier[C]): ev.R = ev.convert(milliseconds) + def millis[C](c: C)(implicit ev: Classifier[C]): ev.R = milliseconds(c) + def millisecond[C](c: C)(implicit ev: Classifier[C]): ev.R = milliseconds(c) + def milli[C](c: C)(implicit ev: Classifier[C]): ev.R = milliseconds(c) + + def seconds[C](c: C)(implicit ev: Classifier[C]): ev.R = ev.convert(seconds) + def second[C](c: C)(implicit ev: Classifier[C]): ev.R = seconds(c) + + def minutes[C](c: C)(implicit ev: Classifier[C]): ev.R = ev.convert(minutes) + def minute[C](c: C)(implicit ev: Classifier[C]): ev.R = minutes(c) + + def hours[C](c: C)(implicit ev: Classifier[C]): ev.R = ev.convert(hours) + def hour[C](c: C)(implicit ev: Classifier[C]): ev.R = hours(c) + + def days[C](c: C)(implicit ev: Classifier[C]): ev.R = ev.convert(days) + def day[C](c: C)(implicit ev: Classifier[C]): ev.R = days(c) +} + +/** + * This object just holds some cogs which make the DSL machine work, not for direct consumption. + */ +object DurationConversions { + trait Classifier[C] { + type R + def convert(d: FiniteDuration): R + } + + implicit object spanConvert extends Classifier[span.type] { + type R = FiniteDuration + def convert(d: FiniteDuration): FiniteDuration = d + } + + implicit object fromNowConvert extends Classifier[fromNow.type] { + type R = Deadline + def convert(d: FiniteDuration): Deadline = Deadline.now + d + } + +} diff --git a/library/src/scala/concurrent/duration/package.scala b/library/src/scala/concurrent/duration/package.scala new file mode 100644 index 000000000000..09348cdde8d5 --- /dev/null +++ b/library/src/scala/concurrent/duration/package.scala @@ -0,0 +1,88 @@ +/* + * 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.concurrent + +import scala.language.`2.13` +import scala.language.implicitConversions + +package object duration { + /** + * This object can be used as closing token if you prefer dot-less style but do not want + * to enable language.postfixOps: + * + * {{{ + * import scala.concurrent.duration._ + * + * val duration = 2 seconds span + * }}} + */ + object span + + /** + * This object can be used as closing token for declaring a deadline at some future point + * in time: + * + * {{{ + * import scala.concurrent.duration._ + * + * val deadline = 3 seconds fromNow + * }}} + */ + object fromNow + + type TimeUnit = java.util.concurrent.TimeUnit + final val DAYS = java.util.concurrent.TimeUnit.DAYS + final val HOURS = java.util.concurrent.TimeUnit.HOURS + final val MICROSECONDS = java.util.concurrent.TimeUnit.MICROSECONDS + final val MILLISECONDS = java.util.concurrent.TimeUnit.MILLISECONDS + final val MINUTES = java.util.concurrent.TimeUnit.MINUTES + final val NANOSECONDS = java.util.concurrent.TimeUnit.NANOSECONDS + final val SECONDS = java.util.concurrent.TimeUnit.SECONDS + + implicit def pairIntToDuration(p: (Int, TimeUnit)): Duration = Duration(p._1.toLong, p._2) + implicit def pairLongToDuration(p: (Long, TimeUnit)): FiniteDuration = Duration(p._1, p._2) + implicit def durationToPair(d: Duration): (Long, TimeUnit) = (d.length, d.unit) + + implicit final class DurationInt(private val n: Int) extends AnyVal with DurationConversions { + override protected def durationIn(unit: TimeUnit): FiniteDuration = Duration(n.toLong, unit) + } + + implicit final class DurationLong(private val n: Long) extends AnyVal with DurationConversions { + override protected def durationIn(unit: TimeUnit): FiniteDuration = Duration(n, unit) + } + + implicit final class DurationDouble(private val d: Double) extends AnyVal with DurationConversions { + override protected def durationIn(unit: TimeUnit): FiniteDuration = + Duration(d, unit) match { + case f: FiniteDuration => f + case _ => throw new IllegalArgumentException("Duration DSL not applicable to " + d) + } + } + + /* + * Avoid reflection based invocation by using non-duck type + */ + implicit final class IntMult(private val i: Int) extends AnyVal { + def *(d: Duration): Duration = d * i.toDouble + def *(d: FiniteDuration): FiniteDuration = d * i.toLong + } + + implicit final class LongMult(private val i: Long) extends AnyVal { + def *(d: Duration): Duration = d * i.toDouble + def *(d: FiniteDuration): FiniteDuration = d * i.toLong + } + + implicit final class DoubleMult(private val f: Double) extends AnyVal { + def *(d: Duration): Duration = d * f.toDouble + } +} diff --git a/library/src/scala/concurrent/impl/ExecutionContextImpl.scala b/library/src/scala/concurrent/impl/ExecutionContextImpl.scala new file mode 100644 index 000000000000..3096297f620a --- /dev/null +++ b/library/src/scala/concurrent/impl/ExecutionContextImpl.scala @@ -0,0 +1,138 @@ +/* + * 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.concurrent.impl + +import scala.language.`2.13` +import java.util.concurrent.{ Semaphore, ForkJoinPool, ForkJoinWorkerThread, Callable, Executor, ExecutorService, ThreadFactory, TimeUnit } +import java.util.Collection +import scala.concurrent.{ BlockContext, ExecutionContext, CanAwait, ExecutionContextExecutor, ExecutionContextExecutorService } + +private[scala] class ExecutionContextImpl private[impl] (final val executor: Executor, final val reporter: Throwable => Unit) extends ExecutionContextExecutor { + require(executor ne null, "Executor must not be null") + override final def execute(runnable: Runnable): Unit = executor execute runnable + override final def reportFailure(t: Throwable): Unit = reporter(t) +} + +private[concurrent] object ExecutionContextImpl { + + final class DefaultThreadFactory( + final val daemonic: Boolean, + final val maxBlockers: Int, + final val prefix: String, + final val uncaught: Thread.UncaughtExceptionHandler) extends ThreadFactory with ForkJoinPool.ForkJoinWorkerThreadFactory { + + require(prefix ne null, "DefaultThreadFactory.prefix must be non null") + require(maxBlockers >= 0, "DefaultThreadFactory.maxBlockers must be greater-or-equal-to 0") + + private final val blockerPermits = new Semaphore(maxBlockers) + + @annotation.nowarn("cat=deprecation") + def wire[T <: Thread](thread: T): T = { + thread.setDaemon(daemonic) + thread.setUncaughtExceptionHandler(uncaught) + thread.setName(prefix + "-" + thread.getId()) + thread + } + + def newThread(runnable: Runnable): Thread = wire(new Thread(runnable)) + + def newThread(fjp: ForkJoinPool): ForkJoinWorkerThread = + wire(new ForkJoinWorkerThread(fjp) with BlockContext { + private[this] final var isBlocked: Boolean = false // This is only ever read & written if this thread is the current thread + final override def blockOn[T](thunk: => T)(implicit permission: CanAwait): T = + if ((Thread.currentThread eq this) && !isBlocked && blockerPermits.tryAcquire()) { + try { + val b: ForkJoinPool.ManagedBlocker with (() => T) = + new ForkJoinPool.ManagedBlocker with (() => T) { + private[this] final var result: T = null.asInstanceOf[T] + private[this] final var done: Boolean = false + final override def block(): Boolean = { + if (!done) { + result = thunk // If this throws then it will stop blocking. + done = true + } + + isReleasable + } + + final override def isReleasable = done + final override def apply(): T = result + } + isBlocked = true + ForkJoinPool.managedBlock(b) + b() + } finally { + isBlocked = false + blockerPermits.release() + } + } else thunk // Unmanaged blocking + }) + } + + def createDefaultExecutorService(reporter: Throwable => Unit): ExecutionContextExecutorService = { + def getInt(name: String, default: String) = (try System.getProperty(name, default) catch { + case e: SecurityException => default + }) match { + case s if s.charAt(0) == 'x' => (Runtime.getRuntime.availableProcessors * s.substring(1).toDouble).ceil.toInt + case other => other.toInt + } + + val desiredParallelism = // A range between min and max given num + scala.math.min( + scala.math.max( + getInt("scala.concurrent.context.minThreads", "1"), + getInt("scala.concurrent.context.numThreads", "x1")), + getInt("scala.concurrent.context.maxThreads", "x1") + ) + + val threadFactory = new DefaultThreadFactory(daemonic = true, + maxBlockers = getInt("scala.concurrent.context.maxExtraThreads", "256"), + prefix = "scala-execution-context-global", + uncaught = (thread: Thread, cause: Throwable) => reporter(cause)) + + new ForkJoinPool(desiredParallelism, threadFactory, threadFactory.uncaught, true) with ExecutionContextExecutorService { + final override def reportFailure(cause: Throwable): Unit = + getUncaughtExceptionHandler() match { + case null => + case some => some.uncaughtException(Thread.currentThread, cause) + } + } + } + + def fromExecutor(e: Executor, reporter: Throwable => Unit = ExecutionContext.defaultReporter): ExecutionContextExecutor = + e match { + case null => createDefaultExecutorService(reporter) + case some => new ExecutionContextImpl(some, reporter) + } + + def fromExecutorService(es: ExecutorService, reporter: Throwable => Unit = ExecutionContext.defaultReporter): + ExecutionContextExecutorService = es match { + case null => createDefaultExecutorService(reporter) + case some => + new ExecutionContextImpl(some, reporter) with ExecutionContextExecutorService { + private[this] final def asExecutorService: ExecutorService = executor.asInstanceOf[ExecutorService] + final override def shutdown() = asExecutorService.shutdown() + final override def shutdownNow() = asExecutorService.shutdownNow() + final override def isShutdown = asExecutorService.isShutdown + final override def isTerminated = asExecutorService.isTerminated + final override def awaitTermination(l: Long, timeUnit: TimeUnit) = asExecutorService.awaitTermination(l, timeUnit) + final override def submit[T](callable: Callable[T]) = asExecutorService.submit(callable) + final override def submit[T](runnable: Runnable, t: T) = asExecutorService.submit(runnable, t) + final override def submit(runnable: Runnable) = asExecutorService.submit(runnable) + final override def invokeAll[T](callables: Collection[_ <: Callable[T]]) = asExecutorService.invokeAll(callables) + final override def invokeAll[T](callables: Collection[_ <: Callable[T]], l: Long, timeUnit: TimeUnit) = asExecutorService.invokeAll(callables, l, timeUnit) + final override def invokeAny[T](callables: Collection[_ <: Callable[T]]) = asExecutorService.invokeAny(callables) + final override def invokeAny[T](callables: Collection[_ <: Callable[T]], l: Long, timeUnit: TimeUnit) = asExecutorService.invokeAny(callables, l, timeUnit) + } + } +} diff --git a/library/src/scala/concurrent/impl/FutureConvertersImpl.scala b/library/src/scala/concurrent/impl/FutureConvertersImpl.scala new file mode 100644 index 000000000000..68c4293a439d --- /dev/null +++ b/library/src/scala/concurrent/impl/FutureConvertersImpl.scala @@ -0,0 +1,102 @@ +/* + * 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.concurrent.impl + +import scala.language.`2.13` +import java.util.concurrent.{CompletableFuture, CompletionStage, TimeUnit} +import java.util.function.{BiConsumer, BiFunction, Consumer, Function => JFunction} + +import scala.concurrent.Future +import scala.concurrent.impl.Promise.DefaultPromise +import scala.util.{Failure, Success, Try} + +private[scala] object FutureConvertersImpl { + final class CF[T](val wrapped: Future[T]) extends CompletableFuture[T] with (Try[T] => Unit) { + override def apply(t: Try[T]): Unit = t match { + case Success(v) => complete(v) + case Failure(e) => completeExceptionally(e) + } + + // Ensure that completions of this future cannot hold the Scala Future's completer hostage + + override def thenApply[U](fn: JFunction[_ >: T, _ <: U]): CompletableFuture[U] = thenApplyAsync(fn) + + override def thenAccept(fn: Consumer[_ >: T]): CompletableFuture[Void] = thenAcceptAsync(fn) + + override def thenRun(fn: Runnable): CompletableFuture[Void] = thenRunAsync(fn) + + override def thenCombine[U, V](cs: CompletionStage[_ <: U], fn: BiFunction[_ >: T, _ >: U, _ <: V]): CompletableFuture[V] = thenCombineAsync(cs, fn) + + override def thenAcceptBoth[U](cs: CompletionStage[_ <: U], fn: BiConsumer[_ >: T, _ >: U]): CompletableFuture[Void] = thenAcceptBothAsync(cs, fn) + + override def runAfterBoth(cs: CompletionStage[_], fn: Runnable): CompletableFuture[Void] = runAfterBothAsync(cs, fn) + + override def applyToEither[U](cs: CompletionStage[_ <: T], fn: JFunction[_ >: T, U]): CompletableFuture[U] = applyToEitherAsync(cs, fn) + + override def acceptEither(cs: CompletionStage[_ <: T], fn: Consumer[_ >: T]): CompletableFuture[Void] = acceptEitherAsync(cs, fn) + + override def runAfterEither(cs: CompletionStage[_], fn: Runnable): CompletableFuture[Void] = runAfterEitherAsync(cs, fn) + + override def thenCompose[U](fn: JFunction[_ >: T, _ <: CompletionStage[U]]): CompletableFuture[U] = thenComposeAsync(fn) + + override def whenComplete(fn: BiConsumer[_ >: T, _ >: Throwable]): CompletableFuture[T] = whenCompleteAsync(fn) + + override def handle[U](fn: BiFunction[_ >: T, Throwable, _ <: U]): CompletableFuture[U] = handleAsync(fn) + + override def exceptionally(fn: JFunction[Throwable, _ <: T]): CompletableFuture[T] = { + val cf = new CompletableFuture[T] + whenCompleteAsync((t, e) => { + if (e == null) cf.complete(t) + else { + val n: AnyRef = + try { + fn(e).asInstanceOf[AnyRef] + } catch { + case thr: Throwable => + cf.completeExceptionally(thr) + this + } + if (n ne this) cf.complete(n.asInstanceOf[T]) + } + } + ) + cf + } + + /** + * @inheritdoc + * + * WARNING: completing the result of this method will not complete the underlying + * Scala Future or Promise (ie, the one that that was passed to `toJava`.) + */ + override def toCompletableFuture: CompletableFuture[T] = this + + override def obtrudeValue(value: T): Unit = throw new UnsupportedOperationException("obtrudeValue may not be used on the result of toJava(scalaFuture)") + + override def obtrudeException(ex: Throwable): Unit = throw new UnsupportedOperationException("obtrudeException may not be used on the result of toJava(scalaFuture)") + + override def get(): T = scala.concurrent.blocking(super.get()) + + override def get(timeout: Long, unit: TimeUnit): T = scala.concurrent.blocking(super.get(timeout, unit)) + + override def toString(): String = super[CompletableFuture].toString + } + + final class P[T](val wrapped: CompletionStage[T]) extends DefaultPromise[T] with BiFunction[T, Throwable, Unit] { + override def apply(v: T, e: Throwable): Unit = { + if (e == null) success(v) + else failure(e) + } + } +} + diff --git a/library/src/scala/concurrent/impl/Promise.scala b/library/src/scala/concurrent/impl/Promise.scala new file mode 100644 index 000000000000..983a82e7bb7c --- /dev/null +++ b/library/src/scala/concurrent/impl/Promise.scala @@ -0,0 +1,545 @@ +/* + * 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.concurrent.impl + +import scala.language.`2.13` +import scala.concurrent.{Batchable, CanAwait, ExecutionContext, ExecutionException, Future, TimeoutException} +import scala.concurrent.duration.Duration +import scala.annotation.{nowarn, switch, tailrec} +import scala.util.control.{ControlThrowable, NonFatal} +import scala.util.{Failure, Success, Try} +import scala.runtime.NonLocalReturnControl +import java.util.concurrent.locks.AbstractQueuedSynchronizer +import java.util.concurrent.atomic.AtomicReference +import java.util.Objects.requireNonNull +import java.io.{IOException, NotSerializableException, ObjectInputStream, ObjectOutputStream} + +/** + * Latch used to implement waiting on a DefaultPromise's result. + * + * Inspired by: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/locks/AbstractQueuedSynchronizer.java + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * https://creativecommons.org/publicdomain/zero/1.0/ + */ +private[impl] final class CompletionLatch[T] extends AbstractQueuedSynchronizer with (Try[T] => Unit) { + //@volatie not needed since we use acquire/release + /*@volatile*/ private[this] var _result: Try[T] = null + final def result: Try[T] = _result + override protected def tryAcquireShared(ignored: Int): Int = if (getState != 0) 1 else -1 + override protected def tryReleaseShared(ignore: Int): Boolean = { + setState(1) + true + } + override def apply(value: Try[T]): Unit = { + _result = value // This line MUST go before releaseShared + releaseShared(1) + } +} + +private[concurrent] object Promise { + /** + * Link represents a completion dependency between 2 DefaultPromises. + * As the DefaultPromise referred to by a Link can itself be linked to another promise + * `relink` traverses such chains and compresses them so that the link always points + * to the root of the dependency chain. + * + * In order to conserve memory, the owner of a Link (a DefaultPromise) is not stored + * on the Link, but is instead passed in as a parameter to the operation(s). + * + * If when compressing a chain of Links it is discovered that the root has been completed, + * the `owner`'s value is completed with that value, and the Link chain is discarded. + **/ + private[concurrent] final class Link[T](to: DefaultPromise[T]) extends AtomicReference[DefaultPromise[T]](to) { + /** + * Compresses this chain and returns the currently known root of this chain of Links. + **/ + final def promise(owner: DefaultPromise[T]): DefaultPromise[T] = { + val c = get() + compressed(current = c, target = c, owner = owner) + } + + /** + * The combination of traversing and possibly unlinking of a given `target` DefaultPromise. + **/ + @inline @tailrec private[this] final def compressed(current: DefaultPromise[T], target: DefaultPromise[T], owner: DefaultPromise[T]): DefaultPromise[T] = { + val value = target.get() + if (value.isInstanceOf[Callbacks[_]]) { + if (compareAndSet(current, target)) target // Link + else compressed(current = get(), target = target, owner = owner) // Retry + } else if (value.isInstanceOf[Link[_]]) compressed(current = current, target = value.asInstanceOf[Link[T]].get(), owner = owner) // Compress + else /*if (value.isInstanceOf[Try[T]])*/ { + owner.unlink(value.asInstanceOf[Try[T]]) // Discard links + owner + } + } + } + + /** + * The process of "resolving" a Try is to validate that it only contains + * those values which makes sense in the context of Futures. + **/ + // requireNonNull is paramount to guard against null completions + private[this] final def resolve[T](value: Try[T]): Try[T] = + if (requireNonNull(value).isInstanceOf[Success[T]]) value + else { + val t = value.asInstanceOf[Failure[T]].exception + if (t.isInstanceOf[ControlThrowable] || t.isInstanceOf[InterruptedException] || t.isInstanceOf[Error]) { + if (t.isInstanceOf[NonLocalReturnControl[T @unchecked]]) + Success(t.asInstanceOf[NonLocalReturnControl[T]].value) + else + Failure(new ExecutionException("Boxed Exception", t)) + } else value + } + + // Left non-final to enable addition of extra fields by Java/Scala converters in scala-java8-compat. + class DefaultPromise[T] private[this] (initial: AnyRef) extends AtomicReference[AnyRef](initial) with scala.concurrent.Promise[T] with scala.concurrent.Future[T] with (Try[T] => Unit) { + /** + * Constructs a new, completed, Promise. + */ + final def this(result: Try[T]) = this(resolve(result): AnyRef) + + /** + * Constructs a new, un-completed, Promise. + */ + final def this() = this(Noop: AnyRef) + + /** + * WARNING: the `resolved` value needs to have been pre-resolved using `resolve()` + * INTERNAL API + */ + override final def apply(resolved: Try[T]): Unit = + tryComplete0(get(), resolved) + + /** + * Returns the associated `Future` with this `Promise` + */ + override final def future: Future[T] = this + + override final def transform[S](f: Try[T] => Try[S])(implicit executor: ExecutionContext): Future[S] = + dispatchOrAddCallbacks(get(), new Transformation[T, S](Xform_transform, f, executor)) + + override final def transformWith[S](f: Try[T] => Future[S])(implicit executor: ExecutionContext): Future[S] = + dispatchOrAddCallbacks(get(), new Transformation[T, S](Xform_transformWith, f, executor)) + + override final def zipWith[U, R](that: Future[U])(f: (T, U) => R)(implicit executor: ExecutionContext): Future[R] = { + val state = get() + if (state.isInstanceOf[Try[_]]) { + if (state.asInstanceOf[Try[T]].isFailure) this.asInstanceOf[Future[R]] + else { + val l = state.asInstanceOf[Success[T]].get + that.map(r => f(l, r)) + } + } else { + val buffer = new AtomicReference[Success[Any]]() + val zipped = new DefaultPromise[R]() + + val thisF: Try[T] => Unit = { + case left: Success[_] => + val right = buffer.getAndSet(left).asInstanceOf[Success[U]] + if (right ne null) + zipped.tryComplete(try Success(f(left.get, right.get)) catch { case e if NonFatal(e) => Failure(e) }) + case f => // Can only be Failure + zipped.tryComplete(f.asInstanceOf[Failure[R]]) + } + + val thatF: Try[U] => Unit = { + case right: Success[_] => + val left = buffer.getAndSet(right).asInstanceOf[Success[T]] + if (left ne null) + zipped.tryComplete(try Success(f(left.get, right.get)) catch { case e if NonFatal(e) => Failure(e) }) + case f => // Can only be Failure + zipped.tryComplete(f.asInstanceOf[Failure[R]]) + } + // Cheaper than this.onComplete since we already polled the state + this.dispatchOrAddCallbacks(state, new Transformation[T, Unit](Xform_onComplete, thisF, executor)) + that.onComplete(thatF) + zipped.future + } + } + + override final def foreach[U](f: T => U)(implicit executor: ExecutionContext): Unit = { + val state = get() + if (!state.isInstanceOf[Failure[_]]) dispatchOrAddCallbacks(state, new Transformation[T, Unit](Xform_foreach, f, executor)) + } + + override final def flatMap[S](f: T => Future[S])(implicit executor: ExecutionContext): Future[S] = { + val state = get() + if (!state.isInstanceOf[Failure[_]]) dispatchOrAddCallbacks(state, new Transformation[T, S](Xform_flatMap, f, executor)) + else this.asInstanceOf[Future[S]] + } + + override final def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S] = { + val state = get() + if (!state.isInstanceOf[Failure[_]]) dispatchOrAddCallbacks(state, new Transformation[T, S](Xform_map, f, executor)) + else this.asInstanceOf[Future[S]] + } + + override final def filter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = { + val state = get() + if (!state.isInstanceOf[Failure[_]]) dispatchOrAddCallbacks(state, new Transformation[T, T](Xform_filter, p, executor)) // Short-circuit if we get a Success + else this + } + + override final def collect[S](pf: PartialFunction[T, S])(implicit executor: ExecutionContext): Future[S] = { + val state = get() + if (!state.isInstanceOf[Failure[_]]) dispatchOrAddCallbacks(state, new Transformation[T, S](Xform_collect, pf, executor)) // Short-circuit if we get a Success + else this.asInstanceOf[Future[S]] + } + + override final def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U] = { + val state = get() + if (!state.isInstanceOf[Success[_]]) dispatchOrAddCallbacks(state, new Transformation[T, U](Xform_recoverWith, pf, executor)) // Short-circuit if we get a Failure + else this.asInstanceOf[Future[U]] + } + + override final def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U] = { + val state = get() + if (!state.isInstanceOf[Success[_]]) dispatchOrAddCallbacks(state, new Transformation[T, U](Xform_recover, pf, executor)) // Short-circuit if we get a Failure + else this.asInstanceOf[Future[U]] + } + + override final def mapTo[S](implicit tag: scala.reflect.ClassTag[S]): Future[S] = + if (!get().isInstanceOf[Failure[_]]) super[Future].mapTo[S](tag) // Short-circuit if we get a Success + else this.asInstanceOf[Future[S]] + + + override final def onComplete[U](func: Try[T] => U)(implicit executor: ExecutionContext): Unit = + dispatchOrAddCallbacks(get(), new Transformation[T, Unit](Xform_onComplete, func, executor)) + + /** The same as [[onComplete]], but additionally returns a function which can be + * invoked to unregister the callback function. Removing a callback from a long-lived + * future can enable garbage collection of objects referenced by the closure. + */ + private[concurrent] final def onCompleteWithUnregister[U](func: Try[T] => U)(implicit executor: ExecutionContext): () => Unit = { + val t = new Transformation[T, Unit](Xform_onComplete, func, executor) + dispatchOrAddCallbacks(get(), t) + () => unregisterCallback(t) + } + + override final def failed: Future[Throwable] = + if (!get().isInstanceOf[Success[_]]) super.failed + else Future.failedFailureFuture // Cached instance in case of already known success + + @tailrec override final def toString: String = { + val state = get() + if (state.isInstanceOf[Try[_]]) "Future("+state+")" + else if (state.isInstanceOf[Link[_]]) state.asInstanceOf[Link[T]].promise(this).toString + else /*if (state.isInstanceOf[Callbacks[T]]) */ "Future()" + } + + private[this] final def tryAwait0(atMost: Duration): Try[T] = + if (atMost ne Duration.Undefined) { + val v = value0 + if (v ne null) v + else { + val r = + if (atMost <= Duration.Zero) null + else { + val l = new CompletionLatch[T]() + onComplete(l)(ExecutionContext.parasitic) + + if (atMost.isFinite) + l.tryAcquireSharedNanos(1, atMost.toNanos) + else + l.acquireSharedInterruptibly(1) + + l.result + } + if (r ne null) r + else throw new TimeoutException("Future timed out after [" + atMost + "]") + } + } else throw new IllegalArgumentException("Cannot wait for Undefined duration of time") + + @throws(classOf[TimeoutException]) + @throws(classOf[InterruptedException]) + final def ready(atMost: Duration)(implicit permit: CanAwait): this.type = { + tryAwait0(atMost) + this + } + + @throws(classOf[Exception]) + final def result(atMost: Duration)(implicit permit: CanAwait): T = + tryAwait0(atMost).get // returns the value, or throws the contained exception + + override final def isCompleted: Boolean = value0 ne null + + override final def value: Option[Try[T]] = Option(value0) + + @tailrec // returns null if not completed + private final def value0: Try[T] = { + val state = get() + if (state.isInstanceOf[Try[_]]) state.asInstanceOf[Try[T]] + else if (state.isInstanceOf[Link[_]]) state.asInstanceOf[Link[T]].promise(this).value0 + else /*if (state.isInstanceOf[Callbacks[T]])*/ null + } + + override final def tryComplete(value: Try[T]): Boolean = { + val state = get() + if (state.isInstanceOf[Try[_]]) false + else tryComplete0(state, resolve(value)) + } + + @tailrec // WARNING: important that the supplied Try really is resolve():d + private[Promise] final def tryComplete0(state: AnyRef, resolved: Try[T]): Boolean = + if (state.isInstanceOf[Callbacks[_]]) { + if (compareAndSet(state, resolved)) { + if (state ne Noop) submitWithValue(state.asInstanceOf[Callbacks[T]], resolved) + true + } else tryComplete0(get(), resolved) + } else if (state.isInstanceOf[Link[_]]) { + val p = state.asInstanceOf[Link[T]].promise(this) // If this returns owner/this, we are in a completed link + (p ne this) && p.tryComplete0(p.get(), resolved) // Use this to get tailcall optimization and avoid re-resolution + } else /* if(state.isInstanceOf[Try[T]]) */ false + + override final def completeWith(other: Future[T]): this.type = { + if (other ne this) { + val state = get() + if (!state.isInstanceOf[Try[_]]) { + val resolved = if (other.isInstanceOf[DefaultPromise[_]]) other.asInstanceOf[DefaultPromise[T]].value0 else other.value.orNull + if (resolved ne null) tryComplete0(state, resolved) + else other.onComplete(this)(ExecutionContext.parasitic) + } + } + + this + } + + /** Tries to add the callback, if already completed, it dispatches the callback to be executed. + * Used by `onComplete()` to add callbacks to a promise and by `link()` to transfer callbacks + * to the root promise when linking two promises together. + */ + @tailrec private final def dispatchOrAddCallbacks[C <: Callbacks[T]](state: AnyRef, callbacks: C): C = + if (state.isInstanceOf[Try[_]]) { + submitWithValue(callbacks, state.asInstanceOf[Try[T]]) // invariant: callbacks should never be Noop here + callbacks + } else if (state.isInstanceOf[Callbacks[_]]) { + if(compareAndSet(state, if (state ne Noop) concatCallbacks(callbacks, state.asInstanceOf[Callbacks[T]]) else callbacks)) callbacks + else dispatchOrAddCallbacks(get(), callbacks) + } else /*if (state.isInstanceOf[Link[T]])*/ { + val p = state.asInstanceOf[Link[T]].promise(this) + p.dispatchOrAddCallbacks(p.get(), callbacks) + } + + @tailrec private def unregisterCallback(t: Transformation[_, _]): Unit = { + val state = get() + if (state eq t) { + if (!compareAndSet(state, Noop)) unregisterCallback(t) + } else if (state.isInstanceOf[ManyCallbacks[_]]) { + if (!compareAndSet(state, removeCallback(state.asInstanceOf[ManyCallbacks[T]], t))) unregisterCallback(t) + } + } + + // IMPORTANT: Noop should never be passed in here, neither as left OR as right + @tailrec private[this] final def concatCallbacks(left: Callbacks[T], right: Callbacks[T]): Callbacks[T] = + if (left.isInstanceOf[Transformation[T,_]]) new ManyCallbacks[T](left.asInstanceOf[Transformation[T,_]], right) + else /*if (left.isInstanceOf[ManyCallbacks[T]) */ { // This should only happen when linking + val m = left.asInstanceOf[ManyCallbacks[T]] + concatCallbacks(m.rest, new ManyCallbacks(m.first, right)) + } + + @tailrec private[this] final def removeCallback(cs: Callbacks[T], t: Transformation[_, _], result: Callbacks[T] = null): AnyRef = + if (cs eq t) { + if (result == null) Noop + else result + } + else if (cs.isInstanceOf[ManyCallbacks[_]]) { + val m = cs.asInstanceOf[ManyCallbacks[T]] + if (m.first eq t) { + if (result == null) m.rest + else concatCallbacks(m.rest, result) + } + else removeCallback(m.rest, t, if (result == null) m.first else new ManyCallbacks(m.first, result)) + } else cs + + // IMPORTANT: Noop should not be passed in here, `callbacks` cannot be null + @tailrec + private[this] final def submitWithValue(callbacks: Callbacks[T], resolved: Try[T]): Unit = + if(callbacks.isInstanceOf[ManyCallbacks[T]]) { + val m: ManyCallbacks[T] = callbacks.asInstanceOf[ManyCallbacks[T]] + m.first.submitWithValue(resolved) + submitWithValue(m.rest, resolved) + } else { + callbacks.asInstanceOf[Transformation[T, _]].submitWithValue(resolved) + } + + /** Link this promise to the root of another promise. + */ + @tailrec private[concurrent] final def linkRootOf(target: DefaultPromise[T], link: Link[T]): Unit = + if (this ne target) { + val state = get() + if (state.isInstanceOf[Try[_]]) { + if(!target.tryComplete0(target.get(), state.asInstanceOf[Try[T]])) + throw new IllegalStateException("Cannot link completed promises together") + } else if (state.isInstanceOf[Callbacks[_]]) { + val l = if (link ne null) link else new Link(target) + val p = l.promise(this) + if ((this ne p) && compareAndSet(state, l)) { + if (state ne Noop) p.dispatchOrAddCallbacks(p.get(), state.asInstanceOf[Callbacks[T]]) // Noop-check is important here + } else linkRootOf(p, l) + } else /* if (state.isInstanceOf[Link[T]]) */ + state.asInstanceOf[Link[T]].promise(this).linkRootOf(target, link) + } + + /** + * Unlinks (removes) the link chain if the root is discovered to be already completed, + * and completes the `owner` with that result. + **/ + @tailrec private[concurrent] final def unlink(resolved: Try[T]): Unit = { + val state = get() + if (state.isInstanceOf[Link[_]]) { + val next = if (compareAndSet(state, resolved)) state.asInstanceOf[Link[T]].get() else this + next.unlink(resolved) + } else tryComplete0(state, resolved) + } + + @throws[IOException] + private def writeObject(out: ObjectOutputStream): Unit = + throw new NotSerializableException("Promises and Futures cannot be serialized") + + @throws[IOException] + @throws[ClassNotFoundException] + private def readObject(in: ObjectInputStream): Unit = + throw new NotSerializableException("Promises and Futures cannot be deserialized") + } + + // Constant byte tags for unpacking transformation function inputs or outputs + // These need to be Ints to get compiled into constants. + final val Xform_noop = 0 + final val Xform_map = 1 + final val Xform_flatMap = 2 + final val Xform_transform = 3 + final val Xform_transformWith = 4 + final val Xform_foreach = 5 + final val Xform_onComplete = 6 + final val Xform_recover = 7 + final val Xform_recoverWith = 8 + final val Xform_filter = 9 + final val Xform_collect = 10 + + /* Marker trait + */ + sealed trait Callbacks[-T] + + final class ManyCallbacks[-T](final val first: Transformation[T,_], final val rest: Callbacks[T]) extends Callbacks[T] { + override final def toString: String = "ManyCallbacks" + } + + private[this] final val Noop = new Transformation[Nothing, Nothing](Xform_noop, null, ExecutionContext.parasitic) + + /** + * A Transformation[F, T] receives an F (it is a Callback[F]) and applies a transformation function to that F, + * Producing a value of type T (it is a Promise[T]). + * In order to conserve allocations, indirections, and avoid introducing bi/mega-morphicity the transformation + * function's type parameters are erased, and the _xform tag will be used to reify them. + **/ + final class Transformation[-F, T] private[this] ( + private[this] final var _fun: Any => Any, + private[this] final var _ec: ExecutionContext, + private[this] final var _arg: Try[F], + private[this] final val _xform: Int + ) extends DefaultPromise[T]() with Callbacks[F] with Runnable with Batchable { + final def this(xform: Int, f: _ => _, ec: ExecutionContext) = + this(f.asInstanceOf[Any => Any], ec.prepare(): @nowarn("cat=deprecation"), null, xform) + + final def benefitsFromBatching: Boolean = _xform != Xform_onComplete && _xform != Xform_foreach + + // Gets invoked when a value is available, schedules it to be run():ed by the ExecutionContext + // submitWithValue *happens-before* run(), through ExecutionContext.execute. + // Invariant: _arg is `null`, _ec is non-null. `this` ne Noop. + // requireNonNull(resolved) will hold as guarded by `resolve` + final def submitWithValue(resolved: Try[F]): this.type = { + _arg = resolved + val e = _ec + try e.execute(this) /* Safe publication of _arg, _fun, _ec */ + catch { + case t: Throwable => + _fun = null // allow to GC + _arg = null // see above + _ec = null // see above again + handleFailure(t, e) + } + + this + } + + private[this] final def handleFailure(t: Throwable, e: ExecutionContext): Unit = { + val wasInterrupted = t.isInstanceOf[InterruptedException] + if (wasInterrupted || NonFatal(t)) { + val completed = tryComplete0(get(), resolve(Failure(t))) + if (completed && wasInterrupted) Thread.currentThread.interrupt() + + // Report or rethrow failures which are unlikely to otherwise be noticed + if (_xform == Xform_foreach || _xform == Xform_onComplete || !completed) + e.reportFailure(t) + } else throw t + } + + // Gets invoked by the ExecutionContext, when we have a value to transform. + override final def run(): Unit = { + val v = _arg + val fun = _fun + val ec = _ec + _fun = null // allow to GC + _arg = null // see above + _ec = null // see above + try { + val resolvedResult: Try[_] = + (_xform: @switch) match { + case Xform_noop => + null + case Xform_map => + if (v.isInstanceOf[Success[F]]) Success(fun(v.get)) else v // Faster than `resolve(v map fun)` + case Xform_flatMap => + if (v.isInstanceOf[Success[F]]) { + val f = fun(v.get) + if (f.isInstanceOf[DefaultPromise[_]]) f.asInstanceOf[DefaultPromise[T]].linkRootOf(this, null) else completeWith(f.asInstanceOf[Future[T]]) + null + } else v + case Xform_transform => + resolve(fun(v).asInstanceOf[Try[T]]) + case Xform_transformWith => + val f = fun(v) + if (f.isInstanceOf[DefaultPromise[_]]) f.asInstanceOf[DefaultPromise[T]].linkRootOf(this, null) else completeWith(f.asInstanceOf[Future[T]]) + null + case Xform_foreach => + v.foreach(fun) + null + case Xform_onComplete => + fun(v) + null + case Xform_recover => + if (v.isInstanceOf[Failure[_]]) resolve(v.recover(fun.asInstanceOf[PartialFunction[Throwable, F]])) else v //recover F=:=T + case Xform_recoverWith => + if (v.isInstanceOf[Failure[F]]) { + val f = fun.asInstanceOf[PartialFunction[Throwable, Future[T]]].applyOrElse(v.asInstanceOf[Failure[F]].exception, Future.recoverWithFailed) + if (f ne Future.recoverWithFailedMarker) { + if (f.isInstanceOf[DefaultPromise[_]]) f.asInstanceOf[DefaultPromise[T]].linkRootOf(this, null) else completeWith(f.asInstanceOf[Future[T]]) + null + } else v + } else v + case Xform_filter => + if (v.isInstanceOf[Failure[F]] || fun.asInstanceOf[F => Boolean](v.get)) v else Future.filterFailure + case Xform_collect => + if (v.isInstanceOf[Success[F]]) Success(fun.asInstanceOf[PartialFunction[F, T]].applyOrElse(v.get, Future.collectFailed)) else v + case _ => + Failure(new IllegalStateException("BUG: encountered transformation promise with illegal type: " + _xform)) // Safe not to `resolve` + } + if (resolvedResult ne null) + tryComplete0(get(), resolvedResult.asInstanceOf[Try[T]]) // T is erased anyway so we won't have any use for it above + } catch { + case t: Throwable => handleFailure(t, ec) + } + } + } +} diff --git a/library/src/scala/concurrent/package.scala b/library/src/scala/concurrent/package.scala new file mode 100644 index 000000000000..98044742538b --- /dev/null +++ b/library/src/scala/concurrent/package.scala @@ -0,0 +1,205 @@ +/* + * 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.language.`2.13` +import scala.concurrent.duration.Duration +import scala.annotation.implicitNotFound + +/** This package object contains primitives for concurrent and parallel programming. + * + * == Guide == + * + * A more detailed guide to Futures and Promises, including discussion and examples + * can be found at + * [[https://docs.scala-lang.org/overviews/core/futures.html]]. + * + * == Common Imports == + * + * When working with Futures, you will often find that importing the whole concurrent + * package is convenient: + * + * {{{ + * import scala.concurrent._ + * }}} + * + * When using things like `Future`s, it is often required to have an implicit `ExecutionContext` + * in scope. The general advice for these implicits are as follows. + * + * If the code in question is a class or method definition, and no `ExecutionContext` is available, + * request one from the caller by adding an implicit parameter list: + * + * {{{ + * def myMethod(myParam: MyType)(implicit ec: ExecutionContext) = … + * //Or + * class MyClass(myParam: MyType)(implicit ec: ExecutionContext) { … } + * }}} + * + * This allows the caller of the method, or creator of the instance of the class, to decide which + * `ExecutionContext` should be used. + * + * For typical REPL usage and experimentation, importing the global `ExecutionContext` is often desired. + * + * {{{ + * import scala.concurrent.ExcutionContext.Implicits.global + * }}} + * + * == Specifying Durations == + * + * Operations often require a duration to be specified. A duration DSL is available + * to make defining these easier: + * + * {{{ + * import scala.concurrent.duration._ + * val d: Duration = 10.seconds + * }}} + * + * == Using Futures For Non-blocking Computation == + * + * Basic use of futures is easy with the factory method on Future, which executes a + * provided function asynchronously, handing you back a future result of that function + * without blocking the current thread. In order to create the Future you will need + * either an implicit or explicit ExecutionContext to be provided: + * + * {{{ + * import scala.concurrent._ + * import ExecutionContext.Implicits.global // implicit execution context + * + * val firstZebra: Future[Int] = Future { + * val words = Files.readAllLines("/etc/dictionaries-common/words").asScala + * words.indexOfSlice("zebra") + * } + * }}} + * + * == Avoid Blocking == + * + * Although blocking is possible in order to await results (with a mandatory timeout duration): + * + * {{{ + * import scala.concurrent.duration._ + * Await.result(firstZebra, 10.seconds) + * }}} + * + * and although this is sometimes necessary to do, in particular for testing purposes, blocking + * in general is discouraged when working with Futures and concurrency in order to avoid + * potential deadlocks and improve performance. Instead, use callbacks or combinators to + * remain in the future domain: + * + * {{{ + * val animalRange: Future[Int] = for { + * aardvark <- firstAardvark + * zebra <- firstZebra + * } yield zebra - aardvark + * + * animalRange.onSuccess { + * case x if x > 500000 => println("It's a long way from Aardvark to Zebra") + * } + * }}} + */ +package object concurrent { + type ExecutionException = java.util.concurrent.ExecutionException + type CancellationException = java.util.concurrent.CancellationException + type TimeoutException = java.util.concurrent.TimeoutException + + /** Used to designate a piece of code which potentially blocks, allowing the current [[BlockContext]] to adjust + * the runtime's behavior. + * Properly marking blocking code may improve performance or avoid deadlocks. + * + * Blocking on an [[Awaitable]] should be done using [[Await.result]] instead of `blocking`. + * + * @param body A piece of code which contains potentially blocking or long running calls. + * @throws CancellationException if the computation was cancelled + * @throws InterruptedException in the case that a wait within the blocking `body` was interrupted + */ + @throws(classOf[Exception]) + final def blocking[T](body: => T): T = BlockContext.current.blockOn(body)(scala.concurrent.AwaitPermission) +} + +package concurrent { + /** + * This marker trait is used by [[Await]] to ensure that [[Awaitable.ready]] and [[Awaitable.result]] + * are not directly called by user code. An implicit instance of this trait is only available when + * user code is currently calling the methods on [[Await]]. + */ + @implicitNotFound("Don't call `Awaitable` methods directly, use the `Await` object.") + sealed trait CanAwait + + /** + * Internal usage only, implementation detail. + */ + private[concurrent] object AwaitPermission extends CanAwait + + /** + * `Await` is what is used to ensure proper handling of blocking for `Awaitable` instances. + * + * While occasionally useful, e.g. for testing, it is recommended that you avoid Await whenever possible— + * instead favoring combinators and/or callbacks. + * Await's `result` and `ready` methods will block the calling thread's execution until they return, + * which will cause performance degradation, and possibly, deadlock issues. + */ + object Await { + /** + * Await the "completed" state of an `Awaitable`. + * + * Although this method is blocking, the internal use of [[scala.concurrent.blocking blocking]] ensures that + * the underlying [[ExecutionContext]] is given an opportunity to properly manage the blocking. + * + * WARNING: It is strongly discouraged to supply lengthy timeouts since the progress of the calling thread will be + * suspended—blocked—until either the `Awaitable` becomes ready or the timeout expires. + * + * @param awaitable + * the `Awaitable` to be awaited + * @param atMost + * maximum wait time, which may be negative (no waiting is done), + * [[scala.concurrent.duration.Duration.Inf Duration.Inf]] for unbounded waiting, or a finite positive + * duration + * @return the `awaitable` + * @throws InterruptedException if the current thread is interrupted while waiting + * @throws TimeoutException if after waiting for the specified time this `Awaitable` is still not ready + * @throws IllegalArgumentException if `atMost` is [[scala.concurrent.duration.Duration.Undefined Duration.Undefined]] + */ + @throws(classOf[TimeoutException]) + @throws(classOf[InterruptedException]) + final def ready[T](awaitable: Awaitable[T], atMost: Duration): awaitable.type = awaitable match { + case f: Future[T] if f.isCompleted => awaitable.ready(atMost)(AwaitPermission) + case _ => blocking(awaitable.ready(atMost)(AwaitPermission)) + } + + /** + * Await and return the result (of type `T`) of an `Awaitable`. + * + * Although this method is blocking, the internal use of [[scala.concurrent.blocking blocking]] ensures that + * the underlying [[ExecutionContext]] is given an opportunity to properly manage the blocking. + * + * WARNING: It is strongly discouraged to supply lengthy timeouts since the progress of the calling thread will be + * suspended—blocked—until either the `Awaitable` has a result or the timeout expires. + * + * @param awaitable + * the `Awaitable` to be awaited + * @param atMost + * maximum wait time, which may be negative (no waiting is done), + * [[scala.concurrent.duration.Duration.Inf Duration.Inf]] for unbounded waiting, or a finite positive + * duration + * @return the result value if `awaitable` is completed within the specific maximum wait time + * @throws InterruptedException if the current thread is interrupted while waiting + * @throws TimeoutException if after waiting for the specified time `awaitable` is still not ready + * @throws IllegalArgumentException if `atMost` is [[scala.concurrent.duration.Duration.Undefined Duration.Undefined]] + */ + @throws(classOf[TimeoutException]) + @throws(classOf[InterruptedException]) + final def result[T](awaitable: Awaitable[T], atMost: Duration): T = awaitable match { + case f: Future[T] if f.isCompleted => f.result(atMost)(AwaitPermission) + case _ => blocking(awaitable.result(atMost)(AwaitPermission)) + } + } +} diff --git a/library/src/scala/deprecated.scala b/library/src/scala/deprecated.scala new file mode 100644 index 000000000000..181d25ebc37d --- /dev/null +++ b/library/src/scala/deprecated.scala @@ -0,0 +1,65 @@ +/* + * 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.language.`2.13` +import scala.annotation.meta._ + +/** An annotation that designates that a definition is deprecated. + * A deprecation warning is issued upon usage of the annotated definition. + * + * Library authors should state the library's deprecation policy in their documentation to give + * developers guidance on how long a deprecated definition will be preserved. + * + * Library authors should prepend the name of their library to the version number to help + * developers distinguish deprecations coming from different libraries: + * + * {{{ + * @deprecated("this method will be removed", "FooLib 12.0") + * def oldMethod(x: Int) = ... + * }}} + * + * The compiler will emit deprecation warnings grouped by library and version: + * + * {{{ + * oldMethod(1) + * oldMethod(2) + * aDeprecatedMethodFromLibraryBar(3, 4) + * + * // warning: there was one deprecation warning (since BarLib 3.2) + * // warning: there were two deprecation warnings (since FooLib 12.0) + * // warning: there were three deprecation warnings in total; re-run with -deprecation for details + * }}} + * + * The Scala compiler also warns about using definitions annotated with [[java.lang.Deprecated]]. However it is + * recommended to use the Scala `@deprecated` annotation in Scala code because it allows providing a deprecation message. + * + * '''`@deprecated` in the Scala language and its standard library'''
+ * + * A deprecated element of the Scala language or a definition in the Scala standard library will + * be preserved at least for the current major version. + * + * This means that an element deprecated in some 2.13.x release will be preserved in + * all 2.13.x releases, but may be removed in the future. (A deprecated element + * might be kept longer to ease migration, but developers should not rely on this.) + * + * @see The official documentation on [[https://www.scala-lang.org/news/2.11.0/#binary-compatibility binary compatibility]]. + * @param message the message to print during compilation if the definition is accessed + * @param since a string identifying the first version in which the definition was deprecated + * @see [[scala.deprecatedInheritance]] + * @see [[scala.deprecatedOverriding]] + * @see [[scala.deprecatedName]] + */ +@getter @setter @beanGetter @beanSetter @field +@deprecatedInheritance("Scheduled for being final in the future", "2.13.0") +class deprecated(message: String = "", since: String = "") extends scala.annotation.ConstantAnnotation diff --git a/library/src/scala/deprecatedInheritance.scala b/library/src/scala/deprecatedInheritance.scala new file mode 100644 index 000000000000..684c2f05249c --- /dev/null +++ b/library/src/scala/deprecatedInheritance.scala @@ -0,0 +1,51 @@ +/* + * 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.language.`2.13` +import scala.annotation.meta._ + +/** An annotation that designates that inheriting from a class is deprecated. + * + * This is usually done to warn about a non-final class being made final in a future version. + * Sub-classing such a class then generates a warning. + * + * No warnings are generated if the subclass is in the same compilation unit. + * + * Library authors should state the library's deprecation policy in their documentation to give + * developers guidance on when a type annotated with `@deprecatedInheritance` will be `final`ized. + * + * Library authors should prepend the name of their library to the version number to help + * developers distinguish deprecations coming from different libraries: + * + * {{{ + * @deprecatedInheritance("this class will be made final", "FooLib 12.0") + * class Foo + * }}} + * + * {{{ + * val foo = new Foo // no deprecation warning + * class Bar extends Foo + * // warning: inheritance from class Foo is deprecated (since FooLib 12.0): this class will be made final + * // class Bar extends Foo + * // ^ + * }}} + * + * @param message the message to print during compilation if the class was sub-classed + * @param since a string identifying the first version in which inheritance was deprecated + * @see [[scala.deprecated]] + * @see [[scala.deprecatedOverriding]] + * @see [[scala.deprecatedName]] + */ +@getter @setter @beanGetter @beanSetter +final class deprecatedInheritance(message: String = "", since: String = "") extends scala.annotation.ConstantAnnotation diff --git a/library/src/scala/deprecatedName.scala b/library/src/scala/deprecatedName.scala new file mode 100644 index 000000000000..931c6e3d348e --- /dev/null +++ b/library/src/scala/deprecatedName.scala @@ -0,0 +1,54 @@ +/* + * 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.language.`2.13` +import scala.annotation.meta._ + +/** An annotation that designates that the name of a parameter is deprecated. + * + * Using this name in a named argument generates a deprecation warning. + * + * If the `name` is omitted, then using the canonical name is deprecated. + * In that case, lints such as `-Xlint:named-booleans` which encourage + * the use of a name will not warn. + * + * Library authors should state the library's deprecation policy in their documentation to give + * developers guidance on how long a deprecated name will be preserved. + * + * Library authors should prepend the name of their library to the version number to help + * developers distinguish deprecations coming from different libraries: + * + * {{{ + * def inc(x: Int, @deprecatedName("y", "FooLib 12.0") n: Int): Int = x + n + * inc(1, y = 2) + * }}} + * will produce the following warning: + * {{{ + * warning: the parameter name y is deprecated (since FooLib 12.0): use n instead + * inc(1, y = 2) + * ^ + * }}} + * + * @see [[scala.deprecated]] + * @see [[scala.deprecatedInheritance]] + * @see [[scala.deprecatedOverriding]] + */ +@param +@deprecatedInheritance("Scheduled for being final in the future", "2.13.0") +class deprecatedName(name: String = "", since: String = "") extends scala.annotation.StaticAnnotation { + // at the time we remove these constructors, we should also change this from a StaticAnnotation to + // a ConstantAnnotation; for now, the presence of auxiliary constructors blocks that change + @deprecated("The parameter name should be a String, not a symbol.", "2.13.0") def this(name: Symbol, since: String) = this(name.name, since) + @deprecated("The parameter name should be a String, not a symbol.", "2.13.0") def this(name: Symbol) = this(name.name, "") +} diff --git a/library/src/scala/deprecatedOverriding.scala b/library/src/scala/deprecatedOverriding.scala new file mode 100644 index 000000000000..d5495c87e31a --- /dev/null +++ b/library/src/scala/deprecatedOverriding.scala @@ -0,0 +1,53 @@ +/* + * 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.language.`2.13` +import scala.annotation.meta._ + +/** An annotation that designates that overriding a member is deprecated. + * + * Overriding such a member in a sub-class then generates a warning. + * + * Library authors should state the library's deprecation policy in their documentation to give + * developers guidance on when a method annotated with `@deprecatedOverriding` will be `final`ized. + * + * Library authors should prepend the name of their library to the version number to help + * developers distinguish deprecations coming from different libraries: + * + * {{{ + * class Foo { + * @deprecatedOverriding("this method will be made final", "FooLib 12.0") + * def add(x: Int, y: Int) = x + y + * } + * }}} + * + * {{{ + * class Bar extends Foo // no deprecation warning + * class Baz extends Foo { + * override def add(x: Int, y: Int) = x - y + * } + * // warning: overriding method add in class Foo is deprecated (since FooLib 12.0): this method will be made final + * // override def add(x: Int, y: Int) = x - y + * // ^ + * }}} + * + * @param message the message to print during compilation if the member was overridden + * @param since a string identifying the first version in which overriding was deprecated + * @see [[scala.deprecated]] + * @see [[scala.deprecatedInheritance]] + * @see [[scala.deprecatedName]] + */ +@getter @setter @beanGetter @beanSetter +@deprecatedInheritance("Scheduled for being final in the future", "2.13.0") +class deprecatedOverriding(message: String = "", since: String = "") extends scala.annotation.ConstantAnnotation diff --git a/library/src/scala/inline.scala b/library/src/scala/inline.scala new file mode 100644 index 000000000000..a54f29c099de --- /dev/null +++ b/library/src/scala/inline.scala @@ -0,0 +1,54 @@ +/* + * 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.language.`2.13` + +/** + * An annotation for methods that the optimizer should inline. + * + * Note that by default, the Scala optimizer is disabled and no callsites are inlined. See + * `-opt:help` and [[https://docs.scala-lang.org/overviews/compiler-options/optimizer.html the overview document]] + * for information on how to enable the optimizer and inliner. + * + * When inlining is enabled, the inliner will always try to inline methods or callsites annotated + * `@inline` (under the condition that inlining from the defining class is allowed). + * If inlining is not possible, for example because the method is not + * final, an optimizer warning will be issued. See `-Wopt:help` for details. + * + * Examples: + * + * {{{ + * @inline final def f1(x: Int) = x + * @noinline final def f2(x: Int) = x + * final def f3(x: Int) = x + * + * def t1 = f1(1) // inlined if possible + * def t2 = f2(1) // not inlined + * def t3 = f3(1) // may be inlined (the inliner heuristics can select the callsite) + * def t4 = f1(1): @noinline // not inlined (override at callsite) + * def t5 = f2(1): @inline // inlined if possible (override at callsite) + * def t6 = f3(1): @inline // inlined if possible + * def t7 = f3(1): @noinline // not inlined + * } + * }}} + * + * Note: parentheses are required when annotating a callsite within a larger expression. + * + * {{{ + * def t1 = f1(1) + f1(1): @noinline // equivalent to (f1(1) + f1(1)): @noinline + * def t2 = f1(1) + (f1(1): @noinline) // the second call to f1 is not inlined + * }}} + */ +@deprecatedInheritance("Scheduled for being final in the future", "2.13.0") +class inline extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/io/AnsiColor.scala b/library/src/scala/io/AnsiColor.scala new file mode 100644 index 000000000000..e21db830dbb8 --- /dev/null +++ b/library/src/scala/io/AnsiColor.scala @@ -0,0 +1,178 @@ +/* + * 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.language.`2.13` + +/** ANSI escape codes providing control over text formatting and color on supporting text terminals. + * + * ==ANSI Style and Control Codes== + * + * This group of escape codes provides control over text styling. For example, to turn on reverse video with bold and + * then turn off all styling embed these codes, + * + * {{{ + * import io.AnsiColor._ + * + * object ColorDemo extends App { + * + * println(s"\${REVERSED}\${BOLD}Hello 1979!\${RESET}") + * } + * }}} + * + * ==Foreground and Background Colors== + * + * Embedding ANSI color codes in text output will control the text foreground and background colors. + * + * + * + * + * + * + * + * + * + * + * + *
ForegroundBackground
BLACK BLACK_B
RED RED_B
GREEN GREEN_B
YELLOW YELLOW_B
BLUE BLUE_B
MAGENTAMAGENTA_B
CYAN CYAN_B
WHITE WHITE_B
+ * + * @groupname style-control ANSI Style and Control Codes + * @groupprio style-control 101 + * + * @groupname color-black ANSI Black + * @groupdesc color-black
 
+ * @groupprio color-black 110 + * + * @groupname color-red ANSI Red + * @groupdesc color-red
 
+ * @groupprio color-red 120 + * + * @groupname color-green ANSI Green + * @groupdesc color-green
 
+ * @groupprio color-green 130 + * + * @groupname color-yellow ANSI Yellow + * @groupdesc color-yellow
 
+ * @groupprio color-yellow 140 + * + * @groupname color-blue ANSI Blue + * @groupdesc color-blue
 
+ * @groupprio color-blue 150 + * + * @groupname color-magenta ANSI Magenta + * @groupdesc color-magenta
 
+ * @groupprio color-magenta 160 + * + * @groupname color-cyan ANSI Cyan + * @groupdesc color-cyan
 
+ * @groupprio color-cyan 170 + * + * @groupname color-white ANSI White + * @groupdesc color-white
 
+ * @groupprio color-white 180 + */ +trait AnsiColor { + /** Foreground color for ANSI black + * @group color-black + */ + final val BLACK = "\u001b[30m" + /** Foreground color for ANSI red + * @group color-red + */ + final val RED = "\u001b[31m" + /** Foreground color for ANSI green + * @group color-green + */ + final val GREEN = "\u001b[32m" + /** Foreground color for ANSI yellow + * @group color-yellow + */ + final val YELLOW = "\u001b[33m" + /** Foreground color for ANSI blue + * @group color-blue + */ + final val BLUE = "\u001b[34m" + /** Foreground color for ANSI magenta + * @group color-magenta + */ + final val MAGENTA = "\u001b[35m" + /** Foreground color for ANSI cyan + * @group color-cyan + */ + final val CYAN = "\u001b[36m" + /** Foreground color for ANSI white + * @group color-white + */ + final val WHITE = "\u001b[37m" + + /** Background color for ANSI black + * @group color-black + */ + final val BLACK_B = "\u001b[40m" + /** Background color for ANSI red + * @group color-red + */ + final val RED_B = "\u001b[41m" + /** Background color for ANSI green + * @group color-green + */ + final val GREEN_B = "\u001b[42m" + /** Background color for ANSI yellow + * @group color-yellow + */ + final val YELLOW_B = "\u001b[43m" + /** Background color for ANSI blue + * @group color-blue + */ + final val BLUE_B = "\u001b[44m" + /** Background color for ANSI magenta + * @group color-magenta + */ + final val MAGENTA_B = "\u001b[45m" + /** Background color for ANSI cyan + * @group color-cyan + */ + final val CYAN_B = "\u001b[46m" + /** Background color for ANSI white + * @group color-white + */ + final val WHITE_B = "\u001b[47m" + + /** Reset ANSI styles + * @group style-control + */ + final val RESET = "\u001b[0m" + /** ANSI bold + * @group style-control + */ + final val BOLD = "\u001b[1m" + /** ANSI underlines + * @group style-control + */ + final val UNDERLINED = "\u001b[4m" + /** ANSI blink + * @group style-control + */ + final val BLINK = "\u001b[5m" + /** ANSI reversed + * @group style-control + */ + final val REVERSED = "\u001b[7m" + /** ANSI invisible + * @group style-control + */ + final val INVISIBLE = "\u001b[8m" +} + +object AnsiColor extends AnsiColor { } diff --git a/library/src/scala/io/BufferedSource.scala b/library/src/scala/io/BufferedSource.scala new file mode 100644 index 000000000000..b569cce8d797 --- /dev/null +++ b/library/src/scala/io/BufferedSource.scala @@ -0,0 +1,115 @@ +/* + * 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.io + +import scala.language.`2.13` +import java.io.{ InputStream, BufferedReader, InputStreamReader, PushbackReader } +import Source.DefaultBufSize +import scala.collection.{ Iterator, AbstractIterator } +import scala.collection.mutable.StringBuilder + +/** This object provides convenience methods to create an iterable + * representation of a source file. + */ +class BufferedSource(inputStream: InputStream, bufferSize: Int)(implicit val codec: Codec) extends Source { + def this(inputStream: InputStream)(implicit codec: Codec) = this(inputStream, DefaultBufSize)(codec) + def reader() = new InputStreamReader(inputStream, codec.decoder) + def bufferedReader() = new BufferedReader(reader(), bufferSize) + + // The same reader has to be shared between the iterators produced + // by iter and getLines. This is because calling hasNext can cause a + // block of data to be read from the stream, which will then be lost + // to getLines if it creates a new reader, even though next() was + // never called on the original. + private[this] var charReaderCreated = false + private[this] lazy val charReader = { + charReaderCreated = true + bufferedReader() + } + + override val iter = ( + Iterator + continually (codec wrap charReader.read()) + takeWhile (_ != -1) + map (_.toChar) + ) + + private def decachedReader: BufferedReader = { + // Don't want to lose a buffered char sitting in iter either. Yes, + // this is ridiculous, but if I can't get rid of Source, and all the + // Iterator bits are designed into Source, and people create Sources + // in the repl, and the repl calls toString for the result line, and + // that calls hasNext to find out if they're empty, and that leads + // to chars being buffered, and no, I don't work here, they left a + // door unlocked. + // To avoid inflicting this silliness indiscriminately, we can + // skip it if the char reader was never created: and almost always + // it will not have been created, since getLines will be called + // immediately on the source. + if (charReaderCreated && iter.hasNext) { + val pb = new PushbackReader(charReader) + pb unread iter.next().toInt + new BufferedReader(pb, bufferSize) + } + else charReader + } + + + class BufferedLineIterator extends AbstractIterator[String] with Iterator[String] { + private[this] val lineReader = decachedReader + var nextLine: String = null + + override def hasNext = { + if (nextLine == null) + nextLine = lineReader.readLine + + nextLine != null + } + override def next(): String = { + val result = { + if (nextLine == null) lineReader.readLine + else try nextLine finally nextLine = null + } + if (result == null) Iterator.empty.next() + else result + } + } + + override def getLines(): Iterator[String] = new BufferedLineIterator + + /** Efficiently appends the entire remaining input. + * + * Note: This function may temporarily load the entire buffer into + * memory. + */ + override def addString(sb: StringBuilder, start: String, sep: String, end: String): sb.type = + if (sep.isEmpty) { + val allReader = decachedReader + val buf = new Array[Char](bufferSize) + val jsb = sb.underlying + + if (start.length != 0) jsb.append(start) + var n = allReader.read(buf) + while (n != -1) { + jsb.append(buf, 0, n) + n = allReader.read(buf) + } + if (end.length != 0) jsb.append(end) + sb + // This case is expected to be uncommon, so we're reusing code at + // the cost of temporary memory allocations. + // mkString will callback into BufferedSource.addString to read + // the Buffer into a String, and then we use StringOps.addString + // for the interspersing of sep. + } else mkString.addString(sb, start, sep, end) +} diff --git a/library/src/scala/io/Codec.scala b/library/src/scala/io/Codec.scala new file mode 100644 index 000000000000..44b5becd7d78 --- /dev/null +++ b/library/src/scala/io/Codec.scala @@ -0,0 +1,137 @@ +/* + * 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.language.`2.13` +import java.nio.charset.{CharacterCodingException, Charset, CharsetDecoder, CharsetEncoder, CodingErrorAction => Action} +import java.nio.charset.StandardCharsets.{ISO_8859_1, UTF_8} +import scala.annotation.migration +import scala.language.implicitConversions + +// Some notes about encodings for use in refining this implementation. +// +// Emails: encoding recorded in header, e.g. Content-Type: charset= "iso-8859-1" +// HTML: optional content-type meta tag. +// +// XML: optional encoding parameter. +// +// +// MacRoman vs. UTF-8: see https://groups.google.com/d/msg/jruby-developers/-qtwRhoE1WM/whSPVpTNV28J +// -Dfile.encoding: see https://bugs.java.com/view_bug.do?bug_id=4375816 + +/** A class for character encoding/decoding preferences. + * + */ +class Codec(val charSet: Charset) { + type Configure[T] = (T => T, Boolean) + type Handler = CharacterCodingException => Int + + // these variables allow configuring the Codec object, and then + // all decoders and encoders retrieved from it will use these settings. + private[this] var _onMalformedInput: Action = null + private[this] var _onUnmappableCharacter: Action = null + private[this] var _encodingReplacement: Array[Byte] = null + private[this] var _decodingReplacement: String = null + private[this] var _onCodingException: Handler = e => throw e + + /** The name of the Codec. */ + override def toString = name + + // these methods can be chained to configure the variables above + def onMalformedInput(newAction: Action): this.type = { _onMalformedInput = newAction ; this } + def onUnmappableCharacter(newAction: Action): this.type = { _onUnmappableCharacter = newAction ; this } + def decodingReplaceWith(newReplacement: String): this.type = { _decodingReplacement = newReplacement ; this } + def encodingReplaceWith(newReplacement: Array[Byte]): this.type = { _encodingReplacement = newReplacement ; this } + def onCodingException(handler: Handler): this.type = { _onCodingException = handler ; this } + + def name = charSet.name + def encoder: CharsetEncoder = { + val enc = charSet.newEncoder() + if (_onMalformedInput ne null) enc onMalformedInput _onMalformedInput + if (_onUnmappableCharacter ne null) enc onUnmappableCharacter _onUnmappableCharacter + if (_encodingReplacement ne null) enc replaceWith _encodingReplacement + enc + } + def decoder: CharsetDecoder = { + val dec = charSet.newDecoder() + if (_onMalformedInput ne null) dec onMalformedInput _onMalformedInput + if (_onUnmappableCharacter ne null) dec onUnmappableCharacter _onUnmappableCharacter + if (_decodingReplacement ne null) dec replaceWith _decodingReplacement + dec + } + + def wrap(body: => Int): Int = + try body catch { case e: CharacterCodingException => _onCodingException(e) } +} + +trait LowPriorityCodecImplicits { + self: Codec.type => + + /** The Codec of Last Resort. */ + implicit lazy val fallbackSystemCodec: Codec = defaultCharsetCodec +} + +object Codec extends LowPriorityCodecImplicits { + final val ISO8859: Codec = Codec(ISO_8859_1) + final val UTF8: Codec = Codec(UTF_8) + + /** Optimistically these two possible defaults will be the same thing. + * In practice this is not necessarily true, and in fact Sun classifies + * the fact that you can influence anything at all via -Dfile.encoding + * as an accident, with any anomalies considered "not a bug". + */ + def defaultCharsetCodec: Codec = apply(Charset.defaultCharset) + def fileEncodingCodec: Codec = apply(scala.util.Properties.encodingString) + def default: Codec = defaultCharsetCodec + + def apply(encoding: String): Codec = new Codec(Charset forName encoding) + def apply(charSet: Charset): Codec = new Codec(charSet) + def apply(decoder: CharsetDecoder): Codec = { + val _decoder = decoder + new Codec(decoder.charset()) { override def decoder = _decoder } + } + + @migration("This method was previously misnamed `toUTF8`. Converts from Array[Byte] to Array[Char].", "2.9.0") + def fromUTF8(bytes: Array[Byte]): Array[Char] = fromUTF8(bytes, 0, bytes.length) + def fromUTF8(bytes: Array[Byte], offset: Int, len: Int): Array[Char] = { + val bbuffer = java.nio.ByteBuffer.wrap(bytes, offset, len) + val cbuffer = UTF8.charSet decode bbuffer + val chars = new Array[Char](cbuffer.remaining()) + cbuffer get chars + + chars + } + + @migration("This method was previously misnamed `fromUTF8`. Converts from character sequence to Array[Byte].", "2.9.0") + def toUTF8(cs: CharSequence): Array[Byte] = { + val cbuffer = java.nio.CharBuffer.wrap(cs, 0, cs.length) + val bbuffer = UTF8.charSet encode cbuffer + val bytes = new Array[Byte](bbuffer.remaining()) + bbuffer get bytes + + bytes + } + def toUTF8(chars: Array[Char], offset: Int, len: Int): Array[Byte] = { + val cbuffer = java.nio.CharBuffer.wrap(chars, offset, len) + val bbuffer = UTF8.charSet encode cbuffer + val bytes = new Array[Byte](bbuffer.remaining()) + bbuffer get bytes + + bytes + } + + implicit def string2codec(s: String): Codec = apply(s) + implicit def charset2codec(c: Charset): Codec = apply(c) + implicit def decoder2codec(cd: CharsetDecoder): Codec = apply(cd) +} diff --git a/library/src/scala/io/Position.scala b/library/src/scala/io/Position.scala new file mode 100644 index 000000000000..0a61e4c05c9f --- /dev/null +++ b/library/src/scala/io/Position.scala @@ -0,0 +1,85 @@ +/* + * 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.language.`2.13` +import annotation.nowarn + +/** The object Position provides convenience methods to encode + * line and column number in one single integer. The encoded line + * (column) numbers range from 0 to `LINE_MASK` (`COLUMN_MASK`), + * where `0` indicates that the line (column) is undefined and + * `1` represents the first line (column). + * + * Line (Column) numbers greater than `LINE_MASK` (`COLUMN_MASK`) are + * replaced by `LINE_MASK` (`COLUMN_MASK`). Furthermore, if the encoded + * line number is `LINE_MASK`, the column number is always set to 0. + * + * The following properties hold: + * + * the undefined position is 0: `encode(0,0) == 0` + * encodings are non-negative : `encode(line,column) >= 0` + * position order is preserved: + * {{{ + * (line1 <= line2) || (line1 == line2 && column1 <= column2) + * }}} + * implies + * {{{ + * encode(line1,column1) <= encode(line2,column2) + * }}} + */ +@deprecated("this class will be removed", "2.10.0") +private[scala] abstract class Position { + /** Definable behavior for overflow conditions. + */ + def checkInput(line: Int, column: Int): Unit + + /** Number of bits used to encode the line number */ + final val LINE_BITS = 20 + /** Number of bits used to encode the column number */ + final val COLUMN_BITS = 31 - LINE_BITS // no negatives => 31 + /** Mask to decode the line number */ + final val LINE_MASK = (1 << LINE_BITS) - 1 + /** Mask to decode the column number */ + final val COLUMN_MASK = (1 << COLUMN_BITS) - 1 + + /** Encodes a position into a single integer. */ + final def encode(line: Int, column: Int): Int = { + checkInput(line, column) + + if (line >= LINE_MASK) + LINE_MASK << COLUMN_BITS + else + (line << COLUMN_BITS) | scala.math.min(COLUMN_MASK, column) + } + + /** Returns the line number of the encoded position. */ + final def line(pos: Int): Int = (pos >> COLUMN_BITS) & LINE_MASK + + /** Returns the column number of the encoded position. */ + final def column(pos: Int): Int = pos & COLUMN_MASK + + /** Returns a string representation of the encoded position. */ + def toString(pos: Int): String = line(pos) + ":" + column(pos) +} + +@nowarn +private[scala] object Position extends Position { + def checkInput(line: Int, column: Int): Unit = { + if (line < 0) + throw new IllegalArgumentException(s"$line < 0") + if (line == 0 && column != 0 || column < 0) + throw new IllegalArgumentException(s"$line,$column not allowed") + } +} diff --git a/library/src/scala/io/Source.scala b/library/src/scala/io/Source.scala new file mode 100644 index 000000000000..c66c12d3c8e1 --- /dev/null +++ b/library/src/scala/io/Source.scala @@ -0,0 +1,382 @@ +/* + * 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.language.`2.13` +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 { + 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/library/src/scala/io/StdIn.scala b/library/src/scala/io/StdIn.scala new file mode 100644 index 000000000000..b79cbce83b3b --- /dev/null +++ b/library/src/scala/io/StdIn.scala @@ -0,0 +1,242 @@ +/* + * 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.language.`2.13` +import java.text.MessageFormat + +/** private[scala] because this is not functionality we should be providing + * in the standard library, at least not in this idiosyncratic form. + * Factored into trait because it is better code structure regardless. + */ +private[scala] trait StdIn { + import scala.Console._ + + /** Read a full line from the default input. Returns `null` if the end of the + * input stream has been reached. + * + * @return the string read from the terminal or null if the end of stream was reached. + */ + def readLine(): String = in.readLine() + + /** Print and flush formatted text to the default output, and read a full line from the default input. + * Returns `null` if the end of the input stream has been reached. + * + * @param text the format of the text to print out, as in `printf`. + * @param args the parameters used to instantiate the format, as in `printf`. + * @return the string read from the default input + */ + def readLine(text: String, args: Any*): String = { + printf(text, args: _*) + out.flush() + readLine() + } + + /** Reads a boolean value from an entire line of the default input. + * Has a fairly liberal interpretation of the input. + * + * @return the boolean value read, or false if it couldn't be converted to a boolean + * @throws java.io.EOFException if the end of the input stream has been reached. + */ + def readBoolean(): Boolean = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s.toLowerCase() match { + case "true" => true + case "t" => true + case "yes" => true + case "y" => true + case _ => false + } + } + + /** Reads a byte value from an entire line of the default input. + * + * @return the Byte that was read + * @throws java.io.EOFException if the end of the + * input stream has been reached. + * @throws java.lang.NumberFormatException if the value couldn't be converted to a Byte + */ + def readByte(): Byte = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s.toByte + } + + /** Reads a short value from an entire line of the default input. + * + * @return the short that was read + * @throws java.io.EOFException if the end of the + * input stream has been reached. + * @throws java.lang.NumberFormatException if the value couldn't be converted to a Short + */ + def readShort(): Short = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s.toShort + } + + /** Reads a char value from an entire line of the default input. + * + * @return the Char that was read + * @throws java.io.EOFException if the end of the + * input stream has been reached. + * @throws java.lang.StringIndexOutOfBoundsException if the line read from default input was empty + */ + def readChar(): Char = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s charAt 0 + } + + /** Reads an int value from an entire line of the default input. + * + * @return the Int that was read + * @throws java.io.EOFException if the end of the + * input stream has been reached. + * @throws java.lang.NumberFormatException if the value couldn't be converted to an Int + */ + def readInt(): Int = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s.toInt + } + + /** Reads an long value from an entire line of the default input. + * + * @return the Long that was read + * @throws java.io.EOFException if the end of the + * input stream has been reached. + * @throws java.lang.NumberFormatException if the value couldn't be converted to a Long + */ + def readLong(): Long = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s.toLong + } + + /** Reads a float value from an entire line of the default input. + * @return the Float that was read. + * @throws java.io.EOFException if the end of the + * input stream has been reached. + * @throws java.lang.NumberFormatException if the value couldn't be converted to a Float + * + */ + def readFloat(): Float = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s.toFloat + } + + /** Reads a double value from an entire line of the default input. + * + * @return the Double that was read. + * @throws java.io.EOFException if the end of the + * input stream has been reached. + * @throws java.lang.NumberFormatException if the value couldn't be converted to a Float + */ + def readDouble(): Double = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + s.toDouble + } + + /** Reads in some structured input (from the default input), specified by + * a format specifier. See class `java.text.MessageFormat` for details of + * the format specification. + * + * @param format the format of the input. + * @return a list of all extracted values. + * @throws java.io.EOFException if the end of the input stream has been + * reached. + */ + def readf(format: String): List[Any] = { + val s = readLine() + if (s == null) + throw new java.io.EOFException("Console has reached end of input") + else + textComponents(new MessageFormat(format).parse(s)) + } + + /** Reads in some structured input (from the default input), specified by + * a format specifier, returning only the first value extracted, according + * to the format specification. + * + * @param format format string, as accepted by `readf`. + * @return The first value that was extracted from the input + */ + def readf1(format: String): Any = readf(format).head + + /** Reads in some structured input (from the default input), specified + * by a format specifier, returning only the first two values extracted, + * according to the format specification. + * + * @param format format string, as accepted by `readf`. + * @return A [[scala.Tuple2]] containing the first two values extracted + */ + def readf2(format: String): (Any, Any) = { + val res = readf(format) + (res.head, res.tail.head) + } + + /** Reads in some structured input (from the default input), specified + * by a format specifier, returning only the first three values extracted, + * according to the format specification. + * + * @param format format string, as accepted by `readf`. + * @return A [[scala.Tuple3]] containing the first three values extracted + */ + def readf3(format: String): (Any, Any, Any) = { + val res = readf(format) + (res.head, res.tail.head, res.tail.tail.head) + } + + private def textComponents(a: Array[AnyRef]): List[Any] = { + var i: Int = a.length - 1 + var res: List[Any] = Nil + while (i >= 0) { + res = (a(i) match { + case x: java.lang.Boolean => x.booleanValue() + case x: java.lang.Byte => x.byteValue() + case x: java.lang.Short => x.shortValue() + case x: java.lang.Character => x.charValue() + case x: java.lang.Integer => x.intValue() + case x: java.lang.Long => x.longValue() + case x: java.lang.Float => x.floatValue() + case x: java.lang.Double => x.doubleValue() + case x => x + }) :: res + i -= 1 + } + res + } +} + +object StdIn extends StdIn diff --git a/library/src/scala/jdk/Accumulator.scala b/library/src/scala/jdk/Accumulator.scala new file mode 100644 index 000000000000..612e317db497 --- /dev/null +++ b/library/src/scala/jdk/Accumulator.scala @@ -0,0 +1,405 @@ +/* + * 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.jdk + +import java.{lang => jl} + +import scala.language.`2.13` +import scala.collection.Stepper.EfficientSplit +import scala.collection.{Stepper, StepperShape, mutable} +import scala.language.implicitConversions + +/** Accumulators are mutable sequences with two distinct features: + * - An accumulator can be appended efficiently to another + * - There are manually specialized Accumulators for `Int`, `Long` and `Double` that don't box + * the elements + * + * These two features make Accumulators a good candidate to collect the results of a parallel Java + * stream pipeline into a Scala collection. The + * [[scala.collection.convert.StreamExtensions.StreamHasToScala.toScala]] extension method on Java + * streams (available by importing + * [[scala.jdk.StreamConverters `scala.jdk.StreamConverters._`]]) is specialized for + * Accumulators: they are built in parallel, the parts are merged efficiently. + * + * Building specialized Accumulators is handled transparently. As a user, using the + * [[Accumulator]] object as a factory automatically creates an [[IntAccumulator]], + * [[LongAccumulator]], [[DoubleAccumulator]] or [[AnyAccumulator]] depending on the element type. + * + * Note: to run the example, start the Scala REPL with `scala -Yrepl-class-based` to avoid + * deadlocks, see [[https://github.com/scala/bug/issues/9076]]. + * + * {{{ + * scala> import scala.jdk.StreamConverters._ + * import scala.jdk.StreamConverters._ + * + * scala> def isPrime(n: Int): Boolean = !(2 +: (3 to Math.sqrt(n).toInt by 2) exists (n % _ == 0)) + * isPrime: (n: Int)Boolean + * + * scala> val intAcc = (1 to 10000).asJavaParStream.filter(isPrime).toScala(scala.jdk.Accumulator) + * intAcc: scala.jdk.IntAccumulator = IntAccumulator(1, 3, 5, 7, 11, 13, 17, 19, ... + * + * scala> val stringAcc = (1 to 100).asJavaParStream.mapToObj("<>" * _).toScala(Accumulator) + * stringAcc: scala.jdk.AnyAccumulator[String] = AnyAccumulator(<>, <><>, <><><>, ... + * }}} + * + * There are two possibilities to process elements of a primitive Accumulator without boxing: + * specialized operations of the Accumulator, or the Stepper interface. The most common collection + * operations are overloaded or overridden in the primitive Accumulator classes, for example + * [[IntAccumulator.map(f:Int=>Int)* IntAccumulator.map]] or [[IntAccumulator.exists]]. + * Thanks to Scala's function specialization, + * `intAcc.exists(x => testOn(x))` does not incur boxing. + * + * The [[scala.collection.Stepper]] interface provides iterator-like `hasStep` and `nextStep` methods, and is + * specialized for `Int`, `Long` and `Double`. The `intAccumulator.stepper` method creates an + * [[scala.collection.IntStepper]] that yields the elements of the accumulator without boxing. + * + * Accumulators can hold more than `Int.MaxValue` elements. They have a [[sizeLong]] method that + * returns the size as a `Long`. Note that certain operations defined in [[scala.collection.Seq]] + * are implemented using [[length]], so they will not work correctly for large accumulators. + * + * The [[Accumulator]] class is a base class to share code between [[AnyAccumulator]] (for + * reference types) and the manual specializations [[IntAccumulator]], [[LongAccumulator]] and + * [[DoubleAccumulator]]. + */ +abstract class Accumulator[@specialized(Double, Int, Long) A, +CC[X] <: mutable.Seq[X], +C <: mutable.Seq[A]] + extends mutable.Seq[A] + with mutable.Builder[A, C] { + + /** + * Implementation Details + * + * Every subclass has two arrays + * - `current: Array[A]` + * - `history: Array[Array[A]]` + * + * Elements are added to `current` at [[index]] until it's full, then `current` is added to `history` at [[hIndex]]. + * [[nextBlockSize]] defines the size of the next `current`. See also [[cumulative]]. + */ + private[jdk] var index: Int = 0 + private[jdk] var hIndex: Int = 0 + private[jdk] var totalSize: Long = 0L + + /** + * The total number of elements stored in the history up to `history(i)` (where `0 <= i < hIndex`). + * This method is constant-time, the cumulative lengths are stored. + * - [[AnyAccumulator]] keeps a separate array to store the cumulative lengths. + * - [[LongAccumulator]] and [[DoubleAccumulator]] store the cumulative length at the last slot in every + * array in the history. Every array is allocated with 1 extra slot for this purpose. [[DoubleAccumulator]] + * converts the length to double for storing and back to long, which is correct for lengths that fit in the + * double's 52 fraction bits (so any collection that fits in memory). + * - [[IntAccumulator]] uses the last two slots in every array to store the cumulative length, every array is + * allocated with 1 extra slot. So `history(0)` has 17 slots of which the first 15 store elements. + */ + private[jdk] def cumulative(i: Int): Long + + private[jdk] def nextBlockSize: Int = { + if (totalSize < 32) 16 + else if (totalSize <= Int.MaxValue) { + val bit = 64 - jl.Long.numberOfLeadingZeros(totalSize) + 1 << (bit - (bit >> 2)) + } + else 1 << 24 + } + + protected def efficientStepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit + + final override def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit = + efficientStepper(shape) + + final override def length: Int = + if (sizeLong < Int.MaxValue) sizeLong.toInt + else throw new IllegalArgumentException(s"Size too large for an Int: $sizeLong") + + final override def knownSize: Int = if (sizeLong < Int.MaxValue) size else -1 + + /** Size of the accumulated collection, as a `Long` */ + final def sizeLong: Long = totalSize + + /** Remove all accumulated elements from this accumulator. */ + def clear(): Unit = { + index = 0 + hIndex = 0 + totalSize = 0L + } + + private[jdk] def seekSlot(ix: Long): Long = { + var lo = -1 + var hi = hIndex + while (lo + 1 < hi) { + val m = (lo + hi) >>> 1 // Shift allows division-as-unsigned, prevents overflow + if (cumulative(m) > ix) hi = m + else lo = m + } + (hi.toLong << 32) | (if (hi==0) ix else ix - cumulative(hi-1)).toInt + } +} + +/** Contains factory methods to build Accumulators. + * + * Note that the `Accumulator` object itself is not a factory, but it is implicitly convert to + * a factory according to the element type, see [[Accumulator.toFactory]]. + * + * This allows passing the `Accumulator` object as argument when a [[collection.Factory]], and + * the implicit [[Accumulator.AccumulatorFactoryShape]] instance is used to build a specialized + * Accumulator according to the element type: + * + * {{{ + * scala> val intAcc = Accumulator(1,2,3) + * intAcc: scala.collection.convert.IntAccumulator = IntAccumulator(1, 2, 3) + * + * scala> val anyAccc = Accumulator("K") + * anyAccc: scala.collection.convert.AnyAccumulator[String] = AnyAccumulator(K) + * + * scala> val intAcc2 = List(1,2,3).to(Accumulator) + * intAcc2: scala.jdk.IntAccumulator = IntAccumulator(1, 2, 3) + * + * scala> val anyAcc2 = List("K").to(Accumulator) + * anyAcc2: scala.jdk.AnyAccumulator[String] = AnyAccumulator(K) + * }}} + * + * @define coll Accumulator + * @define Coll `Accumulator` + */ +object Accumulator { + implicit def toFactory[A, C](sa: Accumulator.type)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): collection.Factory[A, C] = canAccumulate.factory + + /** Creates a target $coll from an existing source collection + * + * @param source Source collection + * @tparam A the type of the ${coll}’s elements + * @tparam C the (inferred) specific type of the $coll + * @return a new $coll with the elements of `source` + */ + def from[A, C](source: IterableOnce[A])(implicit canAccumulate: AccumulatorFactoryShape[A, C]): C = + source.iterator.to(canAccumulate.factory) + + /** An empty collection + * @tparam A the type of the ${coll}'s elements + */ + def empty[A, C](implicit canAccumulate: AccumulatorFactoryShape[A, C]): C = + canAccumulate.empty + + /** Creates an $coll with the specified elements. + * @tparam A the type of the ${coll}'s elements + * @tparam C the (inferred) specific type of the $coll + * @param elems the elements of the created $coll + * @return a new $coll with elements `elems` + */ + def apply[A, C](elems: A*)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): C = + canAccumulate.factory.fromSpecific(elems) + + /** Produces an $coll containing repeated applications of a function to a start value. + * + * @param start the start value of the $coll + * @param len the number of elements contained in the $coll + * @param f the function that's repeatedly applied + * @return an $coll with `len` values in the sequence `start, f(start), f(f(start)), ...` + */ + def iterate[A, C](start: A, len: Int)(f: A => A)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): C = + from(new collection.View.Iterate(start, len)(f)) + + /** Produces an $coll that uses a function `f` to produce elements of type `A` + * and update an internal state of type `S`. + * + * @param init State initial value + * @param f Computes the next element (or returns `None` to signal + * the end of the collection) + * @tparam A Type of the elements + * @tparam S Type of the internal state + * @tparam C Type (usually inferred) of the $coll + * @return an $coll that produces elements using `f` until `f` returns `None` + */ + def unfold[A, S, C](init: S)(f: S => Option[(A, S)])(implicit canAccumulate: AccumulatorFactoryShape[A, C]): C = + from(new collection.View.Unfold(init)(f)) + + /** Produces an $coll containing a sequence of increasing of integers. + * + * @param start the first element of the $coll + * @param end the end value of the $coll (the first value NOT contained) + * @return an $coll with values `start, start + 1, ..., end - 1` + */ + def range[A: Integral, C](start: A, end: A)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): C = + from(collection.immutable.NumericRange(start, end, implicitly[Integral[A]].one)) + + /** Produces an $coll containing equally spaced values in some integer interval. + * @param start the start value of the $coll + * @param end the end value of the $coll (the first value NOT contained) + * @param step the difference between successive elements of the $coll (must be positive or negative) + * @return an $coll with values `start, start + step, ...` up to, but excluding `end` + */ + def range[A: Integral, C](start: A, end: A, step: A)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): C = + from(collection.immutable.NumericRange(start, end, step)) + + /** + * @return A builder for $Coll objects. + * @tparam A the type of the ${coll}’s elements + * @tparam C the specific type of the $coll + */ + def newBuilder[A, C](implicit canAccumulate: AccumulatorFactoryShape[A, C]): collection.mutable.Builder[A, C] = + canAccumulate.factory.newBuilder + + /** Produces an $coll containing the results of some element computation a number of times. + * @param n the number of elements contained in the $coll. + * @param elem the element computation + * @return An $coll that contains the results of `n` evaluations of `elem`. + */ + def fill[A, C](n: Int)(elem: => A)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): C = + from(new collection.View.Fill(n)(elem)) + + /** Produces a two-dimensional $coll containing the results of some element computation a number of times. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param elem the element computation + * @return An $coll that contains the results of `n1 x n2` evaluations of `elem`. + */ + def fill[A, C](n1: Int, n2: Int)(elem: => A)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): AnyAccumulator[C] = + fill(n1)(fill(n2)(elem)(canAccumulate))(AccumulatorFactoryShape.anyAccumulatorFactoryShape[C]) + + /** Produces a three-dimensional $coll containing the results of some element computation a number of times. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param elem the element computation + * @return An $coll that contains the results of `n1 x n2 x n3` evaluations of `elem`. + */ + def fill[A, C](n1: Int, n2: Int, n3: Int)(elem: => A)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): AnyAccumulator[AnyAccumulator[C]] = + fill(n1)(fill(n2, n3)(elem)(canAccumulate))(AccumulatorFactoryShape.anyAccumulatorFactoryShape[AnyAccumulator[C]]) + + /** Produces a four-dimensional $coll containing the results of some element computation a number of times. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param elem the element computation + * @return An $coll that contains the results of `n1 x n2 x n3 x n4` evaluations of `elem`. + */ + def fill[A, C](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => A)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): AnyAccumulator[AnyAccumulator[AnyAccumulator[C]]] = + fill(n1)(fill(n2, n3, n4)(elem)(canAccumulate))(AccumulatorFactoryShape.anyAccumulatorFactoryShape[AnyAccumulator[AnyAccumulator[C]]]) + + /** Produces a five-dimensional $coll containing the results of some element computation a number of times. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param n5 the number of elements in the 5th dimension + * @param elem the element computation + * @return An $coll that contains the results of `n1 x n2 x n3 x n4 x n5` evaluations of `elem`. + */ + def fill[A, C](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => A)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): AnyAccumulator[AnyAccumulator[AnyAccumulator[AnyAccumulator[C]]]] = + fill(n1)(fill(n2, n3, n4, n5)(elem)(canAccumulate))(AccumulatorFactoryShape.anyAccumulatorFactoryShape[AnyAccumulator[AnyAccumulator[AnyAccumulator[C]]]]) + + /** Produces an $coll containing values of a given function over a range of integer values starting from 0. + * @param n The number of elements in the $coll + * @param f The function computing element values + * @return An $coll consisting of elements `f(0), ..., f(n -1)` + */ + def tabulate[A, C](n: Int)(f: Int => A)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): C = + from(new collection.View.Tabulate(n)(f)) + + /** Produces a two-dimensional $coll containing values of a given function over ranges of integer values starting from 0. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param f The function computing element values + * @return An $coll consisting of elements `f(i1, i2)` + * for `0 <= i1 < n1` and `0 <= i2 < n2`. + */ + def tabulate[A, C](n1: Int, n2: Int)(f: (Int, Int) => A)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): AnyAccumulator[C] = + tabulate(n1)(i1 => tabulate(n2)(f(i1, _))(canAccumulate))(AccumulatorFactoryShape.anyAccumulatorFactoryShape[C]) + + /** Produces a three-dimensional $coll containing values of a given function over ranges of integer values starting from 0. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param f The function computing element values + * @return An $coll consisting of elements `f(i1, i2, i3)` + * for `0 <= i1 < n1`, `0 <= i2 < n2`, and `0 <= i3 < n3`. + */ + def tabulate[A, C](n1: Int, n2: Int, n3: Int)(f: (Int, Int, Int) => A)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): AnyAccumulator[AnyAccumulator[C]] = + tabulate(n1)(i1 => tabulate(n2, n3)(f(i1, _, _))(canAccumulate))(AccumulatorFactoryShape.anyAccumulatorFactoryShape[AnyAccumulator[C]]) + + /** Produces a four-dimensional $coll containing values of a given function over ranges of integer values starting from 0. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param f The function computing element values + * @return An $coll consisting of elements `f(i1, i2, i3, i4)` + * for `0 <= i1 < n1`, `0 <= i2 < n2`, `0 <= i3 < n3`, and `0 <= i4 < n4`. + */ + def tabulate[A, C](n1: Int, n2: Int, n3: Int, n4: Int)(f: (Int, Int, Int, Int) => A)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): AnyAccumulator[AnyAccumulator[AnyAccumulator[C]]] = + tabulate(n1)(i1 => tabulate(n2, n3, n4)(f(i1, _, _, _))(canAccumulate))(AccumulatorFactoryShape.anyAccumulatorFactoryShape[AnyAccumulator[AnyAccumulator[C]]]) + + /** Produces a five-dimensional $coll containing values of a given function over ranges of integer values starting from 0. + * @param n1 the number of elements in the 1st dimension + * @param n2 the number of elements in the 2nd dimension + * @param n3 the number of elements in the 3rd dimension + * @param n4 the number of elements in the 4th dimension + * @param n5 the number of elements in the 5th dimension + * @param f The function computing element values + * @return An $coll consisting of elements `f(i1, i2, i3, i4, i5)` + * for `0 <= i1 < n1`, `0 <= i2 < n2`, `0 <= i3 < n3`, `0 <= i4 < n4`, and `0 <= i5 < n5`. + */ + def tabulate[A, C](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(f: (Int, Int, Int, Int, Int) => A)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): AnyAccumulator[AnyAccumulator[AnyAccumulator[AnyAccumulator[C]]]] = + tabulate(n1)(i1 => tabulate(n2, n3, n4, n5)(f(i1, _, _, _, _))(canAccumulate))(AccumulatorFactoryShape.anyAccumulatorFactoryShape[AnyAccumulator[AnyAccumulator[AnyAccumulator[C]]]]) + + /** Concatenates all argument collections into a single $coll. + * + * @param xss the collections that are to be concatenated. + * @return the concatenation of all the collections. + */ + def concat[A, C](xss: Iterable[A]*)(implicit canAccumulate: AccumulatorFactoryShape[A, C]): C = + if (xss.isEmpty) canAccumulate.empty + else { + val b = canAccumulate.factory.newBuilder + xss.foreach(b ++= _) + b.result() + } + + /** An implicit `AccumulatorFactoryShape` is used in Accumulator factory method to return + * specialized variants according to the element type. + */ + sealed trait AccumulatorFactoryShape[A, C] { + def factory: collection.Factory[A, C] + def empty: C + } + + object AccumulatorFactoryShape extends LowPriorityAccumulatorFactoryShape { + implicit val doubleAccumulatorFactoryShape: AccumulatorFactoryShape[Double, DoubleAccumulator] = new AccumulatorFactoryShape[Double, DoubleAccumulator] { + def factory: collection.Factory[Double, DoubleAccumulator] = DoubleAccumulator + def empty: DoubleAccumulator = DoubleAccumulator.empty + } + + implicit val intAccumulatorFactoryShape: AccumulatorFactoryShape[Int, IntAccumulator] = new AccumulatorFactoryShape[Int, IntAccumulator] { + def factory: collection.Factory[Int, IntAccumulator] = IntAccumulator + def empty: IntAccumulator = IntAccumulator.empty + } + + implicit val longAccumulatorFactoryShape: AccumulatorFactoryShape[Long, LongAccumulator] = new AccumulatorFactoryShape[Long, LongAccumulator] { + def factory: collection.Factory[Long, LongAccumulator] = LongAccumulator + def empty: LongAccumulator = LongAccumulator.empty + } + + implicit val jDoubleAccumulatorFactoryShape: AccumulatorFactoryShape[jl.Double, DoubleAccumulator] = doubleAccumulatorFactoryShape.asInstanceOf[AccumulatorFactoryShape[jl.Double, DoubleAccumulator]] + implicit val jIntegerAccumulatorFactoryShape: AccumulatorFactoryShape[jl.Integer, IntAccumulator] = intAccumulatorFactoryShape.asInstanceOf[AccumulatorFactoryShape[jl.Integer, IntAccumulator]] + implicit val jLongAccumulatorFactoryShape: AccumulatorFactoryShape[jl.Long, LongAccumulator] = longAccumulatorFactoryShape.asInstanceOf[AccumulatorFactoryShape[jl.Long, LongAccumulator]] + } + + sealed trait LowPriorityAccumulatorFactoryShape { + implicit def anyAccumulatorFactoryShape[A]: AccumulatorFactoryShape[A, AnyAccumulator[A]] = anyAccumulatorFactoryShapePrototype.asInstanceOf[AccumulatorFactoryShape[A, AnyAccumulator[A]]] + + private val anyAccumulatorFactoryShapePrototype = new AccumulatorFactoryShape[AnyRef, AnyAccumulator[AnyRef]] { + def factory: collection.Factory[AnyRef, AnyAccumulator[AnyRef]] = collection.IterableFactory.toFactory(AnyAccumulator) + def empty: AnyAccumulator[AnyRef] = AnyAccumulator.empty[AnyRef] + } + } +} diff --git a/library/src/scala/jdk/AnyAccumulator.scala b/library/src/scala/jdk/AnyAccumulator.scala new file mode 100644 index 000000000000..94814594008c --- /dev/null +++ b/library/src/scala/jdk/AnyAccumulator.scala @@ -0,0 +1,381 @@ +/* + * 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.jdk + +import scala.language.`2.13` +import java.io.{ObjectInputStream, ObjectOutputStream} +import java.util.Spliterator +import java.util.function.Consumer + +import scala.collection.Stepper.EfficientSplit +import scala.collection.{AnyStepper, Factory, IterableFactoryDefaults, SeqFactory, Stepper, StepperShape, mutable} +import scala.reflect.ClassTag + +/** An Accumulator for arbitrary element types, see [[Accumulator]]. */ +final class AnyAccumulator[A] + extends Accumulator[A, AnyAccumulator, AnyAccumulator[A]] + with mutable.SeqOps[A, AnyAccumulator, AnyAccumulator[A]] + with IterableFactoryDefaults[A, AnyAccumulator] + with Serializable { + private[jdk] var current: Array[AnyRef] = AnyAccumulator.emptyAnyRefArray + private[jdk] var history: Array[Array[AnyRef]] = AnyAccumulator.emptyAnyRefArrayArray + private[jdk] var cumul: Array[Long] = AnyAccumulator.emptyLongArray + + private[jdk] def cumulative(i: Int): Long = cumul(i) + + override protected[this] def className: String = "AnyAccumulator" + + def efficientStepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit = + shape.parUnbox(new AnyAccumulatorStepper[A](this.asInstanceOf[AnyAccumulator[A]])) + + private def expand(): Unit = { + if (index > 0) { + if (hIndex >= history.length) hExpand() + history(hIndex) = current + cumul(hIndex) = (if (hIndex > 0) cumulative(hIndex-1) else 0) + index + hIndex += 1 + } + current = new Array[AnyRef](nextBlockSize) + index = 0 + } + + private def hExpand(): Unit = { + if (hIndex == 0) { + history = new Array[Array[AnyRef]](4) + cumul = new Array[Long](4) + } + else { + history = java.util.Arrays.copyOf(history, history.length << 1) + cumul = java.util.Arrays.copyOf(cumul, cumul.length << 1) + } + } + + /** Appends an element to this `AnyAccumulator`. */ + def addOne(a: A): this.type = { + totalSize += 1 + if (index >= current.length) expand() + current(index) = a.asInstanceOf[AnyRef] + index += 1 + this + } + + /** Result collection consisting of all elements appended so far. */ + override def result(): AnyAccumulator[A] = this + + /** Removes all elements from `that` and appends them to this `AnyAccumulator`. */ + def drain[A1 <: A](that: AnyAccumulator[A1]): Unit = { + var h = 0 + var prev = 0L + var more = true + while (more && h < that.hIndex) { + val n = (that.cumulative(h) - prev).toInt + if (current.length - index >= n) { + System.arraycopy(that.history(h), 0, current, index, n) + prev = that.cumulative(h) + index += n + h += 1 + } + else more = false + } + if (h >= that.hIndex && current.length - index >= that.index) { + if (that.index > 0) System.arraycopy(that.current, 0, current, index, that.index) + index += that.index + } + else { + val slots = (if (index > 0) 1 else 0) + that.hIndex - h + if (hIndex + slots > history.length) { + val n = math.max(4, 1 << (32 - java.lang.Integer.numberOfLeadingZeros(1 + hIndex + slots))) + history = java.util.Arrays.copyOf(history, n) + cumul = java.util.Arrays.copyOf(cumul, n) + } + var pv = if (hIndex > 0) cumulative(hIndex-1) else 0L + if (index > 0) { + pv += index + cumul(hIndex) = pv + history(hIndex) = if (index < (current.length >>> 3) && current.length > 32) java.util.Arrays.copyOf(current, index) else current + hIndex += 1 + } + while (h < that.hIndex) { + pv += that.cumulative(h) - prev + prev = that.cumulative(h) + cumul(hIndex) = pv + history(hIndex) = that.history(h) + h += 1 + hIndex += 1 + } + index = that.index + current = that.current + } + totalSize += that.totalSize + that.clear() + } + + override def clear(): Unit = { + super.clear() + current = AnyAccumulator.emptyAnyRefArray + history = AnyAccumulator.emptyAnyRefArrayArray + cumul = AnyAccumulator.emptyLongArray + } + + /** Retrieves the `ix`th element. */ + def apply(ix: Long): A = { + if (totalSize - ix <= index || hIndex == 0) current((ix - (totalSize - index)).toInt).asInstanceOf[A] + else { + val w = seekSlot(ix) + history((w >>> 32).toInt)((w & 0xFFFFFFFFL).toInt).asInstanceOf[A] + } + } + + /** Retrieves the `ix`th element, using an `Int` index. */ + def apply(i: Int): A = apply(i.toLong) + + def update(idx: Long, elem: A): Unit = { + if (totalSize - idx <= index || hIndex == 0) current((idx - (totalSize - index)).toInt) = elem.asInstanceOf[AnyRef] + else { + val w = seekSlot(idx) + history((w >>> 32).toInt)((w & 0xFFFFFFFFL).toInt) = elem.asInstanceOf[AnyRef] + } + } + + def update(idx: Int, elem: A): Unit = update(idx.toLong, elem) + + /** Returns an `Iterator` over the contents of this `AnyAccumulator`. */ + def iterator: Iterator[A] = stepper.iterator + + def countLong(p: A => Boolean): Long = { + var r = 0L + val s = stepper + while (s.hasStep) + if (p(s.nextStep())) r += 1 + r + } + + /** Copy the elements in this `AnyAccumulator` into an `Array` */ + override def toArray[B >: A : ClassTag]: Array[B] = { + if (totalSize > Int.MaxValue) throw new IllegalArgumentException("Too many elements accumulated for an array: "+totalSize.toString) + val a = new Array[B](totalSize.toInt) + var j = 0 + var h = 0 + var pv = 0L + while (h < hIndex) { + val x = history(h) + val n = cumulative(h) - pv + pv = cumulative(h) + var i = 0 + while (i < n) { + a(j) = x(i).asInstanceOf[B] + i += 1 + j += 1 + } + h += 1 + } + var i = 0 + while (i < index) { + a(j) = current(i).asInstanceOf[B] + i += 1 + j += 1 + } + a + } + + /** Copies the elements in this `AnyAccumulator` to a `List` */ + override def toList: List[A] = { + var ans: List[A] = Nil + var i = index - 1 + while (i >= 0) { + ans = current(i).asInstanceOf[A] :: ans + i -= 1 + } + var h = hIndex - 1 + while (h >= 0) { + val a = history(h) + i = (cumulative(h) - (if (h == 0) 0L else cumulative(h-1))).toInt - 1 + while (i >= 0) { + ans = a(i).asInstanceOf[A] :: ans + i -= 1 + } + h -= 1 + } + ans + } + + /** + * Copy the elements in this `AnyAccumulator` to a specified collection. Example use: + * `acc.to(Vector)`. + */ + override def to[C1](factory: Factory[A, C1]): C1 = { + if (totalSize > Int.MaxValue) throw new IllegalArgumentException("Too many elements accumulated for a Scala collection: "+totalSize.toString) + factory.fromSpecific(iterator) + } + + override def iterableFactory: SeqFactory[AnyAccumulator] = AnyAccumulator + + private def writeReplace(): AnyRef = new AnyAccumulator.SerializationProxy(this) +} + +object AnyAccumulator extends collection.SeqFactory[AnyAccumulator] { + private val emptyAnyRefArray = new Array[AnyRef](0) + private val emptyAnyRefArrayArray = new Array[Array[AnyRef]](0) + private val emptyLongArray = new Array[Long](0) + + import java.util.{function => jf} + + /** A `Supplier` of `AnyAccumulator`s, suitable for use with `java.util.stream.Stream`'s `collect` method. */ + def supplier[A]: jf.Supplier[AnyAccumulator[A]] = () => new AnyAccumulator[A] + + /** A `BiConsumer` that adds an element to an `AnyAccumulator`, suitable for use with `java.util.stream.Stream`'s `collect` method. */ + def adder[A]: jf.BiConsumer[AnyAccumulator[A], A] = (ac: AnyAccumulator[A], a: A) => ac addOne a + + /** A `BiConsumer` that adds an `Int` to an `AnyAccumulator`, suitable for use with `java.util.stream.Stream`'s `collect` method. */ + def unboxedIntAdder: jf.ObjIntConsumer[AnyAccumulator[Int]] = (ac: AnyAccumulator[Int], a: Int) => ac addOne a + + /** A `BiConsumer` that adds a `Long` to an `AnyAccumulator`, suitable for use with `java.util.stream.Stream`'s `collect` method. */ + def unboxedLongAdder: jf.ObjLongConsumer[AnyAccumulator[Long]] = (ac: AnyAccumulator[Long], a: Long) => ac addOne a + + /** A `BiConsumer` that adds a `Double` to an `AnyAccumulator`, suitable for use with `java.util.stream.Stream`'s `collect` method. */ + def unboxedDoubleAdder: jf.ObjDoubleConsumer[AnyAccumulator[Double]] = (ac: AnyAccumulator[Double], a: Double) => ac addOne a + + /** A `BiConsumer` that merges `AnyAccumulator`s, suitable for use with `java.util.stream.Stream`'s `collect` method. */ + def merger[A]: jf.BiConsumer[AnyAccumulator[A], AnyAccumulator[A]] = (a1: AnyAccumulator[A], a2: AnyAccumulator[A]) => a1 drain a2 + + def from[A](source: IterableOnce[A]): AnyAccumulator[A] = source match { + case acc: AnyAccumulator[A] => acc + case _ => new AnyAccumulator[A].addAll(source) + } + + def empty[A]: AnyAccumulator[A] = new AnyAccumulator[A] + + def newBuilder[A]: mutable.Builder[A, AnyAccumulator[A]] = new AnyAccumulator[A] + + class SerializationProxy[A](@transient private val acc: AnyAccumulator[A]) extends Serializable { + @transient private var result: AnyAccumulator[AnyRef] = _ + + private def writeObject(out: ObjectOutputStream): Unit = { + out.defaultWriteObject() + val size = acc.sizeLong + out.writeLong(size) + val st = acc.stepper + while (st.hasStep) + out.writeObject(st.nextStep()) + } + + private def readObject(in: ObjectInputStream): Unit = { + in.defaultReadObject() + val res = new AnyAccumulator[AnyRef]() + var elems = in.readLong() + while (elems > 0) { + res += in.readObject() + elems -= 1L + } + result = res + } + + private def readResolve(): AnyRef = result + } +} + +private[jdk] class AnyAccumulatorStepper[A](private[this] val acc: AnyAccumulator[A]) extends AnyStepper[A] with EfficientSplit { + import java.util.Spliterator._ + + private var h: Int = 0 + private var i: Int = 0 + private var a: Array[AnyRef] = if (acc.hIndex > 0) acc.history(0) else acc.current + private var n: Long = if (acc.hIndex > 0) acc.cumulative(0) else acc.index + private var N: Long = acc.totalSize + + private def duplicateSelf(limit: Long): AnyAccumulatorStepper[A] = { + val ans = new AnyAccumulatorStepper(acc) + ans.h = h + ans.i = i + ans.a = a + ans.n = n + ans.N = limit + ans + } + + private def loadMore(): Unit = { + h += 1 + if (h < acc.hIndex) { a = acc.history(h); n = acc.cumulative(h) - acc.cumulative(h-1) } + else { a = acc.current; n = acc.index } + i = 0 + } + + def characteristics: Int = ORDERED | SIZED | SUBSIZED + + def estimateSize: Long = N + + def hasStep: Boolean = N > 0 + + def nextStep(): A = + if (N <= 0) throw new NoSuchElementException("Next in empty Stepper") + else { + if (i >= n) loadMore() + val ans = a(i).asInstanceOf[A] + i += 1 + N -= 1 + ans + } + + def trySplit(): AnyStepper[A] = + if (N <= 1) null + else { + val half = N >> 1 + val M = (if (h <= 0) 0L else acc.cumulative(h-1)) + i + val R = M + half + val ans = duplicateSelf(half) + if (h < acc.hIndex) { + val w = acc.seekSlot(R) + h = (w >>> 32).toInt + if (h < acc.hIndex) { + a = acc.history(h) + n = acc.cumulative(h) - (if (h > 0) acc.cumulative(h-1) else 0) + } + else { + a = acc.current + n = acc.index + } + i = (w & 0xFFFFFFFFL).toInt + } + else i += half.toInt + N -= half + ans + } + + override def spliterator[B >: A]: Spliterator[B] = new AnyStepper.AnyStepperSpliterator[B](this) { + // Overridden for efficiency + override def tryAdvance(c: Consumer[_ >: B]): Boolean = { + if (N <= 0) false + else { + if (i >= n) loadMore() + c.accept(a(i).asInstanceOf[B]) + i += 1 + N -= 1 + true + } + } + + // Overridden for efficiency + override def forEachRemaining(f: java.util.function.Consumer[_ >: B]): Unit = { + while (N > 0) { + if (i >= n) loadMore() + val i0 = i + if ((n-i) > N) n = i + N.toInt + while (i < n) { + f.accept(a(i).asInstanceOf[B]) + i += 1 + } + N -= (n - i0) + } + } + } +} diff --git a/library/src/scala/jdk/CollectionConverters.scala b/library/src/scala/jdk/CollectionConverters.scala new file mode 100644 index 000000000000..269d93ebd00c --- /dev/null +++ b/library/src/scala/jdk/CollectionConverters.scala @@ -0,0 +1,96 @@ +/* + * 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.jdk + +import scala.language.`2.13` +import scala.collection.convert.{AsJavaExtensions, AsScalaExtensions} + +/** This object provides extension methods that convert between Scala and Java collections. + * + * When writing Java code, use the explicit conversion methods defined in + * [[javaapi.CollectionConverters]] instead. + * + * Note: to create [[java.util.stream.Stream Java Streams]] that operate on Scala collections + * (sequentially or in parallel), use [[StreamConverters]]. + * + * {{{ + * import scala.jdk.CollectionConverters._ + * val s: java.util.Set[String] = Set("one", "two").asJava + * }}} + * + * The conversions return adapters for the corresponding API, i.e., the collections are wrapped, + * not copied. Changes to the original collection are reflected in the view, and vice versa: + * + * {{{ + * scala> import scala.jdk.CollectionConverters._ + * + * scala> val s = collection.mutable.Set("one") + * s: scala.collection.mutable.Set[String] = HashSet(one) + * + * scala> val js = s.asJava + * js: java.util.Set[String] = [one] + * + * scala> js.add("two") + * + * scala> s + * res2: scala.collection.mutable.Set[String] = HashSet(two, one) + * }}} + * + * The following conversions are supported via `asScala` and `asJava`: + * + * {{{ + * scala.collection.Iterable <=> java.lang.Iterable + * scala.collection.Iterator <=> java.util.Iterator + * scala.collection.mutable.Buffer <=> java.util.List + * scala.collection.mutable.Set <=> java.util.Set + * scala.collection.mutable.Map <=> java.util.Map + * scala.collection.concurrent.Map <=> java.util.concurrent.ConcurrentMap + * }}} + * + * The following conversions are supported via `asScala` and through + * specially-named extension methods to convert to Java collections, as shown: + * + * {{{ + * scala.collection.Iterable <=> java.util.Collection (via asJavaCollection) + * scala.collection.Iterator <=> java.util.Enumeration (via asJavaEnumeration) + * scala.collection.mutable.Map <=> java.util.Dictionary (via asJavaDictionary) + * }}} + * + * In addition, the following one-way conversions are provided via `asJava`: + * + * {{{ + * scala.collection.Seq => java.util.List + * scala.collection.mutable.Seq => java.util.List + * scala.collection.Set => java.util.Set + * scala.collection.Map => java.util.Map + * }}} + * + * The following one way conversion is provided via `asScala`: + * + * {{{ + * java.util.Properties => scala.collection.mutable.Map + * }}} + * + * In all cases, converting from a source type to a target type and back + * again will return the original source object. For example: + * + * {{{ + * import scala.jdk.CollectionConverters._ + * + * val source = new scala.collection.mutable.ListBuffer[Int] + * val target: java.util.List[Int] = source.asJava + * val other: scala.collection.mutable.Buffer[Int] = target.asScala + * assert(source eq other) + * }}} + */ +object CollectionConverters extends AsJavaExtensions with AsScalaExtensions diff --git a/library/src/scala/jdk/DoubleAccumulator.scala b/library/src/scala/jdk/DoubleAccumulator.scala new file mode 100644 index 000000000000..403f877364c4 --- /dev/null +++ b/library/src/scala/jdk/DoubleAccumulator.scala @@ -0,0 +1,489 @@ +/* + * 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.jdk + +import scala.language.`2.13` +import java.io.{ObjectInputStream, ObjectOutputStream} +import java.util.Spliterator +import java.util.function.{Consumer, DoubleConsumer} +import java.{lang => jl} + +import scala.annotation._ +import scala.collection.Stepper.EfficientSplit +import scala.collection.{AnyStepper, DoubleStepper, Factory, SeqFactory, Stepper, StepperShape, mutable} +import scala.language.implicitConversions + +/** A specialized Accumulator that holds `Double`s without boxing, see [[Accumulator]]. */ +final class DoubleAccumulator + extends Accumulator[Double, AnyAccumulator, DoubleAccumulator] + with mutable.SeqOps[Double, AnyAccumulator, DoubleAccumulator] + with Serializable { + private[jdk] var current: Array[Double] = DoubleAccumulator.emptyDoubleArray + private[jdk] var history: Array[Array[Double]] = DoubleAccumulator.emptyDoubleArrayArray + + private[jdk] def cumulative(i: Int) = { val x = history(i); x(x.length-1).toLong } + + override protected[this] def className: String = "DoubleAccumulator" + + def efficientStepper[S <: Stepper[_]](implicit shape: StepperShape[Double, S]): S with EfficientSplit = { + val st = new DoubleAccumulatorStepper(this) + val r = + if (shape.shape == StepperShape.DoubleShape) st + else { + assert(shape.shape == StepperShape.ReferenceShape, s"unexpected StepperShape: $shape") + AnyStepper.ofParDoubleStepper(st) + } + r.asInstanceOf[S with EfficientSplit] + } + + private def expand(): Unit = { + if (index > 0) { + current(current.length-1) = (if (hIndex > 0) { val x = history(hIndex-1); x(x.length-1) } else 0) + index + if (hIndex >= history.length) hExpand() + history(hIndex) = current + hIndex += 1 + } + current = new Array[Double](nextBlockSize+1) + index = 0 + } + + private def hExpand(): Unit = { + if (hIndex == 0) history = new Array[Array[Double]](4) + else history = java.util.Arrays.copyOf(history, history.length << 1) + } + + /** Appends an element to this `DoubleAccumulator`. */ + def addOne(a: Double): this.type = { + totalSize += 1 + if (index+1 >= current.length) expand() + current(index) = a + index += 1 + this + } + + /** Result collection consisting of all elements appended so far. */ + override def result(): DoubleAccumulator = this + + /** Removes all elements from `that` and appends them to this `DoubleAccumulator`. */ + def drain(that: DoubleAccumulator): Unit = { + var h = 0 + var prev = 0L + var more = true + while (more && h < that.hIndex) { + val cuml = that.cumulative(h) + val n = (cuml - prev).toInt + if (current.length - index - 1 >= n) { + System.arraycopy(that.history(h), 0, current, index, n) + prev = cuml + index += n + h += 1 + } + else more = false + } + if (h >= that.hIndex && current.length - index - 1>= that.index) { + if (that.index > 0) System.arraycopy(that.current, 0, current, index, that.index) + index += that.index + } + else { + val slots = (if (index > 0) 1 else 0) + that.hIndex - h + if (hIndex + slots > history.length) { + val n = math.max(4, 1 << (32 - jl.Integer.numberOfLeadingZeros(1 + hIndex + slots))) + history = java.util.Arrays.copyOf(history, n) + } + var pv = if (hIndex > 0) cumulative(hIndex-1) else 0L + if (index > 0) { + val x = + if (index < (current.length >>> 3) && current.length - 1 > 32) { + val ans = java.util.Arrays.copyOf(current, index + 1) + ans(ans.length - 1) = current(current.length - 1) + ans + } + else current + pv = pv + index + x(x.length - 1) = pv.toDouble // see comment on Accumulator.cumulative + history(hIndex) = x + hIndex += 1 + } + while (h < that.hIndex) { + val cuml = that.cumulative(h) + pv = pv + cuml - prev + prev = cuml + val x = that.history(h) + x(x.length - 1) = pv.toDouble // see comment on Accumulator.cumulative + history(hIndex) = x + h += 1 + hIndex += 1 + } + index = that.index + current = that.current + } + totalSize += that.totalSize + that.clear() + } + + override def clear(): Unit = { + super.clear() + current = DoubleAccumulator.emptyDoubleArray + history = DoubleAccumulator.emptyDoubleArrayArray + } + + /** Retrieves the `ix`th element. */ + def apply(ix: Long): Double = { + if (totalSize - ix <= index || hIndex == 0) current((ix - (totalSize - index)).toInt) + else { + val w = seekSlot(ix) + history((w >>> 32).toInt)((w & 0xFFFFFFFFL).toInt) + } + } + + /** Retrieves the `ix`th element, using an `Int` index. */ + def apply(i: Int): Double = apply(i.toLong) + + def update(idx: Long, elem: Double): Unit = { + if (totalSize - idx <= index || hIndex == 0) current((idx - (totalSize - index)).toInt) = elem + else { + val w = seekSlot(idx) + history((w >>> 32).toInt)((w & 0xFFFFFFFFL).toInt) = elem + } + } + + def update(idx: Int, elem: Double): Unit = update(idx.toLong, elem) + + /** Returns an `Iterator` over the contents of this `DoubleAccumulator`. The `Iterator` is not specialized. */ + def iterator: Iterator[Double] = stepper.iterator + + override def foreach[U](f: Double => U): Unit = { + val s = stepper + while (s.hasStep) f(s.nextStep()) + } + + def map(f: Double => Double): DoubleAccumulator = { + val b = newSpecificBuilder + val s = stepper + while (s.hasStep) + b.addOne(f(s.nextStep())) + b.result() + } + + def flatMap(f: Double => IterableOnce[Double]): DoubleAccumulator = { + val b = newSpecificBuilder + val s = stepper + while (s.hasStep) + b.addAll(f(s.nextStep())) + b.result() + } + + def collect(pf: PartialFunction[Double, Double]): DoubleAccumulator = { + val b = newSpecificBuilder + val s = stepper + while (s.hasStep) { + val n = s.nextStep() + pf.runWith(b.addOne)(n) + } + b.result() + } + + private def filterAccImpl(pred: Double => Boolean, not: Boolean): DoubleAccumulator = { + val b = newSpecificBuilder + val s = stepper + while (s.hasStep) { + val n = s.nextStep() + if (pred(n) != not) b.addOne(n) + } + b.result() + } + + override def filter(pred: Double => Boolean): DoubleAccumulator = filterAccImpl(pred, not = false) + + override def filterNot(pred: Double => Boolean): DoubleAccumulator = filterAccImpl(pred, not = true) + + override def forall(p: Double => Boolean): Boolean = { + val s = stepper + while (s.hasStep) + if (!p(s.nextStep())) return false + true + } + + override def exists(p: Double => Boolean): Boolean = { + val s = stepper + while (s.hasStep) + if (p(s.nextStep())) return true + false + } + + override def count(p: Double => Boolean): Int = { + var r = 0 + val s = stepper + while (s.hasStep) + if (p(s.nextStep())) r += 1 + r + } + + def countLong(p: Double => Boolean): Long = { + var r = 0L + val s = stepper + while (s.hasStep) + if (p(s.nextStep())) r += 1 + r + } + + /** Copies the elements in this `DoubleAccumulator` into an `Array[Double]` */ + @nowarn // cat=lint-overload see toArray[B: ClassTag] + def toArray: Array[Double] = { + if (totalSize > Int.MaxValue) throw new IllegalArgumentException("Too many elements accumulated for an array: "+totalSize.toString) + val a = new Array[Double](totalSize.toInt) + var j = 0 + var h = 0 + var pv = 0L + while (h < hIndex) { + val x = history(h) + val cuml = x(x.length-1).toLong + val n = (cuml - pv).toInt + pv = cuml + System.arraycopy(x, 0, a, j, n) + j += n + h += 1 + } + System.arraycopy(current, 0, a, j, index) + j += index + a + } + + /** Copies the elements in this `DoubleAccumulator` to a `List` */ + override def toList: List[Double] = { + var ans: List[Double] = Nil + var i = index - 1 + while (i >= 0) { + ans = current(i) :: ans + i -= 1 + } + var h = hIndex - 1 + while (h >= 0) { + val a = history(h) + i = (cumulative(h) - (if (h == 0) 0L else cumulative(h-1))).toInt - 1 + while (i >= 0) { + ans = a(i) :: ans + i -= 1 + } + h -= 1 + } + ans + } + + /** + * Copy the elements in this `DoubleAccumulator` to a specified collection. + * Note that the target collection is not specialized. + * Usage example: `acc.to(Vector)` + */ + override def to[C1](factory: Factory[Double, C1]): C1 = { + if (totalSize > Int.MaxValue) throw new IllegalArgumentException("Too many elements accumulated for a Scala collection: "+totalSize.toString) + factory.fromSpecific(iterator) + } + + override protected def fromSpecific(coll: IterableOnce[Double]): DoubleAccumulator = DoubleAccumulator.fromSpecific(coll) + override protected def newSpecificBuilder: DoubleAccumulator = DoubleAccumulator.newBuilder + override def iterableFactory: SeqFactory[AnyAccumulator] = AnyAccumulator + + override def empty: DoubleAccumulator = DoubleAccumulator.empty + + private def writeReplace(): AnyRef = new DoubleAccumulator.SerializationProxy(this) +} + +object DoubleAccumulator extends collection.SpecificIterableFactory[Double, DoubleAccumulator] { + private val emptyDoubleArray = new Array[Double](0) + private val emptyDoubleArrayArray = new Array[Array[Double]](0) + + implicit def toJavaDoubleAccumulator(ia: DoubleAccumulator.type): collection.SpecificIterableFactory[jl.Double, DoubleAccumulator] = DoubleAccumulator.asInstanceOf[collection.SpecificIterableFactory[jl.Double, DoubleAccumulator]] + + import java.util.{function => jf} + + /** A `Supplier` of `DoubleAccumulator`s, suitable for use with `java.util.stream.DoubleStream`'s `collect` method. Suitable for `Stream[Double]` also. */ + def supplier: jf.Supplier[DoubleAccumulator] = () => new DoubleAccumulator + + /** A `BiConsumer` that adds an element to an `DoubleAccumulator`, suitable for use with `java.util.stream.DoubleStream`'s `collect` method. */ + def adder: jf.ObjDoubleConsumer[DoubleAccumulator] = (ac: DoubleAccumulator, a: Double) => ac addOne a + + /** A `BiConsumer` that adds a boxed `Double` to an `DoubleAccumulator`, suitable for use with `java.util.stream.Stream`'s `collect` method. */ + def boxedAdder: jf.BiConsumer[DoubleAccumulator, Double] = (ac: DoubleAccumulator, a: Double) => ac addOne a + + /** A `BiConsumer` that merges `DoubleAccumulator`s, suitable for use with `java.util.stream.DoubleStream`'s `collect` method. Suitable for `Stream[Double]` also. */ + def merger: jf.BiConsumer[DoubleAccumulator, DoubleAccumulator] = (a1: DoubleAccumulator, a2: DoubleAccumulator) => a1 drain a2 + + private def fromArray(a: Array[Double]): DoubleAccumulator = { + val r = new DoubleAccumulator + var i = 0 + while (i < a.length) { r addOne a(i); i += 1 } + r + } + + override def fromSpecific(it: IterableOnce[Double]): DoubleAccumulator = it match { + case acc: DoubleAccumulator => acc + case as: collection.immutable.ArraySeq.ofDouble => fromArray(as.unsafeArray) + case as: collection.mutable.ArraySeq.ofDouble => fromArray(as.array) // this case ensures Array(1).to(Accumulator) doesn't box + case _ => (new DoubleAccumulator).addAll(it) + } + + override def empty: DoubleAccumulator = new DoubleAccumulator + + override def newBuilder: DoubleAccumulator = new DoubleAccumulator + + class SerializationProxy[A](@transient private val acc: DoubleAccumulator) extends Serializable { + @transient private var result: DoubleAccumulator = _ + + private def writeObject(out: ObjectOutputStream): Unit = { + out.defaultWriteObject() + val size = acc.sizeLong + out.writeLong(size) + val st = acc.stepper + while (st.hasStep) + out.writeDouble(st.nextStep()) + } + + private def readObject(in: ObjectInputStream): Unit = { + in.defaultReadObject() + val res = new DoubleAccumulator() + var elems = in.readLong() + while (elems > 0) { + res += in.readDouble() + elems -= 1L + } + result = res + } + + private def readResolve(): AnyRef = result + } +} + +private[jdk] class DoubleAccumulatorStepper(private val acc: DoubleAccumulator) extends DoubleStepper with EfficientSplit { + import java.util.Spliterator._ + + private var h: Int = 0 + private var i: Int = 0 + private var a: Array[Double] = if (acc.hIndex > 0) acc.history(0) else acc.current + private var n: Long = if (acc.hIndex > 0) acc.cumulative(0) else acc.index + private var N: Long = acc.totalSize + + private def duplicateSelf(limit: Long): DoubleAccumulatorStepper = { + val ans = new DoubleAccumulatorStepper(acc) + ans.h = h + ans.i = i + ans.a = a + ans.n = n + ans.N = limit + ans + } + + private def loadMore(): Unit = { + h += 1 + if (h < acc.hIndex) { a = acc.history(h); n = acc.cumulative(h) - acc.cumulative(h-1) } + else { a = acc.current; n = acc.index } + i = 0 + } + + def characteristics: Int = ORDERED | SIZED | SUBSIZED | NONNULL + + def estimateSize: Long = N + + def hasStep: Boolean = N > 0 + + def nextStep(): Double = + if (n <= 0) throw new NoSuchElementException("next on empty Stepper") + else { + if (i >= n) loadMore() + val ans = a(i) + i += 1 + N -= 1 + ans + } + + def trySplit(): DoubleStepper = + if (N <= 1) null + else { + val half = N >> 1 + val M = (if (h <= 0) 0L else acc.cumulative(h-1)) + i + val R = M + half + val ans = duplicateSelf(half) + if (h < acc.hIndex) { + val w = acc.seekSlot(R) + h = (w >>> 32).toInt + if (h < acc.hIndex) { + a = acc.history(h) + n = acc.cumulative(h) - (if (h > 0) acc.cumulative(h-1) else 0) + } + else { + a = acc.current + n = acc.index + } + i = (w & 0xFFFFFFFFL).toInt + } + else i += half.toInt + N -= half + ans + } + + override def spliterator[B >: Double]: Spliterator.OfDouble = new DoubleStepper.DoubleStepperSpliterator(this) { + // Overridden for efficiency + override def tryAdvance(c: DoubleConsumer): Boolean = + if (N <= 0) false + else { + if (i >= n) loadMore() + c.accept(a(i)) + i += 1 + N -= 1 + true + } + + // Overridden for efficiency + override def tryAdvance(c: Consumer[_ >: jl.Double]): Boolean = (c: AnyRef) match { + case ic: DoubleConsumer => tryAdvance(ic) + case _ => + if (N <= 0) false + else { + if (i >= n) loadMore() + c.accept(a(i)) + i += 1 + N -= 1 + true + } + } + + // Overridden for efficiency + override def forEachRemaining(c: DoubleConsumer): Unit = + while (N > 0) { + if (i >= n) loadMore() + val i0 = i + if ((n-i) > N) n = i + N.toInt + while (i < n) { + c.accept(a(i)) + i += 1 + } + N -= (n - i0) + } + + // Overridden for efficiency + override def forEachRemaining(c: Consumer[_ >: jl.Double]): Unit = (c: AnyRef) match { + case ic: DoubleConsumer => forEachRemaining(ic) + case _ => + while (N > 0) { + if (i >= n) loadMore() + val i0 = i + if ((n-i) > N) n = i + N.toInt + while (i < n) { + c.accept(a(i)) + i += 1 + } + N -= (n - i0) + } + } + } +} diff --git a/library/src/scala/jdk/DurationConverters.scala b/library/src/scala/jdk/DurationConverters.scala new file mode 100644 index 000000000000..bca4e6a512bb --- /dev/null +++ b/library/src/scala/jdk/DurationConverters.scala @@ -0,0 +1,35 @@ +/* + * 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.jdk + +import scala.language.`2.13` +import java.time.{Duration => JDuration} + +import scala.concurrent.duration.FiniteDuration + +/** This object provides extension methods that convert between Scala and Java duration types. + * + * When writing Java code, use the explicit conversion methods defined in + * [[javaapi.DurationConverters]] instead. + */ +object DurationConverters { + implicit class JavaDurationOps(private val duration: JDuration) extends AnyVal { + /** Convert a Java duration to a Scala duration, see [[javaapi.DurationConverters.toScala]]. */ + def toScala: FiniteDuration = javaapi.DurationConverters.toScala(duration) + } + + implicit final class ScalaDurationOps(private val duration: FiniteDuration) extends AnyVal { + /** Convert a Scala duration to a Java duration, see [[javaapi.DurationConverters.toJava]]. */ + def toJava: JDuration = javaapi.DurationConverters.toJava(duration) + } +} diff --git a/library/src/scala/jdk/FunctionConverters.scala b/library/src/scala/jdk/FunctionConverters.scala new file mode 100644 index 000000000000..2332d3f2ac74 --- /dev/null +++ b/library/src/scala/jdk/FunctionConverters.scala @@ -0,0 +1,54 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. + + +package scala.jdk + +import scala.language.`2.13` + +/** This object provides extension methods that convert between Scala and Java function types. + * + * When writing Java code, use the explicit conversion methods defined in + * [[javaapi.FunctionConverters]] instead. + * + * Using the `.asJava` extension method on a Scala function produces the most specific possible + * Java function type: + * + * {{{ + * scala> import scala.jdk.FunctionConverters._ + * scala> val f = (x: Int) => x + 1 + * + * scala> val jf1 = f.asJava + * jf1: java.util.function.IntUnaryOperator = ... + * }}} + * + * More generic Java function types can be created using the corresponding `asJavaXYZ` extension + * method: + * + * {{{ + * scala> val jf2 = f.asJavaFunction + * jf2: java.util.function.Function[Int,Int] = ... + * + * scala> val jf3 = f.asJavaUnaryOperator + * jf3: java.util.function.UnaryOperator[Int] = ... + * }}} + * + * Converting a Java function to Scala is done using the `asScala` extension method: + * + * {{{ + * scala> List(1,2,3).map(jf2.asScala) + * res1: List[Int] = List(2, 3, 4) + * }}} + */ +object FunctionConverters extends Priority0FunctionExtensions diff --git a/library/src/scala/jdk/FunctionExtensions.scala b/library/src/scala/jdk/FunctionExtensions.scala new file mode 100644 index 000000000000..9f97426bbe12 --- /dev/null +++ b/library/src/scala/jdk/FunctionExtensions.scala @@ -0,0 +1,221 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. + + +package scala.jdk + +import scala.language.`2.13` +import language.implicitConversions + +trait Priority3FunctionExtensions { + import FunctionWrappers._ + + @inline implicit def enrichAsJavaBiFunction[T, U, R](sf: scala.Function2[T, U, R]): RichFunction2AsBiFunction[T, U, R] = new RichFunction2AsBiFunction[T, U, R](sf) +} + + + +import language.implicitConversions + +trait Priority2FunctionExtensions extends Priority3FunctionExtensions { + import FunctionWrappers._ + + @inline implicit def enrichAsJavaBiConsumer[T, U](sf: scala.Function2[T, U, Unit]): RichFunction2AsBiConsumer[T, U] = new RichFunction2AsBiConsumer[T, U](sf) + + @inline implicit def enrichAsJavaBiPredicate[T, U](sf: scala.Function2[T, U, Boolean]): RichFunction2AsBiPredicate[T, U] = new RichFunction2AsBiPredicate[T, U](sf) + + @inline implicit def enrichAsJavaFunction[T, R](sf: scala.Function1[T, R]): RichFunction1AsFunction[T, R] = new RichFunction1AsFunction[T, R](sf) + + @inline implicit def enrichAsJavaToDoubleBiFunction[T, U](sf: scala.Function2[T, U, Double]): RichFunction2AsToDoubleBiFunction[T, U] = new RichFunction2AsToDoubleBiFunction[T, U](sf) + + @inline implicit def enrichAsJavaToIntBiFunction[T, U](sf: scala.Function2[T, U, Int]): RichFunction2AsToIntBiFunction[T, U] = new RichFunction2AsToIntBiFunction[T, U](sf) + + @inline implicit def enrichAsJavaToLongBiFunction[T, U](sf: scala.Function2[T, U, Long]): RichFunction2AsToLongBiFunction[T, U] = new RichFunction2AsToLongBiFunction[T, U](sf) +} + + + +import language.implicitConversions + +trait Priority1FunctionExtensions extends Priority2FunctionExtensions { + import FunctionWrappers._ + + @inline implicit def enrichAsJavaBinaryOperator[T, A1, A2](sf: scala.Function2[T, A1, A2])(implicit evA1: =:=[A1, T], evA2: =:=[A2, T]): RichFunction2AsBinaryOperator[T] = new RichFunction2AsBinaryOperator[T](sf.asInstanceOf[scala.Function2[T, T, T]]) + + @inline implicit def enrichAsJavaConsumer[T](sf: scala.Function1[T, Unit]): RichFunction1AsConsumer[T] = new RichFunction1AsConsumer[T](sf) + + @inline implicit def enrichAsJavaDoubleFunction[A0, R](sf: scala.Function1[A0, R])(implicit evA0: =:=[A0, Double]): RichFunction1AsDoubleFunction[R] = new RichFunction1AsDoubleFunction[R](sf.asInstanceOf[scala.Function1[Double, R]]) + + @inline implicit def enrichAsJavaIntFunction[A0, R](sf: scala.Function1[A0, R])(implicit evA0: =:=[A0, Int]): RichFunction1AsIntFunction[R] = new RichFunction1AsIntFunction[R](sf.asInstanceOf[scala.Function1[Int, R]]) + + @inline implicit def enrichAsJavaLongFunction[A0, R](sf: scala.Function1[A0, R])(implicit evA0: =:=[A0, Long]): RichFunction1AsLongFunction[R] = new RichFunction1AsLongFunction[R](sf.asInstanceOf[scala.Function1[Long, R]]) + + @inline implicit def enrichAsJavaObjDoubleConsumer[T, A1](sf: scala.Function2[T, A1, Unit])(implicit evA1: =:=[A1, Double]): RichFunction2AsObjDoubleConsumer[T] = new RichFunction2AsObjDoubleConsumer[T](sf.asInstanceOf[scala.Function2[T, Double, Unit]]) + + @inline implicit def enrichAsJavaObjIntConsumer[T, A1](sf: scala.Function2[T, A1, Unit])(implicit evA1: =:=[A1, Int]): RichFunction2AsObjIntConsumer[T] = new RichFunction2AsObjIntConsumer[T](sf.asInstanceOf[scala.Function2[T, Int, Unit]]) + + @inline implicit def enrichAsJavaObjLongConsumer[T, A1](sf: scala.Function2[T, A1, Unit])(implicit evA1: =:=[A1, Long]): RichFunction2AsObjLongConsumer[T] = new RichFunction2AsObjLongConsumer[T](sf.asInstanceOf[scala.Function2[T, Long, Unit]]) + + @inline implicit def enrichAsJavaPredicate[T](sf: scala.Function1[T, Boolean]): RichFunction1AsPredicate[T] = new RichFunction1AsPredicate[T](sf) + + @inline implicit def enrichAsJavaSupplier[T](sf: scala.Function0[T]): RichFunction0AsSupplier[T] = new RichFunction0AsSupplier[T](sf) + + @inline implicit def enrichAsJavaToDoubleFunction[T](sf: scala.Function1[T, Double]): RichFunction1AsToDoubleFunction[T] = new RichFunction1AsToDoubleFunction[T](sf) + + @inline implicit def enrichAsJavaToIntFunction[T](sf: scala.Function1[T, Int]): RichFunction1AsToIntFunction[T] = new RichFunction1AsToIntFunction[T](sf) + + @inline implicit def enrichAsJavaToLongFunction[T](sf: scala.Function1[T, Long]): RichFunction1AsToLongFunction[T] = new RichFunction1AsToLongFunction[T](sf) + + @inline implicit def enrichAsJavaUnaryOperator[T, A1](sf: scala.Function1[T, A1])(implicit evA1: =:=[A1, T]): RichFunction1AsUnaryOperator[T] = new RichFunction1AsUnaryOperator[T](sf.asInstanceOf[scala.Function1[T, T]]) +} + + + +import language.implicitConversions + +trait Priority0FunctionExtensions extends Priority1FunctionExtensions { + import FunctionWrappers._ + + @inline implicit def enrichAsJavaBooleanSupplier(sf: scala.Function0[Boolean]): RichFunction0AsBooleanSupplier = new RichFunction0AsBooleanSupplier(sf) + + @inline implicit def enrichAsJavaDoubleBinaryOperator[A0, A1](sf: scala.Function2[A0, A1, Double])(implicit evA0: =:=[A0, Double], evA1: =:=[A1, Double]): RichFunction2AsDoubleBinaryOperator = new RichFunction2AsDoubleBinaryOperator(sf.asInstanceOf[scala.Function2[Double, Double, Double]]) + + @inline implicit def enrichAsJavaDoubleConsumer[A0](sf: scala.Function1[A0, Unit])(implicit evA0: =:=[A0, Double]): RichFunction1AsDoubleConsumer = new RichFunction1AsDoubleConsumer(sf.asInstanceOf[scala.Function1[Double, Unit]]) + + @inline implicit def enrichAsJavaDoublePredicate[A0](sf: scala.Function1[A0, Boolean])(implicit evA0: =:=[A0, Double]): RichFunction1AsDoublePredicate = new RichFunction1AsDoublePredicate(sf.asInstanceOf[scala.Function1[Double, Boolean]]) + + @inline implicit def enrichAsJavaDoubleSupplier(sf: scala.Function0[Double]): RichFunction0AsDoubleSupplier = new RichFunction0AsDoubleSupplier(sf) + + @inline implicit def enrichAsJavaDoubleToIntFunction[A0](sf: scala.Function1[A0, Int])(implicit evA0: =:=[A0, Double]): RichFunction1AsDoubleToIntFunction = new RichFunction1AsDoubleToIntFunction(sf.asInstanceOf[scala.Function1[Double, Int]]) + + @inline implicit def enrichAsJavaDoubleToLongFunction[A0](sf: scala.Function1[A0, Long])(implicit evA0: =:=[A0, Double]): RichFunction1AsDoubleToLongFunction = new RichFunction1AsDoubleToLongFunction(sf.asInstanceOf[scala.Function1[Double, Long]]) + + @inline implicit def enrichAsJavaDoubleUnaryOperator[A0](sf: scala.Function1[A0, Double])(implicit evA0: =:=[A0, Double]): RichFunction1AsDoubleUnaryOperator = new RichFunction1AsDoubleUnaryOperator(sf.asInstanceOf[scala.Function1[Double, Double]]) + + @inline implicit def enrichAsJavaIntBinaryOperator[A0, A1](sf: scala.Function2[A0, A1, Int])(implicit evA0: =:=[A0, Int], evA1: =:=[A1, Int]): RichFunction2AsIntBinaryOperator = new RichFunction2AsIntBinaryOperator(sf.asInstanceOf[scala.Function2[Int, Int, Int]]) + + @inline implicit def enrichAsJavaIntConsumer[A0](sf: scala.Function1[A0, Unit])(implicit evA0: =:=[A0, Int]): RichFunction1AsIntConsumer = new RichFunction1AsIntConsumer(sf.asInstanceOf[scala.Function1[Int, Unit]]) + + @inline implicit def enrichAsJavaIntPredicate[A0](sf: scala.Function1[A0, Boolean])(implicit evA0: =:=[A0, Int]): RichFunction1AsIntPredicate = new RichFunction1AsIntPredicate(sf.asInstanceOf[scala.Function1[Int, Boolean]]) + + @inline implicit def enrichAsJavaIntSupplier(sf: scala.Function0[Int]): RichFunction0AsIntSupplier = new RichFunction0AsIntSupplier(sf) + + @inline implicit def enrichAsJavaIntToDoubleFunction[A0](sf: scala.Function1[A0, Double])(implicit evA0: =:=[A0, Int]): RichFunction1AsIntToDoubleFunction = new RichFunction1AsIntToDoubleFunction(sf.asInstanceOf[scala.Function1[Int, Double]]) + + @inline implicit def enrichAsJavaIntToLongFunction[A0](sf: scala.Function1[A0, Long])(implicit evA0: =:=[A0, Int]): RichFunction1AsIntToLongFunction = new RichFunction1AsIntToLongFunction(sf.asInstanceOf[scala.Function1[Int, Long]]) + + @inline implicit def enrichAsJavaIntUnaryOperator[A0](sf: scala.Function1[A0, Int])(implicit evA0: =:=[A0, Int]): RichFunction1AsIntUnaryOperator = new RichFunction1AsIntUnaryOperator(sf.asInstanceOf[scala.Function1[Int, Int]]) + + @inline implicit def enrichAsJavaLongBinaryOperator[A0, A1](sf: scala.Function2[A0, A1, Long])(implicit evA0: =:=[A0, Long], evA1: =:=[A1, Long]): RichFunction2AsLongBinaryOperator = new RichFunction2AsLongBinaryOperator(sf.asInstanceOf[scala.Function2[Long, Long, Long]]) + + @inline implicit def enrichAsJavaLongConsumer[A0](sf: scala.Function1[A0, Unit])(implicit evA0: =:=[A0, Long]): RichFunction1AsLongConsumer = new RichFunction1AsLongConsumer(sf.asInstanceOf[scala.Function1[Long, Unit]]) + + @inline implicit def enrichAsJavaLongPredicate[A0](sf: scala.Function1[A0, Boolean])(implicit evA0: =:=[A0, Long]): RichFunction1AsLongPredicate = new RichFunction1AsLongPredicate(sf.asInstanceOf[scala.Function1[Long, Boolean]]) + + @inline implicit def enrichAsJavaLongSupplier(sf: scala.Function0[Long]): RichFunction0AsLongSupplier = new RichFunction0AsLongSupplier(sf) + + @inline implicit def enrichAsJavaLongToDoubleFunction[A0](sf: scala.Function1[A0, Double])(implicit evA0: =:=[A0, Long]): RichFunction1AsLongToDoubleFunction = new RichFunction1AsLongToDoubleFunction(sf.asInstanceOf[scala.Function1[Long, Double]]) + + @inline implicit def enrichAsJavaLongToIntFunction[A0](sf: scala.Function1[A0, Int])(implicit evA0: =:=[A0, Long]): RichFunction1AsLongToIntFunction = new RichFunction1AsLongToIntFunction(sf.asInstanceOf[scala.Function1[Long, Int]]) + + @inline implicit def enrichAsJavaLongUnaryOperator[A0](sf: scala.Function1[A0, Long])(implicit evA0: =:=[A0, Long]): RichFunction1AsLongUnaryOperator = new RichFunction1AsLongUnaryOperator(sf.asInstanceOf[scala.Function1[Long, Long]]) + + + + @inline implicit def enrichAsScalaFromBiConsumer[T, U](jf: java.util.function.BiConsumer[T, U]): RichBiConsumerAsFunction2[T, U] = new RichBiConsumerAsFunction2[T, U](jf) + + @inline implicit def enrichAsScalaFromBiFunction[T, U, R](jf: java.util.function.BiFunction[T, U, R]): RichBiFunctionAsFunction2[T, U, R] = new RichBiFunctionAsFunction2[T, U, R](jf) + + @inline implicit def enrichAsScalaFromBiPredicate[T, U](jf: java.util.function.BiPredicate[T, U]): RichBiPredicateAsFunction2[T, U] = new RichBiPredicateAsFunction2[T, U](jf) + + @inline implicit def enrichAsScalaFromBinaryOperator[T](jf: java.util.function.BinaryOperator[T]): RichBinaryOperatorAsFunction2[T] = new RichBinaryOperatorAsFunction2[T](jf) + + @inline implicit def enrichAsScalaFromBooleanSupplier(jf: java.util.function.BooleanSupplier): RichBooleanSupplierAsFunction0 = new RichBooleanSupplierAsFunction0(jf) + + @inline implicit def enrichAsScalaFromConsumer[T](jf: java.util.function.Consumer[T]): RichConsumerAsFunction1[T] = new RichConsumerAsFunction1[T](jf) + + @inline implicit def enrichAsScalaFromDoubleBinaryOperator(jf: java.util.function.DoubleBinaryOperator): RichDoubleBinaryOperatorAsFunction2 = new RichDoubleBinaryOperatorAsFunction2(jf) + + @inline implicit def enrichAsScalaFromDoubleConsumer(jf: java.util.function.DoubleConsumer): RichDoubleConsumerAsFunction1 = new RichDoubleConsumerAsFunction1(jf) + + @inline implicit def enrichAsScalaFromDoubleFunction[R](jf: java.util.function.DoubleFunction[R]): RichDoubleFunctionAsFunction1[R] = new RichDoubleFunctionAsFunction1[R](jf) + + @inline implicit def enrichAsScalaFromDoublePredicate(jf: java.util.function.DoublePredicate): RichDoublePredicateAsFunction1 = new RichDoublePredicateAsFunction1(jf) + + @inline implicit def enrichAsScalaFromDoubleSupplier(jf: java.util.function.DoubleSupplier): RichDoubleSupplierAsFunction0 = new RichDoubleSupplierAsFunction0(jf) + + @inline implicit def enrichAsScalaFromDoubleToIntFunction(jf: java.util.function.DoubleToIntFunction): RichDoubleToIntFunctionAsFunction1 = new RichDoubleToIntFunctionAsFunction1(jf) + + @inline implicit def enrichAsScalaFromDoubleToLongFunction(jf: java.util.function.DoubleToLongFunction): RichDoubleToLongFunctionAsFunction1 = new RichDoubleToLongFunctionAsFunction1(jf) + + @inline implicit def enrichAsScalaFromDoubleUnaryOperator(jf: java.util.function.DoubleUnaryOperator): RichDoubleUnaryOperatorAsFunction1 = new RichDoubleUnaryOperatorAsFunction1(jf) + + @inline implicit def enrichAsScalaFromFunction[T, R](jf: java.util.function.Function[T, R]): RichFunctionAsFunction1[T, R] = new RichFunctionAsFunction1[T, R](jf) + + @inline implicit def enrichAsScalaFromIntBinaryOperator(jf: java.util.function.IntBinaryOperator): RichIntBinaryOperatorAsFunction2 = new RichIntBinaryOperatorAsFunction2(jf) + + @inline implicit def enrichAsScalaFromIntConsumer(jf: java.util.function.IntConsumer): RichIntConsumerAsFunction1 = new RichIntConsumerAsFunction1(jf) + + @inline implicit def enrichAsScalaFromIntFunction[R](jf: java.util.function.IntFunction[R]): RichIntFunctionAsFunction1[R] = new RichIntFunctionAsFunction1[R](jf) + + @inline implicit def enrichAsScalaFromIntPredicate(jf: java.util.function.IntPredicate): RichIntPredicateAsFunction1 = new RichIntPredicateAsFunction1(jf) + + @inline implicit def enrichAsScalaFromIntSupplier(jf: java.util.function.IntSupplier): RichIntSupplierAsFunction0 = new RichIntSupplierAsFunction0(jf) + + @inline implicit def enrichAsScalaFromIntToDoubleFunction(jf: java.util.function.IntToDoubleFunction): RichIntToDoubleFunctionAsFunction1 = new RichIntToDoubleFunctionAsFunction1(jf) + + @inline implicit def enrichAsScalaFromIntToLongFunction(jf: java.util.function.IntToLongFunction): RichIntToLongFunctionAsFunction1 = new RichIntToLongFunctionAsFunction1(jf) + + @inline implicit def enrichAsScalaFromIntUnaryOperator(jf: java.util.function.IntUnaryOperator): RichIntUnaryOperatorAsFunction1 = new RichIntUnaryOperatorAsFunction1(jf) + + @inline implicit def enrichAsScalaFromLongBinaryOperator(jf: java.util.function.LongBinaryOperator): RichLongBinaryOperatorAsFunction2 = new RichLongBinaryOperatorAsFunction2(jf) + + @inline implicit def enrichAsScalaFromLongConsumer(jf: java.util.function.LongConsumer): RichLongConsumerAsFunction1 = new RichLongConsumerAsFunction1(jf) + + @inline implicit def enrichAsScalaFromLongFunction[R](jf: java.util.function.LongFunction[R]): RichLongFunctionAsFunction1[R] = new RichLongFunctionAsFunction1[R](jf) + + @inline implicit def enrichAsScalaFromLongPredicate(jf: java.util.function.LongPredicate): RichLongPredicateAsFunction1 = new RichLongPredicateAsFunction1(jf) + + @inline implicit def enrichAsScalaFromLongSupplier(jf: java.util.function.LongSupplier): RichLongSupplierAsFunction0 = new RichLongSupplierAsFunction0(jf) + + @inline implicit def enrichAsScalaFromLongToDoubleFunction(jf: java.util.function.LongToDoubleFunction): RichLongToDoubleFunctionAsFunction1 = new RichLongToDoubleFunctionAsFunction1(jf) + + @inline implicit def enrichAsScalaFromLongToIntFunction(jf: java.util.function.LongToIntFunction): RichLongToIntFunctionAsFunction1 = new RichLongToIntFunctionAsFunction1(jf) + + @inline implicit def enrichAsScalaFromLongUnaryOperator(jf: java.util.function.LongUnaryOperator): RichLongUnaryOperatorAsFunction1 = new RichLongUnaryOperatorAsFunction1(jf) + + @inline implicit def enrichAsScalaFromObjDoubleConsumer[T](jf: java.util.function.ObjDoubleConsumer[T]): RichObjDoubleConsumerAsFunction2[T] = new RichObjDoubleConsumerAsFunction2[T](jf) + + @inline implicit def enrichAsScalaFromObjIntConsumer[T](jf: java.util.function.ObjIntConsumer[T]): RichObjIntConsumerAsFunction2[T] = new RichObjIntConsumerAsFunction2[T](jf) + + @inline implicit def enrichAsScalaFromObjLongConsumer[T](jf: java.util.function.ObjLongConsumer[T]): RichObjLongConsumerAsFunction2[T] = new RichObjLongConsumerAsFunction2[T](jf) + + @inline implicit def enrichAsScalaFromPredicate[T](jf: java.util.function.Predicate[T]): RichPredicateAsFunction1[T] = new RichPredicateAsFunction1[T](jf) + + @inline implicit def enrichAsScalaFromSupplier[T](jf: java.util.function.Supplier[T]): RichSupplierAsFunction0[T] = new RichSupplierAsFunction0[T](jf) + + @inline implicit def enrichAsScalaFromToDoubleBiFunction[T, U](jf: java.util.function.ToDoubleBiFunction[T, U]): RichToDoubleBiFunctionAsFunction2[T, U] = new RichToDoubleBiFunctionAsFunction2[T, U](jf) + + @inline implicit def enrichAsScalaFromToDoubleFunction[T](jf: java.util.function.ToDoubleFunction[T]): RichToDoubleFunctionAsFunction1[T] = new RichToDoubleFunctionAsFunction1[T](jf) + + @inline implicit def enrichAsScalaFromToIntBiFunction[T, U](jf: java.util.function.ToIntBiFunction[T, U]): RichToIntBiFunctionAsFunction2[T, U] = new RichToIntBiFunctionAsFunction2[T, U](jf) + + @inline implicit def enrichAsScalaFromToIntFunction[T](jf: java.util.function.ToIntFunction[T]): RichToIntFunctionAsFunction1[T] = new RichToIntFunctionAsFunction1[T](jf) + + @inline implicit def enrichAsScalaFromToLongBiFunction[T, U](jf: java.util.function.ToLongBiFunction[T, U]): RichToLongBiFunctionAsFunction2[T, U] = new RichToLongBiFunctionAsFunction2[T, U](jf) + + @inline implicit def enrichAsScalaFromToLongFunction[T](jf: java.util.function.ToLongFunction[T]): RichToLongFunctionAsFunction1[T] = new RichToLongFunctionAsFunction1[T](jf) + + @inline implicit def enrichAsScalaFromUnaryOperator[T](jf: java.util.function.UnaryOperator[T]): RichUnaryOperatorAsFunction1[T] = new RichUnaryOperatorAsFunction1[T](jf) +} diff --git a/library/src/scala/jdk/FunctionWrappers.scala b/library/src/scala/jdk/FunctionWrappers.scala new file mode 100644 index 000000000000..d6a4d071144d --- /dev/null +++ b/library/src/scala/jdk/FunctionWrappers.scala @@ -0,0 +1,1092 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. + + +package scala.jdk + +import scala.language.`2.13` + +object FunctionWrappers { + case class FromJavaBiConsumer[T, U](jf: java.util.function.BiConsumer[T, U]) extends scala.Function2[T, U, Unit] { + def apply(x1: T, x2: U) = jf.accept(x1, x2) + } + + class RichBiConsumerAsFunction2[T, U](private val underlying: java.util.function.BiConsumer[T, U]) extends AnyVal { + @inline def asScala: scala.Function2[T, U, Unit] = underlying match { + case AsJavaBiConsumer((sf @ _)) => sf.asInstanceOf[scala.Function2[T, U, Unit]] + case _ => new FromJavaBiConsumer[T, U](underlying) + } + } + + case class AsJavaBiConsumer[T, U](sf: scala.Function2[T, U, Unit]) extends java.util.function.BiConsumer[T, U] { + def accept(x1: T, x2: U) = sf.apply(x1, x2) + } + + class RichFunction2AsBiConsumer[T, U](private val underlying: scala.Function2[T, U, Unit]) extends AnyVal { + @inline def asJava: java.util.function.BiConsumer[T, U] = underlying match { + case FromJavaBiConsumer((jf @ _)) => jf.asInstanceOf[java.util.function.BiConsumer[T, U]] + case _ => new AsJavaBiConsumer[T, U](underlying) + }; + @inline def asJavaBiConsumer: java.util.function.BiConsumer[T, U] = underlying match { + case FromJavaBiConsumer((sf @ _)) => sf.asInstanceOf[java.util.function.BiConsumer[T, U]] + case _ => new AsJavaBiConsumer[T, U](underlying) + } + } + + + case class FromJavaBiFunction[T, U, R](jf: java.util.function.BiFunction[T, U, R]) extends scala.Function2[T, U, R] { + def apply(x1: T, x2: U) = jf.apply(x1, x2) + } + + class RichBiFunctionAsFunction2[T, U, R](private val underlying: java.util.function.BiFunction[T, U, R]) extends AnyVal { + @inline def asScala: scala.Function2[T, U, R] = underlying match { + case AsJavaBiFunction((sf @ _)) => sf.asInstanceOf[scala.Function2[T, U, R]] + case _ => new FromJavaBiFunction[T, U, R](underlying) + } + } + + case class AsJavaBiFunction[T, U, R](sf: scala.Function2[T, U, R]) extends java.util.function.BiFunction[T, U, R] { + def apply(x1: T, x2: U) = sf.apply(x1, x2) + } + + class RichFunction2AsBiFunction[T, U, R](private val underlying: scala.Function2[T, U, R]) extends AnyVal { + @inline def asJava: java.util.function.BiFunction[T, U, R] = underlying match { + case FromJavaBiFunction((jf @ _)) => jf.asInstanceOf[java.util.function.BiFunction[T, U, R]] + case _ => new AsJavaBiFunction[T, U, R](underlying) + }; + @inline def asJavaBiFunction: java.util.function.BiFunction[T, U, R] = underlying match { + case FromJavaBiFunction((sf @ _)) => sf.asInstanceOf[java.util.function.BiFunction[T, U, R]] + case _ => new AsJavaBiFunction[T, U, R](underlying) + } + } + + + case class FromJavaBiPredicate[T, U](jf: java.util.function.BiPredicate[T, U]) extends scala.Function2[T, U, Boolean] { + def apply(x1: T, x2: U) = jf.test(x1, x2) + } + + class RichBiPredicateAsFunction2[T, U](private val underlying: java.util.function.BiPredicate[T, U]) extends AnyVal { + @inline def asScala: scala.Function2[T, U, Boolean] = underlying match { + case AsJavaBiPredicate((sf @ _)) => sf.asInstanceOf[scala.Function2[T, U, Boolean]] + case _ => new FromJavaBiPredicate[T, U](underlying) + } + } + + case class AsJavaBiPredicate[T, U](sf: scala.Function2[T, U, Boolean]) extends java.util.function.BiPredicate[T, U] { + def test(x1: T, x2: U) = sf.apply(x1, x2) + } + + class RichFunction2AsBiPredicate[T, U](private val underlying: scala.Function2[T, U, Boolean]) extends AnyVal { + @inline def asJava: java.util.function.BiPredicate[T, U] = underlying match { + case FromJavaBiPredicate((jf @ _)) => jf.asInstanceOf[java.util.function.BiPredicate[T, U]] + case _ => new AsJavaBiPredicate[T, U](underlying) + }; + @inline def asJavaBiPredicate: java.util.function.BiPredicate[T, U] = underlying match { + case FromJavaBiPredicate((sf @ _)) => sf.asInstanceOf[java.util.function.BiPredicate[T, U]] + case _ => new AsJavaBiPredicate[T, U](underlying) + } + } + + + case class FromJavaBinaryOperator[T](jf: java.util.function.BinaryOperator[T]) extends scala.Function2[T, T, T] { + def apply(x1: T, x2: T) = jf.apply(x1, x2) + } + + class RichBinaryOperatorAsFunction2[T](private val underlying: java.util.function.BinaryOperator[T]) extends AnyVal { + @inline def asScala: scala.Function2[T, T, T] = underlying match { + case AsJavaBinaryOperator((sf @ _)) => sf.asInstanceOf[scala.Function2[T, T, T]] + case _ => new FromJavaBinaryOperator[T](underlying) + } + } + + case class AsJavaBinaryOperator[T](sf: scala.Function2[T, T, T]) extends java.util.function.BinaryOperator[T] { + def apply(x1: T, x2: T) = sf.apply(x1, x2) + } + + class RichFunction2AsBinaryOperator[T](private val underlying: scala.Function2[T, T, T]) extends AnyVal { + @inline def asJava: java.util.function.BinaryOperator[T] = underlying match { + case FromJavaBinaryOperator((jf @ _)) => jf.asInstanceOf[java.util.function.BinaryOperator[T]] + case _ => new AsJavaBinaryOperator[T](underlying) + }; + @inline def asJavaBinaryOperator: java.util.function.BinaryOperator[T] = underlying match { + case FromJavaBinaryOperator((sf @ _)) => sf.asInstanceOf[java.util.function.BinaryOperator[T]] + case _ => new AsJavaBinaryOperator[T](underlying) + } + } + + + case class FromJavaBooleanSupplier(jf: java.util.function.BooleanSupplier) extends scala.Function0[Boolean] { + def apply() = jf.getAsBoolean() + } + + class RichBooleanSupplierAsFunction0(private val underlying: java.util.function.BooleanSupplier) extends AnyVal { + @inline def asScala: scala.Function0[Boolean] = underlying match { + case AsJavaBooleanSupplier((sf @ _)) => sf.asInstanceOf[scala.Function0[Boolean]] + case _ => new FromJavaBooleanSupplier(underlying) + } + } + + case class AsJavaBooleanSupplier(sf: scala.Function0[Boolean]) extends java.util.function.BooleanSupplier { + def getAsBoolean() = sf.apply() + } + + class RichFunction0AsBooleanSupplier(private val underlying: scala.Function0[Boolean]) extends AnyVal { + @inline def asJava: java.util.function.BooleanSupplier = underlying match { + case FromJavaBooleanSupplier((jf @ _)) => jf.asInstanceOf[java.util.function.BooleanSupplier] + case _ => new AsJavaBooleanSupplier(underlying) + } + } + + + case class FromJavaConsumer[T](jf: java.util.function.Consumer[T]) extends scala.Function1[T, Unit] { + def apply(x1: T) = jf.accept(x1) + } + + class RichConsumerAsFunction1[T](private val underlying: java.util.function.Consumer[T]) extends AnyVal { + @inline def asScala: scala.Function1[T, Unit] = underlying match { + case AsJavaConsumer((sf @ _)) => sf.asInstanceOf[scala.Function1[T, Unit]] + case _ => new FromJavaConsumer[T](underlying) + } + } + + case class AsJavaConsumer[T](sf: scala.Function1[T, Unit]) extends java.util.function.Consumer[T] { + def accept(x1: T) = sf.apply(x1) + } + + class RichFunction1AsConsumer[T](private val underlying: scala.Function1[T, Unit]) extends AnyVal { + @inline def asJava: java.util.function.Consumer[T] = underlying match { + case FromJavaConsumer((jf @ _)) => jf.asInstanceOf[java.util.function.Consumer[T]] + case _ => new AsJavaConsumer[T](underlying) + }; + @inline def asJavaConsumer: java.util.function.Consumer[T] = underlying match { + case FromJavaConsumer((sf @ _)) => sf.asInstanceOf[java.util.function.Consumer[T]] + case _ => new AsJavaConsumer[T](underlying) + } + } + + + case class FromJavaDoubleBinaryOperator(jf: java.util.function.DoubleBinaryOperator) extends scala.Function2[Double, Double, Double] { + def apply(x1: scala.Double, x2: scala.Double) = jf.applyAsDouble(x1, x2) + } + + class RichDoubleBinaryOperatorAsFunction2(private val underlying: java.util.function.DoubleBinaryOperator) extends AnyVal { + @inline def asScala: scala.Function2[Double, Double, Double] = underlying match { + case AsJavaDoubleBinaryOperator((sf @ _)) => sf.asInstanceOf[scala.Function2[Double, Double, Double]] + case _ => new FromJavaDoubleBinaryOperator(underlying) + } + } + + case class AsJavaDoubleBinaryOperator(sf: scala.Function2[Double, Double, Double]) extends java.util.function.DoubleBinaryOperator { + def applyAsDouble(x1: scala.Double, x2: scala.Double) = sf.apply(x1, x2) + } + + class RichFunction2AsDoubleBinaryOperator(private val underlying: scala.Function2[Double, Double, Double]) extends AnyVal { + @inline def asJava: java.util.function.DoubleBinaryOperator = underlying match { + case FromJavaDoubleBinaryOperator((jf @ _)) => jf.asInstanceOf[java.util.function.DoubleBinaryOperator] + case _ => new AsJavaDoubleBinaryOperator(underlying) + } + } + + + case class FromJavaDoubleConsumer(jf: java.util.function.DoubleConsumer) extends scala.Function1[Double, Unit] { + def apply(x1: scala.Double) = jf.accept(x1) + } + + class RichDoubleConsumerAsFunction1(private val underlying: java.util.function.DoubleConsumer) extends AnyVal { + @inline def asScala: scala.Function1[Double, Unit] = underlying match { + case AsJavaDoubleConsumer((sf @ _)) => sf.asInstanceOf[scala.Function1[Double, Unit]] + case _ => new FromJavaDoubleConsumer(underlying) + } + } + + case class AsJavaDoubleConsumer(sf: scala.Function1[Double, Unit]) extends java.util.function.DoubleConsumer { + def accept(x1: scala.Double) = sf.apply(x1) + } + + class RichFunction1AsDoubleConsumer(private val underlying: scala.Function1[Double, Unit]) extends AnyVal { + @inline def asJava: java.util.function.DoubleConsumer = underlying match { + case FromJavaDoubleConsumer((jf @ _)) => jf.asInstanceOf[java.util.function.DoubleConsumer] + case _ => new AsJavaDoubleConsumer(underlying) + } + } + + + case class FromJavaDoubleFunction[R](jf: java.util.function.DoubleFunction[R]) extends scala.Function1[Double, R] { + def apply(x1: scala.Double) = jf.apply(x1) + } + + class RichDoubleFunctionAsFunction1[R](private val underlying: java.util.function.DoubleFunction[R]) extends AnyVal { + @inline def asScala: scala.Function1[Double, R] = underlying match { + case AsJavaDoubleFunction((sf @ _)) => sf.asInstanceOf[scala.Function1[Double, R]] + case _ => new FromJavaDoubleFunction[R](underlying) + } + } + + case class AsJavaDoubleFunction[R](sf: scala.Function1[Double, R]) extends java.util.function.DoubleFunction[R] { + def apply(x1: scala.Double) = sf.apply(x1) + } + + class RichFunction1AsDoubleFunction[R](private val underlying: scala.Function1[Double, R]) extends AnyVal { + @inline def asJava: java.util.function.DoubleFunction[R] = underlying match { + case FromJavaDoubleFunction((jf @ _)) => jf.asInstanceOf[java.util.function.DoubleFunction[R]] + case _ => new AsJavaDoubleFunction[R](underlying) + }; + @inline def asJavaDoubleFunction: java.util.function.DoubleFunction[R] = underlying match { + case FromJavaDoubleFunction((sf @ _)) => sf.asInstanceOf[java.util.function.DoubleFunction[R]] + case _ => new AsJavaDoubleFunction[R](underlying) + } + } + + + case class FromJavaDoublePredicate(jf: java.util.function.DoublePredicate) extends scala.Function1[Double, Boolean] { + def apply(x1: scala.Double) = jf.test(x1) + } + + class RichDoublePredicateAsFunction1(private val underlying: java.util.function.DoublePredicate) extends AnyVal { + @inline def asScala: scala.Function1[Double, Boolean] = underlying match { + case AsJavaDoublePredicate((sf @ _)) => sf.asInstanceOf[scala.Function1[Double, Boolean]] + case _ => new FromJavaDoublePredicate(underlying) + } + } + + case class AsJavaDoublePredicate(sf: scala.Function1[Double, Boolean]) extends java.util.function.DoublePredicate { + def test(x1: scala.Double) = sf.apply(x1) + } + + class RichFunction1AsDoublePredicate(private val underlying: scala.Function1[Double, Boolean]) extends AnyVal { + @inline def asJava: java.util.function.DoublePredicate = underlying match { + case FromJavaDoublePredicate((jf @ _)) => jf.asInstanceOf[java.util.function.DoublePredicate] + case _ => new AsJavaDoublePredicate(underlying) + } + } + + + case class FromJavaDoubleSupplier(jf: java.util.function.DoubleSupplier) extends scala.Function0[Double] { + def apply() = jf.getAsDouble() + } + + class RichDoubleSupplierAsFunction0(private val underlying: java.util.function.DoubleSupplier) extends AnyVal { + @inline def asScala: scala.Function0[Double] = underlying match { + case AsJavaDoubleSupplier((sf @ _)) => sf.asInstanceOf[scala.Function0[Double]] + case _ => new FromJavaDoubleSupplier(underlying) + } + } + + case class AsJavaDoubleSupplier(sf: scala.Function0[Double]) extends java.util.function.DoubleSupplier { + def getAsDouble() = sf.apply() + } + + class RichFunction0AsDoubleSupplier(private val underlying: scala.Function0[Double]) extends AnyVal { + @inline def asJava: java.util.function.DoubleSupplier = underlying match { + case FromJavaDoubleSupplier((jf @ _)) => jf.asInstanceOf[java.util.function.DoubleSupplier] + case _ => new AsJavaDoubleSupplier(underlying) + } + } + + + case class FromJavaDoubleToIntFunction(jf: java.util.function.DoubleToIntFunction) extends scala.Function1[Double, Int] { + def apply(x1: scala.Double) = jf.applyAsInt(x1) + } + + class RichDoubleToIntFunctionAsFunction1(private val underlying: java.util.function.DoubleToIntFunction) extends AnyVal { + @inline def asScala: scala.Function1[Double, Int] = underlying match { + case AsJavaDoubleToIntFunction((sf @ _)) => sf.asInstanceOf[scala.Function1[Double, Int]] + case _ => new FromJavaDoubleToIntFunction(underlying) + } + } + + case class AsJavaDoubleToIntFunction(sf: scala.Function1[Double, Int]) extends java.util.function.DoubleToIntFunction { + def applyAsInt(x1: scala.Double) = sf.apply(x1) + } + + class RichFunction1AsDoubleToIntFunction(private val underlying: scala.Function1[Double, Int]) extends AnyVal { + @inline def asJava: java.util.function.DoubleToIntFunction = underlying match { + case FromJavaDoubleToIntFunction((jf @ _)) => jf.asInstanceOf[java.util.function.DoubleToIntFunction] + case _ => new AsJavaDoubleToIntFunction(underlying) + } + } + + + case class FromJavaDoubleToLongFunction(jf: java.util.function.DoubleToLongFunction) extends scala.Function1[Double, Long] { + def apply(x1: scala.Double) = jf.applyAsLong(x1) + } + + class RichDoubleToLongFunctionAsFunction1(private val underlying: java.util.function.DoubleToLongFunction) extends AnyVal { + @inline def asScala: scala.Function1[Double, Long] = underlying match { + case AsJavaDoubleToLongFunction((sf @ _)) => sf.asInstanceOf[scala.Function1[Double, Long]] + case _ => new FromJavaDoubleToLongFunction(underlying) + } + } + + case class AsJavaDoubleToLongFunction(sf: scala.Function1[Double, Long]) extends java.util.function.DoubleToLongFunction { + def applyAsLong(x1: scala.Double) = sf.apply(x1) + } + + class RichFunction1AsDoubleToLongFunction(private val underlying: scala.Function1[Double, Long]) extends AnyVal { + @inline def asJava: java.util.function.DoubleToLongFunction = underlying match { + case FromJavaDoubleToLongFunction((jf @ _)) => jf.asInstanceOf[java.util.function.DoubleToLongFunction] + case _ => new AsJavaDoubleToLongFunction(underlying) + } + } + + + case class FromJavaDoubleUnaryOperator(jf: java.util.function.DoubleUnaryOperator) extends scala.Function1[Double, Double] { + def apply(x1: scala.Double) = jf.applyAsDouble(x1) + } + + class RichDoubleUnaryOperatorAsFunction1(private val underlying: java.util.function.DoubleUnaryOperator) extends AnyVal { + @inline def asScala: scala.Function1[Double, Double] = underlying match { + case AsJavaDoubleUnaryOperator((sf @ _)) => sf.asInstanceOf[scala.Function1[Double, Double]] + case _ => new FromJavaDoubleUnaryOperator(underlying) + } + } + + case class AsJavaDoubleUnaryOperator(sf: scala.Function1[Double, Double]) extends java.util.function.DoubleUnaryOperator { + def applyAsDouble(x1: scala.Double) = sf.apply(x1) + } + + class RichFunction1AsDoubleUnaryOperator(private val underlying: scala.Function1[Double, Double]) extends AnyVal { + @inline def asJava: java.util.function.DoubleUnaryOperator = underlying match { + case FromJavaDoubleUnaryOperator((jf @ _)) => jf.asInstanceOf[java.util.function.DoubleUnaryOperator] + case _ => new AsJavaDoubleUnaryOperator(underlying) + } + } + + + case class FromJavaFunction[T, R](jf: java.util.function.Function[T, R]) extends scala.Function1[T, R] { + def apply(x1: T) = jf.apply(x1) + } + + class RichFunctionAsFunction1[T, R](private val underlying: java.util.function.Function[T, R]) extends AnyVal { + @inline def asScala: scala.Function1[T, R] = underlying match { + case AsJavaFunction((sf @ _)) => sf.asInstanceOf[scala.Function1[T, R]] + case _ => new FromJavaFunction[T, R](underlying) + } + } + + case class AsJavaFunction[T, R](sf: scala.Function1[T, R]) extends java.util.function.Function[T, R] { + def apply(x1: T) = sf.apply(x1) + } + + class RichFunction1AsFunction[T, R](private val underlying: scala.Function1[T, R]) extends AnyVal { + @inline def asJava: java.util.function.Function[T, R] = underlying match { + case FromJavaFunction((jf @ _)) => jf.asInstanceOf[java.util.function.Function[T, R]] + case _ => new AsJavaFunction[T, R](underlying) + }; + @inline def asJavaFunction: java.util.function.Function[T, R] = underlying match { + case FromJavaFunction((sf @ _)) => sf.asInstanceOf[java.util.function.Function[T, R]] + case _ => new AsJavaFunction[T, R](underlying) + } + } + + + case class FromJavaIntBinaryOperator(jf: java.util.function.IntBinaryOperator) extends scala.Function2[Int, Int, Int] { + def apply(x1: scala.Int, x2: scala.Int) = jf.applyAsInt(x1, x2) + } + + class RichIntBinaryOperatorAsFunction2(private val underlying: java.util.function.IntBinaryOperator) extends AnyVal { + @inline def asScala: scala.Function2[Int, Int, Int] = underlying match { + case AsJavaIntBinaryOperator((sf @ _)) => sf.asInstanceOf[scala.Function2[Int, Int, Int]] + case _ => new FromJavaIntBinaryOperator(underlying) + } + } + + case class AsJavaIntBinaryOperator(sf: scala.Function2[Int, Int, Int]) extends java.util.function.IntBinaryOperator { + def applyAsInt(x1: scala.Int, x2: scala.Int) = sf.apply(x1, x2) + } + + class RichFunction2AsIntBinaryOperator(private val underlying: scala.Function2[Int, Int, Int]) extends AnyVal { + @inline def asJava: java.util.function.IntBinaryOperator = underlying match { + case FromJavaIntBinaryOperator((jf @ _)) => jf.asInstanceOf[java.util.function.IntBinaryOperator] + case _ => new AsJavaIntBinaryOperator(underlying) + } + } + + + case class FromJavaIntConsumer(jf: java.util.function.IntConsumer) extends scala.Function1[Int, Unit] { + def apply(x1: scala.Int) = jf.accept(x1) + } + + class RichIntConsumerAsFunction1(private val underlying: java.util.function.IntConsumer) extends AnyVal { + @inline def asScala: scala.Function1[Int, Unit] = underlying match { + case AsJavaIntConsumer((sf @ _)) => sf.asInstanceOf[scala.Function1[Int, Unit]] + case _ => new FromJavaIntConsumer(underlying) + } + } + + case class AsJavaIntConsumer(sf: scala.Function1[Int, Unit]) extends java.util.function.IntConsumer { + def accept(x1: scala.Int) = sf.apply(x1) + } + + class RichFunction1AsIntConsumer(private val underlying: scala.Function1[Int, Unit]) extends AnyVal { + @inline def asJava: java.util.function.IntConsumer = underlying match { + case FromJavaIntConsumer((jf @ _)) => jf.asInstanceOf[java.util.function.IntConsumer] + case _ => new AsJavaIntConsumer(underlying) + } + } + + + case class FromJavaIntFunction[R](jf: java.util.function.IntFunction[R]) extends scala.Function1[Int, R] { + def apply(x1: scala.Int) = jf.apply(x1) + } + + class RichIntFunctionAsFunction1[R](private val underlying: java.util.function.IntFunction[R]) extends AnyVal { + @inline def asScala: scala.Function1[Int, R] = underlying match { + case AsJavaIntFunction((sf @ _)) => sf.asInstanceOf[scala.Function1[Int, R]] + case _ => new FromJavaIntFunction[R](underlying) + } + } + + case class AsJavaIntFunction[R](sf: scala.Function1[Int, R]) extends java.util.function.IntFunction[R] { + def apply(x1: scala.Int) = sf.apply(x1) + } + + class RichFunction1AsIntFunction[R](private val underlying: scala.Function1[Int, R]) extends AnyVal { + @inline def asJava: java.util.function.IntFunction[R] = underlying match { + case FromJavaIntFunction((jf @ _)) => jf.asInstanceOf[java.util.function.IntFunction[R]] + case _ => new AsJavaIntFunction[R](underlying) + }; + @inline def asJavaIntFunction: java.util.function.IntFunction[R] = underlying match { + case FromJavaIntFunction((sf @ _)) => sf.asInstanceOf[java.util.function.IntFunction[R]] + case _ => new AsJavaIntFunction[R](underlying) + } + } + + + case class FromJavaIntPredicate(jf: java.util.function.IntPredicate) extends scala.Function1[Int, Boolean] { + def apply(x1: scala.Int) = jf.test(x1) + } + + class RichIntPredicateAsFunction1(private val underlying: java.util.function.IntPredicate) extends AnyVal { + @inline def asScala: scala.Function1[Int, Boolean] = underlying match { + case AsJavaIntPredicate((sf @ _)) => sf.asInstanceOf[scala.Function1[Int, Boolean]] + case _ => new FromJavaIntPredicate(underlying) + } + } + + case class AsJavaIntPredicate(sf: scala.Function1[Int, Boolean]) extends java.util.function.IntPredicate { + def test(x1: scala.Int) = sf.apply(x1) + } + + class RichFunction1AsIntPredicate(private val underlying: scala.Function1[Int, Boolean]) extends AnyVal { + @inline def asJava: java.util.function.IntPredicate = underlying match { + case FromJavaIntPredicate((jf @ _)) => jf.asInstanceOf[java.util.function.IntPredicate] + case _ => new AsJavaIntPredicate(underlying) + } + } + + + case class FromJavaIntSupplier(jf: java.util.function.IntSupplier) extends scala.Function0[Int] { + def apply() = jf.getAsInt() + } + + class RichIntSupplierAsFunction0(private val underlying: java.util.function.IntSupplier) extends AnyVal { + @inline def asScala: scala.Function0[Int] = underlying match { + case AsJavaIntSupplier((sf @ _)) => sf.asInstanceOf[scala.Function0[Int]] + case _ => new FromJavaIntSupplier(underlying) + } + } + + case class AsJavaIntSupplier(sf: scala.Function0[Int]) extends java.util.function.IntSupplier { + def getAsInt() = sf.apply() + } + + class RichFunction0AsIntSupplier(private val underlying: scala.Function0[Int]) extends AnyVal { + @inline def asJava: java.util.function.IntSupplier = underlying match { + case FromJavaIntSupplier((jf @ _)) => jf.asInstanceOf[java.util.function.IntSupplier] + case _ => new AsJavaIntSupplier(underlying) + } + } + + + case class FromJavaIntToDoubleFunction(jf: java.util.function.IntToDoubleFunction) extends scala.Function1[Int, Double] { + def apply(x1: scala.Int) = jf.applyAsDouble(x1) + } + + class RichIntToDoubleFunctionAsFunction1(private val underlying: java.util.function.IntToDoubleFunction) extends AnyVal { + @inline def asScala: scala.Function1[Int, Double] = underlying match { + case AsJavaIntToDoubleFunction((sf @ _)) => sf.asInstanceOf[scala.Function1[Int, Double]] + case _ => new FromJavaIntToDoubleFunction(underlying) + } + } + + case class AsJavaIntToDoubleFunction(sf: scala.Function1[Int, Double]) extends java.util.function.IntToDoubleFunction { + def applyAsDouble(x1: scala.Int) = sf.apply(x1) + } + + class RichFunction1AsIntToDoubleFunction(private val underlying: scala.Function1[Int, Double]) extends AnyVal { + @inline def asJava: java.util.function.IntToDoubleFunction = underlying match { + case FromJavaIntToDoubleFunction((jf @ _)) => jf.asInstanceOf[java.util.function.IntToDoubleFunction] + case _ => new AsJavaIntToDoubleFunction(underlying) + } + } + + + case class FromJavaIntToLongFunction(jf: java.util.function.IntToLongFunction) extends scala.Function1[Int, Long] { + def apply(x1: scala.Int) = jf.applyAsLong(x1) + } + + class RichIntToLongFunctionAsFunction1(private val underlying: java.util.function.IntToLongFunction) extends AnyVal { + @inline def asScala: scala.Function1[Int, Long] = underlying match { + case AsJavaIntToLongFunction((sf @ _)) => sf.asInstanceOf[scala.Function1[Int, Long]] + case _ => new FromJavaIntToLongFunction(underlying) + } + } + + case class AsJavaIntToLongFunction(sf: scala.Function1[Int, Long]) extends java.util.function.IntToLongFunction { + def applyAsLong(x1: scala.Int) = sf.apply(x1) + } + + class RichFunction1AsIntToLongFunction(private val underlying: scala.Function1[Int, Long]) extends AnyVal { + @inline def asJava: java.util.function.IntToLongFunction = underlying match { + case FromJavaIntToLongFunction((jf @ _)) => jf.asInstanceOf[java.util.function.IntToLongFunction] + case _ => new AsJavaIntToLongFunction(underlying) + } + } + + + case class FromJavaIntUnaryOperator(jf: java.util.function.IntUnaryOperator) extends scala.Function1[Int, Int] { + def apply(x1: scala.Int) = jf.applyAsInt(x1) + } + + class RichIntUnaryOperatorAsFunction1(private val underlying: java.util.function.IntUnaryOperator) extends AnyVal { + @inline def asScala: scala.Function1[Int, Int] = underlying match { + case AsJavaIntUnaryOperator((sf @ _)) => sf.asInstanceOf[scala.Function1[Int, Int]] + case _ => new FromJavaIntUnaryOperator(underlying) + } + } + + case class AsJavaIntUnaryOperator(sf: scala.Function1[Int, Int]) extends java.util.function.IntUnaryOperator { + def applyAsInt(x1: scala.Int) = sf.apply(x1) + } + + class RichFunction1AsIntUnaryOperator(private val underlying: scala.Function1[Int, Int]) extends AnyVal { + @inline def asJava: java.util.function.IntUnaryOperator = underlying match { + case FromJavaIntUnaryOperator((jf @ _)) => jf.asInstanceOf[java.util.function.IntUnaryOperator] + case _ => new AsJavaIntUnaryOperator(underlying) + } + } + + + case class FromJavaLongBinaryOperator(jf: java.util.function.LongBinaryOperator) extends scala.Function2[Long, Long, Long] { + def apply(x1: scala.Long, x2: scala.Long) = jf.applyAsLong(x1, x2) + } + + class RichLongBinaryOperatorAsFunction2(private val underlying: java.util.function.LongBinaryOperator) extends AnyVal { + @inline def asScala: scala.Function2[Long, Long, Long] = underlying match { + case AsJavaLongBinaryOperator((sf @ _)) => sf.asInstanceOf[scala.Function2[Long, Long, Long]] + case _ => new FromJavaLongBinaryOperator(underlying) + } + } + + case class AsJavaLongBinaryOperator(sf: scala.Function2[Long, Long, Long]) extends java.util.function.LongBinaryOperator { + def applyAsLong(x1: scala.Long, x2: scala.Long) = sf.apply(x1, x2) + } + + class RichFunction2AsLongBinaryOperator(private val underlying: scala.Function2[Long, Long, Long]) extends AnyVal { + @inline def asJava: java.util.function.LongBinaryOperator = underlying match { + case FromJavaLongBinaryOperator((jf @ _)) => jf.asInstanceOf[java.util.function.LongBinaryOperator] + case _ => new AsJavaLongBinaryOperator(underlying) + } + } + + + case class FromJavaLongConsumer(jf: java.util.function.LongConsumer) extends scala.Function1[Long, Unit] { + def apply(x1: scala.Long) = jf.accept(x1) + } + + class RichLongConsumerAsFunction1(private val underlying: java.util.function.LongConsumer) extends AnyVal { + @inline def asScala: scala.Function1[Long, Unit] = underlying match { + case AsJavaLongConsumer((sf @ _)) => sf.asInstanceOf[scala.Function1[Long, Unit]] + case _ => new FromJavaLongConsumer(underlying) + } + } + + case class AsJavaLongConsumer(sf: scala.Function1[Long, Unit]) extends java.util.function.LongConsumer { + def accept(x1: scala.Long) = sf.apply(x1) + } + + class RichFunction1AsLongConsumer(private val underlying: scala.Function1[Long, Unit]) extends AnyVal { + @inline def asJava: java.util.function.LongConsumer = underlying match { + case FromJavaLongConsumer((jf @ _)) => jf.asInstanceOf[java.util.function.LongConsumer] + case _ => new AsJavaLongConsumer(underlying) + } + } + + + case class FromJavaLongFunction[R](jf: java.util.function.LongFunction[R]) extends scala.Function1[Long, R] { + def apply(x1: scala.Long) = jf.apply(x1) + } + + class RichLongFunctionAsFunction1[R](private val underlying: java.util.function.LongFunction[R]) extends AnyVal { + @inline def asScala: scala.Function1[Long, R] = underlying match { + case AsJavaLongFunction((sf @ _)) => sf.asInstanceOf[scala.Function1[Long, R]] + case _ => new FromJavaLongFunction[R](underlying) + } + } + + case class AsJavaLongFunction[R](sf: scala.Function1[Long, R]) extends java.util.function.LongFunction[R] { + def apply(x1: scala.Long) = sf.apply(x1) + } + + class RichFunction1AsLongFunction[R](private val underlying: scala.Function1[Long, R]) extends AnyVal { + @inline def asJava: java.util.function.LongFunction[R] = underlying match { + case FromJavaLongFunction((jf @ _)) => jf.asInstanceOf[java.util.function.LongFunction[R]] + case _ => new AsJavaLongFunction[R](underlying) + }; + @inline def asJavaLongFunction: java.util.function.LongFunction[R] = underlying match { + case FromJavaLongFunction((sf @ _)) => sf.asInstanceOf[java.util.function.LongFunction[R]] + case _ => new AsJavaLongFunction[R](underlying) + } + } + + + case class FromJavaLongPredicate(jf: java.util.function.LongPredicate) extends scala.Function1[Long, Boolean] { + def apply(x1: scala.Long) = jf.test(x1) + } + + class RichLongPredicateAsFunction1(private val underlying: java.util.function.LongPredicate) extends AnyVal { + @inline def asScala: scala.Function1[Long, Boolean] = underlying match { + case AsJavaLongPredicate((sf @ _)) => sf.asInstanceOf[scala.Function1[Long, Boolean]] + case _ => new FromJavaLongPredicate(underlying) + } + } + + case class AsJavaLongPredicate(sf: scala.Function1[Long, Boolean]) extends java.util.function.LongPredicate { + def test(x1: scala.Long) = sf.apply(x1) + } + + class RichFunction1AsLongPredicate(private val underlying: scala.Function1[Long, Boolean]) extends AnyVal { + @inline def asJava: java.util.function.LongPredicate = underlying match { + case FromJavaLongPredicate((jf @ _)) => jf.asInstanceOf[java.util.function.LongPredicate] + case _ => new AsJavaLongPredicate(underlying) + } + } + + + case class FromJavaLongSupplier(jf: java.util.function.LongSupplier) extends scala.Function0[Long] { + def apply() = jf.getAsLong() + } + + class RichLongSupplierAsFunction0(private val underlying: java.util.function.LongSupplier) extends AnyVal { + @inline def asScala: scala.Function0[Long] = underlying match { + case AsJavaLongSupplier((sf @ _)) => sf.asInstanceOf[scala.Function0[Long]] + case _ => new FromJavaLongSupplier(underlying) + } + } + + case class AsJavaLongSupplier(sf: scala.Function0[Long]) extends java.util.function.LongSupplier { + def getAsLong() = sf.apply() + } + + class RichFunction0AsLongSupplier(private val underlying: scala.Function0[Long]) extends AnyVal { + @inline def asJava: java.util.function.LongSupplier = underlying match { + case FromJavaLongSupplier((jf @ _)) => jf.asInstanceOf[java.util.function.LongSupplier] + case _ => new AsJavaLongSupplier(underlying) + } + } + + + case class FromJavaLongToDoubleFunction(jf: java.util.function.LongToDoubleFunction) extends scala.Function1[Long, Double] { + def apply(x1: scala.Long) = jf.applyAsDouble(x1) + } + + class RichLongToDoubleFunctionAsFunction1(private val underlying: java.util.function.LongToDoubleFunction) extends AnyVal { + @inline def asScala: scala.Function1[Long, Double] = underlying match { + case AsJavaLongToDoubleFunction((sf @ _)) => sf.asInstanceOf[scala.Function1[Long, Double]] + case _ => new FromJavaLongToDoubleFunction(underlying) + } + } + + case class AsJavaLongToDoubleFunction(sf: scala.Function1[Long, Double]) extends java.util.function.LongToDoubleFunction { + def applyAsDouble(x1: scala.Long) = sf.apply(x1) + } + + class RichFunction1AsLongToDoubleFunction(private val underlying: scala.Function1[Long, Double]) extends AnyVal { + @inline def asJava: java.util.function.LongToDoubleFunction = underlying match { + case FromJavaLongToDoubleFunction((jf @ _)) => jf.asInstanceOf[java.util.function.LongToDoubleFunction] + case _ => new AsJavaLongToDoubleFunction(underlying) + } + } + + + case class FromJavaLongToIntFunction(jf: java.util.function.LongToIntFunction) extends scala.Function1[Long, Int] { + def apply(x1: scala.Long) = jf.applyAsInt(x1) + } + + class RichLongToIntFunctionAsFunction1(private val underlying: java.util.function.LongToIntFunction) extends AnyVal { + @inline def asScala: scala.Function1[Long, Int] = underlying match { + case AsJavaLongToIntFunction((sf @ _)) => sf.asInstanceOf[scala.Function1[Long, Int]] + case _ => new FromJavaLongToIntFunction(underlying) + } + } + + case class AsJavaLongToIntFunction(sf: scala.Function1[Long, Int]) extends java.util.function.LongToIntFunction { + def applyAsInt(x1: scala.Long) = sf.apply(x1) + } + + class RichFunction1AsLongToIntFunction(private val underlying: scala.Function1[Long, Int]) extends AnyVal { + @inline def asJava: java.util.function.LongToIntFunction = underlying match { + case FromJavaLongToIntFunction((jf @ _)) => jf.asInstanceOf[java.util.function.LongToIntFunction] + case _ => new AsJavaLongToIntFunction(underlying) + } + } + + + case class FromJavaLongUnaryOperator(jf: java.util.function.LongUnaryOperator) extends scala.Function1[Long, Long] { + def apply(x1: scala.Long) = jf.applyAsLong(x1) + } + + class RichLongUnaryOperatorAsFunction1(private val underlying: java.util.function.LongUnaryOperator) extends AnyVal { + @inline def asScala: scala.Function1[Long, Long] = underlying match { + case AsJavaLongUnaryOperator((sf @ _)) => sf.asInstanceOf[scala.Function1[Long, Long]] + case _ => new FromJavaLongUnaryOperator(underlying) + } + } + + case class AsJavaLongUnaryOperator(sf: scala.Function1[Long, Long]) extends java.util.function.LongUnaryOperator { + def applyAsLong(x1: scala.Long) = sf.apply(x1) + } + + class RichFunction1AsLongUnaryOperator(private val underlying: scala.Function1[Long, Long]) extends AnyVal { + @inline def asJava: java.util.function.LongUnaryOperator = underlying match { + case FromJavaLongUnaryOperator((jf @ _)) => jf.asInstanceOf[java.util.function.LongUnaryOperator] + case _ => new AsJavaLongUnaryOperator(underlying) + } + } + + + case class FromJavaObjDoubleConsumer[T](jf: java.util.function.ObjDoubleConsumer[T]) extends scala.Function2[T, Double, Unit] { + def apply(x1: T, x2: scala.Double) = jf.accept(x1, x2) + } + + class RichObjDoubleConsumerAsFunction2[T](private val underlying: java.util.function.ObjDoubleConsumer[T]) extends AnyVal { + @inline def asScala: scala.Function2[T, Double, Unit] = underlying match { + case AsJavaObjDoubleConsumer((sf @ _)) => sf.asInstanceOf[scala.Function2[T, Double, Unit]] + case _ => new FromJavaObjDoubleConsumer[T](underlying) + } + } + + case class AsJavaObjDoubleConsumer[T](sf: scala.Function2[T, Double, Unit]) extends java.util.function.ObjDoubleConsumer[T] { + def accept(x1: T, x2: scala.Double) = sf.apply(x1, x2) + } + + class RichFunction2AsObjDoubleConsumer[T](private val underlying: scala.Function2[T, Double, Unit]) extends AnyVal { + @inline def asJava: java.util.function.ObjDoubleConsumer[T] = underlying match { + case FromJavaObjDoubleConsumer((jf @ _)) => jf.asInstanceOf[java.util.function.ObjDoubleConsumer[T]] + case _ => new AsJavaObjDoubleConsumer[T](underlying) + }; + @inline def asJavaObjDoubleConsumer: java.util.function.ObjDoubleConsumer[T] = underlying match { + case FromJavaObjDoubleConsumer((sf @ _)) => sf.asInstanceOf[java.util.function.ObjDoubleConsumer[T]] + case _ => new AsJavaObjDoubleConsumer[T](underlying) + } + } + + + case class FromJavaObjIntConsumer[T](jf: java.util.function.ObjIntConsumer[T]) extends scala.Function2[T, Int, Unit] { + def apply(x1: T, x2: scala.Int) = jf.accept(x1, x2) + } + + class RichObjIntConsumerAsFunction2[T](private val underlying: java.util.function.ObjIntConsumer[T]) extends AnyVal { + @inline def asScala: scala.Function2[T, Int, Unit] = underlying match { + case AsJavaObjIntConsumer((sf @ _)) => sf.asInstanceOf[scala.Function2[T, Int, Unit]] + case _ => new FromJavaObjIntConsumer[T](underlying) + } + } + + case class AsJavaObjIntConsumer[T](sf: scala.Function2[T, Int, Unit]) extends java.util.function.ObjIntConsumer[T] { + def accept(x1: T, x2: scala.Int) = sf.apply(x1, x2) + } + + class RichFunction2AsObjIntConsumer[T](private val underlying: scala.Function2[T, Int, Unit]) extends AnyVal { + @inline def asJava: java.util.function.ObjIntConsumer[T] = underlying match { + case FromJavaObjIntConsumer((jf @ _)) => jf.asInstanceOf[java.util.function.ObjIntConsumer[T]] + case _ => new AsJavaObjIntConsumer[T](underlying) + }; + @inline def asJavaObjIntConsumer: java.util.function.ObjIntConsumer[T] = underlying match { + case FromJavaObjIntConsumer((sf @ _)) => sf.asInstanceOf[java.util.function.ObjIntConsumer[T]] + case _ => new AsJavaObjIntConsumer[T](underlying) + } + } + + + case class FromJavaObjLongConsumer[T](jf: java.util.function.ObjLongConsumer[T]) extends scala.Function2[T, Long, Unit] { + def apply(x1: T, x2: scala.Long) = jf.accept(x1, x2) + } + + class RichObjLongConsumerAsFunction2[T](private val underlying: java.util.function.ObjLongConsumer[T]) extends AnyVal { + @inline def asScala: scala.Function2[T, Long, Unit] = underlying match { + case AsJavaObjLongConsumer((sf @ _)) => sf.asInstanceOf[scala.Function2[T, Long, Unit]] + case _ => new FromJavaObjLongConsumer[T](underlying) + } + } + + case class AsJavaObjLongConsumer[T](sf: scala.Function2[T, Long, Unit]) extends java.util.function.ObjLongConsumer[T] { + def accept(x1: T, x2: scala.Long) = sf.apply(x1, x2) + } + + class RichFunction2AsObjLongConsumer[T](private val underlying: scala.Function2[T, Long, Unit]) extends AnyVal { + @inline def asJava: java.util.function.ObjLongConsumer[T] = underlying match { + case FromJavaObjLongConsumer((jf @ _)) => jf.asInstanceOf[java.util.function.ObjLongConsumer[T]] + case _ => new AsJavaObjLongConsumer[T](underlying) + }; + @inline def asJavaObjLongConsumer: java.util.function.ObjLongConsumer[T] = underlying match { + case FromJavaObjLongConsumer((sf @ _)) => sf.asInstanceOf[java.util.function.ObjLongConsumer[T]] + case _ => new AsJavaObjLongConsumer[T](underlying) + } + } + + + case class FromJavaPredicate[T](jf: java.util.function.Predicate[T]) extends scala.Function1[T, Boolean] { + def apply(x1: T) = jf.test(x1) + } + + class RichPredicateAsFunction1[T](private val underlying: java.util.function.Predicate[T]) extends AnyVal { + @inline def asScala: scala.Function1[T, Boolean] = underlying match { + case AsJavaPredicate((sf @ _)) => sf.asInstanceOf[scala.Function1[T, Boolean]] + case _ => new FromJavaPredicate[T](underlying) + } + } + + case class AsJavaPredicate[T](sf: scala.Function1[T, Boolean]) extends java.util.function.Predicate[T] { + def test(x1: T) = sf.apply(x1) + } + + class RichFunction1AsPredicate[T](private val underlying: scala.Function1[T, Boolean]) extends AnyVal { + @inline def asJava: java.util.function.Predicate[T] = underlying match { + case FromJavaPredicate((jf @ _)) => jf.asInstanceOf[java.util.function.Predicate[T]] + case _ => new AsJavaPredicate[T](underlying) + }; + @inline def asJavaPredicate: java.util.function.Predicate[T] = underlying match { + case FromJavaPredicate((sf @ _)) => sf.asInstanceOf[java.util.function.Predicate[T]] + case _ => new AsJavaPredicate[T](underlying) + } + } + + + case class FromJavaSupplier[T](jf: java.util.function.Supplier[T]) extends scala.Function0[T] { + def apply() = jf.get() + } + + class RichSupplierAsFunction0[T](private val underlying: java.util.function.Supplier[T]) extends AnyVal { + @inline def asScala: scala.Function0[T] = underlying match { + case AsJavaSupplier((sf @ _)) => sf.asInstanceOf[scala.Function0[T]] + case _ => new FromJavaSupplier[T](underlying) + } + } + + case class AsJavaSupplier[T](sf: scala.Function0[T]) extends java.util.function.Supplier[T] { + def get() = sf.apply() + } + + class RichFunction0AsSupplier[T](private val underlying: scala.Function0[T]) extends AnyVal { + @inline def asJava: java.util.function.Supplier[T] = underlying match { + case FromJavaSupplier((jf @ _)) => jf.asInstanceOf[java.util.function.Supplier[T]] + case _ => new AsJavaSupplier[T](underlying) + }; + @inline def asJavaSupplier: java.util.function.Supplier[T] = underlying match { + case FromJavaSupplier((sf @ _)) => sf.asInstanceOf[java.util.function.Supplier[T]] + case _ => new AsJavaSupplier[T](underlying) + } + } + + + case class FromJavaToDoubleBiFunction[T, U](jf: java.util.function.ToDoubleBiFunction[T, U]) extends scala.Function2[T, U, Double] { + def apply(x1: T, x2: U) = jf.applyAsDouble(x1, x2) + } + + class RichToDoubleBiFunctionAsFunction2[T, U](private val underlying: java.util.function.ToDoubleBiFunction[T, U]) extends AnyVal { + @inline def asScala: scala.Function2[T, U, Double] = underlying match { + case AsJavaToDoubleBiFunction((sf @ _)) => sf.asInstanceOf[scala.Function2[T, U, Double]] + case _ => new FromJavaToDoubleBiFunction[T, U](underlying) + } + } + + case class AsJavaToDoubleBiFunction[T, U](sf: scala.Function2[T, U, Double]) extends java.util.function.ToDoubleBiFunction[T, U] { + def applyAsDouble(x1: T, x2: U) = sf.apply(x1, x2) + } + + class RichFunction2AsToDoubleBiFunction[T, U](private val underlying: scala.Function2[T, U, Double]) extends AnyVal { + @inline def asJava: java.util.function.ToDoubleBiFunction[T, U] = underlying match { + case FromJavaToDoubleBiFunction((jf @ _)) => jf.asInstanceOf[java.util.function.ToDoubleBiFunction[T, U]] + case _ => new AsJavaToDoubleBiFunction[T, U](underlying) + }; + @inline def asJavaToDoubleBiFunction: java.util.function.ToDoubleBiFunction[T, U] = underlying match { + case FromJavaToDoubleBiFunction((sf @ _)) => sf.asInstanceOf[java.util.function.ToDoubleBiFunction[T, U]] + case _ => new AsJavaToDoubleBiFunction[T, U](underlying) + } + } + + + case class FromJavaToDoubleFunction[T](jf: java.util.function.ToDoubleFunction[T]) extends scala.Function1[T, Double] { + def apply(x1: T) = jf.applyAsDouble(x1) + } + + class RichToDoubleFunctionAsFunction1[T](private val underlying: java.util.function.ToDoubleFunction[T]) extends AnyVal { + @inline def asScala: scala.Function1[T, Double] = underlying match { + case AsJavaToDoubleFunction((sf @ _)) => sf.asInstanceOf[scala.Function1[T, Double]] + case _ => new FromJavaToDoubleFunction[T](underlying) + } + } + + case class AsJavaToDoubleFunction[T](sf: scala.Function1[T, Double]) extends java.util.function.ToDoubleFunction[T] { + def applyAsDouble(x1: T) = sf.apply(x1) + } + + class RichFunction1AsToDoubleFunction[T](private val underlying: scala.Function1[T, Double]) extends AnyVal { + @inline def asJava: java.util.function.ToDoubleFunction[T] = underlying match { + case FromJavaToDoubleFunction((jf @ _)) => jf.asInstanceOf[java.util.function.ToDoubleFunction[T]] + case _ => new AsJavaToDoubleFunction[T](underlying) + }; + @inline def asJavaToDoubleFunction: java.util.function.ToDoubleFunction[T] = underlying match { + case FromJavaToDoubleFunction((sf @ _)) => sf.asInstanceOf[java.util.function.ToDoubleFunction[T]] + case _ => new AsJavaToDoubleFunction[T](underlying) + } + } + + + case class FromJavaToIntBiFunction[T, U](jf: java.util.function.ToIntBiFunction[T, U]) extends scala.Function2[T, U, Int] { + def apply(x1: T, x2: U) = jf.applyAsInt(x1, x2) + } + + class RichToIntBiFunctionAsFunction2[T, U](private val underlying: java.util.function.ToIntBiFunction[T, U]) extends AnyVal { + @inline def asScala: scala.Function2[T, U, Int] = underlying match { + case AsJavaToIntBiFunction((sf @ _)) => sf.asInstanceOf[scala.Function2[T, U, Int]] + case _ => new FromJavaToIntBiFunction[T, U](underlying) + } + } + + case class AsJavaToIntBiFunction[T, U](sf: scala.Function2[T, U, Int]) extends java.util.function.ToIntBiFunction[T, U] { + def applyAsInt(x1: T, x2: U) = sf.apply(x1, x2) + } + + class RichFunction2AsToIntBiFunction[T, U](private val underlying: scala.Function2[T, U, Int]) extends AnyVal { + @inline def asJava: java.util.function.ToIntBiFunction[T, U] = underlying match { + case FromJavaToIntBiFunction((jf @ _)) => jf.asInstanceOf[java.util.function.ToIntBiFunction[T, U]] + case _ => new AsJavaToIntBiFunction[T, U](underlying) + }; + @inline def asJavaToIntBiFunction: java.util.function.ToIntBiFunction[T, U] = underlying match { + case FromJavaToIntBiFunction((sf @ _)) => sf.asInstanceOf[java.util.function.ToIntBiFunction[T, U]] + case _ => new AsJavaToIntBiFunction[T, U](underlying) + } + } + + + case class FromJavaToIntFunction[T](jf: java.util.function.ToIntFunction[T]) extends scala.Function1[T, Int] { + def apply(x1: T) = jf.applyAsInt(x1) + } + + class RichToIntFunctionAsFunction1[T](private val underlying: java.util.function.ToIntFunction[T]) extends AnyVal { + @inline def asScala: scala.Function1[T, Int] = underlying match { + case AsJavaToIntFunction((sf @ _)) => sf.asInstanceOf[scala.Function1[T, Int]] + case _ => new FromJavaToIntFunction[T](underlying) + } + } + + case class AsJavaToIntFunction[T](sf: scala.Function1[T, Int]) extends java.util.function.ToIntFunction[T] { + def applyAsInt(x1: T) = sf.apply(x1) + } + + class RichFunction1AsToIntFunction[T](private val underlying: scala.Function1[T, Int]) extends AnyVal { + @inline def asJava: java.util.function.ToIntFunction[T] = underlying match { + case FromJavaToIntFunction((jf @ _)) => jf.asInstanceOf[java.util.function.ToIntFunction[T]] + case _ => new AsJavaToIntFunction[T](underlying) + }; + @inline def asJavaToIntFunction: java.util.function.ToIntFunction[T] = underlying match { + case FromJavaToIntFunction((sf @ _)) => sf.asInstanceOf[java.util.function.ToIntFunction[T]] + case _ => new AsJavaToIntFunction[T](underlying) + } + } + + + case class FromJavaToLongBiFunction[T, U](jf: java.util.function.ToLongBiFunction[T, U]) extends scala.Function2[T, U, Long] { + def apply(x1: T, x2: U) = jf.applyAsLong(x1, x2) + } + + class RichToLongBiFunctionAsFunction2[T, U](private val underlying: java.util.function.ToLongBiFunction[T, U]) extends AnyVal { + @inline def asScala: scala.Function2[T, U, Long] = underlying match { + case AsJavaToLongBiFunction((sf @ _)) => sf.asInstanceOf[scala.Function2[T, U, Long]] + case _ => new FromJavaToLongBiFunction[T, U](underlying) + } + } + + case class AsJavaToLongBiFunction[T, U](sf: scala.Function2[T, U, Long]) extends java.util.function.ToLongBiFunction[T, U] { + def applyAsLong(x1: T, x2: U) = sf.apply(x1, x2) + } + + class RichFunction2AsToLongBiFunction[T, U](private val underlying: scala.Function2[T, U, Long]) extends AnyVal { + @inline def asJava: java.util.function.ToLongBiFunction[T, U] = underlying match { + case FromJavaToLongBiFunction((jf @ _)) => jf.asInstanceOf[java.util.function.ToLongBiFunction[T, U]] + case _ => new AsJavaToLongBiFunction[T, U](underlying) + }; + @inline def asJavaToLongBiFunction: java.util.function.ToLongBiFunction[T, U] = underlying match { + case FromJavaToLongBiFunction((sf @ _)) => sf.asInstanceOf[java.util.function.ToLongBiFunction[T, U]] + case _ => new AsJavaToLongBiFunction[T, U](underlying) + } + } + + + case class FromJavaToLongFunction[T](jf: java.util.function.ToLongFunction[T]) extends scala.Function1[T, Long] { + def apply(x1: T) = jf.applyAsLong(x1) + } + + class RichToLongFunctionAsFunction1[T](private val underlying: java.util.function.ToLongFunction[T]) extends AnyVal { + @inline def asScala: scala.Function1[T, Long] = underlying match { + case AsJavaToLongFunction((sf @ _)) => sf.asInstanceOf[scala.Function1[T, Long]] + case _ => new FromJavaToLongFunction[T](underlying) + } + } + + case class AsJavaToLongFunction[T](sf: scala.Function1[T, Long]) extends java.util.function.ToLongFunction[T] { + def applyAsLong(x1: T) = sf.apply(x1) + } + + class RichFunction1AsToLongFunction[T](private val underlying: scala.Function1[T, Long]) extends AnyVal { + @inline def asJava: java.util.function.ToLongFunction[T] = underlying match { + case FromJavaToLongFunction((jf @ _)) => jf.asInstanceOf[java.util.function.ToLongFunction[T]] + case _ => new AsJavaToLongFunction[T](underlying) + }; + @inline def asJavaToLongFunction: java.util.function.ToLongFunction[T] = underlying match { + case FromJavaToLongFunction((sf @ _)) => sf.asInstanceOf[java.util.function.ToLongFunction[T]] + case _ => new AsJavaToLongFunction[T](underlying) + } + } + + + case class FromJavaUnaryOperator[T](jf: java.util.function.UnaryOperator[T]) extends scala.Function1[T, T] { + def apply(x1: T) = jf.apply(x1) + } + + class RichUnaryOperatorAsFunction1[T](private val underlying: java.util.function.UnaryOperator[T]) extends AnyVal { + @inline def asScala: scala.Function1[T, T] = underlying match { + case AsJavaUnaryOperator((sf @ _)) => sf.asInstanceOf[scala.Function1[T, T]] + case _ => new FromJavaUnaryOperator[T](underlying) + } + } + + case class AsJavaUnaryOperator[T](sf: scala.Function1[T, T]) extends java.util.function.UnaryOperator[T] { + def apply(x1: T) = sf.apply(x1) + } + + class RichFunction1AsUnaryOperator[T](private val underlying: scala.Function1[T, T]) extends AnyVal { + @inline def asJava: java.util.function.UnaryOperator[T] = underlying match { + case FromJavaUnaryOperator((jf @ _)) => jf.asInstanceOf[java.util.function.UnaryOperator[T]] + case _ => new AsJavaUnaryOperator[T](underlying) + }; + @inline def asJavaUnaryOperator: java.util.function.UnaryOperator[T] = underlying match { + case FromJavaUnaryOperator((sf @ _)) => sf.asInstanceOf[java.util.function.UnaryOperator[T]] + case _ => new AsJavaUnaryOperator[T](underlying) + } + } +} diff --git a/library/src/scala/jdk/FutureConverters.scala b/library/src/scala/jdk/FutureConverters.scala new file mode 100644 index 000000000000..aea951238cb7 --- /dev/null +++ b/library/src/scala/jdk/FutureConverters.scala @@ -0,0 +1,44 @@ +/* + * 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.jdk + +import scala.language.`2.13` +import java.util.concurrent.CompletionStage + +import scala.concurrent.Future + +/** This object provides extension methods that convert between Scala [[scala.concurrent.Future]] and Java + * [[java.util.concurrent.CompletionStage]] + * + * When writing Java code, use the explicit conversion methods defined in + * [[javaapi.FutureConverters]] instead. + * + * Note that the bridge is implemented at the read-only side of asynchronous handles, namely + * [[scala.concurrent.Future]] (instead of [[scala.concurrent.Promise]]) and [[java.util.concurrent.CompletionStage]] (instead of + * [[java.util.concurrent.CompletableFuture]]). This is intentional, as the semantics of bridging + * the write-handles would be prone to race conditions; if both ends (`CompletableFuture` and + * `Promise`) are completed independently at the same time, they may contain different values + * afterwards. For this reason, `toCompletableFuture` is not supported on the created + * `CompletionStage`s. + */ +object FutureConverters { + implicit class FutureOps[T](private val f: Future[T]) extends AnyVal { + /** Convert a Scala Future to a Java CompletionStage, see [[javaapi.FutureConverters.asJava]]. */ + def asJava: CompletionStage[T] = javaapi.FutureConverters.asJava(f) + } + + implicit class CompletionStageOps[T](private val cs: CompletionStage[T]) extends AnyVal { + /** Convert a Java CompletionStage to a Scala Future, see [[javaapi.FutureConverters.asScala]]. */ + def asScala: Future[T] = javaapi.FutureConverters.asScala(cs) + } +} diff --git a/library/src/scala/jdk/IntAccumulator.scala b/library/src/scala/jdk/IntAccumulator.scala new file mode 100644 index 000000000000..2b507940ec2e --- /dev/null +++ b/library/src/scala/jdk/IntAccumulator.scala @@ -0,0 +1,495 @@ +/* + * 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.jdk + +import java.io.{ObjectInputStream, ObjectOutputStream} +import java.util.Spliterator +import java.util.function.{Consumer, IntConsumer} +import java.{lang => jl} + +import scala.annotation._ +import scala.collection.Stepper.EfficientSplit +import scala.collection.{AnyStepper, Factory, IntStepper, SeqFactory, Stepper, StepperShape, mutable} +import scala.language.implicitConversions + +import scala.language.`2.13` + +/** A specialized Accumulator that holds `Int`s without boxing, see [[Accumulator]]. */ +final class IntAccumulator + extends Accumulator[Int, AnyAccumulator, IntAccumulator] + with mutable.SeqOps[Int, AnyAccumulator, IntAccumulator] + with Serializable { + private[jdk] var current: Array[Int] = IntAccumulator.emptyIntArray + private[jdk] var history: Array[Array[Int]] = IntAccumulator.emptyIntArrayArray + + private[jdk] def cumulative(i: Int) = { val x = history(i); x(x.length-2).toLong << 32 | (x(x.length-1)&0xFFFFFFFFL) } + + override protected[this] def className: String = "IntAccumulator" + + def efficientStepper[S <: Stepper[_]](implicit shape: StepperShape[Int, S]): S with EfficientSplit = { + val st = new IntAccumulatorStepper(this) + val r = + if (shape.shape == StepperShape.IntShape) st + else { + assert(shape.shape == StepperShape.ReferenceShape, s"unexpected StepperShape: $shape") + AnyStepper.ofParIntStepper(st) + } + r.asInstanceOf[S with EfficientSplit] + } + + private def expand(): Unit = { + if (index > 0) { + val cuml = (if (hIndex > 0) cumulative(hIndex-1) else 0) + index + current(current.length-2) = (cuml >>> 32).toInt + current(current.length-1) = (cuml & 0xFFFFFFFFL).toInt + if (hIndex >= history.length) hExpand() + history(hIndex) = current + hIndex += 1 + } + current = new Array[Int](nextBlockSize+1) + index = 0 + } + + private def hExpand(): Unit = { + if (hIndex == 0) history = new Array[Array[Int]](4) + else history = java.util.Arrays.copyOf(history, history.length << 1) + } + + /** Appends an element to this `IntAccumulator`. */ + def addOne(a: Int): this.type = { + totalSize += 1 + if (index+2 >= current.length) expand() + current(index) = a + index += 1 + this + } + + /** Result collection consisting of all elements appended so far. */ + override def result(): IntAccumulator = this + + /** Removes all elements from `that` and appends them to this `IntAccumulator`. */ + def drain(that: IntAccumulator): Unit = { + var h = 0 + var prev = 0L + var more = true + while (more && h < that.hIndex) { + val cuml = that.cumulative(h) + val n = (cuml - prev).toInt + if (current.length - index - 2 >= n) { + System.arraycopy(that.history(h), 0, current, index, n) + prev = cuml + index += n + h += 1 + } + else more = false + } + if (h >= that.hIndex && current.length - index - 2 >= that.index) { + if (that.index > 0) System.arraycopy(that.current, 0, current, index, that.index) + index += that.index + } + else { + val slots = (if (index > 0) 1 else 0) + that.hIndex - h + if (hIndex + slots > history.length) { + val n = math.max(4, 1 << (32 - jl.Integer.numberOfLeadingZeros(1 + hIndex + slots))) + history = java.util.Arrays.copyOf(history, n) + } + var pv = if (hIndex > 0) cumulative(hIndex-1) else 0L + if (index > 0) { + val x = + if (index < (current.length >>> 3) && current.length - 1 > 32) { + val ans = java.util.Arrays.copyOf(current, index + 2) + ans(ans.length - 2) = current(current.length - 2) + ans(ans.length - 1) = current(current.length - 1) + ans + } + else current + pv = pv + index + x(x.length - 2) = (pv >>> 32).toInt + x(x.length - 1) = (pv & 0xFFFFFFFFL).toInt + history(hIndex) = x + hIndex += 1 + } + while (h < that.hIndex) { + val cuml = that.cumulative(h) + pv = pv + cuml - prev + prev = cuml + val x = that.history(h) + x(x.length - 2) = (pv >>> 32).toInt + x(x.length - 1) = (pv & 0xFFFFFFFFL).toInt + history(hIndex) = x + h += 1 + hIndex += 1 + } + index = that.index + current = that.current + } + totalSize += that.totalSize + that.clear() + } + + override def clear(): Unit = { + super.clear() + current = IntAccumulator.emptyIntArray + history = IntAccumulator.emptyIntArrayArray + } + + /** Retrieves the `ix`th element. */ + def apply(ix: Long): Int = { + if (totalSize - ix <= index || hIndex == 0) current((ix - (totalSize - index)).toInt) + else { + val w = seekSlot(ix) + history((w >>> 32).toInt)((w & 0xFFFFFFFFL).toInt) + } + } + + /** Retrieves the `ix`th element, using an `Int` index. */ + def apply(i: Int): Int = apply(i.toLong) + + def update(idx: Long, elem: Int): Unit = { + if (totalSize - idx <= index || hIndex == 0) current((idx - (totalSize - index)).toInt) = elem + else { + val w = seekSlot(idx) + history((w >>> 32).toInt)((w & 0xFFFFFFFFL).toInt) = elem + } + } + + def update(idx: Int, elem: Int): Unit = update(idx.toLong, elem) + + /** Returns an `Iterator` over the contents of this `IntAccumulator`. The `Iterator` is not specialized. */ + def iterator: Iterator[Int] = stepper.iterator + + override def foreach[U](f: Int => U): Unit = { + val s = stepper + while (s.hasStep) f(s.nextStep()) + } + + def map(f: Int => Int): IntAccumulator = { + val b = newSpecificBuilder + val s = stepper + while (s.hasStep) + b.addOne(f(s.nextStep())) + b.result() + } + + def flatMap(f: Int => IterableOnce[Int]): IntAccumulator = { + val b = newSpecificBuilder + val s = stepper + while (s.hasStep) + b.addAll(f(s.nextStep())) + b.result() + } + + def collect(pf: PartialFunction[Int, Int]): IntAccumulator = { + val b = newSpecificBuilder + val s = stepper + while (s.hasStep) { + val n = s.nextStep() + pf.runWith(b.addOne)(n) + } + b.result() + } + + private def filterAccImpl(pred: Int => Boolean, not: Boolean): IntAccumulator = { + val b = newSpecificBuilder + val s = stepper + while (s.hasStep) { + val n = s.nextStep() + if (pred(n) != not) b.addOne(n) + } + b.result() + } + + override def filter(pred: Int => Boolean): IntAccumulator = filterAccImpl(pred, not = false) + + override def filterNot(pred: Int => Boolean): IntAccumulator = filterAccImpl(pred, not = true) + + override def forall(p: Int => Boolean): Boolean = { + val s = stepper + while (s.hasStep) + if (!p(s.nextStep())) return false + true + } + + override def exists(p: Int => Boolean): Boolean = { + val s = stepper + while (s.hasStep) + if (p(s.nextStep())) return true + false + } + + override def count(p: Int => Boolean): Int = { + var r = 0 + val s = stepper + while (s.hasStep) + if (p(s.nextStep())) r += 1 + r + } + + def countLong(p: Int => Boolean): Long = { + var r = 0L + val s = stepper + while (s.hasStep) + if (p(s.nextStep())) r += 1 + r + } + + /** Copies the elements in this `IntAccumulator` into an `Array[Int]` */ + @nowarn // cat=lint-overload see toArray[B: ClassTag] + def toArray: Array[Int] = { + if (totalSize > Int.MaxValue) throw new IllegalArgumentException("Too many elements accumulated for an array: "+totalSize.toString) + val a = new Array[Int](totalSize.toInt) + var j = 0 + var h = 0 + var pv = 0L + while (h < hIndex) { + val x = history(h) + val cuml = cumulative(h) + val n = (cuml - pv).toInt + pv = cuml + System.arraycopy(x, 0, a, j, n) + j += n + h += 1 + } + System.arraycopy(current, 0, a, j, index) + j += index + a + } + + /** Copies the elements in this `IntAccumulator` to a `List` */ + override def toList: List[Int] = { + var ans: List[Int] = Nil + var i = index - 1 + while (i >= 0) { + ans = current(i) :: ans + i -= 1 + } + var h = hIndex - 1 + while (h >= 0) { + val a = history(h) + i = (cumulative(h) - (if (h == 0) 0L else cumulative(h-1))).toInt - 1 + while (i >= 0) { + ans = a(i) :: ans + i -= 1 + } + h -= 1 + } + ans + } + + /** + * Copy the elements in this `IntAccumulator` to a specified collection. + * Note that the target collection is not specialized. + * Usage example: `acc.to(Vector)` + */ + override def to[C1](factory: Factory[Int, C1]): C1 = { + if (totalSize > Int.MaxValue) throw new IllegalArgumentException("Too many elements accumulated for a Scala collection: "+totalSize.toString) + factory.fromSpecific(iterator) + } + + override protected def fromSpecific(coll: IterableOnce[Int]): IntAccumulator = IntAccumulator.fromSpecific(coll) + override protected def newSpecificBuilder: IntAccumulator = IntAccumulator.newBuilder + override def iterableFactory: SeqFactory[AnyAccumulator] = AnyAccumulator + + override def empty: IntAccumulator = IntAccumulator.empty + + private def writeReplace(): AnyRef = new IntAccumulator.SerializationProxy(this) +} + +object IntAccumulator extends collection.SpecificIterableFactory[Int, IntAccumulator] { + private val emptyIntArray = new Array[Int](0) + private val emptyIntArrayArray = new Array[Array[Int]](0) + + implicit def toJavaIntegerAccumulator(ia: IntAccumulator.type): collection.SpecificIterableFactory[jl.Integer, IntAccumulator] = IntAccumulator.asInstanceOf[collection.SpecificIterableFactory[jl.Integer, IntAccumulator]] + + import java.util.{function => jf} + + /** A `Supplier` of `IntAccumulator`s, suitable for use with `java.util.stream.IntStream`'s `collect` method. Suitable for `Stream[Int]` also. */ + def supplier: jf.Supplier[IntAccumulator] = () => new IntAccumulator + + /** A `BiConsumer` that adds an element to an `IntAccumulator`, suitable for use with `java.util.stream.IntStream`'s `collect` method. */ + def adder: jf.ObjIntConsumer[IntAccumulator] = (ac: IntAccumulator, a: Int) => ac addOne a + + /** A `BiConsumer` that adds a boxed `Int` to an `IntAccumulator`, suitable for use with `java.util.stream.Stream`'s `collect` method. */ + def boxedAdder: jf.BiConsumer[IntAccumulator, Int] = (ac: IntAccumulator, a: Int) => ac addOne a + + /** A `BiConsumer` that merges `IntAccumulator`s, suitable for use with `java.util.stream.IntStream`'s `collect` method. Suitable for `Stream[Int]` also. */ + def merger: jf.BiConsumer[IntAccumulator, IntAccumulator] = (a1: IntAccumulator, a2: IntAccumulator) => a1 drain a2 + + private def fromArray(a: Array[Int]): IntAccumulator = { + val r = new IntAccumulator + var i = 0 + while (i < a.length) { r addOne a(i); i += 1 } + r + } + + override def fromSpecific(it: IterableOnce[Int]): IntAccumulator = it match { + case acc: IntAccumulator => acc + case as: collection.immutable.ArraySeq.ofInt => fromArray(as.unsafeArray) + case as: collection.mutable.ArraySeq.ofInt => fromArray(as.array) // this case ensures Array(1).to(Accumulator) doesn't box + case _ => (new IntAccumulator).addAll(it) + } + + override def empty: IntAccumulator = new IntAccumulator + + override def newBuilder: IntAccumulator = new IntAccumulator + + class SerializationProxy[A](@transient private val acc: IntAccumulator) extends Serializable { + @transient private var result: IntAccumulator = _ + + private def writeObject(out: ObjectOutputStream): Unit = { + out.defaultWriteObject() + val size = acc.sizeLong + out.writeLong(size) + val st = acc.stepper + while (st.hasStep) + out.writeInt(st.nextStep()) + } + + private def readObject(in: ObjectInputStream): Unit = { + in.defaultReadObject() + val res = new IntAccumulator() + var elems = in.readLong() + while (elems > 0) { + res += in.readInt() + elems -= 1L + } + result = res + } + + private def readResolve(): AnyRef = result + } +} + +private[jdk] class IntAccumulatorStepper(private val acc: IntAccumulator) extends IntStepper with EfficientSplit { + import java.util.Spliterator._ + + private var h: Int = 0 + private var i: Int = 0 + private var a: Array[Int] = if (acc.hIndex > 0) acc.history(0) else acc.current + private var n: Long = if (acc.hIndex > 0) acc.cumulative(0) else acc.index + private var N: Long = acc.totalSize + + private def duplicateSelf(limit: Long): IntAccumulatorStepper = { + val ans = new IntAccumulatorStepper(acc) + ans.h = h + ans.i = i + ans.a = a + ans.n = n + ans.N = limit + ans + } + + private def loadMore(): Unit = { + h += 1 + if (h < acc.hIndex) { a = acc.history(h); n = acc.cumulative(h) - acc.cumulative(h-1) } + else { a = acc.current; n = acc.index } + i = 0 + } + + def characteristics: Int = ORDERED | SIZED | SUBSIZED | NONNULL + + def estimateSize: Long = N + + def hasStep: Boolean = N > 0 + + def nextStep(): Int = + if (N <= 0) throw new NoSuchElementException("next on empty Stepper") + else { + if (i >= n) loadMore() + val ans = a(i) + i += 1 + N -= 1 + ans + } + + def trySplit(): IntStepper = + if (N <= 1) null + else { + val half = N >> 1 + val M = (if (h <= 0) 0L else acc.cumulative(h-1)) + i + val R = M + half + val ans = duplicateSelf(half) + if (h < acc.hIndex) { + val w = acc.seekSlot(R) + h = (w >>> 32).toInt + if (h < acc.hIndex) { + a = acc.history(h) + n = acc.cumulative(h) - (if (h > 0) acc.cumulative(h-1) else 0) + } + else { + a = acc.current + n = acc.index + } + i = (w & 0xFFFFFFFFL).toInt + } + else i += half.toInt + N -= half + ans + } + + override def spliterator[B >: Int]: Spliterator.OfInt = new IntStepper.IntStepperSpliterator(this) { + // Overridden for efficiency + override def tryAdvance(c: IntConsumer): Boolean = + if (N <= 0) false + else { + if (i >= n) loadMore() + c.accept(a(i)) + i += 1 + N -= 1 + true + } + + // Overridden for efficiency + override def tryAdvance(c: Consumer[_ >: jl.Integer]): Boolean = (c: AnyRef) match { + case ic: IntConsumer => tryAdvance(ic) + case _ => + if (N <= 0) false + else { + if (i >= n) loadMore() + c.accept(a(i)) + i += 1 + N -= 1 + true + } + } + + // Overridden for efficiency + override def forEachRemaining(c: IntConsumer): Unit = + while (N > 0) { + if (i >= n) loadMore() + val i0 = i + if ((n-i) > N) n = i + N.toInt + while (i < n) { + c.accept(a(i)) + i += 1 + } + N -= (n - i0) + } + + // Overridden for efficiency + override def forEachRemaining(c: Consumer[_ >: jl.Integer]): Unit = (c: AnyRef) match { + case ic: IntConsumer => forEachRemaining(ic) + case _ => + while (N > 0) { + if (i >= n) loadMore() + val i0 = i + if ((n-i) > N) n = i + N.toInt + while (i < n) { + c.accept(a(i)) + i += 1 + } + N -= (n - i0) + } + } + } +} diff --git a/library/src/scala/jdk/LongAccumulator.scala b/library/src/scala/jdk/LongAccumulator.scala new file mode 100644 index 000000000000..dd39c6e05a4d --- /dev/null +++ b/library/src/scala/jdk/LongAccumulator.scala @@ -0,0 +1,490 @@ +/* + * 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.jdk + +import java.io.{ObjectInputStream, ObjectOutputStream} +import java.util.Spliterator +import java.util.function.{Consumer, LongConsumer} +import java.{lang => jl} + +import scala.annotation._ +import scala.collection.Stepper.EfficientSplit +import scala.collection.{AnyStepper, Factory, LongStepper, SeqFactory, Stepper, StepperShape, mutable} +import scala.language.implicitConversions + +import scala.language.`2.13` + +/** A specialized Accumulator that holds `Long`s without boxing, see [[Accumulator]]. */ +final class LongAccumulator + extends Accumulator[Long, AnyAccumulator, LongAccumulator] + with mutable.SeqOps[Long, AnyAccumulator, LongAccumulator] + with Serializable { + private[jdk] var current: Array[Long] = LongAccumulator.emptyLongArray + private[jdk] var history: Array[Array[Long]] = LongAccumulator.emptyLongArrayArray + + private[jdk] def cumulative(i: Int) = { val x = history(i); x(x.length-1) } + + override protected[this] def className: String = "LongAccumulator" + + def efficientStepper[S <: Stepper[_]](implicit shape: StepperShape[Long, S]): S with EfficientSplit = { + val st = new LongAccumulatorStepper(this) + val r = + if (shape.shape == StepperShape.LongShape) st + else { + assert(shape.shape == StepperShape.ReferenceShape, s"unexpected StepperShape: $shape") + AnyStepper.ofParLongStepper(st) + } + r.asInstanceOf[S with EfficientSplit] + } + + private def expand(): Unit = { + if (index > 0) { + current(current.length-1) = (if (hIndex > 0) { val x = history(hIndex-1); x(x.length-1) } else 0) + index + if (hIndex >= history.length) hExpand() + history(hIndex) = current + hIndex += 1 + } + current = new Array[Long](nextBlockSize+1) + index = 0 + } + + private def hExpand(): Unit = { + if (hIndex == 0) history = new Array[Array[Long]](4) + else history = java.util.Arrays.copyOf(history, history.length << 1) + } + + /** Appends an element to this `LongAccumulator`. */ + def addOne(a: Long): this.type = { + totalSize += 1 + if (index+1 >= current.length) expand() + current(index) = a + index += 1 + this + } + + /** Result collection consisting of all elements appended so far. */ + override def result(): LongAccumulator = this + + /** Removes all elements from `that` and appends them to this `LongAccumulator`. */ + def drain(that: LongAccumulator): Unit = { + var h = 0 + var prev = 0L + var more = true + while (more && h < that.hIndex) { + val cuml = that.cumulative(h) + val n = (cuml - prev).toInt + if (current.length - index - 1 >= n) { + System.arraycopy(that.history(h), 0, current, index, n) + prev = cuml + index += n + h += 1 + } + else more = false + } + if (h >= that.hIndex && current.length - index - 1>= that.index) { + if (that.index > 0) System.arraycopy(that.current, 0, current, index, that.index) + index += that.index + } + else { + val slots = (if (index > 0) 1 else 0) + that.hIndex - h + if (hIndex + slots > history.length) { + val n = math.max(4, 1 << (32 - jl.Integer.numberOfLeadingZeros(1 + hIndex + slots))) + history = java.util.Arrays.copyOf(history, n) + } + var pv = if (hIndex > 0) cumulative(hIndex-1) else 0L + if (index > 0) { + val x = + if (index < (current.length >>> 3) && current.length - 1 > 32) { + val ans = java.util.Arrays.copyOf(current, index + 1) + ans(ans.length - 1) = current(current.length - 1) + ans + } + else current + pv = pv + index + x(x.length - 1) = pv + history(hIndex) = x + hIndex += 1 + } + while (h < that.hIndex) { + val cuml = that.cumulative(h) + pv = pv + cuml - prev + prev = cuml + val x = that.history(h) + x(x.length - 1) = pv + history(hIndex) = x + h += 1 + hIndex += 1 + } + index = that.index + current = that.current + } + totalSize += that.totalSize + that.clear() + } + + override def clear(): Unit = { + super.clear() + current = LongAccumulator.emptyLongArray + history = LongAccumulator.emptyLongArrayArray + } + + /** Retrieves the `ix`th element. */ + def apply(ix: Long): Long = { + if (totalSize - ix <= index || hIndex == 0) current((ix - (totalSize - index)).toInt) + else { + val w = seekSlot(ix) + history((w >>> 32).toInt)((w & 0xFFFFFFFFL).toInt) + } + } + + /** Retrieves the `ix`th element, using an `Int` index. */ + def apply(i: Int): Long = apply(i.toLong) + + def update(idx: Long, elem: Long): Unit = { + if (totalSize - idx <= index || hIndex == 0) current((idx - (totalSize - index)).toInt) = elem + else { + val w = seekSlot(idx) + history((w >>> 32).toInt)((w & 0xFFFFFFFFL).toInt) = elem + } + } + + def update(idx: Int, elem: Long): Unit = update(idx.toLong, elem) + + /** Returns an `Iterator` over the contents of this `LongAccumulator`. The `Iterator` is not specialized. */ + def iterator: Iterator[Long] = stepper.iterator + + override def foreach[U](f: Long => U): Unit = { + val s = stepper + while (s.hasStep) f(s.nextStep()) + } + + def map(f: Long => Long): LongAccumulator = { + val b = newSpecificBuilder + val s = stepper + while (s.hasStep) + b.addOne(f(s.nextStep())) + b.result() + } + + def flatMap(f: Long => IterableOnce[Long]): LongAccumulator = { + val b = newSpecificBuilder + val s = stepper + while (s.hasStep) + b.addAll(f(s.nextStep())) + b.result() + } + + def collect(pf: PartialFunction[Long, Long]): LongAccumulator = { + val b = newSpecificBuilder + val s = stepper + while (s.hasStep) { + val n = s.nextStep() + pf.runWith(b.addOne)(n) + } + b.result() + } + + private def filterAccImpl(pred: Long => Boolean, not: Boolean): LongAccumulator = { + val b = newSpecificBuilder + val s = stepper + while (s.hasStep) { + val n = s.nextStep() + if (pred(n) != not) b.addOne(n) + } + b.result() + } + + override def filter(pred: Long => Boolean): LongAccumulator = filterAccImpl(pred, not = false) + + override def filterNot(pred: Long => Boolean): LongAccumulator = filterAccImpl(pred, not = true) + + override def forall(p: Long => Boolean): Boolean = { + val s = stepper + while (s.hasStep) + if (!p(s.nextStep())) return false + true + } + + override def exists(p: Long => Boolean): Boolean = { + val s = stepper + while (s.hasStep) + if (p(s.nextStep())) return true + false + } + + override def count(p: Long => Boolean): Int = { + var r = 0 + val s = stepper + while (s.hasStep) + if (p(s.nextStep())) r += 1 + r + } + + def countLong(p: Long => Boolean): Long = { + var r = 0L + val s = stepper + while (s.hasStep) + if (p(s.nextStep())) r += 1 + r + } + + /** Copies the elements in this `LongAccumulator` into an `Array[Long]` */ + @nowarn // cat=lint-overload see toArray[B: ClassTag] + def toArray: Array[Long] = { + if (totalSize > Int.MaxValue) throw new IllegalArgumentException("Too many elements accumulated for an array: "+totalSize.toString) + val a = new Array[Long](totalSize.toInt) + var j = 0 + var h = 0 + var pv = 0L + while (h < hIndex) { + val x = history(h) + val cuml = x(x.length-1) + val n = (cuml - pv).toInt + pv = cuml + System.arraycopy(x, 0, a, j, n) + j += n + h += 1 + } + System.arraycopy(current, 0, a, j, index) + j += index + a + } + + /** Copies the elements in this `LongAccumulator` to a `List` */ + override def toList: List[Long] = { + var ans: List[Long] = Nil + var i = index - 1 + while (i >= 0) { + ans = current(i) :: ans + i -= 1 + } + var h = hIndex - 1 + while (h >= 0) { + val a = history(h) + i = (cumulative(h) - (if (h == 0) 0L else cumulative(h-1))).toInt - 1 + while (i >= 0) { + ans = a(i) :: ans + i -= 1 + } + h -= 1 + } + ans + } + + /** + * Copy the elements in this `LongAccumulator` to a specified collection. + * Note that the target collection is not specialized. + * Usage example: `acc.to(Vector)` + */ + override def to[C1](factory: Factory[Long, C1]): C1 = { + if (totalSize > Int.MaxValue) throw new IllegalArgumentException("Too many elements accumulated for a Scala collection: "+totalSize.toString) + factory.fromSpecific(iterator) + } + + override protected def fromSpecific(coll: IterableOnce[Long]): LongAccumulator = LongAccumulator.fromSpecific(coll) + override protected def newSpecificBuilder: LongAccumulator = LongAccumulator.newBuilder + override def iterableFactory: SeqFactory[AnyAccumulator] = AnyAccumulator + + override def empty: LongAccumulator = LongAccumulator.empty + + private def writeReplace(): AnyRef = new LongAccumulator.SerializationProxy(this) +} + +object LongAccumulator extends collection.SpecificIterableFactory[Long, LongAccumulator] { + private val emptyLongArray = new Array[Long](0) + private val emptyLongArrayArray = new Array[Array[Long]](0) + + implicit def toJavaLongAccumulator(ia: LongAccumulator.type): collection.SpecificIterableFactory[jl.Long, LongAccumulator] = LongAccumulator.asInstanceOf[collection.SpecificIterableFactory[jl.Long, LongAccumulator]] + + import java.util.{function => jf} + + /** A `Supplier` of `LongAccumulator`s, suitable for use with `java.util.stream.LongStream`'s `collect` method. Suitable for `Stream[Long]` also. */ + def supplier: jf.Supplier[LongAccumulator] = () => new LongAccumulator + + /** A `BiConsumer` that adds an element to an `LongAccumulator`, suitable for use with `java.util.stream.LongStream`'s `collect` method. */ + def adder: jf.ObjLongConsumer[LongAccumulator] = (ac: LongAccumulator, a: Long) => ac addOne a + + /** A `BiConsumer` that adds a boxed `Long` to an `LongAccumulator`, suitable for use with `java.util.stream.Stream`'s `collect` method. */ + def boxedAdder: jf.BiConsumer[LongAccumulator, Long] = (ac: LongAccumulator, a: Long) => ac addOne a + + /** A `BiConsumer` that merges `LongAccumulator`s, suitable for use with `java.util.stream.LongStream`'s `collect` method. Suitable for `Stream[Long]` also. */ + def merger: jf.BiConsumer[LongAccumulator, LongAccumulator] = (a1: LongAccumulator, a2: LongAccumulator) => a1 drain a2 + + private def fromArray(a: Array[Long]): LongAccumulator = { + val r = new LongAccumulator + var i = 0 + while (i < a.length) { r addOne a(i); i += 1 } + r + } + + override def fromSpecific(it: IterableOnce[Long]): LongAccumulator = it match { + case acc: LongAccumulator => acc + case as: collection.immutable.ArraySeq.ofLong => fromArray(as.unsafeArray) + case as: collection.mutable.ArraySeq.ofLong => fromArray(as.array) // this case ensures Array(1).to(Accumulator) doesn't box + case _ => (new LongAccumulator).addAll(it) + } + + override def empty: LongAccumulator = new LongAccumulator + + override def newBuilder: LongAccumulator = new LongAccumulator + + class SerializationProxy[A](@transient private val acc: LongAccumulator) extends Serializable { + @transient private var result: LongAccumulator = _ + + private def writeObject(out: ObjectOutputStream): Unit = { + out.defaultWriteObject() + val size = acc.sizeLong + out.writeLong(size) + val st = acc.stepper + while (st.hasStep) + out.writeLong(st.nextStep()) + } + + private def readObject(in: ObjectInputStream): Unit = { + in.defaultReadObject() + val res = new LongAccumulator() + var elems = in.readLong() + while (elems > 0) { + res += in.readLong() + elems -= 1L + } + result = res + } + + private def readResolve(): AnyRef = result + } +} + +private[jdk] class LongAccumulatorStepper(private val acc: LongAccumulator) extends LongStepper with EfficientSplit { + import java.util.Spliterator._ + + private var h: Int = 0 + private var i: Int = 0 + private var a: Array[Long] = if (acc.hIndex > 0) acc.history(0) else acc.current + private var n: Long = if (acc.hIndex > 0) acc.cumulative(0) else acc.index + private var N: Long = acc.totalSize + + private def duplicateSelf(limit: Long): LongAccumulatorStepper = { + val ans = new LongAccumulatorStepper(acc) + ans.h = h + ans.i = i + ans.a = a + ans.n = n + ans.N = limit + ans + } + + private def loadMore(): Unit = { + h += 1 + if (h < acc.hIndex) { a = acc.history(h); n = acc.cumulative(h) - acc.cumulative(h-1) } + else { a = acc.current; n = acc.index } + i = 0 + } + + def characteristics: Int = ORDERED | SIZED | SUBSIZED | NONNULL + + def estimateSize: Long = N + + def hasStep: Boolean = N > 0 + + def nextStep(): Long = + if (n <= 0) throw new NoSuchElementException("next on empty Stepper") + else { + if (i >= n) loadMore() + val ans = a(i) + i += 1 + N -= 1 + ans + } + + def trySplit(): LongStepper = + if (N <= 1) null + else { + val half = N >> 1 + val M = (if (h <= 0) 0L else acc.cumulative(h-1)) + i + val R = M + half + val ans = duplicateSelf(half) + if (h < acc.hIndex) { + val w = acc.seekSlot(R) + h = (w >>> 32).toInt + if (h < acc.hIndex) { + a = acc.history(h) + n = acc.cumulative(h) - (if (h > 0) acc.cumulative(h-1) else 0) + } + else { + a = acc.current + n = acc.index + } + i = (w & 0xFFFFFFFFL).toInt + } + else i += half.toInt + N -= half + ans + } + + override def spliterator[B >: Long]: Spliterator.OfLong = new LongStepper.LongStepperSpliterator(this) { + // Overridden for efficiency + override def tryAdvance(c: LongConsumer): Boolean = + if (N <= 0) false + else { + if (i >= n) loadMore() + c.accept(a(i)) + i += 1 + N -= 1 + true + } + + // Overridden for efficiency + override def tryAdvance(c: Consumer[_ >: jl.Long]): Boolean = (c: AnyRef) match { + case ic: LongConsumer => tryAdvance(ic) + case _ => + if (N <= 0) false + else { + if (i >= n) loadMore() + c.accept(a(i)) + i += 1 + N -= 1 + true + } + } + + // Overridden for efficiency + override def forEachRemaining(c: LongConsumer): Unit = + while (N > 0) { + if (i >= n) loadMore() + val i0 = i + if ((n-i) > N) n = i + N.toInt + while (i < n) { + c.accept(a(i)) + i += 1 + } + N -= (n - i0) + } + + // Overridden for efficiency + override def forEachRemaining(c: Consumer[_ >: jl.Long]): Unit = (c: AnyRef) match { + case ic: LongConsumer => forEachRemaining(ic) + case _ => + while (N > 0) { + if (i >= n) loadMore() + val i0 = i + if ((n-i) > N) n = i + N.toInt + while (i < n) { + c.accept(a(i)) + i += 1 + } + N -= (n - i0) + } + } + } +} diff --git a/library/src/scala/jdk/OptionConverters.scala b/library/src/scala/jdk/OptionConverters.scala new file mode 100644 index 000000000000..ddf179f14c0e --- /dev/null +++ b/library/src/scala/jdk/OptionConverters.scala @@ -0,0 +1,112 @@ +/* + * 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.jdk + +import scala.language.`2.13` +import java.util.{Optional, OptionalDouble, OptionalInt, OptionalLong} + +/** This object provides extension methods that convert between Scala `Option` and Java `Optional` + * types. + * + * When writing Java code, use the explicit conversion methods defined in + * [[javaapi.OptionConverters]] instead. + * + * Scala `Option` is extended with a `toJava` method that creates a corresponding `Optional`, and + * a `toJavaPrimitive` method that creates a specialized variant (e.g., `OptionalInt`) if + * applicable. + * + * Java `Optional` is extended with a `toScala` method and a `toJavaPrimitive` method. + * + * Finally, specialized `Optional` types are extended with `toScala` and `toJavaGeneric` methods. + * + * Example usage: + * + * {{{ + * import scala.jdk.OptionConverters._ + * val a = Option("example").toJava // Creates java.util.Optional[String] containing "example" + * val b = (None: Option[String]).toJava // Creates an empty java.util.Optional[String] + * val c = a.toScala // Back to Option("example") + * val d = b.toScala // Back to None typed as Option[String] + * val e = Option(2.7).toJava // java.util.Optional[Double] containing boxed 2.7 + * val f = Option(2.7).toJavaPrimitive // java.util.OptionalDouble containing 2.7 (not boxed) + * val g = f.toScala // Back to Option(2.7) + * val h = f.toJavaGeneric // Same as e + * val i = e.toJavaPrimitive // Same as f + * }}} + */ +object OptionConverters { + /** Provides conversions from Java `Optional` to Scala `Option` and specialized `Optional` types */ + implicit class RichOptional[A](private val o: java.util.Optional[A]) extends AnyVal { + /** Convert a Java `Optional` to a Scala `Option` */ + def toScala: Option[A] = if (o.isPresent) Some(o.get) else None + + /** Convert a Java `Optional` to a Scala `Option` */ + @deprecated("Use `toScala` instead", "2.13.0") + def asScala: Option[A] = if (o.isPresent) Some(o.get) else None + + /** Convert a generic Java `Optional` to a specialized variant */ + def toJavaPrimitive[O](implicit shape: OptionShape[A, O]): O = shape.fromJava(o) + } + + /** Provides conversions from Scala `Option` to Java `Optional` types */ + implicit class RichOption[A](private val o: Option[A]) extends AnyVal { + /** Convert a Scala `Option` to a generic Java `Optional` */ + def toJava: Optional[A] = o match { case Some(a) => Optional.ofNullable(a); case _ => Optional.empty[A] } + + /** Convert a Scala `Option` to a generic Java `Optional` */ + @deprecated("Use `toJava` instead", "2.13.0") + def asJava: Optional[A] = o match { case Some(a) => Optional.ofNullable(a); case _ => Optional.empty[A] } + + /** Convert a Scala `Option` to a specialized Java `Optional` */ + def toJavaPrimitive[O](implicit shape: OptionShape[A, O]): O = shape.fromScala(o) + } + + /** Provides conversions from `OptionalDouble` to Scala `Option` and the generic `Optional` */ + implicit class RichOptionalDouble(private val o: OptionalDouble) extends AnyVal { + /** Convert a Java `OptionalDouble` to a Scala `Option` */ + def toScala: Option[Double] = if (o.isPresent) Some(o.getAsDouble) else None + + /** Convert a Java `OptionalDouble` to a Scala `Option` */ + @deprecated("Use `toScala` instead", "2.13.0") + def asScala: Option[Double] = if (o.isPresent) Some(o.getAsDouble) else None + + /** Convert a Java `OptionalDouble` to a generic Java `Optional` */ + def toJavaGeneric: Optional[Double] = if (o.isPresent) Optional.of(o.getAsDouble) else Optional.empty[Double] + } + + /** Provides conversions from `OptionalInt` to Scala `Option` and the generic `Optional` */ + implicit class RichOptionalInt(private val o: OptionalInt) extends AnyVal { + /** Convert a Java `OptionalInt` to a Scala `Option` */ + def toScala: Option[Int] = if (o.isPresent) Some(o.getAsInt) else None + + /** Convert a Java `OptionalInt` to a Scala `Option` */ + @deprecated("Use `toScala` instead", "2.13.0") + def asScala: Option[Int] = if (o.isPresent) Some(o.getAsInt) else None + + /** Convert a Java `OptionalInt` to a generic Java `Optional` */ + def toJavaGeneric: Optional[Int] = if (o.isPresent) Optional.of(o.getAsInt) else Optional.empty[Int] + } + + /** Provides conversions from `OptionalLong` to Scala `Option` and the generic `Optional` */ + implicit class RichOptionalLong(private val o: OptionalLong) extends AnyVal { + /** Convert a Java `OptionalLong` to a Scala `Option` */ + def toScala: Option[Long] = if (o.isPresent) Some(o.getAsLong) else None + + /** Convert a Java `OptionalLong` to a Scala `Option` */ + @deprecated("Use `toScala` instead", "2.13.0") + def asScala: Option[Long] = if (o.isPresent) Some(o.getAsLong) else None + + /** Convert a Java `OptionalLong` to a generic Java `Optional` */ + def toJavaGeneric: Optional[Long] = if (o.isPresent) Optional.of(o.getAsLong) else Optional.empty[Long] + } +} diff --git a/library/src/scala/jdk/OptionShape.scala b/library/src/scala/jdk/OptionShape.scala new file mode 100644 index 000000000000..b6540c7ad696 --- /dev/null +++ b/library/src/scala/jdk/OptionShape.scala @@ -0,0 +1,68 @@ +/* + * 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.jdk + +import java.util.{Optional, OptionalDouble, OptionalInt, OptionalLong} +import java.{lang => jl} + +import scala.language.`2.13` +import scala.annotation.implicitNotFound + +/** A type class implementing conversions from a generic Scala `Option` or Java `Optional` to + * a specialized Java variant (for `Double`, `Int` and `Long`). + * + * @tparam A the primitive type wrapped in an option + * @tparam O the specialized Java `Optional` wrapping an element of type `A` + */ +@implicitNotFound("No specialized Optional type exists for elements of type ${A}") +sealed abstract class OptionShape[A, O] { + /** Converts from `Optional` to the specialized variant `O` */ + def fromJava(o: Optional[A]): O + /** Converts from `Option` to the specialized variant `O` */ + def fromScala(o: Option[A]): O +} + +object OptionShape { + implicit val doubleOptionShape: OptionShape[Double, OptionalDouble] = new OptionShape[Double, OptionalDouble] { + def fromJava(o: Optional[Double]): OptionalDouble = + if (o.isPresent) OptionalDouble.of(o.get) else OptionalDouble.empty + + def fromScala(o: Option[Double]): OptionalDouble = o match { + case Some(d) => OptionalDouble.of(d) + case _ => OptionalDouble.empty + } + } + implicit val jDoubleOptionShape: OptionShape[jl.Double, OptionalDouble] = doubleOptionShape.asInstanceOf[OptionShape[jl.Double, OptionalDouble]] + + implicit val intOptionShape: OptionShape[Int, OptionalInt] = new OptionShape[Int, OptionalInt] { + def fromJava(o: Optional[Int]): OptionalInt = + if (o.isPresent) OptionalInt.of(o.get) else OptionalInt.empty + + def fromScala(o: Option[Int]): OptionalInt = o match { + case Some(d) => OptionalInt.of(d) + case _ => OptionalInt.empty + } + } + implicit val jIntegerOptionShape: OptionShape[jl.Integer, OptionalInt] = intOptionShape.asInstanceOf[OptionShape[jl.Integer, OptionalInt]] + + implicit val longOptionShape: OptionShape[Long, OptionalLong] = new OptionShape[Long, OptionalLong] { + def fromJava(o: Optional[Long]): OptionalLong = + if (o.isPresent) OptionalLong.of(o.get) else OptionalLong.empty + + def fromScala(o: Option[Long]): OptionalLong = o match { + case Some(d) => OptionalLong.of(d) + case _ => OptionalLong.empty + } + } + implicit val jLongOptionShape: OptionShape[jl.Long, OptionalLong] = longOptionShape.asInstanceOf[OptionShape[jl.Long, OptionalLong]] +} diff --git a/library/src/scala/jdk/StreamConverters.scala b/library/src/scala/jdk/StreamConverters.scala new file mode 100644 index 000000000000..c0cf5a2c5cc8 --- /dev/null +++ b/library/src/scala/jdk/StreamConverters.scala @@ -0,0 +1,93 @@ +/* + * 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.jdk + +import scala.language.`2.13` +import scala.collection.convert.StreamExtensions + +/** This object provides extension methods to create [[java.util.stream.Stream Java Streams]] that + * operate on Scala collections (sequentially or in parallel). For more information on Java + * streams, consult the documentation + * ([[https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html]]). + * + * When writing Java code, use the explicit conversion methods defined in + * [[javaapi.StreamConverters]] instead. + * + * The methods `asJavaSeqStream` and `asJavaParStream` convert a collection to a Java Stream: + * + * {{{ + * scala> import scala.jdk.StreamConverters._ + * + * scala> val s = (1 to 10).toList.asJavaSeqStream + * s: java.util.stream.IntStream = java.util.stream.IntPipeline\$Head@7b1e5e55 + * + * scala> s.map(_ * 2).filter(_ > 5).toScala(List) + * res1: List[Int] = List(6, 8, 10, 12, 14, 16, 18, 20) + * }}} + * + * Note: using parallel streams in the Scala REPL causes deadlocks, see + * [[https://github.com/scala/bug/issues/9076]]. As a workaround, use `scala -Yrepl-class-based`. + * + * {{{ + * scala> def isPrime(n: Int): Boolean = !(2 +: (3 to Math.sqrt(n).toInt by 2) exists (n % _ == 0)) + * isPrime: (n: Int)Boolean + * + * scala> (10000 to 1000000).asJavaParStream.filter(isPrime).toScala(Vector) + * res6: scala.collection.immutable.Vector[Int] = Vector(10007, 10009, 10037, 10039, ... + * }}} + * + * A Java [[Stream]] provides operations on a sequence of elements. Streams are created from + * [[java.util.Spliterator Spliterators]], which are similar to Iterators with the additional + * capability to partition off some of their elements. This partitioning, if supported by the + * Spliterator, is used for parallelizing Stream operations. + * + * Scala collections have a method [[scala.collection.IterableOnce.stepper `stepper`]] that + * returns a [[scala.collection.Stepper]] for the collection, which in turn can be converted to a + * Spliterator for creating a Java Stream. + * + * The `asJavaSeqStream ` extension method is available on any Scala collection. The + * `asJavaParStream` extension method can only be invoked on collections where the return type of + * the [[scala.collection.IterableOnce.stepper `stepper`]] method is marked with the + * [[scala.collection.Stepper.EfficientSplit]] marker trait. This trait is added to steppers that + * support partitioning, and therefore efficient parallel processing. + * + * The following extension methods are available: + * + * | Collection Type | Extension Methods | + * | --- | --- | + * | `IterableOnce` | `asJavaSeqStream` | + * | `IndexedSeq`, Arrays, `BitSet`, `Accumulator`, `HashMap`, `HashSet`, `Range`, `TreeMap`, `TreeSet`, `Vector`, Strings | `asJavaParStream` | + * | `Map` | `asJavaSeqKeyStream`, `asJavaSeqValueStream` | + * | `HashMap`, `TreeMap` | `asJavaParKeyStream`, `asJavaParValueStream` | + * | `Stepper` | `asJavaSeqStream` | + * | `Stepper with EfficientSplit` | `asJavaParStream` | + * | Strings | `asJavaSeqStream`, `asJavaParStream`, `asJavaSeqCharStream`, `asJavaParCharStream`, `asJavaSeqCodePointStream`, `asJavaParCodePointStream` | + * | Java streams | `toScala`, `asJavaPrimitiveStream` | + * + * The `asJavaPrimitiveStream` method converts a `Stream[Int]` to an `IntStream`. It is the dual + * of the `boxed` method defined on primitive streams (e.g., `IntStream.boxed` is a + * `Stream[Integer]`). + * + * The `toScala` extension methods on Java streams collects the result of a stream pipeline into a + * Scala collection, for example `stream.toScala(List)`, `stream.toScala(Vector)`. Note that + * transformation operations on streams are lazy (also called "intermediate"), terminal operations + * such as `forEach`, `count` or `toScala` trigger the evaluation. + * + * Collecting a parallel stream to a collection can be performed in parallel. This is beneficial if + * the target collection supports efficient merging of the segments that are built in parallel. + * To support this use case, the Scala standard library provides the [[Accumulator]] collection. + * This collection supports efficient parallel construction, and it has specialized subtypes for + * `Int`, `Long` and `Double` so that primitive Java streams can be collected to a Scala collection + * without boxing the elements. + */ +object StreamConverters extends StreamExtensions diff --git a/library/src/scala/jdk/javaapi/CollectionConverters.scala b/library/src/scala/jdk/javaapi/CollectionConverters.scala new file mode 100644 index 000000000000..e3e7e036b9e7 --- /dev/null +++ b/library/src/scala/jdk/javaapi/CollectionConverters.scala @@ -0,0 +1,78 @@ +/* + * 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.jdk.javaapi + +import scala.language.`2.13` +import scala.collection.convert.{AsJavaConverters, AsScalaConverters} + +/** This object contains methods that convert between Scala and Java collections. + * + * The explicit conversion methods defined here are intended to be used in Java code. For Scala + * code, it is recommended to use the extension methods defined in + * [[scala.jdk.CollectionConverters]]. + * + * Note: to create [[java.util.stream.Stream Java Streams]] that operate on Scala collections + * (sequentially or in parallel), use [[StreamConverters]]. + * + * {{{ + * // Java Code + * import scala.jdk.javaapi.CollectionConverters; + * public class A { + * public void t(scala.collection.immutable.List l) { + * java.util.List jl = CollectionConverters.asJava(l); + * } + * } + * }}} + * + * The conversions return adapters for the corresponding API, i.e., the collections are wrapped, + * not copied. Changes to the original collection are reflected in the view, and vice versa. + * + * The following conversions are supported via `asScala` and `asJava`: + * + * {{{ + * scala.collection.Iterable <=> java.lang.Iterable + * scala.collection.Iterator <=> java.util.Iterator + * scala.collection.mutable.Buffer <=> java.util.List + * scala.collection.mutable.Set <=> java.util.Set + * scala.collection.mutable.Map <=> java.util.Map + * scala.collection.concurrent.Map <=> java.util.concurrent.ConcurrentMap + * }}} + * + * The following conversions are supported via `asScala` and through + * specially-named methods to convert to Java collections, as shown: + * + * {{{ + * scala.collection.Iterable <=> java.util.Collection (via asJavaCollection) + * scala.collection.Iterator <=> java.util.Enumeration (via asJavaEnumeration) + * scala.collection.mutable.Map <=> java.util.Dictionary (via asJavaDictionary) + * }}} + * + * In addition, the following one-way conversions are provided via `asJava`: + * + * {{{ + * scala.collection.Seq => java.util.List + * scala.collection.mutable.Seq => java.util.List + * scala.collection.Set => java.util.Set + * scala.collection.Map => java.util.Map + * }}} + * + * The following one way conversion is provided via `asScala`: + * + * {{{ + * java.util.Properties => scala.collection.mutable.Map + * }}} + * + * In all cases, converting from a source type to a target type and back + * again will return the original source object. + */ +object CollectionConverters extends AsJavaConverters with AsScalaConverters diff --git a/library/src/scala/jdk/javaapi/DurationConverters.scala b/library/src/scala/jdk/javaapi/DurationConverters.scala new file mode 100644 index 000000000000..f2646f781e49 --- /dev/null +++ b/library/src/scala/jdk/javaapi/DurationConverters.scala @@ -0,0 +1,74 @@ +/* + * 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.jdk.javaapi + +import scala.language.`2.13` +import java.time.temporal.ChronoUnit +import java.time.{Duration => JDuration} +import java.util.concurrent.TimeUnit + +import scala.concurrent.duration.{Duration, FiniteDuration} + +/** This object contains methods that convert between Scala and Java duration types. + * + * The explicit conversion methods defined here are intended to be used in Java code. For Scala + * code, it is recommended to use the extension methods defined in [[scala.jdk.DurationConverters]]. + */ +object DurationConverters { + /** Convert a Java duration to a Scala duration. If the nanosecond part of the Java duration is + * zero, the returned duration will have a time unit of seconds. If there is a nanoseconds part, + * the Scala duration will have a time unit of nanoseconds. + * + * @throws IllegalArgumentException If the given Java Duration is out of bounds of what can be + * expressed by [[scala.concurrent.duration.FiniteDuration]]. + */ + def toScala(duration: JDuration): FiniteDuration = { + val originalSeconds = duration.getSeconds + val originalNanos = duration.getNano + if (originalNanos == 0) { + if (originalSeconds == 0) Duration.Zero + else FiniteDuration(originalSeconds, TimeUnit.SECONDS) + } else if (originalSeconds == 0) { + FiniteDuration(originalNanos, TimeUnit.NANOSECONDS) + } else { + try { + val secondsAsNanos = Math.multiplyExact(originalSeconds, 1000000000) + val totalNanos = secondsAsNanos + originalNanos + if ((totalNanos < 0 && secondsAsNanos < 0) || (totalNanos > 0 && secondsAsNanos > 0)) + FiniteDuration(totalNanos, TimeUnit.NANOSECONDS) + else + throw new ArithmeticException() + } catch { + case _: ArithmeticException => + throw new IllegalArgumentException(s"Java duration $duration cannot be expressed as a Scala duration") + } + } + } + + /** Convert a Scala `FiniteDuration` to a Java duration. Note that the Scala duration keeps the + * time unit it was created with, while a Java duration always is a pair of seconds and nanos, + * so the unit it lost. + */ + def toJava(duration: FiniteDuration): JDuration = { + if (duration.length == 0) JDuration.ZERO + else duration.unit match { + case TimeUnit.NANOSECONDS => JDuration.ofNanos(duration.length) + case TimeUnit.MICROSECONDS => JDuration.of(duration.length, ChronoUnit.MICROS) + case TimeUnit.MILLISECONDS => JDuration.ofMillis(duration.length) + case TimeUnit.SECONDS => JDuration.ofSeconds(duration.length) + case TimeUnit.MINUTES => JDuration.ofMinutes(duration.length) + case TimeUnit.HOURS => JDuration.ofHours(duration.length) + case TimeUnit.DAYS => JDuration.ofDays(duration.length) + } + } +} diff --git a/library/src/scala/jdk/javaapi/FunctionConverters.scala b/library/src/scala/jdk/javaapi/FunctionConverters.scala new file mode 100644 index 000000000000..9a5daea45a9c --- /dev/null +++ b/library/src/scala/jdk/javaapi/FunctionConverters.scala @@ -0,0 +1,958 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. + + +package scala.jdk.javaapi + +import scala.language.`2.13` + +/** This object contains methods that convert between Scala and Java function types. + * + * The explicit conversion methods defined here are intended to be used in Java code. For Scala + * code, it is recommended to use the extension methods defined in [[scala.jdk.FunctionConverters]]. + * + * For details how the function converters work, see [[scala.jdk.FunctionConverters]]. + * + */ +object FunctionConverters { + import scala.jdk.FunctionWrappers._ + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromBiConsumer[T, U](jf: java.util.function.BiConsumer[T, U]): scala.Function2[T, U, scala.runtime.BoxedUnit] = jf match { + case AsJavaBiConsumer((f @ _)) => f.asInstanceOf[scala.Function2[T, U, scala.runtime.BoxedUnit]] + case _ => new FromJavaBiConsumer[T, U](jf).asInstanceOf[scala.Function2[T, U, scala.runtime.BoxedUnit]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaBiConsumer[T, U](sf: scala.Function2[T, U, scala.runtime.BoxedUnit]): java.util.function.BiConsumer[T, U] = ((sf): AnyRef) match { + case FromJavaBiConsumer((f @ _)) => f.asInstanceOf[java.util.function.BiConsumer[T, U]] + case _ => new AsJavaBiConsumer[T, U](sf.asInstanceOf[scala.Function2[T, U, Unit]]) + } + + + @inline def asScalaFromBiFunction[T, U, R](jf: java.util.function.BiFunction[T, U, R]): scala.Function2[T, U, R] = jf match { + case AsJavaBiFunction((f @ _)) => f.asInstanceOf[scala.Function2[T, U, R]] + case _ => new FromJavaBiFunction[T, U, R](jf).asInstanceOf[scala.Function2[T, U, R]] + } + + @inline def asJavaBiFunction[T, U, R](sf: scala.Function2[T, U, R]): java.util.function.BiFunction[T, U, R] = ((sf): AnyRef) match { + case FromJavaBiFunction((f @ _)) => f.asInstanceOf[java.util.function.BiFunction[T, U, R]] + case _ => new AsJavaBiFunction[T, U, R](sf.asInstanceOf[scala.Function2[T, U, R]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromBiPredicate[T, U](jf: java.util.function.BiPredicate[T, U]): scala.Function2[T, U, java.lang.Boolean] = jf match { + case AsJavaBiPredicate((f @ _)) => f.asInstanceOf[scala.Function2[T, U, java.lang.Boolean]] + case _ => new FromJavaBiPredicate[T, U](jf).asInstanceOf[scala.Function2[T, U, java.lang.Boolean]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaBiPredicate[T, U](sf: scala.Function2[T, U, java.lang.Boolean]): java.util.function.BiPredicate[T, U] = ((sf): AnyRef) match { + case FromJavaBiPredicate((f @ _)) => f.asInstanceOf[java.util.function.BiPredicate[T, U]] + case _ => new AsJavaBiPredicate[T, U](sf.asInstanceOf[scala.Function2[T, U, Boolean]]) + } + + + @inline def asScalaFromBinaryOperator[T](jf: java.util.function.BinaryOperator[T]): scala.Function2[T, T, T] = jf match { + case AsJavaBinaryOperator((f @ _)) => f.asInstanceOf[scala.Function2[T, T, T]] + case _ => new FromJavaBinaryOperator[T](jf).asInstanceOf[scala.Function2[T, T, T]] + } + + @inline def asJavaBinaryOperator[T](sf: scala.Function2[T, T, T]): java.util.function.BinaryOperator[T] = ((sf): AnyRef) match { + case FromJavaBinaryOperator((f @ _)) => f.asInstanceOf[java.util.function.BinaryOperator[T]] + case _ => new AsJavaBinaryOperator[T](sf.asInstanceOf[scala.Function2[T, T, T]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromBooleanSupplier(jf: java.util.function.BooleanSupplier): scala.Function0[java.lang.Boolean] = jf match { + case AsJavaBooleanSupplier((f @ _)) => f.asInstanceOf[scala.Function0[java.lang.Boolean]] + case _ => new FromJavaBooleanSupplier(jf).asInstanceOf[scala.Function0[java.lang.Boolean]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaBooleanSupplier(sf: scala.Function0[java.lang.Boolean]): java.util.function.BooleanSupplier = ((sf): AnyRef) match { + case FromJavaBooleanSupplier((f @ _)) => f.asInstanceOf[java.util.function.BooleanSupplier] + case _ => new AsJavaBooleanSupplier(sf.asInstanceOf[scala.Function0[Boolean]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromConsumer[T](jf: java.util.function.Consumer[T]): scala.Function1[T, scala.runtime.BoxedUnit] = jf match { + case AsJavaConsumer((f @ _)) => f.asInstanceOf[scala.Function1[T, scala.runtime.BoxedUnit]] + case _ => new FromJavaConsumer[T](jf).asInstanceOf[scala.Function1[T, scala.runtime.BoxedUnit]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaConsumer[T](sf: scala.Function1[T, scala.runtime.BoxedUnit]): java.util.function.Consumer[T] = ((sf): AnyRef) match { + case FromJavaConsumer((f @ _)) => f.asInstanceOf[java.util.function.Consumer[T]] + case _ => new AsJavaConsumer[T](sf.asInstanceOf[scala.Function1[T, Unit]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromDoubleBinaryOperator(jf: java.util.function.DoubleBinaryOperator): scala.Function2[java.lang.Double, java.lang.Double, java.lang.Double] = jf match { + case AsJavaDoubleBinaryOperator((f @ _)) => f.asInstanceOf[scala.Function2[java.lang.Double, java.lang.Double, java.lang.Double]] + case _ => new FromJavaDoubleBinaryOperator(jf).asInstanceOf[scala.Function2[java.lang.Double, java.lang.Double, java.lang.Double]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaDoubleBinaryOperator(sf: scala.Function2[java.lang.Double, java.lang.Double, java.lang.Double]): java.util.function.DoubleBinaryOperator = ((sf): AnyRef) match { + case FromJavaDoubleBinaryOperator((f @ _)) => f.asInstanceOf[java.util.function.DoubleBinaryOperator] + case _ => new AsJavaDoubleBinaryOperator(sf.asInstanceOf[scala.Function2[Double, Double, Double]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromDoubleConsumer(jf: java.util.function.DoubleConsumer): scala.Function1[java.lang.Double, scala.runtime.BoxedUnit] = jf match { + case AsJavaDoubleConsumer((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Double, scala.runtime.BoxedUnit]] + case _ => new FromJavaDoubleConsumer(jf).asInstanceOf[scala.Function1[java.lang.Double, scala.runtime.BoxedUnit]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaDoubleConsumer(sf: scala.Function1[java.lang.Double, scala.runtime.BoxedUnit]): java.util.function.DoubleConsumer = ((sf): AnyRef) match { + case FromJavaDoubleConsumer((f @ _)) => f.asInstanceOf[java.util.function.DoubleConsumer] + case _ => new AsJavaDoubleConsumer(sf.asInstanceOf[scala.Function1[Double, Unit]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromDoubleFunction[R](jf: java.util.function.DoubleFunction[R]): scala.Function1[java.lang.Double, R] = jf match { + case AsJavaDoubleFunction((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Double, R]] + case _ => new FromJavaDoubleFunction[R](jf).asInstanceOf[scala.Function1[java.lang.Double, R]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaDoubleFunction[R](sf: scala.Function1[java.lang.Double, R]): java.util.function.DoubleFunction[R] = ((sf): AnyRef) match { + case FromJavaDoubleFunction((f @ _)) => f.asInstanceOf[java.util.function.DoubleFunction[R]] + case _ => new AsJavaDoubleFunction[R](sf.asInstanceOf[scala.Function1[Double, R]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromDoublePredicate(jf: java.util.function.DoublePredicate): scala.Function1[java.lang.Double, java.lang.Boolean] = jf match { + case AsJavaDoublePredicate((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Double, java.lang.Boolean]] + case _ => new FromJavaDoublePredicate(jf).asInstanceOf[scala.Function1[java.lang.Double, java.lang.Boolean]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaDoublePredicate(sf: scala.Function1[java.lang.Double, java.lang.Boolean]): java.util.function.DoublePredicate = ((sf): AnyRef) match { + case FromJavaDoublePredicate((f @ _)) => f.asInstanceOf[java.util.function.DoublePredicate] + case _ => new AsJavaDoublePredicate(sf.asInstanceOf[scala.Function1[Double, Boolean]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromDoubleSupplier(jf: java.util.function.DoubleSupplier): scala.Function0[java.lang.Double] = jf match { + case AsJavaDoubleSupplier((f @ _)) => f.asInstanceOf[scala.Function0[java.lang.Double]] + case _ => new FromJavaDoubleSupplier(jf).asInstanceOf[scala.Function0[java.lang.Double]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaDoubleSupplier(sf: scala.Function0[java.lang.Double]): java.util.function.DoubleSupplier = ((sf): AnyRef) match { + case FromJavaDoubleSupplier((f @ _)) => f.asInstanceOf[java.util.function.DoubleSupplier] + case _ => new AsJavaDoubleSupplier(sf.asInstanceOf[scala.Function0[Double]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromDoubleToIntFunction(jf: java.util.function.DoubleToIntFunction): scala.Function1[java.lang.Double, java.lang.Integer] = jf match { + case AsJavaDoubleToIntFunction((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Double, java.lang.Integer]] + case _ => new FromJavaDoubleToIntFunction(jf).asInstanceOf[scala.Function1[java.lang.Double, java.lang.Integer]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaDoubleToIntFunction(sf: scala.Function1[java.lang.Double, java.lang.Integer]): java.util.function.DoubleToIntFunction = ((sf): AnyRef) match { + case FromJavaDoubleToIntFunction((f @ _)) => f.asInstanceOf[java.util.function.DoubleToIntFunction] + case _ => new AsJavaDoubleToIntFunction(sf.asInstanceOf[scala.Function1[Double, Int]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromDoubleToLongFunction(jf: java.util.function.DoubleToLongFunction): scala.Function1[java.lang.Double, java.lang.Long] = jf match { + case AsJavaDoubleToLongFunction((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Double, java.lang.Long]] + case _ => new FromJavaDoubleToLongFunction(jf).asInstanceOf[scala.Function1[java.lang.Double, java.lang.Long]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaDoubleToLongFunction(sf: scala.Function1[java.lang.Double, java.lang.Long]): java.util.function.DoubleToLongFunction = ((sf): AnyRef) match { + case FromJavaDoubleToLongFunction((f @ _)) => f.asInstanceOf[java.util.function.DoubleToLongFunction] + case _ => new AsJavaDoubleToLongFunction(sf.asInstanceOf[scala.Function1[Double, Long]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromDoubleUnaryOperator(jf: java.util.function.DoubleUnaryOperator): scala.Function1[java.lang.Double, java.lang.Double] = jf match { + case AsJavaDoubleUnaryOperator((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Double, java.lang.Double]] + case _ => new FromJavaDoubleUnaryOperator(jf).asInstanceOf[scala.Function1[java.lang.Double, java.lang.Double]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaDoubleUnaryOperator(sf: scala.Function1[java.lang.Double, java.lang.Double]): java.util.function.DoubleUnaryOperator = ((sf): AnyRef) match { + case FromJavaDoubleUnaryOperator((f @ _)) => f.asInstanceOf[java.util.function.DoubleUnaryOperator] + case _ => new AsJavaDoubleUnaryOperator(sf.asInstanceOf[scala.Function1[Double, Double]]) + } + + + @inline def asScalaFromFunction[T, R](jf: java.util.function.Function[T, R]): scala.Function1[T, R] = jf match { + case AsJavaFunction((f @ _)) => f.asInstanceOf[scala.Function1[T, R]] + case _ => new FromJavaFunction[T, R](jf).asInstanceOf[scala.Function1[T, R]] + } + + @inline def asJavaFunction[T, R](sf: scala.Function1[T, R]): java.util.function.Function[T, R] = ((sf): AnyRef) match { + case FromJavaFunction((f @ _)) => f.asInstanceOf[java.util.function.Function[T, R]] + case _ => new AsJavaFunction[T, R](sf.asInstanceOf[scala.Function1[T, R]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromIntBinaryOperator(jf: java.util.function.IntBinaryOperator): scala.Function2[java.lang.Integer, java.lang.Integer, java.lang.Integer] = jf match { + case AsJavaIntBinaryOperator((f @ _)) => f.asInstanceOf[scala.Function2[java.lang.Integer, java.lang.Integer, java.lang.Integer]] + case _ => new FromJavaIntBinaryOperator(jf).asInstanceOf[scala.Function2[java.lang.Integer, java.lang.Integer, java.lang.Integer]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaIntBinaryOperator(sf: scala.Function2[java.lang.Integer, java.lang.Integer, java.lang.Integer]): java.util.function.IntBinaryOperator = ((sf): AnyRef) match { + case FromJavaIntBinaryOperator((f @ _)) => f.asInstanceOf[java.util.function.IntBinaryOperator] + case _ => new AsJavaIntBinaryOperator(sf.asInstanceOf[scala.Function2[Int, Int, Int]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromIntConsumer(jf: java.util.function.IntConsumer): scala.Function1[java.lang.Integer, scala.runtime.BoxedUnit] = jf match { + case AsJavaIntConsumer((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Integer, scala.runtime.BoxedUnit]] + case _ => new FromJavaIntConsumer(jf).asInstanceOf[scala.Function1[java.lang.Integer, scala.runtime.BoxedUnit]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaIntConsumer(sf: scala.Function1[java.lang.Integer, scala.runtime.BoxedUnit]): java.util.function.IntConsumer = ((sf): AnyRef) match { + case FromJavaIntConsumer((f @ _)) => f.asInstanceOf[java.util.function.IntConsumer] + case _ => new AsJavaIntConsumer(sf.asInstanceOf[scala.Function1[Int, Unit]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromIntFunction[R](jf: java.util.function.IntFunction[R]): scala.Function1[java.lang.Integer, R] = jf match { + case AsJavaIntFunction((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Integer, R]] + case _ => new FromJavaIntFunction[R](jf).asInstanceOf[scala.Function1[java.lang.Integer, R]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaIntFunction[R](sf: scala.Function1[java.lang.Integer, R]): java.util.function.IntFunction[R] = ((sf): AnyRef) match { + case FromJavaIntFunction((f @ _)) => f.asInstanceOf[java.util.function.IntFunction[R]] + case _ => new AsJavaIntFunction[R](sf.asInstanceOf[scala.Function1[Int, R]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromIntPredicate(jf: java.util.function.IntPredicate): scala.Function1[java.lang.Integer, java.lang.Boolean] = jf match { + case AsJavaIntPredicate((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Integer, java.lang.Boolean]] + case _ => new FromJavaIntPredicate(jf).asInstanceOf[scala.Function1[java.lang.Integer, java.lang.Boolean]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaIntPredicate(sf: scala.Function1[java.lang.Integer, java.lang.Boolean]): java.util.function.IntPredicate = ((sf): AnyRef) match { + case FromJavaIntPredicate((f @ _)) => f.asInstanceOf[java.util.function.IntPredicate] + case _ => new AsJavaIntPredicate(sf.asInstanceOf[scala.Function1[Int, Boolean]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromIntSupplier(jf: java.util.function.IntSupplier): scala.Function0[java.lang.Integer] = jf match { + case AsJavaIntSupplier((f @ _)) => f.asInstanceOf[scala.Function0[java.lang.Integer]] + case _ => new FromJavaIntSupplier(jf).asInstanceOf[scala.Function0[java.lang.Integer]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaIntSupplier(sf: scala.Function0[java.lang.Integer]): java.util.function.IntSupplier = ((sf): AnyRef) match { + case FromJavaIntSupplier((f @ _)) => f.asInstanceOf[java.util.function.IntSupplier] + case _ => new AsJavaIntSupplier(sf.asInstanceOf[scala.Function0[Int]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromIntToDoubleFunction(jf: java.util.function.IntToDoubleFunction): scala.Function1[java.lang.Integer, java.lang.Double] = jf match { + case AsJavaIntToDoubleFunction((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Integer, java.lang.Double]] + case _ => new FromJavaIntToDoubleFunction(jf).asInstanceOf[scala.Function1[java.lang.Integer, java.lang.Double]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaIntToDoubleFunction(sf: scala.Function1[java.lang.Integer, java.lang.Double]): java.util.function.IntToDoubleFunction = ((sf): AnyRef) match { + case FromJavaIntToDoubleFunction((f @ _)) => f.asInstanceOf[java.util.function.IntToDoubleFunction] + case _ => new AsJavaIntToDoubleFunction(sf.asInstanceOf[scala.Function1[Int, Double]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromIntToLongFunction(jf: java.util.function.IntToLongFunction): scala.Function1[java.lang.Integer, java.lang.Long] = jf match { + case AsJavaIntToLongFunction((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Integer, java.lang.Long]] + case _ => new FromJavaIntToLongFunction(jf).asInstanceOf[scala.Function1[java.lang.Integer, java.lang.Long]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaIntToLongFunction(sf: scala.Function1[java.lang.Integer, java.lang.Long]): java.util.function.IntToLongFunction = ((sf): AnyRef) match { + case FromJavaIntToLongFunction((f @ _)) => f.asInstanceOf[java.util.function.IntToLongFunction] + case _ => new AsJavaIntToLongFunction(sf.asInstanceOf[scala.Function1[Int, Long]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromIntUnaryOperator(jf: java.util.function.IntUnaryOperator): scala.Function1[java.lang.Integer, java.lang.Integer] = jf match { + case AsJavaIntUnaryOperator((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Integer, java.lang.Integer]] + case _ => new FromJavaIntUnaryOperator(jf).asInstanceOf[scala.Function1[java.lang.Integer, java.lang.Integer]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaIntUnaryOperator(sf: scala.Function1[java.lang.Integer, java.lang.Integer]): java.util.function.IntUnaryOperator = ((sf): AnyRef) match { + case FromJavaIntUnaryOperator((f @ _)) => f.asInstanceOf[java.util.function.IntUnaryOperator] + case _ => new AsJavaIntUnaryOperator(sf.asInstanceOf[scala.Function1[Int, Int]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromLongBinaryOperator(jf: java.util.function.LongBinaryOperator): scala.Function2[java.lang.Long, java.lang.Long, java.lang.Long] = jf match { + case AsJavaLongBinaryOperator((f @ _)) => f.asInstanceOf[scala.Function2[java.lang.Long, java.lang.Long, java.lang.Long]] + case _ => new FromJavaLongBinaryOperator(jf).asInstanceOf[scala.Function2[java.lang.Long, java.lang.Long, java.lang.Long]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaLongBinaryOperator(sf: scala.Function2[java.lang.Long, java.lang.Long, java.lang.Long]): java.util.function.LongBinaryOperator = ((sf): AnyRef) match { + case FromJavaLongBinaryOperator((f @ _)) => f.asInstanceOf[java.util.function.LongBinaryOperator] + case _ => new AsJavaLongBinaryOperator(sf.asInstanceOf[scala.Function2[Long, Long, Long]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromLongConsumer(jf: java.util.function.LongConsumer): scala.Function1[java.lang.Long, scala.runtime.BoxedUnit] = jf match { + case AsJavaLongConsumer((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Long, scala.runtime.BoxedUnit]] + case _ => new FromJavaLongConsumer(jf).asInstanceOf[scala.Function1[java.lang.Long, scala.runtime.BoxedUnit]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaLongConsumer(sf: scala.Function1[java.lang.Long, scala.runtime.BoxedUnit]): java.util.function.LongConsumer = ((sf): AnyRef) match { + case FromJavaLongConsumer((f @ _)) => f.asInstanceOf[java.util.function.LongConsumer] + case _ => new AsJavaLongConsumer(sf.asInstanceOf[scala.Function1[Long, Unit]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromLongFunction[R](jf: java.util.function.LongFunction[R]): scala.Function1[java.lang.Long, R] = jf match { + case AsJavaLongFunction((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Long, R]] + case _ => new FromJavaLongFunction[R](jf).asInstanceOf[scala.Function1[java.lang.Long, R]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaLongFunction[R](sf: scala.Function1[java.lang.Long, R]): java.util.function.LongFunction[R] = ((sf): AnyRef) match { + case FromJavaLongFunction((f @ _)) => f.asInstanceOf[java.util.function.LongFunction[R]] + case _ => new AsJavaLongFunction[R](sf.asInstanceOf[scala.Function1[Long, R]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromLongPredicate(jf: java.util.function.LongPredicate): scala.Function1[java.lang.Long, java.lang.Boolean] = jf match { + case AsJavaLongPredicate((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Long, java.lang.Boolean]] + case _ => new FromJavaLongPredicate(jf).asInstanceOf[scala.Function1[java.lang.Long, java.lang.Boolean]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaLongPredicate(sf: scala.Function1[java.lang.Long, java.lang.Boolean]): java.util.function.LongPredicate = ((sf): AnyRef) match { + case FromJavaLongPredicate((f @ _)) => f.asInstanceOf[java.util.function.LongPredicate] + case _ => new AsJavaLongPredicate(sf.asInstanceOf[scala.Function1[Long, Boolean]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromLongSupplier(jf: java.util.function.LongSupplier): scala.Function0[java.lang.Long] = jf match { + case AsJavaLongSupplier((f @ _)) => f.asInstanceOf[scala.Function0[java.lang.Long]] + case _ => new FromJavaLongSupplier(jf).asInstanceOf[scala.Function0[java.lang.Long]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaLongSupplier(sf: scala.Function0[java.lang.Long]): java.util.function.LongSupplier = ((sf): AnyRef) match { + case FromJavaLongSupplier((f @ _)) => f.asInstanceOf[java.util.function.LongSupplier] + case _ => new AsJavaLongSupplier(sf.asInstanceOf[scala.Function0[Long]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromLongToDoubleFunction(jf: java.util.function.LongToDoubleFunction): scala.Function1[java.lang.Long, java.lang.Double] = jf match { + case AsJavaLongToDoubleFunction((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Long, java.lang.Double]] + case _ => new FromJavaLongToDoubleFunction(jf).asInstanceOf[scala.Function1[java.lang.Long, java.lang.Double]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaLongToDoubleFunction(sf: scala.Function1[java.lang.Long, java.lang.Double]): java.util.function.LongToDoubleFunction = ((sf): AnyRef) match { + case FromJavaLongToDoubleFunction((f @ _)) => f.asInstanceOf[java.util.function.LongToDoubleFunction] + case _ => new AsJavaLongToDoubleFunction(sf.asInstanceOf[scala.Function1[Long, Double]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromLongToIntFunction(jf: java.util.function.LongToIntFunction): scala.Function1[java.lang.Long, java.lang.Integer] = jf match { + case AsJavaLongToIntFunction((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Long, java.lang.Integer]] + case _ => new FromJavaLongToIntFunction(jf).asInstanceOf[scala.Function1[java.lang.Long, java.lang.Integer]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaLongToIntFunction(sf: scala.Function1[java.lang.Long, java.lang.Integer]): java.util.function.LongToIntFunction = ((sf): AnyRef) match { + case FromJavaLongToIntFunction((f @ _)) => f.asInstanceOf[java.util.function.LongToIntFunction] + case _ => new AsJavaLongToIntFunction(sf.asInstanceOf[scala.Function1[Long, Int]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromLongUnaryOperator(jf: java.util.function.LongUnaryOperator): scala.Function1[java.lang.Long, java.lang.Long] = jf match { + case AsJavaLongUnaryOperator((f @ _)) => f.asInstanceOf[scala.Function1[java.lang.Long, java.lang.Long]] + case _ => new FromJavaLongUnaryOperator(jf).asInstanceOf[scala.Function1[java.lang.Long, java.lang.Long]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaLongUnaryOperator(sf: scala.Function1[java.lang.Long, java.lang.Long]): java.util.function.LongUnaryOperator = ((sf): AnyRef) match { + case FromJavaLongUnaryOperator((f @ _)) => f.asInstanceOf[java.util.function.LongUnaryOperator] + case _ => new AsJavaLongUnaryOperator(sf.asInstanceOf[scala.Function1[Long, Long]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromObjDoubleConsumer[T](jf: java.util.function.ObjDoubleConsumer[T]): scala.Function2[T, java.lang.Double, scala.runtime.BoxedUnit] = jf match { + case AsJavaObjDoubleConsumer((f @ _)) => f.asInstanceOf[scala.Function2[T, java.lang.Double, scala.runtime.BoxedUnit]] + case _ => new FromJavaObjDoubleConsumer[T](jf).asInstanceOf[scala.Function2[T, java.lang.Double, scala.runtime.BoxedUnit]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaObjDoubleConsumer[T](sf: scala.Function2[T, java.lang.Double, scala.runtime.BoxedUnit]): java.util.function.ObjDoubleConsumer[T] = ((sf): AnyRef) match { + case FromJavaObjDoubleConsumer((f @ _)) => f.asInstanceOf[java.util.function.ObjDoubleConsumer[T]] + case _ => new AsJavaObjDoubleConsumer[T](sf.asInstanceOf[scala.Function2[T, Double, Unit]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromObjIntConsumer[T](jf: java.util.function.ObjIntConsumer[T]): scala.Function2[T, java.lang.Integer, scala.runtime.BoxedUnit] = jf match { + case AsJavaObjIntConsumer((f @ _)) => f.asInstanceOf[scala.Function2[T, java.lang.Integer, scala.runtime.BoxedUnit]] + case _ => new FromJavaObjIntConsumer[T](jf).asInstanceOf[scala.Function2[T, java.lang.Integer, scala.runtime.BoxedUnit]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaObjIntConsumer[T](sf: scala.Function2[T, java.lang.Integer, scala.runtime.BoxedUnit]): java.util.function.ObjIntConsumer[T] = ((sf): AnyRef) match { + case FromJavaObjIntConsumer((f @ _)) => f.asInstanceOf[java.util.function.ObjIntConsumer[T]] + case _ => new AsJavaObjIntConsumer[T](sf.asInstanceOf[scala.Function2[T, Int, Unit]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromObjLongConsumer[T](jf: java.util.function.ObjLongConsumer[T]): scala.Function2[T, java.lang.Long, scala.runtime.BoxedUnit] = jf match { + case AsJavaObjLongConsumer((f @ _)) => f.asInstanceOf[scala.Function2[T, java.lang.Long, scala.runtime.BoxedUnit]] + case _ => new FromJavaObjLongConsumer[T](jf).asInstanceOf[scala.Function2[T, java.lang.Long, scala.runtime.BoxedUnit]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaObjLongConsumer[T](sf: scala.Function2[T, java.lang.Long, scala.runtime.BoxedUnit]): java.util.function.ObjLongConsumer[T] = ((sf): AnyRef) match { + case FromJavaObjLongConsumer((f @ _)) => f.asInstanceOf[java.util.function.ObjLongConsumer[T]] + case _ => new AsJavaObjLongConsumer[T](sf.asInstanceOf[scala.Function2[T, Long, Unit]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromPredicate[T](jf: java.util.function.Predicate[T]): scala.Function1[T, java.lang.Boolean] = jf match { + case AsJavaPredicate((f @ _)) => f.asInstanceOf[scala.Function1[T, java.lang.Boolean]] + case _ => new FromJavaPredicate[T](jf).asInstanceOf[scala.Function1[T, java.lang.Boolean]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaPredicate[T](sf: scala.Function1[T, java.lang.Boolean]): java.util.function.Predicate[T] = ((sf): AnyRef) match { + case FromJavaPredicate((f @ _)) => f.asInstanceOf[java.util.function.Predicate[T]] + case _ => new AsJavaPredicate[T](sf.asInstanceOf[scala.Function1[T, Boolean]]) + } + + + @inline def asScalaFromSupplier[T](jf: java.util.function.Supplier[T]): scala.Function0[T] = jf match { + case AsJavaSupplier((f @ _)) => f.asInstanceOf[scala.Function0[T]] + case _ => new FromJavaSupplier[T](jf).asInstanceOf[scala.Function0[T]] + } + + @inline def asJavaSupplier[T](sf: scala.Function0[T]): java.util.function.Supplier[T] = ((sf): AnyRef) match { + case FromJavaSupplier((f @ _)) => f.asInstanceOf[java.util.function.Supplier[T]] + case _ => new AsJavaSupplier[T](sf.asInstanceOf[scala.Function0[T]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromToDoubleBiFunction[T, U](jf: java.util.function.ToDoubleBiFunction[T, U]): scala.Function2[T, U, java.lang.Double] = jf match { + case AsJavaToDoubleBiFunction((f @ _)) => f.asInstanceOf[scala.Function2[T, U, java.lang.Double]] + case _ => new FromJavaToDoubleBiFunction[T, U](jf).asInstanceOf[scala.Function2[T, U, java.lang.Double]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaToDoubleBiFunction[T, U](sf: scala.Function2[T, U, java.lang.Double]): java.util.function.ToDoubleBiFunction[T, U] = ((sf): AnyRef) match { + case FromJavaToDoubleBiFunction((f @ _)) => f.asInstanceOf[java.util.function.ToDoubleBiFunction[T, U]] + case _ => new AsJavaToDoubleBiFunction[T, U](sf.asInstanceOf[scala.Function2[T, U, Double]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromToDoubleFunction[T](jf: java.util.function.ToDoubleFunction[T]): scala.Function1[T, java.lang.Double] = jf match { + case AsJavaToDoubleFunction((f @ _)) => f.asInstanceOf[scala.Function1[T, java.lang.Double]] + case _ => new FromJavaToDoubleFunction[T](jf).asInstanceOf[scala.Function1[T, java.lang.Double]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaToDoubleFunction[T](sf: scala.Function1[T, java.lang.Double]): java.util.function.ToDoubleFunction[T] = ((sf): AnyRef) match { + case FromJavaToDoubleFunction((f @ _)) => f.asInstanceOf[java.util.function.ToDoubleFunction[T]] + case _ => new AsJavaToDoubleFunction[T](sf.asInstanceOf[scala.Function1[T, Double]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromToIntBiFunction[T, U](jf: java.util.function.ToIntBiFunction[T, U]): scala.Function2[T, U, java.lang.Integer] = jf match { + case AsJavaToIntBiFunction((f @ _)) => f.asInstanceOf[scala.Function2[T, U, java.lang.Integer]] + case _ => new FromJavaToIntBiFunction[T, U](jf).asInstanceOf[scala.Function2[T, U, java.lang.Integer]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaToIntBiFunction[T, U](sf: scala.Function2[T, U, java.lang.Integer]): java.util.function.ToIntBiFunction[T, U] = ((sf): AnyRef) match { + case FromJavaToIntBiFunction((f @ _)) => f.asInstanceOf[java.util.function.ToIntBiFunction[T, U]] + case _ => new AsJavaToIntBiFunction[T, U](sf.asInstanceOf[scala.Function2[T, U, Int]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromToIntFunction[T](jf: java.util.function.ToIntFunction[T]): scala.Function1[T, java.lang.Integer] = jf match { + case AsJavaToIntFunction((f @ _)) => f.asInstanceOf[scala.Function1[T, java.lang.Integer]] + case _ => new FromJavaToIntFunction[T](jf).asInstanceOf[scala.Function1[T, java.lang.Integer]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaToIntFunction[T](sf: scala.Function1[T, java.lang.Integer]): java.util.function.ToIntFunction[T] = ((sf): AnyRef) match { + case FromJavaToIntFunction((f @ _)) => f.asInstanceOf[java.util.function.ToIntFunction[T]] + case _ => new AsJavaToIntFunction[T](sf.asInstanceOf[scala.Function1[T, Int]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromToLongBiFunction[T, U](jf: java.util.function.ToLongBiFunction[T, U]): scala.Function2[T, U, java.lang.Long] = jf match { + case AsJavaToLongBiFunction((f @ _)) => f.asInstanceOf[scala.Function2[T, U, java.lang.Long]] + case _ => new FromJavaToLongBiFunction[T, U](jf).asInstanceOf[scala.Function2[T, U, java.lang.Long]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaToLongBiFunction[T, U](sf: scala.Function2[T, U, java.lang.Long]): java.util.function.ToLongBiFunction[T, U] = ((sf): AnyRef) match { + case FromJavaToLongBiFunction((f @ _)) => f.asInstanceOf[java.util.function.ToLongBiFunction[T, U]] + case _ => new AsJavaToLongBiFunction[T, U](sf.asInstanceOf[scala.Function2[T, U, Long]]) + } + + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asScalaFromToLongFunction[T](jf: java.util.function.ToLongFunction[T]): scala.Function1[T, java.lang.Long] = jf match { + case AsJavaToLongFunction((f @ _)) => f.asInstanceOf[scala.Function1[T, java.lang.Long]] + case _ => new FromJavaToLongFunction[T](jf).asInstanceOf[scala.Function1[T, java.lang.Long]] + } + + /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + * primitive type `scala.X` to improve compatibility when using it in Java code (the + * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + */ + @inline def asJavaToLongFunction[T](sf: scala.Function1[T, java.lang.Long]): java.util.function.ToLongFunction[T] = ((sf): AnyRef) match { + case FromJavaToLongFunction((f @ _)) => f.asInstanceOf[java.util.function.ToLongFunction[T]] + case _ => new AsJavaToLongFunction[T](sf.asInstanceOf[scala.Function1[T, Long]]) + } + + + @inline def asScalaFromUnaryOperator[T](jf: java.util.function.UnaryOperator[T]): scala.Function1[T, T] = jf match { + case AsJavaUnaryOperator((f @ _)) => f.asInstanceOf[scala.Function1[T, T]] + case _ => new FromJavaUnaryOperator[T](jf).asInstanceOf[scala.Function1[T, T]] + } + + @inline def asJavaUnaryOperator[T](sf: scala.Function1[T, T]): java.util.function.UnaryOperator[T] = ((sf): AnyRef) match { + case FromJavaUnaryOperator((f @ _)) => f.asInstanceOf[java.util.function.UnaryOperator[T]] + case _ => new AsJavaUnaryOperator[T](sf.asInstanceOf[scala.Function1[T, T]]) + } +} diff --git a/library/src/scala/jdk/javaapi/FutureConverters.scala b/library/src/scala/jdk/javaapi/FutureConverters.scala new file mode 100644 index 000000000000..c1538e8d1c31 --- /dev/null +++ b/library/src/scala/jdk/javaapi/FutureConverters.scala @@ -0,0 +1,90 @@ +/* + * 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.jdk.javaapi + +import scala.language.`2.13` +import java.util.concurrent.{CompletableFuture, CompletionStage} +import scala.concurrent.impl.FutureConvertersImpl.{CF, P} +import scala.concurrent.{ExecutionContext, Future} +import scala.util.Success + +/** This object contains methods that convert between Scala [[scala.concurrent.Future]] and Java [[java.util.concurrent.CompletionStage]]. + * + * The explicit conversion methods defined here are intended to be used in Java code. For Scala + * code, it is recommended to use the extension methods defined in [[scala.jdk.FutureConverters]]. + * + * Note that the bridge is implemented at the read-only side of asynchronous handles, namely + * [[scala.concurrent.Future]] (instead of [[scala.concurrent.Promise]]) and [[java.util.concurrent.CompletionStage]] (instead of + * [[java.util.concurrent.CompletableFuture]]). This is intentional, as the semantics of bridging + * the write-handles would be prone to race conditions; if both ends (`CompletableFuture` and + * `Promise`) are completed independently at the same time, they may contain different values + * afterwards. For this reason, `toCompletableFuture` is not supported on the created + * `CompletionStage`s. + */ +object FutureConverters { + /** Returns a [[java.util.concurrent.CompletionStage]] that will be completed with the same value or exception as the + * given Scala [[scala.concurrent.Future]] when that completes. Since the Future is a read-only representation, + * this CompletionStage does not support the `toCompletableFuture` method. + * + * The semantics of Scala Future demand that all callbacks are invoked asynchronously by default, + * therefore the returned CompletionStage routes all calls to synchronous transformations to + * their asynchronous counterparts, i.e., `thenRun` will internally call `thenRunAsync`. + * + * @param f The Scala Future which may eventually supply the completion for the returned + * CompletionStage + * @return a CompletionStage that runs all callbacks asynchronously and does not support the + * CompletableFuture interface + */ + def asJava[T](f: Future[T]): CompletionStage[T] = { + f match { + case p: P[T] => p.wrapped + // in theory not safe (could be `class C extends Future[A] with CompletionStage[B]`): + case c: CompletionStage[T @unchecked] => c + case _ => + val cf = new CF[T](f) + f.onComplete(cf)(ExecutionContext.parasitic) + cf + } + } + + /** Returns a Scala [[scala.concurrent.Future]] that will be completed with the same value or exception as the + * given [[java.util.concurrent.CompletionStage]] when that completes. Transformations of the returned Future are + * executed asynchronously as specified by the ExecutionContext that is given to the combinator + * methods. + * + * @param cs The CompletionStage which may eventually supply the completion for the returned + * Scala Future + * @return a Scala Future that represents the CompletionStage's completion + */ + def asScala[T](cs: CompletionStage[T]): Future[T] = { + cs match { + case cf: CF[T] => cf.wrapped + // in theory not safe (could be `class C extends Future[A] with CompletionStage[B]`): + case f: Future[T @unchecked] => f + case _ => + val p = new P[T](cs) + val completedCF = cs match { + case cf0: CompletableFuture[T @unchecked] => + // drop `MinimalStage` (scala/bug#12918) + val cf = cf0.toCompletableFuture + if (cf.isDone && !cf.isCompletedExceptionally) cf else null + case _ => null + } + if (completedCF != null) + p.tryComplete(Success(completedCF.join())) + else + cs.handle(p) + p.future + } + } +} diff --git a/library/src/scala/jdk/javaapi/OptionConverters.scala b/library/src/scala/jdk/javaapi/OptionConverters.scala new file mode 100644 index 000000000000..93b6720c8eb4 --- /dev/null +++ b/library/src/scala/jdk/javaapi/OptionConverters.scala @@ -0,0 +1,85 @@ +/* + * 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.jdk.javaapi + +import scala.language.`2.13` +import java.util.{Optional, OptionalDouble, OptionalInt, OptionalLong} +import java.{lang => jl} + +/** This object contains methods that convert between Scala `Option` and Java `Optional` types. + * + * The explicit conversion methods defined here are intended to be used in Java code. For Scala + * code, it is recommended to use the extension methods defined in [[scala.jdk.OptionConverters]]. + * + * @define primitiveNote Note: this method uses the boxed type `java.lang.X` instead of the + * primitive type `scala.X` to improve compatibility when using it in + * Java code (the Scala compiler emits `C[Int]` as `C[Object]` in bytecode + * due to [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In + * Scala code, add `import scala.jdk.OptionConverters._` and use the + * extension methods instead. + */ +object OptionConverters { + /** Convert a Scala `Option` to a Java `Optional` */ + def toJava[A](o: Option[A]): Optional[A] = o match { + case Some(a) => Optional.ofNullable(a) + case _ => Optional.empty[A] + } + + /** Convert a Scala `Option[java.lang.Double]` to a Java `OptionalDouble` + * + * $primitiveNote + */ + def toJavaOptionalDouble(o: Option[jl.Double]): OptionalDouble = o match { + case Some(a) => OptionalDouble.of(a) + case _ => OptionalDouble.empty + } + + /** Convert a Scala `Option[java.lang.Integer]` to a Java `OptionalInt` + * + * $primitiveNote + */ + def toJavaOptionalInt(o: Option[jl.Integer]): OptionalInt = o match { + case Some(a) => OptionalInt.of(a) + case _ => OptionalInt.empty + } + + /** Convert a Scala `Option[java.lang.Long]` to a Java `OptionalLong` + * + * $primitiveNote + */ + def toJavaOptionalLong(o: Option[jl.Long]): OptionalLong = o match { + case Some(a) => OptionalLong.of(a) + case _ => OptionalLong.empty + } + + /** Convert a Java `Optional` to a Scala `Option` */ + def toScala[A](o: Optional[A]): Option[A] = if (o.isPresent) Some(o.get) else None + + /** Convert a Java `OptionalDouble` to a Scala `Option[java.lang.Double]` + * + * $primitiveNote + */ + def toScala(o: OptionalDouble): Option[jl.Double] = if (o.isPresent) Some(o.getAsDouble) else None + + /** Convert a Java `OptionalInt` to a Scala `Option[java.lang.Integer]` + * + * $primitiveNote + */ + def toScala(o: OptionalInt): Option[jl.Integer] = if (o.isPresent) Some(o.getAsInt) else None + + /** Convert a Java `OptionalLong` to a Scala `Option[java.lang.Long]` + * + * $primitiveNote + */ + def toScala(o: OptionalLong): Option[jl.Long] = if (o.isPresent) Some(o.getAsLong) else None +} diff --git a/library/src/scala/jdk/javaapi/StreamConverters.scala b/library/src/scala/jdk/javaapi/StreamConverters.scala new file mode 100644 index 000000000000..f91c34e0b711 --- /dev/null +++ b/library/src/scala/jdk/javaapi/StreamConverters.scala @@ -0,0 +1,357 @@ +/* + * 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.jdk.javaapi + +import scala.language.`2.13` +import java.util.stream.{DoubleStream, IntStream, LongStream, Stream, StreamSupport} +import java.{lang => jl} + +/** This object contains methods to create Java Streams that operate on Scala collections + * (sequentially or in parallel). For more information on Java streams, consult the documentation + * ([[https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html]]). + * + * The explicit conversion methods defined here are intended to be used in Java code. For Scala + * code, it is recommended to use the extension methods defined in [[scala.jdk.StreamConverters]]. + * + * Note: to convert between Scala collections and classic Java collections, use + * [[CollectionConverters]]. + * + * For details how the stream converters work, see [[scala.jdk.StreamConverters]]. + * + * @define parNote Note: parallel processing is only efficient for collections that have a + * [[scala.collection.Stepper]] implementation which supports efficient splitting. For collections + * where this is the case, the [[scala.collection.IterableOnce.stepper `stepper`]] + * method has a return type marked `with EfficientSplit`. + * + * @define primitiveNote Note: this method uses the boxed type `java.lang.X` instead of the + * primitive type `scala.X` to improve compatibility when using it in + * Java code (the Scala compiler emits `C[Int]` as `C[Object]` in bytecode + * due to [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In + * Scala code, add `import scala.jdk.StreamConverters._` and use the + * extension methods instead. + */ +object StreamConverters { + ///////////////////////////////////// + // sequential streams for collections + ///////////////////////////////////// + + /** Create a sequential [[java.util.stream.Stream Java Stream]] for a Scala collection. */ + def asJavaSeqStream[A](cc: IterableOnce[A]): Stream[A] = StreamSupport.stream(cc.stepper.spliterator, false) + + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for a Scala collection. + * + * $primitiveNote + */ + def asJavaSeqIntStream (cc: IterableOnce[jl.Integer]): IntStream = StreamSupport.intStream(cc.stepper.spliterator, false) + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for a Scala collection. + * + * $primitiveNote + */ + def asJavaSeqIntStreamFromByte (cc: IterableOnce[jl.Byte]): IntStream = StreamSupport.intStream(cc.stepper.spliterator, false) + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for a Scala collection. + * + * $primitiveNote + */ + def asJavaSeqIntStreamFromShort(cc: IterableOnce[jl.Short]): IntStream = StreamSupport.intStream(cc.stepper.spliterator, false) + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for a Scala collection. + * + * $primitiveNote + */ + def asJavaSeqIntStreamFromChar (cc: IterableOnce[jl.Character]): IntStream = StreamSupport.intStream(cc.stepper.spliterator, false) + + /** Create a sequential [[java.util.stream.DoubleStream Java DoubleStream]] for a Scala collection. + * + * $primitiveNote + */ + def asJavaSeqDoubleStream (cc: IterableOnce[jl.Double]): DoubleStream = StreamSupport.doubleStream(cc.stepper.spliterator, false) + /** Create a sequential [[java.util.stream.DoubleStream Java DoubleStream]] for a Scala collection. + * + * $primitiveNote + */ + def asJavaSeqDoubleStreamFromFloat(cc: IterableOnce[jl.Float]): DoubleStream = StreamSupport.doubleStream(cc.stepper.spliterator, false) + + /** Create a sequential [[java.util.stream.LongStream Java LongStream]] for a Scala collection. + * + * $primitiveNote + */ + def asJavaSeqLongStream(cc: IterableOnce[jl.Long]): LongStream = StreamSupport.longStream(cc.stepper.spliterator, false) + + // Map Key Streams + + /** Create a sequential [[java.util.stream.Stream Java Stream]] for the keys of a Scala Map. */ + def asJavaSeqKeyStream[K, V](m: collection.Map[K, V]): Stream[K] = StreamSupport.stream(m.keyStepper.spliterator, false) + + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for the keys of a Scala Map. + * + * $primitiveNote + */ + def asJavaSeqKeyIntStream [V](m: collection.Map[jl.Integer, V]): IntStream = StreamSupport.intStream(m.keyStepper.spliterator, false) + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for the keys of a Scala Map. + * + * $primitiveNote + */ + def asJavaSeqKeyIntStreamFromByte [V](m: collection.Map[jl.Byte, V]): IntStream = StreamSupport.intStream(m.keyStepper.spliterator, false) + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for the keys of a Scala Map. + * + * $primitiveNote + */ + def asJavaSeqKeyIntStreamFromShort[V](m: collection.Map[jl.Short, V]): IntStream = StreamSupport.intStream(m.keyStepper.spliterator, false) + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for the keys of a Scala Map. + * + * $primitiveNote + */ + def asJavaSeqKeyIntStreamFromChar [V](m: collection.Map[jl.Character, V]): IntStream = StreamSupport.intStream(m.keyStepper.spliterator, false) + + /** Create a sequential [[java.util.stream.DoubleStream Java DoubleStream]] for the keys of a Scala Map. + * + * $primitiveNote + */ + def asJavaSeqKeyDoubleStream [V](m: collection.Map[jl.Double, V]): DoubleStream = StreamSupport.doubleStream(m.keyStepper.spliterator, false) + /** Create a sequential [[java.util.stream.DoubleStream Java DoubleStream]] for the keys of a Scala Map. + * + * $primitiveNote + */ + def asJavaSeqKeyDoubleStreamFromFloat[V](m: collection.Map[jl.Float, V]): DoubleStream = StreamSupport.doubleStream(m.keyStepper.spliterator, false) + + /** Create a sequential [[java.util.stream.LongStream Java LongStream]] for the keys of a Scala Map. + * + * $primitiveNote + */ + def asJavaSeqKeyLongStream[V](m: collection.Map[jl.Long, V]): LongStream = StreamSupport.longStream(m.keyStepper.spliterator, false) + + // Map Value Streams + + /** Create a sequential [[java.util.stream.Stream Java Stream]] for the values of a Scala Map. */ + def asJavaSeqValueStream[K, V](m: collection.Map[K, V]): Stream[V] = StreamSupport.stream(m.valueStepper.spliterator, false) + + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for the values of a + * + * $primitiveNote + */ + def asJavaSeqValueIntStream [K](m: collection.Map[K, jl.Integer]): IntStream = StreamSupport.intStream(m.valueStepper.spliterator, false) + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for the values of a + * + * $primitiveNote + */ + def asJavaSeqValueIntStreamFromByte [K](m: collection.Map[K, jl.Byte]): IntStream = StreamSupport.intStream(m.valueStepper.spliterator, false) + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for the values of a + * + * $primitiveNote + */ + def asJavaSeqValueIntStreamFromShort[K](m: collection.Map[K, jl.Short]): IntStream = StreamSupport.intStream(m.valueStepper.spliterator, false) + /** Create a sequential [[java.util.stream.IntStream Java IntStream]] for the values of a + * + * $primitiveNote + */ + def asJavaSeqValueIntStreamFromChar [K](m: collection.Map[K, jl.Character]): IntStream = StreamSupport.intStream(m.valueStepper.spliterator, false) + + /** Create a sequential [[java.util.stream.DoubleStream Java DoubleStream]] for the values of a + * + * $primitiveNote + */ + def asJavaSeqValueDoubleStream [K](m: collection.Map[K, jl.Double]): DoubleStream = StreamSupport.doubleStream(m.valueStepper.spliterator, false) + /** Create a sequential [[java.util.stream.DoubleStream Java DoubleStream]] for the values of a + * + * $primitiveNote + */ + def asJavaSeqValueDoubleStreamFromFloat[K](m: collection.Map[K, jl.Float]): DoubleStream = StreamSupport.doubleStream(m.valueStepper.spliterator, false) + + /** Create a sequential [[java.util.stream.LongStream Java LongStream]] for the values of a + * + * $primitiveNote + */ + def asJavaSeqValueLongStream[K](m: collection.Map[K, jl.Long]): LongStream = StreamSupport.longStream(m.valueStepper.spliterator, false) + + /////////////////////////////////// + // parallel streams for collections + /////////////////////////////////// + + /** Create a parallel [[java.util.stream.Stream Java Stream]] for a Scala collection. + * + * $parNote + */ + def asJavaParStream[A](cc: IterableOnce[A]): Stream[A] = StreamSupport.stream(cc.stepper.spliterator, true) + + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for a Scala collection. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParIntStream (cc: IterableOnce[jl.Integer]): IntStream = StreamSupport.intStream(cc.stepper.spliterator, true) + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for a Scala collection. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParIntStreamFromByte (cc: IterableOnce[jl.Byte]): IntStream = StreamSupport.intStream(cc.stepper.spliterator, true) + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for a Scala collection. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParIntStreamFromShort(cc: IterableOnce[jl.Short]): IntStream = StreamSupport.intStream(cc.stepper.spliterator, true) + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for a Scala collection. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParIntStreamFromChar (cc: IterableOnce[jl.Character]): IntStream = StreamSupport.intStream(cc.stepper.spliterator, true) + + /** Create a parallel [[java.util.stream.DoubleStream Java DoubleStream]] for a Scala collection. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParDoubleStream (cc: IterableOnce[jl.Double]): DoubleStream = StreamSupport.doubleStream(cc.stepper.spliterator, true) + /** Create a parallel [[java.util.stream.DoubleStream Java DoubleStream]] for a Scala collection. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParDoubleStreamFromFloat(cc: IterableOnce[jl.Float]): DoubleStream = StreamSupport.doubleStream(cc.stepper.spliterator, true) + + /** Create a parallel [[java.util.stream.LongStream Java LongStream]] for a Scala collection. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParLongStream(cc: IterableOnce[jl.Long]): LongStream = StreamSupport.longStream(cc.stepper.spliterator, true) + + + // Map Key Streams + + /** Create a parallel [[java.util.stream.Stream Java Stream]] for the keys of a Scala Map. + * + * $parNote + */ + def asJavaParKeyStream[K, V](m: collection.Map[K, V]): Stream[K] = StreamSupport.stream(m.keyStepper.spliterator, true) + + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for the keys of a Scala Map. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParKeyIntStream [V](m: collection.Map[jl.Integer, V]): IntStream = StreamSupport.intStream(m.keyStepper.spliterator, true) + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for the keys of a Scala Map. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParKeyIntStreamFromByte [V](m: collection.Map[jl.Byte, V]): IntStream = StreamSupport.intStream(m.keyStepper.spliterator, true) + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for the keys of a Scala Map. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParKeyIntStreamFromShort[V](m: collection.Map[jl.Short, V]): IntStream = StreamSupport.intStream(m.keyStepper.spliterator, true) + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for the keys of a Scala Map. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParKeyIntStreamFromChar [V](m: collection.Map[jl.Character, V]): IntStream = StreamSupport.intStream(m.keyStepper.spliterator, true) + + /** Create a parallel [[java.util.stream.DoubleStream Java DoubleStream]] for the keys of a Scala Map. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParKeyDoubleStream [V](m: collection.Map[jl.Double, V]): DoubleStream = StreamSupport.doubleStream(m.keyStepper.spliterator, true) + /** Create a parallel [[java.util.stream.DoubleStream Java DoubleStream]] for the keys of a Scala Map. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParKeyDoubleStreamFromFloat[V](m: collection.Map[jl.Float, V]): DoubleStream = StreamSupport.doubleStream(m.keyStepper.spliterator, true) + + /** Create a parallel [[java.util.stream.LongStream Java LongStream]] for the keys of a Scala Map. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParKeyLongStream[V](m: collection.Map[jl.Long, V]): LongStream = StreamSupport.longStream(m.keyStepper.spliterator, true) + + // Map Value Streams + + /** Create a parallel [[java.util.stream.Stream Java Stream]] for the values of a Scala Map. + * + * $parNote + */ + def asJavaParValueStream[K, V](m: collection.Map[K, V]): Stream[V] = StreamSupport.stream(m.valueStepper.spliterator, true) + + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for the values of a Scala Map. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParValueIntStream [K](m: collection.Map[K, jl.Integer]): IntStream = StreamSupport.intStream(m.valueStepper.spliterator, true) + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for the values of a Scala Map. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParValueIntStreamFromByte [K](m: collection.Map[K, jl.Byte]): IntStream = StreamSupport.intStream(m.valueStepper.spliterator, true) + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for the values of a Scala Map. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParValueIntStreamFromShort[K](m: collection.Map[K, jl.Short]): IntStream = StreamSupport.intStream(m.valueStepper.spliterator, true) + /** Create a parallel [[java.util.stream.IntStream Java IntStream]] for the values of a Scala Map. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParValueIntStreamFromChar [K](m: collection.Map[K, jl.Character]): IntStream = StreamSupport.intStream(m.valueStepper.spliterator, true) + + /** Create a parallel [[java.util.stream.DoubleStream Java DoubleStream]] for the values of a Scala Map. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParValueDoubleStream [K](m: collection.Map[K, jl.Double]): DoubleStream = StreamSupport.doubleStream(m.valueStepper.spliterator, true) + /** Create a parallel [[java.util.stream.DoubleStream Java DoubleStream]] for the values of a Scala Map. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParValueDoubleStreamFromFloat[K](m: collection.Map[K, jl.Float]): DoubleStream = StreamSupport.doubleStream(m.valueStepper.spliterator, true) + + /** Create a parallel [[java.util.stream.LongStream Java LongStream]] for the values of a Scala Map. + * + * $parNote + * + * $primitiveNote + */ + def asJavaParValueLongStream[K](m: collection.Map[K, jl.Long]): LongStream = StreamSupport.longStream(m.valueStepper.spliterator, true) +} diff --git a/library/src/scala/jdk/package.scala b/library/src/scala/jdk/package.scala new file mode 100644 index 000000000000..b3188ae0faa3 --- /dev/null +++ b/library/src/scala/jdk/package.scala @@ -0,0 +1,48 @@ +/* + * 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.language.`2.13` + +/** The jdk package contains utilities to interact with JDK classes. + * + * This packages offers a number of converters, that are able to wrap or copy + * types from the scala library to equivalent types in the JDK class library + * and vice versa: + * + * - [[CollectionConverters]], converting collections like [[scala.collection.Seq]], + * [[scala.collection.Map]], [[scala.collection.Set]], + * [[scala.collection.mutable.Buffer]], [[scala.collection.Iterator]] + * and [[scala.collection.Iterable]] to their JDK counterparts + * - [[OptionConverters]], converting between [[Option]] and + * [[java.util.Optional]] and primitive variations + * - [[StreamConverters]], to create JDK Streams from scala collections + * - [[DurationConverters]], for conversions between scala + * [[scala.concurrent.duration.FiniteDuration]] and [[java.time.Duration]] + * - [[FunctionConverters]], from scala Functions to java + * [[java.util.function.Function]], [[java.util.function.UnaryOperator]], + * [[java.util.function.Consumer]] and [[java.util.function.Predicate]], as + * well as primitive variations and Bi-variations. + * + * By convention, converters that wrap an object to provide a different + * interface to the same underlying data structure use .asScala and .asJava + * extension methods, whereas converters that copy the underlying data structure + * use .toScala and .toJava. + * + * In the [[javaapi]] package, the same converters can be found with a + * java-friendly interface that don't rely on implicit enrichments. + * + * Additionally, this package offers [[Accumulator]]s, capable of efficiently + * traversing JDK Streams. + **/ +package object jdk diff --git a/library/src/scala/language.scala b/library/src/scala/language.scala new file mode 100644 index 000000000000..47194f14a7b6 --- /dev/null +++ b/library/src/scala/language.scala @@ -0,0 +1,563 @@ +/* + * 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.language.`2.13` + +import scala.annotation.compileTimeOnly + +/** + * The `scala.language` object controls the language features available to the programmer, as proposed in the + * [[https://docs.google.com/document/d/1nlkvpoIRkx7at1qJEZafJwthZ3GeIklTFhqmXMvTX9Q/edit '''SIP-18 document''']]. + * + * Each of these features has to be explicitly imported into the current scope to become available: + * {{{ + * import language.postfixOps // or language._ + * List(1, 2, 3) reverse + * }}} + * + * The language features are: + * - [[dynamics `dynamics`]] enables defining calls rewriting using the [[scala.Dynamic `Dynamic`]] trait + * - [[existentials `existentials`]] enables writing existential types + * - [[higherKinds `higherKinds`]] enables writing higher-kinded types + * - [[implicitConversions `implicitConversions`]] enables defining implicit methods and members + * - [[postfixOps `postfixOps`]] enables postfix operators (not recommended) + * - [[reflectiveCalls `reflectiveCalls`]] enables using structural types + * - [[experimental `experimental`]] contains newer features that have not yet been tested in production + * + * @groupname production Language Features + * @groupname experimental Experimental Language Features + * @groupprio experimental 10 + */ +object language { + + import languageFeature._ + + /** Only where this feature is enabled, can direct or indirect subclasses of trait scala.Dynamic + * be defined. If `dynamics` is not enabled, a definition of a class, trait, + * or object that has `Dynamic` as a base trait is rejected by the compiler. + * + * Selections of dynamic members of existing subclasses of trait `Dynamic` are unaffected; + * they can be used anywhere. + * + * '''Why introduce the feature?''' To enable flexible DSLs and convenient interfacing + * with dynamic languages. + * + * '''Why control it?''' Dynamic member selection can undermine static checkability + * of programs. Furthermore, dynamic member selection often relies on reflection, + * which is not available on all platforms. + * + * @group production + */ + implicit lazy val dynamics: dynamics = languageFeature.dynamics + + /** Only where this feature is enabled, is postfix operator notation `(expr op)` permitted. + * If `postfixOps` is not enabled, an expression using postfix notation is rejected by the compiler. + * + * '''Why keep the feature?''' Postfix notation is preserved for backward + * compatibility only. Historically, several DSLs written in Scala need the notation. + * + * '''Why control it?''' Postfix operators interact poorly with semicolon inference. + * Most programmers avoid them for this reason alone. Postfix syntax is + * associated with an abuse of infix notation, `a op1 b op2 c op3`, + * that can be harder to read than ordinary method invocation with judicious + * use of parentheses. It is recommended not to enable this feature except for + * legacy code. + * + * @group production + */ + implicit lazy val postfixOps: postfixOps = languageFeature.postfixOps + + /** Where this feature is enabled, accesses to members of structural types that need + * reflection are supported. If `reflectiveCalls` is not enabled, an expression + * requiring reflection will trigger a warning from the compiler. + * + * A structural type is a type of the form + * `Parents { Decls }` where `Decls` contains declarations of new members that do + * not override any member in `Parents`. To access one of these members, a + * reflective call is needed. + * + * '''Why keep the feature?''' Structural types provide great flexibility because + * they avoid the need to define inheritance hierarchies a priori. Besides, + * their definition falls out quite naturally from Scala’s concept of type refinement. + * + * '''Why control it?''' Reflection is not available on all platforms. Popular tools + * such as ProGuard have problems dealing with it. Even where reflection is available, + * reflective dispatch can lead to surprising performance degradations. + * + * @group production + */ + implicit lazy val reflectiveCalls: reflectiveCalls = languageFeature.reflectiveCalls + + /** Where this feature is enabled, definitions of implicit conversion methods are allowed. + * If `implicitConversions` is not enabled, the definition of an implicit + * conversion method will trigger a warning from the compiler. + * + * An implicit conversion is an implicit value of unary function type `A => B`, + * or an implicit method that has in its first parameter section a single, + * non-implicit parameter. Examples: + * + * {{{ + * implicit def intToString(i: Int): String = s"\$i" + * implicit val conv: Int => String = i => s"\$i" + * implicit val numerals: List[String] = List("zero", "one", "two", "three") + * implicit val strlen: String => Int = _.length + * implicit def listToInt[T](xs: List[T])(implicit f: T => Int): Int = xs.map(f).sum + * }}} + * + * This language feature warns only for implicit conversions introduced by methods. + * + * Other values, including functions or data types which extend `Function1`, + * such as `Map`, `Set`, and `List`, do not warn. + * + * Implicit class definitions, which introduce a conversion to the wrapping class, + * also do not warn. + * + * '''Why keep the feature?''' Implicit conversions are central to many aspects + * of Scala’s core libraries. + * + * '''Why control it?''' Implicit conversions are known to cause many pitfalls + * if over-used. And there is a tendency to over-use them because they look + * very powerful and their effects seem to be easy to understand. Also, in + * most situations using implicit parameters leads to a better design than + * implicit conversions. + * + * @group production + */ + implicit lazy val implicitConversions: implicitConversions = languageFeature.implicitConversions + + /** Where this feature is enabled, higher-kinded types can be written. + * If `higherKinds` is not enabled, a higher-kinded type such as `F[A]` + * will trigger a warning from the compiler. + * + * '''Why keep the feature?''' Higher-kinded types enable the definition of very general + * abstractions such as functor, monad, or arrow. A significant set of advanced + * libraries relies on them. Higher-kinded types are also at the core of the + * scala-virtualized effort to produce high-performance parallel DSLs through staging. + * + * '''Why control it?''' Higher kinded types in Scala lead to a Turing-complete + * type system, where compiler termination is no longer guaranteed. They tend + * to be useful mostly for type-level computation and for highly generic design + * patterns. The level of abstraction implied by these design patterns is often + * a barrier to understanding for newcomers to a Scala codebase. Some syntactic + * aspects of higher-kinded types are hard to understand for the uninitiated and + * type inference is less effective for them than for normal types. Because we are + * not completely happy with them yet, it is possible that some aspects of + * higher-kinded types will change in future versions of Scala. So an explicit + * enabling also serves as a warning that code involving higher-kinded types + * might have to be slightly revised in the future. + * + * @group production + */ + @deprecated("higherKinds no longer needs to be imported explicitly", "2.13.1") + implicit lazy val higherKinds: higherKinds = languageFeature.higherKinds + + /** Where this feature is enabled, existential types that cannot be expressed as wildcard + * types can be written and are allowed in inferred types of values or return + * types of methods. If `existentials` is not enabled, those cases will trigger + * a warning from the compiler. + * + * Existential types with wildcard type syntax such as `List[_]`, + * or `Map[String, _]` are not affected. + * + * '''Why keep the feature?''' Existential types are needed to make sense of Java’s wildcard + * types and raw types and the erased types of run-time values. + * + * '''Why control it?''' Having complex existential types in a code base usually makes + * application code very brittle, with a tendency to produce type errors with + * obscure error messages. Therefore, going overboard with existential types + * is generally perceived not to be a good idea. Also, complicated existential types + * might be no longer supported in a future simplification of the language. + * + * @group production + */ + implicit lazy val existentials: existentials = languageFeature.existentials + + /** The experimental object contains features that are known to have unstable API or + * behavior that may change in future releases. + * + * Experimental features '''may undergo API changes''' in future releases, so production + * code should not rely on them. + * + * Programmers are encouraged to try out experimental features and + * [[https://github.com/scala/bug/issues report any bugs or API inconsistencies]] + * they encounter so they can be improved in future releases. + * + * @group experimental + */ + object experimental { + + import languageFeature.experimental._ + + /** Only where this feature is enabled, are macro definitions allowed. + * If `macros` is not enabled, macro definitions are rejected by the compiler. + * + * Macro implementations and macro applications are not governed by this + * language feature; they can be used anywhere. + * + * '''Why introduce the feature?''' Macros promise to make the language more regular, + * replacing ad-hoc language constructs with a general powerful abstraction + * capability that can express them. Macros are also a more disciplined and + * powerful replacement for compiler plugins. + * + * '''Why control it?''' For their very power, macros can lead to code that is hard + * to debug and understand. + */ + implicit lazy val macros: macros = languageFeature.experimental.macros + + /* Experimental support for richer dependent types (disabled for now) + * One can still run the compiler with support for parsing singleton applications + * using command line option `-language:experimental.dependent`. + * But one cannot use a feature import for this as long as this entry is commented out. + */ + //object dependent + + /** Experimental support for named type arguments. + * + * @see [[https://dotty.epfl.ch/docs/reference/other-new-features/named-typeargs]] + */ + @compileTimeOnly("`namedTypeArguments` can only be used at compile time in import statements") + object namedTypeArguments + + /** Experimental support for generic number literals. + * + * @see [[https://dotty.epfl.ch/docs/reference/changed-features/numeric-literals]] + */ + @compileTimeOnly("`genericNumberLiterals` can only be used at compile time in import statements") + object genericNumberLiterals + + /** Experimental support for `erased` modifier + * + * @see [[https://dotty.epfl.ch/docs/reference/experimental/erased-defs]] + */ + @compileTimeOnly("`erasedDefinitions` can only be used at compile time in import statements") + object erasedDefinitions + + /** Experimental support for using indentation for arguments + */ + @compileTimeOnly("`fewerBraces` can only be used at compile time in import statements") + @deprecated("`fewerBraces` is now standard, no language import is needed", since = "3.3") + object fewerBraces + + /** Experimental support for typechecked exception capabilities + * + * @see [[https://dotty.epfl.ch/docs/reference/experimental/canthrow]] + */ + @compileTimeOnly("`saferExceptions` can only be used at compile time in import statements") + object saferExceptions + + /** Adds support for clause interleaving: + * Methods can now have as many type clauses as they like, this allows to have type bounds depend on terms: `def f(x: Int)[A <: x.type]: A` + * + * @see [[https://github.com/scala/improvement-proposals/blob/main/content/clause-interleaving.md]] + */ + @compileTimeOnly("`clauseInterleaving` can only be used at compile time in import statements") + @deprecated("`clauseInterleaving` is now standard, no language import is needed", since = "3.6") + object clauseInterleaving + + /** Experimental support for pure function type syntax + * + * @see [[https://dotty.epfl.ch/docs/reference/experimental/purefuns]] + */ + @compileTimeOnly("`pureFunctions` can only be used at compile time in import statements") + object pureFunctions + + /** Experimental support for capture checking; implies support for pureFunctions + * + * @see [[https://dotty.epfl.ch/docs/reference/experimental/cc]] + */ + @compileTimeOnly("`captureChecking` can only be used at compile time in import statements") + object captureChecking + + /** Experimental support for separation checking; requires captureChecking also to be enabled. + * + * @see [[https://dotty.epfl.ch/docs/reference/experimental/cc]] + */ + @compileTimeOnly("`separationChecking` can only be used at compile time in import statements") + object separationChecking + + /** Experimental support for automatic conversions of arguments, without requiring + * a language import `import scala.language.implicitConversions`. + * + * @see [[https://dotty.epfl.ch/docs/reference/experimental/into-modifier]] + */ + @compileTimeOnly("`into` can only be used at compile time in import statements") + object into + + /** Experimental support for named tuples. + * + * @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 + * - better tracking of dependencies through classes + * - better usability of context bounds + * - better syntax and conventions for type classes + * - ability to merge exported types in intersections + * + * @see [[https://dotty.epfl.ch/docs/reference/experimental/modularity]] + * @see [[https://dotty.epfl.ch/docs/reference/experimental/typeclasses]] + */ + @compileTimeOnly("`modularity` can only be used at compile time in import statements") + object modularity + + /** Was needed to add support for relaxed imports of extension methods. + * The language import is no longer needed as this is now a standard feature since SIP was accepted. + * @see [[http://dotty.epfl.ch/docs/reference/contextual/extension-methods]] + */ + @compileTimeOnly("`relaxedExtensionImports` can only be used at compile time in import statements") + @deprecated("The experimental.relaxedExtensionImports language import is no longer needed since the feature is now standard", since = "3.4") + object relaxedExtensionImports + + /** Enhance match type extractors to follow aliases and singletons. + * + * @see [[https://github.com/scala/improvement-proposals/pull/84]] + */ + @compileTimeOnly("`betterMatchTypeExtractors` can only be used at compile time in import statements") + @deprecated("The experimental.betterMatchTypeExtractors language import is no longer needed since the feature is now standard. It now has no effect, including when setting an older source version.", since = "3.6") + object betterMatchTypeExtractors + + /** Experimental support for quote pattern matching with polymorphic functions + * + * @see [[https://dotty.epfl.ch/docs/reference/experimental/quoted-patterns-with-polymorphic-functions]] + */ + @compileTimeOnly("`quotedPatternsWithPolymorphicFunctions` can only be used at compile time in import statements") + object quotedPatternsWithPolymorphicFunctions + + /** Experimental support for improvements in `for` comprehensions + * + * @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 + + } + + /** The deprecated object contains features that are no longer officially suypported in Scala. + * Features in this object are slated for removal. New code should not use them and + * old code should migrate away from them. + */ + @compileTimeOnly("`deprecated` can only be used at compile time in import statements") + object deprecated: + + /** Symbol literals have been deprecated since 2.13. Since Scala 3.0 they + * are no longer an official part of Scala. For compatibility with legacy software, + * symbol literals are still supported with a language import, but new software + * should not use them. + */ + @compileTimeOnly("`symbolLiterals` can only be used at compile time in import statements") + object symbolLiterals + + end deprecated + + /** Where imported, auto-tupling is disabled. + * + * '''Why control the feature?''' Auto-tupling can lead to confusing and + * brittle code in presence of overloads. In particular, surprising overloads + * can be selected, and adding new overloads can change which overload is selected + * in suprising ways. + * + * '''Why allow it?''' Not allowing auto-tupling is difficult to reconcile with + * operators accepting tuples. + */ + @compileTimeOnly("`noAutoTupling` can only be used at compile time in import statements") + object noAutoTupling + + /** Where imported, loose equality using eqAny is disabled. + * + * '''Why allow and control the feature?''' For compatibility and migration reasons, + * strict equality is opt-in. See linked documentation for more information. + * + * @see [[https://dotty.epfl.ch/docs/reference/contextual/multiversal-equality]] + */ + @compileTimeOnly("`strictEquality` can only be used at compile time in import statements") + object strictEquality + + /** Where imported, ad hoc extensions of non-open classes in other + * compilation units are allowed. + * + * '''Why control the feature?''' Ad-hoc extensions should usually be avoided + * since they typically cannot rely on an "internal" contract between a class + * and its extensions. Only open classes need to specify such a contract. + * Ad-hoc extensions might break for future versions of the extended class, + * since the extended class is free to change its implementation without + * being constrained by an internal contract. + * + * '''Why allow it?''' An ad-hoc extension can sometimes be necessary, + * for instance when mocking a class in a testing framework, or to work + * around a bug or missing feature in the original class. Nevertheless, + * such extensions should be limited in scope and clearly documented. + * That's why the language import is required for them. + */ + @compileTimeOnly("`adhocExtensions` can only be used at compile time in import statements") + object adhocExtensions + + /** Unsafe Nulls fot Explicit Nulls + * Inside the "unsafe" scope, `Null` is considered as a subtype of all reference types. + * + * @see [[http://dotty.epfl.ch/docs/reference/other-new-features/explicit-nulls.html]] + */ + @compileTimeOnly("`unsafeNulls` can only be used at compile time in import statements") + object unsafeNulls + + @compileTimeOnly("`future` can only be used at compile time in import statements") + object future + + @compileTimeOnly("`future-migration` can only be used at compile time in import statements") + object `future-migration` + + /** Set source version to 2.13. Effectively, this doesn't change the source language, + * but rather adapts the generated code as if it was compiled with Scala 2.13 + */ + @compileTimeOnly("`2.13` can only be used at compile time in import statements") + private[scala] object `2.13` + + /** Set source version to 3.0-migration. + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.0-migration` can only be used at compile time in import statements") + object `3.0-migration` + + /** Set source version to 3.0. + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.0` can only be used at compile time in import statements") + object `3.0` + + /** Set source version to 3.1-migration. + * + * This is a no-op, and should not be used. A syntax error will be reported upon import. + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.1-migration` can only be used at compile time in import statements") + @deprecated("`3.1-migration` is not valid, use `3.1` instead", since = "3.2") + object `3.1-migration` + + /** Set source version to 3.1 + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.1` can only be used at compile time in import statements") + object `3.1` + + /** Set source version to 3.2-migration. + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.2-migration` can only be used at compile time in import statements") + object `3.2-migration` + + /** Set source version to 3.2 + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.2` can only be used at compile time in import statements") + object `3.2` + + /** Set source version to 3.3-migration. + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.3-migration` can only be used at compile time in import statements") + object `3.3-migration` + + /** Set source version to 3.3 + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.3` can only be used at compile time in import statements") + object `3.3` + + /** Set source version to 3.4-migration. + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.4-migration` can only be used at compile time in import statements") + object `3.4-migration` + + /** Set source version to 3.4 + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.4` can only be used at compile time in import statements") + object `3.4` + + /** Set source version to 3.5-migration. + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.5-migration` can only be used at compile time in import statements") + object `3.5-migration` + + /** Set source version to 3.5 + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.5` can only be used at compile time in import statements") + object `3.5` + + /** Set source version to 3.6-migration. + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.6-migration` can only be used at compile time in import statements") + object `3.6-migration` + + /** Set source version to 3.6 + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.6` can only be used at compile time in import statements") + object `3.6` + + /** Set source version to 3.7-migration. + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @compileTimeOnly("`3.7-migration` can only be used at compile time in import statements") + object `3.7-migration` + + /** Set source version to 3.7 + * + * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] + */ + @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` + +} diff --git a/library/src/scala/languageFeature.scala b/library/src/scala/languageFeature.scala new file mode 100644 index 000000000000..8ce9724ecc17 --- /dev/null +++ b/library/src/scala/languageFeature.scala @@ -0,0 +1,52 @@ +/* + * 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.language.`2.13` +import scala.annotation.meta + +object languageFeature { + + @meta.languageFeature("extension of type scala.Dynamic", enableRequired = true) + sealed trait dynamics + object dynamics extends dynamics + + @meta.languageFeature("postfix operator #", enableRequired = true) + sealed trait postfixOps + object postfixOps extends postfixOps + + @meta.languageFeature("reflective access of structural type member #", enableRequired = false) + sealed trait reflectiveCalls + object reflectiveCalls extends reflectiveCalls + + @meta.languageFeature("implicit conversion #", enableRequired = false) + sealed trait implicitConversions + object implicitConversions extends implicitConversions + + @deprecated("scala.language.higherKinds no longer needs to be imported explicitly", "2.13.1") + @meta.languageFeature("higher-kinded type", enableRequired = false) + sealed trait higherKinds + @deprecated("scala.language.higherKinds no longer needs to be imported explicitly", "2.13.1") + object higherKinds extends higherKinds + + @meta.languageFeature("#, which cannot be expressed by wildcards,", enableRequired = false) + sealed trait existentials + object existentials extends existentials + + object experimental { + @meta.languageFeature("macro definition", enableRequired = true) + sealed trait macros + object macros extends macros + } +} + diff --git a/library/src/scala/math/BigDecimal.scala b/library/src/scala/math/BigDecimal.scala new file mode 100644 index 000000000000..7e0cb6434401 --- /dev/null +++ b/library/src/scala/math/BigDecimal.scala @@ -0,0 +1,722 @@ +/* + * 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 math + +import scala.language.`2.13` +import scala.language.implicitConversions + +import java.math.{ + BigDecimal => BigDec, + MathContext, + RoundingMode => JRM, +} +import scala.collection.immutable.NumericRange + +object BigDecimal { + private final val maximumHashScale = 4934 // Quit maintaining hash identity with BigInt beyond this scale + private final val hashCodeNotComputed = 0x5D50690F // Magic value (happens to be "BigDecimal" old MurmurHash3 value) + private final val deci2binary = 3.3219280948873626 // Ratio of log(10) to log(2) + private[this] val minCached = -512 + private[this] val maxCached = 512 + val defaultMathContext = MathContext.DECIMAL128 + + /** Cache only for defaultMathContext using BigDecimals in a small range. */ + private[this] lazy val cache = new Array[BigDecimal](maxCached - minCached + 1) + + object RoundingMode extends Enumeration { + // Annoying boilerplate to ensure consistency with java.math.RoundingMode + type RoundingMode = Value + val UP = Value(JRM.UP.ordinal) + val DOWN = Value(JRM.DOWN.ordinal) + val CEILING = Value(JRM.CEILING.ordinal) + val FLOOR = Value(JRM.FLOOR.ordinal) + val HALF_UP = Value(JRM.HALF_UP.ordinal) + val HALF_DOWN = Value(JRM.HALF_DOWN.ordinal) + val HALF_EVEN = Value(JRM.HALF_EVEN.ordinal) + val UNNECESSARY = Value(JRM.UNNECESSARY.ordinal) + } + + /** Constructs a `BigDecimal` using the decimal text representation of `Double` value `d`, rounding if necessary. */ + def decimal(d: Double, mc: MathContext): BigDecimal = + new BigDecimal(new BigDec(java.lang.Double.toString(d), mc), mc) + + /** Constructs a `BigDecimal` using the decimal text representation of `Double` value `d`. */ + def decimal(d: Double): BigDecimal = decimal(d, defaultMathContext) + + /** Constructs a `BigDecimal` using the decimal text representation of `Float` value `f`, rounding if necessary. + * Note that `BigDecimal.decimal(0.1f) != 0.1f` since equality agrees with the `Double` representation, and + * `0.1 != 0.1f`. + */ + def decimal(f: Float, mc: MathContext): BigDecimal = + new BigDecimal(new BigDec(java.lang.Float.toString(f), mc), mc) + + /** Constructs a `BigDecimal` using the decimal text representation of `Float` value `f`. + * Note that `BigDecimal.decimal(0.1f) != 0.1f` since equality agrees with the `Double` representation, and + * `0.1 != 0.1f`. + */ + def decimal(f: Float): BigDecimal = decimal(f, defaultMathContext) + + // This exists solely to avoid conversion from Int/Long to Float, screwing everything up. + /** Constructs a `BigDecimal` from a `Long`, rounding if necessary. This is identical to `BigDecimal(l, mc)`. */ + def decimal(l: Long, mc: MathContext): BigDecimal = apply(l, mc) + + // This exists solely to avoid conversion from Int/Long to Float, screwing everything up. + /** Constructs a `BigDecimal` from a `Long`. This is identical to `BigDecimal(l)`. */ + def decimal(l: Long): BigDecimal = apply(l) + + /** Constructs a `BigDecimal` using a `java.math.BigDecimal`, rounding if necessary. */ + def decimal(bd: BigDec, mc: MathContext): BigDecimal = new BigDecimal(bd.round(mc), mc) + + /** Constructs a `BigDecimal` by expanding the binary fraction + * contained by `Double` value `d` into a decimal representation, + * rounding if necessary. When a `Float` is converted to a + * `Double`, the binary fraction is preserved, so this method + * also works for converted `Float`s. + */ + def binary(d: Double, mc: MathContext): BigDecimal = new BigDecimal(new BigDec(d, mc), mc) + + /** Constructs a `BigDecimal` by expanding the binary fraction + * contained by `Double` value `d` into a decimal representation. + * Note: this also works correctly on converted `Float`s. + */ + def binary(d: Double): BigDecimal = binary(d, defaultMathContext) + + /** Constructs a `BigDecimal` from a `java.math.BigDecimal`. The + * precision is the default for `BigDecimal` or enough to represent + * the `java.math.BigDecimal` exactly, whichever is greater. + */ + def exact(repr: BigDec): BigDecimal = { + val mc = + if (repr.precision <= defaultMathContext.getPrecision) defaultMathContext + else new MathContext(repr.precision, java.math.RoundingMode.HALF_EVEN) + new BigDecimal(repr, mc) + } + + /** Constructs a `BigDecimal` by fully expanding the binary fraction + * contained by `Double` value `d`, adjusting the precision as + * necessary. Note: this works correctly on converted `Float`s also. + */ + def exact(d: Double): BigDecimal = exact(new BigDec(d)) + + /** Constructs a `BigDecimal` that exactly represents a `BigInt`. + */ + def exact(bi: BigInt): BigDecimal = exact(new BigDec(bi.bigInteger)) + + /** Constructs a `BigDecimal` that exactly represents a `Long`. Note that + * all creation methods for `BigDecimal` that do not take a `MathContext` + * represent a `Long`; this is equivalent to `apply`, `valueOf`, etc.. + */ + def exact(l: Long): BigDecimal = apply(l) + + /** Constructs a `BigDecimal` that exactly represents the number + * specified in a `String`. + */ + def exact(s: String): BigDecimal = exact(new BigDec(s)) + + /** Constructs a `BigDecimal` that exactly represents the number + * specified in base 10 in a character array. + */ + def exact(cs: Array[Char]): BigDecimal = exact(new BigDec(cs)) + + + /** Constructs a `BigDecimal` using the java BigDecimal static + * valueOf constructor. Equivalent to `BigDecimal.decimal`. + * + * @param d the specified double value + * @return the constructed `BigDecimal` + */ + def valueOf(d: Double): BigDecimal = apply(BigDec valueOf d) + + /** Constructs a `BigDecimal` using the java BigDecimal static + * valueOf constructor. + * + * @param x the specified `Long` value + * @return the constructed `BigDecimal` + */ + def valueOf(x: Long): BigDecimal = apply(x) + + /** Constructs a `BigDecimal` whose value is equal to that of the + * specified `Integer` value. + * + * @param i the specified integer value + * @return the constructed `BigDecimal` + */ + def apply(i: Int): BigDecimal = apply(i, defaultMathContext) + + /** Constructs a `BigDecimal` whose value is equal to that of the + * specified `Integer` value, rounding if necessary. + * + * @param i the specified integer value + * @param mc the precision and rounding mode for creation of this value and future operations on it + * @return the constructed `BigDecimal` + */ + def apply(i: Int, mc: MathContext): BigDecimal = + if (mc == defaultMathContext && minCached <= i && i <= maxCached) { + val offset = i - minCached + var n = cache(offset) + if (n eq null) { n = new BigDecimal(BigDec.valueOf(i.toLong), mc); cache(offset) = n } + n + } + else apply(i.toLong, mc) + + /** Constructs a `BigDecimal` whose value is equal to that of the + * specified long value. + * + * @param l the specified long value + * @return the constructed `BigDecimal` + */ + def apply(l: Long): BigDecimal = + if (minCached <= l && l <= maxCached) apply(l.toInt) + else new BigDecimal(BigDec.valueOf(l), defaultMathContext) + + /** Constructs a `BigDecimal` whose value is equal to that of the + * specified long value, but rounded if necessary. + * + * @param l the specified long value + * @param mc the precision and rounding mode for creation of this value and future operations on it + * @return the constructed `BigDecimal` + */ + def apply(l: Long, mc: MathContext): BigDecimal = + new BigDecimal(new BigDec(l, mc), mc) + + /** Constructs a `BigDecimal` whose unscaled value is equal to that + * of the specified long value. + * + * @param unscaledVal the value + * @param scale the scale + * @return the constructed `BigDecimal` + */ + def apply(unscaledVal: Long, scale: Int): BigDecimal = + apply(BigInt(unscaledVal), scale) + + /** Constructs a `BigDecimal` whose unscaled value is equal to that + * of the specified long value, but rounded if necessary. + * + * @param unscaledVal the value + * @param scale the scale + * @param mc the precision and rounding mode for creation of this value and future operations on it + * @return the constructed `BigDecimal` + */ + def apply(unscaledVal: Long, scale: Int, mc: MathContext): BigDecimal = + apply(BigInt(unscaledVal), scale, mc) + + /** Constructs a `BigDecimal` whose value is equal to that of the + * specified double value. Equivalent to `BigDecimal.decimal`. + * + * @param d the specified `Double` value + * @return the constructed `BigDecimal` + */ + def apply(d: Double): BigDecimal = decimal(d, defaultMathContext) + + // note we don't use the static valueOf because it doesn't let us supply + // a MathContext, but we should be duplicating its logic, modulo caching. + /** Constructs a `BigDecimal` whose value is equal to that of the + * specified double value, but rounded if necessary. Equivalent to + * `BigDecimal.decimal`. + * + * @param d the specified `Double` value + * @param mc the precision and rounding mode for creation of this value and future operations on it + * @return the constructed `BigDecimal` + */ + def apply(d: Double, mc: MathContext): BigDecimal = decimal(d, mc) + + /** Translates a character array representation of a `BigDecimal` + * into a `BigDecimal`. + */ + def apply(x: Array[Char]): BigDecimal = exact(x) + + /** Translates a character array representation of a `BigDecimal` + * into a `BigDecimal`, rounding if necessary. + */ + def apply(x: Array[Char], mc: MathContext): BigDecimal = + new BigDecimal(new BigDec(x, mc), mc) + + /** Translates the decimal String representation of a `BigDecimal` + * into a `BigDecimal`. + */ + def apply(x: String): BigDecimal = exact(x) + + /** Translates the decimal String representation of a `BigDecimal` + * into a `BigDecimal`, rounding if necessary. + */ + def apply(x: String, mc: MathContext): BigDecimal = + new BigDecimal(new BigDec(x, mc), mc) + + /** Constructs a `BigDecimal` whose value is equal to that of the + * specified `BigInt` value. + * + * @param x the specified `BigInt` value + * @return the constructed `BigDecimal` + */ + def apply(x: BigInt): BigDecimal = exact(x) + + /** Constructs a `BigDecimal` whose value is equal to that of the + * specified `BigInt` value, rounding if necessary. + * + * @param x the specified `BigInt` value + * @param mc the precision and rounding mode for creation of this value and future operations on it + * @return the constructed `BigDecimal` + */ + def apply(x: BigInt, mc: MathContext): BigDecimal = + new BigDecimal(new BigDec(x.bigInteger, mc), mc) + + /** Constructs a `BigDecimal` whose unscaled value is equal to that + * of the specified `BigInt` value. + * + * @param unscaledVal the specified `BigInt` value + * @param scale the scale + * @return the constructed `BigDecimal` + */ + def apply(unscaledVal: BigInt, scale: Int): BigDecimal = + exact(new BigDec(unscaledVal.bigInteger, scale)) + + /** Constructs a `BigDecimal` whose unscaled value is equal to that + * of the specified `BigInt` value. + * + * @param unscaledVal the specified `BigInt` value + * @param scale the scale + * @param mc the precision and rounding mode for creation of this value and future operations on it + * @return the constructed `BigDecimal` + */ + def apply(unscaledVal: BigInt, scale: Int, mc: MathContext): BigDecimal = + new BigDecimal(new BigDec(unscaledVal.bigInteger, scale, mc), mc) + + /** Constructs a `BigDecimal` from a `java.math.BigDecimal`. */ + def apply(bd: BigDec): BigDecimal = new BigDecimal(bd, defaultMathContext) + + /** Implicit conversion from `Int` to `BigDecimal`. */ + implicit def int2bigDecimal(i: Int): BigDecimal = apply(i) + + /** Implicit conversion from `Long` to `BigDecimal`. */ + implicit def long2bigDecimal(l: Long): BigDecimal = apply(l) + + /** Implicit conversion from `Double` to `BigDecimal`. */ + implicit def double2bigDecimal(d: Double): BigDecimal = decimal(d) + + /** Implicit conversion from `java.math.BigDecimal` to `scala.BigDecimal`. */ + implicit def javaBigDecimal2bigDecimal(x: BigDec): BigDecimal = if (x == null) null else apply(x) +} + +/** + * `BigDecimal` represents decimal floating-point numbers of arbitrary precision. + * By default, the precision approximately matches that of IEEE 128-bit floating + * point numbers (34 decimal digits, `HALF_EVEN` rounding mode). Within the range + * of IEEE binary128 numbers, `BigDecimal` will agree with `BigInt` for both + * equality and hash codes (and will agree with primitive types as well). Beyond + * that range--numbers with more than 4934 digits when written out in full--the + * `hashCode` of `BigInt` and `BigDecimal` is allowed to diverge due to difficulty + * in efficiently computing both the decimal representation in `BigDecimal` and the + * binary representation in `BigInt`. + * + * When creating a `BigDecimal` from a `Double` or `Float`, care must be taken as + * the binary fraction representation of `Double` and `Float` does not easily + * convert into a decimal representation. Three explicit schemes are available + * for conversion. `BigDecimal.decimal` will convert the floating-point number + * to a decimal text representation, and build a `BigDecimal` based on that. + * `BigDecimal.binary` will expand the binary fraction to the requested or default + * precision. `BigDecimal.exact` will expand the binary fraction to the + * full number of digits, thus producing the exact decimal value corresponding to + * the binary fraction of that floating-point number. `BigDecimal` equality + * matches the decimal expansion of `Double`: `BigDecimal.decimal(0.1) == 0.1`. + * Note that since `0.1f != 0.1`, the same is not true for `Float`. Instead, + * `0.1f == BigDecimal.decimal((0.1f).toDouble)`. + * + * To test whether a `BigDecimal` number can be converted to a `Double` or + * `Float` and then back without loss of information by using one of these + * methods, test with `isDecimalDouble`, `isBinaryDouble`, or `isExactDouble` + * or the corresponding `Float` versions. Note that `BigInt`'s `isValidDouble` + * will agree with `isExactDouble`, not the `isDecimalDouble` used by default. + * + * `BigDecimal` uses the decimal representation of binary floating-point numbers + * to determine equality and hash codes. This yields different answers than + * conversion between `Long` and `Double` values, where the exact form is used. + * As always, since floating-point is a lossy representation, it is advisable to + * take care when assuming identity will be maintained across multiple conversions. + * + * `BigDecimal` maintains a `MathContext` that determines the rounding that + * is applied to certain calculations. In most cases, the value of the + * `BigDecimal` is also rounded to the precision specified by the `MathContext`. + * To create a `BigDecimal` with a different precision than its `MathContext`, + * use `new BigDecimal(new java.math.BigDecimal(...), mc)`. Rounding will + * be applied on those mathematical operations that can dramatically change the + * number of digits in a full representation, namely multiplication, division, + * and powers. The left-hand argument's `MathContext` always determines the + * degree of rounding, if any, and is the one propagated through arithmetic + * operations that do not apply rounding themselves. + */ +final class BigDecimal(val bigDecimal: BigDec, val mc: MathContext) +extends ScalaNumber with ScalaNumericConversions with Serializable with Ordered[BigDecimal] { + def this(bigDecimal: BigDec) = this(bigDecimal, BigDecimal.defaultMathContext) + import BigDecimal.RoundingMode._ + import BigDecimal.{decimal, binary, exact} + + if (bigDecimal eq null) throw new IllegalArgumentException("null value for BigDecimal") + if (mc eq null) throw new IllegalArgumentException("null MathContext for BigDecimal") + + // There was an implicit to cut down on the wrapper noise for BigDec -> BigDecimal. + // However, this may mask introduction of surprising behavior (e.g. lack of rounding + // where one might expect it). Wrappers should be applied explicitly with an + // eye to correctness. + + // Sane hash code computation (which is surprisingly hard). + // Note--not lazy val because we can't afford the extra space. + private final var computedHashCode: Int = BigDecimal.hashCodeNotComputed + private final def computeHashCode(): Unit = { + computedHashCode = + if (isWhole && (precision - scale) < BigDecimal.maximumHashScale) toBigInt.hashCode + else if (isDecimalDouble) doubleValue.## + else { + val temp = bigDecimal.stripTrailingZeros + scala.util.hashing.MurmurHash3.mixLast( temp.scaleByPowerOfTen(temp.scale).toBigInteger.hashCode, temp.scale ) + } + } + + /** Returns the hash code for this BigDecimal. + * Note that this does not merely use the underlying java object's + * `hashCode` because we compare `BigDecimal`s with `compareTo` + * which deems 2 == 2.00, whereas in java these are unequal + * with unequal `hashCode`s. These hash codes agree with `BigInt` + * for whole numbers up ~4934 digits (the range of IEEE 128 bit floating + * point). Beyond this, hash codes will disagree; this prevents the + * explicit representation of the `BigInt` form for `BigDecimal` values + * with large exponents. + */ + override def hashCode(): Int = { + if (computedHashCode == BigDecimal.hashCodeNotComputed) computeHashCode() + computedHashCode + } + + /** Compares this BigDecimal with the specified value for equality. Where `Float` and `Double` + * disagree, `BigDecimal` will agree with the `Double` value + */ + override def equals (that: Any): Boolean = that match { + case that: BigDecimal => this equals that + case that: BigInt => + that.bitLength > (precision-scale-2)*BigDecimal.deci2binary && + this.toBigIntExact.exists(that equals _) + case that: Double => + !that.isInfinity && { + val d = toDouble + !d.isInfinity && d == that && equals(decimal(d)) + } + case that: Float => + !that.isInfinity && { + val f = toFloat + !f.isInfinity && f == that && equals(decimal(f.toDouble)) + } + case _ => isValidLong && unifiedPrimitiveEquals(that) + } + override def isValidByte = noArithmeticException(toByteExact) + override def isValidShort = noArithmeticException(toShortExact) + override def isValidChar = isValidInt && toIntExact >= Char.MinValue && toIntExact <= Char.MaxValue + override def isValidInt = noArithmeticException(toIntExact) + def isValidLong = noArithmeticException(toLongExact) + + /** Tests whether this `BigDecimal` holds the decimal representation of a `Double`. */ + def isDecimalDouble = { + val d = toDouble + !d.isInfinity && equals(decimal(d)) + } + + /** Tests whether this `BigDecimal` holds the decimal representation of a `Float`. */ + def isDecimalFloat = { + val f = toFloat + !f.isInfinity && equals(decimal(f)) + } + + /** Tests whether this `BigDecimal` holds, to within precision, the binary representation of a `Double`. */ + def isBinaryDouble = { + val d = toDouble + !d.isInfinity && equals(binary(d,mc)) + } + + /** Tests whether this `BigDecimal` holds, to within precision, the binary representation of a `Float`. */ + def isBinaryFloat = { + val f = toFloat + !f.isInfinity && equals(binary(f,mc)) + } + + /** Tests whether this `BigDecimal` holds the exact expansion of a `Double`'s binary fractional form into base 10. */ + def isExactDouble = { + val d = toDouble + !d.isInfinity && equals(exact(d)) + } + + /** Tests whether this `BigDecimal` holds the exact expansion of a `Float`'s binary fractional form into base 10. */ + def isExactFloat = { + val f = toFloat + !f.isInfinity && equals(exact(f.toDouble)) + } + + + private def noArithmeticException(body: => Unit): Boolean = { + try { body ; true } + catch { case _: ArithmeticException => false } + } + + def isWhole = scale <= 0 || bigDecimal.stripTrailingZeros.scale <= 0 + + def underlying: java.math.BigDecimal = bigDecimal + + + /** Compares this BigDecimal with the specified BigDecimal for equality. + */ + def equals (that: BigDecimal): Boolean = compare(that) == 0 + + /** Compares this BigDecimal with the specified BigDecimal + */ + def compare (that: BigDecimal): Int = this.bigDecimal compareTo that.bigDecimal + + /** Addition of BigDecimals + */ + def + (that: BigDecimal): BigDecimal = new BigDecimal(this.bigDecimal.add(that.bigDecimal, mc), mc) + + /** Subtraction of BigDecimals + */ + def - (that: BigDecimal): BigDecimal = new BigDecimal(this.bigDecimal.subtract(that.bigDecimal, mc), mc) + + /** Multiplication of BigDecimals + */ + def * (that: BigDecimal): BigDecimal = new BigDecimal(this.bigDecimal.multiply(that.bigDecimal, mc), mc) + + /** Division of BigDecimals + */ + def / (that: BigDecimal): BigDecimal = new BigDecimal(this.bigDecimal.divide(that.bigDecimal, mc), mc) + + /** Division and Remainder - returns tuple containing the result of + * divideToIntegralValue and the remainder. The computation is exact: no rounding is applied. + */ + def /% (that: BigDecimal): (BigDecimal, BigDecimal) = { + val qr = this.bigDecimal.divideAndRemainder(that.bigDecimal, mc) + (new BigDecimal(qr(0), mc), new BigDecimal(qr(1), mc)) + } + + /** Divide to Integral value. + */ + def quot (that: BigDecimal): BigDecimal = + new BigDecimal(this.bigDecimal.divideToIntegralValue(that.bigDecimal, mc), mc) + + /** Returns the minimum of this and that, or this if the two are equal + */ + def min (that: BigDecimal): BigDecimal = (this compare that) match { + case x if x <= 0 => this + case _ => that + } + + /** Returns the maximum of this and that, or this if the two are equal + */ + def max (that: BigDecimal): BigDecimal = (this compare that) match { + case x if x >= 0 => this + case _ => that + } + + /** Remainder after dividing this by that. + */ + def remainder (that: BigDecimal): BigDecimal = new BigDecimal(this.bigDecimal.remainder(that.bigDecimal, mc), mc) + + /** Remainder after dividing this by that. + */ + def % (that: BigDecimal): BigDecimal = this.remainder(that) + + /** Returns a BigDecimal whose value is this ** n. + */ + def pow (n: Int): BigDecimal = new BigDecimal(this.bigDecimal.pow(n, mc), mc) + + /** Returns a BigDecimal whose value is the negation of this BigDecimal + */ + def unary_- : BigDecimal = new BigDecimal(this.bigDecimal.negate(mc), mc) + + /** Returns the absolute value of this BigDecimal + */ + def abs: BigDecimal = if (signum < 0) unary_- else this + + /** Returns the sign of this BigDecimal; + * -1 if it is less than 0, + * +1 if it is greater than 0, + * 0 if it is equal to 0. + */ + def signum: Int = this.bigDecimal.signum() + + /** Returns the sign of this BigDecimal; + * -1 if it is less than 0, + * +1 if it is greater than 0, + * 0 if it is equal to 0. + */ + def sign: BigDecimal = signum + + /** Returns the precision of this `BigDecimal`. + */ + def precision: Int = this.bigDecimal.precision + + /** Returns a BigDecimal rounded according to the supplied MathContext settings, but + * preserving its own MathContext for future operations. + */ + def round(mc: MathContext): BigDecimal = { + val r = this.bigDecimal round mc + if (r eq bigDecimal) this else new BigDecimal(r, this.mc) + } + + /** Returns a `BigDecimal` rounded according to its own `MathContext` */ + def rounded: BigDecimal = { + val r = bigDecimal round mc + if (r eq bigDecimal) this else new BigDecimal(r, mc) + } + + /** Returns the scale of this `BigDecimal`. + */ + def scale: Int = this.bigDecimal.scale + + /** Returns the size of an ulp, a unit in the last place, of this BigDecimal. + */ + def ulp: BigDecimal = new BigDecimal(this.bigDecimal.ulp, mc) + + /** Returns a new BigDecimal based on the supplied MathContext, rounded as needed. + */ + def apply(mc: MathContext): BigDecimal = new BigDecimal(this.bigDecimal round mc, mc) + + /** Returns a `BigDecimal` whose scale is the specified value, and whose value is + * numerically equal to this BigDecimal's. + */ + def setScale(scale: Int): BigDecimal = + if (this.scale == scale) this + else new BigDecimal(this.bigDecimal.setScale(scale), mc) + + def setScale(scale: Int, mode: RoundingMode): BigDecimal = + if (this.scale == scale) this + else new BigDecimal(this.bigDecimal.setScale(scale, JRM.valueOf(mode.id)), mc) + + /** Converts this BigDecimal to a Byte. + * If the BigDecimal is too big to fit in a Byte, only the low-order 8 bits are returned. + * Note that this conversion can lose information about the overall magnitude of the + * BigDecimal value as well as return a result with the opposite sign. + */ + override def byteValue = intValue.toByte + + /** Converts this BigDecimal to a Short. + * If the BigDecimal is too big to fit in a Short, only the low-order 16 bits are returned. + * Note that this conversion can lose information about the overall magnitude of the + * BigDecimal value as well as return a result with the opposite sign. + */ + override def shortValue = intValue.toShort + + /** Converts this BigDecimal to a Char. + * If the BigDecimal is too big to fit in a Char, only the low-order 16 bits are returned. + * Note that this conversion can lose information about the overall magnitude of the + * BigDecimal value and that it always returns a positive result. + */ + def charValue = intValue.toChar + + /** Converts this BigDecimal to an Int. + * If the BigDecimal is too big to fit in an Int, only the low-order 32 bits + * are returned. Note that this conversion can lose information about the + * overall magnitude of the BigDecimal value as well as return a result with + * the opposite sign. + */ + def intValue = this.bigDecimal.intValue + + /** Converts this BigDecimal to a Long. + * If the BigDecimal is too big to fit in a Long, only the low-order 64 bits + * are returned. Note that this conversion can lose information about the + * overall magnitude of the BigDecimal value as well as return a result with + * the opposite sign. + */ + def longValue = this.bigDecimal.longValue + + /** Converts this BigDecimal to a Float. + * if this BigDecimal has too great a magnitude to represent as a float, + * it will be converted to `Float.NEGATIVE_INFINITY` or + * `Float.POSITIVE_INFINITY` as appropriate. + */ + def floatValue = this.bigDecimal.floatValue + + /** Converts this BigDecimal to a Double. + * if this BigDecimal has too great a magnitude to represent as a double, + * it will be converted to `Double.NEGATIVE_INFINITY` or + * `Double.POSITIVE_INFINITY` as appropriate. + */ + def doubleValue = this.bigDecimal.doubleValue + + /** Converts this `BigDecimal` to a [[scala.Byte]], checking for lost information. + * If this `BigDecimal` has a nonzero fractional part, or is out of the possible + * range for a [[scala.Byte]] result, then a `java.lang.ArithmeticException` is + * thrown. + */ + def toByteExact = bigDecimal.byteValueExact + + /** Converts this `BigDecimal` to a [[scala.Short]], checking for lost information. + * If this `BigDecimal` has a nonzero fractional part, or is out of the possible + * range for a [[scala.Short]] result, then a `java.lang.ArithmeticException` is + * thrown. + */ + def toShortExact = bigDecimal.shortValueExact + + /** Converts this `BigDecimal` to a [[scala.Int]], checking for lost information. + * If this `BigDecimal` has a nonzero fractional part, or is out of the possible + * range for an [[scala.Int]] result, then a `java.lang.ArithmeticException` is + * thrown. + */ + def toIntExact = bigDecimal.intValueExact + + /** Converts this `BigDecimal` to a [[scala.Long]], checking for lost information. + * If this `BigDecimal` has a nonzero fractional part, or is out of the possible + * range for a [[scala.Long]] result, then a `java.lang.ArithmeticException` is + * thrown. + */ + def toLongExact = bigDecimal.longValueExact + + /** Creates a partially constructed NumericRange[BigDecimal] in range + * `[start;end)`, where start is the target BigDecimal. The step + * must be supplied via the "by" method of the returned object in order + * to receive the fully constructed range. For example: + * {{{ + * val partial = BigDecimal(1.0) to 2.0 // not usable yet + * val range = partial by 0.01 // now a NumericRange + * val range2 = BigDecimal(0) to 1.0 by 0.01 // all at once of course is fine too + * }}} + * + * @param end the end value of the range (exclusive) + * @return the partially constructed NumericRange + */ + def until(end: BigDecimal): Range.Partial[BigDecimal, NumericRange.Exclusive[BigDecimal]] = + new Range.Partial(until(end, _)) + + /** Same as the one-argument `until`, but creates the range immediately. */ + def until(end: BigDecimal, step: BigDecimal): NumericRange.Exclusive[BigDecimal] = Range.BigDecimal(this, end, step) + + /** Like `until`, but inclusive of the end value. */ + def to(end: BigDecimal): Range.Partial[BigDecimal, NumericRange.Inclusive[BigDecimal]] = + new Range.Partial(to(end, _)) + + /** Like `until`, but inclusive of the end value. */ + def to(end: BigDecimal, step: BigDecimal) = Range.BigDecimal.inclusive(this, end, step) + + /** Converts this `BigDecimal` to a scala.BigInt. + */ + def toBigInt: BigInt = new BigInt(this.bigDecimal.toBigInteger) + + /** Converts this `BigDecimal` to a scala.BigInt if it + * can be done losslessly, returning Some(BigInt) or None. + */ + def toBigIntExact: Option[BigInt] = + if (isWhole) { + try Some(new BigInt(this.bigDecimal.toBigIntegerExact)) + catch { case _: ArithmeticException => None } + } + else None + + /** Returns the decimal String representation of this BigDecimal. + */ + override def toString: String = this.bigDecimal.toString + +} diff --git a/library/src/scala/math/BigInt.scala b/library/src/scala/math/BigInt.scala new file mode 100644 index 000000000000..cf7c45d5682a --- /dev/null +++ b/library/src/scala/math/BigInt.scala @@ -0,0 +1,639 @@ +/* + * 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 math + +import java.math.BigInteger + +import scala.language.`2.13` +import scala.annotation.nowarn +import scala.language.implicitConversions +import scala.collection.immutable.NumericRange + +object BigInt { + + private val longMinValueBigInteger = BigInteger.valueOf(Long.MinValue) + private val longMinValue = new BigInt(longMinValueBigInteger, Long.MinValue) + + private[this] val minCached = -1024 + private[this] val maxCached = 1024 + private[this] val cache = new Array[BigInt](maxCached - minCached + 1) + + private[this] def getCached(i: Int): BigInt = { + val offset = i - minCached + var n = cache(offset) + if (n eq null) { + n = new BigInt(null, i.toLong) + cache(offset) = n + } + n + } + + private val minusOne = BigInteger.valueOf(-1) + + /** Constructs a `BigInt` whose value is equal to that of the + * specified integer value. + * + * @param i the specified integer value + * @return the constructed `BigInt` + */ + def apply(i: Int): BigInt = + if (minCached <= i && i <= maxCached) getCached(i) else apply(i: Long) + + /** Constructs a `BigInt` whose value is equal to that of the + * specified long value. + * + * @param l the specified long value + * @return the constructed `BigInt` + */ + def apply(l: Long): BigInt = + if (minCached <= l && l <= maxCached) getCached(l.toInt) + else if (l == Long.MinValue) longMinValue + else new BigInt(null, l) + + /** Translates a byte array containing the two's-complement binary + * representation of a BigInt into a BigInt. + */ + def apply(x: Array[Byte]): BigInt = + apply(new BigInteger(x)) + + /** Translates the sign-magnitude representation of a BigInt into a BigInt. + * + * @param signum signum of the number (-1 for negative, 0 for zero, 1 + * for positive). + * @param magnitude big-endian binary representation of the magnitude of + * the number. + */ + def apply(signum: Int, magnitude: Array[Byte]): BigInt = + apply(new BigInteger(signum, magnitude)) + + /** Constructs a randomly generated positive BigInt that is probably prime, + * with the specified bitLength. + */ + def apply(bitlength: Int, certainty: Int, rnd: scala.util.Random): BigInt = + apply(new BigInteger(bitlength, certainty, rnd.self)) + + /** Constructs a randomly generated BigInt, uniformly distributed over the + * range `0` to `(2 ^ numBits - 1)`, inclusive. + */ + def apply(numbits: Int, rnd: scala.util.Random): BigInt = + apply(new BigInteger(numbits, rnd.self)) + + /** Translates the decimal String representation of a BigInt into a BigInt. + */ + def apply(x: String): BigInt = + apply(new BigInteger(x)) + + /** Translates the string representation of a `BigInt` in the + * specified `radix` into a BigInt. + */ + def apply(x: String, radix: Int): BigInt = + apply(new BigInteger(x, radix)) + + /** Translates a `java.math.BigInteger` into a BigInt. + */ + def apply(x: BigInteger): BigInt = { + if (x.bitLength <= 63) { + val l = x.longValue + if (minCached <= l && l <= maxCached) getCached(l.toInt) else new BigInt(x, l) + } else new BigInt(x, Long.MinValue) + } + + /** Returns a positive BigInt that is probably prime, with the specified bitLength. + */ + def probablePrime(bitLength: Int, rnd: scala.util.Random): BigInt = + apply(BigInteger.probablePrime(bitLength, rnd.self)) + + /** Implicit conversion from `Int` to `BigInt`. + */ + implicit def int2bigInt(i: Int): BigInt = apply(i) + + /** Implicit conversion from `Long` to `BigInt`. + */ + implicit def long2bigInt(l: Long): BigInt = apply(l) + + /** Implicit conversion from `java.math.BigInteger` to `scala.BigInt`. + */ + implicit def javaBigInteger2bigInt(x: BigInteger): BigInt = if (x eq null) null else apply(x) + + // this method is adapted from Google Guava's version at + // https://github.com/google/guava/blob/master/guava/src/com/google/common/math/LongMath.java + // that code carries the following notice: + // * Copyright (C) 2011 The Guava Authors + // * + // * Licensed under the Apache License, Version 2.0 (the "License") + /** + * Returns the greatest common divisor of a and b. Returns 0 if a == 0 && b == 0. + */ + private def longGcd(a: Long, b: Long): Long = { + // both a and b must be >= 0 + if (a == 0) { // 0 % b == 0, so b divides a, but the converse doesn't hold. + // BigInteger.gcd is consistent with this decision. + return b + } + else if (b == 0) return a // similar logic + /* + * Uses the binary GCD algorithm; see http://en.wikipedia.org/wiki/Binary_GCD_algorithm. This is + * >60% faster than the Euclidean algorithm in benchmarks. + */ + val aTwos = java.lang.Long.numberOfTrailingZeros(a) + var a1 = a >> aTwos // divide out all 2s + + val bTwos = java.lang.Long.numberOfTrailingZeros(b) + var b1 = b >> bTwos + while (a1 != b1) { // both a, b are odd + // The key to the binary GCD algorithm is as follows: + // Both a1 and b1 are odd. Assume a1 > b1; then gcd(a1 - b1, b1) = gcd(a1, b1). + // But in gcd(a1 - b1, b1), a1 - b1 is even and b1 is odd, so we can divide out powers of two. + // We bend over backwards to avoid branching, adapting a technique from + // http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax + val delta = a1 - b1 // can't overflow, since a1 and b1 are nonnegative + val minDeltaOrZero = delta & (delta >> (java.lang.Long.SIZE - 1)) + // equivalent to Math.min(delta, 0) + a1 = delta - minDeltaOrZero - minDeltaOrZero // sets a to Math.abs(a - b) + + // a is now nonnegative and even + b1 += minDeltaOrZero // sets b to min(old a, b) + + a1 >>= java.lang.Long.numberOfTrailingZeros(a1) // divide out all 2s, since 2 doesn't divide b + + } + a1 << scala.math.min(aTwos, bTwos) + } + +} + +/** A type with efficient encoding of arbitrary integers. + * + * It wraps `java.math.BigInteger`, with optimization for small values that can be encoded in a `Long`. + */ +final class BigInt private (private var _bigInteger: BigInteger, private val _long: Long) + extends ScalaNumber + with ScalaNumericConversions + with Serializable + with Ordered[BigInt] +{ + // The class has a special encoding for integer that fit in a Long *and* are not equal to Long.MinValue. + // + // The Long value Long.MinValue is a tag specifying that the integer is encoded in the BigInteger field. + // + // There are three possible states for the class fields (_bigInteger, _long) + // 1. (null, l) where l != Long.MinValue, encodes the integer "l" + // 2. (b, l) where l != Long.MinValue; then b is a BigInteger with value l, encodes "l" == "b" + // 3a. (b, Long.MinValue) where b == Long.MinValue, encodes Long.MinValue + // 3b. (b, Long.MinValue) where b does not fit in a Long, encodes "b" + // + // There is only one possible transition 1. -> 2., when the method .bigInteger is called, then the field + // _bigInteger caches the result. + // + // The case 3a. is the only one where the BigInteger could actually fit in a Long, but as its value is used as a + // tag, we'll take the slow path instead. + // + // Additionally, we know that if this.isValidLong is true, then _long is the encoded value. + + /** Public constructor present for compatibility. Use the BigInt.apply companion object method instead. */ + def this(bigInteger: BigInteger) = this( + bigInteger, // even if it is a short BigInteger, we cache the instance + if (bigInteger.bitLength <= 63) + bigInteger.longValue // if _bigInteger is actually equal to Long.MinValue, no big deal, its value acts as a tag + else Long.MinValue + ) + + /** Returns whether the integer is encoded in the Long. Returns true for all values fitting in a Long except + * Long.MinValue. */ + private def longEncoding: Boolean = _long != Long.MinValue + + def bigInteger: BigInteger = { + val read = _bigInteger + if (read ne null) read else { + val write = BigInteger.valueOf(_long) + _bigInteger = write // reference assignment is atomic; this is multi-thread safe (if possibly wasteful) + write + } + } + + /** Returns the hash code for this BigInt. */ + override def hashCode(): Int = + if (isValidLong) unifiedPrimitiveHashcode + else bigInteger.## + + /** Compares this BigInt with the specified value for equality. */ + @nowarn("cat=other-non-cooperative-equals") + override def equals(that: Any): Boolean = that match { + case that: BigInt => this equals that + case that: BigDecimal => that equals this + case that: Double => isValidDouble && toDouble == that + case that: Float => isValidFloat && toFloat == that + case x => isValidLong && unifiedPrimitiveEquals(x) + } + + override def isValidByte: Boolean = _long >= Byte.MinValue && _long <= Byte.MaxValue /* && longEncoding */ + override def isValidShort: Boolean = _long >= Short.MinValue && _long <= Short.MaxValue /* && longEncoding */ + override def isValidChar: Boolean = _long >= Char.MinValue && _long <= Char.MaxValue /* && longEncoding */ + override def isValidInt: Boolean = _long >= Int.MinValue && _long <= Int.MaxValue /* && longEncoding */ + def isValidLong: Boolean = longEncoding || _bigInteger == BigInt.longMinValueBigInteger // rhs of || tests == Long.MinValue + + /** Returns `true` iff this can be represented exactly by [[scala.Float]]; otherwise returns `false`. + */ + def isValidFloat: Boolean = { + val bitLen = bitLength + (bitLen <= 24 || + { + val lowest = lowestSetBit + bitLen <= java.lang.Float.MAX_EXPONENT + 1 && // exclude this < -2^128 && this >= 2^128 + lowest >= bitLen - 24 && + lowest < java.lang.Float.MAX_EXPONENT + 1 // exclude this == -2^128 + } + ) && !bitLengthOverflow + } + /** Returns `true` iff this can be represented exactly by [[scala.Double]]; otherwise returns `false`. + */ + def isValidDouble: Boolean = { + val bitLen = bitLength + (bitLen <= 53 || + { + val lowest = lowestSetBit + bitLen <= java.lang.Double.MAX_EXPONENT + 1 && // exclude this < -2^1024 && this >= 2^1024 + lowest >= bitLen - 53 && + lowest < java.lang.Double.MAX_EXPONENT + 1 // exclude this == -2^1024 + } + ) && !bitLengthOverflow + } + /** Some implementations of java.math.BigInteger allow huge values with bit length greater than Int.MaxValue. + * The BigInteger.bitLength method returns truncated bit length in this case. + * This method tests if result of bitLength is valid. + * This method will become unnecessary if BigInt constructors reject huge BigIntegers. + */ + private def bitLengthOverflow = { + val shifted = bigInteger.shiftRight(Int.MaxValue) + (shifted.signum != 0) && !(shifted equals BigInt.minusOne) + } + + @deprecated("isWhole on an integer type is always true", "2.12.15") + def isWhole: Boolean = true + def underlying: BigInteger = bigInteger + + /** Compares this BigInt with the specified BigInt for equality. + */ + def equals(that: BigInt): Boolean = + if (this.longEncoding) + that.longEncoding && (this._long == that._long) + else + !that.longEncoding && (this._bigInteger == that._bigInteger) + + /** Compares this BigInt with the specified BigInt + */ + def compare(that: BigInt): Int = + if (this.longEncoding) { + if (that.longEncoding) java.lang.Long.compare(this._long, that._long) else -that._bigInteger.signum() + } else { + if (that.longEncoding) _bigInteger.signum() else this._bigInteger.compareTo(that._bigInteger) + } + + /** Addition of BigInts + */ + def +(that: BigInt): BigInt = { + if (this.longEncoding && that.longEncoding) { // fast path + val x = this._long + val y = that._long + val z = x + y + if ((~(x ^ y) & (x ^ z)) >= 0L) return BigInt(z) + } + BigInt(this.bigInteger.add(that.bigInteger)) + } + + /** Subtraction of BigInts + */ + def -(that: BigInt): BigInt = { + if (this.longEncoding && that.longEncoding) { // fast path + val x = this._long + val y = that._long + val z = x - y + if (((x ^ y) & (x ^ z)) >= 0L) return BigInt(z) + } + BigInt(this.bigInteger.subtract(that.bigInteger)) + } + + /** Multiplication of BigInts + */ + def *(that: BigInt): BigInt = { + if (this.longEncoding && that.longEncoding) { // fast path + val x = this._long + val y = that._long + val z = x * y + // original code checks the y != Long.MinValue, but when longEncoding is true, that is never the case + // if (x == 0 || (y == z / x && !(x == -1 && y == Long.MinValue))) return BigInt(z) + if (x == 0 || y == z / x) return BigInt(z) + } + BigInt(this.bigInteger.multiply(that.bigInteger)) + } + + /** Division of BigInts + */ + def /(that: BigInt): BigInt = + // in the fast path, note that the original code avoided storing -Long.MinValue in a long: + // if (this._long != Long.MinValue || that._long != -1) return BigInt(this._long / that._long) + // but we know this._long cannot be Long.MinValue, because Long.MinValue is the tag for bigger integers + if (this.longEncoding && that.longEncoding) BigInt(this._long / that._long) + else BigInt(this.bigInteger.divide(that.bigInteger)) + + /** Remainder of BigInts + */ + def %(that: BigInt): BigInt = + // see / for the original logic regarding Long.MinValue + if (this.longEncoding && that.longEncoding) BigInt(this._long % that._long) + else BigInt(this.bigInteger.remainder(that.bigInteger)) + + /** Returns a pair of two BigInts containing (this / that) and (this % that). + */ + def /%(that: BigInt): (BigInt, BigInt) = + if (this.longEncoding && that.longEncoding) { + val x = this._long + val y = that._long + // original line: if (x != Long.MinValue || y != -1) return (BigInt(x / y), BigInt(x % y)) + (BigInt(x / y), BigInt(x % y)) + } else { + val dr = this.bigInteger.divideAndRemainder(that.bigInteger) + (BigInt(dr(0)), BigInt(dr(1))) + } + + /** Leftshift of BigInt + */ + def <<(n: Int): BigInt = + if (longEncoding && n <= 0) (this >> (-n)) else BigInt(this.bigInteger.shiftLeft(n)) + + /** (Signed) rightshift of BigInt + */ + def >>(n: Int): BigInt = + if (longEncoding && n >= 0) { + if (n < 64) BigInt(_long >> n) + else if (_long < 0) BigInt(-1) + else BigInt(0) // for _long >= 0 + } else BigInt(this.bigInteger.shiftRight(n)) + + /** Bitwise and of BigInts + */ + def &(that: BigInt): BigInt = + if (this.longEncoding && that.longEncoding) + BigInt(this._long & that._long) + else BigInt(this.bigInteger.and(that.bigInteger)) + + /** Bitwise or of BigInts + */ + def |(that: BigInt): BigInt = + if (this.longEncoding && that.longEncoding) + BigInt(this._long | that._long) + else BigInt(this.bigInteger.or(that.bigInteger)) + + /** Bitwise exclusive-or of BigInts + */ + def ^(that: BigInt): BigInt = + if (this.longEncoding && that.longEncoding) + BigInt(this._long ^ that._long) + else BigInt(this.bigInteger.xor(that.bigInteger)) + + /** Bitwise and-not of BigInts. Returns a BigInt whose value is (this & ~that). + */ + def &~(that: BigInt): BigInt = + if (this.longEncoding && that.longEncoding) + BigInt(this._long & ~that._long) + else BigInt(this.bigInteger.andNot(that.bigInteger)) + + /** Returns the greatest common divisor of abs(this) and abs(that) + */ + def gcd(that: BigInt): BigInt = + if (this.longEncoding) { + if (this._long == 0) return that.abs + // if (this._long == Long.MinValue) return (-this) gcd that + // this != 0 && this != Long.MinValue + if (that.longEncoding) { + if (that._long == 0) return this.abs + // if (that._long == Long.MinValue) return this gcd (-that) + BigInt(BigInt.longGcd(this._long.abs, that._long.abs)) + } else that gcd this // force the BigInteger on the left + } else { + // this is not a valid long + if (that.longEncoding) { + if (that._long == 0) return this.abs + // if (that._long == Long.MinValue) return this gcd (-that) + val red = (this._bigInteger mod BigInteger.valueOf(that._long.abs)).longValue() + if (red == 0) return that.abs + BigInt(BigInt.longGcd(that._long.abs, red)) + } else BigInt(this.bigInteger.gcd(that.bigInteger)) + } + + + /** Returns a BigInt whose value is (this mod that). + * This method differs from `%` in that it always returns a non-negative BigInt. + * @param that A positive number + */ + def mod(that: BigInt): BigInt = + if (this.longEncoding && that.longEncoding && that._long > 0) { + val res = this._long % that._long + if (res >= 0) BigInt(res) else BigInt(res + that._long) + } else BigInt(this.bigInteger.mod(that.bigInteger)) + + /** Returns the minimum of this and that + */ + def min(that: BigInt): BigInt = + if (this <= that) this else that + + /** Returns the maximum of this and that + */ + def max(that: BigInt): BigInt = + if (this >= that) this else that + + /** Returns a BigInt whose value is (this raised to the power of exp). + */ + def pow(exp: Int): BigInt = BigInt(this.bigInteger.pow(exp)) + + /** Returns a BigInt whose value is + * (this raised to the power of exp modulo m). + */ + def modPow(exp: BigInt, m: BigInt): BigInt = BigInt(this.bigInteger.modPow(exp.bigInteger, m.bigInteger)) + + /** Returns a BigInt whose value is (the inverse of this modulo m). + */ + def modInverse(m: BigInt): BigInt = BigInt(this.bigInteger.modInverse(m.bigInteger)) + + /** Returns a BigInt whose value is the negation of this BigInt + */ + def unary_- : BigInt = if (longEncoding) BigInt(-_long) else BigInt(this.bigInteger.negate()) + + /** Returns the absolute value of this BigInt + */ + def abs: BigInt = if (signum < 0) -this else this + + /** Returns the sign of this BigInt; + * -1 if it is less than 0, + * +1 if it is greater than 0, + * 0 if it is equal to 0. + */ + def signum: Int = if (longEncoding) java.lang.Long.signum(_long) else _bigInteger.signum() + + /** Returns the sign of this BigInt; + * -1 if it is less than 0, + * +1 if it is greater than 0, + * 0 if it is equal to 0. + */ + def sign: BigInt = BigInt(signum) + + /** Returns the bitwise complement of this BigInt + */ + def unary_~ : BigInt = + // it is equal to -(this + 1) + if (longEncoding && _long != Long.MaxValue) BigInt(-(_long + 1)) else BigInt(this.bigInteger.not()) + + /** Returns true if and only if the designated bit is set. + */ + def testBit(n: Int): Boolean = + if (longEncoding && n >= 0) { + if (n <= 63) + (_long & (1L << n)) != 0 + else + _long < 0 // give the sign bit + } else this.bigInteger.testBit(n) + + /** Returns a BigInt whose value is equivalent to this BigInt with the designated bit set. + */ + def setBit(n: Int): BigInt = // note that we do not operate on the Long sign bit #63 + if (longEncoding && n <= 62 && n >= 0) BigInt(_long | (1L << n)) else BigInt(this.bigInteger.setBit(n)) + + /** Returns a BigInt whose value is equivalent to this BigInt with the designated bit cleared. + */ + def clearBit(n: Int): BigInt = // note that we do not operate on the Long sign bit #63 + if (longEncoding && n <= 62 && n >= 0) BigInt(_long & ~(1L << n)) else BigInt(this.bigInteger.clearBit(n)) + + /** Returns a BigInt whose value is equivalent to this BigInt with the designated bit flipped. + */ + def flipBit(n: Int): BigInt = // note that we do not operate on the Long sign bit #63 + if (longEncoding && n <= 62 && n >= 0) BigInt(_long ^ (1L << n)) else BigInt(this.bigInteger.flipBit(n)) + + /** Returns the index of the rightmost (lowest-order) one bit in this BigInt + * (the number of zero bits to the right of the rightmost one bit). + */ + def lowestSetBit: Int = + if (longEncoding) { + if (_long == 0) -1 else java.lang.Long.numberOfTrailingZeros(_long) + } else this.bigInteger.getLowestSetBit() + + /** Returns the number of bits in the minimal two's-complement representation of this BigInt, + * excluding a sign bit. + */ + def bitLength: Int = + // bitLength is defined as ceil(log2(this < 0 ? -this : this + 1))) + // where ceil(log2(x)) = 64 - numberOfLeadingZeros(x - 1) + if (longEncoding) { + if (_long < 0) 64 - java.lang.Long.numberOfLeadingZeros(-(_long + 1)) // takes care of Long.MinValue + else 64 - java.lang.Long.numberOfLeadingZeros(_long) + } else _bigInteger.bitLength() + + /** Returns the number of bits in the two's complement representation of this BigInt + * that differ from its sign bit. + */ + def bitCount: Int = + if (longEncoding) { + if (_long < 0) java.lang.Long.bitCount(-(_long + 1)) else java.lang.Long.bitCount(_long) + } else this.bigInteger.bitCount() + + /** Returns true if this BigInt is probably prime, false if it's definitely composite. + * @param certainty a measure of the uncertainty that the caller is willing to tolerate: + * if the call returns true the probability that this BigInt is prime + * exceeds (1 - 1/2 ^ certainty). + * The execution time of this method is proportional to the value of + * this parameter. + */ + def isProbablePrime(certainty: Int): Boolean = this.bigInteger.isProbablePrime(certainty) + + /** Converts this BigInt to a byte. + * If the BigInt is too big to fit in a byte, only the low-order 8 bits are returned. + * Note that this conversion can lose information about the overall magnitude of the + * BigInt value as well as return a result with the opposite sign. + */ + override def byteValue: Byte = intValue.toByte + + /** Converts this BigInt to a short. + * If the BigInt is too big to fit in a short, only the low-order 16 bits are returned. + * Note that this conversion can lose information about the overall magnitude of the + * BigInt value as well as return a result with the opposite sign. + */ + override def shortValue: Short = intValue.toShort + + /** Converts this BigInt to a char. + * If the BigInt is too big to fit in a char, only the low-order 16 bits are returned. + * Note that this conversion can lose information about the overall magnitude of the + * BigInt value and that it always returns a positive result. + */ + def charValue: Char = intValue.toChar + + /** Converts this BigInt to an int. + * If the BigInt is too big to fit in an int, only the low-order 32 bits + * are returned. Note that this conversion can lose information about the + * overall magnitude of the BigInt value as well as return a result with + * the opposite sign. + */ + def intValue: Int = if (longEncoding) _long.toInt else this.bigInteger.intValue + + /** Converts this BigInt to a long. + * If the BigInt is too big to fit in a long, only the low-order 64 bits + * are returned. Note that this conversion can lose information about the + * overall magnitude of the BigInt value as well as return a result with + * the opposite sign. + */ + def longValue: Long = if (longEncoding) _long else _bigInteger.longValue + + /** Converts this `BigInt` to a `float`. + * If this `BigInt` has too great a magnitude to represent as a float, + * it will be converted to `Float.NEGATIVE_INFINITY` or + * `Float.POSITIVE_INFINITY` as appropriate. + */ + def floatValue: Float = this.bigInteger.floatValue + + /** Converts this `BigInt` to a `double`. + * if this `BigInt` has too great a magnitude to represent as a double, + * it will be converted to `Double.NEGATIVE_INFINITY` or + * `Double.POSITIVE_INFINITY` as appropriate. + */ + def doubleValue: Double = + if (isValidLong && (-(1L << 53) <= _long && _long <= (1L << 53))) _long.toDouble + else this.bigInteger.doubleValue + + /** Create a `NumericRange[BigInt]` in range `[start;end)` + * with the specified step, where start is the target BigInt. + * + * @param end the end value of the range (exclusive) + * @param step the distance between elements (defaults to 1) + * @return the range + */ + def until(end: BigInt, step: BigInt = BigInt(1)): NumericRange.Exclusive[BigInt] = Range.BigInt(this, end, step) + + /** Like until, but inclusive of the end value. + */ + def to(end: BigInt, step: BigInt = BigInt(1)): NumericRange.Inclusive[BigInt] = Range.BigInt.inclusive(this, end, step) + + /** Returns the decimal String representation of this BigInt. + */ + override def toString(): String = if (longEncoding) _long.toString() else _bigInteger.toString() + + /** Returns the String representation in the specified radix of this BigInt. + */ + def toString(radix: Int): String = this.bigInteger.toString(radix) + + /** Returns a byte array containing the two's-complement representation of + * this BigInt. The byte array will be in big-endian byte-order: the most + * significant byte is in the zeroth element. The array will contain the + * minimum number of bytes required to represent this BigInt, including at + * least one sign bit. + */ + def toByteArray: Array[Byte] = this.bigInteger.toByteArray() +} diff --git a/library/src/scala/math/Equiv.scala b/library/src/scala/math/Equiv.scala new file mode 100644 index 000000000000..4c708783f20a --- /dev/null +++ b/library/src/scala/math/Equiv.scala @@ -0,0 +1,508 @@ +/* + * 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 math + +import scala.language.`2.13` +import java.util.Comparator +import scala.annotation.migration + +/** A trait for representing equivalence relations. It is important to + * distinguish between a type that can be compared for equality or + * equivalence and a representation of equivalence on some type. This + * trait is for representing the latter. + * + * An [[https://en.wikipedia.org/wiki/Equivalence_relation equivalence relation]] + * is a binary relation on a type. This relation is exposed as + * the `equiv` method of the `Equiv` trait. The relation must be: + * + * 1. reflexive: `equiv(x, x) == true` for any x of type `T`. + * 1. symmetric: `equiv(x, y) == equiv(y, x)` for any `x` and `y` of type `T`. + * 1. transitive: if `equiv(x, y) == true` and `equiv(y, z) == true`, then + * `equiv(x, z) == true` for any `x`, `y`, and `z` of type `T`. + */ + +trait Equiv[T] extends Any with Serializable { + /** Returns `true` iff `x` is equivalent to `y`. + */ + def equiv(x: T, y: T): Boolean +} + +trait LowPriorityEquiv { + self: Equiv.type => + + /** + * @deprecated This implicit universal `Equiv` instance allows accidentally + * comparing instances of types for which equality isn't well-defined or implemented. + * (For example, it does not make sense to compare two `Function1` instances.) + * + * Use `Equiv.universal` explicitly instead. If you really want an implicit universal `Equiv` instance + * despite the potential problems, consider `implicit def universalEquiv[T]: Equiv[T] = universal[T]`. + */ + @deprecated("Use explicit Equiv.universal instead. See Scaladoc entry for more information: " + + "https://www.scala-lang.org/api/current/scala/math/Equiv$.html#universalEquiv[T]:scala.math.Equiv[T]", + since = "2.13.0") + implicit def universalEquiv[T]: Equiv[T] = universal[T] +} + +object Equiv extends LowPriorityEquiv { + def reference[T <: AnyRef]: Equiv[T] = { _ eq _ } + def universal[T]: Equiv[T] = { _ == _ } + def fromComparator[T](cmp: Comparator[T]): Equiv[T] = { + (x, y) => cmp.compare(x, y) == 0 + } + def fromFunction[T](cmp: (T, T) => Boolean): Equiv[T] = { + (x, y) => cmp(x, y) + } + def by[T, S: Equiv](f: T => S): Equiv[T] = + ((x, y) => implicitly[Equiv[S]].equiv(f(x), f(y))) + + @inline def apply[T: Equiv]: Equiv[T] = implicitly[Equiv[T]] + + /* copied from Ordering */ + + private final val optionSeed = 43 + private final val iterableSeed = 47 + + private final class IterableEquiv[CC[X] <: Iterable[X], T](private val eqv: Equiv[T]) extends Equiv[CC[T]] { + def equiv(x: CC[T], y: CC[T]): Boolean = { + val xe = x.iterator + val ye = y.iterator + + while (xe.hasNext && ye.hasNext) { + if (!eqv.equiv(xe.next(), ye.next())) return false + } + + xe.hasNext == ye.hasNext + } + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: IterableEquiv[_, _] => this.eqv == that.eqv + case _ => false + } + override def hashCode(): Int = eqv.hashCode() * iterableSeed + } + + trait ExtraImplicits { + /** Not in the standard scope due to the potential for divergence: + * For instance `implicitly[Equiv[Any]]` diverges in its presence. + */ + implicit def seqEquiv[CC[X] <: scala.collection.Seq[X], T](implicit eqv: Equiv[T]): Equiv[CC[T]] = + new IterableEquiv[CC, T](eqv) + + implicit def sortedSetEquiv[CC[X] <: scala.collection.SortedSet[X], T](implicit eqv: Equiv[T]): Equiv[CC[T]] = + new IterableEquiv[CC, T](eqv) + } + + /** An object containing implicits which are not in the default scope. */ + object Implicits extends ExtraImplicits { } + + implicit object Unit extends Equiv[Unit] { + def equiv(x: Unit, y: Unit): Boolean = true + } + + implicit object Boolean extends Equiv[Boolean] { + def equiv(x: Boolean, y: Boolean): Boolean = x == y + } + + implicit object Byte extends Equiv[Byte] { + def equiv(x: Byte, y: Byte): Boolean = x == y + } + + implicit object Char extends Equiv[Char] { + def equiv(x: Char, y: Char): Boolean = x == y + } + + implicit object Short extends Equiv[Short] { + def equiv(x: Short, y: Short): Boolean = x == y + } + + implicit object Int extends Equiv[Int] { + def equiv(x: Int, y: Int): Boolean = x == y + } + + implicit object Long extends Equiv[Long] { + def equiv(x: Long, y: Long): Boolean = x == y + } + + /** `Equiv`s for `Float`s. + * + * @define floatEquiv Because the behaviour of `Float`s specified by IEEE is + * not consistent with behaviors required of an equivalence + * relation for `NaN` (it is not reflexive), there are two + * equivalences defined for `Float`: `StrictEquiv`, which + * is reflexive, and `IeeeEquiv`, which is consistent + * with IEEE spec and floating point operations defined in + * [[scala.math]]. + */ + object Float { + /** An equivalence for `Float`s which is reflexive (treats all `NaN`s + * as equivalent), and treats `-0.0` and `0.0` as not equivalent; it + * behaves the same as [[java.lang.Float.compare]]. + * + * $floatEquiv + * + * This equivalence may be preferable for collections. + * + * @see [[IeeeEquiv]] + */ + trait StrictEquiv extends Equiv[Float] { + def equiv(x: Float, y: Float): Boolean = java.lang.Float.compare(x, y) == 0 + } + implicit object StrictEquiv extends StrictEquiv + + /** An equivalence for `Float`s which is consistent with IEEE specifications. + * + * $floatEquiv + * + * This equivalence may be preferable for numeric contexts. + * + * @see [[StrictEquiv]] + */ + trait IeeeEquiv extends Equiv[Float] { + override def equiv(x: Float, y: Float): Boolean = x == y + } + implicit object IeeeEquiv extends IeeeEquiv + } + + @migration( + " The default implicit equivalence for floats no longer conforms to\n" + + " to IEEE 754's behavior for -0.0F and NaN.\n" + + " Import `Equiv.Float.IeeeEquiv` to recover the previous behavior.\n" + + " See also https://www.scala-lang.org/api/current/scala/math/Equiv$$Float$.html.", "2.13.2") + implicit object DeprecatedFloatEquiv extends Float.StrictEquiv + + /** `Equiv`s for `Double`s. + * + * @define doubleEquiv Because the behaviour of `Double`s specified by IEEE is + * not consistent with behaviors required of an equivalence + * relation for `NaN` (it is not reflexive), there are two + * equivalences defined for `Double`: `StrictEquiv`, which + * is reflexive, and `IeeeEquiv`, which is consistent + * with IEEE spec and floating point operations defined in + * [[scala.math]]. + */ + object Double { + /** An equivalence for `Double`s which is reflexive (treats all `NaN`s + * as equivalent), and treats `-0.0` and `0.0` as not equivalent; it + * behaves the same as [[java.lang.Double.compare]]. + * + * $doubleEquiv + * + * This equivalence may be preferable for collections. + * + * @see [[IeeeEquiv]] + */ + trait StrictEquiv extends Equiv[Double] { + def equiv(x: Double, y: Double): Boolean = java.lang.Double.compare(x, y) == 0 + } + implicit object StrictEquiv extends StrictEquiv + + /** An equivalence for `Double`s which is consistent with IEEE specifications. + * + * $doubleEquiv + * + * This equivalence may be preferable for numeric contexts. + * + * @see [[StrictEquiv]] + */ + trait IeeeEquiv extends Equiv[Double] { + def equiv(x: Double, y: Double): Boolean = x == y + } + implicit object IeeeEquiv extends IeeeEquiv + } + @migration( + " The default implicit equivalence for doubles no longer conforms to\n" + + " to IEEE 754's behavior for -0.0D and NaN.\n" + + " Import `Equiv.Double.IeeeEquiv` to recover the previous behavior.\n" + + " See also https://www.scala-lang.org/api/current/scala/math/Equiv$$Double$.html.", "2.13.2") + implicit object DeprecatedDoubleEquiv extends Double.StrictEquiv + + implicit object BigInt extends Equiv[BigInt] { + def equiv(x: BigInt, y: BigInt): Boolean = x == y + } + + implicit object BigDecimal extends Equiv[BigDecimal] { + def equiv(x: BigDecimal, y: BigDecimal): Boolean = x == y + } + + implicit object String extends Equiv[String] { + def equiv(x: String, y: String): Boolean = x == y + } + + implicit object Symbol extends Equiv[Symbol] { + def equiv(x: Symbol, y: Symbol): Boolean = x == y + } + + implicit def Option[T](implicit eqv: Equiv[T]): Equiv[Option[T]] = + new OptionEquiv[T](eqv) + + private[this] final class OptionEquiv[T](private val eqv: Equiv[T]) extends Equiv[Option[T]] { + def equiv(x: Option[T], y: Option[T]): Boolean = (x, y) match { + case (None, None) => true + case (Some(x), Some(y)) => eqv.equiv(x, y) + case _ => false + } + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: OptionEquiv[_] => this.eqv == that.eqv + case _ => false + } + override def hashCode(): Int = eqv.hashCode() * optionSeed + } + + implicit def Tuple2[T1, T2](implicit eqv1: Equiv[T1], eqv2: Equiv[T2]): Equiv[(T1, T2)] = + new Tuple2Equiv(eqv1, eqv2) + + private[this] final class Tuple2Equiv[T1, T2](private val eqv1: Equiv[T1], + private val eqv2: Equiv[T2]) extends Equiv[(T1, T2)] { + def equiv(x: (T1, T2), y: (T1, T2)): Boolean = + eqv1.equiv(x._1, y._1) && + eqv2.equiv(x._2, y._2) + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple2Equiv[_, _] => + this.eqv1 == that.eqv1 && + this.eqv2 == that.eqv2 + case _ => false + } + override def hashCode(): Int = (eqv1, eqv2).hashCode() + } + + implicit def Tuple3[T1, T2, T3](implicit eqv1: Equiv[T1], eqv2: Equiv[T2], eqv3: Equiv[T3]) : Equiv[(T1, T2, T3)] = + new Tuple3Equiv(eqv1, eqv2, eqv3) + + private[this] final class Tuple3Equiv[T1, T2, T3](private val eqv1: Equiv[T1], + private val eqv2: Equiv[T2], + private val eqv3: Equiv[T3]) extends Equiv[(T1, T2, T3)] { + def equiv(x: (T1, T2, T3), y: (T1, T2, T3)): Boolean = + eqv1.equiv(x._1, y._1) && + eqv2.equiv(x._2, y._2) && + eqv3.equiv(x._3, y._3) + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple3Equiv[_, _, _] => + this.eqv1 == that.eqv1 && + this.eqv2 == that.eqv2 && + this.eqv3 == that.eqv3 + case _ => false + } + override def hashCode(): Int = (eqv1, eqv2, eqv3).hashCode() + } + + implicit def Tuple4[T1, T2, T3, T4](implicit eqv1: Equiv[T1], eqv2: Equiv[T2], eqv3: Equiv[T3], eqv4: Equiv[T4]) : Equiv[(T1, T2, T3, T4)] = + new Tuple4Equiv(eqv1, eqv2, eqv3, eqv4) + + private[this] final class Tuple4Equiv[T1, T2, T3, T4](private val eqv1: Equiv[T1], + private val eqv2: Equiv[T2], + private val eqv3: Equiv[T3], + private val eqv4: Equiv[T4]) + extends Equiv[(T1, T2, T3, T4)] { + def equiv(x: (T1, T2, T3, T4), y: (T1, T2, T3, T4)): Boolean = + eqv1.equiv(x._1, y._1) && + eqv2.equiv(x._2, y._2) && + eqv3.equiv(x._3, y._3) && + eqv4.equiv(x._4, y._4) + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple4Equiv[_, _, _, _] => + this.eqv1 == that.eqv1 && + this.eqv2 == that.eqv2 && + this.eqv3 == that.eqv3 && + this.eqv4 == that.eqv4 + case _ => false + } + override def hashCode(): Int = (eqv1, eqv2, eqv3, eqv4).hashCode() + } + + implicit def Tuple5[T1, T2, T3, T4, T5](implicit eqv1: Equiv[T1], eqv2: Equiv[T2], eqv3: Equiv[T3], eqv4: Equiv[T4], eqv5: Equiv[T5]): Equiv[(T1, T2, T3, T4, T5)] = + new Tuple5Equiv(eqv1, eqv2, eqv3, eqv4, eqv5) + + private[this] final class Tuple5Equiv[T1, T2, T3, T4, T5](private val eqv1: Equiv[T1], + private val eqv2: Equiv[T2], + private val eqv3: Equiv[T3], + private val eqv4: Equiv[T4], + private val eqv5: Equiv[T5]) + extends Equiv[(T1, T2, T3, T4, T5)] { + def equiv(x: (T1, T2, T3, T4, T5), y: (T1, T2, T3, T4, T5)): Boolean = + eqv1.equiv(x._1, y._1) && + eqv2.equiv(x._2, y._2) && + eqv3.equiv(x._3, y._3) && + eqv4.equiv(x._4, y._4) && + eqv5.equiv(x._5, y._5) + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple5Equiv[_, _, _, _, _] => + this.eqv1 == that.eqv1 && + this.eqv2 == that.eqv2 && + this.eqv3 == that.eqv3 && + this.eqv4 == that.eqv4 && + this.eqv5 == that.eqv5 + case _ => false + } + override def hashCode(): Int = (eqv1, eqv2, eqv3, eqv4, eqv5).hashCode() + } + + implicit def Tuple6[T1, T2, T3, T4, T5, T6](implicit eqv1: Equiv[T1], eqv2: Equiv[T2], eqv3: Equiv[T3], eqv4: Equiv[T4], eqv5: Equiv[T5], eqv6: Equiv[T6]): Equiv[(T1, T2, T3, T4, T5, T6)] = + new Tuple6Equiv(eqv1, eqv2, eqv3, eqv4, eqv5, eqv6) + + private[this] final class Tuple6Equiv[T1, T2, T3, T4, T5, T6](private val eqv1: Equiv[T1], + private val eqv2: Equiv[T2], + private val eqv3: Equiv[T3], + private val eqv4: Equiv[T4], + private val eqv5: Equiv[T5], + private val eqv6: Equiv[T6]) + extends Equiv[(T1, T2, T3, T4, T5, T6)] { + def equiv(x: (T1, T2, T3, T4, T5, T6), y: (T1, T2, T3, T4, T5, T6)): Boolean = + eqv1.equiv(x._1, y._1) && + eqv2.equiv(x._2, y._2) && + eqv3.equiv(x._3, y._3) && + eqv4.equiv(x._4, y._4) && + eqv5.equiv(x._5, y._5) && + eqv6.equiv(x._6, y._6) + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple6Equiv[_, _, _, _, _, _] => + this.eqv1 == that.eqv1 && + this.eqv2 == that.eqv2 && + this.eqv3 == that.eqv3 && + this.eqv4 == that.eqv4 && + this.eqv5 == that.eqv5 && + this.eqv6 == that.eqv6 + case _ => false + } + override def hashCode(): Int = (eqv1, eqv2, eqv3, eqv4, eqv5, eqv6).hashCode() + } + + implicit def Tuple7[T1, T2, T3, T4, T5, T6, T7](implicit eqv1: Equiv[T1], eqv2: Equiv[T2], eqv3: Equiv[T3], eqv4: Equiv[T4], eqv5: Equiv[T5], eqv6: Equiv[T6], eqv7: Equiv[T7]): Equiv[(T1, T2, T3, T4, T5, T6, T7)] = + new Tuple7Equiv(eqv1, eqv2, eqv3, eqv4, eqv5, eqv6, eqv7) + + private[this] final class Tuple7Equiv[T1, T2, T3, T4, T5, T6, T7](private val eqv1: Equiv[T1], + private val eqv2: Equiv[T2], + private val eqv3: Equiv[T3], + private val eqv4: Equiv[T4], + private val eqv5: Equiv[T5], + private val eqv6: Equiv[T6], + private val eqv7: Equiv[T7]) + extends Equiv[(T1, T2, T3, T4, T5, T6, T7)] { + def equiv(x: (T1, T2, T3, T4, T5, T6, T7), y: (T1, T2, T3, T4, T5, T6, T7)): Boolean = + eqv1.equiv(x._1, y._1) && + eqv2.equiv(x._2, y._2) && + eqv3.equiv(x._3, y._3) && + eqv4.equiv(x._4, y._4) && + eqv5.equiv(x._5, y._5) && + eqv6.equiv(x._6, y._6) && + eqv7.equiv(x._7, y._7) + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple7Equiv[_, _, _, _, _, _, _] => + this.eqv1 == that.eqv1 && + this.eqv2 == that.eqv2 && + this.eqv3 == that.eqv3 && + this.eqv4 == that.eqv4 && + this.eqv5 == that.eqv5 && + this.eqv6 == that.eqv6 && + this.eqv7 == that.eqv7 + case _ => false + } + override def hashCode(): Int = (eqv1, eqv2, eqv3, eqv4, eqv5, eqv6, eqv7).hashCode() + } + + implicit def Tuple8[T1, T2, T3, T4, T5, T6, T7, T8](implicit eqv1: Equiv[T1], eqv2: Equiv[T2], eqv3: Equiv[T3], eqv4: Equiv[T4], eqv5: Equiv[T5], eqv6: Equiv[T6], eqv7: Equiv[T7], eqv8: Equiv[T8]): Equiv[(T1, T2, T3, T4, T5, T6, T7, T8)] = + new Tuple8Equiv(eqv1, eqv2, eqv3, eqv4, eqv5, eqv6, eqv7, eqv8) + + private[this] final class Tuple8Equiv[T1, T2, T3, T4, T5, T6, T7, T8](private val eqv1: Equiv[T1], + private val eqv2: Equiv[T2], + private val eqv3: Equiv[T3], + private val eqv4: Equiv[T4], + private val eqv5: Equiv[T5], + private val eqv6: Equiv[T6], + private val eqv7: Equiv[T7], + private val eqv8: Equiv[T8]) + extends Equiv[(T1, T2, T3, T4, T5, T6, T7, T8)] { + def equiv(x: (T1, T2, T3, T4, T5, T6, T7, T8), y: (T1, T2, T3, T4, T5, T6, T7, T8)): Boolean = + eqv1.equiv(x._1, y._1) && + eqv2.equiv(x._2, y._2) && + eqv3.equiv(x._3, y._3) && + eqv4.equiv(x._4, y._4) && + eqv5.equiv(x._5, y._5) && + eqv6.equiv(x._6, y._6) && + eqv7.equiv(x._7, y._7) && + eqv8.equiv(x._8, y._8) + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple8Equiv[_, _, _, _, _, _, _, _] => + this.eqv1 == that.eqv1 && + this.eqv2 == that.eqv2 && + this.eqv3 == that.eqv3 && + this.eqv4 == that.eqv4 && + this.eqv5 == that.eqv5 && + this.eqv6 == that.eqv6 && + this.eqv7 == that.eqv7 && + this.eqv8 == that.eqv8 + case _ => false + } + override def hashCode(): Int = (eqv1, eqv2, eqv3, eqv4, eqv5, eqv6, eqv7, eqv8).hashCode() + } + + implicit def Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9](implicit eqv1: Equiv[T1], eqv2: Equiv[T2], eqv3: Equiv[T3], eqv4: Equiv[T4], eqv5: Equiv[T5], eqv6: Equiv[T6], eqv7: Equiv[T7], eqv8 : Equiv[T8], eqv9: Equiv[T9]): Equiv[(T1, T2, T3, T4, T5, T6, T7, T8, T9)] = + new Tuple9Equiv(eqv1, eqv2, eqv3, eqv4, eqv5, eqv6, eqv7, eqv8, eqv9) + + private[this] final class Tuple9Equiv[T1, T2, T3, T4, T5, T6, T7, T8, T9](private val eqv1: Equiv[T1], + private val eqv2: Equiv[T2], + private val eqv3: Equiv[T3], + private val eqv4: Equiv[T4], + private val eqv5: Equiv[T5], + private val eqv6: Equiv[T6], + private val eqv7: Equiv[T7], + private val eqv8: Equiv[T8], + private val eqv9: Equiv[T9]) + extends Equiv[(T1, T2, T3, T4, T5, T6, T7, T8, T9)] { + def equiv(x: (T1, T2, T3, T4, T5, T6, T7, T8, T9), y: (T1, T2, T3, T4, T5, T6, T7, T8, T9)): Boolean = + eqv1.equiv(x._1, y._1) && + eqv2.equiv(x._2, y._2) && + eqv3.equiv(x._3, y._3) && + eqv4.equiv(x._4, y._4) && + eqv5.equiv(x._5, y._5) && + eqv6.equiv(x._6, y._6) && + eqv7.equiv(x._7, y._7) && + eqv8.equiv(x._8, y._8) && + eqv9.equiv(x._9, y._9) + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple9Equiv[_, _, _, _, _, _, _, _, _] => + this.eqv1 == that.eqv1 && + this.eqv2 == that.eqv2 && + this.eqv3 == that.eqv3 && + this.eqv4 == that.eqv4 && + this.eqv5 == that.eqv5 && + this.eqv6 == that.eqv6 && + this.eqv7 == that.eqv7 && + this.eqv8 == that.eqv8 && + this.eqv9 == that.eqv9 + case _ => false + } + override def hashCode(): Int = (eqv1, eqv2, eqv3, eqv4, eqv5, eqv6, eqv7, eqv8, eqv9).hashCode() + } + +} diff --git a/library/src/scala/math/Fractional.scala b/library/src/scala/math/Fractional.scala new file mode 100644 index 000000000000..e0469d9cb054 --- /dev/null +++ b/library/src/scala/math/Fractional.scala @@ -0,0 +1,36 @@ +/* + * 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 math + +import scala.language.`2.13` +import scala.language.implicitConversions + +trait Fractional[T] extends Numeric[T] { + def div(x: T, y: T): T + + class FractionalOps(lhs: T) extends NumericOps(lhs) { + def /(rhs: T) = div(lhs, rhs) + } + override implicit def mkNumericOps(lhs: T): FractionalOps = + new FractionalOps(lhs) +} + +object Fractional { + @inline def apply[T](implicit frac: Fractional[T]): Fractional[T] = frac + + trait ExtraImplicits { + implicit def infixFractionalOps[T](x: T)(implicit num: Fractional[T]): Fractional[T]#FractionalOps = new num.FractionalOps(x) + } + object Implicits extends ExtraImplicits +} diff --git a/library/src/scala/math/Integral.scala b/library/src/scala/math/Integral.scala new file mode 100644 index 000000000000..89465fb81023 --- /dev/null +++ b/library/src/scala/math/Integral.scala @@ -0,0 +1,42 @@ +/* + * 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 math + +import scala.language.`2.13` +import scala.language.implicitConversions + +trait Integral[T] extends Numeric[T] { + def quot(x: T, y: T): T + def rem(x: T, y: T): T + + class IntegralOps(lhs: T) extends NumericOps(lhs) { + def /(rhs: T) = quot(lhs, rhs) + def %(rhs: T) = rem(lhs, rhs) + def /%(rhs: T) = (quot(lhs, rhs), rem(lhs, rhs)) + } + override implicit def mkNumericOps(lhs: T): IntegralOps = new IntegralOps(lhs) +} + +object Integral { + @inline def apply[T](implicit int: Integral[T]): Integral[T] = int + + trait ExtraImplicits { + /** The regrettable design of Numeric/Integral/Fractional has them all + * bumping into one another when searching for this implicit, so they + * are exiled into their own companions. + */ + implicit def infixIntegralOps[T](x: T)(implicit num: Integral[T]): Integral[T]#IntegralOps = new num.IntegralOps(x) + } + object Implicits extends ExtraImplicits +} diff --git a/library/src/scala/math/Numeric.scala b/library/src/scala/math/Numeric.scala new file mode 100644 index 000000000000..e129a98e9fdd --- /dev/null +++ b/library/src/scala/math/Numeric.scala @@ -0,0 +1,262 @@ +/* + * 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 math + +import scala.language.`2.13` +import scala.collection.StringParsers +import scala.language.implicitConversions +import scala.util.Try + +object Numeric { + @inline def apply[T](implicit num: Numeric[T]): Numeric[T] = num + + trait ExtraImplicits { + /** These implicits create conversions from a value for which an implicit Numeric + * exists to the inner class which creates infix operations. Once imported, you + * can write methods as follows: + * {{{ + * def plus[T: Numeric](x: T, y: T) = x + y + * }}} + */ + implicit def infixNumericOps[T](x: T)(implicit num: Numeric[T]): Numeric[T]#NumericOps = new num.NumericOps(x) + } + object Implicits extends ExtraImplicits { } + + trait BigIntIsIntegral extends Integral[BigInt] { + def plus(x: BigInt, y: BigInt): BigInt = x + y + def minus(x: BigInt, y: BigInt): BigInt = x - y + def times(x: BigInt, y: BigInt): BigInt = x * y + def quot(x: BigInt, y: BigInt): BigInt = x / y + def rem(x: BigInt, y: BigInt): BigInt = x % y + def negate(x: BigInt): BigInt = -x + def fromInt(x: Int): BigInt = BigInt(x) + def parseString(str: String): Option[BigInt] = Try(BigInt(str)).toOption + def toInt(x: BigInt): Int = x.intValue + def toLong(x: BigInt): Long = x.longValue + def toFloat(x: BigInt): Float = x.floatValue + def toDouble(x: BigInt): Double = x.doubleValue + } + implicit object BigIntIsIntegral extends BigIntIsIntegral with Ordering.BigIntOrdering + + trait IntIsIntegral extends Integral[Int] { + def plus(x: Int, y: Int): Int = x + y + def minus(x: Int, y: Int): Int = x - y + def times(x: Int, y: Int): Int = x * y + def quot(x: Int, y: Int): Int = x / y + def rem(x: Int, y: Int): Int = x % y + def negate(x: Int): Int = -x + def fromInt(x: Int): Int = x + def parseString(str: String): Option[Int] = StringParsers.parseInt(str) + def toInt(x: Int): Int = x + def toLong(x: Int): Long = x.toLong + def toFloat(x: Int): Float = x.toFloat + def toDouble(x: Int): Double = x.toDouble + override def signum(x: Int): Int = math.signum(x) + override def sign(x: Int): Int = math.signum(x) + } + implicit object IntIsIntegral extends IntIsIntegral with Ordering.IntOrdering + + trait ShortIsIntegral extends Integral[Short] { + def plus(x: Short, y: Short): Short = (x + y).toShort + def minus(x: Short, y: Short): Short = (x - y).toShort + def times(x: Short, y: Short): Short = (x * y).toShort + def quot(x: Short, y: Short): Short = (x / y).toShort + def rem(x: Short, y: Short): Short = (x % y).toShort + def negate(x: Short): Short = (-x).toShort + def fromInt(x: Int): Short = x.toShort + def parseString(str: String): Option[Short] = StringParsers.parseShort(str) + def toInt(x: Short): Int = x.toInt + def toLong(x: Short): Long = x.toLong + def toFloat(x: Short): Float = x.toFloat + def toDouble(x: Short): Double = x.toDouble + override def signum(x: Short): Int = math.signum(x.toInt) + override def sign(x: Short): Short = math.signum(x.toInt).toShort + } + implicit object ShortIsIntegral extends ShortIsIntegral with Ordering.ShortOrdering + + trait ByteIsIntegral extends Integral[Byte] { + def plus(x: Byte, y: Byte): Byte = (x + y).toByte + def minus(x: Byte, y: Byte): Byte = (x - y).toByte + def times(x: Byte, y: Byte): Byte = (x * y).toByte + def quot(x: Byte, y: Byte): Byte = (x / y).toByte + def rem(x: Byte, y: Byte): Byte = (x % y).toByte + def negate(x: Byte): Byte = (-x).toByte + def fromInt(x: Int): Byte = x.toByte + def parseString(str: String): Option[Byte] = StringParsers.parseByte(str) + def toInt(x: Byte): Int = x.toInt + def toLong(x: Byte): Long = x.toLong + def toFloat(x: Byte): Float = x.toFloat + def toDouble(x: Byte): Double = x.toDouble + override def signum(x: Byte): Int = math.signum(x.toInt) + override def sign(x: Byte): Byte = math.signum(x.toInt).toByte + } + implicit object ByteIsIntegral extends ByteIsIntegral with Ordering.ByteOrdering + + trait CharIsIntegral extends Integral[Char] { + def plus(x: Char, y: Char): Char = (x + y).toChar + def minus(x: Char, y: Char): Char = (x - y).toChar + def times(x: Char, y: Char): Char = (x * y).toChar + def quot(x: Char, y: Char): Char = (x / y).toChar + def rem(x: Char, y: Char): Char = (x % y).toChar + def negate(x: Char): Char = (-x).toChar + def fromInt(x: Int): Char = x.toChar + def parseString(str: String): Option[Char] = Try(str.toInt.toChar).toOption + def toInt(x: Char): Int = x.toInt + def toLong(x: Char): Long = x.toLong + def toFloat(x: Char): Float = x.toFloat + def toDouble(x: Char): Double = x.toDouble + override def signum(x: Char): Int = math.signum(x.toInt) + override def sign(x: Char): Char = math.signum(x.toInt).toChar + } + implicit object CharIsIntegral extends CharIsIntegral with Ordering.CharOrdering + + trait LongIsIntegral extends Integral[Long] { + def plus(x: Long, y: Long): Long = x + y + def minus(x: Long, y: Long): Long = x - y + def times(x: Long, y: Long): Long = x * y + def quot(x: Long, y: Long): Long = x / y + def rem(x: Long, y: Long): Long = x % y + def negate(x: Long): Long = -x + def fromInt(x: Int): Long = x.toLong + def parseString(str: String): Option[Long] = StringParsers.parseLong(str) + def toInt(x: Long): Int = x.toInt + def toLong(x: Long): Long = x + def toFloat(x: Long): Float = x.toFloat + def toDouble(x: Long): Double = x.toDouble + override def signum(x: Long): Int = math.signum(x).toInt + override def sign(x: Long): Long = math.signum(x) + } + implicit object LongIsIntegral extends LongIsIntegral with Ordering.LongOrdering + + trait FloatIsFractional extends Fractional[Float] { + def plus(x: Float, y: Float): Float = x + y + def minus(x: Float, y: Float): Float = x - y + def times(x: Float, y: Float): Float = x * y + def negate(x: Float): Float = -x + def fromInt(x: Int): Float = x.toFloat + def parseString(str: String): Option[Float] = StringParsers.parseFloat(str) + def toInt(x: Float): Int = x.toInt + def toLong(x: Float): Long = x.toLong + def toFloat(x: Float): Float = x + def toDouble(x: Float): Double = x.toDouble + def div(x: Float, y: Float): Float = x / y + // logic in Numeric base trait mishandles abs(-0.0f) + override def abs(x: Float): Float = math.abs(x) + // logic in Numeric base trait mishandles sign(-0.0f) and sign(Float.NaN) + override def sign(x: Float): Float = math.signum(x) + } + implicit object FloatIsFractional extends FloatIsFractional with Ordering.Float.IeeeOrdering + + trait DoubleIsFractional extends Fractional[Double] { + def plus(x: Double, y: Double): Double = x + y + def minus(x: Double, y: Double): Double = x - y + def times(x: Double, y: Double): Double = x * y + def negate(x: Double): Double = -x + def fromInt(x: Int): Double = x.toDouble + def parseString(str: String): Option[Double] = StringParsers.parseDouble(str) + def toInt(x: Double): Int = x.toInt + def toLong(x: Double): Long = x.toLong + def toFloat(x: Double): Float = x.toFloat + def toDouble(x: Double): Double = x + def div(x: Double, y: Double): Double = x / y + // logic in Numeric base trait mishandles abs(-0.0) + override def abs(x: Double): Double = math.abs(x) + // logic in Numeric base trait mishandles sign(-0.0) and sign(Double.NaN) + override def sign(x: Double): Double = math.signum(x) + } + implicit object DoubleIsFractional extends DoubleIsFractional with Ordering.Double.IeeeOrdering + + trait BigDecimalIsConflicted extends Numeric[BigDecimal] { + // works around pollution of math context by ignoring identity element + def plus(x: BigDecimal, y: BigDecimal): BigDecimal = { + import BigDecimalIsConflicted._0 + if (x eq _0) y else x + y + } + def minus(x: BigDecimal, y: BigDecimal): BigDecimal = { + import BigDecimalIsConflicted._0 + if (x eq _0) -y else x - y + } + // works around pollution of math context by ignoring identity element + def times(x: BigDecimal, y: BigDecimal): BigDecimal = { + import BigDecimalIsConflicted._1 + if (x eq _1) y else x * y + } + def negate(x: BigDecimal): BigDecimal = -x + def fromInt(x: Int): BigDecimal = BigDecimal(x) + def parseString(str: String): Option[BigDecimal] = Try(BigDecimal(str)).toOption + def toInt(x: BigDecimal): Int = x.intValue + def toLong(x: BigDecimal): Long = x.longValue + def toFloat(x: BigDecimal): Float = x.floatValue + def toDouble(x: BigDecimal): Double = x.doubleValue + } + private object BigDecimalIsConflicted { + private val _0 = BigDecimal(0) // cached zero is ordinarily cached for default math context + private val _1 = BigDecimal(1) // cached one is ordinarily cached for default math context + } + + trait BigDecimalIsFractional extends BigDecimalIsConflicted with Fractional[BigDecimal] { + def div(x: BigDecimal, y: BigDecimal): BigDecimal = x / y + } + trait BigDecimalAsIfIntegral extends BigDecimalIsConflicted with Integral[BigDecimal] { + def quot(x: BigDecimal, y: BigDecimal): BigDecimal = x quot y + def rem(x: BigDecimal, y: BigDecimal): BigDecimal = x remainder y + } + + // For BigDecimal we offer an implicit Fractional object, but also one + // which acts like an Integral type, which is useful in NumericRange. + implicit object BigDecimalIsFractional extends BigDecimalIsFractional with Ordering.BigDecimalOrdering + object BigDecimalAsIfIntegral extends BigDecimalAsIfIntegral with Ordering.BigDecimalOrdering +} + +trait Numeric[T] extends Ordering[T] { + def plus(x: T, y: T): T + def minus(x: T, y: T): T + def times(x: T, y: T): T + def negate(x: T): T + def fromInt(x: Int): T + def parseString(str: String): Option[T] + def toInt(x: T): Int + def toLong(x: T): Long + def toFloat(x: T): Float + def toDouble(x: T): Double + + def zero = fromInt(0) + def one = fromInt(1) + + def abs(x: T): T = if (lt(x, zero)) negate(x) else x + + @deprecated("use `sign` method instead", since = "2.13.0") def signum(x: T): Int = + if (lt(x, zero)) -1 + else if (gt(x, zero)) 1 + else 0 + def sign(x: T): T = + if (lt(x, zero)) negate(one) + else if (gt(x, zero)) one + else zero + + class NumericOps(lhs: T) { + def +(rhs: T) = plus(lhs, rhs) + def -(rhs: T) = minus(lhs, rhs) + def *(rhs: T) = times(lhs, rhs) + def unary_- = negate(lhs) + def abs: T = Numeric.this.abs(lhs) + @deprecated("use `sign` method instead", since = "2.13.0") def signum: Int = Numeric.this.signum(lhs) + def sign: T = Numeric.this.sign(lhs) + def toInt: Int = Numeric.this.toInt(lhs) + def toLong: Long = Numeric.this.toLong(lhs) + def toFloat: Float = Numeric.this.toFloat(lhs) + def toDouble: Double = Numeric.this.toDouble(lhs) + } + implicit def mkNumericOps(lhs: T): NumericOps = new NumericOps(lhs) +} diff --git a/library/src/scala/math/Ordered.scala b/library/src/scala/math/Ordered.scala new file mode 100644 index 000000000000..88109ff5f854 --- /dev/null +++ b/library/src/scala/math/Ordered.scala @@ -0,0 +1,102 @@ +/* + * 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 math + +import scala.language.`2.13` +import scala.language.implicitConversions + +/** A trait for data that have a single, natural ordering. See + * [[scala.math.Ordering]] before using this trait for + * more information about whether to use [[scala.math.Ordering]] instead. + * + * Classes that implement this trait can be sorted with + * [[scala.util.Sorting]] and can be compared with standard comparison operators + * (e.g. > and <). + * + * Ordered should be used for data with a single, natural ordering (like + * integers) while Ordering allows for multiple ordering implementations. + * An Ordering instance will be implicitly created if necessary. + * + * [[scala.math.Ordering]] is an alternative to this trait that allows multiple orderings to be + * defined for the same type. + * + * [[scala.math.PartiallyOrdered]] is an alternative to this trait for partially ordered data. + * + * For example, create a simple class that implements `Ordered` and then sort it with [[scala.util.Sorting]]: + * {{{ + * case class OrderedClass(n:Int) extends Ordered[OrderedClass] { + * def compare(that: OrderedClass) = this.n - that.n + * } + * + * val x = Array(OrderedClass(1), OrderedClass(5), OrderedClass(3)) + * scala.util.Sorting.quickSort(x) + * x + * }}} + * + * It is important that the `equals` method for an instance of `Ordered[A]` be consistent with the + * compare method. However, due to limitations inherent in the type erasure semantics, there is no + * reasonable way to provide a default implementation of equality for instances of `Ordered[A]`. + * Therefore, if you need to be able to use equality on an instance of `Ordered[A]` you must + * provide it yourself either when inheriting or instantiating. + * + * It is important that the `hashCode` method for an instance of `Ordered[A]` be consistent with + * the `compare` method. However, it is not possible to provide a sensible default implementation. + * Therefore, if you need to be able compute the hash of an instance of `Ordered[A]` you must + * provide it yourself either when inheriting or instantiating. + * + * @see [[scala.math.Ordering]], [[scala.math.PartiallyOrdered]] + */ +trait Ordered[A] extends Any with java.lang.Comparable[A] { + + /** Result of comparing `this` with operand `that`. + * + * Implement this method to determine how instances of A will be sorted. + * + * Returns `x` where: + * + * - `x < 0` when `this < that` + * + * - `x == 0` when `this == that` + * + * - `x > 0` when `this > that` + * + */ + def compare(that: A): Int + + /** Returns true if `this` is less than `that` + */ + def < (that: A): Boolean = (this compare that) < 0 + + /** Returns true if `this` is greater than `that`. + */ + def > (that: A): Boolean = (this compare that) > 0 + + /** Returns true if `this` is less than or equal to `that`. + */ + def <= (that: A): Boolean = (this compare that) <= 0 + + /** Returns true if `this` is greater than or equal to `that`. + */ + def >= (that: A): Boolean = (this compare that) >= 0 + + /** Result of comparing `this` with operand `that`. + */ + def compareTo(that: A): Int = compare(that) +} + +object Ordered { + /** Lens from `Ordering[T]` to `Ordered[T]` */ + implicit def orderingToOrdered[T](x: T)(implicit ord: Ordering[T]): Ordered[T] = + new Ordered[T] { def compare(that: T): Int = ord.compare(x, that) } +} diff --git a/library/src/scala/math/Ordering.scala b/library/src/scala/math/Ordering.scala new file mode 100644 index 000000000000..e8d94a564665 --- /dev/null +++ b/library/src/scala/math/Ordering.scala @@ -0,0 +1,929 @@ +/* + * 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 math + +import scala.language.`2.13` +import java.util.Comparator + +import scala.language.implicitConversions +import scala.annotation.migration + +/** Ordering is a trait whose instances each represent a strategy for sorting + * instances of a type. + * + * Ordering's companion object defines many implicit objects to deal with + * subtypes of [[AnyVal]] (e.g. `Int`, `Double`), `String`, and others. + * + * To sort instances by one or more member variables, you can take advantage + * of these built-in orderings using [[Ordering.by]] and [[Ordering.on]]: + * + * {{{ + * import scala.util.Sorting + * val pairs = Array(("a", 5, 2), ("c", 3, 1), ("b", 1, 3)) + * + * // sort by 2nd element + * Sorting.quickSort(pairs)(Ordering.by[(String, Int, Int), Int](_._2)) + * + * // sort by the 3rd element, then 1st + * Sorting.quickSort(pairs)(Ordering[(Int, String)].on(x => (x._3, x._1))) + * }}} + * + * An `Ordering[T]` is implemented by specifying the [[compare]] method, + * `compare(a: T, b: T): Int`, which decides how to order two instances + * `a` and `b`. Instances of `Ordering[T]` can be used by things like + * `scala.util.Sorting` to sort collections like `Array[T]`. + * + * For example: + * + * {{{ + * import scala.util.Sorting + * + * case class Person(name:String, age:Int) + * val people = Array(Person("bob", 30), Person("ann", 32), Person("carl", 19)) + * + * // sort by age + * object AgeOrdering extends Ordering[Person] { + * def compare(a:Person, b:Person) = a.age.compare(b.age) + * } + * Sorting.quickSort(people)(AgeOrdering) + * }}} + * + * This trait and [[scala.math.Ordered]] both provide this same functionality, but + * in different ways. A type `T` can be given a single way to order itself by + * extending `Ordered`. Using `Ordering`, this same type may be sorted in many + * other ways. `Ordered` and `Ordering` both provide implicits allowing them to be + * used interchangeably. + * + * You can `import scala.math.Ordering.Implicits._` to gain access to other + * implicit orderings. + * + * @see [[scala.math.Ordered]], [[scala.util.Sorting]], [[scala.math.Ordering.Implicits]] + */ +trait Ordering[T] extends Comparator[T] with PartialOrdering[T] with Serializable { + outer => + + /** Returns whether a comparison between `x` and `y` is defined, and if so + * the result of `compare(x, y)`. + */ + def tryCompare(x: T, y: T): Some[Int] = Some(compare(x, y)) + + /** Returns an integer whose sign communicates how x compares to y. + * + * The result sign has the following meaning: + * + * - negative if x < y + * - positive if x > y + * - zero otherwise (if x == y) + */ + def compare(x: T, y: T): Int + + /** Return true if `x` <= `y` in the ordering. */ + override def lteq(x: T, y: T): Boolean = compare(x, y) <= 0 + + /** Return true if `x` >= `y` in the ordering. */ + override def gteq(x: T, y: T): Boolean = compare(x, y) >= 0 + + /** Return true if `x` < `y` in the ordering. */ + override def lt(x: T, y: T): Boolean = compare(x, y) < 0 + + /** Return true if `x` > `y` in the ordering. */ + override def gt(x: T, y: T): Boolean = compare(x, y) > 0 + + /** Return true if `x` == `y` in the ordering. */ + override def equiv(x: T, y: T): Boolean = compare(x, y) == 0 + + /** Return `x` if `x` >= `y`, otherwise `y`. */ + def max[U <: T](x: U, y: U): U = if (gteq(x, y)) x else y + + /** Return `x` if `x` <= `y`, otherwise `y`. */ + def min[U <: T](x: U, y: U): U = if (lteq(x, y)) x else y + + /** Return the opposite ordering of this one. + * + * Implementations overriding this method MUST override [[isReverseOf]] + * as well if they change the behavior at all (for example, caching does + * not require overriding it). + */ + override def reverse: Ordering[T] = new Ordering.Reverse[T](this) + + /** Returns whether or not the other ordering is the opposite + * ordering of this one. + * + * Equivalent to `other == this.reverse`. + * + * Implementations should only override this method if they are overriding + * [[reverse]] as well. + */ + def isReverseOf(other: Ordering[_]): Boolean = other match { + case that: Ordering.Reverse[_] => that.outer == this + case _ => false + } + + /** Given f, a function from U into T, creates an Ordering[U] whose compare + * function is equivalent to: + * + * {{{ + * def compare(x:U, y:U) = Ordering[T].compare(f(x), f(y)) + * }}} + */ + def on[U](f: U => T): Ordering[U] = new Ordering[U] { + def compare(x: U, y: U) = outer.compare(f(x), f(y)) + } + + /** Creates an Ordering[T] whose compare function returns the + * result of this Ordering's compare function, if it is non-zero, + * or else the result of `other`s compare function. + * + * @example + * {{{ + * case class Pair(a: Int, b: Int) + * + * val pairOrdering = Ordering.by[Pair, Int](_.a) + * .orElse(Ordering.by[Pair, Int](_.b)) + * }}} + * + * @param other an Ordering to use if this Ordering returns zero + */ + def orElse(other: Ordering[T]): Ordering[T] = (x, y) => { + val res1 = outer.compare(x, y) + if (res1 != 0) res1 else other.compare(x, y) + } + + /** Given f, a function from T into S, creates an Ordering[T] whose compare + * function returns the result of this Ordering's compare function, + * if it is non-zero, or else a result equivalent to: + * + * {{{ + * Ordering[S].compare(f(x), f(y)) + * }}} + * + * This function is equivalent to passing the result of `Ordering.by(f)` + * to `orElse`. + * + * @example + * {{{ + * case class Pair(a: Int, b: Int) + * + * val pairOrdering = Ordering.by[Pair, Int](_.a) + * .orElseBy[Int](_.b) + * }}} + */ + def orElseBy[S](f: T => S)(implicit ord: Ordering[S]): Ordering[T] = (x, y) => { + val res1 = outer.compare(x, y) + if (res1 != 0) res1 else ord.compare(f(x), f(y)) + } + + /** This inner class defines comparison operators available for `T`. + * + * It can't extend `AnyVal` because it is not a top-level class + * or a member of a statically accessible object. + */ + class OrderingOps(lhs: T) { + def <(rhs: T): Boolean = lt(lhs, rhs) + def <=(rhs: T): Boolean = lteq(lhs, rhs) + def >(rhs: T): Boolean = gt(lhs, rhs) + def >=(rhs: T): Boolean = gteq(lhs, rhs) + def equiv(rhs: T): Boolean = Ordering.this.equiv(lhs, rhs) + def max(rhs: T): T = Ordering.this.max(lhs, rhs) + def min(rhs: T): T = Ordering.this.min(lhs, rhs) + } + + /** This implicit method augments `T` with the comparison operators defined + * in `scala.math.Ordering.Ops`. + */ + implicit def mkOrderingOps(lhs: T): OrderingOps = new OrderingOps(lhs) +} + +trait LowPriorityOrderingImplicits { + + type AsComparable[A] = A => Comparable[_ >: A] + + /** This would conflict with all the nice implicit Orderings + * available, but thanks to the magic of prioritized implicits + * via subclassing we can make `Ordered[A] => Ordering[A]` only + * turn up if nothing else works. Since `Ordered[A]` extends + * `Comparable[A]` anyway, we can throw in some Java interop too. + */ + implicit def ordered[A](implicit asComparable: AsComparable[A]): Ordering[A] = new Ordering[A] { + def compare(x: A, y: A): Int = asComparable(x).compareTo(y) + } + + implicit def comparatorToOrdering[A](implicit cmp: Comparator[A]): Ordering[A] = new Ordering[A] { + def compare(x: A, y: A) = cmp.compare(x, y) + } +} + +/** This is the companion object for the [[scala.math.Ordering]] trait. + * + * It contains many implicit orderings as well as well as methods to construct + * new orderings. + */ +object Ordering extends LowPriorityOrderingImplicits { + private final val reverseSeed = 41 + private final val optionSeed = 43 + private final val iterableSeed = 47 + + @inline def apply[T](implicit ord: Ordering[T]) = ord + + /** An ordering which caches the value of its reverse. */ + sealed trait CachedReverse[T] extends Ordering[T] { + private[this] val _reverse = super.reverse + override final def reverse: Ordering[T] = _reverse + override final def isReverseOf(other: Ordering[_]): Boolean = other eq _reverse + } + + /** A reverse ordering */ + private final class Reverse[T](private[Ordering] val outer: Ordering[T]) extends Ordering[T] { + override def reverse: Ordering[T] = outer + override def isReverseOf(other: Ordering[_]): Boolean = other == outer + + def compare(x: T, y: T): Int = outer.compare(y, x) + override def lteq(x: T, y: T): Boolean = outer.lteq(y, x) + override def gteq(x: T, y: T): Boolean = outer.gteq(y, x) + override def lt(x: T, y: T): Boolean = outer.lt(y, x) + override def gt(x: T, y: T): Boolean = outer.gt(y, x) + override def equiv(x: T, y: T): Boolean = outer.equiv(y, x) + override def max[U <: T](x: U, y: U): U = outer.min(x, y) + override def min[U <: T](x: U, y: U): U = outer.max(x, y) + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Reverse[_] => this.outer == that.outer + case _ => false + } + override def hashCode(): Int = outer.hashCode() * reverseSeed + } + + @SerialVersionUID(-2996748994664583574L) + private final class IterableOrdering[CC[X] <: Iterable[X], T](private val ord: Ordering[T]) extends Ordering[CC[T]] { + def compare(x: CC[T], y: CC[T]): Int = { + val xe = x.iterator + val ye = y.iterator + + while (xe.hasNext && ye.hasNext) { + val res = ord.compare(xe.next(), ye.next()) + if (res != 0) return res + } + + Boolean.compare(xe.hasNext, ye.hasNext) + } + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: IterableOrdering[_, _] => this.ord == that.ord + case _ => false + } + override def hashCode(): Int = ord.hashCode() * iterableSeed + } + + trait ExtraImplicits { + /** Not in the standard scope due to the potential for divergence: + * For instance `implicitly[Ordering[Any]]` diverges in its presence. + */ + implicit def seqOrdering[CC[X] <: scala.collection.Seq[X], T](implicit ord: Ordering[T]): Ordering[CC[T]] = + new IterableOrdering[CC, T](ord) + + implicit def sortedSetOrdering[CC[X] <: scala.collection.SortedSet[X], T](implicit ord: Ordering[T]): Ordering[CC[T]] = + new IterableOrdering[CC, T](ord) + + /** This implicit creates a conversion from any value for which an + * implicit `Ordering` exists to the class which creates infix operations. + * With it imported, you can write methods as follows: + * + * {{{ + * def lessThan[T: Ordering](x: T, y: T) = x < y + * }}} + */ + implicit def infixOrderingOps[T](x: T)(implicit ord: Ordering[T]): Ordering[T]#OrderingOps = new ord.OrderingOps(x) + } + + /** An object containing implicits which are not in the default scope. */ + object Implicits extends ExtraImplicits { } + + /** Construct an Ordering[T] given a function `lt`. */ + def fromLessThan[T](cmp: (T, T) => Boolean): Ordering[T] = new Ordering[T] { + def compare(x: T, y: T) = if (cmp(x, y)) -1 else if (cmp(y, x)) 1 else 0 + // overrides to avoid multiple comparisons + override def lt(x: T, y: T): Boolean = cmp(x, y) + override def gt(x: T, y: T): Boolean = cmp(y, x) + override def gteq(x: T, y: T): Boolean = !cmp(x, y) + override def lteq(x: T, y: T): Boolean = !cmp(y, x) + } + + /** Given f, a function from T into S, creates an Ordering[T] whose compare + * function is equivalent to: + * + * {{{ + * def compare(x:T, y:T) = Ordering[S].compare(f(x), f(y)) + * }}} + * + * This function is an analogue to Ordering.on where the Ordering[S] + * parameter is passed implicitly. + */ + def by[T, S](f: T => S)(implicit ord: Ordering[S]): Ordering[T] = new Ordering[T] { + def compare(x: T, y: T) = ord.compare(f(x), f(y)) + override def lt(x: T, y: T): Boolean = ord.lt(f(x), f(y)) + override def gt(x: T, y: T): Boolean = ord.gt(f(x), f(y)) + override def gteq(x: T, y: T): Boolean = ord.gteq(f(x), f(y)) + override def lteq(x: T, y: T): Boolean = ord.lteq(f(x), f(y)) + } + + trait UnitOrdering extends Ordering[Unit] { + def compare(x: Unit, y: Unit) = 0 + } + @SerialVersionUID(4089257611611206746L) + implicit object Unit extends UnitOrdering + + trait BooleanOrdering extends Ordering[Boolean] { + def compare(x: Boolean, y: Boolean): Int = java.lang.Boolean.compare(x, y) + } + @SerialVersionUID(-94703182178890445L) + implicit object Boolean extends BooleanOrdering + + trait ByteOrdering extends Ordering[Byte] { + def compare(x: Byte, y: Byte): Int = java.lang.Byte.compare(x, y) + } + @SerialVersionUID(-2268545360148786406L) + implicit object Byte extends ByteOrdering + + trait CharOrdering extends Ordering[Char] { + def compare(x: Char, y: Char): Int = java.lang.Character.compare(x, y) + } + @SerialVersionUID(2588141633104296698L) + implicit object Char extends CharOrdering + + trait ShortOrdering extends Ordering[Short] { + def compare(x: Short, y: Short): Int = java.lang.Short.compare(x, y) + } + @SerialVersionUID(4919657051864630912L) + implicit object Short extends ShortOrdering + + trait IntOrdering extends Ordering[Int] { + def compare(x: Int, y: Int): Int = java.lang.Integer.compare(x, y) + } + @SerialVersionUID(-8412871093094815037L) + implicit object Int extends IntOrdering with CachedReverse[Int] + + trait LongOrdering extends Ordering[Long] { + def compare(x: Long, y: Long): Int = java.lang.Long.compare(x, y) + } + @SerialVersionUID(-5231423581640563981L) + implicit object Long extends LongOrdering + + /** `Ordering`s for `Float`s. + * + * The default extends `Ordering.Float.TotalOrdering`. + * + * `Ordering.Float.TotalOrdering` uses the `java.lang.Float.compare` semantics for all operations. + * Scala also provides the `Ordering.Float.IeeeOrdering` semantics. Which uses the IEEE 754 semantics + * for float ordering. + * + * Historically: `IeeeOrdering` was used in Scala from 2.10.x through 2.12.x. This changed in 2.13.0 + * to `TotalOrdering`. + * + * Prior to Scala 2.10.0, the `Ordering` instance used semantics + * consistent with `java.lang.Float.compare`. + * + * Scala 2.10.0 changed the implementation of `lt`, `equiv`, `min`, etc., to be + * IEEE 754 compliant, while keeping the `compare` method NOT compliant, + * creating an internally inconsistent instance. IEEE 754 specifies that + * `0.0F == -0.0F`. In addition, it requires all comparisons with `Float.NaN` return + * `false` thus `0.0F < Float.NaN`, `0.0F > Float.NaN`, and + * `Float.NaN == Float.NaN` all yield `false`, analogous `None` in `flatMap`. + * + * + * {{{ + * List(0.0F, 1.0F, 0.0F / 0.0F, -1.0F / 0.0F).sorted // List(-Infinity, 0.0, 1.0, NaN) + * List(0.0F, 1.0F, 0.0F / 0.0F, -1.0F / 0.0F).min // -Infinity + * implicitly[Ordering[Float]].lt(0.0F, 0.0F / 0.0F) // true + * { + * import Ordering.Float.IeeeOrdering + * List(0.0F, 1.0F, 0.0F / 0.0F, -1.0F / 0.0F).sorted // List(-Infinity, 0.0, 1.0, NaN) + * List(0.0F, 1.0F, 0.0F / 0.0F, -1.0F / 0.0F).min // NaN + * implicitly[Ordering[Float]].lt(0.0F, 0.0F / 0.0F) // false + * } + * }}} + * + * @define floatOrdering Because the behavior of `Float`s specified by IEEE is + * not consistent with a total ordering when dealing with + * `NaN`, there are two orderings defined for `Float`: + * `TotalOrdering`, which is consistent with a total + * ordering, and `IeeeOrdering`, which is consistent + * as much as possible with IEEE spec and floating point + * operations defined in [[scala.math]]. + */ + object Float { + /** An ordering for `Float`s which is a fully consistent total ordering, + * and treats `NaN` as larger than all other `Float` values; it behaves + * the same as [[java.lang.Float.compare]]. + * + * $floatOrdering + * + * This ordering may be preferable for sorting collections. + * + * @see [[IeeeOrdering]] + */ + trait TotalOrdering extends Ordering[Float] { + def compare(x: Float, y: Float) = java.lang.Float.compare(x, y) + } + @SerialVersionUID(2951539161283192433L) + implicit object TotalOrdering extends TotalOrdering + + /** An ordering for `Float`s which is consistent with IEEE specifications + * whenever possible. + * + * - `lt`, `lteq`, `equiv`, `gteq` and `gt` are consistent with primitive + * comparison operations for `Float`s, and return `false` when called with + * `NaN`. + * - `min` and `max` are consistent with `math.min` and `math.max`, and + * return `NaN` when called with `NaN` as either argument. + * - `compare` behaves the same as [[java.lang.Float.compare]]. + * + * $floatOrdering + * + * This ordering may be preferable for numeric contexts. + * + * @see [[TotalOrdering]] + */ + trait IeeeOrdering extends Ordering[Float] { + def compare(x: Float, y: Float) = java.lang.Float.compare(x, y) + + override def lteq(x: Float, y: Float): Boolean = x <= y + override def gteq(x: Float, y: Float): Boolean = x >= y + override def lt(x: Float, y: Float): Boolean = x < y + override def gt(x: Float, y: Float): Boolean = x > y + override def equiv(x: Float, y: Float): Boolean = x == y + override def max[U <: Float](x: U, y: U): U = math.max(x, y).asInstanceOf[U] + override def min[U <: Float](x: U, y: U): U = math.min(x, y).asInstanceOf[U] + } + @SerialVersionUID(2142189527751553605L) + implicit object IeeeOrdering extends IeeeOrdering + } + @migration( + " The default implicit ordering for floats now maintains consistency\n" + + " between its `compare` method and its `lt`, `min`, `equiv`, etc., methods,\n" + + " which means nonconforming to IEEE 754's behavior for -0.0F and NaN.\n" + + " The sort order of floats remains the same, however, with NaN at the end.\n" + + " Import Ordering.Float.IeeeOrdering to recover the previous behavior.\n" + + " See also https://www.scala-lang.org/api/current/scala/math/Ordering$$Float$.html.", "2.13.0") + @SerialVersionUID(-8500693657289762132L) + implicit object DeprecatedFloatOrdering extends Float.TotalOrdering + + /** `Ordering`s for `Double`s. + * + * The behavior of the comparison operations provided by the default (implicit) + * ordering on `Double` changed in 2.10.0 and 2.13.0. + * Prior to Scala 2.10.0, the `Ordering` instance used semantics + * consistent with `java.lang.Double.compare`. + * + * Scala 2.10.0 changed the implementation of `lt`, `equiv`, `min`, etc., to be + * IEEE 754 compliant, while keeping the `compare` method NOT compliant, + * creating an internally inconsistent instance. IEEE 754 specifies that + * `0.0 == -0.0`. In addition, it requires all comparisons with `Double.NaN` return + * `false` thus `0.0 < Double.NaN`, `0.0 > Double.NaN`, and + * `Double.NaN == Double.NaN` all yield `false`, analogous `None` in `flatMap`. + * + * Recognizing the limitation of the IEEE 754 semantics in terms of ordering, + * Scala 2.13.0 created two instances: `Ordering.Double.IeeeOrdering`, which retains + * the IEEE 754 semantics from Scala 2.12.x, and `Ordering.Double.TotalOrdering`, + * which brings back the `java.lang.Double.compare` semantics for all operations. + * The default extends `TotalOrdering`. + * + * {{{ + * List(0.0, 1.0, 0.0 / 0.0, -1.0 / 0.0).sorted // List(-Infinity, 0.0, 1.0, NaN) + * List(0.0, 1.0, 0.0 / 0.0, -1.0 / 0.0).min // -Infinity + * implicitly[Ordering[Double]].lt(0.0, 0.0 / 0.0) // true + * { + * import Ordering.Double.IeeeOrdering + * List(0.0, 1.0, 0.0 / 0.0, -1.0 / 0.0).sorted // List(-Infinity, 0.0, 1.0, NaN) + * List(0.0, 1.0, 0.0 / 0.0, -1.0 / 0.0).min // NaN + * implicitly[Ordering[Double]].lt(0.0, 0.0 / 0.0) // false + * } + * }}} + * + * @define doubleOrdering Because the behavior of `Double`s specified by IEEE is + * not consistent with a total ordering when dealing with + * `NaN`, there are two orderings defined for `Double`: + * `TotalOrdering`, which is consistent with a total + * ordering, and `IeeeOrdering`, which is consistent + * as much as possible with IEEE spec and floating point + * operations defined in [[scala.math]]. + */ + object Double { + /** An ordering for `Double`s which is a fully consistent total ordering, + * and treats `NaN` as larger than all other `Double` values; it behaves + * the same as [[java.lang.Double.compare]]. + * + * $doubleOrdering + * + * This ordering may be preferable for sorting collections. + * + * @see [[IeeeOrdering]] + */ + trait TotalOrdering extends Ordering[Double] { + def compare(x: Double, y: Double) = java.lang.Double.compare(x, y) + } + @SerialVersionUID(-831119229746134011L) + implicit object TotalOrdering extends TotalOrdering + + /** An ordering for `Double`s which is consistent with IEEE specifications + * whenever possible. + * + * - `lt`, `lteq`, `equiv`, `gteq` and `gt` are consistent with primitive + * comparison operations for `Double`s, and return `false` when called with + * `NaN`. + * - `min` and `max` are consistent with `math.min` and `math.max`, and + * return `NaN` when called with `NaN` as either argument. + * - `compare` behaves the same as [[java.lang.Double.compare]]. + * + * $doubleOrdering + * + * This ordering may be preferable for numeric contexts. + * + * @see [[TotalOrdering]] + */ + trait IeeeOrdering extends Ordering[Double] { + def compare(x: Double, y: Double) = java.lang.Double.compare(x, y) + + override def lteq(x: Double, y: Double): Boolean = x <= y + override def gteq(x: Double, y: Double): Boolean = x >= y + override def lt(x: Double, y: Double): Boolean = x < y + override def gt(x: Double, y: Double): Boolean = x > y + override def equiv(x: Double, y: Double): Boolean = x == y + override def max[U <: Double](x: U, y: U): U = math.max(x, y).asInstanceOf[U] + override def min[U <: Double](x: U, y: U): U = math.min(x, y).asInstanceOf[U] + } + @SerialVersionUID(5722631152457877238L) + implicit object IeeeOrdering extends IeeeOrdering + } + @migration( + " The default implicit ordering for doubles now maintains consistency\n" + + " between its `compare` method and its `lt`, `min`, `equiv`, etc., methods,\n" + + " which means nonconforming to IEEE 754's behavior for -0.0 and NaN.\n" + + " The sort order of doubles remains the same, however, with NaN at the end.\n" + + " Import Ordering.Double.IeeeOrdering to recover the previous behavior.\n" + + " See also https://www.scala-lang.org/api/current/scala/math/Ordering$$Double$.html.", "2.13.0") + @SerialVersionUID(-7340686892557971538L) + implicit object DeprecatedDoubleOrdering extends Double.TotalOrdering + + trait BigIntOrdering extends Ordering[BigInt] { + def compare(x: BigInt, y: BigInt) = x.compare(y) + } + @SerialVersionUID(-3075297647817530785L) + implicit object BigInt extends BigIntOrdering + + trait BigDecimalOrdering extends Ordering[BigDecimal] { + def compare(x: BigDecimal, y: BigDecimal) = x.compare(y) + } + @SerialVersionUID(-833457937756812905L) + implicit object BigDecimal extends BigDecimalOrdering + + trait StringOrdering extends Ordering[String] { + def compare(x: String, y: String) = x.compareTo(y) + } + @SerialVersionUID(1302240016074071079L) + implicit object String extends StringOrdering + + trait SymbolOrdering extends Ordering[Symbol] { + def compare(x: Symbol, y: Symbol): Int = x.name.compareTo(y.name) + } + @SerialVersionUID(1996702162912307637L) + implicit object Symbol extends SymbolOrdering + + trait OptionOrdering[T] extends Ordering[Option[T]] { + def optionOrdering: Ordering[T] + def compare(x: Option[T], y: Option[T]) = (x, y) match { + case (None, None) => 0 + case (None, _) => -1 + case (_, None) => 1 + case (Some(x), Some(y)) => optionOrdering.compare(x, y) + } + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: OptionOrdering[_] => this.optionOrdering == that.optionOrdering + case _ => false + } + override def hashCode(): Int = optionOrdering.hashCode() * optionSeed + } + implicit def Option[T](implicit ord: Ordering[T]): Ordering[Option[T]] = { + @SerialVersionUID(6958068162830323876L) + class O extends OptionOrdering[T] { val optionOrdering = ord } + new O() + } + + /** @deprecated Iterables are not guaranteed to have a consistent order, so the `Ordering` + * returned by this method may not be stable or meaningful. If you are using a type + * with a consistent order (such as `Seq`), use its `Ordering` (found in the + * [[Implicits]] object) instead. + */ + @deprecated("Iterables are not guaranteed to have a consistent order; if using a type with a " + + "consistent order (e.g. Seq), use its Ordering (found in the Ordering.Implicits object)", since = "2.13.0") + implicit def Iterable[T](implicit ord: Ordering[T]): Ordering[Iterable[T]] = + new IterableOrdering[Iterable, T](ord) + + implicit def Tuple2[T1, T2](implicit ord1: Ordering[T1], ord2: Ordering[T2]): Ordering[(T1, T2)] = + new Tuple2Ordering(ord1, ord2) + + @SerialVersionUID(4945084135299531202L) + private[this] final class Tuple2Ordering[T1, T2](private val ord1: Ordering[T1], + private val ord2: Ordering[T2]) extends Ordering[(T1, T2)] { + def compare(x: (T1, T2), y: (T1, T2)): Int = { + val compare1 = ord1.compare(x._1, y._1) + if (compare1 != 0) return compare1 + ord2.compare(x._2, y._2) + } + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple2Ordering[_, _] => + this.ord1 == that.ord1 && + this.ord2 == that.ord2 + case _ => false + } + override def hashCode(): Int = (ord1, ord2).hashCode() + } + + implicit def Tuple3[T1, T2, T3](implicit ord1: Ordering[T1], ord2: Ordering[T2], ord3: Ordering[T3]) : Ordering[(T1, T2, T3)] = + new Tuple3Ordering(ord1, ord2, ord3) + + @SerialVersionUID(-5367223704121832335L) + private[this] final class Tuple3Ordering[T1, T2, T3](private val ord1: Ordering[T1], + private val ord2: Ordering[T2], + private val ord3: Ordering[T3]) extends Ordering[(T1, T2, T3)] { + def compare(x: (T1, T2, T3), y: (T1, T2, T3)): Int = { + val compare1 = ord1.compare(x._1, y._1) + if (compare1 != 0) return compare1 + val compare2 = ord2.compare(x._2, y._2) + if (compare2 != 0) return compare2 + ord3.compare(x._3, y._3) + } + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple3Ordering[_, _, _] => + this.ord1 == that.ord1 && + this.ord2 == that.ord2 && + this.ord3 == that.ord3 + case _ => false + } + override def hashCode(): Int = (ord1, ord2, ord3).hashCode() + } + + implicit def Tuple4[T1, T2, T3, T4](implicit ord1: Ordering[T1], ord2: Ordering[T2], ord3: Ordering[T3], ord4: Ordering[T4]) : Ordering[(T1, T2, T3, T4)] = + new Tuple4Ordering(ord1, ord2, ord3, ord4) + + @SerialVersionUID(-6055313861145218178L) + private[this] final class Tuple4Ordering[T1, T2, T3, T4](private val ord1: Ordering[T1], + private val ord2: Ordering[T2], + private val ord3: Ordering[T3], + private val ord4: Ordering[T4]) + extends Ordering[(T1, T2, T3, T4)] { + def compare(x: (T1, T2, T3, T4), y: (T1, T2, T3, T4)): Int = { + val compare1 = ord1.compare(x._1, y._1) + if (compare1 != 0) return compare1 + val compare2 = ord2.compare(x._2, y._2) + if (compare2 != 0) return compare2 + val compare3 = ord3.compare(x._3, y._3) + if (compare3 != 0) return compare3 + ord4.compare(x._4, y._4) + } + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple4Ordering[_, _, _, _] => + this.ord1 == that.ord1 && + this.ord2 == that.ord2 && + this.ord3 == that.ord3 && + this.ord4 == that.ord4 + case _ => false + } + override def hashCode(): Int = (ord1, ord2, ord3, ord4).hashCode() + } + + implicit def Tuple5[T1, T2, T3, T4, T5](implicit ord1: Ordering[T1], ord2: Ordering[T2], ord3: Ordering[T3], ord4: Ordering[T4], ord5: Ordering[T5]): Ordering[(T1, T2, T3, T4, T5)] = + new Tuple5Ordering(ord1, ord2, ord3, ord4, ord5) + + @SerialVersionUID(-5517329921227646061L) + private[this] final class Tuple5Ordering[T1, T2, T3, T4, T5](private val ord1: Ordering[T1], + private val ord2: Ordering[T2], + private val ord3: Ordering[T3], + private val ord4: Ordering[T4], + private val ord5: Ordering[T5]) + extends Ordering[(T1, T2, T3, T4, T5)] { + def compare(x: (T1, T2, T3, T4, T5), y: (T1, T2, T3, T4, T5)): Int = { + val compare1 = ord1.compare(x._1, y._1) + if (compare1 != 0) return compare1 + val compare2 = ord2.compare(x._2, y._2) + if (compare2 != 0) return compare2 + val compare3 = ord3.compare(x._3, y._3) + if (compare3 != 0) return compare3 + val compare4 = ord4.compare(x._4, y._4) + if (compare4 != 0) return compare4 + ord5.compare(x._5, y._5) + } + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple5Ordering[_, _, _, _, _] => + this.ord1 == that.ord1 && + this.ord2 == that.ord2 && + this.ord3 == that.ord3 && + this.ord4 == that.ord4 && + this.ord5 == that.ord5 + case _ => false + } + override def hashCode(): Int = (ord1, ord2, ord3, ord4, ord5).hashCode() + } + + @SerialVersionUID(3045467524192969060L) + implicit def Tuple6[T1, T2, T3, T4, T5, T6](implicit ord1: Ordering[T1], ord2: Ordering[T2], ord3: Ordering[T3], ord4: Ordering[T4], ord5: Ordering[T5], ord6: Ordering[T6]): Ordering[(T1, T2, T3, T4, T5, T6)] = + new Tuple6Ordering(ord1, ord2, ord3, ord4, ord5, ord6) + + private[this] final class Tuple6Ordering[T1, T2, T3, T4, T5, T6](private val ord1: Ordering[T1], + private val ord2: Ordering[T2], + private val ord3: Ordering[T3], + private val ord4: Ordering[T4], + private val ord5: Ordering[T5], + private val ord6: Ordering[T6]) + extends Ordering[(T1, T2, T3, T4, T5, T6)] { + def compare(x: (T1, T2, T3, T4, T5, T6), y: (T1, T2, T3, T4, T5, T6)): Int = { + val compare1 = ord1.compare(x._1, y._1) + if (compare1 != 0) return compare1 + val compare2 = ord2.compare(x._2, y._2) + if (compare2 != 0) return compare2 + val compare3 = ord3.compare(x._3, y._3) + if (compare3 != 0) return compare3 + val compare4 = ord4.compare(x._4, y._4) + if (compare4 != 0) return compare4 + val compare5 = ord5.compare(x._5, y._5) + if (compare5 != 0) return compare5 + ord6.compare(x._6, y._6) + } + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple6Ordering[_, _, _, _, _, _] => + this.ord1 == that.ord1 && + this.ord2 == that.ord2 && + this.ord3 == that.ord3 && + this.ord4 == that.ord4 && + this.ord5 == that.ord5 && + this.ord6 == that.ord6 + case _ => false + } + override def hashCode(): Int = (ord1, ord2, ord3, ord4, ord5, ord6).hashCode() + } + + implicit def Tuple7[T1, T2, T3, T4, T5, T6, T7](implicit ord1: Ordering[T1], ord2: Ordering[T2], ord3: Ordering[T3], ord4: Ordering[T4], ord5: Ordering[T5], ord6: Ordering[T6], ord7: Ordering[T7]): Ordering[(T1, T2, T3, T4, T5, T6, T7)] = + new Tuple7Ordering(ord1, ord2, ord3, ord4, ord5, ord6, ord7) + + @SerialVersionUID(1253188205893682451L) + private[this] final class Tuple7Ordering[T1, T2, T3, T4, T5, T6, T7](private val ord1: Ordering[T1], + private val ord2: Ordering[T2], + private val ord3: Ordering[T3], + private val ord4: Ordering[T4], + private val ord5: Ordering[T5], + private val ord6: Ordering[T6], + private val ord7: Ordering[T7]) + extends Ordering[(T1, T2, T3, T4, T5, T6, T7)] { + def compare(x: (T1, T2, T3, T4, T5, T6, T7), y: (T1, T2, T3, T4, T5, T6, T7)): Int = { + val compare1 = ord1.compare(x._1, y._1) + if (compare1 != 0) return compare1 + val compare2 = ord2.compare(x._2, y._2) + if (compare2 != 0) return compare2 + val compare3 = ord3.compare(x._3, y._3) + if (compare3 != 0) return compare3 + val compare4 = ord4.compare(x._4, y._4) + if (compare4 != 0) return compare4 + val compare5 = ord5.compare(x._5, y._5) + if (compare5 != 0) return compare5 + val compare6 = ord6.compare(x._6, y._6) + if (compare6 != 0) return compare6 + ord7.compare(x._7, y._7) + } + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple7Ordering[_, _, _, _, _, _, _] => + this.ord1 == that.ord1 && + this.ord2 == that.ord2 && + this.ord3 == that.ord3 && + this.ord4 == that.ord4 && + this.ord5 == that.ord5 && + this.ord6 == that.ord6 && + this.ord7 == that.ord7 + case _ => false + } + override def hashCode(): Int = (ord1, ord2, ord3, ord4, ord5, ord6, ord7).hashCode() + } + + @SerialVersionUID(4003095353309354068L) + implicit def Tuple8[T1, T2, T3, T4, T5, T6, T7, T8](implicit ord1: Ordering[T1], ord2: Ordering[T2], ord3: Ordering[T3], ord4: Ordering[T4], ord5: Ordering[T5], ord6: Ordering[T6], ord7: Ordering[T7], ord8: Ordering[T8]): Ordering[(T1, T2, T3, T4, T5, T6, T7, T8)] = + new Tuple8Ordering(ord1, ord2, ord3, ord4, ord5, ord6, ord7, ord8) + + private[this] final class Tuple8Ordering[T1, T2, T3, T4, T5, T6, T7, T8](private val ord1: Ordering[T1], + private val ord2: Ordering[T2], + private val ord3: Ordering[T3], + private val ord4: Ordering[T4], + private val ord5: Ordering[T5], + private val ord6: Ordering[T6], + private val ord7: Ordering[T7], + private val ord8: Ordering[T8]) + extends Ordering[(T1, T2, T3, T4, T5, T6, T7, T8)] { + def compare(x: (T1, T2, T3, T4, T5, T6, T7, T8), y: (T1, T2, T3, T4, T5, T6, T7, T8)): Int = { + val compare1 = ord1.compare(x._1, y._1) + if (compare1 != 0) return compare1 + val compare2 = ord2.compare(x._2, y._2) + if (compare2 != 0) return compare2 + val compare3 = ord3.compare(x._3, y._3) + if (compare3 != 0) return compare3 + val compare4 = ord4.compare(x._4, y._4) + if (compare4 != 0) return compare4 + val compare5 = ord5.compare(x._5, y._5) + if (compare5 != 0) return compare5 + val compare6 = ord6.compare(x._6, y._6) + if (compare6 != 0) return compare6 + val compare7 = ord7.compare(x._7, y._7) + if (compare7 != 0) return compare7 + ord8.compare(x._8, y._8) + } + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple8Ordering[_, _, _, _, _, _, _, _] => + this.ord1 == that.ord1 && + this.ord2 == that.ord2 && + this.ord3 == that.ord3 && + this.ord4 == that.ord4 && + this.ord5 == that.ord5 && + this.ord6 == that.ord6 && + this.ord7 == that.ord7 && + this.ord8 == that.ord8 + case _ => false + } + override def hashCode(): Int = (ord1, ord2, ord3, ord4, ord5, ord6, ord7, ord8).hashCode() + } + + @SerialVersionUID(8185342054829975001L) + implicit def Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9](implicit ord1: Ordering[T1], ord2: Ordering[T2], ord3: Ordering[T3], ord4: Ordering[T4], ord5: Ordering[T5], ord6: Ordering[T6], ord7: Ordering[T7], ord8 : Ordering[T8], ord9: Ordering[T9]): Ordering[(T1, T2, T3, T4, T5, T6, T7, T8, T9)] = + new Tuple9Ordering(ord1, ord2, ord3, ord4, ord5, ord6, ord7, ord8, ord9) + + private[this] final class Tuple9Ordering[T1, T2, T3, T4, T5, T6, T7, T8, T9](private val ord1: Ordering[T1], + private val ord2: Ordering[T2], + private val ord3: Ordering[T3], + private val ord4: Ordering[T4], + private val ord5: Ordering[T5], + private val ord6: Ordering[T6], + private val ord7: Ordering[T7], + private val ord8: Ordering[T8], + private val ord9: Ordering[T9]) + extends Ordering[(T1, T2, T3, T4, T5, T6, T7, T8, T9)] { + def compare(x: (T1, T2, T3, T4, T5, T6, T7, T8, T9), y: (T1, T2, T3, T4, T5, T6, T7, T8, T9)): Int = { + val compare1 = ord1.compare(x._1, y._1) + if (compare1 != 0) return compare1 + val compare2 = ord2.compare(x._2, y._2) + if (compare2 != 0) return compare2 + val compare3 = ord3.compare(x._3, y._3) + if (compare3 != 0) return compare3 + val compare4 = ord4.compare(x._4, y._4) + if (compare4 != 0) return compare4 + val compare5 = ord5.compare(x._5, y._5) + if (compare5 != 0) return compare5 + val compare6 = ord6.compare(x._6, y._6) + if (compare6 != 0) return compare6 + val compare7 = ord7.compare(x._7, y._7) + if (compare7 != 0) return compare7 + val compare8 = ord8.compare(x._8, y._8) + if (compare8 != 0) return compare8 + ord9.compare(x._9, y._9) + } + + override def equals(obj: scala.Any): Boolean = obj match { + case that: AnyRef if this eq that => true + case that: Tuple9Ordering[_, _, _, _, _, _, _, _, _] => + this.ord1 == that.ord1 && + this.ord2 == that.ord2 && + this.ord3 == that.ord3 && + this.ord4 == that.ord4 && + this.ord5 == that.ord5 && + this.ord6 == that.ord6 && + this.ord7 == that.ord7 && + this.ord8 == that.ord8 && + this.ord9 == that.ord9 + case _ => false + } + override def hashCode(): Int = (ord1, ord2, ord3, ord4, ord5, ord6, ord7, ord8, ord9).hashCode() + } +} diff --git a/library/src/scala/math/PartialOrdering.scala b/library/src/scala/math/PartialOrdering.scala new file mode 100644 index 000000000000..0ba83b9b459a --- /dev/null +++ b/library/src/scala/math/PartialOrdering.scala @@ -0,0 +1,90 @@ +/* + * 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 math + +import scala.language.`2.13` + +/** A trait for representing partial orderings. It is important to + * distinguish between a type that has a partial order and a representation + * of partial ordering on some type. This trait is for representing the + * latter. + * + * A [[https://en.wikipedia.org/wiki/Partially_ordered_set partial ordering]] is a + * binary relation on a type `T`, exposed as the `lteq` method of this trait. + * This relation must be: + * + * - reflexive: `lteq(x, x) == '''true'''`, for any `x` of type `T`. + * - anti-symmetric: if `lteq(x, y) == '''true'''` and + * `lteq(y, x) == '''true'''` + * then `equiv(x, y) == '''true'''`, for any `x` and `y` of type `T`. + * - transitive: if `lteq(x, y) == '''true'''` and + * `lteq(y, z) == '''true'''` then `lteq(x, z) == '''true'''`, + * for any `x`, `y`, and `z` of type `T`. + * + * Additionally, a partial ordering induces an + * [[https://en.wikipedia.org/wiki/Equivalence_relation equivalence relation]] + * on a type `T`: `x` and `y` of type `T` are equivalent if and only if + * `lteq(x, y) && lteq(y, x) == '''true'''`. This equivalence relation is + * exposed as the `equiv` method, inherited from the + * [[scala.math.Equiv Equiv]] trait. + */ + +trait PartialOrdering[T] extends Equiv[T] { + outer => + + /** Result of comparing `x` with operand `y`. + * Returns `None` if operands are not comparable. + * If operands are comparable, returns `Some(r)` where + * - `r < 0` iff `x < y` + * - `r == 0` iff `x == y` + * - `r > 0` iff `x > y` + */ + def tryCompare(x: T, y: T): Option[Int] + + /** Returns `'''true'''` iff `x` comes before `y` in the ordering. + */ + def lteq(x: T, y: T): Boolean + + /** Returns `'''true'''` iff `y` comes before `x` in the ordering. + */ + def gteq(x: T, y: T): Boolean = lteq(y, x) + + /** Returns `'''true'''` iff `x` comes before `y` in the ordering + * and is not the same as `y`. + */ + def lt(x: T, y: T): Boolean = lteq(x, y) && !equiv(x, y) + + /** Returns `'''true'''` iff `y` comes before `x` in the ordering + * and is not the same as `x`. + */ + def gt(x: T, y: T): Boolean = gteq(x, y) && !equiv(x, y) + + /** Returns `'''true'''` iff `x` is equivalent to `y` in the ordering. + */ + def equiv(x: T, y: T): Boolean = lteq(x,y) && lteq(y,x) + + def reverse : PartialOrdering[T] = new PartialOrdering[T] { + override def reverse = outer + def tryCompare(x: T, y: T) = outer.tryCompare(y, x) + def lteq(x: T, y: T) = outer.lteq(y, x) + override def gteq(x: T, y: T) = outer.gteq(y, x) + override def lt(x: T, y: T) = outer.lt(y, x) + override def gt(x: T, y: T) = outer.gt(y, x) + override def equiv(x: T, y: T) = outer.equiv(y, x) + } +} + +object PartialOrdering { + @inline def apply[T](implicit ev: PartialOrdering[T]): PartialOrdering[T] = ev +} diff --git a/library/src/scala/math/PartiallyOrdered.scala b/library/src/scala/math/PartiallyOrdered.scala new file mode 100644 index 000000000000..0d9c2574c2cf --- /dev/null +++ b/library/src/scala/math/PartiallyOrdered.scala @@ -0,0 +1,56 @@ +/* + * 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 math + +import scala.language.`2.13` + +/** A class for partially ordered data. + */ +trait PartiallyOrdered[+A] extends Any { + + type AsPartiallyOrdered[B] = B => PartiallyOrdered[B] + + /** Result of comparing `'''this'''` with operand `that`. + * Returns `None` if operands are not comparable. + * If operands are comparable, returns `Some(x)` where + * - `x < 0` iff `'''this''' < that` + * - `x == 0` iff `'''this''' == that` + * - `x > 0` iff `'''this''' > that` + */ + def tryCompareTo [B >: A: AsPartiallyOrdered](that: B): Option[Int] + + def < [B >: A: AsPartiallyOrdered](that: B): Boolean = + (this tryCompareTo that) match { + case Some(x) if x < 0 => true + case _ => false + } + + def > [B >: A: AsPartiallyOrdered](that: B): Boolean = + (this tryCompareTo that) match { + case Some(x) if x > 0 => true + case _ => false + } + + def <= [B >: A: AsPartiallyOrdered](that: B): Boolean = + (this tryCompareTo that) match { + case Some(x) if x <= 0 => true + case _ => false + } + + def >= [B >: A: AsPartiallyOrdered](that: B): Boolean = + (this tryCompareTo that) match { + case Some(x) if x >= 0 => true + case _ => false + } +} diff --git a/library/src/scala/math/ScalaNumber.java b/library/src/scala/math/ScalaNumber.java new file mode 100644 index 000000000000..5ed76ec3fb22 --- /dev/null +++ b/library/src/scala/math/ScalaNumber.java @@ -0,0 +1,20 @@ +/* + * 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.math; + +/** A marker class for Number types introduced by Scala + */ +public abstract class ScalaNumber extends java.lang.Number { + protected abstract boolean isWhole(); + public abstract Object underlying(); +} diff --git a/library/src/scala/math/ScalaNumericConversions.scala b/library/src/scala/math/ScalaNumericConversions.scala new file mode 100644 index 000000000000..b265bedaa7d2 --- /dev/null +++ b/library/src/scala/math/ScalaNumericConversions.scala @@ -0,0 +1,124 @@ +/* + * 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 math + +import scala.language.`2.13` + +/** A slightly more specific conversion trait for classes which + * extend ScalaNumber (which excludes value classes.) + */ +trait ScalaNumericConversions extends ScalaNumber with ScalaNumericAnyConversions { + def underlying: Object +} + +/** Conversions which present a consistent conversion interface + * across all the numeric types, suitable for use in value classes. + */ +trait ScalaNumericAnyConversions extends Any { + /** @return `'''true'''` if this number has no decimal component, `'''false'''` otherwise. */ + def isWhole: Boolean + + def byteValue: Byte + def shortValue: Short + def intValue: Int + def longValue: Long + def floatValue: Float + def doubleValue: Double + + /** Returns the value of this as a [[scala.Char]]. This may involve + * rounding or truncation. + */ + def toChar = intValue.toChar + + /** Returns the value of this as a [[scala.Byte]]. This may involve + * rounding or truncation. + */ + def toByte = byteValue + + /** Returns the value of this as a [[scala.Short]]. This may involve + * rounding or truncation. + */ + def toShort = shortValue + + /** Returns the value of this as an [[scala.Int]]. This may involve + * rounding or truncation. + */ + def toInt = intValue + + /** Returns the value of this as a [[scala.Long]]. This may involve + * rounding or truncation. + */ + def toLong = longValue + + /** Returns the value of this as a [[scala.Float]]. This may involve + * rounding or truncation. + */ + def toFloat = floatValue + + /** Returns the value of this as a [[scala.Double]]. This may involve + * rounding or truncation. + */ + def toDouble = doubleValue + + /** Returns `true` iff this has a zero fractional part, and is within the + * range of [[scala.Byte]] MinValue and MaxValue; otherwise returns `false`. + */ + def isValidByte = isWhole && (toInt == toByte) + + /** Returns `true` iff this has a zero fractional part, and is within the + * range of [[scala.Short]] MinValue and MaxValue; otherwise returns `false`. + */ + def isValidShort = isWhole && (toInt == toShort) + + /** Returns `true` iff this has a zero fractional part, and is within the + * range of [[scala.Int]] MinValue and MaxValue; otherwise returns `false`. + */ + def isValidInt = isWhole && (toLong == toInt) + + /** Returns `true` iff this has a zero fractional part, and is within the + * range of [[scala.Char]] MinValue and MaxValue; otherwise returns `false`. + */ + def isValidChar = isWhole && (toInt >= Char.MinValue && toInt <= Char.MaxValue) + + protected def unifiedPrimitiveHashcode = { + val lv = toLong + if (lv >= Int.MinValue && lv <= Int.MaxValue) lv.toInt + else lv.## + } + + /** Should only be called after all known non-primitive + * types have been excluded. This method won't dispatch + * anywhere else after checking against the primitives + * to avoid infinite recursion between equals and this on + * unknown "Number" variants. + * + * Additionally, this should only be called if the numeric + * type is happy to be converted to Long, Float, and Double. + * If for instance a BigInt much larger than the Long range is + * sent here, it will claim equality with whatever Long is left + * in its lower 64 bits. Or a BigDecimal with more precision + * than Double can hold: same thing. There's no way given the + * interface available here to prevent this error. + */ + protected def unifiedPrimitiveEquals(x: Any) = x match { + case x: Char => isValidChar && (toInt == x.toInt) + case x: Byte => isValidByte && (toByte == x) + case x: Short => isValidShort && (toShort == x) + case x: Int => isValidInt && (toInt == x) + case x: Long => toLong == x + case x: Float => toFloat == x + case x: Double => toDouble == x + case _ => false + } +} diff --git a/library/src/scala/math/package.scala b/library/src/scala/math/package.scala new file mode 100644 index 000000000000..203db407069b --- /dev/null +++ b/library/src/scala/math/package.scala @@ -0,0 +1,440 @@ +/* + * 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.language.`2.13` + +/** The package object `scala.math` contains methods for performing basic + * numeric operations such as elementary exponential, logarithmic, root and + * trigonometric functions. + * + * All methods forward to [[java.lang.Math]] unless otherwise noted. + * + * @see [[java.lang.Math]] + * + * @groupname math-const Mathematical Constants + * @groupprio math-const 10 + * + * @groupname minmax Minimum and Maximum + * @groupdesc minmax Find the min or max of two numbers. Note: [[scala.collection.IterableOnceOps]] has + * min and max methods which determine the min or max of a collection. + * @groupprio minmax 20 + * + * @groupname rounding Rounding + * @groupprio rounding 30 + * + * @groupname scaling Scaling + * @groupdesc scaling Scaling with rounding guarantees + * @groupprio scaling 40 + * + * @groupname explog Exponential and Logarithmic + * @groupprio explog 50 + * + * @groupname trig Trigonometric + * @groupdesc trig Arguments in radians + * @groupprio trig 60 + * + * @groupname angle-conversion Angular Measurement Conversion + * @groupprio angle-conversion 70 + * + * @groupname hyperbolic Hyperbolic + * @groupprio hyperbolic 80 + * + * @groupname abs Absolute Values + * @groupdesc abs Determine the magnitude of a value by discarding the sign. Results are >= 0. + * @groupprio abs 90 + * + * @groupname signs Signs + * @groupdesc signs For `signum` extract the sign of a value. Results are -1, 0 or 1. + * Note the `signum` methods are not pure forwarders to the Java versions. + * In particular, the return type of `java.lang.Long.signum` is `Int`, + * but here it is widened to `Long` so that each overloaded variant + * will return the same numeric type it is passed. + * @groupprio signs 100 + * + * @groupname root-extraction Root Extraction + * @groupprio root-extraction 110 + * + * @groupname polar-coords Polar Coordinates + * @groupprio polar-coords 120 + * + * @groupname ulp Unit of Least Precision + * @groupprio ulp 130 + * + * @groupname randomisation Pseudo Random Number Generation + * @groupprio randomisation 140 + * + * @groupname exact Exact Arithmetic + * @groupdesc exact Integral addition, multiplication, stepping and conversion throwing ArithmeticException instead of underflowing or overflowing + * @groupprio exact 150 + * + * @groupname modquo Modulus and Quotient + * @groupdesc modquo Calculate quotient values by rounding to negative infinity + * @groupprio modquo 160 + * + * @groupname adjacent-float Adjacent Floats + * @groupprio adjacent-float 170 + */ +package object math { + /** The `Double` value that is closer than any other to `e`, the base of + * the natural logarithms. + * @group math-const + */ + @inline final val E = java.lang.Math.E + + /** The `Double` value that is closer than any other to `pi`, the ratio of + * the circumference of a circle to its diameter. + * @group math-const + */ + @inline final val Pi = java.lang.Math.PI + + /** Returns a `Double` value with a positive sign, greater than or equal + * to `0.0` and less than `1.0`. + * + * @group randomisation + */ + def random(): Double = java.lang.Math.random() + + /** @group trig */ + def sin(x: Double): Double = java.lang.Math.sin(x) + /** @group trig */ + def cos(x: Double): Double = java.lang.Math.cos(x) + /** @group trig */ + def tan(x: Double): Double = java.lang.Math.tan(x) + /** @group trig */ + def asin(x: Double): Double = java.lang.Math.asin(x) + /** @group trig */ + def acos(x: Double): Double = java.lang.Math.acos(x) + /** @group trig */ + def atan(x: Double): Double = java.lang.Math.atan(x) + + /** Converts an angle measured in degrees to an approximately equivalent + * angle measured in radians. + * + * @param x an angle, in degrees + * @return the measurement of the angle `x` in radians. + * @group angle-conversion + */ + def toRadians(x: Double): Double = java.lang.Math.toRadians(x) + + /** Converts an angle measured in radians to an approximately equivalent + * angle measured in degrees. + * + * @param x angle, in radians + * @return the measurement of the angle `x` in degrees. + * @group angle-conversion + */ + def toDegrees(x: Double): Double = java.lang.Math.toDegrees(x) + + /** Converts rectangular coordinates `(x, y)` to polar `(r, theta)`. + * + * @param x the ordinate coordinate + * @param y the abscissa coordinate + * @return the ''theta'' component of the point `(r, theta)` in polar + * coordinates that corresponds to the point `(x, y)` in + * Cartesian coordinates. + * @group polar-coords + */ + def atan2(y: Double, x: Double): Double = java.lang.Math.atan2(y, x) + + /** Returns the square root of the sum of the squares of both given `Double` + * values without intermediate underflow or overflow. + * + * The ''r'' component of the point `(r, theta)` in polar + * coordinates that corresponds to the point `(x, y)` in + * Cartesian coordinates. + * @group polar-coords + */ + def hypot(x: Double, y: Double): Double = java.lang.Math.hypot(x, y) + + // ----------------------------------------------------------------------- + // rounding functions + // ----------------------------------------------------------------------- + + /** @group rounding */ + def ceil(x: Double): Double = java.lang.Math.ceil(x) + /** @group rounding */ + def floor(x: Double): Double = java.lang.Math.floor(x) + + /** Returns the `Double` value that is closest in value to the + * argument and is equal to a mathematical integer. + * + * @param x a `Double` value + * @return the closest floating-point value to a that is equal to a + * mathematical integer. + * @group rounding + */ + def rint(x: Double): Double = java.lang.Math.rint(x) + + /** There is no reason to round a `Long`, but this method prevents unintended conversion to `Float` followed by rounding to `Int`. + * + * @note Does not forward to [[java.lang.Math]] + * @group rounding + */ + @deprecated("This is an integer type; there is no reason to round it. Perhaps you meant to call this with a floating-point value?", "2.11.0") + def round(x: Long): Long = x + + /** Returns the closest `Int` to the argument. + * + * @param x a floating-point value to be rounded to a `Int`. + * @return the value of the argument rounded to the nearest `Int` value. + * @group rounding + */ + def round(x: Float): Int = java.lang.Math.round(x) + + /** Returns the closest `Long` to the argument. + * + * @param x a floating-point value to be rounded to a `Long`. + * @return the value of the argument rounded to the nearest`long` value. + * @group rounding + */ + def round(x: Double): Long = java.lang.Math.round(x) + + /** @group abs */ + def abs(x: Int): Int = java.lang.Math.abs(x) + /** @group abs */ + def abs(x: Long): Long = java.lang.Math.abs(x) + /** @group abs */ + def abs(x: Float): Float = java.lang.Math.abs(x) + /** @group abs */ + def abs(x: Double): Double = java.lang.Math.abs(x) + + /** @group minmax */ + def max(x: Int, y: Int): Int = java.lang.Math.max(x, y) + /** @group minmax */ + def max(x: Long, y: Long): Long = java.lang.Math.max(x, y) + /** @group minmax */ + def max(x: Float, y: Float): Float = java.lang.Math.max(x, y) + /** @group minmax */ + def max(x: Double, y: Double): Double = java.lang.Math.max(x, y) + + /** @group minmax */ + def min(x: Int, y: Int): Int = java.lang.Math.min(x, y) + /** @group minmax */ + def min(x: Long, y: Long): Long = java.lang.Math.min(x, y) + /** @group minmax */ + def min(x: Float, y: Float): Float = java.lang.Math.min(x, y) + /** @group minmax */ + def min(x: Double, y: Double): Double = java.lang.Math.min(x, y) + + /** @group signs + * @note Forwards to [[java.lang.Integer]] + */ + def signum(x: Int): Int = java.lang.Integer.signum(x) + /** @group signs + * @note Forwards to [[java.lang.Long]] + */ + def signum(x: Long): Long = java.lang.Long.signum(x) + /** @group signs */ + def signum(x: Float): Float = java.lang.Math.signum(x) + /** @group signs */ + def signum(x: Double): Double = java.lang.Math.signum(x) + + /** @group modquo */ + def floorDiv(x: Int, y: Int): Int = java.lang.Math.floorDiv(x, y) + + /** @group modquo */ + def floorDiv(x: Long, y: Long): Long = java.lang.Math.floorDiv(x, y) + + /** @group modquo */ + def floorMod(x: Int, y: Int): Int = java.lang.Math.floorMod(x, y) + + /** @group modquo */ + def floorMod(x: Long, y: Long): Long = java.lang.Math.floorMod(x, y) + + /** @group signs */ + def copySign(magnitude: Double, sign: Double): Double = java.lang.Math.copySign(magnitude, sign) + + /** @group signs */ + def copySign(magnitude: Float, sign: Float): Float = java.lang.Math.copySign(magnitude, sign) + + /** @group adjacent-float */ + def nextAfter(start: Double, direction: Double): Double = java.lang.Math.nextAfter(start, direction) + + /** @group adjacent-float */ + def nextAfter(start: Float, direction: Double): Float = java.lang.Math.nextAfter(start, direction) + + /** @group adjacent-float */ + def nextUp(d: Double): Double = java.lang.Math.nextUp(d) + + /** @group adjacent-float */ + def nextUp(f: Float): Float = java.lang.Math.nextUp(f) + + /** @group adjacent-float */ + def nextDown(d: Double): Double = java.lang.Math.nextDown(d) + + /** @group adjacent-float */ + def nextDown(f: Float): Float = java.lang.Math.nextDown(f) + + /** @group scaling */ + def scalb(d: Double, scaleFactor: Int): Double = java.lang.Math.scalb(d, scaleFactor) + + /** @group scaling */ + def scalb(f: Float, scaleFactor: Int): Float = java.lang.Math.scalb(f, scaleFactor) + + // ----------------------------------------------------------------------- + // root functions + // ----------------------------------------------------------------------- + + /** Returns the square root of a `Double` value. + * + * @param x the number to take the square root of + * @return the value √x + * @group root-extraction + */ + def sqrt(x: Double): Double = java.lang.Math.sqrt(x) + + /** Returns the cube root of the given `Double` value. + * + * @param x the number to take the cube root of + * @return the value ∛x + * @group root-extraction + */ + def cbrt(x: Double): Double = java.lang.Math.cbrt(x) + + // ----------------------------------------------------------------------- + // exponential functions + // ----------------------------------------------------------------------- + + /** Returns the value of the first argument raised to the power of the + * second argument. + * + * @param x the base. + * @param y the exponent. + * @return the value `x^y^`. + * @group explog + */ + def pow(x: Double, y: Double): Double = java.lang.Math.pow(x, y) + + /** Returns Euler's number `e` raised to the power of a `Double` value. + * + * @param x the exponent to raise `e` to. + * @return the value `e^a^`, where `e` is the base of the natural + * logarithms. + * @group explog + */ + def exp(x: Double): Double = java.lang.Math.exp(x) + + /** Returns `exp(x) - 1`. + * @group explog + */ + def expm1(x: Double): Double = java.lang.Math.expm1(x) + + /** @group explog */ + def getExponent(f: Float): Int = java.lang.Math.getExponent(f) + + /** @group explog */ + def getExponent(d: Double): Int = java.lang.Math.getExponent(d) + + // ----------------------------------------------------------------------- + // logarithmic functions + // ----------------------------------------------------------------------- + + /** Returns the natural logarithm of a `Double` value. + * + * @param x the number to take the natural logarithm of + * @return the value `logₑ(x)` where `e` is Eulers number + * @group explog + */ + def log(x: Double): Double = java.lang.Math.log(x) + + /** Returns the natural logarithm of the sum of the given `Double` value and 1. + * @group explog + */ + def log1p(x: Double): Double = java.lang.Math.log1p(x) + + /** Returns the base 10 logarithm of the given `Double` value. + * @group explog + */ + def log10(x: Double): Double = java.lang.Math.log10(x) + + // ----------------------------------------------------------------------- + // trigonometric functions + // ----------------------------------------------------------------------- + + /** Returns the hyperbolic sine of the given `Double` value. + * @group hyperbolic + */ + def sinh(x: Double): Double = java.lang.Math.sinh(x) + + /** Returns the hyperbolic cosine of the given `Double` value. + * @group hyperbolic + */ + def cosh(x: Double): Double = java.lang.Math.cosh(x) + + /** Returns the hyperbolic tangent of the given `Double` value. + * @group hyperbolic + */ + def tanh(x: Double):Double = java.lang.Math.tanh(x) + + // ----------------------------------------------------------------------- + // miscellaneous functions + // ----------------------------------------------------------------------- + + /** Returns the size of an ulp of the given `Double` value. + * @group ulp + */ + def ulp(x: Double): Double = java.lang.Math.ulp(x) + + /** Returns the size of an ulp of the given `Float` value. + * @group ulp + */ + def ulp(x: Float): Float = java.lang.Math.ulp(x) + + /** @group exact */ + def IEEEremainder(x: Double, y: Double): Double = java.lang.Math.IEEEremainder(x, y) + + // ----------------------------------------------------------------------- + // exact functions + // ----------------------------------------------------------------------- + + /** @group exact */ + def addExact(x: Int, y: Int): Int = java.lang.Math.addExact(x, y) + + /** @group exact */ + def addExact(x: Long, y: Long): Long = java.lang.Math.addExact(x, y) + + /** @group exact */ + def subtractExact(x: Int, y: Int): Int = java.lang.Math.subtractExact(x, y) + + /** @group exact */ + def subtractExact(x: Long, y: Long): Long = java.lang.Math.subtractExact(x, y) + + /** @group exact */ + def multiplyExact(x: Int, y: Int): Int = java.lang.Math.multiplyExact(x, y) + + /** @group exact */ + def multiplyExact(x: Long, y: Long): Long = java.lang.Math.multiplyExact(x, y) + + /** @group exact */ + def incrementExact(x: Int): Int = java.lang.Math.incrementExact(x) + + /** @group exact */ + def incrementExact(x: Long) = java.lang.Math.incrementExact(x) + + /** @group exact */ + def decrementExact(x: Int) = java.lang.Math.decrementExact(x) + + /** @group exact */ + def decrementExact(x: Long) = java.lang.Math.decrementExact(x) + + /** @group exact */ + def negateExact(x: Int) = java.lang.Math.negateExact(x) + + /** @group exact */ + def negateExact(x: Long) = java.lang.Math.negateExact(x) + + /** @group exact */ + def toIntExact(x: Long): Int = java.lang.Math.toIntExact(x) + +} diff --git a/library/src/scala/native.scala b/library/src/scala/native.scala new file mode 100644 index 000000000000..bf853d575b6f --- /dev/null +++ b/library/src/scala/native.scala @@ -0,0 +1,29 @@ +/* + * 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.language.`2.13` + +/** Marker for native methods. + * + * {{{ + * @native def f(x: Int, y: List[Long]): String = ... + * }}} + * + * A `@native` method is compiled to the platform's native method, + * while discarding the method's body (if any). The body will be type checked if present. + * + * A method marked @native must be a member of a class, not a trait (since 2.12). + */ +@deprecatedInheritance("Scheduled for being final in the future", "2.13.0") +class native extends scala.annotation.StaticAnnotation {} diff --git a/library/src/scala/noinline.scala b/library/src/scala/noinline.scala new file mode 100644 index 000000000000..d2a770e9c822 --- /dev/null +++ b/library/src/scala/noinline.scala @@ -0,0 +1,50 @@ +/* + * 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.language.`2.13` + +/** + * An annotation for methods that the optimizer should not inline. + * + * Note that by default, the Scala optimizer is disabled and no callsites are inlined. See + * `-opt:help` for information how to enable the optimizer and inliner. + * + * When inlining is enabled, the inliner will never inline methods or callsites annotated + * `@noinline`. + * + * Examples: + * + * {{{ + * @inline final def f1(x: Int) = x + * @noinline final def f2(x: Int) = x + * final def f3(x: Int) = x + * + * def t1 = f1(1) // inlined if possible + * def t2 = f2(1) // not inlined + * def t3 = f3(1) // may be inlined (the inliner heuristics can select the callsite) + * def t4 = f1(1): @noinline // not inlined (override at callsite) + * def t5 = f2(1): @inline // inlined if possible (override at callsite) + * def t6 = f3(1): @inline // inlined if possible + * def t7 = f3(1): @noinline // not inlined + * } + * }}} + * + * Note: parentheses are required when annotating a callsite within a larger expression. + * + * {{{ + * def t1 = f1(1) + f1(1): @noinline // equivalent to (f1(1) + f1(1)): @noinline + * def t2 = f1(1) + (f1(1): @noinline) // the second call to f1 is not inlined + * }}} + */ +final class noinline extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/package.scala b/library/src/scala/package.scala new file mode 100644 index 000000000000..fe9ed612506e --- /dev/null +++ b/library/src/scala/package.scala @@ -0,0 +1,148 @@ +/* + * 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. + */ + +import scala.language.`2.13` +import scala.annotation.migration + +/** + * Core Scala types. They are always available without an explicit import. + * @contentDiagram hideNodes "scala.Serializable" + */ +package object scala { + type Cloneable = java.lang.Cloneable + type Serializable = java.io.Serializable + + type Throwable = java.lang.Throwable + type Exception = java.lang.Exception + type Error = java.lang.Error + + type RuntimeException = java.lang.RuntimeException + type NullPointerException = java.lang.NullPointerException + type ClassCastException = java.lang.ClassCastException + type IndexOutOfBoundsException = java.lang.IndexOutOfBoundsException + type ArrayIndexOutOfBoundsException = java.lang.ArrayIndexOutOfBoundsException + type StringIndexOutOfBoundsException = java.lang.StringIndexOutOfBoundsException + type UnsupportedOperationException = java.lang.UnsupportedOperationException + type IllegalArgumentException = java.lang.IllegalArgumentException + type NoSuchElementException = java.util.NoSuchElementException + type NumberFormatException = java.lang.NumberFormatException + type AbstractMethodError = java.lang.AbstractMethodError + type InterruptedException = java.lang.InterruptedException + + // A dummy used by the specialization annotation. + val AnyRef = new Specializable { + override def toString = "object AnyRef" + } + + @deprecated("Use IterableOnce instead of TraversableOnce", "2.13.0") + type TraversableOnce[+A] = scala.collection.IterableOnce[A] + + type IterableOnce[+A] = scala.collection.IterableOnce[A] + + @deprecated("Use Iterable instead of Traversable", "2.13.0") + type Traversable[+A] = scala.collection.Iterable[A] + @deprecated("Use Iterable instead of Traversable", "2.13.0") + val Traversable = scala.collection.Iterable + + type Iterable[+A] = scala.collection.Iterable[A] + val Iterable = scala.collection.Iterable + + @migration("scala.Seq is now scala.collection.immutable.Seq instead of scala.collection.Seq", "2.13.0") + type Seq[+A] = scala.collection.immutable.Seq[A] + val Seq = scala.collection.immutable.Seq + + @migration("scala.IndexedSeq is now scala.collection.immutable.IndexedSeq instead of scala.collection.IndexedSeq", "2.13.0") + type IndexedSeq[+A] = scala.collection.immutable.IndexedSeq[A] + val IndexedSeq = scala.collection.immutable.IndexedSeq + + type Iterator[+A] = scala.collection.Iterator[A] + val Iterator = scala.collection.Iterator + + @deprecated("Use scala.collection.BufferedIterator instead of scala.BufferedIterator", "2.13.0") + type BufferedIterator[+A] = scala.collection.BufferedIterator[A] + + type List[+A] = scala.collection.immutable.List[A] + val List = scala.collection.immutable.List + + val Nil = scala.collection.immutable.Nil + + type ::[+A] = scala.collection.immutable.::[A] + val :: = scala.collection.immutable.:: + + val +: = scala.collection.+: + val :+ = scala.collection.:+ + + @deprecated("Use LazyList instead of Stream", "2.13.0") + type Stream[+A] = scala.collection.immutable.Stream[A] + @deprecated("Use LazyList instead of Stream", "2.13.0") + val Stream = scala.collection.immutable.Stream + + type LazyList[+A] = scala.collection.immutable.LazyList[A] + val LazyList = scala.collection.immutable.LazyList + // This should be an alias to LazyList.#:: but we need to support Stream, too + //val #:: = scala.collection.immutable.LazyList.#:: + object #:: { + def unapply[A](s: LazyList[A]): Option[(A, LazyList[A])] = + if (s.nonEmpty) Some((s.head, s.tail)) else None + @deprecated("Prefer LazyList instead", since = "2.13.0") + def unapply[A](s: Stream[A]): Option[(A, Stream[A])] = + if (s.nonEmpty) Some((s.head, s.tail)) else None + } + + type Vector[+A] = scala.collection.immutable.Vector[A] + val Vector = scala.collection.immutable.Vector + + type StringBuilder = scala.collection.mutable.StringBuilder + val StringBuilder = scala.collection.mutable.StringBuilder + + type Range = scala.collection.immutable.Range + val Range = scala.collection.immutable.Range + + // Numeric types which were moved into scala.math.* + + type BigDecimal = scala.math.BigDecimal + val BigDecimal = scala.math.BigDecimal + + type BigInt = scala.math.BigInt + val BigInt = scala.math.BigInt + + type Equiv[T] = scala.math.Equiv[T] + val Equiv = scala.math.Equiv + + type Fractional[T] = scala.math.Fractional[T] + val Fractional = scala.math.Fractional + + type Integral[T] = scala.math.Integral[T] + val Integral = scala.math.Integral + + type Numeric[T] = scala.math.Numeric[T] + val Numeric = scala.math.Numeric + + type Ordered[T] = scala.math.Ordered[T] + val Ordered = scala.math.Ordered + + type Ordering[T] = scala.math.Ordering[T] + val Ordering = scala.math.Ordering + + type PartialOrdering[T] = scala.math.PartialOrdering[T] + type PartiallyOrdered[T] = scala.math.PartiallyOrdered[T] + + type Either[+A, +B] = scala.util.Either[A, B] + val Either = scala.util.Either + + type Left[+A, +B] = scala.util.Left[A, B] + val Left = scala.util.Left + + type Right[+A, +B] = scala.util.Right[A, B] + val Right = scala.util.Right + +} diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index a96785ce2741..009f1c28fbd9 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3640,7 +3640,10 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val StringConstant` */ trait StringConstantModule { this: StringConstant.type => - /** Create a constant String value */ + /** Create a constant String value + * + * @throw `IllegalArgumentException` if the argument is `null` + */ def apply(x: String): StringConstant /** Match String value constant and extract its value */ def unapply(constant: StringConstant): Some[String] diff --git a/library/src/scala/ref/PhantomReference.scala b/library/src/scala/ref/PhantomReference.scala new file mode 100644 index 000000000000..d2d180da1d5c --- /dev/null +++ b/library/src/scala/ref/PhantomReference.scala @@ -0,0 +1,23 @@ +/* + * 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.ref + +import scala.language.`2.13` + +class PhantomReference[+T <: AnyRef](value: T, queue: ReferenceQueue[T]) extends ReferenceWrapper[T] { + val underlying: java.lang.ref.PhantomReference[_ <: T] = + new PhantomReferenceWithWrapper[T](value, queue, this) +} + +private class PhantomReferenceWithWrapper[T <: AnyRef](value: T, queue: ReferenceQueue[T], val wrapper: PhantomReference[T]) + extends java.lang.ref.PhantomReference[T](value, queue.underlying.asInstanceOf[java.lang.ref.ReferenceQueue[T]]) with ReferenceWithWrapper[T] diff --git a/library/src/scala/ref/Reference.scala b/library/src/scala/ref/Reference.scala new file mode 100644 index 000000000000..549d23a162c1 --- /dev/null +++ b/library/src/scala/ref/Reference.scala @@ -0,0 +1,29 @@ +/* + * 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.ref + +import scala.language.`2.13` + +/** + * @see `java.lang.ref.Reference` + */ +trait Reference[+T <: AnyRef] extends Function0[T] { + /** return the underlying value */ + def apply(): T + /** return `Some` underlying if it hasn't been collected, otherwise `None` */ + def get: Option[T] + override def toString: String = get.map(_.toString).getOrElse("") + def clear(): Unit + def enqueue(): Boolean + def isEnqueued: Boolean +} diff --git a/library/src/scala/ref/ReferenceQueue.scala b/library/src/scala/ref/ReferenceQueue.scala new file mode 100644 index 000000000000..2fb8e646b444 --- /dev/null +++ b/library/src/scala/ref/ReferenceQueue.scala @@ -0,0 +1,32 @@ +/* + * 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.ref + +import scala.language.`2.13` + +class ReferenceQueue[+T <: AnyRef] { + + private[ref] val underlying: java.lang.ref.ReferenceQueue[_ <: T] = new java.lang.ref.ReferenceQueue[T] + override def toString: String = underlying.toString + + protected def Wrapper(jref: java.lang.ref.Reference[_]): Option[Reference[T]] = + jref match { + case null => None + case ref => Some(ref.asInstanceOf[ReferenceWithWrapper[T]].wrapper) + } + + def poll: Option[Reference[T]] = Wrapper(underlying.poll) + def remove: Option[Reference[T]] = Wrapper(underlying.remove) + def remove(timeout: Long): Option[Reference[T]] = Wrapper(underlying.remove(timeout)) + +} diff --git a/library/src/scala/ref/ReferenceWrapper.scala b/library/src/scala/ref/ReferenceWrapper.scala new file mode 100644 index 000000000000..7877f1816016 --- /dev/null +++ b/library/src/scala/ref/ReferenceWrapper.scala @@ -0,0 +1,35 @@ +/* + * 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.ref + +import scala.language.`2.13` +import scala.annotation.nowarn + +@nowarn("cat=deprecation") +trait ReferenceWrapper[+T <: AnyRef] extends Reference[T] with Proxy { + val underlying: java.lang.ref.Reference[_ <: T] + override def get = Option(underlying.get) + def apply() = { + val ret = underlying.get + if (ret eq null) throw new NoSuchElementException + ret + } + def clear(): Unit = underlying.clear() + def enqueue(): Boolean = underlying.enqueue() + def isEnqueued: Boolean = underlying.isEnqueued + def self: java.lang.ref.Reference[_ <: T] = underlying +} + +private trait ReferenceWithWrapper[T <: AnyRef] { + val wrapper: ReferenceWrapper[T] +} diff --git a/library/src/scala/ref/SoftReference.scala b/library/src/scala/ref/SoftReference.scala new file mode 100644 index 000000000000..dd79863ff03b --- /dev/null +++ b/library/src/scala/ref/SoftReference.scala @@ -0,0 +1,37 @@ +/* + * 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.ref + +import scala.language.`2.13` + +class SoftReference[+T <: AnyRef](value : T, queue : ReferenceQueue[T]) extends ReferenceWrapper[T] { + def this(value : T) = this(value, null) + + val underlying: java.lang.ref.SoftReference[_ <: T] = + new SoftReferenceWithWrapper[T](value, queue, this) +} + +/** + * A companion object that implements an extractor for `SoftReference` values + */ +object SoftReference { + + /** Creates a `SoftReference` pointing to `value` */ + def apply[T <: AnyRef](value: T): SoftReference[T] = new SoftReference(value) + + /** Optionally returns the referenced value, or `None` if that value no longer exists */ + def unapply[T <: AnyRef](sr: SoftReference[T]): Option[T] = Option(sr.underlying.get) +} + +private class SoftReferenceWithWrapper[T <: AnyRef](value: T, queue: ReferenceQueue[T], val wrapper: SoftReference[T]) + extends java.lang.ref.SoftReference[T](value, if (queue == null) null else queue.underlying.asInstanceOf[java.lang.ref.ReferenceQueue[T]]) with ReferenceWithWrapper[T] diff --git a/library/src/scala/ref/WeakReference.scala b/library/src/scala/ref/WeakReference.scala new file mode 100644 index 000000000000..196b79131a04 --- /dev/null +++ b/library/src/scala/ref/WeakReference.scala @@ -0,0 +1,39 @@ +/* + * 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.ref + +import scala.language.`2.13` + +/** + * A wrapper class for java.lang.ref.WeakReference + * The new functionality is (1) results are Option values, instead of using null. + * (2) There is an extractor that maps the weak reference itself into an option. + */ +class WeakReference[+T <: AnyRef](value: T, queue: ReferenceQueue[T]) extends ReferenceWrapper[T] { + def this(value: T) = this(value, null) + val underlying: java.lang.ref.WeakReference[_ <: T] = + new WeakReferenceWithWrapper[T](value, queue, this) +} + +/** An extractor for weak reference values */ +object WeakReference { + + /** Creates a weak reference pointing to `value` */ + def apply[T <: AnyRef](value: T): WeakReference[T] = new WeakReference(value) + + /** Optionally returns the referenced value, or `None` if that value no longer exists */ + def unapply[T <: AnyRef](wr: WeakReference[T]): Option[T] = Option(wr.underlying.get) +} + +private class WeakReferenceWithWrapper[T <: AnyRef](value: T, queue: ReferenceQueue[T], val wrapper: WeakReference[T]) + extends java.lang.ref.WeakReference[T](value, if (queue == null) null else queue.underlying.asInstanceOf[java.lang.ref.ReferenceQueue[T]]) with ReferenceWithWrapper[T] diff --git a/library/src/scala/reflect/ClassManifestDeprecatedApis.scala b/library/src/scala/reflect/ClassManifestDeprecatedApis.scala new file mode 100644 index 000000000000..17e05050132a --- /dev/null +++ b/library/src/scala/reflect/ClassManifestDeprecatedApis.scala @@ -0,0 +1,249 @@ +/* + * 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 reflect + +import scala.language.`2.13` +import scala.collection.mutable.{ArrayBuilder, ArraySeq} +import java.lang.{Class => jClass} + +import scala.annotation.{nowarn, tailrec} + +@deprecated("use scala.reflect.ClassTag instead", "2.10.0") +trait ClassManifestDeprecatedApis[T] extends OptManifest[T] { + self: ClassManifest[T] => + + // Still in use in target test.junit.comp. + @deprecated("use runtimeClass instead", "2.10.0") + def erasure: jClass[_] = runtimeClass + + private def subtype(sub: jClass[_], sup: jClass[_]): Boolean = { + @tailrec + def loop(left: Set[jClass[_]], seen: Set[jClass[_]]): Boolean = { + left.nonEmpty && { + val next = left.head + val supers = next.getInterfaces.toSet ++ Option(next.getSuperclass) + supers(sup) || { + val xs = left ++ supers filterNot seen + loop(xs - next, seen + next) + } + } + } + loop(Set(sub), Set()) + } + + private def subargs(args1: List[OptManifest[_]], args2: List[OptManifest[_]]) = (args1 corresponds args2) { + // !!! [Martin] this is wrong, need to take variance into account + case (x: ClassManifest[_], y: ClassManifest[_]) => x <:< y + case (x, y) => (x eq NoManifest) && (y eq NoManifest) + } + + /** Tests whether the type represented by this manifest is a subtype + * of the type represented by `that` manifest, subject to the limitations + * described in the header. + */ + @deprecated("use scala.reflect.runtime.universe.TypeTag for subtype checking instead", "2.10.0") + def <:<(that: ClassManifest[_]): Boolean = { + // All types which could conform to these types will override <:<. + def cannotMatch = { + import Manifest._ + that.isInstanceOf[AnyValManifest[_]] || (that eq AnyVal) || (that eq Nothing) || (that eq Null) + } + + // This is wrong, and I don't know how it can be made right + // without more development of Manifests, due to arity-defying + // relationships like: + // + // List[String] <: AnyRef + // Map[Int, Int] <: Iterable[(Int, Int)] + // + // Given the manifest for Map[K, V] how do I determine that a + // supertype has single type argument (K, V) ? I don't see how we + // can say whether X <:< Y when type arguments are involved except + // when the erasure is the same, even before considering variance. + !cannotMatch && { + // this part is wrong for not considering variance + if (this.runtimeClass == that.runtimeClass) + subargs(this.typeArguments, that.typeArguments) + // this part is wrong for punting unless the rhs has no type + // arguments, but it's better than a blindfolded pinata swing. + else + that.typeArguments.isEmpty && subtype(this.runtimeClass, that.runtimeClass) + } + } + + /** Tests whether the type represented by this manifest is a supertype + * of the type represented by `that` manifest, subject to the limitations + * described in the header. + */ + @deprecated("use scala.reflect.runtime.universe.TypeTag for subtype checking instead", "2.10.0") + def >:>(that: ClassManifest[_]): Boolean = + that <:< this + + override def canEqual(other: Any) = other match { + case _: ClassManifest[_] => true + case _ => false + } + + protected def arrayClass[A](tp: jClass[_]): jClass[Array[A]] = + java.lang.reflect.Array.newInstance(tp, 0).getClass.asInstanceOf[jClass[Array[A]]] + + @deprecated("use wrap instead", "2.10.0") + def arrayManifest: ClassManifest[Array[T]] = + ClassManifest.classType[Array[T]](arrayClass[T](runtimeClass), this) + + @deprecated("use wrap.newArray instead", "2.10.0") + def newArray2(len: Int): Array[Array[T]] = + java.lang.reflect.Array.newInstance(arrayClass[T](runtimeClass), len) + .asInstanceOf[Array[Array[T]]] + + @deprecated("use wrap.wrap.newArray instead", "2.10.0") + def newArray3(len: Int): Array[Array[Array[T]]] = + java.lang.reflect.Array.newInstance(arrayClass[Array[T]](arrayClass[T](runtimeClass)), len) + .asInstanceOf[Array[Array[Array[T]]]] + + @deprecated("use wrap.wrap.wrap.newArray instead", "2.10.0") + def newArray4(len: Int): Array[Array[Array[Array[T]]]] = + java.lang.reflect.Array.newInstance(arrayClass[Array[Array[T]]](arrayClass[Array[T]](arrayClass[T](runtimeClass))), len) + .asInstanceOf[Array[Array[Array[Array[T]]]]] + + @deprecated("use wrap.wrap.wrap.wrap.newArray instead", "2.10.0") + def newArray5(len: Int): Array[Array[Array[Array[Array[T]]]]] = + java.lang.reflect.Array.newInstance(arrayClass[Array[Array[Array[T]]]](arrayClass[Array[Array[T]]](arrayClass[Array[T]](arrayClass[T](runtimeClass)))), len) + .asInstanceOf[Array[Array[Array[Array[Array[T]]]]]] + + @deprecated("create WrappedArray directly instead", "2.10.0") + def newWrappedArray(len: Int): ArraySeq[T] = + // it's safe to assume T <: AnyRef here because the method is overridden for all value type manifests + new ArraySeq.ofRef[T with AnyRef](newArray(len).asInstanceOf[Array[T with AnyRef]]).asInstanceOf[ArraySeq[T]] + + @deprecated("use ArrayBuilder.make(this) instead", "2.10.0") + def newArrayBuilder(): ArrayBuilder[T] = + // it's safe to assume T <: AnyRef here because the method is overridden for all value type manifests + new ArrayBuilder.ofRef[T with AnyRef]()(this.asInstanceOf[ClassManifest[T with AnyRef]]).asInstanceOf[ArrayBuilder[T]] + + @deprecated("use scala.reflect.runtime.universe.TypeTag to capture type structure instead", "2.10.0") + def typeArguments: List[OptManifest[_]] = List() + + protected def argString = + if (typeArguments.nonEmpty) typeArguments.mkString("[", ", ", "]") + else if (runtimeClass.isArray) "["+ClassManifest.fromClass(runtimeClass.getComponentType)+"]" + else "" +} + +/** `ClassManifestFactory` defines factory methods for manifests. + * It is intended for use by the compiler and should not be used in client code. + * + * Unlike `ClassManifest`, this factory isn't annotated with a deprecation warning. + * This is done to prevent avalanches of deprecation warnings in the code that calls methods with manifests. + * + * In a perfect world, we would just remove the @deprecated annotation from `ClassManifest` the object + * and then delete it in 2.11. After all, that object is explicitly marked as internal, so no one should use it. + * However a lot of existing libraries disregarded the Scaladoc that comes with `ClassManifest`, + * so we need to somehow nudge them into migrating prior to removing stuff out of the blue. + * Hence we've introduced this design decision as the lesser of two evils. + */ +@nowarn("""cat=deprecation&origin=scala\.reflect\.ClassManifest.*""") +object ClassManifestFactory { + val Byte = ManifestFactory.Byte + val Short = ManifestFactory.Short + val Char = ManifestFactory.Char + val Int = ManifestFactory.Int + val Long = ManifestFactory.Long + val Float = ManifestFactory.Float + val Double = ManifestFactory.Double + val Boolean = ManifestFactory.Boolean + val Unit = ManifestFactory.Unit + val Any = ManifestFactory.Any + val Object = ManifestFactory.Object + val AnyVal = ManifestFactory.AnyVal + val Nothing = ManifestFactory.Nothing + val Null = ManifestFactory.Null + + def fromClass[T](clazz: jClass[T]): ClassManifest[T] = clazz match { + case java.lang.Byte.TYPE => Byte.asInstanceOf[ClassManifest[T]] + case java.lang.Short.TYPE => Short.asInstanceOf[ClassManifest[T]] + case java.lang.Character.TYPE => Char.asInstanceOf[ClassManifest[T]] + case java.lang.Integer.TYPE => Int.asInstanceOf[ClassManifest[T]] + case java.lang.Long.TYPE => Long.asInstanceOf[ClassManifest[T]] + case java.lang.Float.TYPE => Float.asInstanceOf[ClassManifest[T]] + case java.lang.Double.TYPE => Double.asInstanceOf[ClassManifest[T]] + case java.lang.Boolean.TYPE => Boolean.asInstanceOf[ClassManifest[T]] + case java.lang.Void.TYPE => Unit.asInstanceOf[ClassManifest[T]] + case _ => classType[T with AnyRef](clazz).asInstanceOf[ClassManifest[T]] + } + + def singleType[T <: AnyRef](value: AnyRef): Manifest[T] = Manifest.singleType(value) + + /** ClassManifest for the class type `clazz`, where `clazz` is + * a top-level or static class. + * @note This no-prefix, no-arguments case is separate because we + * it's called from ScalaRunTime.boxArray itself. If we + * pass varargs as arrays into this, we get an infinitely recursive call + * to boxArray. (Besides, having a separate case is more efficient) + */ + def classType[T](clazz: jClass[_]): ClassManifest[T] = + new ClassTypeManifest[T](None, clazz, Nil) + + /** ClassManifest for the class type `clazz[args]`, where `clazz` is + * a top-level or static class and `args` are its type arguments */ + def classType[T](clazz: jClass[_], arg1: OptManifest[_], args: OptManifest[_]*): ClassManifest[T] = + new ClassTypeManifest[T](None, clazz, arg1 :: args.toList) + + /** ClassManifest for the class type `clazz[args]`, where `clazz` is + * a class with non-package prefix type `prefix` and type arguments `args`. + */ + def classType[T](prefix: OptManifest[_], clazz: jClass[_], args: OptManifest[_]*): ClassManifest[T] = + new ClassTypeManifest[T](Some(prefix), clazz, args.toList) + + def arrayType[T](arg: OptManifest[_]): ClassManifest[Array[T]] = (arg: @unchecked) match { + case NoManifest => Object.asInstanceOf[ClassManifest[Array[T]]] + case m: ClassManifest[_] => m.asInstanceOf[ClassManifest[T]].arrayManifest + } + + @SerialVersionUID(1L) + private class AbstractTypeClassManifest[T](prefix: OptManifest[_], name: String, clazz: jClass[_], args: OptManifest[_]*) extends ClassManifest[T] { + override def runtimeClass = clazz + override val typeArguments = args.toList + override def toString = prefix.toString+"#"+name+argString + } + + /** ClassManifest for the abstract type `prefix # name`. `upperBound` is not + * strictly necessary as it could be obtained by reflection. It was + * added so that erasure can be calculated without reflection. */ + def abstractType[T](prefix: OptManifest[_], name: String, clazz: jClass[_], args: OptManifest[_]*): ClassManifest[T] = + new AbstractTypeClassManifest(prefix, name, clazz) + + /** ClassManifest for the abstract type `prefix # name`. `upperBound` is not + * strictly necessary as it could be obtained by reflection. It was + * added so that erasure can be calculated without reflection. + * todo: remove after next bootstrap + */ + def abstractType[T](prefix: OptManifest[_], name: String, upperbound: ClassManifest[_], args: OptManifest[_]*): ClassManifest[T] = + new AbstractTypeClassManifest(prefix, name, upperbound.runtimeClass) +} + +/** Manifest for the class type `clazz[args]`, where `clazz` is + * a top-level or static class */ +@nowarn("""cat=deprecation&origin=scala\.reflect\.ClassManifest""") +@SerialVersionUID(1L) +private class ClassTypeManifest[T]( + prefix: Option[OptManifest[_]], + val runtimeClass: jClass[_], + override val typeArguments: List[OptManifest[_]]) extends ClassManifest[T] +{ + override def toString = + (if (prefix.isEmpty) "" else prefix.get.toString+"#") + + (if (runtimeClass.isArray) "Array" else runtimeClass.getName) + + argString +} diff --git a/library/src/scala/reflect/ClassTag.scala b/library/src/scala/reflect/ClassTag.scala new file mode 100644 index 000000000000..25cf56490ff1 --- /dev/null +++ b/library/src/scala/reflect/ClassTag.scala @@ -0,0 +1,169 @@ +/* + * 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 reflect + +import scala.language.`2.13` +import java.lang.{Class => jClass} +import java.lang.ref.{WeakReference => jWeakReference} +import scala.annotation.{implicitNotFound, nowarn} +import scala.runtime.ClassValueCompat + +/** + * + * A `ClassTag[T]` stores the erased class of a given type `T`, accessible via the `runtimeClass` + * field. This is particularly useful for instantiating `Array`s whose element types are unknown + * at compile time. + * + * `ClassTag`s are a weaker special case of [[scala.reflect.api.TypeTags.TypeTag]]s, in that they + * wrap only the runtime class of a given type, whereas a `TypeTag` contains all static type + * information. That is, `ClassTag`s are constructed from knowing only the top-level class of a + * type, without necessarily knowing all of its argument types. This runtime information is enough + * for runtime `Array` creation. + * + * For example: + * {{{ + * scala> def mkArray[T : ClassTag](elems: T*) = Array[T](elems: _*) + * mkArray: [T](elems: T*)(implicit evidence\$1: scala.reflect.ClassTag[T])Array[T] + * + * scala> mkArray(42, 13) + * res0: Array[Int] = Array(42, 13) + * + * scala> mkArray("Japan","Brazil","Germany") + * res1: Array[String] = Array(Japan, Brazil, Germany) + * }}} + * + * See [[scala.reflect.api.TypeTags]] for more examples, or the + * [[https://docs.scala-lang.org/overviews/reflection/typetags-manifests.html Reflection Guide: TypeTags]] + * for more details. + * + */ +@nowarn("""cat=deprecation&origin=scala\.reflect\.ClassManifestDeprecatedApis""") +@implicitNotFound(msg = "No ClassTag available for ${T}") +trait ClassTag[T] extends ClassManifestDeprecatedApis[T] with Equals with Serializable { + // please, don't add any APIs here, like it was with `newWrappedArray` and `newArrayBuilder` + // class tags, and all tags in general, should be as minimalistic as possible + + /** A class representing the type `U` to which `T` would be erased. + * Note that there is no subtyping relationship between `T` and `U`. + */ + def runtimeClass: jClass[_] + + /** Produces a `ClassTag` that knows how to instantiate an `Array[Array[T]]` */ + def wrap: ClassTag[Array[T]] = ClassTag[Array[T]](arrayClass(runtimeClass)) + + /** Produces a new array with element type `T` and length `len` */ + def newArray(len: Int): Array[T] = + java.lang.reflect.Array.newInstance(runtimeClass, len).asInstanceOf[Array[T]] + + /** A ClassTag[T] can serve as an extractor that matches only objects of type T. + * + * The compiler tries to turn unchecked type tests in pattern matches into checked ones + * by wrapping a `(_: T)` type pattern as `ct(_: T)`, where `ct` is the `ClassTag[T]` instance. + * Type tests necessary before calling other extractors are treated similarly. + * `SomeExtractor(...)` is turned into `ct(SomeExtractor(...))` if `T` in `SomeExtractor.unapply(x: T)` + * is uncheckable, but we have an instance of `ClassTag[T]`. + */ + def unapply(x: Any): Option[T] = + if (runtimeClass.isInstance(x)) Some(x.asInstanceOf[T]) + else None + + // case class accessories + override def canEqual(x: Any) = x.isInstanceOf[ClassTag[_]] + override def equals(x: Any) = x.isInstanceOf[ClassTag[_]] && this.runtimeClass == x.asInstanceOf[ClassTag[_]].runtimeClass + override def hashCode = runtimeClass.## + override def toString = { + def prettyprint(clazz: jClass[_]): String = + if (clazz.isArray) s"Array[${prettyprint(clazz.getComponentType)}]" else + clazz.getName + prettyprint(runtimeClass) + } +} + +/** + * Class tags corresponding to primitive types and constructor/extractor for ClassTags. + */ +object ClassTag { + private[this] val ObjectTYPE = classOf[java.lang.Object] + private[this] val NothingTYPE = classOf[scala.runtime.Nothing$] + private[this] val NullTYPE = classOf[scala.runtime.Null$] + + import ManifestFactory._ + + val Byte : ByteManifest = Manifest.Byte + val Short : ShortManifest = Manifest.Short + val Char : CharManifest = Manifest.Char + val Int : IntManifest = Manifest.Int + val Long : LongManifest = Manifest.Long + val Float : FloatManifest = Manifest.Float + val Double : DoubleManifest = Manifest.Double + val Boolean : BooleanManifest = Manifest.Boolean + val Unit : UnitManifest = Manifest.Unit + val Any : ClassTag[scala.Any] = Manifest.Any + val Object : ClassTag[java.lang.Object] = Manifest.Object + val AnyVal : ClassTag[scala.AnyVal] = Manifest.AnyVal + val AnyRef : ClassTag[scala.AnyRef] = Manifest.AnyRef + val Nothing : ClassTag[scala.Nothing] = Manifest.Nothing + val Null : ClassTag[scala.Null] = Manifest.Null + + private val cacheDisabled = java.lang.Boolean.getBoolean("scala.reflect.classtag.cache.disable") + private[this] object cache extends ClassValueCompat[jWeakReference[ClassTag[_]]] { + override def computeValue(runtimeClass: jClass[_]): jWeakReference[ClassTag[_]] = + new jWeakReference(computeTag(runtimeClass)) + + def computeTag(runtimeClass: jClass[_]): ClassTag[_] = + runtimeClass match { + case x if x.isPrimitive => primitiveClassTag(runtimeClass) + case ObjectTYPE => ClassTag.Object + case NothingTYPE => ClassTag.Nothing + case NullTYPE => ClassTag.Null + case _ => new GenericClassTag[AnyRef](runtimeClass) + } + + private def primitiveClassTag[T](runtimeClass: Class[_]): ClassTag[_] = + (runtimeClass: @unchecked) match { + case java.lang.Byte.TYPE => ClassTag.Byte + case java.lang.Short.TYPE => ClassTag.Short + case java.lang.Character.TYPE => ClassTag.Char + case java.lang.Integer.TYPE => ClassTag.Int + case java.lang.Long.TYPE => ClassTag.Long + case java.lang.Float.TYPE => ClassTag.Float + case java.lang.Double.TYPE => ClassTag.Double + case java.lang.Boolean.TYPE => ClassTag.Boolean + case java.lang.Void.TYPE => ClassTag.Unit + } + } + + @SerialVersionUID(1L) + private class GenericClassTag[T](val runtimeClass: jClass[_]) extends ClassTag[T] { + override def newArray(len: Int): Array[T] = { + java.lang.reflect.Array.newInstance(runtimeClass, len).asInstanceOf[Array[T]] + } + } + + def apply[T](runtimeClass1: jClass[_]): ClassTag[T] = { + if (cacheDisabled) { + cache.computeTag(runtimeClass1).asInstanceOf[ClassTag[T]] + } else { + val ref = cache.get(runtimeClass1).asInstanceOf[jWeakReference[ClassTag[T]]] + var tag = ref.get + if (tag == null) { + cache.remove(runtimeClass1) + tag = cache.computeTag(runtimeClass1).asInstanceOf[ClassTag[T]] + } + tag + } + } + + def unapply[T](ctag: ClassTag[T]): Option[Class[_]] = Some(ctag.runtimeClass) +} diff --git a/library/src/scala/reflect/Manifest.scala b/library/src/scala/reflect/Manifest.scala new file mode 100644 index 000000000000..85b645633a65 --- /dev/null +++ b/library/src/scala/reflect/Manifest.scala @@ -0,0 +1,462 @@ +/* + * 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 reflect + +import scala.language.`2.13` +import scala.annotation.{implicitNotFound, nowarn} +import scala.collection.mutable.{ArrayBuilder, ArraySeq} + +/** A `Manifest[T]` is an opaque descriptor for type T. Its supported use + * is to give access to the erasure of the type as a `Class` instance, as + * is necessary for the creation of native `Arrays` if the class is not + * known at compile time. + * + * The type-relation operators `<:<` and `=:=` should be considered + * approximations only, as there are numerous aspects of type conformance + * which are not yet adequately represented in manifests. + * + * Example usages: + * {{{ + * def arr[T] = new Array[T](0) // does not compile + * def arr[T](implicit m: Manifest[T]) = new Array[T](0) // compiles + * def arr[T: Manifest] = new Array[T](0) // shorthand for the preceding + * + * // Methods manifest and optManifest are in [[scala.Predef]]. + * def isApproxSubType[T: Manifest, U: Manifest] = manifest[T] <:< manifest[U] + * isApproxSubType[List[String], List[AnyRef]] // true + * isApproxSubType[List[String], List[Int]] // false + * + * def methods[T: Manifest] = manifest[T].runtimeClass.getMethods + * def retType[T: Manifest](name: String) = + * methods[T] find (_.getName == name) map (_.getGenericReturnType) + * + * retType[Map[_, _]]("values") // Some(scala.collection.Iterable) + * }}} + */ +@nowarn("""cat=deprecation&origin=scala\.reflect\.ClassManifest(DeprecatedApis.*)?""") +@implicitNotFound(msg = "No Manifest available for ${T}.") +// TODO undeprecated until Scala reflection becomes non-experimental +// @deprecated("use scala.reflect.ClassTag (to capture erasures) or scala.reflect.runtime.universe.TypeTag (to capture types) or both instead", "2.10.0") +trait Manifest[T] extends ClassManifest[T] with Equals { + override def typeArguments: List[Manifest[_]] = Nil + + override def arrayManifest: Manifest[Array[T]] = + Manifest.classType[Array[T]](arrayClass[T](runtimeClass), this) + + override def canEqual(that: Any): Boolean = that match { + case _: Manifest[_] => true + case _ => false + } + /** Note: testing for erasure here is important, as it is many times + * faster than <:< and rules out most comparisons. + */ + override def equals(that: Any): Boolean = that match { + case m: Manifest[_] => (m canEqual this) && (this.runtimeClass == m.runtimeClass) && (this <:< m) && (m <:< this) + case _ => false + } + override def hashCode = this.runtimeClass.## +} + +/** The object `Manifest` defines factory methods for manifests. + * It is intended for use by the compiler and should not be used in client code. + */ +// TODO undeprecated until Scala reflection becomes non-experimental +// @deprecated("use scala.reflect.ClassTag (to capture erasures), scala.reflect.runtime.universe.TypeTag (to capture types) or both instead", "2.10.0") +object Manifest { + /* Forward all the public members of ManifestFactory, since this object used + * to be a `private val Manifest = ManifestFactory` in the package object. It + * was moved here because it needs to be in the same file as `trait Manifest` + * defined above. + */ + + def valueManifests: List[AnyValManifest[_]] = + ManifestFactory.valueManifests + + val Byte: ManifestFactory.ByteManifest = ManifestFactory.Byte + val Short: ManifestFactory.ShortManifest = ManifestFactory.Short + val Char: ManifestFactory.CharManifest = ManifestFactory.Char + val Int: ManifestFactory.IntManifest = ManifestFactory.Int + val Long: ManifestFactory.LongManifest = ManifestFactory.Long + val Float: ManifestFactory.FloatManifest = ManifestFactory.Float + val Double: ManifestFactory.DoubleManifest = ManifestFactory.Double + val Boolean: ManifestFactory.BooleanManifest = ManifestFactory.Boolean + val Unit: ManifestFactory.UnitManifest = ManifestFactory.Unit + + val Any: Manifest[scala.Any] = ManifestFactory.Any + val Object: Manifest[java.lang.Object] = ManifestFactory.Object + val AnyRef: Manifest[scala.AnyRef] = ManifestFactory.AnyRef + val AnyVal: Manifest[scala.AnyVal] = ManifestFactory.AnyVal + val Null: Manifest[scala.Null] = ManifestFactory.Null + val Nothing: Manifest[scala.Nothing] = ManifestFactory.Nothing + + /** Manifest for the singleton type `value.type`. */ + def singleType[T <: AnyRef](value: AnyRef): Manifest[T] = + ManifestFactory.singleType[T](value) + + /** Manifest for the class type `clazz[args]`, where `clazz` is + * a top-level or static class. + * @note This no-prefix, no-arguments case is separate because we + * it's called from ScalaRunTime.boxArray itself. If we + * pass varargs as arrays into this, we get an infinitely recursive call + * to boxArray. (Besides, having a separate case is more efficient) + */ + def classType[T](clazz: Predef.Class[_]): Manifest[T] = + ManifestFactory.classType[T](clazz) + + /** Manifest for the class type `clazz`, where `clazz` is + * a top-level or static class and args are its type arguments. */ + def classType[T](clazz: Predef.Class[T], arg1: Manifest[_], args: Manifest[_]*): Manifest[T] = + ManifestFactory.classType[T](clazz, arg1, args: _*) + + /** Manifest for the class type `clazz[args]`, where `clazz` is + * a class with non-package prefix type `prefix` and type arguments `args`. + */ + def classType[T](prefix: Manifest[_], clazz: Predef.Class[_], args: Manifest[_]*): Manifest[T] = + ManifestFactory.classType[T](prefix, clazz, args: _*) + + def arrayType[T](arg: Manifest[_]): Manifest[Array[T]] = + ManifestFactory.arrayType[T](arg) + + /** Manifest for the abstract type `prefix # name`. `upperBound` is not + * strictly necessary as it could be obtained by reflection. It was + * added so that erasure can be calculated without reflection. */ + def abstractType[T](prefix: Manifest[_], name: String, upperBound: Predef.Class[_], args: Manifest[_]*): Manifest[T] = + ManifestFactory.abstractType[T](prefix, name, upperBound, args: _*) + + /** Manifest for the unknown type `_ >: L <: U` in an existential. */ + def wildcardType[T](lowerBound: Manifest[_], upperBound: Manifest[_]): Manifest[T] = + ManifestFactory.wildcardType[T](lowerBound, upperBound) + + /** Manifest for the intersection type `parents_0 with ... with parents_n`. */ + def intersectionType[T](parents: Manifest[_]*): Manifest[T] = + ManifestFactory.intersectionType[T](parents: _*) + +} + +// TODO undeprecated until Scala reflection becomes non-experimental +// @deprecated("use type tags and manually check the corresponding class or type instead", "2.10.0") +@nowarn("""cat=deprecation&origin=scala\.reflect\.ClassManifest(DeprecatedApis.*)?""") +@SerialVersionUID(1L) +abstract class AnyValManifest[T <: AnyVal](override val toString: String) extends Manifest[T] with Equals { + override def <:<(that: ClassManifest[_]): Boolean = + (that eq this) || (that eq Manifest.Any) || (that eq Manifest.AnyVal) + override def canEqual(other: Any) = other match { + case _: AnyValManifest[_] => true + case _ => false + } + override def equals(that: Any): Boolean = this eq that.asInstanceOf[AnyRef] + @transient + override val hashCode = System.identityHashCode(this) +} + +/** `ManifestFactory` defines factory methods for manifests. + * It is intended for use by the compiler and should not be used in client code. + * + * Unlike `Manifest`, this factory isn't annotated with a deprecation warning. + * This is done to prevent avalanches of deprecation warnings in the code that calls methods with manifests. + * Why so complicated? Read up the comments for `ClassManifestFactory`. + */ +@nowarn("""cat=deprecation&origin=scala\.reflect\.ClassManifest(DeprecatedApis.*)?""") +object ManifestFactory { + def valueManifests: List[AnyValManifest[_]] = + List(Byte, Short, Char, Int, Long, Float, Double, Boolean, Unit) + + @SerialVersionUID(1L) + final private[reflect] class ByteManifest extends AnyValManifest[scala.Byte]("Byte") { + def runtimeClass: Class[java.lang.Byte] = java.lang.Byte.TYPE + @inline override def newArray(len: Int): Array[Byte] = new Array[Byte](len) + override def newWrappedArray(len: Int): ArraySeq[Byte] = new ArraySeq.ofByte(new Array[Byte](len)) + override def newArrayBuilder(): ArrayBuilder[Byte] = new ArrayBuilder.ofByte() + override def unapply(x: Any): Option[Byte] = { + x match { + case d: Byte => Some(d) + case _ => None + } + } + private def readResolve(): Any = Manifest.Byte + } + val Byte: ByteManifest = new ByteManifest + + @SerialVersionUID(1L) + final private[reflect] class ShortManifest extends AnyValManifest[scala.Short]("Short") { + def runtimeClass: Class[java.lang.Short] = java.lang.Short.TYPE + @inline override def newArray(len: Int): Array[Short] = new Array[Short](len) + override def newWrappedArray(len: Int): ArraySeq[Short] = new ArraySeq.ofShort(new Array[Short](len)) + override def newArrayBuilder(): ArrayBuilder[Short] = new ArrayBuilder.ofShort() + override def unapply(x: Any): Option[Short] = { + x match { + case d: Short => Some(d) + case _ => None + } + } + private def readResolve(): Any = Manifest.Short + } + val Short: ShortManifest = new ShortManifest + + @SerialVersionUID(1L) + final private[reflect] class CharManifest extends AnyValManifest[scala.Char]("Char") { + def runtimeClass: Class[java.lang.Character] = java.lang.Character.TYPE + @inline override def newArray(len: Int): Array[Char] = new Array[Char](len) + override def newWrappedArray(len: Int): ArraySeq[Char] = new ArraySeq.ofChar(new Array[Char](len)) + override def newArrayBuilder(): ArrayBuilder[Char] = new ArrayBuilder.ofChar() + override def unapply(x: Any): Option[Char] = { + x match { + case d: Char => Some(d) + case _ => None + } + } + private def readResolve(): Any = Manifest.Char + } + val Char: CharManifest = new CharManifest + + @SerialVersionUID(1L) + final private[reflect] class IntManifest extends AnyValManifest[scala.Int]("Int") { + def runtimeClass: Class[java.lang.Integer] = java.lang.Integer.TYPE + @inline override def newArray(len: Int): Array[Int] = new Array[Int](len) + override def newWrappedArray(len: Int): ArraySeq[Int] = new ArraySeq.ofInt(new Array[Int](len)) + override def newArrayBuilder(): ArrayBuilder[Int] = new ArrayBuilder.ofInt() + override def unapply(x: Any): Option[Int] = { + x match { + case d: Int => Some(d) + case _ => None + } + } + private def readResolve(): Any = Manifest.Int + } + val Int: IntManifest = new IntManifest + + @SerialVersionUID(1L) + final private[reflect] class LongManifest extends AnyValManifest[scala.Long]("Long") { + def runtimeClass: Class[java.lang.Long] = java.lang.Long.TYPE + @inline override def newArray(len: Int): Array[Long] = new Array[Long](len) + override def newWrappedArray(len: Int): ArraySeq[Long] = new ArraySeq.ofLong(new Array[Long](len)) + override def newArrayBuilder(): ArrayBuilder[Long] = new ArrayBuilder.ofLong() + override def unapply(x: Any): Option[Long] = { + x match { + case d: Long => Some(d) + case _ => None + } + } + private def readResolve(): Any = Manifest.Long + } + val Long: LongManifest = new LongManifest + + @SerialVersionUID(1L) + final private[reflect] class FloatManifest extends AnyValManifest[scala.Float]("Float") { + def runtimeClass: Class[java.lang.Float] = java.lang.Float.TYPE + @inline override def newArray(len: Int): Array[Float] = new Array[Float](len) + override def newWrappedArray(len: Int): ArraySeq[Float] = new ArraySeq.ofFloat(new Array[Float](len)) + override def newArrayBuilder(): ArrayBuilder[Float] = new ArrayBuilder.ofFloat() + override def unapply(x: Any): Option[Float] = { + x match { + case d: Float => Some(d) + case _ => None + } + } + private def readResolve(): Any = Manifest.Float + } + val Float: FloatManifest = new FloatManifest + + @SerialVersionUID(1L) + final private[reflect] class DoubleManifest extends AnyValManifest[scala.Double]("Double") { + def runtimeClass: Class[java.lang.Double] = java.lang.Double.TYPE + @inline override def newArray(len: Int): Array[Double] = new Array[Double](len) + override def newWrappedArray(len: Int): ArraySeq[Double] = new ArraySeq.ofDouble(new Array[Double](len)) + override def newArrayBuilder(): ArrayBuilder[Double] = new ArrayBuilder.ofDouble() + + override def unapply(x: Any): Option[Double] = { + x match { + case d: Double => Some(d) + case _ => None + } + } + private def readResolve(): Any = Manifest.Double + } + val Double: DoubleManifest = new DoubleManifest + + @SerialVersionUID(1L) + final private[reflect] class BooleanManifest extends AnyValManifest[scala.Boolean]("Boolean") { + def runtimeClass: Class[java.lang.Boolean] = java.lang.Boolean.TYPE + @inline override def newArray(len: Int): Array[Boolean] = new Array[Boolean](len) + override def newWrappedArray(len: Int): ArraySeq[Boolean] = new ArraySeq.ofBoolean(new Array[Boolean](len)) + override def newArrayBuilder(): ArrayBuilder[Boolean] = new ArrayBuilder.ofBoolean() + override def unapply(x: Any): Option[Boolean] = { + x match { + case d: Boolean => Some(d) + case _ => None + } + } + private def readResolve(): Any = Manifest.Boolean + } + val Boolean: BooleanManifest = new BooleanManifest + + @SerialVersionUID(1L) + final private[reflect] class UnitManifest extends AnyValManifest[scala.Unit]("Unit") { + def runtimeClass: Class[java.lang.Void] = java.lang.Void.TYPE + @inline override def newArray(len: Int): Array[Unit] = new Array[Unit](len) + override def newWrappedArray(len: Int): ArraySeq[Unit] = new ArraySeq.ofUnit(new Array[Unit](len)) + override def newArrayBuilder(): ArrayBuilder[Unit] = new ArrayBuilder.ofUnit() + override protected def arrayClass[T](tp: Class[_]): Class[Array[T]] = + if (tp eq runtimeClass) classOf[Array[scala.runtime.BoxedUnit]].asInstanceOf[Class[Array[T]]] + else super.arrayClass(tp) + override def unapply(x: Any): Option[Unit] = { + x match { + case d: Unit => Some(d) + case _ => None + } + } + private def readResolve(): Any = Manifest.Unit + } + val Unit: UnitManifest = new UnitManifest + + private[this] val ObjectTYPE = classOf[java.lang.Object] + private[this] val NothingTYPE = classOf[scala.runtime.Nothing$] + private[this] val NullTYPE = classOf[scala.runtime.Null$] + + @SerialVersionUID(1L) + final private class AnyManifest extends PhantomManifest[scala.Any](ObjectTYPE, "Any") { + override def newArray(len: Int) = new Array[scala.Any](len) + override def <:<(that: ClassManifest[_]): Boolean = (that eq this) + private def readResolve(): Any = Manifest.Any + } + val Any: Manifest[scala.Any] = new AnyManifest + + @SerialVersionUID(1L) + final private class ObjectManifest extends PhantomManifest[java.lang.Object](ObjectTYPE, "Object") { + override def newArray(len: Int) = new Array[java.lang.Object](len) + override def <:<(that: ClassManifest[_]): Boolean = (that eq this) || (that eq Any) + private def readResolve(): Any = Manifest.Object + } + val Object: Manifest[java.lang.Object] = new ObjectManifest + + val AnyRef: Manifest[scala.AnyRef] = Object.asInstanceOf[Manifest[scala.AnyRef]] + + @SerialVersionUID(1L) + final private class AnyValPhantomManifest extends PhantomManifest[scala.AnyVal](ObjectTYPE, "AnyVal") { + override def newArray(len: Int) = new Array[scala.AnyVal](len) + override def <:<(that: ClassManifest[_]): Boolean = (that eq this) || (that eq Any) + private def readResolve(): Any = Manifest.AnyVal + } + val AnyVal: Manifest[scala.AnyVal] = new AnyValPhantomManifest + + @SerialVersionUID(1L) + final private class NullManifest extends PhantomManifest[scala.Null](NullTYPE, "Null") { + override def newArray(len: Int) = new Array[scala.Null](len) + override def <:<(that: ClassManifest[_]): Boolean = + (that ne null) && (that ne Nothing) && !(that <:< AnyVal) + private def readResolve(): Any = Manifest.Null + } + val Null: Manifest[scala.Null] = new NullManifest + + @SerialVersionUID(1L) + final private class NothingManifest extends PhantomManifest[scala.Nothing](NothingTYPE, "Nothing") { + override def newArray(len: Int) = new Array[scala.Nothing](len) + override def <:<(that: ClassManifest[_]): Boolean = (that ne null) + private def readResolve(): Any = Manifest.Nothing + } + val Nothing: Manifest[scala.Nothing] = new NothingManifest + + @SerialVersionUID(1L) + final private class SingletonTypeManifest[T <: AnyRef](value: AnyRef) extends Manifest[T] { + lazy val runtimeClass: Class[_ <: AnyRef] = value.getClass + override lazy val toString = value.toString + ".type" + } + + /** Manifest for the singleton type `value.type`. */ + def singleType[T <: AnyRef](value: AnyRef): Manifest[T] = + new SingletonTypeManifest[T](value) + + /** Manifest for the class type `clazz[args]`, where `clazz` is + * a top-level or static class. + * @note This no-prefix, no-arguments case is separate because we + * it's called from ScalaRunTime.boxArray itself. If we + * pass varargs as arrays into this, we get an infinitely recursive call + * to boxArray. (Besides, having a separate case is more efficient) + */ + def classType[T](clazz: Predef.Class[_]): Manifest[T] = + new ClassTypeManifest[T](None, clazz, Nil) + + /** Manifest for the class type `clazz`, where `clazz` is + * a top-level or static class and args are its type arguments. */ + def classType[T](clazz: Predef.Class[T], arg1: Manifest[_], args: Manifest[_]*): Manifest[T] = + new ClassTypeManifest[T](None, clazz, arg1 :: args.toList) + + /** Manifest for the class type `clazz[args]`, where `clazz` is + * a class with non-package prefix type `prefix` and type arguments `args`. + */ + def classType[T](prefix: Manifest[_], clazz: Predef.Class[_], args: Manifest[_]*): Manifest[T] = + new ClassTypeManifest[T](Some(prefix), clazz, args.toList) + + @SerialVersionUID(1L) + private abstract class PhantomManifest[T](_runtimeClass: Predef.Class[_], + override val toString: String) extends ClassTypeManifest[T](None, _runtimeClass, Nil) { + override def equals(that: Any): Boolean = this eq that.asInstanceOf[AnyRef] + @transient + override val hashCode = System.identityHashCode(this) + } + + /** Manifest for the class type `clazz[args]`, where `clazz` is + * a top-level or static class. */ + @SerialVersionUID(1L) + private class ClassTypeManifest[T](prefix: Option[Manifest[_]], + val runtimeClass: Predef.Class[_], + override val typeArguments: List[Manifest[_]]) extends Manifest[T] { + override def toString = + (if (prefix.isEmpty) "" else prefix.get.toString+"#") + + (if (runtimeClass.isArray) "Array" else runtimeClass.getName) + + argString + } + + def arrayType[T](arg: Manifest[_]): Manifest[Array[T]] = + arg.asInstanceOf[Manifest[T]].arrayManifest + + @SerialVersionUID(1L) + private class AbstractTypeManifest[T](prefix: Manifest[_], name: String, upperBound: Predef.Class[_], args: scala.collection.Seq[Manifest[_]]) extends Manifest[T] { + def runtimeClass = upperBound + override val typeArguments = args.toList + override def toString = prefix.toString+"#"+name+argString + } + + /** Manifest for the abstract type `prefix # name`. `upperBound` is not + * strictly necessary as it could be obtained by reflection. It was + * added so that erasure can be calculated without reflection. */ + def abstractType[T](prefix: Manifest[_], name: String, upperBound: Predef.Class[_], args: Manifest[_]*): Manifest[T] = + new AbstractTypeManifest[T](prefix, name, upperBound, args) + + @SerialVersionUID(1L) + private class WildcardManifest[T](lowerBound: Manifest[_], upperBound: Manifest[_]) extends Manifest[T] { + def runtimeClass = upperBound.runtimeClass + override def toString = + "_" + + (if (lowerBound eq Nothing) "" else " >: "+lowerBound) + + (if (upperBound eq Nothing) "" else " <: "+upperBound) + } + + /** Manifest for the unknown type `_ >: L <: U` in an existential. + */ + def wildcardType[T](lowerBound: Manifest[_], upperBound: Manifest[_]): Manifest[T] = + new WildcardManifest[T](lowerBound, upperBound) + + @SerialVersionUID(1L) + private class IntersectionTypeManifest[T](parents: Array[Manifest[_]]) extends Manifest[T] { + // We use an `Array` instead of a `Seq` for `parents` to avoid cyclic dependencies during deserialization + // which can cause serialization proxies to leak and cause a ClassCastException. + def runtimeClass = parents(0).runtimeClass + override def toString = parents.mkString(" with ") + } + + /** Manifest for the intersection type `parents_0 with ... with parents_n`. */ + def intersectionType[T](parents: Manifest[_]*): Manifest[T] = + new IntersectionTypeManifest[T](parents.toArray) +} diff --git a/library/src/scala/reflect/NameTransformer.scala b/library/src/scala/reflect/NameTransformer.scala new file mode 100644 index 000000000000..15d2af96f15e --- /dev/null +++ b/library/src/scala/reflect/NameTransformer.scala @@ -0,0 +1,169 @@ +/* + * 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 reflect + +import scala.language.`2.13` + +/** Provides functions to encode and decode Scala symbolic names. + * Also provides some constants. + */ +object NameTransformer { + // TODO: reduce duplication with and in StdNames + // I made these constants because we cannot change them without bumping our major version anyway. + final val NAME_JOIN_STRING = "$" + final val MODULE_SUFFIX_STRING = "$" + final val MODULE_INSTANCE_NAME = "MODULE$" + final val LOCAL_SUFFIX_STRING = " " + final val LAZY_LOCAL_SUFFIX_STRING = "$lzy" + final val MODULE_VAR_SUFFIX_STRING = "$module" + final val SETTER_SUFFIX_STRING = "_$eq" + final val TRAIT_SETTER_SEPARATOR_STRING = "$_setter_$" + + private[this] val nops = 128 + private[this] val ncodes = 26 * 26 + + private class OpCodes(val op: Char, val code: String, val next: OpCodes) + + private[this] val op2code = new Array[String](nops) + private[this] val code2op = new Array[OpCodes](ncodes) + private def enterOp(op: Char, code: String) = { + op2code(op.toInt) = code + val c = (code.charAt(1) - 'a') * 26 + code.charAt(2) - 'a' + code2op(c.toInt) = new OpCodes(op, code, code2op(c)) + } + + /* Note: decoding assumes opcodes are only ever lowercase. */ + enterOp('~', "$tilde") + enterOp('=', "$eq") + enterOp('<', "$less") + enterOp('>', "$greater") + enterOp('!', "$bang") + enterOp('#', "$hash") + enterOp('%', "$percent") + enterOp('^', "$up") + enterOp('&', "$amp") + enterOp('|', "$bar") + enterOp('*', "$times") + enterOp('/', "$div") + enterOp('+', "$plus") + enterOp('-', "$minus") + enterOp(':', "$colon") + enterOp('\\', "$bslash") + enterOp('?', "$qmark") + enterOp('@', "$at") + + /** Replace operator symbols by corresponding `\$opname`. + * + * @param name the string to encode + * @return the string with all recognized opchars replaced with their encoding + */ + def encode(name: String): String = { + var buf: StringBuilder = null + val len = name.length() + var i = 0 + while (i < len) { + val c = name charAt i + if (c < nops && (op2code(c.toInt) ne null)) { + if (buf eq null) { + buf = new StringBuilder() + buf.append(name.substring(0, i)) + } + buf.append(op2code(c.toInt)) + /* Handle glyphs that are not valid Java/JVM identifiers */ + } + else if (!Character.isJavaIdentifierPart(c)) { + if (buf eq null) { + buf = new StringBuilder() + buf.append(name.substring(0, i)) + } + buf.append("$u%04X".format(c.toInt)) + } + else if (buf ne null) { + buf.append(c) + } + i += 1 + } + if (buf eq null) name else buf.toString() + } + + /** Replace `\$opname` by corresponding operator symbol. + * + * @param name0 the string to decode + * @return the string with all recognized operator symbol encodings replaced with their name + */ + def decode(name0: String): String = { + //System.out.println("decode: " + name);//DEBUG + val name = if (name0.endsWith("")) name0.stripSuffix("") + "this" + else name0 + var buf: StringBuilder = null + val len = name.length() + var i = 0 + while (i < len) { + var ops: OpCodes = null + var unicode = false + val c = name charAt i + if (c == '$' && i + 2 < len) { + val ch1 = name.charAt(i+1) + if ('a' <= ch1 && ch1 <= 'z') { + val ch2 = name.charAt(i+2) + if ('a' <= ch2 && ch2 <= 'z') { + ops = code2op((ch1 - 'a') * 26 + ch2 - 'a') + while ((ops ne null) && !name.startsWith(ops.code, i)) ops = ops.next + if (ops ne null) { + if (buf eq null) { + buf = new StringBuilder() + buf.append(name.substring(0, i)) + } + buf.append(ops.op) + i += ops.code.length() + } + /* Handle the decoding of Unicode glyphs that are + * not valid Java/JVM identifiers */ + } else if ((len - i) >= 6 && // Check that there are enough characters left + ch1 == 'u' && + ((Character.isDigit(ch2)) || + ('A' <= ch2 && ch2 <= 'F'))) { + /* Skip past "$u", next four should be hexadecimal */ + val hex = name.substring(i+2, i+6) + try { + val str = Integer.parseInt(hex, 16).toChar + if (buf eq null) { + buf = new StringBuilder() + buf.append(name.substring(0, i)) + } + buf.append(str) + /* 2 for "$u", 4 for hexadecimal number */ + i += 6 + unicode = true + } catch { + case _:NumberFormatException => + /* `hex` did not decode to a hexadecimal number, so + * do nothing. */ + } + } + } + } + /* If we didn't see an opcode or encoded Unicode glyph, and the + buffer is non-empty, write the current character and advance + one */ + if ((ops eq null) && !unicode) { + if (buf ne null) + buf.append(c) + i += 1 + } + } + //System.out.println("= " + (if (buf == null) name else buf.toString()));//DEBUG + if (buf eq null) name else buf.toString() + } +} diff --git a/library/src/scala/reflect/NoManifest.scala b/library/src/scala/reflect/NoManifest.scala new file mode 100644 index 000000000000..69a2b5e898d3 --- /dev/null +++ b/library/src/scala/reflect/NoManifest.scala @@ -0,0 +1,24 @@ +/* + * 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 reflect + +import scala.language.`2.13` + +/** One of the branches of an [[scala.reflect.OptManifest]]. + */ +// TODO undeprecated until Scala reflection becomes non-experimental +// @deprecated("This notion doesn't have a corresponding concept in 2.10, because scala.reflect.runtime.universe.TypeTag can capture arbitrary types. Use type tags instead of manifests, and there will be no need in opt manifests.", "2.10.0") +object NoManifest extends OptManifest[Nothing] with Serializable { + override def toString = "" +} diff --git a/library/src/scala/reflect/OptManifest.scala b/library/src/scala/reflect/OptManifest.scala new file mode 100644 index 000000000000..fd208ac3a14b --- /dev/null +++ b/library/src/scala/reflect/OptManifest.scala @@ -0,0 +1,24 @@ +/* + * 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 reflect + +import scala.language.`2.13` + +/** A `OptManifest[T]` is an optional [[scala.reflect.Manifest]]. + * + * It is either a `Manifest` or the value `NoManifest`. + */ +// TODO undeprecated until Scala reflection becomes non-experimental +// @deprecated("This notion doesn't have a corresponding concept in 2.10, because scala.reflect.runtime.universe.TypeTag can capture arbitrary types. Use type tags instead of manifests, and there will be no need in opt manifests.", "2.10.0") +trait OptManifest[+T] extends Serializable diff --git a/library/src/scala/reflect/ScalaLongSignature.java b/library/src/scala/reflect/ScalaLongSignature.java new file mode 100644 index 000000000000..29a77dc2f352 --- /dev/null +++ b/library/src/scala/reflect/ScalaLongSignature.java @@ -0,0 +1,24 @@ +/* + * 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.reflect; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ScalaLongSignature { + String[] bytes(); +} diff --git a/library/src/scala/reflect/ScalaSignature.java b/library/src/scala/reflect/ScalaSignature.java new file mode 100644 index 000000000000..dbd5a46bfd10 --- /dev/null +++ b/library/src/scala/reflect/ScalaSignature.java @@ -0,0 +1,24 @@ +/* + * 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.reflect; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ScalaSignature { + String bytes(); +} diff --git a/library/src/scala/reflect/macros/internal/macroImpl.scala b/library/src/scala/reflect/macros/internal/macroImpl.scala new file mode 100644 index 000000000000..0751d0d011ce --- /dev/null +++ b/library/src/scala/reflect/macros/internal/macroImpl.scala @@ -0,0 +1,32 @@ +/* + * 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.reflect.macros +package internal + +import scala.language.`2.13` + +/** Links macro definitions with their implementation. + * This is necessary to preserve macro def -> macro impl links between compilation runs. + * + * More precisely, after typechecking right-hand side of a macro def + * `typedMacroBody` slaps `macroImpl` annotation onto the macro def + * with the result of typechecking as a sole parameter. + * + * As an unfortunate consequence, this annotation must be defined in scala-library.jar, + * because anyone (even those programmers who compile their programs with only scala-library on classpath) + * must be able to define macros. + * + * To lessen the weirdness we define this annotation as `private[scala]`. + * It will not prevent pickling, but it will prevent application developers (and scaladocs) from seeing the annotation. + */ +private[scala] final class macroImpl(val referenceToMacroImpl: Any) extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/reflect/package.scala b/library/src/scala/reflect/package.scala new file mode 100644 index 000000000000..b342fc352122 --- /dev/null +++ b/library/src/scala/reflect/package.scala @@ -0,0 +1,78 @@ +/* + * 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 java.lang.reflect.{AccessibleObject => jAccessibleObject} +import scala.annotation.nowarn + +import scala.language.`2.13` + +package object reflect { + + // in the new scheme of things ClassManifests are aliased to ClassTags + // this is done because we want `toArray` in collections work with ClassTags + // but changing it to use the ClassTag context bound without aliasing ClassManifest + // will break everyone who subclasses and overrides `toArray` + // luckily for us, aliasing doesn't hamper backward compatibility, so it's ideal in this situation + // I wish we could do the same for Manifests and TypeTags though + + // note, by the way, that we don't touch ClassManifest the object + // because its Byte, Short and so on factory fields are incompatible with ClassTag's + + /** A `ClassManifest[T]` is an opaque descriptor for type `T`. + * It is used by the compiler to preserve information necessary + * for instantiating `Arrays` in those cases where the element type + * is unknown at compile time. + * + * The type-relation operators make an effort to present a more accurate + * picture than can be realized with erased types, but they should not be + * relied upon to give correct answers. In particular they are likely to + * be wrong when variance is involved or when a subtype has a different + * number of type arguments than a supertype. + */ + @deprecated("use scala.reflect.ClassTag instead", "2.10.0") + @annotation.implicitNotFound(msg = "No ClassManifest available for ${T}.") + type ClassManifest[T] = scala.reflect.ClassTag[T] + + /** The object `ClassManifest` defines factory methods for manifests. + * It is intended for use by the compiler and should not be used in client code. + */ + @deprecated("use scala.reflect.ClassTag instead", "2.10.0") + val ClassManifest = ClassManifestFactory + + def classTag[T](implicit ctag: ClassTag[T]) = ctag + + /** Make a java reflection object accessible, if it is not already + * and it is possible to do so. If a SecurityException is thrown in the + * attempt, it is caught and discarded. + */ + def ensureAccessible[T <: jAccessibleObject](m: T): T = { + // This calls `setAccessible` unnecessarily, because `isAccessible` is only `true` if `setAccessible(true)` + // was called before, not if the reflected object is inherently accessible. + // TODO: replace by `canAccess` once we're on JDK 9+ + if (!m.isAccessible: @nowarn("cat=deprecation")) { + try m setAccessible true + catch { case _: SecurityException => } // does nothing + } + m + } + + // anchor for the class tag materialization macro emitted during tag materialization in Implicits.scala + // implementation is hardwired into `scala.reflect.reify.Taggers` + // using the mechanism implemented in `scala.tools.reflect.FastTrack` + // todo. once we have implicit macros for tag generation, we can remove this anchor + private[scala] def materializeClassTag[T](): ClassTag[T] = macro ??? +} + +/** An exception that indicates an error during Scala reflection */ +case class ScalaReflectionException(msg: String) extends Exception(msg) diff --git a/library/src/scala/runtime/AbstractFunction0.scala b/library/src/scala/runtime/AbstractFunction0.scala new file mode 100644 index 000000000000..1b2253a01ca2 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction0.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction0[@specialized(Specializable.Primitives) +R] extends Function0[R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction1.scala b/library/src/scala/runtime/AbstractFunction1.scala new file mode 100644 index 000000000000..c490b36e2acc --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction1.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction1[@specialized(Specializable.Arg) -T1, @specialized(Specializable.Return) +R] extends Function1[T1, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction10.scala b/library/src/scala/runtime/AbstractFunction10.scala new file mode 100644 index 000000000000..02d0db52ef19 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction10.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction10[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, +R] extends Function10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction11.scala b/library/src/scala/runtime/AbstractFunction11.scala new file mode 100644 index 000000000000..403c0407e8dc --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction11.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction11[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, +R] extends Function11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction12.scala b/library/src/scala/runtime/AbstractFunction12.scala new file mode 100644 index 000000000000..d704ba37d683 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction12.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction12[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, +R] extends Function12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction13.scala b/library/src/scala/runtime/AbstractFunction13.scala new file mode 100644 index 000000000000..907507a64762 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction13.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction13[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, +R] extends Function13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction14.scala b/library/src/scala/runtime/AbstractFunction14.scala new file mode 100644 index 000000000000..7ea6ed451bfb --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction14.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction14[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, +R] extends Function14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction15.scala b/library/src/scala/runtime/AbstractFunction15.scala new file mode 100644 index 000000000000..c1dd9a63e4f1 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction15.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction15[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, +R] extends Function15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction16.scala b/library/src/scala/runtime/AbstractFunction16.scala new file mode 100644 index 000000000000..6830b3048d60 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction16.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction16[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, +R] extends Function16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction17.scala b/library/src/scala/runtime/AbstractFunction17.scala new file mode 100644 index 000000000000..ab5b6304e546 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction17.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction17[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, +R] extends Function17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction18.scala b/library/src/scala/runtime/AbstractFunction18.scala new file mode 100644 index 000000000000..a5dea969565c --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction18.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction18[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, +R] extends Function18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction19.scala b/library/src/scala/runtime/AbstractFunction19.scala new file mode 100644 index 000000000000..b2723deb1e77 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction19.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction19[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, +R] extends Function19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction2.scala b/library/src/scala/runtime/AbstractFunction2.scala new file mode 100644 index 000000000000..58d84f76481b --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction2.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction2[@specialized(Specializable.Args) -T1, @specialized(Specializable.Args) -T2, @specialized(Specializable.Return) +R] extends Function2[T1, T2, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction20.scala b/library/src/scala/runtime/AbstractFunction20.scala new file mode 100644 index 000000000000..01b65bc527b3 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction20.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction20[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, +R] extends Function20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction21.scala b/library/src/scala/runtime/AbstractFunction21.scala new file mode 100644 index 000000000000..e40ca7daeccc --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction21.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction21[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, -T21, +R] extends Function21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction22.scala b/library/src/scala/runtime/AbstractFunction22.scala new file mode 100644 index 000000000000..a97373349452 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction22.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction22[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, -T21, -T22, +R] extends Function22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction3.scala b/library/src/scala/runtime/AbstractFunction3.scala new file mode 100644 index 000000000000..c1a88307bbae --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction3.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction3[-T1, -T2, -T3, +R] extends Function3[T1, T2, T3, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction4.scala b/library/src/scala/runtime/AbstractFunction4.scala new file mode 100644 index 000000000000..49837a0880d4 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction4.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction4[-T1, -T2, -T3, -T4, +R] extends Function4[T1, T2, T3, T4, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction5.scala b/library/src/scala/runtime/AbstractFunction5.scala new file mode 100644 index 000000000000..a5e5f307f9a6 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction5.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction5[-T1, -T2, -T3, -T4, -T5, +R] extends Function5[T1, T2, T3, T4, T5, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction6.scala b/library/src/scala/runtime/AbstractFunction6.scala new file mode 100644 index 000000000000..e6be191fced3 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction6.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction6[-T1, -T2, -T3, -T4, -T5, -T6, +R] extends Function6[T1, T2, T3, T4, T5, T6, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction7.scala b/library/src/scala/runtime/AbstractFunction7.scala new file mode 100644 index 000000000000..51c7fa6d1cfa --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction7.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction7[-T1, -T2, -T3, -T4, -T5, -T6, -T7, +R] extends Function7[T1, T2, T3, T4, T5, T6, T7, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction8.scala b/library/src/scala/runtime/AbstractFunction8.scala new file mode 100644 index 000000000000..07fd8838e9f0 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction8.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction8[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, +R] extends Function8[T1, T2, T3, T4, T5, T6, T7, T8, R] { + +} diff --git a/library/src/scala/runtime/AbstractFunction9.scala b/library/src/scala/runtime/AbstractFunction9.scala new file mode 100644 index 000000000000..915eddf41932 --- /dev/null +++ b/library/src/scala/runtime/AbstractFunction9.scala @@ -0,0 +1,21 @@ +/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT. See scala.Function0 for timestamp. + +package scala.runtime + +import scala.language.`2.13` + +abstract class AbstractFunction9[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, +R] extends Function9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R] { + +} diff --git a/library/src/scala/runtime/AbstractPartialFunction.scala b/library/src/scala/runtime/AbstractPartialFunction.scala new file mode 100644 index 000000000000..895dac79058b --- /dev/null +++ b/library/src/scala/runtime/AbstractPartialFunction.scala @@ -0,0 +1,38 @@ +/* + * 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 runtime + +import scala.language.`2.13` + +/** `AbstractPartialFunction` reformulates all operations of its supertrait `PartialFunction` + * in terms of `isDefinedAt` and `applyOrElse`. + * + * This allows more efficient implementations in many cases: + * - optimized `orElse` method supports chained `orElse` in linear time, + * and with no slow-down if the `orElse` part is not needed. + * - optimized `lift` method helps to avoid double evaluation of pattern matchers & guards + * of partial function literals. + * + * This trait is used as a basis for implementation of all partial function literals. + */ +abstract class AbstractPartialFunction[@specialized(Specializable.Arg) -T1, @specialized(Specializable.Return) +R] extends Function1[T1, R] with PartialFunction[T1, R] { self => + // this method must be overridden for better performance, + // for backwards compatibility, fall back to the one inherited from PartialFunction + // this assumes the old-school partial functions override the apply method, though + // override def applyOrElse[A1 <: T1, B1 >: R](x: A1, default: A1 => B1): B1 = ??? + + // probably okay to make final since classes compiled before have overridden against the old version of AbstractPartialFunction + // let's not make it final so as not to confuse anyone + /*final*/ def apply(x: T1): R = applyOrElse(x, PartialFunction.empty) +} diff --git a/library/src/scala/runtime/ArrayCharSequence.scala b/library/src/scala/runtime/ArrayCharSequence.scala new file mode 100644 index 000000000000..2ec482915c86 --- /dev/null +++ b/library/src/scala/runtime/ArrayCharSequence.scala @@ -0,0 +1,49 @@ +/* + * 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 runtime + +import scala.language.`2.13` + +// Still need this one since the implicit class ArrayCharSequence only converts +// a single argument. +final class ArrayCharSequence(val xs: Array[Char], start: Int, end: Int) extends CharSequence { + // yikes + // java.lang.VerifyError: (class: scala/runtime/ArrayCharSequence, method: signature: ([C)V) + // Constructor must call super() or this() + // + // def this(xs: Array[Char]) = this(xs, 0, xs.length) + + def length: Int = math.max(0, end - start) + def charAt(index: Int): Char = { + if (0 <= index && index < length) + xs(start + index) + else throw new ArrayIndexOutOfBoundsException(s"$index is out of bounds (min 0, max ${xs.length - 1})") + } + def subSequence(start0: Int, end0: Int): CharSequence = { + if (start0 < 0) throw new ArrayIndexOutOfBoundsException(s"$start0 is out of bounds (min 0, max ${length -1})") + else if (end0 > length) throw new ArrayIndexOutOfBoundsException(s"$end0 is out of bounds (min 0, max ${xs.length -1})") + else if (end0 <= start0) new ArrayCharSequence(xs, 0, 0) + else { + val newlen = end0 - start0 + val start1 = start + start0 + new ArrayCharSequence(xs, start1, start1 + newlen) + } + } + override def toString = { + val start = math.max(this.start, 0) + val end = math.min(xs.length, start + length) + + if (start >= end) "" else new String(xs, start, end - start) + } +} diff --git a/library/src/scala/runtime/BooleanRef.java b/library/src/scala/runtime/BooleanRef.java new file mode 100644 index 000000000000..2c43fd719366 --- /dev/null +++ b/library/src/scala/runtime/BooleanRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class BooleanRef implements java.io.Serializable { + private static final long serialVersionUID = -5730524563015615974L; + + public boolean elem; + public BooleanRef(boolean elem) { this.elem = elem; } + public String toString() { return String.valueOf(elem); } + + public static BooleanRef create(boolean e) { return new BooleanRef(e); } + public static BooleanRef zero() { return new BooleanRef(false); } +} diff --git a/library/src/scala/runtime/BoxedUnit.java b/library/src/scala/runtime/BoxedUnit.java new file mode 100644 index 000000000000..aaa986f87f1a --- /dev/null +++ b/library/src/scala/runtime/BoxedUnit.java @@ -0,0 +1,38 @@ +/* + * 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.runtime; + + +public final class BoxedUnit implements java.io.Serializable { + private static final long serialVersionUID = 8405543498931817370L; + + public final static BoxedUnit UNIT = new BoxedUnit(); + + public final static Class TYPE = java.lang.Void.TYPE; + + private Object readResolve() { return UNIT; } + + private BoxedUnit() { } + + public boolean equals(java.lang.Object other) { + return this == other; + } + + public int hashCode() { + return 0; + } + + public String toString() { + return "()"; + } +} diff --git a/library/src/scala/runtime/BoxesRunTime.java b/library/src/scala/runtime/BoxesRunTime.java new file mode 100644 index 000000000000..3ddc2516fbb9 --- /dev/null +++ b/library/src/scala/runtime/BoxesRunTime.java @@ -0,0 +1,760 @@ +/* + * 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.runtime; + +import scala.math.ScalaNumber; + +/** An object (static class) that defines methods used for creating, + * reverting, and calculating with, boxed values. There are four classes + * of methods in this object: + * - Convenience boxing methods which call the static valueOf method + * on the boxed class, thus utilizing the JVM boxing cache. + * - Convenience unboxing methods returning default value on null. + * - The generalised comparison method to be used when an object may + * be a boxed value. + * - Standard value operators for boxed number and quasi-number values. + */ +public final class BoxesRunTime +{ + private static final int CHAR = 0, /* BYTE = 1, SHORT = 2, */ INT = 3, LONG = 4, FLOAT = 5, DOUBLE = 6, OTHER = 7; + + /** We don't need to return BYTE and SHORT, as everything which might + * care widens to INT. + */ + private static int typeCode(Object a) { + if (a instanceof java.lang.Integer) return INT; + if (a instanceof java.lang.Double) return DOUBLE; + if (a instanceof java.lang.Long) return LONG; + if (a instanceof java.lang.Character) return CHAR; + if (a instanceof java.lang.Float) return FLOAT; + if ((a instanceof java.lang.Byte) || (a instanceof java.lang.Short)) return INT; + return OTHER; + } + +/* BOXING ... BOXING ... BOXING ... BOXING ... BOXING ... BOXING ... BOXING ... BOXING */ + + public static java.lang.Boolean boxToBoolean(boolean b) { + return java.lang.Boolean.valueOf(b); + } + + public static java.lang.Character boxToCharacter(char c) { + return java.lang.Character.valueOf(c); + } + + public static java.lang.Byte boxToByte(byte b) { + return java.lang.Byte.valueOf(b); + } + + public static java.lang.Short boxToShort(short s) { + return java.lang.Short.valueOf(s); + } + + public static java.lang.Integer boxToInteger(int i) { + return java.lang.Integer.valueOf(i); + } + + public static java.lang.Long boxToLong(long l) { + return java.lang.Long.valueOf(l); + } + + public static java.lang.Float boxToFloat(float f) { + return java.lang.Float.valueOf(f); + } + + public static java.lang.Double boxToDouble(double d) { + // System.out.println("box " + d); + // (new Throwable()).printStackTrace(); + return java.lang.Double.valueOf(d); + } + +/* UNBOXING ... UNBOXING ... UNBOXING ... UNBOXING ... UNBOXING ... UNBOXING ... UNBOXING */ + + public static boolean unboxToBoolean(Object b) { + return b == null ? false : ((java.lang.Boolean)b).booleanValue(); + } + + public static char unboxToChar(Object c) { + return c == null ? 0 : ((java.lang.Character)c).charValue(); + } + + public static byte unboxToByte(Object b) { + return b == null ? 0 : ((java.lang.Byte)b).byteValue(); + } + + public static short unboxToShort(Object s) { + return s == null ? 0 : ((java.lang.Short)s).shortValue(); + } + + public static int unboxToInt(Object i) { + return i == null ? 0 : ((java.lang.Integer)i).intValue(); + } + + public static long unboxToLong(Object l) { + return l == null ? 0 : ((java.lang.Long)l).longValue(); + } + + public static float unboxToFloat(Object f) { + return f == null ? 0.0f : ((java.lang.Float)f).floatValue(); + } + + public static double unboxToDouble(Object d) { + // System.out.println("unbox " + d); + return d == null ? 0.0d : ((java.lang.Double)d).doubleValue(); + } + + /* COMPARISON ... COMPARISON ... COMPARISON ... COMPARISON ... COMPARISON ... COMPARISON */ + + public static boolean equals(Object x, Object y) { + if (x == y) return true; + return equals2(x, y); + } + + /** Since all applicable logic has to be present in the equals method of a ScalaNumber + * in any case, we dispatch to it as soon as we spot one on either side. + */ + public static boolean equals2(Object x, Object y) { + if (x instanceof java.lang.Number) + return equalsNumObject((java.lang.Number)x, y); + if (x instanceof java.lang.Character) + return equalsCharObject((java.lang.Character)x, y); + if (x == null) + return y == null; + + return x.equals(y); + } + + public static boolean equalsNumObject(java.lang.Number xn, Object y) { + if (y instanceof java.lang.Number) + return equalsNumNum(xn, (java.lang.Number)y); + if (y instanceof java.lang.Character) + return equalsNumChar(xn, (java.lang.Character)y); + if (xn == null) + return y == null; + + return xn.equals(y); + } + + public static boolean equalsNumNum(java.lang.Number xn, java.lang.Number yn) { + int xcode = typeCode(xn); + int ycode = typeCode(yn); + switch (ycode > xcode ? ycode : xcode) { + case INT: + return xn.intValue() == yn.intValue(); + case LONG: + return xn.longValue() == yn.longValue(); + case FLOAT: + return xn.floatValue() == yn.floatValue(); + case DOUBLE: + return xn.doubleValue() == yn.doubleValue(); + default: + if ((yn instanceof ScalaNumber) && !(xn instanceof ScalaNumber)) + return yn.equals(xn); + } + if (xn == null) + return yn == null; + + return xn.equals(yn); + } + + public static boolean equalsCharObject(java.lang.Character xc, Object y) { + if (y instanceof java.lang.Character) + return xc.charValue() == ((java.lang.Character)y).charValue(); + if (y instanceof java.lang.Number) + return equalsNumChar((java.lang.Number)y, xc); + if (xc == null) + return y == null; + + return xc.equals(y); + } + + public static boolean equalsNumChar(java.lang.Number xn, java.lang.Character yc) { + if (yc == null) + return xn == null; + + char ch = yc.charValue(); + switch (typeCode(xn)) { + case INT: + return xn.intValue() == ch; + case LONG: + return xn.longValue() == ch; + case FLOAT: + return xn.floatValue() == ch; + case DOUBLE: + return xn.doubleValue() == ch; + default: + return xn.equals(yc); + } + } + + private static int unboxCharOrInt(Object arg1, int code) { + if (code == CHAR) + return ((java.lang.Character) arg1).charValue(); + else + return ((java.lang.Number) arg1).intValue(); + } + private static long unboxCharOrLong(Object arg1, int code) { + if (code == CHAR) + return ((java.lang.Character) arg1).charValue(); + else + return ((java.lang.Number) arg1).longValue(); + } + private static float unboxCharOrFloat(Object arg1, int code) { + if (code == CHAR) + return ((java.lang.Character) arg1).charValue(); + else + return ((java.lang.Number) arg1).floatValue(); + } + private static double unboxCharOrDouble(Object arg1, int code) { + if (code == CHAR) + return ((java.lang.Character) arg1).charValue(); + else + return ((java.lang.Number) arg1).doubleValue(); + } + +/* OPERATORS ... OPERATORS ... OPERATORS ... OPERATORS ... OPERATORS ... OPERATORS ... OPERATORS ... OPERATORS */ + + /** arg1 + arg2 */ + public static Object add(Object arg1, Object arg2) throws NoSuchMethodException { + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + int maxcode = (code1 < code2) ? code2 : code1; + if (maxcode <= INT) { + return boxToInteger(unboxCharOrInt(arg1, code1) + unboxCharOrInt(arg2, code2)); + } + if (maxcode <= LONG) { + return boxToLong(unboxCharOrLong(arg1, code1) + unboxCharOrLong(arg2, code2)); + } + if (maxcode <= FLOAT) { + return boxToFloat(unboxCharOrFloat(arg1, code1) + unboxCharOrFloat(arg2, code2)); + } + if (maxcode <= DOUBLE) { + return boxToDouble(unboxCharOrDouble(arg1, code1) + unboxCharOrDouble(arg2, code2)); + } + throw new NoSuchMethodException(); + } + + /** arg1 - arg2 */ + public static Object subtract(Object arg1, Object arg2) throws NoSuchMethodException { + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + int maxcode = (code1 < code2) ? code2 : code1; + if (maxcode <= INT) { + return boxToInteger(unboxCharOrInt(arg1, code1) - unboxCharOrInt(arg2, code2)); + } + if (maxcode <= LONG) { + return boxToLong(unboxCharOrLong(arg1, code1) - unboxCharOrLong(arg2, code2)); + } + if (maxcode <= FLOAT) { + return boxToFloat(unboxCharOrFloat(arg1, code1) - unboxCharOrFloat(arg2, code2)); + } + if (maxcode <= DOUBLE) { + return boxToDouble(unboxCharOrDouble(arg1, code1) - unboxCharOrDouble(arg2, code2)); + } + throw new NoSuchMethodException(); + } + + /** arg1 * arg2 */ + public static Object multiply(Object arg1, Object arg2) throws NoSuchMethodException { + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + int maxcode = (code1 < code2) ? code2 : code1; + if (maxcode <= INT) { + return boxToInteger(unboxCharOrInt(arg1, code1) * unboxCharOrInt(arg2, code2)); + } + if (maxcode <= LONG) { + return boxToLong(unboxCharOrLong(arg1, code1) * unboxCharOrLong(arg2, code2)); + } + if (maxcode <= FLOAT) { + return boxToFloat(unboxCharOrFloat(arg1, code1) * unboxCharOrFloat(arg2, code2)); + } + if (maxcode <= DOUBLE) { + return boxToDouble(unboxCharOrDouble(arg1, code1) * unboxCharOrDouble(arg2, code2)); + } + throw new NoSuchMethodException(); + } + + /** arg1 / arg2 */ + public static Object divide(Object arg1, Object arg2) throws NoSuchMethodException { + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + int maxcode = (code1 < code2) ? code2 : code1; + + if (maxcode <= INT) + return boxToInteger(unboxCharOrInt(arg1, code1) / unboxCharOrInt(arg2, code2)); + if (maxcode <= LONG) + return boxToLong(unboxCharOrLong(arg1, code1) / unboxCharOrLong(arg2, code2)); + if (maxcode <= FLOAT) + return boxToFloat(unboxCharOrFloat(arg1, code1) / unboxCharOrFloat(arg2, code2)); + if (maxcode <= DOUBLE) + return boxToDouble(unboxCharOrDouble(arg1, code1) / unboxCharOrDouble(arg2, code2)); + + throw new NoSuchMethodException(); + } + + /** arg1 % arg2 */ + public static Object takeModulo(Object arg1, Object arg2) throws NoSuchMethodException { + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + int maxcode = (code1 < code2) ? code2 : code1; + + if (maxcode <= INT) + return boxToInteger(unboxCharOrInt(arg1, code1) % unboxCharOrInt(arg2, code2)); + if (maxcode <= LONG) + return boxToLong(unboxCharOrLong(arg1, code1) % unboxCharOrLong(arg2, code2)); + if (maxcode <= FLOAT) + return boxToFloat(unboxCharOrFloat(arg1, code1) % unboxCharOrFloat(arg2, code2)); + if (maxcode <= DOUBLE) + return boxToDouble(unboxCharOrDouble(arg1, code1) % unboxCharOrDouble(arg2, code2)); + + throw new NoSuchMethodException(); + } + + /** arg1 >> arg2 */ + public static Object shiftSignedRight(Object arg1, Object arg2) throws NoSuchMethodException { + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + if (code1 <= INT) { + int val1 = unboxCharOrInt(arg1, code1); + if (code2 <= INT) { + int val2 = unboxCharOrInt(arg2, code2); + return boxToInteger(val1 >> val2); + } + if (code2 <= LONG) { + long val2 = unboxCharOrLong(arg2, code2); + return boxToInteger(val1 >> val2); + } + } + if (code1 <= LONG) { + long val1 = unboxCharOrLong(arg1, code1); + if (code2 <= INT) { + int val2 = unboxCharOrInt(arg2, code2); + return boxToLong(val1 >> val2); + } + if (code2 <= LONG) { + long val2 = unboxCharOrLong(arg2, code2); + return boxToLong(val1 >> val2); + } + } + throw new NoSuchMethodException(); + } + + /** arg1 << arg2 */ + public static Object shiftSignedLeft(Object arg1, Object arg2) throws NoSuchMethodException { + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + if (code1 <= INT) { + int val1 = unboxCharOrInt(arg1, code1); + if (code2 <= INT) { + int val2 = unboxCharOrInt(arg2, code2); + return boxToInteger(val1 << val2); + } + if (code2 <= LONG) { + long val2 = unboxCharOrLong(arg2, code2); + return boxToInteger(val1 << val2); + } + } + if (code1 <= LONG) { + long val1 = unboxCharOrLong(arg1, code1); + if (code2 <= INT) { + int val2 = unboxCharOrInt(arg2, code2); + return boxToLong(val1 << val2); + } + if (code2 <= LONG) { + long val2 = unboxCharOrLong(arg2, code2); + return boxToLong(val1 << val2); + } + } + throw new NoSuchMethodException(); + } + + /** arg1 >>> arg2 */ + public static Object shiftLogicalRight(Object arg1, Object arg2) throws NoSuchMethodException { + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + if (code1 <= INT) { + int val1 = unboxCharOrInt(arg1, code1); + if (code2 <= INT) { + int val2 = unboxCharOrInt(arg2, code2); + return boxToInteger(val1 >>> val2); + } + if (code2 <= LONG) { + long val2 = unboxCharOrLong(arg2, code2); + return boxToInteger(val1 >>> val2); + } + } + if (code1 <= LONG) { + long val1 = unboxCharOrLong(arg1, code1); + if (code2 <= INT) { + int val2 = unboxCharOrInt(arg2, code2); + return boxToLong(val1 >>> val2); + } + if (code2 <= LONG) { + long val2 = unboxCharOrLong(arg2, code2); + return boxToLong(val1 >>> val2); + } + } + throw new NoSuchMethodException(); + } + + /** -arg */ + public static Object negate(Object arg) throws NoSuchMethodException { + int code = typeCode(arg); + if (code <= INT) { + int val = unboxCharOrInt(arg, code); + return boxToInteger(-val); + } + if (code <= LONG) { + long val = unboxCharOrLong(arg, code); + return boxToLong(-val); + } + if (code <= FLOAT) { + float val = unboxCharOrFloat(arg, code); + return boxToFloat(-val); + } + if (code <= DOUBLE) { + double val = unboxCharOrDouble(arg, code); + return boxToDouble(-val); + } + throw new NoSuchMethodException(); + } + + /** +arg */ + public static Object positive(Object arg) throws NoSuchMethodException { + int code = typeCode(arg); + if (code <= INT) { + return boxToInteger(+unboxCharOrInt(arg, code)); + } + if (code <= LONG) { + return boxToLong(+unboxCharOrLong(arg, code)); + } + if (code <= FLOAT) { + return boxToFloat(+unboxCharOrFloat(arg, code)); + } + if (code <= DOUBLE) { + return boxToDouble(+unboxCharOrDouble(arg, code)); + } + throw new NoSuchMethodException(); + } + + /** arg1 & arg2 */ + public static Object takeAnd(Object arg1, Object arg2) throws NoSuchMethodException { + if ((arg1 instanceof Boolean) || (arg2 instanceof Boolean)) { + if ((arg1 instanceof Boolean) && (arg2 instanceof Boolean)) + return boxToBoolean(((java.lang.Boolean) arg1).booleanValue() & ((java.lang.Boolean) arg2).booleanValue()); + else + throw new NoSuchMethodException(); + } + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + int maxcode = (code1 < code2) ? code2 : code1; + + if (maxcode <= INT) + return boxToInteger(unboxCharOrInt(arg1, code1) & unboxCharOrInt(arg2, code2)); + if (maxcode <= LONG) + return boxToLong(unboxCharOrLong(arg1, code1) & unboxCharOrLong(arg2, code2)); + + throw new NoSuchMethodException(); + } + + /** arg1 | arg2 */ + public static Object takeOr(Object arg1, Object arg2) throws NoSuchMethodException { + if ((arg1 instanceof Boolean) || (arg2 instanceof Boolean)) { + if ((arg1 instanceof Boolean) && (arg2 instanceof Boolean)) + return boxToBoolean(((java.lang.Boolean) arg1).booleanValue() | ((java.lang.Boolean) arg2).booleanValue()); + else + throw new NoSuchMethodException(); + } + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + int maxcode = (code1 < code2) ? code2 : code1; + + if (maxcode <= INT) + return boxToInteger(unboxCharOrInt(arg1, code1) | unboxCharOrInt(arg2, code2)); + if (maxcode <= LONG) + return boxToLong(unboxCharOrLong(arg1, code1) | unboxCharOrLong(arg2, code2)); + + throw new NoSuchMethodException(); + } + + /** arg1 ^ arg2 */ + public static Object takeXor(Object arg1, Object arg2) throws NoSuchMethodException { + if ((arg1 instanceof Boolean) || (arg2 instanceof Boolean)) { + if ((arg1 instanceof Boolean) && (arg2 instanceof Boolean)) + return boxToBoolean(((java.lang.Boolean) arg1).booleanValue() ^ ((java.lang.Boolean) arg2).booleanValue()); + else + throw new NoSuchMethodException(); + } + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + int maxcode = (code1 < code2) ? code2 : code1; + + if (maxcode <= INT) + return boxToInteger(unboxCharOrInt(arg1, code1) ^ unboxCharOrInt(arg2, code2)); + if (maxcode <= LONG) + return boxToLong(unboxCharOrLong(arg1, code1) ^ unboxCharOrLong(arg2, code2)); + + throw new NoSuchMethodException(); + } + + /** arg1 && arg2 */ + public static Object takeConditionalAnd(Object arg1, Object arg2) throws NoSuchMethodException { + if ((arg1 instanceof Boolean) && (arg2 instanceof Boolean)) { + return boxToBoolean(((java.lang.Boolean) arg1).booleanValue() && ((java.lang.Boolean) arg2).booleanValue()); + } + throw new NoSuchMethodException(); + } + + /** arg1 || arg2 */ + public static Object takeConditionalOr(Object arg1, Object arg2) throws NoSuchMethodException { + if ((arg1 instanceof Boolean) && (arg2 instanceof Boolean)) { + return boxToBoolean(((java.lang.Boolean) arg1).booleanValue() || ((java.lang.Boolean) arg2).booleanValue()); + } + throw new NoSuchMethodException(); + } + + /** ~arg */ + public static Object complement(Object arg) throws NoSuchMethodException { + int code = typeCode(arg); + if (code <= INT) { + return boxToInteger(~unboxCharOrInt(arg, code)); + } + if (code <= LONG) { + return boxToLong(~unboxCharOrLong(arg, code)); + } + throw new NoSuchMethodException(); + } + + /** !arg */ + public static Object takeNot(Object arg) throws NoSuchMethodException { + if (arg instanceof Boolean) { + return boxToBoolean(!((java.lang.Boolean) arg).booleanValue()); + } + throw new NoSuchMethodException(); + } + + public static Object testEqual(Object arg1, Object arg2) throws NoSuchMethodException { + return boxToBoolean(arg1 == arg2); + } + + public static Object testNotEqual(Object arg1, Object arg2) throws NoSuchMethodException { + return boxToBoolean(arg1 != arg2); + } + + public static Object testLessThan(Object arg1, Object arg2) throws NoSuchMethodException { + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + int maxcode = (code1 < code2) ? code2 : code1; + if (maxcode <= INT) { + int val1 = unboxCharOrInt(arg1, code1); + int val2 = unboxCharOrInt(arg2, code2); + return boxToBoolean(val1 < val2); + } + if (maxcode <= LONG) { + long val1 = unboxCharOrLong(arg1, code1); + long val2 = unboxCharOrLong(arg2, code2); + return boxToBoolean(val1 < val2); + } + if (maxcode <= FLOAT) { + float val1 = unboxCharOrFloat(arg1, code1); + float val2 = unboxCharOrFloat(arg2, code2); + return boxToBoolean(val1 < val2); + } + if (maxcode <= DOUBLE) { + double val1 = unboxCharOrDouble(arg1, code1); + double val2 = unboxCharOrDouble(arg2, code2); + return boxToBoolean(val1 < val2); + } + throw new NoSuchMethodException(); + } + + public static Object testLessOrEqualThan(Object arg1, Object arg2) throws NoSuchMethodException { + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + int maxcode = (code1 < code2) ? code2 : code1; + if (maxcode <= INT) { + int val1 = unboxCharOrInt(arg1, code1); + int val2 = unboxCharOrInt(arg2, code2); + return boxToBoolean(val1 <= val2); + } + if (maxcode <= LONG) { + long val1 = unboxCharOrLong(arg1, code1); + long val2 = unboxCharOrLong(arg2, code2); + return boxToBoolean(val1 <= val2); + } + if (maxcode <= FLOAT) { + float val1 = unboxCharOrFloat(arg1, code1); + float val2 = unboxCharOrFloat(arg2, code2); + return boxToBoolean(val1 <= val2); + } + if (maxcode <= DOUBLE) { + double val1 = unboxCharOrDouble(arg1, code1); + double val2 = unboxCharOrDouble(arg2, code2); + return boxToBoolean(val1 <= val2); + } + throw new NoSuchMethodException(); + } + + public static Object testGreaterOrEqualThan(Object arg1, Object arg2) throws NoSuchMethodException { + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + int maxcode = (code1 < code2) ? code2 : code1; + if (maxcode <= INT) { + int val1 = unboxCharOrInt(arg1, code1); + int val2 = unboxCharOrInt(arg2, code2); + return boxToBoolean(val1 >= val2); + } + if (maxcode <= LONG) { + long val1 = unboxCharOrLong(arg1, code1); + long val2 = unboxCharOrLong(arg2, code2); + return boxToBoolean(val1 >= val2); + } + if (maxcode <= FLOAT) { + float val1 = unboxCharOrFloat(arg1, code1); + float val2 = unboxCharOrFloat(arg2, code2); + return boxToBoolean(val1 >= val2); + } + if (maxcode <= DOUBLE) { + double val1 = unboxCharOrDouble(arg1, code1); + double val2 = unboxCharOrDouble(arg2, code2); + return boxToBoolean(val1 >= val2); + } + throw new NoSuchMethodException(); + } + + public static Object testGreaterThan(Object arg1, Object arg2) throws NoSuchMethodException { + int code1 = typeCode(arg1); + int code2 = typeCode(arg2); + int maxcode = (code1 < code2) ? code2 : code1; + if (maxcode <= INT) { + int val1 = unboxCharOrInt(arg1, code1); + int val2 = unboxCharOrInt(arg2, code2); + return boxToBoolean(val1 > val2); + } + if (maxcode <= LONG) { + long val1 = unboxCharOrLong(arg1, code1); + long val2 = unboxCharOrLong(arg2, code2); + return boxToBoolean(val1 > val2); + } + if (maxcode <= FLOAT) { + float val1 = unboxCharOrFloat(arg1, code1); + float val2 = unboxCharOrFloat(arg2, code2); + return boxToBoolean(val1 > val2); + } + if (maxcode <= DOUBLE) { + double val1 = unboxCharOrDouble(arg1, code1); + double val2 = unboxCharOrDouble(arg2, code2); + return boxToBoolean(val1 > val2); + } + throw new NoSuchMethodException(); + } + + public static boolean isBoxedNumberOrBoolean(Object arg) { + return (arg instanceof java.lang.Boolean) || isBoxedNumber(arg); + } + public static boolean isBoxedNumber(Object arg) { + return ( + (arg instanceof java.lang.Integer) + || (arg instanceof java.lang.Long) + || (arg instanceof java.lang.Double) + || (arg instanceof java.lang.Float) + || (arg instanceof java.lang.Short) + || (arg instanceof java.lang.Character) + || (arg instanceof java.lang.Byte) + ); + } + + /** arg.toChar */ + public static java.lang.Character toCharacter(Object arg) throws NoSuchMethodException { + if (arg instanceof java.lang.Integer) return boxToCharacter((char)unboxToInt(arg)); + if (arg instanceof java.lang.Short) return boxToCharacter((char)unboxToShort(arg)); + if (arg instanceof java.lang.Character) return (java.lang.Character)arg; + if (arg instanceof java.lang.Long) return boxToCharacter((char)unboxToLong(arg)); + if (arg instanceof java.lang.Byte) return boxToCharacter((char)unboxToByte(arg)); + if (arg instanceof java.lang.Float) return boxToCharacter((char)unboxToFloat(arg)); + if (arg instanceof java.lang.Double) return boxToCharacter((char)unboxToDouble(arg)); + throw new NoSuchMethodException(); + } + + /** arg.toByte */ + public static java.lang.Byte toByte(Object arg) throws NoSuchMethodException { + if (arg instanceof java.lang.Integer) return boxToByte((byte)unboxToInt(arg)); + if (arg instanceof java.lang.Character) return boxToByte((byte)unboxToChar(arg)); + if (arg instanceof java.lang.Byte) return (java.lang.Byte)arg; + if (arg instanceof java.lang.Long) return boxToByte((byte)unboxToLong(arg)); + if (arg instanceof java.lang.Short) return boxToByte((byte)unboxToShort(arg)); + if (arg instanceof java.lang.Float) return boxToByte((byte)unboxToFloat(arg)); + if (arg instanceof java.lang.Double) return boxToByte((byte)unboxToDouble(arg)); + throw new NoSuchMethodException(); + } + + /** arg.toShort */ + public static java.lang.Short toShort(Object arg) throws NoSuchMethodException { + if (arg instanceof java.lang.Integer) return boxToShort((short)unboxToInt(arg)); + if (arg instanceof java.lang.Long) return boxToShort((short)unboxToLong(arg)); + if (arg instanceof java.lang.Character) return boxToShort((short)unboxToChar(arg)); + if (arg instanceof java.lang.Byte) return boxToShort((short)unboxToByte(arg)); + if (arg instanceof java.lang.Short) return (java.lang.Short)arg; + if (arg instanceof java.lang.Float) return boxToShort((short)unboxToFloat(arg)); + if (arg instanceof java.lang.Double) return boxToShort((short)unboxToDouble(arg)); + throw new NoSuchMethodException(); + } + + /** arg.toInt */ + public static java.lang.Integer toInteger(Object arg) throws NoSuchMethodException { + if (arg instanceof java.lang.Integer) return (java.lang.Integer)arg; + if (arg instanceof java.lang.Long) return boxToInteger((int)unboxToLong(arg)); + if (arg instanceof java.lang.Double) return boxToInteger((int)unboxToDouble(arg)); + if (arg instanceof java.lang.Float) return boxToInteger((int)unboxToFloat(arg)); + if (arg instanceof java.lang.Character) return boxToInteger((int)unboxToChar(arg)); + if (arg instanceof java.lang.Byte) return boxToInteger((int)unboxToByte(arg)); + if (arg instanceof java.lang.Short) return boxToInteger((int)unboxToShort(arg)); + throw new NoSuchMethodException(); + } + + /** arg.toLong */ + public static java.lang.Long toLong(Object arg) throws NoSuchMethodException { + if (arg instanceof java.lang.Integer) return boxToLong((long)unboxToInt(arg)); + if (arg instanceof java.lang.Double) return boxToLong((long)unboxToDouble(arg)); + if (arg instanceof java.lang.Float) return boxToLong((long)unboxToFloat(arg)); + if (arg instanceof java.lang.Long) return (java.lang.Long)arg; + if (arg instanceof java.lang.Character) return boxToLong((long)unboxToChar(arg)); + if (arg instanceof java.lang.Byte) return boxToLong((long)unboxToByte(arg)); + if (arg instanceof java.lang.Short) return boxToLong((long)unboxToShort(arg)); + throw new NoSuchMethodException(); + } + + /** arg.toFloat */ + public static java.lang.Float toFloat(Object arg) throws NoSuchMethodException { + if (arg instanceof java.lang.Integer) return boxToFloat((float)unboxToInt(arg)); + if (arg instanceof java.lang.Long) return boxToFloat((float)unboxToLong(arg)); + if (arg instanceof java.lang.Float) return (java.lang.Float)arg; + if (arg instanceof java.lang.Double) return boxToFloat((float)unboxToDouble(arg)); + if (arg instanceof java.lang.Character) return boxToFloat((float)unboxToChar(arg)); + if (arg instanceof java.lang.Byte) return boxToFloat((float)unboxToByte(arg)); + if (arg instanceof java.lang.Short) return boxToFloat((float)unboxToShort(arg)); + throw new NoSuchMethodException(); + } + + /** arg.toDouble */ + public static java.lang.Double toDouble(Object arg) throws NoSuchMethodException { + if (arg instanceof java.lang.Integer) return boxToDouble((double)unboxToInt(arg)); + if (arg instanceof java.lang.Float) return boxToDouble((double)unboxToFloat(arg)); + if (arg instanceof java.lang.Double) return (java.lang.Double)arg; + if (arg instanceof java.lang.Long) return boxToDouble((double)unboxToLong(arg)); + if (arg instanceof java.lang.Character) return boxToDouble((double)unboxToChar(arg)); + if (arg instanceof java.lang.Byte) return boxToDouble((double)unboxToByte(arg)); + if (arg instanceof java.lang.Short) return boxToDouble((double)unboxToShort(arg)); + throw new NoSuchMethodException(); + } + +} diff --git a/library/src/scala/runtime/ByteRef.java b/library/src/scala/runtime/ByteRef.java new file mode 100644 index 000000000000..4630440fd7a7 --- /dev/null +++ b/library/src/scala/runtime/ByteRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class ByteRef implements java.io.Serializable { + private static final long serialVersionUID = -100666928446877072L; + + public byte elem; + public ByteRef(byte elem) { this.elem = elem; } + public String toString() { return java.lang.Byte.toString(elem); } + + public static ByteRef create(byte e) { return new ByteRef(e); } + public static ByteRef zero() { return new ByteRef((byte)0); } +} diff --git a/library/src/scala/runtime/CharRef.java b/library/src/scala/runtime/CharRef.java new file mode 100644 index 000000000000..05e8fa55c982 --- /dev/null +++ b/library/src/scala/runtime/CharRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class CharRef implements java.io.Serializable { + private static final long serialVersionUID = 6537214938268005702L; + + public char elem; + public CharRef(char elem) { this.elem = elem; } + public String toString() { return java.lang.Character.toString(elem); } + + public static CharRef create(char e) { return new CharRef(e); } + public static CharRef zero() { return new CharRef((char)0); } +} diff --git a/library/src/scala/runtime/ClassValueCompat.scala b/library/src/scala/runtime/ClassValueCompat.scala new file mode 100644 index 000000000000..24daf6695013 --- /dev/null +++ b/library/src/scala/runtime/ClassValueCompat.scala @@ -0,0 +1,53 @@ +/* + * 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.runtime + +import scala.language.`2.13` +import scala.runtime.ClassValueCompat._ + +private[scala] abstract class ClassValueCompat[T] extends ClassValueInterface[T] { self => + private val instance: ClassValueInterface[T] = + if (classValueAvailable) new JavaClassValue() + else new FallbackClassValue() + + private class JavaClassValue extends ClassValue[T] with ClassValueInterface[T] { + override def computeValue(cls: Class[_]): T = self.computeValue(cls) + } + + private class FallbackClassValue extends ClassValueInterface[T] { + override def get(cls: Class[_]): T = self.computeValue(cls) + + override def remove(cls: Class[_]): Unit = {} + } + + def get(cls: Class[_]): T = instance.get(cls) + + def remove(cls: Class[_]): Unit = instance.remove(cls) + + protected def computeValue(cls: Class[_]): T +} + +private[scala] object ClassValueCompat { + trait ClassValueInterface[T] { + def get(cls: Class[_]): T + + def remove(cls: Class[_]): Unit + } + + private val classValueAvailable: Boolean = try { + Class.forName("java.lang.ClassValue", false, classOf[Object].getClassLoader) + true + } catch { + case _: ClassNotFoundException => false + } +} diff --git a/library/src/scala/runtime/DoubleRef.java b/library/src/scala/runtime/DoubleRef.java new file mode 100644 index 000000000000..52b40cde396e --- /dev/null +++ b/library/src/scala/runtime/DoubleRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class DoubleRef implements java.io.Serializable { + private static final long serialVersionUID = 8304402127373655534L; + + public double elem; + public DoubleRef(double elem) { this.elem = elem; } + public String toString() { return java.lang.Double.toString(elem); } + + public static DoubleRef create(double e) { return new DoubleRef(e); } + public static DoubleRef zero() { return new DoubleRef(0); } +} diff --git a/library/src/scala/runtime/FloatRef.java b/library/src/scala/runtime/FloatRef.java new file mode 100644 index 000000000000..d28d62a0ccc2 --- /dev/null +++ b/library/src/scala/runtime/FloatRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class FloatRef implements java.io.Serializable { + private static final long serialVersionUID = -5793980990371366933L; + + public float elem; + public FloatRef(float elem) { this.elem = elem; } + public String toString() { return java.lang.Float.toString(elem); } + + public static FloatRef create(float e) { return new FloatRef(e); } + public static FloatRef zero() { return new FloatRef(0); } +} diff --git a/library/src/scala/runtime/IntRef.java b/library/src/scala/runtime/IntRef.java new file mode 100644 index 000000000000..d456c3a750b3 --- /dev/null +++ b/library/src/scala/runtime/IntRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class IntRef implements java.io.Serializable { + private static final long serialVersionUID = 1488197132022872888L; + + public int elem; + public IntRef(int elem) { this.elem = elem; } + public String toString() { return java.lang.Integer.toString(elem); } + + public static IntRef create(int e) { return new IntRef(e); } + public static IntRef zero() { return new IntRef(0); } +} diff --git a/library/src/scala/runtime/LambdaDeserialize.scala b/library/src/scala/runtime/LambdaDeserialize.scala new file mode 100644 index 000000000000..202827923592 --- /dev/null +++ b/library/src/scala/runtime/LambdaDeserialize.scala @@ -0,0 +1,46 @@ +/* + * 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.runtime + +import java.lang.invoke._ +import java.util + +import scala.annotation.varargs +import scala.collection.immutable + +import scala.language.`2.13` + +final class LambdaDeserialize private (lookup: MethodHandles.Lookup, targetMethods: Array[MethodHandle]) { + private val targetMethodMap: util.HashMap[String, MethodHandle] = new util.HashMap[String, MethodHandle](targetMethods.length) + + for (targetMethod <- targetMethods) { + val info = lookup.revealDirect(targetMethod) + val key = LambdaDeserialize.nameAndDescriptorKey(info.getName, info.getMethodType.toMethodDescriptorString) + targetMethodMap.put(key, targetMethod) + } + + private val cache = new util.HashMap[String, MethodHandle] + + def deserializeLambda(serialized: SerializedLambda): AnyRef = LambdaDeserializer.deserializeLambda(lookup, cache, targetMethodMap, serialized) +} + +object LambdaDeserialize { + @varargs @throws[Throwable] + def bootstrap(lookup: MethodHandles.Lookup, invokedName: String, invokedType: MethodType, targetMethods: MethodHandle*): CallSite = { + val targetMethodsArray = targetMethods.asInstanceOf[immutable.ArraySeq[_]].unsafeArray.asInstanceOf[Array[MethodHandle]] + val exact = MethodHandleConstants.LAMBDA_DESERIALIZE_DESERIALIZE_LAMBDA.bindTo(new LambdaDeserialize(lookup, targetMethodsArray)).asType(invokedType) + new ConstantCallSite(exact) + } + + def nameAndDescriptorKey(name: String, descriptor: String): String = name + descriptor +} diff --git a/library/src/scala/runtime/LambdaDeserializer.scala b/library/src/scala/runtime/LambdaDeserializer.scala new file mode 100644 index 000000000000..fa2a038f1f3f --- /dev/null +++ b/library/src/scala/runtime/LambdaDeserializer.scala @@ -0,0 +1,130 @@ +/* + * 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.runtime + +import scala.language.`2.13` +import java.lang.invoke._ + +/** + * This class is only intended to be called by synthetic `$deserializeLambda$` method that the Scala 2.12 + * compiler will add to classes hosting lambdas. + * + * It is not intended to be consumed directly. + */ +object LambdaDeserializer { + /** + * Deserialize a lambda by calling `LambdaMetafactory.altMetafactory` to spin up a lambda class + * and instantiating this class with the captured arguments. + * + * A cache may be provided to ensure that subsequent deserialization of the same lambda expression + * is cheap, it amounts to a reflective call to the constructor of the previously created class. + * However, deserialization of the same lambda expression is not guaranteed to use the same class, + * concurrent deserialization of the same lambda expression may spin up more than one class. + * + * Assumptions: + * - No additional marker interfaces are required beyond `java.io.Serializable`. These are + * not stored in `SerializedLambda`, so we can't reconstitute them. + * - No additional bridge methods are passed to `altMetafactory`. Again, these are not stored. + * + * @param lookup The factory for method handles. Must have access to the implementation method, the + * functional interface class, and `java.io.Serializable`. + * @param cache A cache used to avoid spinning up a class for each deserialization of a given lambda. May be `null` + * @param serialized The lambda to deserialize. Note that this is typically created by the `readResolve` + * member of the anonymous class created by `LambdaMetaFactory`. + * @return An instance of the functional interface + */ + def deserializeLambda(lookup: MethodHandles.Lookup, cache: java.util.Map[String, MethodHandle], + targetMethodMap: java.util.Map[String, MethodHandle], serialized: SerializedLambda): AnyRef = { + val result = deserializeLambdaOrNull(lookup, cache, targetMethodMap, serialized) + if (result == null) throw new IllegalArgumentException("Illegal lambda deserialization") + else result + } + + def deserializeLambdaOrNull(lookup: MethodHandles.Lookup, cache: java.util.Map[String, MethodHandle], + targetMethodMap: java.util.Map[String, MethodHandle], serialized: SerializedLambda): AnyRef = { + assert(targetMethodMap != null) + def slashDot(name: String) = name.replaceAll("/", ".") + val loader = lookup.lookupClass().getClassLoader + val implClass = loader.loadClass(slashDot(serialized.getImplClass)) + val key = LambdaDeserialize.nameAndDescriptorKey(serialized.getImplMethodName, serialized.getImplMethodSignature) + + def makeCallSite: CallSite = { + import serialized._ + def parseDescriptor(s: String) = + MethodType.fromMethodDescriptorString(s, loader) + + val funcInterfaceSignature = parseDescriptor(getFunctionalInterfaceMethodSignature) + val instantiated = parseDescriptor(getInstantiatedMethodType) + val functionalInterfaceClass = loader.loadClass(slashDot(getFunctionalInterfaceClass)) + + val implMethodSig = parseDescriptor(getImplMethodSignature) + // Construct the invoked type from the impl method type. This is the type of a factory + // that will be generated by the meta-factory. It is a method type, with param types + // coming form the types of the captures, and return type being the functional interface. + val invokedType: MethodType = { + // 1. Add receiver for non-static impl methods + val withReceiver = getImplMethodKind match { + case MethodHandleInfo.REF_invokeStatic | MethodHandleInfo.REF_newInvokeSpecial => + implMethodSig + case _ => + implMethodSig.insertParameterTypes(0, implClass) + } + // 2. Remove lambda parameters, leaving only captures. Note: the receiver may be a lambda parameter, + // such as in `Function s = Object::toString` + val lambdaArity = funcInterfaceSignature.parameterCount() + val from = withReceiver.parameterCount() - lambdaArity + val to = withReceiver.parameterCount() + + // 3. Drop the lambda return type and replace with the functional interface. + withReceiver.dropParameterTypes(from, to).changeReturnType(functionalInterfaceClass) + } + + // Lookup the implementation method + val implMethod: MethodHandle = if (targetMethodMap.containsKey(key)) { + targetMethodMap.get(key) + } else { + return null + } + + val flags: Int = LambdaMetafactory.FLAG_SERIALIZABLE + + LambdaMetafactory.altMetafactory( + lookup, getFunctionalInterfaceMethodName, invokedType, + + /* samMethodType = */ funcInterfaceSignature, + /* implMethod = */ implMethod, + /* instantiatedMethodType = */ instantiated, + /* flags = */ flags.asInstanceOf[AnyRef] + ) + } + + val factory: MethodHandle = if (cache == null) { + val callSite = makeCallSite + if (callSite == null) return null + callSite.getTarget + } else cache.synchronized{ + cache.get(key) match { + case null => + val callSite = makeCallSite + if (callSite == null) return null + val temp = callSite.getTarget + cache.put(key, temp) + temp + case target => target + } + } + + val captures = Array.tabulate(serialized.getCapturedArgCount)(n => serialized.getCapturedArg(n)) + factory.invokeWithArguments(captures: _*) + } +} diff --git a/library/src/scala/runtime/LazyRef.scala b/library/src/scala/runtime/LazyRef.scala new file mode 100644 index 000000000000..a27f16c927a5 --- /dev/null +++ b/library/src/scala/runtime/LazyRef.scala @@ -0,0 +1,173 @@ +/* + * 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.runtime + +import scala.language.`2.13` + +/** Classes used as holders for lazy vals defined in methods. */ + +@SerialVersionUID(1L) +class LazyRef[T] extends Serializable { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: T = _ + def value: T = _value + def initialize(value: T): T = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyRef ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +@SerialVersionUID(1L) +class LazyBoolean extends Serializable { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Boolean = _ + def value: Boolean = _value + def initialize(value: Boolean): Boolean = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyBoolean ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +@SerialVersionUID(1L) +class LazyByte extends Serializable { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Byte = _ + + def value: Byte = _value + + def initialize(value: Byte): Byte = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyByte ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +@SerialVersionUID(1L) +class LazyChar extends Serializable { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Char = _ + def value: Char = _value + def initialize(value: Char): Char = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyChar ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +@SerialVersionUID(1L) +class LazyShort extends Serializable { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Short = _ + def value: Short = _value + def initialize(value: Short): Short = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyShort ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +@SerialVersionUID(1L) +class LazyInt extends Serializable { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Int = _ + def value: Int = _value + def initialize(value: Int): Int = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyInt ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +@SerialVersionUID(1L) +class LazyLong extends Serializable { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Long = _ + def value: Long = _value + def initialize(value: Long): Long = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyLong ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +@SerialVersionUID(1L) +class LazyFloat extends Serializable { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Float = _ + def value: Float = _value + def initialize(value: Float): Float = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyFloat ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +@SerialVersionUID(1L) +class LazyDouble extends Serializable { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Double = _ + def value: Double = _value + def initialize(value: Double): Double = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyDouble ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +@SerialVersionUID(1L) +class LazyUnit extends Serializable { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + def initialize(): Unit = _initialized = true + + override def toString = s"LazyUnit${if (_initialized) "" else " thunk"}" +} diff --git a/library/src/scala/runtime/LazyVals.scala b/library/src/scala/runtime/LazyVals.scala index 9959f99f6e17..deea2201414a 100644 --- a/library/src/scala/runtime/LazyVals.scala +++ b/library/src/scala/runtime/LazyVals.scala @@ -26,7 +26,7 @@ object LazyVals { } private val base: Int = { - val processors = java.lang.Runtime.getRuntime.nn.availableProcessors() + val processors = java.lang.Runtime.getRuntime.availableProcessors() 8 * processors * processors } diff --git a/library/src/scala/runtime/LongRef.java b/library/src/scala/runtime/LongRef.java new file mode 100644 index 000000000000..9e189af0ef2b --- /dev/null +++ b/library/src/scala/runtime/LongRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class LongRef implements java.io.Serializable { + private static final long serialVersionUID = -3567869820105829499L; + + public long elem; + public LongRef(long elem) { this.elem = elem; } + public String toString() { return java.lang.Long.toString(elem); } + + public static LongRef create(long e) { return new LongRef(e); } + public static LongRef zero() { return new LongRef(0); } +} diff --git a/library/src/scala/runtime/MethodCache.scala b/library/src/scala/runtime/MethodCache.scala new file mode 100644 index 000000000000..9cd59a1cf4d3 --- /dev/null +++ b/library/src/scala/runtime/MethodCache.scala @@ -0,0 +1,87 @@ +/* + * 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 runtime + +import scala.language.`2.13` +import java.lang.reflect.{ Method => JMethod } +import java.lang.{ Class => JClass } + +import scala.annotation.tailrec + +/** An element of a polymorphic object cache. + * This class is referred to by the `CleanUp` phase. Each `PolyMethodCache` chain + * must only relate to one method as `PolyMethodCache` does not identify + * the method name and argument types. In practice, one variable will be + * generated per call point, and will uniquely relate to the method called + * at that point, making the method name and argument types irrelevant. */ +/* TODO: if performance is acceptable, PolyMethodCache should be made generic on the method type */ +private[scala] sealed abstract class MethodCache { + /** Searches for a cached method in the `MethodCache` chain that + * is compatible with receiver class `forReceiver`. If none is cached, + * `null` is returned. If `null` is returned, find's caller should look- + * up the right method using whichever means it prefers, and add it to + * the cache for later use. */ + def find(forReceiver: JClass[_]): JMethod + def add(forReceiver: JClass[_], forMethod: JMethod): MethodCache +} + +private[scala] final class EmptyMethodCache extends MethodCache { + + def find(forReceiver: JClass[_]): JMethod = null + + def add(forReceiver: JClass[_], forMethod: JMethod): MethodCache = + new PolyMethodCache(this, forReceiver, forMethod, 1) + +} + +private[scala] final class MegaMethodCache( + private[this] val forName: String, + private[this] val forParameterTypes: Array[JClass[_]] +) extends MethodCache { + + def find(forReceiver: JClass[_]): JMethod = + forReceiver.getMethod(forName, forParameterTypes:_*) + + def add(forReceiver: JClass[_], forMethod: JMethod): MethodCache = this + +} + +private[scala] final class PolyMethodCache( + private[this] val next: MethodCache, + private[this] val receiver: JClass[_], + private[this] val method: JMethod, + private[this] val complexity: Int +) extends MethodCache { + + /** To achieve tail recursion this must be a separate method + * from `find`, because the type of next is not `PolyMethodCache`. + */ + @tailrec private def findInternal(forReceiver: JClass[_]): JMethod = + if (forReceiver eq receiver) method + else next match { + case x: PolyMethodCache => x findInternal forReceiver + case _ => next find forReceiver + } + + def find(forReceiver: JClass[_]): JMethod = findInternal(forReceiver) + + // TODO: come up with a more realistic number + final private val MaxComplexity = 160 + + def add(forReceiver: JClass[_], forMethod: JMethod): MethodCache = + if (complexity < MaxComplexity) + new PolyMethodCache(this, forReceiver, forMethod, complexity + 1) + else + new MegaMethodCache(forMethod.getName, forMethod.getParameterTypes) +} diff --git a/library/src/scala/runtime/MethodHandleConstants.java b/library/src/scala/runtime/MethodHandleConstants.java new file mode 100644 index 000000000000..16773431f86a --- /dev/null +++ b/library/src/scala/runtime/MethodHandleConstants.java @@ -0,0 +1,35 @@ +/* + * 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.runtime; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.SerializedLambda; + +class MethodHandleConstants { + // static final MethodHandles are optimized by the JIT (https://stackoverflow.com/a/14146641/248998) + static final MethodHandle LAMBDA_DESERIALIZE_DESERIALIZE_LAMBDA; + + static { + LAMBDA_DESERIALIZE_DESERIALIZE_LAMBDA = lookupDeserialize(); + } + + private static MethodHandle lookupDeserialize() { + try { + return MethodHandles.lookup().findVirtual(Class.forName("scala.runtime.LambdaDeserialize"), "deserializeLambda", MethodType.methodType(Object.class, SerializedLambda.class)); + } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) { + throw new ExceptionInInitializerError(e); + } + } +} diff --git a/library/src/scala/runtime/ModuleSerializationProxy.scala b/library/src/scala/runtime/ModuleSerializationProxy.scala new file mode 100644 index 000000000000..91f2d7acf1c9 --- /dev/null +++ b/library/src/scala/runtime/ModuleSerializationProxy.scala @@ -0,0 +1,44 @@ +/* + * 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.runtime + +import scala.language.`2.13` +import java.io.Serializable +import java.security.PrivilegedActionException +import java.security.PrivilegedExceptionAction +import scala.annotation.nowarn + +private[runtime] object ModuleSerializationProxy { + private val instances: ClassValueCompat[Object] = new ClassValueCompat[Object] { + @nowarn("cat=deprecation") // AccessController is deprecated on JDK 17 + def getModule(cls: Class[_]): Object = + java.security.AccessController.doPrivileged( + (() => cls.getField("MODULE$").get(null)): PrivilegedExceptionAction[Object]) + override protected def computeValue(cls: Class[_]): Object = + try getModule(cls) + catch { + case e: PrivilegedActionException => + rethrowRuntime(e.getCause) + } + } + + private def rethrowRuntime(e: Throwable): Object = e match { + case re: RuntimeException => throw re + case _ => throw new RuntimeException(e) + } +} + +@SerialVersionUID(1L) +final class ModuleSerializationProxy(moduleClass: Class[_]) extends Serializable { + private def readResolve = ModuleSerializationProxy.instances.get(moduleClass) +} diff --git a/library/src/scala/runtime/NonLocalReturnControl.scala b/library/src/scala/runtime/NonLocalReturnControl.scala new file mode 100644 index 000000000000..ad68c0162d48 --- /dev/null +++ b/library/src/scala/runtime/NonLocalReturnControl.scala @@ -0,0 +1,22 @@ +/* + * 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.runtime + +import scala.language.`2.13` +import scala.util.control.ControlThrowable + +// remove Unit specialization when binary compatibility permits +@annotation.nowarn("cat=lint-unit-specialization") +class NonLocalReturnControl[@specialized(Byte, Short, Int, Long, Char, Float, Double, Boolean, Unit) T](val key: AnyRef, val value: T) extends ControlThrowable { + final override def fillInStackTrace(): Throwable = this +} diff --git a/library/src/scala/runtime/Nothing$.scala b/library/src/scala/runtime/Nothing$.scala new file mode 100644 index 000000000000..b657fddc0383 --- /dev/null +++ b/library/src/scala/runtime/Nothing$.scala @@ -0,0 +1,23 @@ +/* + * 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 runtime + +import scala.language.`2.13` + +/** + * Dummy class which exist only to satisfy the JVM. It corresponds + * to `scala.Nothing`. If such type appears in method + * signatures, it is erased to this one. + */ +sealed abstract class Nothing$ extends Throwable diff --git a/library/src/scala/runtime/Null$.scala b/library/src/scala/runtime/Null$.scala new file mode 100644 index 000000000000..0202462c914a --- /dev/null +++ b/library/src/scala/runtime/Null$.scala @@ -0,0 +1,24 @@ +/* + * 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 runtime + +import scala.language.`2.13` + +/** + * Dummy class which exist only to satisfy the JVM. It corresponds to + * `scala.Null`. If such type appears in method signatures, it is erased + * to this one. A private constructor ensures that Java code can't create + * subclasses. The only value of type Null$ should be null + */ +sealed abstract class Null$ private () diff --git a/library/src/scala/runtime/ObjectRef.java b/library/src/scala/runtime/ObjectRef.java new file mode 100644 index 000000000000..04545449e9a1 --- /dev/null +++ b/library/src/scala/runtime/ObjectRef.java @@ -0,0 +1,25 @@ +/* + * 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.runtime; + +public final class ObjectRef implements java.io.Serializable { + private static final long serialVersionUID = -9055728157600312291L; + + public T elem; + public ObjectRef(T elem) { this.elem = elem; } + @Override + public String toString() { return String.valueOf(elem); } + + public static ObjectRef create(U e) { return new ObjectRef(e); } + public static ObjectRef zero() { return new ObjectRef(null); } +} diff --git a/library/src/scala/runtime/PStatics.scala b/library/src/scala/runtime/PStatics.scala new file mode 100644 index 000000000000..ccab25d3b901 --- /dev/null +++ b/library/src/scala/runtime/PStatics.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime + +import scala.language.`2.13` + +// things that should be in `Statics`, but can't be yet for bincompat reasons +// TODO 3.T: move to `Statics` +private[scala] object PStatics { + // `Int.MaxValue - 8` traditional soft limit to maximize compatibility with diverse JVMs + // See https://stackoverflow.com/a/8381338 for example + final val VM_MaxArraySize = 2147483639 +} diff --git a/library/src/scala/runtime/RichBoolean.scala b/library/src/scala/runtime/RichBoolean.scala new file mode 100644 index 000000000000..9b7745f9859a --- /dev/null +++ b/library/src/scala/runtime/RichBoolean.scala @@ -0,0 +1,20 @@ +/* + * 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 runtime + +import scala.language.`2.13` + +final class RichBoolean(val self: Boolean) extends AnyVal with OrderedProxy[Boolean] { + protected def ord: scala.math.Ordering.Boolean.type = scala.math.Ordering.Boolean +} diff --git a/library/src/scala/runtime/RichByte.scala b/library/src/scala/runtime/RichByte.scala new file mode 100644 index 000000000000..58b68b21b31b --- /dev/null +++ b/library/src/scala/runtime/RichByte.scala @@ -0,0 +1,37 @@ +/* + * 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 runtime + +import scala.language.`2.13` + +final class RichByte(val self: Byte) extends AnyVal with ScalaWholeNumberProxy[Byte] { + protected def num: scala.math.Numeric.ByteIsIntegral.type = scala.math.Numeric.ByteIsIntegral + protected def ord: scala.math.Ordering.Byte.type = scala.math.Ordering.Byte + + override def doubleValue = self.toDouble + override def floatValue = self.toFloat + override def longValue = self.toLong + override def intValue = self.toInt + override def byteValue = self + override def shortValue = self.toShort + + override def isValidByte = true + + // These method are all overridden and redefined to call out to scala.math to avoid 3 allocations: + // the primitive boxing, the value class boxing and instantiation of the Numeric num. + // We'd like to redefine signum and sign too but forwards binary compatibility doesn't allow us to. + override def abs: Byte = math.abs(self).toByte + override def max(that: Byte): Byte = math.max(self, that).toByte + override def min(that: Byte): Byte = math.min(self, that).toByte +} diff --git a/library/src/scala/runtime/RichChar.scala b/library/src/scala/runtime/RichChar.scala new file mode 100644 index 000000000000..8bdda656f987 --- /dev/null +++ b/library/src/scala/runtime/RichChar.scala @@ -0,0 +1,72 @@ +/* + * 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 runtime + +import scala.language.`2.13` + +final class RichChar(val self: Char) extends AnyVal with IntegralProxy[Char] { + protected def num: scala.math.Numeric.CharIsIntegral.type = scala.math.Numeric.CharIsIntegral + protected def ord: scala.math.Ordering.Char.type = scala.math.Ordering.Char + + override def doubleValue = self.toDouble + override def floatValue = self.toFloat + override def longValue = self.toLong + override def intValue = self.toInt + override def byteValue = self.toByte + override def shortValue = self.toShort + + override def isValidChar = true + + // These method are all overridden and redefined to call out to scala.math to avoid 3 allocations: + // the primitive boxing, the value class boxing and instantiation of the Numeric num. + // We'd like to redefine signum and sign too but forwards binary compatibility doesn't allow us to. + override def abs: Char = self + override def max(that: Char): Char = math.max(self.toInt, that.toInt).toChar + override def min(that: Char): Char = math.min(self.toInt, that.toInt).toChar + + def asDigit: Int = Character.digit(self, Character.MAX_RADIX) + + def isControl: Boolean = Character.isISOControl(self) + def isDigit: Boolean = Character.isDigit(self) + def isLetter: Boolean = Character.isLetter(self) + def isLetterOrDigit: Boolean = Character.isLetterOrDigit(self) + def isWhitespace: Boolean = Character.isWhitespace(self) + def isSpaceChar: Boolean = Character.isSpaceChar(self) + def isHighSurrogate: Boolean = Character.isHighSurrogate(self) + def isLowSurrogate: Boolean = Character.isLowSurrogate(self) + def isSurrogate: Boolean = isHighSurrogate || isLowSurrogate + def isUnicodeIdentifierStart: Boolean = Character.isUnicodeIdentifierStart(self) + def isUnicodeIdentifierPart: Boolean = Character.isUnicodeIdentifierPart(self) + def isIdentifierIgnorable: Boolean = Character.isIdentifierIgnorable(self) + def isMirrored: Boolean = Character.isMirrored(self) + + def isLower: Boolean = Character.isLowerCase(self) + def isUpper: Boolean = Character.isUpperCase(self) + def isTitleCase: Boolean = Character.isTitleCase(self) + + def toLower: Char = Character.toLowerCase(self) + def toUpper: Char = Character.toUpperCase(self) + def toTitleCase: Char = Character.toTitleCase(self) + + def getType: Int = Character.getType(self) + def getNumericValue: Int = Character.getNumericValue(self) + def getDirectionality: Byte = Character.getDirectionality(self) + def reverseBytes: Char = Character.reverseBytes(self) + + // Java 5 Character methods not added: + // + // public static boolean isDefined(char ch) + // public static boolean isJavaIdentifierStart(char ch) + // public static boolean isJavaIdentifierPart(char ch) +} diff --git a/library/src/scala/runtime/RichDouble.scala b/library/src/scala/runtime/RichDouble.scala new file mode 100644 index 000000000000..a1b8bdd065fa --- /dev/null +++ b/library/src/scala/runtime/RichDouble.scala @@ -0,0 +1,72 @@ +/* + * 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 runtime + +import scala.language.`2.13` + +final class RichDouble(val self: Double) extends AnyVal with FractionalProxy[Double] { + protected def num: Fractional[Double] = scala.math.Numeric.DoubleIsFractional + protected def ord: Ordering[Double] = scala.math.Ordering.Double.TotalOrdering + + override def doubleValue = self + override def floatValue = self.toFloat + override def longValue = self.toLong + override def intValue = self.toInt + override def byteValue = self.toByte + override def shortValue = self.toShort + + override def isWhole = { + val l = self.toLong + l.toDouble == self || l == Long.MaxValue && self < Double.PositiveInfinity || l == Long.MinValue && self > Double.NegativeInfinity + } + override def isValidByte = self.toByte.toDouble == self + override def isValidShort = self.toShort.toDouble == self + override def isValidChar = self.toChar.toDouble == self + override def isValidInt = self.toInt.toDouble == self + // override def isValidLong = { val l = self.toLong; l.toDouble == self && l != Long.MaxValue } + // override def isValidFloat = self.toFloat.toDouble == self + // override def isValidDouble = !java.lang.Double.isNaN(self) + + def isNaN: Boolean = java.lang.Double.isNaN(self) + def isInfinity: Boolean = java.lang.Double.isInfinite(self) + def isFinite: Boolean = java.lang.Double.isFinite(self) + def isPosInfinity: Boolean = Double.PositiveInfinity == self + def isNegInfinity: Boolean = Double.NegativeInfinity == self + + // These method are all overridden and redefined to call out to scala.math to avoid 3 allocations: + // the primitive boxing, the value class boxing and instantiation of the Numeric num. + // We'd like to redefine sign too but forwards binary compatibility doesn't allow us to. + override def abs: Double = math.abs(self) + override def max(that: Double): Double = math.max(self, that) + override def min(that: Double): Double = math.min(self, that) + @deprecated("signum does not handle -0.0 or Double.NaN; use `sign` method instead", since = "2.13.0") + override def signum: Int = math.signum(self).toInt + + def round: Long = math.round(self) + def ceil: Double = math.ceil(self) + def floor: Double = math.floor(self) + + /** Converts an angle measured in degrees to an approximately equivalent + * angle measured in radians. + * + * @return the measurement of the angle x in radians. + */ + def toRadians: Double = math.toRadians(self) + + /** Converts an angle measured in radians to an approximately equivalent + * angle measured in degrees. + * @return the measurement of the angle x in degrees. + */ + def toDegrees: Double = math.toDegrees(self) +} diff --git a/library/src/scala/runtime/RichFloat.scala b/library/src/scala/runtime/RichFloat.scala new file mode 100644 index 000000000000..78cabf5e7402 --- /dev/null +++ b/library/src/scala/runtime/RichFloat.scala @@ -0,0 +1,73 @@ +/* + * 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 runtime + +import scala.language.`2.13` + +final class RichFloat(val self: Float) extends AnyVal with FractionalProxy[Float] { + protected def num: Fractional[Float] = scala.math.Numeric.FloatIsFractional + protected def ord: Ordering[Float] = scala.math.Ordering.Float.TotalOrdering + + override def doubleValue = self.toDouble + override def floatValue = self + override def longValue = self.toLong + override def intValue = self.toInt + override def byteValue = self.toByte + override def shortValue = self.toShort + + override def isWhole = { + val l = self.toLong + l.toFloat == self || l == Long.MaxValue && self < Float.PositiveInfinity || l == Long.MinValue && self > Float.NegativeInfinity + } + override def isValidByte = self.toByte.toFloat == self + override def isValidShort = self.toShort.toFloat == self + override def isValidChar = self.toChar.toFloat == self + override def isValidInt = { val i = self.toInt; i.toFloat == self && i != Int.MaxValue } + // override def isValidLong = { val l = self.toLong; l.toFloat == self && l != Long.MaxValue } + // override def isValidFloat = !java.lang.Float.isNaN(self) + // override def isValidDouble = !java.lang.Float.isNaN(self) + + def isNaN: Boolean = java.lang.Float.isNaN(self) + def isInfinity: Boolean = java.lang.Float.isInfinite(self) + def isFinite: Boolean = java.lang.Float.isFinite(self) + def isPosInfinity: Boolean = Float.PositiveInfinity == self + def isNegInfinity: Boolean = Float.NegativeInfinity == self + + // These method are all overridden and redefined to call out to scala.math to avoid 3 allocations: + // the primitive boxing, the value class boxing and instantiation of the Numeric num. + // We'd like to redefine sign too but forwards binary compatibility doesn't allow us to. + override def abs: Float = math.abs(self) + override def max(that: Float): Float = math.max(self, that) + override def min(that: Float): Float = math.min(self, that) + @deprecated("signum does not handle -0.0f or Float.NaN; use `sign` method instead", since = "2.13.0") + override def signum: Int = math.signum(self).toInt + + def round: Int = math.round(self) + def ceil: Float = math.ceil(self.toDouble).toFloat + def floor: Float = math.floor(self.toDouble).toFloat + + /** Converts an angle measured in degrees to an approximately equivalent + * angle measured in radians. + * + * @return the measurement of the angle `x` in radians. + */ + def toRadians: Float = math.toRadians(self.toDouble).toFloat + + /** Converts an angle measured in radians to an approximately equivalent + * angle measured in degrees. + * + * @return the measurement of the angle `x` in degrees. + */ + def toDegrees: Float = math.toDegrees(self.toDouble).toFloat +} diff --git a/library/src/scala/runtime/RichInt.scala b/library/src/scala/runtime/RichInt.scala new file mode 100644 index 000000000000..10d2d3b9ea0e --- /dev/null +++ b/library/src/scala/runtime/RichInt.scala @@ -0,0 +1,88 @@ +/* + * 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 runtime + +import scala.language.`2.13` +import scala.collection.immutable.Range + +// Note that this does not implement IntegralProxy[Int] so that it can return +// the Int-specific Range class from until/to. +final class RichInt(val self: Int) extends AnyVal with ScalaNumberProxy[Int] with RangedProxy[Int] { + protected def num: scala.math.Numeric.IntIsIntegral.type = scala.math.Numeric.IntIsIntegral + protected def ord: scala.math.Ordering.Int.type = scala.math.Ordering.Int + + override def doubleValue = self.toDouble + override def floatValue = self.toFloat + override def longValue = self.toLong + override def intValue = self + override def byteValue = self.toByte + override def shortValue = self.toShort + + /** Returns `'''true'''` if this number has no decimal component. + * Always `'''true'''` for `RichInt`. + */ + @deprecated("isWhole on an integer type is always true", "2.12.15") + def isWhole = true + + override def isValidInt = true + def isValidLong = true + + // These method are all overridden and redefined to call out to scala.math to avoid 3 allocations: + // the primitive boxing, the value class boxing and instantiation of the Numeric num. + // We'd like to redefine signum and sign too but forwards binary compatibility doesn't allow us to. + override def abs: Int = math.abs(self) + override def max(that: Int): Int = math.max(self, that) + override def min(that: Int): Int = math.min(self, that) + + /** There is no reason to round an `Int`, but this method is provided to avoid accidental loss of precision from a detour through `Float`. */ + @deprecated("this is an integer type; there is no reason to round it. Perhaps you meant to call this on a floating-point value?", "2.11.0") + def round: Int = self + + def toBinaryString: String = java.lang.Integer.toBinaryString(self) + def toHexString: String = java.lang.Integer.toHexString(self) + def toOctalString: String = java.lang.Integer.toOctalString(self) + + type ResultWithoutStep = Range + + /** + * @param end The final bound of the range to make. + * @return A [[scala.collection.immutable.Range]] from `this` up to but + * not including `end`. + */ + def until(end: Int): Range = Range(self, end) + + /** + * @param end The final bound of the range to make. + * @param step The number to increase by for each step of the range. + * @return A [[scala.collection.immutable.Range]] from `this` up to but + * not including `end`. + */ + def until(end: Int, step: Int): Range = Range(self, end, step) + + /** like `until`, but includes the last index */ + /** + * @param end The final bound of the range to make. + * @return A [[scala.collection.immutable.Range]] from `'''this'''` up to + * and including `end`. + */ + def to(end: Int): Range.Inclusive = Range.inclusive(self, end) + + /** + * @param end The final bound of the range to make. + * @param step The number to increase by for each step of the range. + * @return A [[scala.collection.immutable.Range]] from `'''this'''` up to + * and including `end`. + */ + def to(end: Int, step: Int): Range.Inclusive = Range.inclusive(self, end, step) +} diff --git a/library/src/scala/runtime/RichLong.scala b/library/src/scala/runtime/RichLong.scala new file mode 100644 index 000000000000..2dc7d6ffa2d4 --- /dev/null +++ b/library/src/scala/runtime/RichLong.scala @@ -0,0 +1,51 @@ +/* + * 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 runtime + +import scala.language.`2.13` + +final class RichLong(val self: Long) extends AnyVal with IntegralProxy[Long] { + protected def num: scala.math.Numeric.LongIsIntegral.type = scala.math.Numeric.LongIsIntegral + protected def ord: scala.math.Ordering.Long.type = scala.math.Ordering.Long + + override def doubleValue = self.toDouble + override def floatValue = self.toFloat + override def longValue = self + override def intValue = self.toInt + override def byteValue = self.toByte + override def shortValue = self.toShort + + override def isValidByte = self.toByte.toLong == self + override def isValidShort = self.toShort.toLong == self + override def isValidChar = self.toChar.toLong == self + override def isValidInt = self.toInt.toLong == self + def isValidLong = true + // override def isValidFloat = self.toFloat.toLong == self && self != Long.MaxValue + // override def isValidDouble = self.toDouble.toLong == self && self != Long.MaxValue + + // These method are all overridden and redefined to call out to scala.math to avoid 3 allocations: + // the primitive boxing, the value class boxing and instantiation of the Numeric num. + // We'd like to redefine signum and sign too but forwards binary compatibility doesn't allow us to. + override def abs: Long = math.abs(self) + override def max(that: Long): Long = math.max(self, that) + override def min(that: Long): Long = math.min(self, that) + + /** There is no reason to round a `Long`, but this method is provided to avoid accidental conversion to `Int` through `Float`. */ + @deprecated("this is an integer type; there is no reason to round it. Perhaps you meant to call this on a floating-point value?", "2.11.0") + def round: Long = self + + def toBinaryString: String = java.lang.Long.toBinaryString(self) + def toHexString: String = java.lang.Long.toHexString(self) + def toOctalString: String = java.lang.Long.toOctalString(self) +} diff --git a/library/src/scala/runtime/RichShort.scala b/library/src/scala/runtime/RichShort.scala new file mode 100644 index 000000000000..31f189380f94 --- /dev/null +++ b/library/src/scala/runtime/RichShort.scala @@ -0,0 +1,37 @@ +/* + * 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 runtime + +import scala.language.`2.13` + +final class RichShort(val self: Short) extends AnyVal with ScalaWholeNumberProxy[Short] { + protected def num: scala.math.Numeric.ShortIsIntegral.type = scala.math.Numeric.ShortIsIntegral + protected def ord: scala.math.Ordering.Short.type = scala.math.Ordering.Short + + override def doubleValue = self.toDouble + override def floatValue = self.toFloat + override def longValue = self.toLong + override def intValue = self.toInt + override def byteValue = self.toByte + override def shortValue = self + + override def isValidShort = true + + // These method are all overridden and redefined to call out to scala.math to avoid 3 allocations: + // the primitive boxing, the value class boxing and instantiation of the Numeric num. + // We'd like to redefine signum and sign too but forwards binary compatibility doesn't allow us to. + override def abs: Short = math.abs(self.toInt).toShort + override def max(that: Short): Short = math.max(self.toInt, that.toInt).toShort + override def min(that: Short): Short = math.min(self.toInt, that.toInt).toShort +} diff --git a/library/src/scala/runtime/ScalaNumberProxy.scala b/library/src/scala/runtime/ScalaNumberProxy.scala new file mode 100644 index 000000000000..cf70558469de --- /dev/null +++ b/library/src/scala/runtime/ScalaNumberProxy.scala @@ -0,0 +1,88 @@ +/* + * 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 runtime + +import scala.language.`2.13` +import scala.collection.immutable +import scala.math.ScalaNumericAnyConversions +import immutable.NumericRange +import Proxy.Typed +import scala.annotation.nowarn + +/** Base classes for the Rich* wrappers of the primitive types. + * As with all classes in scala.runtime.*, this is not a supported API. + */ +@nowarn("cat=deprecation") +trait ScalaNumberProxy[T] extends Any with ScalaNumericAnyConversions with Typed[T] with OrderedProxy[T] { + protected implicit def num: Numeric[T] + + def doubleValue = num.toDouble(self) + def floatValue = num.toFloat(self) + def longValue = num.toLong(self) + def intValue = num.toInt(self) + def byteValue = intValue.toByte + def shortValue = intValue.toShort + + /** Returns `'''this'''` if `'''this''' < that` or `that` otherwise. */ + def min(that: T): T = num.min(self, that) + /** Returns `'''this'''` if `'''this''' > that` or `that` otherwise. */ + def max(that: T): T = num.max(self, that) + /** Returns the absolute value of `'''this'''`. */ + def abs = num.abs(self) + /** + * Returns the sign of `'''this'''`. + * zero if the argument is zero, -zero if the argument is -zero, + * one if the argument is greater than zero, -one if the argument is less than zero, + * and NaN if the argument is NaN where applicable. + */ + def sign: T = num.sign(self) + /** Returns the signum of `'''this'''`. */ + @deprecated("use `sign` method instead", since = "2.13.0") def signum: Int = num.signum(self) +} +trait ScalaWholeNumberProxy[T] extends Any with ScalaNumberProxy[T] { + @deprecated("isWhole on an integer type is always true", "2.12.15") + def isWhole = true +} +trait IntegralProxy[T] extends Any with ScalaWholeNumberProxy[T] with RangedProxy[T] { + protected implicit def num: Integral[T] + type ResultWithoutStep = NumericRange[T] + + def until(end: T): NumericRange.Exclusive[T] = NumericRange(self, end, num.one) + def until(end: T, step: T): NumericRange.Exclusive[T] = NumericRange(self, end, step) + def to(end: T): NumericRange.Inclusive[T] = NumericRange.inclusive(self, end, num.one) + def to(end: T, step: T): NumericRange.Inclusive[T] = NumericRange.inclusive(self, end, step) +} +trait FractionalProxy[T] extends Any with ScalaNumberProxy[T] { + protected implicit def num: Fractional[T] + + def isWhole = false +} + +@nowarn("cat=deprecation") +trait OrderedProxy[T] extends Any with Ordered[T] with Typed[T] { + protected def ord: Ordering[T] + + def compare(y: T) = ord.compare(self, y) +} + +@nowarn("cat=deprecation") +trait RangedProxy[T] extends Any with Typed[T] { + type ResultWithoutStep + + def until(end: T): ResultWithoutStep + def until(end: T, step: T): immutable.IndexedSeq[T] + def to(end: T): ResultWithoutStep + def to(end: T, step: T): immutable.IndexedSeq[T] +} + diff --git a/library/src/scala/runtime/ScalaRunTime.scala b/library/src/scala/runtime/ScalaRunTime.scala new file mode 100644 index 000000000000..d63056b372c0 --- /dev/null +++ b/library/src/scala/runtime/ScalaRunTime.scala @@ -0,0 +1,300 @@ +/* + * 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 runtime + +import scala.language.`2.13` +import scala.collection.{AbstractIterator, AnyConstr, SortedOps, StrictOptimizedIterableOps, StringOps, StringView, View} +import scala.collection.generic.IsIterable +import scala.collection.immutable.{ArraySeq, NumericRange} +import scala.collection.mutable.StringBuilder +import scala.math.min +import scala.reflect.{ClassTag, classTag} +import java.lang.{Class => jClass} +import java.lang.reflect.{Method => JMethod} + +/** The object ScalaRunTime provides support methods required by + * the scala runtime. All these methods should be considered + * outside the API and subject to change or removal without notice. + */ +object ScalaRunTime { + def isArray(x: Any, atLevel: Int = 1): Boolean = + x != null && isArrayClass(x.getClass, atLevel) + + private def isArrayClass(clazz: jClass[_], atLevel: Int): Boolean = + clazz.isArray && (atLevel == 1 || isArrayClass(clazz.getComponentType, atLevel - 1)) + + // A helper method to make my life in the pattern matcher a lot easier. + def drop[Repr](coll: Repr, num: Int)(implicit iterable: IsIterable[Repr] { type C <: Repr }): Repr = + iterable(coll) drop num + + /** Return the class object representing an array with element class `clazz`. + */ + def arrayClass(clazz: jClass[_]): jClass[_] = { + // newInstance throws an exception if the erasure is Void.TYPE. see scala/bug#5680 + if (clazz == java.lang.Void.TYPE) classOf[Array[Unit]] + else java.lang.reflect.Array.newInstance(clazz, 0).getClass + } + + /** Return the class object representing an unboxed value type, + * e.g., classOf[int], not classOf[java.lang.Integer]. The compiler + * rewrites expressions like 5.getClass to come here. + */ + def anyValClass[T <: AnyVal : ClassTag](value: T): jClass[T] = + classTag[T].runtimeClass.asInstanceOf[jClass[T]] + + /** Retrieve generic array element */ + def array_apply(xs: AnyRef, idx: Int): Any = { + (xs: @unchecked) match { + case x: Array[AnyRef] => x(idx).asInstanceOf[Any] + case x: Array[Int] => x(idx).asInstanceOf[Any] + case x: Array[Double] => x(idx).asInstanceOf[Any] + case x: Array[Long] => x(idx).asInstanceOf[Any] + case x: Array[Float] => x(idx).asInstanceOf[Any] + case x: Array[Char] => x(idx).asInstanceOf[Any] + case x: Array[Byte] => x(idx).asInstanceOf[Any] + case x: Array[Short] => x(idx).asInstanceOf[Any] + case x: Array[Boolean] => x(idx).asInstanceOf[Any] + case null => throw new NullPointerException + } + } + + /** update generic array element */ + def array_update(xs: AnyRef, idx: Int, value: Any): Unit = { + (xs: @unchecked) match { + case x: Array[AnyRef] => x(idx) = value.asInstanceOf[AnyRef] + case x: Array[Int] => x(idx) = value.asInstanceOf[Int] + case x: Array[Double] => x(idx) = value.asInstanceOf[Double] + case x: Array[Long] => x(idx) = value.asInstanceOf[Long] + case x: Array[Float] => x(idx) = value.asInstanceOf[Float] + case x: Array[Char] => x(idx) = value.asInstanceOf[Char] + case x: Array[Byte] => x(idx) = value.asInstanceOf[Byte] + case x: Array[Short] => x(idx) = value.asInstanceOf[Short] + case x: Array[Boolean] => x(idx) = value.asInstanceOf[Boolean] + case null => throw new NullPointerException + } + } + + /** Get generic array length */ + @inline def array_length(xs: AnyRef): Int = java.lang.reflect.Array.getLength(xs) + + // TODO: bytecode Object.clone() will in fact work here and avoids + // the type switch. See Array_clone comment in BCodeBodyBuilder. + def array_clone(xs: AnyRef): AnyRef = (xs: @unchecked) match { + case x: Array[AnyRef] => x.clone() + case x: Array[Int] => x.clone() + case x: Array[Double] => x.clone() + case x: Array[Long] => x.clone() + case x: Array[Float] => x.clone() + case x: Array[Char] => x.clone() + case x: Array[Byte] => x.clone() + case x: Array[Short] => x.clone() + case x: Array[Boolean] => x.clone() + case null => throw new NullPointerException + } + + /** Convert an array to an object array. + * Needed to deal with vararg arguments of primitive types that are passed + * to a generic Java vararg parameter T ... + */ + def toObjectArray(src: AnyRef): Array[Object] = { + def copy[@specialized T <: AnyVal](src: Array[T]): Array[Object] = { + val length = src.length + if (length == 0) Array.emptyObjectArray + else { + val dest = new Array[Object](length) + var i = 0 + while (i < length) { + dest(i) = src(i).asInstanceOf[AnyRef] + i += 1 + } + dest + } + } + (src: @unchecked) match { + case x: Array[AnyRef] => x + case x: Array[Int] => copy(x) + case x: Array[Double] => copy(x) + case x: Array[Long] => copy(x) + case x: Array[Float] => copy(x) + case x: Array[Char] => copy(x) + case x: Array[Byte] => copy(x) + case x: Array[Short] => copy(x) + case x: Array[Boolean] => copy(x) + case null => throw new NullPointerException + } + } + + def toArray[T](xs: scala.collection.Seq[T]) = { + if (xs.isEmpty) Array.emptyObjectArray + else { + val arr = new Array[AnyRef](xs.length) + val it = xs.iterator + var i = 0 + while (it.hasNext) { + arr(i) = it.next().asInstanceOf[AnyRef] + i += 1 + } + arr + } + } + + // Java bug: https://bugs.java.com/view_bug.do?bug_id=4071957 + // More background at ticket #2318. + def ensureAccessible(m: JMethod): JMethod = scala.reflect.ensureAccessible(m) + + // This is called by the synthetic case class `toString` method. + // It originally had a `CaseClass` parameter type which was changed to `Product`. + def _toString(x: Product): String = + x.productIterator.mkString(x.productPrefix + "(", ",", ")") + + // This method is called by case classes compiled by older Scala 2.13 / Scala 3 versions, so it needs to stay. + // In newer versions, the synthetic case class `hashCode` has either the calculation inlined or calls + // `MurmurHash3.productHash`. + // There used to be an `_equals` method as well which was removed in 5e7e81ab2a. + def _hashCode(x: Product): Int = scala.util.hashing.MurmurHash3.caseClassHash(x) + + /** A helper for case classes. */ + def typedProductIterator[T](x: Product): Iterator[T] = { + new AbstractIterator[T] { + private[this] var c: Int = 0 + private[this] val cmax = x.productArity + def hasNext = c < cmax + def next() = { + val result = x.productElement(c) + c += 1 + result.asInstanceOf[T] + } + } + } + + /** Given any Scala value, convert it to a String. + * + * The primary motivation for this method is to provide a means for + * correctly obtaining a String representation of a value, while + * avoiding the pitfalls of naively calling toString on said value. + * In particular, it addresses the fact that (a) toString cannot be + * called on null and (b) depending on the apparent type of an + * array, toString may or may not print it in a human-readable form. + * + * @param arg the value to stringify + * @return a string representation of arg. + */ + def stringOf(arg: Any): String = stringOf(arg, scala.Int.MaxValue) + def stringOf(arg: Any, maxElements: Int): String = { + def packageOf(x: AnyRef) = x.getClass.getPackage match { + case null => "" + case p => p.getName + } + def isScalaClass(x: AnyRef) = packageOf(x) startsWith "scala." + def isScalaCompilerClass(x: AnyRef) = packageOf(x) startsWith "scala.tools.nsc." + + // includes specialized subclasses and future proofed against hypothetical TupleN (for N > 22) + def isTuple(x: Any) = x != null && x.getClass.getName.startsWith("scala.Tuple") + + // We use reflection because the scala.xml package might not be available + def isSubClassOf(potentialSubClass: Class[_], ofClass: String) = + try { + val classLoader = potentialSubClass.getClassLoader + val clazz = Class.forName(ofClass, /*initialize =*/ false, classLoader) + clazz.isAssignableFrom(potentialSubClass) + } catch { + case cnfe: ClassNotFoundException => false + } + def isXmlNode(potentialSubClass: Class[_]) = isSubClassOf(potentialSubClass, "scala.xml.Node") + def isXmlMetaData(potentialSubClass: Class[_]) = isSubClassOf(potentialSubClass, "scala.xml.MetaData") + + // When doing our own iteration is dangerous + def useOwnToString(x: Any) = x match { + // Range/NumericRange have a custom toString to avoid walking a gazillion elements + case _: Range | _: NumericRange[_] => true + // Sorted collections to the wrong thing (for us) on iteration - ticket #3493 + case _: SortedOps[_, _] => true + // StringBuilder(a, b, c) and similar not so attractive + case _: StringView | _: StringOps | _: StringBuilder => true + // Don't want to evaluate any elements in a view + case _: View[_] => true + // Node extends NodeSeq extends Seq[Node] and MetaData extends Iterable[MetaData] + // -> catch those by isXmlNode and isXmlMetaData. + // Don't want to a) traverse infinity or b) be overly helpful with peoples' custom + // collections which may have useful toString methods - ticket #3710 + // or c) print AbstractFiles which are somehow also Iterable[AbstractFile]s. + case x: Iterable[_] => (!x.isInstanceOf[StrictOptimizedIterableOps[_, AnyConstr, _]]) || !isScalaClass(x) || isScalaCompilerClass(x) || isXmlNode(x.getClass) || isXmlMetaData(x.getClass) + // Otherwise, nothing could possibly go wrong + case _ => false + } + + // A variation on inner for maps so they print -> instead of bare tuples + def mapInner(arg: Any): String = arg match { + case (k, v) => inner(k) + " -> " + inner(v) + case _ => inner(arg) + } + + // Special casing Unit arrays, the value class which uses a reference array type. + def arrayToString(x: AnyRef) = { + if (x.getClass.getComponentType == classOf[BoxedUnit]) + (0 until min(array_length(x), maxElements)).map(_ => "()").mkString("Array(", ", ", ")") + else + x.asInstanceOf[Array[_]].iterator.take(maxElements).map(inner).mkString("Array(", ", ", ")") + } + + // The recursively applied attempt to prettify Array printing. + // Note that iterator is used if possible and foreach is used as a + // last resort, because the parallel collections "foreach" in a + // random order even on sequences. + def inner(arg: Any): String = arg match { + case null => "null" + case "" => "\"\"" + case x: String => if (x.head.isWhitespace || x.last.isWhitespace) "\"" + x + "\"" else x + case x if useOwnToString(x) => x.toString + case x: AnyRef if isArray(x) => arrayToString(x) + case x: scala.collection.Map[_, _] => x.iterator.take(maxElements).map(mapInner).mkString(x.collectionClassName + "(", ", ", ")") + case x: Iterable[_] => x.iterator.take(maxElements).map(inner).mkString(x.collectionClassName + "(", ", ", ")") + case x: Product1[_] if isTuple(x) => "(" + inner(x._1) + ",)" // that special trailing comma + case x: Product if isTuple(x) => x.productIterator.map(inner).mkString("(", ",", ")") + case x => x.toString + } + + // The try/catch is defense against iterables which aren't actually designed + // to be iterated, such as some scala.tools.nsc.io.AbstractFile derived classes. + try inner(arg) + catch { + case _: UnsupportedOperationException | _: AssertionError => "" + arg + } + } + + /** stringOf formatted for use in a repl result. */ + def replStringOf(arg: Any, maxElements: Int): String = + stringOf(arg, maxElements) match { + case null => "null toString" + case s if s.indexOf('\n') >= 0 => "\n" + s + "\n" + case s => s + "\n" + } + + // Convert arrays to immutable.ArraySeq for use with Scala varargs. + // By construction, calls to these methods always receive a fresh (and non-null), non-empty array. + // In cases where an empty array would appear, the compiler uses a direct reference to Nil instead. + // Synthetic Java varargs forwarders (@annotation.varargs or varargs bridges when overriding) may pass + // `null` to these methods; but returning `null` or `ArraySeq(null)` makes little difference in practice. + def genericWrapArray[T](xs: Array[T]): ArraySeq[T] = ArraySeq.unsafeWrapArray(xs) + def wrapRefArray[T <: AnyRef](xs: Array[T]): ArraySeq[T] = new ArraySeq.ofRef[T](xs) + def wrapIntArray(xs: Array[Int]): ArraySeq[Int] = new ArraySeq.ofInt(xs) + def wrapDoubleArray(xs: Array[Double]): ArraySeq[Double] = new ArraySeq.ofDouble(xs) + def wrapLongArray(xs: Array[Long]): ArraySeq[Long] = new ArraySeq.ofLong(xs) + def wrapFloatArray(xs: Array[Float]): ArraySeq[Float] = new ArraySeq.ofFloat(xs) + def wrapCharArray(xs: Array[Char]): ArraySeq[Char] = new ArraySeq.ofChar(xs) + def wrapByteArray(xs: Array[Byte]): ArraySeq[Byte] = new ArraySeq.ofByte(xs) + def wrapShortArray(xs: Array[Short]): ArraySeq[Short] = new ArraySeq.ofShort(xs) + def wrapBooleanArray(xs: Array[Boolean]): ArraySeq[Boolean] = new ArraySeq.ofBoolean(xs) + def wrapUnitArray(xs: Array[Unit]): ArraySeq[Unit] = new ArraySeq.ofUnit(xs) +} diff --git a/library/src/scala/runtime/ShortRef.java b/library/src/scala/runtime/ShortRef.java new file mode 100644 index 000000000000..11fd2aece720 --- /dev/null +++ b/library/src/scala/runtime/ShortRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class ShortRef implements java.io.Serializable { + private static final long serialVersionUID = 4218441291229072313L; + + public short elem; + public ShortRef(short elem) { this.elem = elem; } + public String toString() { return java.lang.Short.toString(elem); } + + public static ShortRef create(short e) { return new ShortRef(e); } + public static ShortRef zero() { return new ShortRef((short)0); } +} diff --git a/library/src/scala/runtime/Static.java b/library/src/scala/runtime/Static.java new file mode 100644 index 000000000000..1971fe3b463e --- /dev/null +++ b/library/src/scala/runtime/Static.java @@ -0,0 +1,25 @@ +/* + * 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.runtime; + +import java.lang.invoke.*; + +public final class Static { + private Static() { + } + + public static CallSite bootstrap(MethodHandles.Lookup lookup, String invokedName, MethodType invokedType, MethodHandle handle, Object... args) throws Throwable { + Object value = handle.invokeWithArguments(args); + return new ConstantCallSite(MethodHandles.constant(invokedType.returnType(), value)); + } +} diff --git a/library/src/scala/runtime/Statics.java b/library/src/scala/runtime/Statics.java new file mode 100644 index 000000000000..34dc1818065a --- /dev/null +++ b/library/src/scala/runtime/Statics.java @@ -0,0 +1,199 @@ +/* + * 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.runtime; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; + +/** Not for public consumption. Usage by the runtime only. + */ + +public final class Statics { + public static int mix(int hash, int data) { + int h = mixLast(hash, data); + h = Integer.rotateLeft(h, 13); + return h * 5 + 0xe6546b64; + } + + public static int mixLast(int hash, int data) { + int k = data; + + k *= 0xcc9e2d51; + k = Integer.rotateLeft(k, 15); + k *= 0x1b873593; + + return hash ^ k; + } + + public static int finalizeHash(int hash, int length) { + return avalanche(hash ^ length); + } + + /** Force all bits of the hash to avalanche. Used for finalizing the hash. */ + public static int avalanche(int h) { + h ^= h >>> 16; + h *= 0x85ebca6b; + h ^= h >>> 13; + h *= 0xc2b2ae35; + h ^= h >>> 16; + + return h; + } + + public static int longHash(long lv) { + int iv = (int)lv; + if (iv == lv) + return iv; + + return java.lang.Long.hashCode(lv); + } + + public static int doubleHash(double dv) { + int iv = (int)dv; + if (iv == dv) + return iv; + + long lv = (long)dv; + if (lv == dv) + return java.lang.Long.hashCode(lv); + + float fv = (float)dv; + if (fv == dv) + return java.lang.Float.hashCode(fv); + + return java.lang.Double.hashCode(dv); + } + + public static int floatHash(float fv) { + int iv = (int)fv; + if (iv == fv) + return iv; + + long lv = (long)fv; + if (lv == fv) + return java.lang.Long.hashCode(lv); + + return java.lang.Float.hashCode(fv); + } + + /** + * Hashcode algorithm is driven by the requirements imposed + * by primitive equality semantics, namely that equal objects + * have equal hashCodes. The first priority are the integral/char + * types, which already have the same hashCodes for the same + * values except for Long. So Long's hashCode is altered to + * conform to Int's for all values in Int's range. + * + * Float is problematic because it's far too small to hold + * all the Ints, so for instance Int.MaxValue.toFloat claims + * to be == to each of the largest 64 Ints. There is no way + * to preserve equals/hashCode alignment without compromising + * the hashCode distribution, so Floats are only guaranteed + * to have the same hashCode for whole Floats in the range + * Short.MinValue to Short.MaxValue (2^16 total.) + * + * Double has its hashCode altered to match the entire Int range, + * but is not guaranteed beyond that. (But could/should it be? + * The hashCode is only 32 bits so this is a more tractable + * issue than Float's, but it might be better simply to exclude it.) + * + * Note: BigInt and BigDecimal, being arbitrary precision, could + * be made consistent with all other types for the Int range, but + * as yet have not. + * + * Note: Among primitives, Float.NaN != Float.NaN, but the boxed + * versions are equal. This still needs reconciliation. + */ + public static int anyHash(Object x) { + if (x == null) + return 0; + + if (x instanceof java.lang.Number) { + return anyHashNumber((java.lang.Number) x); + } + + return x.hashCode(); + } + + private static int anyHashNumber(Number x) { + if (x instanceof java.lang.Long) + return longHash(((java.lang.Long)x).longValue()); + + if (x instanceof java.lang.Double) + return doubleHash(((java.lang.Double)x).doubleValue()); + + if (x instanceof java.lang.Float) + return floatHash(((java.lang.Float)x).floatValue()); + + return x.hashCode(); + } + + /** Used as a marker object to return from PartialFunctions */ + public static final Object pfMarker = new Object(); + + // @ForceInline would be nice here. + public static void releaseFence() throws Throwable { + VM.RELEASE_FENCE.invoke(); + } + + final static class VM { + static final MethodHandle RELEASE_FENCE; + + static { + RELEASE_FENCE = mkHandle(); + } + + private static MethodHandle mkHandle() { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + return lookup.findStatic(Class.forName("java.lang.invoke.VarHandle"), "releaseFence", MethodType.methodType(Void.TYPE)); + } catch (NoSuchMethodException | ClassNotFoundException e) { + try { + Class unsafeClass = Class.forName("sun.misc.Unsafe"); + return lookup.findVirtual(unsafeClass, "storeFence", MethodType.methodType(void.class)).bindTo(findUnsafe(unsafeClass)); + } catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException e1) { + ExceptionInInitializerError error = new ExceptionInInitializerError(e1); + error.addSuppressed(e); + throw error; + } + } catch (IllegalAccessException e) { + throw new ExceptionInInitializerError(e); + } + } + + private static Object findUnsafe(Class unsafeClass) throws IllegalAccessException { + Object found = null; + for (Field field : unsafeClass.getDeclaredFields()) { + if (field.getType() == unsafeClass) { + field.setAccessible(true); + found = field.get(null); + break; + } + } + if (found == null) throw new IllegalStateException("No instance of Unsafe found"); + return found; + } + } + + /** + * Just throws an exception. + * Used by the synthetic `productElement` and `productElementName` methods in case classes. + * Delegating the exception-throwing to this function reduces the bytecode size of the case class. + */ + public static final T ioobe(int n) throws IndexOutOfBoundsException { + throw new IndexOutOfBoundsException(String.valueOf(n)); + } + +} diff --git a/library/src/scala/runtime/StructuralCallSite.scala b/library/src/scala/runtime/StructuralCallSite.scala new file mode 100644 index 000000000000..29a0febec3f0 --- /dev/null +++ b/library/src/scala/runtime/StructuralCallSite.scala @@ -0,0 +1,47 @@ +/* + * 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.runtime + +import scala.language.`2.13` +import java.lang.invoke._ +import java.lang.ref.SoftReference +import java.lang.reflect.Method + +final class StructuralCallSite private (callType: MethodType) { + private var cache: SoftReference[MethodCache] = new SoftReference(new EmptyMethodCache) + + val parameterTypes: Array[Class[_]] = callType.parameterArray + + def get: MethodCache = { + var cache = this.cache.get + if (cache == null) { + cache = new EmptyMethodCache + this.cache = new SoftReference(cache) + } + cache + } + + def find(receiver: Class[_]): Method = get.find(receiver) + + def add(receiver: Class[_], m: Method): Method = { + cache = new SoftReference(get.add(receiver, m)) + m + } +} + +object StructuralCallSite { + def bootstrap(lookup: MethodHandles.Lookup, invokedName: String, invokedType: MethodType, reflectiveCallType: MethodType): CallSite = { + val structuralCallSite = new StructuralCallSite(reflectiveCallType) + new ConstantCallSite(MethodHandles.constant(classOf[StructuralCallSite], structuralCallSite)) + } +} diff --git a/library/src/scala/runtime/SymbolLiteral.java b/library/src/scala/runtime/SymbolLiteral.java new file mode 100644 index 000000000000..67f59b15fbe2 --- /dev/null +++ b/library/src/scala/runtime/SymbolLiteral.java @@ -0,0 +1,31 @@ +/* + * 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.runtime; + +import java.lang.invoke.*; + +public final class SymbolLiteral { + private SymbolLiteral() { + } + + public static CallSite bootstrap(MethodHandles.Lookup lookup, String invokedName, + MethodType invokedType, + String value) throws Throwable { + ClassLoader classLoader = lookup.lookupClass().getClassLoader(); + MethodType type = MethodType.fromMethodDescriptorString("(Ljava/lang/String;)Lscala/Symbol;", classLoader); + Class symbolClass = Class.forName("scala.Symbol", false, classLoader); + MethodHandle factoryMethod = lookup.findStatic(symbolClass, "apply", type); + Object symbolValue = factoryMethod.invokeWithArguments(value); + return new ConstantCallSite(MethodHandles.constant(symbolClass, symbolValue)); + } +} diff --git a/library/src/scala/runtime/Tuple2Zipped.scala b/library/src/scala/runtime/Tuple2Zipped.scala new file mode 100644 index 000000000000..0c4e9efaef48 --- /dev/null +++ b/library/src/scala/runtime/Tuple2Zipped.scala @@ -0,0 +1,144 @@ +/* + * 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 runtime + +import scala.language.`2.13` +import scala.collection.{BuildFrom, IterableOps} +import scala.language.implicitConversions + +/** This interface is intended as a minimal interface, not complicated + * by the requirement to resolve type constructors, for implicit search (which only + * needs to find an implicit conversion to Iterable for our purposes.) + * @define Coll `ZippedIterable2` + * @define coll collection + * @define collectExample + * @define willNotTerminateInf + */ +@deprecated("Use scala.collection.LazyZip2.", "2.13.0") +trait ZippedIterable2[+El1, +El2] extends Any { + def iterator: Iterator[(El1, El2)] + def isEmpty: Boolean +} +@deprecated("Use scala.collection.LazyZip2.", "2.13.0") +object ZippedIterable2 { + implicit def zippedIterable2ToIterable[El1, El2](zz: ZippedIterable2[El1, El2]): Iterable[(El1, El2)] = { + new scala.collection.AbstractIterable[(El1, El2)] { + def iterator: Iterator[(El1, El2)] = zz.iterator + override def isEmpty: Boolean = zz.isEmpty + } + } +} + +@deprecated("Use scala.collection.LazyZip2.", "2.13.0") +final class Tuple2Zipped[El1, It1 <: Iterable[El1], El2, It2 <: Iterable[El2]](private val colls: (It1, It2)) extends AnyVal with ZippedIterable2[El1, El2] { + private def coll1 = colls._1 + private def coll2 = colls._2 + + def map[B, To](f: (El1, El2) => B)(implicit bf: BuildFrom[It1, B, To]): To = { + val b = bf.newBuilder(coll1) + b.sizeHint(coll1, delta = 0) + val elems1 = coll1.iterator + val elems2 = coll2.iterator + + while (elems1.hasNext && elems2.hasNext) { + b += f(elems1.next(), elems2.next()) + } + + b.result() + } + + def flatMap[B, To](f: (El1, El2) => IterableOnce[B])(implicit bf: BuildFrom[It1, B, To]): To = { + val b = bf.newBuilder(coll1) + val elems1 = coll1.iterator + val elems2 = coll2.iterator + + while (elems1.hasNext && elems2.hasNext) { + b ++= f(elems1.next(), elems2.next()) + } + + b.result() + } + + def filter[To1, To2](f: (El1, El2) => Boolean)(implicit bf1: BuildFrom[It1, El1, To1], bf2: BuildFrom[It2, El2, To2]): (To1, To2) = { + val b1 = bf1.newBuilder(coll1) + val b2 = bf2.newBuilder(coll2) + val elems1 = coll1.iterator + val elems2 = coll2.iterator + + while (elems1.hasNext && elems2.hasNext) { + val el1 = elems1.next() + val el2 = elems2.next() + if (f(el1, el2)) { + b1 += el1 + b2 += el2 + } + } + + (b1.result(), b2.result()) + } + + def exists(p: (El1, El2) => Boolean): Boolean = { + val elems1 = coll1.iterator + val elems2 = coll2.iterator + + while (elems1.hasNext && elems2.hasNext) { + if (p(elems1.next(), elems2.next())) { + return true + } + } + false + } + + def forall(p: (El1, El2) => Boolean): Boolean = + !exists((x, y) => !p(x, y)) + + def iterator: Iterator[(El1, El2)] = coll1.iterator.zip(coll2.iterator) + override def isEmpty: Boolean = coll1.isEmpty || coll2.isEmpty + def foreach[U](f: (El1, El2) => U): Unit = { + val elems1 = coll1.iterator + val elems2 = coll2.iterator + + while (elems1.hasNext && elems2.hasNext) { + f(elems1.next(), elems2.next()) + } + } + + override def toString = s"($coll1, $coll2).zipped" +} + +@deprecated("Use scala.collection.LazyZip2.", since = "2.13.0") +object Tuple2Zipped { + final class Ops[T1, T2](private val x: (T1, T2)) extends AnyVal { + @deprecated("Use xs.lazyZip(yz).map((_, _))", since = "2.13.0") + def invert[El1, It1[a] <: Iterable[a], El2, It2[a] <: Iterable[a], That] + (implicit w1: T1 <:< It1[El1], + w2: T2 <:< It2[El2], + bf: BuildFrom[T1, (El1, El2), That] + ): That = { + val buf = bf.newBuilder(x._1) + val it1 = x._1.iterator + val it2 = x._2.iterator + while (it1.hasNext && it2.hasNext) + buf += ((it1.next(), it2.next())) + + buf.result() + } + + @deprecated("Use xs.lazyZip(ys)", since = "2.13.0") + def zipped[El1, It1 <: Iterable[El1], El2, It2 <: Iterable[El2]] + (implicit w1: T1 => IterableOps[El1, Iterable, It1] with It1, + w2: T2 => IterableOps[El2, Iterable, It2] with It2 + ): Tuple2Zipped[El1, It1, El2, It2] = new Tuple2Zipped((w1(x._1), w2(x._2))) + } +} diff --git a/library/src/scala/runtime/Tuple3Zipped.scala b/library/src/scala/runtime/Tuple3Zipped.scala new file mode 100644 index 000000000000..cb164512daa5 --- /dev/null +++ b/library/src/scala/runtime/Tuple3Zipped.scala @@ -0,0 +1,156 @@ +/* + * 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 runtime + +import scala.language.`2.13` +import scala.collection.{BuildFrom, IterableOps} +import scala.language.implicitConversions + +/** See comment on ZippedIterable2 + * @define Coll `ZippedIterable3` + * @define coll collection + * @define collectExample + * @define willNotTerminateInf + */ +@deprecated("Use scala.collection.LazyZip3.", "2.13.0") +trait ZippedIterable3[+El1, +El2, +El3] extends Any { + def iterator: Iterator[(El1, El2, El3)] + def isEmpty: Boolean +} +@deprecated("Use scala.collection.LazyZip3.", "2.13.0") +object ZippedIterable3 { + implicit def zippedIterable3ToIterable[El1, El2, El3](zz: ZippedIterable3[El1, El2, El3]): Iterable[(El1, El2, El3)] = { + new scala.collection.AbstractIterable[(El1, El2, El3)] { + def iterator: Iterator[(El1, El2, El3)] = zz.iterator + override def isEmpty: Boolean = zz.isEmpty + } + } +} + +@deprecated("Use scala.collection.LazyZip3.", "2.13.0") +final class Tuple3Zipped[El1, It1 <: Iterable[El1], El2, It2 <: Iterable[El2], El3, It3 <: Iterable[El3]](private val colls: (It1, It2, It3)) + extends AnyVal with ZippedIterable3[El1, El2, El3] { + + private def coll1 = colls._1 + private def coll2 = colls._2 + private def coll3 = colls._3 + + def map[B, To](f: (El1, El2, El3) => B)(implicit bf: BuildFrom[It1, B, To]): To = { + val b = bf.newBuilder(coll1) + val elems1 = coll1.iterator + val elems2 = coll2.iterator + val elems3 = coll3.iterator + + while (elems1.hasNext && elems2.hasNext && elems3.hasNext) { + b += f(elems1.next(), elems2.next(), elems3.next()) + } + b.result() + } + + def flatMap[B, To](f: (El1, El2, El3) => IterableOnce[B])(implicit bf: BuildFrom[It1, B, To]): To = { + val b = bf.newBuilder(coll1) + val elems1 = coll1.iterator + val elems2 = coll2.iterator + val elems3 = coll3.iterator + + while (elems1.hasNext && elems2.hasNext && elems3.hasNext) { + b ++= f(elems1.next(), elems2.next(), elems3.next()) + } + b.result() + } + + def filter[To1, To2, To3](f: (El1, El2, El3) => Boolean)( + implicit bf1: BuildFrom[It1, El1, To1], + bf2: BuildFrom[It2, El2, To2], + bf3: BuildFrom[It3, El3, To3]): (To1, To2, To3) = { + val b1 = bf1.newBuilder(coll1) + val b2 = bf2.newBuilder(coll2) + val b3 = bf3.newBuilder(coll3) + val elems1 = coll1.iterator + val elems2 = coll2.iterator + val elems3 = coll3.iterator + + while (elems1.hasNext && elems2.hasNext && elems3.hasNext) { + val el1 = elems1.next() + val el2 = elems2.next() + val el3 = elems3.next() + + if (f(el1, el2, el3)) { + b1 += el1 + b2 += el2 + b3 += el3 + } + } + (b1.result(), b2.result(), b3.result()) + } + + def exists(p: (El1, El2, El3) => Boolean): Boolean = { + val elems1 = coll1.iterator + val elems2 = coll2.iterator + val elems3 = coll3.iterator + + while (elems1.hasNext && elems2.hasNext && elems3.hasNext) { + if (p(elems1.next(), elems2.next(), elems3.next())) { + return true + } + } + false + } + + def forall(p: (El1, El2, El3) => Boolean): Boolean = + !exists((x, y, z) => !p(x, y, z)) + + def iterator: Iterator[(El1, El2, El3)] = coll1.iterator.zip(coll2.iterator).zip(coll3.iterator).map { case ((a, b), c) => (a, b, c)} + override def isEmpty: Boolean = coll1.isEmpty || coll2.isEmpty || coll3.isEmpty + def foreach[U](f: (El1, El2, El3) => U): Unit = { + val elems1 = coll1.iterator + val elems2 = coll2.iterator + val elems3 = coll3.iterator + + while (elems1.hasNext && elems2.hasNext && elems3.hasNext) { + f(elems1.next(), elems2.next(), elems3.next()) + } + } + + override def toString = s"($coll1, $coll2, $coll3).zipped" +} + +@deprecated("Use scala.collection.LazyZip3.", since = "2.13.0") +object Tuple3Zipped { + final class Ops[T1, T2, T3](private val x: (T1, T2, T3)) extends AnyVal { + @deprecated("Use xs.lazyZip(yz).lazyZip(zs).map((_, _, _))", since = "2.13.0") + def invert[El1, It1[a] <: Iterable[a], El2, It2[a] <: Iterable[a], El3, It3[a] <: Iterable[a], That] + (implicit w1: T1 <:< It1[El1], + w2: T2 <:< It2[El2], + w3: T3 <:< It3[El3], + bf: BuildFrom[T1, (El1, El2, El3), That] + ): That = { + val buf = bf.newBuilder(x._1) + val it1 = x._1.iterator + val it2 = x._2.iterator + val it3 = x._3.iterator + while (it1.hasNext && it2.hasNext && it3.hasNext) + buf += ((it1.next(), it2.next(), it3.next())) + + buf.result() + } + + @deprecated("Use xs.lazyZip(ys).lazyZip(zs)", since = "2.13.0") + def zipped[El1, It1 <: Iterable[El1], El2, It2 <: Iterable[El2], El3, It3 <: Iterable[El3]] + (implicit w1: T1 => IterableOps[El1, Iterable, It1] with It1, + w2: T2 => IterableOps[El2, Iterable, It2] with It2, + w3: T3 => IterableOps[El3, Iterable, It3] with It3 + ): Tuple3Zipped[El1, It1, El2, It2, El3, It3] = new Tuple3Zipped((w1(x._1), w2(x._2), w3(x._3))) + } +} diff --git a/library/src/scala/runtime/VolatileBooleanRef.java b/library/src/scala/runtime/VolatileBooleanRef.java new file mode 100644 index 000000000000..0436cf5ee882 --- /dev/null +++ b/library/src/scala/runtime/VolatileBooleanRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class VolatileBooleanRef implements java.io.Serializable { + private static final long serialVersionUID = -5730524563015615974L; + + volatile public boolean elem; + public VolatileBooleanRef(boolean elem) { this.elem = elem; } + public String toString() { return String.valueOf(elem); } + + public static VolatileBooleanRef create(boolean e) { return new VolatileBooleanRef(e); } + public static VolatileBooleanRef zero() { return new VolatileBooleanRef(false); } +} diff --git a/library/src/scala/runtime/VolatileByteRef.java b/library/src/scala/runtime/VolatileByteRef.java new file mode 100644 index 000000000000..23ea7ce3d32e --- /dev/null +++ b/library/src/scala/runtime/VolatileByteRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class VolatileByteRef implements java.io.Serializable { + private static final long serialVersionUID = -100666928446877072L; + + volatile public byte elem; + public VolatileByteRef(byte elem) { this.elem = elem; } + public String toString() { return java.lang.Byte.toString(elem); } + + public static VolatileByteRef create(byte e) { return new VolatileByteRef(e); } + public static VolatileByteRef zero() { return new VolatileByteRef((byte)0); } +} diff --git a/library/src/scala/runtime/VolatileCharRef.java b/library/src/scala/runtime/VolatileCharRef.java new file mode 100644 index 000000000000..b8d11584556a --- /dev/null +++ b/library/src/scala/runtime/VolatileCharRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class VolatileCharRef implements java.io.Serializable { + private static final long serialVersionUID = 6537214938268005702L; + + volatile public char elem; + public VolatileCharRef(char elem) { this.elem = elem; } + public String toString() { return java.lang.Character.toString(elem); } + + public static VolatileCharRef create(char e) { return new VolatileCharRef(e); } + public static VolatileCharRef zero() { return new VolatileCharRef((char)0); } +} diff --git a/library/src/scala/runtime/VolatileDoubleRef.java b/library/src/scala/runtime/VolatileDoubleRef.java new file mode 100644 index 000000000000..809a27040540 --- /dev/null +++ b/library/src/scala/runtime/VolatileDoubleRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class VolatileDoubleRef implements java.io.Serializable { + private static final long serialVersionUID = 8304402127373655534L; + + volatile public double elem; + public VolatileDoubleRef(double elem) { this.elem = elem; } + public String toString() { return java.lang.Double.toString(elem); } + + public static VolatileDoubleRef create(double e) { return new VolatileDoubleRef(e); } + public static VolatileDoubleRef zero() { return new VolatileDoubleRef(0); } +} diff --git a/library/src/scala/runtime/VolatileFloatRef.java b/library/src/scala/runtime/VolatileFloatRef.java new file mode 100644 index 000000000000..954c7522c407 --- /dev/null +++ b/library/src/scala/runtime/VolatileFloatRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class VolatileFloatRef implements java.io.Serializable { + private static final long serialVersionUID = -5793980990371366933L; + + volatile public float elem; + public VolatileFloatRef(float elem) { this.elem = elem; } + public String toString() { return java.lang.Float.toString(elem); } + + public static VolatileFloatRef create(float e) { return new VolatileFloatRef(e); } + public static VolatileFloatRef zero() { return new VolatileFloatRef(0); } +} diff --git a/library/src/scala/runtime/VolatileIntRef.java b/library/src/scala/runtime/VolatileIntRef.java new file mode 100644 index 000000000000..a3d2c33eab71 --- /dev/null +++ b/library/src/scala/runtime/VolatileIntRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class VolatileIntRef implements java.io.Serializable { + private static final long serialVersionUID = 1488197132022872888L; + + volatile public int elem; + public VolatileIntRef(int elem) { this.elem = elem; } + public String toString() { return java.lang.Integer.toString(elem); } + + public static VolatileIntRef create(int e) { return new VolatileIntRef(e); } + public static VolatileIntRef zero() { return new VolatileIntRef(0); } +} diff --git a/library/src/scala/runtime/VolatileLongRef.java b/library/src/scala/runtime/VolatileLongRef.java new file mode 100644 index 000000000000..9e93e0b49e3b --- /dev/null +++ b/library/src/scala/runtime/VolatileLongRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class VolatileLongRef implements java.io.Serializable { + private static final long serialVersionUID = -3567869820105829499L; + + volatile public long elem; + public VolatileLongRef(long elem) { this.elem = elem; } + public String toString() { return java.lang.Long.toString(elem); } + + public static VolatileLongRef create(long e) { return new VolatileLongRef(e); } + public static VolatileLongRef zero() { return new VolatileLongRef(0); } +} diff --git a/library/src/scala/runtime/VolatileObjectRef.java b/library/src/scala/runtime/VolatileObjectRef.java new file mode 100644 index 000000000000..78aef1eaff26 --- /dev/null +++ b/library/src/scala/runtime/VolatileObjectRef.java @@ -0,0 +1,25 @@ +/* + * 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.runtime; + +public final class VolatileObjectRef implements java.io.Serializable { + private static final long serialVersionUID = -9055728157600312291L; + + volatile public T elem; + public VolatileObjectRef(T elem) { this.elem = elem; } + @Override + public String toString() { return String.valueOf(elem); } + + public static VolatileObjectRef create(U e) { return new VolatileObjectRef(e); } + public static VolatileObjectRef zero() { return new VolatileObjectRef(null); } +} diff --git a/library/src/scala/runtime/VolatileShortRef.java b/library/src/scala/runtime/VolatileShortRef.java new file mode 100644 index 000000000000..87a0c12dd7ed --- /dev/null +++ b/library/src/scala/runtime/VolatileShortRef.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +public final class VolatileShortRef implements java.io.Serializable { + private static final long serialVersionUID = 4218441291229072313L; + + volatile public short elem; + public VolatileShortRef(short elem) { this.elem = elem; } + public String toString() { return java.lang.Short.toString(elem); } + + public static VolatileShortRef create(short e) { return new VolatileShortRef(e); } + public static VolatileShortRef zero() { return new VolatileShortRef((short)0); } +} diff --git a/library/src/scala/runtime/coverage/Invoker.scala b/library/src/scala/runtime/coverage/Invoker.scala index b3216ec37c67..98f71076b2f6 100644 --- a/library/src/scala/runtime/coverage/Invoker.scala +++ b/library/src/scala/runtime/coverage/Invoker.scala @@ -51,6 +51,6 @@ object Invoker { @nowarn("cat=deprecation") def measurementFile(dataDir: String): File = new File( dataDir, - MeasurementsPrefix + runtimeUUID + "." + Thread.currentThread.nn.getId + MeasurementsPrefix + runtimeUUID + "." + Thread.currentThread.getId ) } diff --git a/library/src/scala/runtime/java8/JFunction0$mcB$sp.scala b/library/src/scala/runtime/java8/JFunction0$mcB$sp.scala new file mode 100644 index 000000000000..877431126a6d --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction0$mcB$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction0$mcB$sp extends Function0[Any] with Serializable { + def apply$mcB$sp(): Byte + override def apply(): Any = scala.runtime.BoxesRunTime.boxToByte(apply$mcB$sp()) +} diff --git a/library/src/scala/runtime/java8/JFunction0$mcC$sp.scala b/library/src/scala/runtime/java8/JFunction0$mcC$sp.scala new file mode 100644 index 000000000000..18c99570a154 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction0$mcC$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction0$mcC$sp extends Function0[Any] with Serializable { + def apply$mcC$sp(): Char + override def apply(): Any = scala.runtime.BoxesRunTime.boxToCharacter(apply$mcC$sp()) +} diff --git a/library/src/scala/runtime/java8/JFunction0$mcD$sp.scala b/library/src/scala/runtime/java8/JFunction0$mcD$sp.scala new file mode 100644 index 000000000000..d0d805cfacfe --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction0$mcD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction0$mcD$sp extends Function0[Any] with Serializable { + def apply$mcD$sp(): Double + override def apply(): Any = scala.runtime.BoxesRunTime.boxToDouble(apply$mcD$sp()) +} diff --git a/library/src/scala/runtime/java8/JFunction0$mcF$sp.scala b/library/src/scala/runtime/java8/JFunction0$mcF$sp.scala new file mode 100644 index 000000000000..78aa698d06af --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction0$mcF$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction0$mcF$sp extends Function0[Any] with Serializable { + def apply$mcF$sp(): Float + override def apply(): Any = scala.runtime.BoxesRunTime.boxToFloat(apply$mcF$sp()) +} diff --git a/library/src/scala/runtime/java8/JFunction0$mcI$sp.scala b/library/src/scala/runtime/java8/JFunction0$mcI$sp.scala new file mode 100644 index 000000000000..5cb05a4c1226 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction0$mcI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction0$mcI$sp extends Function0[Any] with Serializable { + def apply$mcI$sp(): Int + override def apply(): Any = scala.runtime.BoxesRunTime.boxToInteger(apply$mcI$sp()) +} diff --git a/library/src/scala/runtime/java8/JFunction0$mcJ$sp.scala b/library/src/scala/runtime/java8/JFunction0$mcJ$sp.scala new file mode 100644 index 000000000000..98e2cd15b746 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction0$mcJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction0$mcJ$sp extends Function0[Any] with Serializable { + def apply$mcJ$sp(): Long + override def apply(): Any = scala.runtime.BoxesRunTime.boxToLong(apply$mcJ$sp()) +} diff --git a/library/src/scala/runtime/java8/JFunction0$mcS$sp.scala b/library/src/scala/runtime/java8/JFunction0$mcS$sp.scala new file mode 100644 index 000000000000..2cca873d0565 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction0$mcS$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction0$mcS$sp extends Function0[Any] with Serializable { + def apply$mcS$sp(): Short + override def apply(): Any = scala.runtime.BoxesRunTime.boxToShort(apply$mcS$sp()) +} diff --git a/library/src/scala/runtime/java8/JFunction0$mcV$sp.scala b/library/src/scala/runtime/java8/JFunction0$mcV$sp.scala new file mode 100644 index 000000000000..8ad2c5be6bae --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction0$mcV$sp.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction0$mcV$sp extends Function0[Any] with Serializable { + def apply$mcV$sp(): Unit + override def apply(): Any = { + apply$mcV$sp() + scala.runtime.BoxedUnit.UNIT + } +} diff --git a/library/src/scala/runtime/java8/JFunction0$mcZ$sp.scala b/library/src/scala/runtime/java8/JFunction0$mcZ$sp.scala new file mode 100644 index 000000000000..987caf2d1844 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction0$mcZ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction0$mcZ$sp extends Function0[Any] with Serializable { + def apply$mcZ$sp(): Boolean + override def apply(): Any = scala.runtime.BoxesRunTime.boxToBoolean(apply$mcZ$sp()) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcDD$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcDD$sp.scala new file mode 100644 index 000000000000..d39f70bf2e7e --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcDD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcDD$sp extends Function1[Any, Any] with Serializable { + def apply$mcDD$sp(v1: Double): Double + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToDouble(apply$mcDD$sp(scala.runtime.BoxesRunTime.unboxToDouble(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcDF$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcDF$sp.scala new file mode 100644 index 000000000000..ca19695efffb --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcDF$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcDF$sp extends Function1[Any, Any] with Serializable { + def apply$mcDF$sp(v1: Float): Double + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToDouble(apply$mcDF$sp(scala.runtime.BoxesRunTime.unboxToFloat(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcDI$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcDI$sp.scala new file mode 100644 index 000000000000..92049ad33c39 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcDI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcDI$sp extends Function1[Any, Any] with Serializable { + def apply$mcDI$sp(v1: Int): Double + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToDouble(apply$mcDI$sp(scala.runtime.BoxesRunTime.unboxToInt(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcDJ$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcDJ$sp.scala new file mode 100644 index 000000000000..115be9c367ea --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcDJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcDJ$sp extends Function1[Any, Any] with Serializable { + def apply$mcDJ$sp(v1: Long): Double + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToDouble(apply$mcDJ$sp(scala.runtime.BoxesRunTime.unboxToLong(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcFD$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcFD$sp.scala new file mode 100644 index 000000000000..8fefaf0178cc --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcFD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcFD$sp extends Function1[Any, Any] with Serializable { + def apply$mcFD$sp(v1: Double): Float + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToFloat(apply$mcFD$sp(scala.runtime.BoxesRunTime.unboxToDouble(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcFF$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcFF$sp.scala new file mode 100644 index 000000000000..b5301ab4b903 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcFF$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcFF$sp extends Function1[Any, Any] with Serializable { + def apply$mcFF$sp(v1: Float): Float + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToFloat(apply$mcFF$sp(scala.runtime.BoxesRunTime.unboxToFloat(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcFI$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcFI$sp.scala new file mode 100644 index 000000000000..1ae403365eed --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcFI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcFI$sp extends Function1[Any, Any] with Serializable { + def apply$mcFI$sp(v1: Int): Float + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToFloat(apply$mcFI$sp(scala.runtime.BoxesRunTime.unboxToInt(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcFJ$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcFJ$sp.scala new file mode 100644 index 000000000000..698accc7f735 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcFJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcFJ$sp extends Function1[Any, Any] with Serializable { + def apply$mcFJ$sp(v1: Long): Float + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToFloat(apply$mcFJ$sp(scala.runtime.BoxesRunTime.unboxToLong(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcID$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcID$sp.scala new file mode 100644 index 000000000000..b8ac67b0f974 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcID$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcID$sp extends Function1[Any, Any] with Serializable { + def apply$mcID$sp(v1: Double): Int + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToInteger(apply$mcID$sp(scala.runtime.BoxesRunTime.unboxToDouble(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcIF$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcIF$sp.scala new file mode 100644 index 000000000000..56009880ffbb --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcIF$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcIF$sp extends Function1[Any, Any] with Serializable { + def apply$mcIF$sp(v1: Float): Int + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToInteger(apply$mcIF$sp(scala.runtime.BoxesRunTime.unboxToFloat(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcII$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcII$sp.scala new file mode 100644 index 000000000000..25bfa5dfa29e --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcII$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcII$sp extends Function1[Any, Any] with Serializable { + def apply$mcII$sp(v1: Int): Int + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToInteger(apply$mcII$sp(scala.runtime.BoxesRunTime.unboxToInt(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcIJ$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcIJ$sp.scala new file mode 100644 index 000000000000..469a368cb32e --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcIJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcIJ$sp extends Function1[Any, Any] with Serializable { + def apply$mcIJ$sp(v1: Long): Int + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToInteger(apply$mcIJ$sp(scala.runtime.BoxesRunTime.unboxToLong(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcJD$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcJD$sp.scala new file mode 100644 index 000000000000..b287ca500897 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcJD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcJD$sp extends Function1[Any, Any] with Serializable { + def apply$mcJD$sp(v1: Double): Long + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToLong(apply$mcJD$sp(scala.runtime.BoxesRunTime.unboxToDouble(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcJF$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcJF$sp.scala new file mode 100644 index 000000000000..8e37585e1a74 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcJF$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcJF$sp extends Function1[Any, Any] with Serializable { + def apply$mcJF$sp(v1: Float): Long + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToLong(apply$mcJF$sp(scala.runtime.BoxesRunTime.unboxToFloat(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcJI$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcJI$sp.scala new file mode 100644 index 000000000000..77e4a81b2d85 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcJI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcJI$sp extends Function1[Any, Any] with Serializable { + def apply$mcJI$sp(v1: Int): Long + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToLong(apply$mcJI$sp(scala.runtime.BoxesRunTime.unboxToInt(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcJJ$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcJJ$sp.scala new file mode 100644 index 000000000000..d3652a3d8a1e --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcJJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcJJ$sp extends Function1[Any, Any] with Serializable { + def apply$mcJJ$sp(v1: Long): Long + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToLong(apply$mcJJ$sp(scala.runtime.BoxesRunTime.unboxToLong(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcVD$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcVD$sp.scala new file mode 100644 index 000000000000..a203b7c2e1e7 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcVD$sp.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcVD$sp extends Function1[Any, Any] with Serializable { + def apply$mcVD$sp(v1: Double): Unit + override def apply(t: Any): Any = { + apply$mcVD$sp(scala.runtime.BoxesRunTime.unboxToDouble(t)) + scala.runtime.BoxedUnit.UNIT + } +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcVF$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcVF$sp.scala new file mode 100644 index 000000000000..cc8fca94f72f --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcVF$sp.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcVF$sp extends Function1[Any, Any] with Serializable { + def apply$mcVF$sp(v1: Float): Unit + override def apply(t: Any): Any = { + apply$mcVF$sp(scala.runtime.BoxesRunTime.unboxToFloat(t)) + scala.runtime.BoxedUnit.UNIT + } +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcVI$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcVI$sp.scala new file mode 100644 index 000000000000..057731065c1b --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcVI$sp.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcVI$sp extends Function1[Any, Any] with Serializable { + def apply$mcVI$sp(v1: Int): Unit + override def apply(t: Any): Any = { + apply$mcVI$sp(scala.runtime.BoxesRunTime.unboxToInt(t)) + scala.runtime.BoxedUnit.UNIT + } +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcVJ$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcVJ$sp.scala new file mode 100644 index 000000000000..3e314f4d5feb --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcVJ$sp.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcVJ$sp extends Function1[Any, Any] with Serializable { + def apply$mcVJ$sp(v1: Long): Unit + override def apply(t: Any): Any = { + apply$mcVJ$sp(scala.runtime.BoxesRunTime.unboxToLong(t)) + scala.runtime.BoxedUnit.UNIT + } +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcZD$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcZD$sp.scala new file mode 100644 index 000000000000..f549e4880ab7 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcZD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcZD$sp extends Function1[Any, Any] with Serializable { + def apply$mcZD$sp(v1: Double): Boolean + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToBoolean(apply$mcZD$sp(scala.runtime.BoxesRunTime.unboxToDouble(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcZF$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcZF$sp.scala new file mode 100644 index 000000000000..6cf524f364d3 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcZF$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcZF$sp extends Function1[Any, Any] with Serializable { + def apply$mcZF$sp(v1: Float): Boolean + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToBoolean(apply$mcZF$sp(scala.runtime.BoxesRunTime.unboxToFloat(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcZI$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcZI$sp.scala new file mode 100644 index 000000000000..5b515524c009 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcZI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcZI$sp extends Function1[Any, Any] with Serializable { + def apply$mcZI$sp(v1: Int): Boolean + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToBoolean(apply$mcZI$sp(scala.runtime.BoxesRunTime.unboxToInt(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction1$mcZJ$sp.scala b/library/src/scala/runtime/java8/JFunction1$mcZJ$sp.scala new file mode 100644 index 000000000000..5b7815c7e89e --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction1$mcZJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction1$mcZJ$sp extends Function1[Any, Any] with Serializable { + def apply$mcZJ$sp(v1: Long): Boolean + override def apply(t: Any): Any = scala.runtime.BoxesRunTime.boxToBoolean(apply$mcZJ$sp(scala.runtime.BoxesRunTime.unboxToLong(t))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcDDD$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcDDD$sp.scala new file mode 100644 index 000000000000..31c5c9edcecc --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcDDD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcDDD$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcDDD$sp(v1: Double, v2: Double): Double + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToDouble(apply$mcDDD$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcDDI$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcDDI$sp.scala new file mode 100644 index 000000000000..2cf9d4b6e80a --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcDDI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcDDI$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcDDI$sp(v1: Double, v2: Int): Double + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToDouble(apply$mcDDI$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcDDJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcDDJ$sp.scala new file mode 100644 index 000000000000..127d73aba5e8 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcDDJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcDDJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcDDJ$sp(v1: Double, v2: Long): Double + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToDouble(apply$mcDDJ$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcDID$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcDID$sp.scala new file mode 100644 index 000000000000..a70ccb6c3ed4 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcDID$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcDID$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcDID$sp(v1: Int, v2: Double): Double + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToDouble(apply$mcDID$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcDII$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcDII$sp.scala new file mode 100644 index 000000000000..2de6cddd8bec --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcDII$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcDII$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcDII$sp(v1: Int, v2: Int): Double + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToDouble(apply$mcDII$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcDIJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcDIJ$sp.scala new file mode 100644 index 000000000000..7bda4d00b974 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcDIJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcDIJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcDIJ$sp(v1: Int, v2: Long): Double + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToDouble(apply$mcDIJ$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcDJD$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcDJD$sp.scala new file mode 100644 index 000000000000..f0c078e8334e --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcDJD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcDJD$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcDJD$sp(v1: Long, v2: Double): Double + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToDouble(apply$mcDJD$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcDJI$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcDJI$sp.scala new file mode 100644 index 000000000000..c62fa042e1c7 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcDJI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcDJI$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcDJI$sp(v1: Long, v2: Int): Double + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToDouble(apply$mcDJI$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcDJJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcDJJ$sp.scala new file mode 100644 index 000000000000..c980a47f653c --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcDJJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcDJJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcDJJ$sp(v1: Long, v2: Long): Double + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToDouble(apply$mcDJJ$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcFDD$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcFDD$sp.scala new file mode 100644 index 000000000000..c3a0f40a5d1e --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcFDD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcFDD$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcFDD$sp(v1: Double, v2: Double): Float + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToFloat(apply$mcFDD$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcFDI$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcFDI$sp.scala new file mode 100644 index 000000000000..dfdc4b74dfd9 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcFDI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcFDI$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcFDI$sp(v1: Double, v2: Int): Float + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToFloat(apply$mcFDI$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcFDJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcFDJ$sp.scala new file mode 100644 index 000000000000..0e5caaa89e8b --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcFDJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcFDJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcFDJ$sp(v1: Double, v2: Long): Float + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToFloat(apply$mcFDJ$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcFID$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcFID$sp.scala new file mode 100644 index 000000000000..2808ce6bb3db --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcFID$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcFID$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcFID$sp(v1: Int, v2: Double): Float + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToFloat(apply$mcFID$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcFII$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcFII$sp.scala new file mode 100644 index 000000000000..d233dfd4b89b --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcFII$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcFII$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcFII$sp(v1: Int, v2: Int): Float + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToFloat(apply$mcFII$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcFIJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcFIJ$sp.scala new file mode 100644 index 000000000000..ef66633ace2c --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcFIJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcFIJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcFIJ$sp(v1: Int, v2: Long): Float + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToFloat(apply$mcFIJ$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcFJD$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcFJD$sp.scala new file mode 100644 index 000000000000..8147ea7988ee --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcFJD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcFJD$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcFJD$sp(v1: Long, v2: Double): Float + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToFloat(apply$mcFJD$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcFJI$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcFJI$sp.scala new file mode 100644 index 000000000000..971565be082d --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcFJI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcFJI$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcFJI$sp(v1: Long, v2: Int): Float + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToFloat(apply$mcFJI$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcFJJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcFJJ$sp.scala new file mode 100644 index 000000000000..f7f92df07f78 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcFJJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcFJJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcFJJ$sp(v1: Long, v2: Long): Float + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToFloat(apply$mcFJJ$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcIDD$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcIDD$sp.scala new file mode 100644 index 000000000000..7fea7b0dec2a --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcIDD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcIDD$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcIDD$sp(v1: Double, v2: Double): Int + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToInteger(apply$mcIDD$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcIDI$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcIDI$sp.scala new file mode 100644 index 000000000000..3bf98ac2fd4e --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcIDI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcIDI$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcIDI$sp(v1: Double, v2: Int): Int + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToInteger(apply$mcIDI$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcIDJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcIDJ$sp.scala new file mode 100644 index 000000000000..bcd420495b5b --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcIDJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcIDJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcIDJ$sp(v1: Double, v2: Long): Int + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToInteger(apply$mcIDJ$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcIID$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcIID$sp.scala new file mode 100644 index 000000000000..5ef47488be8e --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcIID$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcIID$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcIID$sp(v1: Int, v2: Double): Int + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToInteger(apply$mcIID$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcIII$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcIII$sp.scala new file mode 100644 index 000000000000..7acabedd4477 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcIII$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcIII$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcIII$sp(v1: Int, v2: Int): Int + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToInteger(apply$mcIII$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcIIJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcIIJ$sp.scala new file mode 100644 index 000000000000..dc93e1b9aa39 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcIIJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcIIJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcIIJ$sp(v1: Int, v2: Long): Int + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToInteger(apply$mcIIJ$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcIJD$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcIJD$sp.scala new file mode 100644 index 000000000000..f8cd79bacd4c --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcIJD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcIJD$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcIJD$sp(v1: Long, v2: Double): Int + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToInteger(apply$mcIJD$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcIJI$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcIJI$sp.scala new file mode 100644 index 000000000000..b138f5e272c4 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcIJI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcIJI$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcIJI$sp(v1: Long, v2: Int): Int + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToInteger(apply$mcIJI$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcIJJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcIJJ$sp.scala new file mode 100644 index 000000000000..423b3700bd80 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcIJJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcIJJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcIJJ$sp(v1: Long, v2: Long): Int + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToInteger(apply$mcIJJ$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcJDD$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcJDD$sp.scala new file mode 100644 index 000000000000..37858178dae6 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcJDD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcJDD$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcJDD$sp(v1: Double, v2: Double): Long + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToLong(apply$mcJDD$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcJDI$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcJDI$sp.scala new file mode 100644 index 000000000000..614c7ca03867 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcJDI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcJDI$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcJDI$sp(v1: Double, v2: Int): Long + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToLong(apply$mcJDI$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcJDJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcJDJ$sp.scala new file mode 100644 index 000000000000..b9d5a48216d2 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcJDJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcJDJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcJDJ$sp(v1: Double, v2: Long): Long + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToLong(apply$mcJDJ$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcJID$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcJID$sp.scala new file mode 100644 index 000000000000..c6b0b3c3325b --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcJID$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcJID$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcJID$sp(v1: Int, v2: Double): Long + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToLong(apply$mcJID$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcJII$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcJII$sp.scala new file mode 100644 index 000000000000..235d7b96dbf0 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcJII$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcJII$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcJII$sp(v1: Int, v2: Int): Long + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToLong(apply$mcJII$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcJIJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcJIJ$sp.scala new file mode 100644 index 000000000000..51de2d927b4f --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcJIJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcJIJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcJIJ$sp(v1: Int, v2: Long): Long + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToLong(apply$mcJIJ$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcJJD$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcJJD$sp.scala new file mode 100644 index 000000000000..d37fa65164cc --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcJJD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcJJD$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcJJD$sp(v1: Long, v2: Double): Long + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToLong(apply$mcJJD$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcJJI$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcJJI$sp.scala new file mode 100644 index 000000000000..67a4e98a26c3 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcJJI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcJJI$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcJJI$sp(v1: Long, v2: Int): Long + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToLong(apply$mcJJI$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcJJJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcJJJ$sp.scala new file mode 100644 index 000000000000..9996290a54da --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcJJJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcJJJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcJJJ$sp(v1: Long, v2: Long): Long + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToLong(apply$mcJJJ$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcVDD$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcVDD$sp.scala new file mode 100644 index 000000000000..bad81ad774de --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcVDD$sp.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcVDD$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcVDD$sp(v1: Double, v2: Double): Unit + override def apply(v1: Any, v2: Any): Any = { + apply$mcVDD$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2)) + scala.runtime.BoxedUnit.UNIT + } +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcVDI$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcVDI$sp.scala new file mode 100644 index 000000000000..7ea5dea1aa80 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcVDI$sp.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcVDI$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcVDI$sp(v1: Double, v2: Int): Unit + override def apply(v1: Any, v2: Any): Any = { + apply$mcVDI$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToInt(v2)) + scala.runtime.BoxedUnit.UNIT + } +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcVDJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcVDJ$sp.scala new file mode 100644 index 000000000000..c8709cf9cd8f --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcVDJ$sp.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcVDJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcVDJ$sp(v1: Double, v2: Long): Unit + override def apply(v1: Any, v2: Any): Any = { + apply$mcVDJ$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToLong(v2)) + scala.runtime.BoxedUnit.UNIT + } +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcVID$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcVID$sp.scala new file mode 100644 index 000000000000..56bbc882c7dc --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcVID$sp.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcVID$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcVID$sp(v1: Int, v2: Double): Unit + override def apply(v1: Any, v2: Any): Any = { + apply$mcVID$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2)) + scala.runtime.BoxedUnit.UNIT + } +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcVII$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcVII$sp.scala new file mode 100644 index 000000000000..9f4669f086d8 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcVII$sp.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcVII$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcVII$sp(v1: Int, v2: Int): Unit + override def apply(v1: Any, v2: Any): Any = { + apply$mcVII$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToInt(v2)) + scala.runtime.BoxedUnit.UNIT + } +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcVIJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcVIJ$sp.scala new file mode 100644 index 000000000000..3b426c309f07 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcVIJ$sp.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcVIJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcVIJ$sp(v1: Int, v2: Long): Unit + override def apply(v1: Any, v2: Any): Any = { + apply$mcVIJ$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToLong(v2)) + scala.runtime.BoxedUnit.UNIT + } +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcVJD$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcVJD$sp.scala new file mode 100644 index 000000000000..e732f7e97d90 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcVJD$sp.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcVJD$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcVJD$sp(v1: Long, v2: Double): Unit + override def apply(v1: Any, v2: Any): Any = { + apply$mcVJD$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2)) + scala.runtime.BoxedUnit.UNIT + } +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcVJI$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcVJI$sp.scala new file mode 100644 index 000000000000..13452f0926b3 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcVJI$sp.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcVJI$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcVJI$sp(v1: Long, v2: Int): Unit + override def apply(v1: Any, v2: Any): Any = { + apply$mcVJI$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToInt(v2)) + scala.runtime.BoxedUnit.UNIT + } +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcVJJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcVJJ$sp.scala new file mode 100644 index 000000000000..8eeaa6c99454 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcVJJ$sp.scala @@ -0,0 +1,23 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcVJJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcVJJ$sp(v1: Long, v2: Long): Unit + override def apply(v1: Any, v2: Any): Any = { + apply$mcVJJ$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToLong(v2)) + scala.runtime.BoxedUnit.UNIT + } +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcZDD$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcZDD$sp.scala new file mode 100644 index 000000000000..f7a690f44339 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcZDD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcZDD$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcZDD$sp(v1: Double, v2: Double): Boolean + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToBoolean(apply$mcZDD$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcZDI$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcZDI$sp.scala new file mode 100644 index 000000000000..fa78cf691ae5 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcZDI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcZDI$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcZDI$sp(v1: Double, v2: Int): Boolean + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToBoolean(apply$mcZDI$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcZDJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcZDJ$sp.scala new file mode 100644 index 000000000000..541078d6b5a5 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcZDJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcZDJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcZDJ$sp(v1: Double, v2: Long): Boolean + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToBoolean(apply$mcZDJ$sp(scala.runtime.BoxesRunTime.unboxToDouble(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcZID$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcZID$sp.scala new file mode 100644 index 000000000000..79e57edc4d48 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcZID$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcZID$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcZID$sp(v1: Int, v2: Double): Boolean + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToBoolean(apply$mcZID$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcZII$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcZII$sp.scala new file mode 100644 index 000000000000..fde9f4e61c79 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcZII$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcZII$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcZII$sp(v1: Int, v2: Int): Boolean + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToBoolean(apply$mcZII$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcZIJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcZIJ$sp.scala new file mode 100644 index 000000000000..de909f4bb048 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcZIJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcZIJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcZIJ$sp(v1: Int, v2: Long): Boolean + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToBoolean(apply$mcZIJ$sp(scala.runtime.BoxesRunTime.unboxToInt(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcZJD$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcZJD$sp.scala new file mode 100644 index 000000000000..ebbdde246224 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcZJD$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcZJD$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcZJD$sp(v1: Long, v2: Double): Boolean + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToBoolean(apply$mcZJD$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToDouble(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcZJI$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcZJI$sp.scala new file mode 100644 index 000000000000..164d437b429f --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcZJI$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcZJI$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcZJI$sp(v1: Long, v2: Int): Boolean + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToBoolean(apply$mcZJI$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToInt(v2))) +} diff --git a/library/src/scala/runtime/java8/JFunction2$mcZJJ$sp.scala b/library/src/scala/runtime/java8/JFunction2$mcZJJ$sp.scala new file mode 100644 index 000000000000..edae96a9ef31 --- /dev/null +++ b/library/src/scala/runtime/java8/JFunction2$mcZJJ$sp.scala @@ -0,0 +1,20 @@ +/* + * 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.runtime.java8 + +import scala.language.`2.13` + +@FunctionalInterface trait JFunction2$mcZJJ$sp extends Function2[Any, Any, Any] with Serializable { + def apply$mcZJJ$sp(v1: Long, v2: Long): Boolean + override def apply(v1: Any, v2: Any): Any = scala.runtime.BoxesRunTime.boxToBoolean(apply$mcZJJ$sp(scala.runtime.BoxesRunTime.unboxToLong(v1), scala.runtime.BoxesRunTime.unboxToLong(v2))) +} diff --git a/library/src/scala/runtime/stdLibPatches/Predef.scala b/library/src/scala/runtime/stdLibPatches/Predef.scala index 996f68d4e122..bfe554b3e735 100644 --- a/library/src/scala/runtime/stdLibPatches/Predef.scala +++ b/library/src/scala/runtime/stdLibPatches/Predef.scala @@ -56,12 +56,12 @@ object Predef: /** Enables an expression of type `T|Null`, where `T` is a subtype of `AnyRef`, to be checked for `null` * using `eq` rather than only `==`. This is needed because `Null` no longer has * `eq` or `ne` methods, only `==` and `!=` inherited from `Any`. */ - inline def eq(inline y: AnyRef | Null): Boolean = + inline infix def eq(inline y: AnyRef | Null): Boolean = x.asInstanceOf[AnyRef] eq y.asInstanceOf[AnyRef] /** Enables an expression of type `T|Null`, where `T` is a subtype of `AnyRef`, to be checked for `null` * using `ne` rather than only `!=`. This is needed because `Null` no longer has * `eq` or `ne` methods, only `==` and `!=` inherited from `Any`. */ - inline def ne(inline y: AnyRef | Null): Boolean = + inline infix def ne(inline y: AnyRef | Null): Boolean = !(x eq y) extension (opt: Option.type) diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 8899f734aece..31ef81b41fa1 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -84,6 +84,13 @@ object language: @compileTimeOnly("`captureChecking` can only be used at compile time in import statements") object captureChecking + /** Experimental support for separation checking; requires captureChecking also to be enabled. + * + * @see [[https://dotty.epfl.ch/docs/reference/experimental/cc]] + */ + @compileTimeOnly("`separationChecking` can only be used at compile time in import statements") + object separationChecking + /** Experimental support for automatic conversions of arguments, without requiring * a language import `import scala.language.implicitConversions`. * @@ -222,6 +229,12 @@ object language: @compileTimeOnly("`future-migration` can only be used at compile time in import statements") object `future-migration` + /** Set source version to 2.13. Effectively, this doesn't change the source language, + * but rather adapts the generated code as if it was compiled with Scala 2.13 + */ + @compileTimeOnly("`2.13` can only be used at compile time in import statements") + private[scala] object `2.13` + /** Set source version to 3.0-migration. * * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] diff --git a/library/src/scala/specialized.scala b/library/src/scala/specialized.scala new file mode 100644 index 000000000000..f673fb6a3bb9 --- /dev/null +++ b/library/src/scala/specialized.scala @@ -0,0 +1,36 @@ +/* + * 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.language.`2.13` + +import Specializable._ + +/** Annotate type parameters on which code should be automatically + * specialized. For example: + * {{{ + * class MyList[@specialized T] ... + * }}} + * + * Type T can be specialized on a subset of the primitive types by + * specifying a list of primitive types to specialize at: + * {{{ + * class MyList[@specialized(Int, Double, Boolean) T] .. + * }}} + */ +// class tspecialized[T](group: Group[T]) extends scala.annotation.StaticAnnotation { + +final class specialized(group: SpecializedGroup) extends scala.annotation.StaticAnnotation { + def this(types: Specializable*) = this(new Group(types.toList)) + def this() = this(Primitives) +} diff --git a/library/src/scala/sys/BooleanProp.scala b/library/src/scala/sys/BooleanProp.scala new file mode 100644 index 000000000000..723623d72769 --- /dev/null +++ b/library/src/scala/sys/BooleanProp.scala @@ -0,0 +1,89 @@ +/* + * 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 sys + +import scala.language.`2.13` +import scala.language.implicitConversions + +/** A few additional conveniences for Boolean properties. + */ +trait BooleanProp extends Prop[Boolean] { + /** The semantics of value are determined at Prop creation. See methods + * `valueIsTrue` and `keyExists` in object BooleanProp for examples. + * + * @return true if the current String is considered true, false otherwise + */ + def value: Boolean + + /** Alter this property so that `value` will be true. */ + def enable(): Unit + + /** Alter this property so that `value` will be false. */ + def disable(): Unit + + /** Toggle the property between enabled and disabled states. */ + def toggle(): Unit +} + +object BooleanProp { + private[sys] + class BooleanPropImpl(key: String, valueFn: String => Boolean) extends PropImpl(key, valueFn) with BooleanProp { + override def setValue[T1 >: Boolean](newValue: T1): Boolean = newValue match { + case x: Boolean if !x => val old = value ; clear() ; old + case x => super.setValue(newValue) + } + def enable() = this setValue true + def disable() = this.clear() + def toggle() = if (value) disable() else enable() + } + private[sys] + class ConstantImpl(val key: String, val value: Boolean) extends BooleanProp { + val isSet = value + def set(newValue: String) = "" + value + def setValue[T1 >: Boolean](newValue: T1): Boolean = value + def get: String = "" + value + def option = if (isSet) Some(value) else None + //def or[T1 >: Boolean](alt: => T1): T1 = if (value) true else alt + + def clear() = () + def enable() = () + def disable() = () + def toggle() = () + + protected def zero = false + } + + /** The java definition of property truth is that the key be in the map and + * the value be equal to the String "true", case insensitively. This method + * creates a BooleanProp instance which adheres to that definition. + * + * @return A BooleanProp which acts like java's Boolean.getBoolean + */ + def valueIsTrue[T](key: String): BooleanProp = new BooleanPropImpl(key, _.toLowerCase == "true") + + /** As an alternative, this method creates a BooleanProp which is true + * if the key exists in the map and is not assigned a value other than "true", + * compared case-insensitively, or the empty string. This way -Dmy.property + * results in a true-valued property, but -Dmy.property=false does not. + * + * @return A BooleanProp with a liberal truth policy + */ + def keyExists[T](key: String): BooleanProp = new BooleanPropImpl(key, s => s == "" || s.equalsIgnoreCase("true")) + + /** A constant true or false property which ignores all method calls. + */ + def constant(key: String, isOn: Boolean): BooleanProp = new ConstantImpl(key, isOn) + + implicit def booleanPropAsBoolean(b: BooleanProp): Boolean = b.value +} diff --git a/library/src/scala/sys/Prop.scala b/library/src/scala/sys/Prop.scala new file mode 100644 index 000000000000..0a7c8b678299 --- /dev/null +++ b/library/src/scala/sys/Prop.scala @@ -0,0 +1,95 @@ +/* + * 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 sys + +import scala.language.`2.13` + +/** A lightweight interface wrapping a property contained in some + * unspecified map. Generally it'll be the system properties but this + * is not a requirement. + * + * See `scala.sys.SystemProperties` for an example usage. + */ +trait Prop[+T] { + /** The full name of the property, e.g., "java.awt.headless". + */ + def key: String + + /** If the key exists in the properties map, converts the value + * to type `T` using valueFn. As yet no validation is performed: + * it will throw an exception on a failed conversion. + * @return the converted value, or `zero` if not in the map + */ + def value: T + + /** True if the key exists in the properties map. Note that this + * is not sufficient for a Boolean property to be considered true. + * @return whether the map contains the key + */ + def isSet: Boolean + + /** Sets the property. + * + * @param newValue the new string value + * @return the old value, or null if it was unset. + */ + def set(newValue: String): String + + /** Sets the property with a value of the represented type. + */ + def setValue[T1 >: T](value: T1): T + + /** Gets the current string value if any. Will not return null: use + * `isSet` to test for existence. + * @return the current string value if any, else the empty string + */ + def get: String + + /** Some(value) if the property is set, None otherwise. + */ + def option: Option[T] + + // Do not open until 2.12. + //** This value if the property is set, an alternative value otherwise. */ + //def or[T1 >: T](alt: => T1): T1 + + /** Removes the property from the underlying map. + */ + def clear(): Unit + + /** A value of type `T` for use when the property is unset. + * The default implementation delivers null for reference types + * and 0/0.0/false for non-reference types. + */ + protected def zero: T +} + +object Prop { + /** A creator of property instances. For any type `T`, if an implicit + * parameter of type Creator[T] is in scope, a Prop[T] can be created + * via this object's apply method. + */ + @annotation.implicitNotFound("No implicit property creator available for type ${T}.") + trait Creator[+T] { + /** Creates a Prop[T] of this type based on the given key. */ + def apply(key: String): Prop[T] + } + + implicit object FileProp extends CreatorImpl[java.io.File](s => new java.io.File(s)) + implicit object StringProp extends CreatorImpl[String](s => s) + implicit object IntProp extends CreatorImpl[Int](_.toInt) + implicit object DoubleProp extends CreatorImpl[Double](_.toDouble) + + def apply[T: Creator](key: String): Prop[T] = implicitly[Creator[T]] apply key +} diff --git a/library/src/scala/sys/PropImpl.scala b/library/src/scala/sys/PropImpl.scala new file mode 100644 index 000000000000..37f0bac5c02a --- /dev/null +++ b/library/src/scala/sys/PropImpl.scala @@ -0,0 +1,53 @@ +/* + * 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 sys + +import scala.language.`2.13` +import scala.collection.mutable + +/** The internal implementation of scala.sys.Prop. + */ +private[sys] class PropImpl[+T](val key: String, valueFn: String => T) extends Prop[T] { + def value: T = if (isSet) valueFn(get) else zero + def isSet = underlying contains key + def set(newValue: String): String = { + val old = if (isSet) get else null + underlying(key) = newValue + old + } + def setValue[T1 >: T](newValue: T1): T = { + val old = value + if (newValue == null) set(null) + else set("" + newValue) + old + } + def get: String = + if (isSet) underlying.getOrElse(key, "") + else "" + + def clear(): Unit = underlying -= key + def option: Option[T] = if (isSet) Some(value) else None + def or[T1 >: T](alt: => T1): T1 = if (isSet) value else alt + + /** The underlying property map, in our case always sys.props */ + protected def underlying: mutable.Map[String, String] = scala.sys.props + protected def zero: T = null.asInstanceOf[T] + private def getString = if (isSet) "currently: " + get else "unset" + override def toString = "%s (%s)".format(key, getString) +} + +private[sys] abstract class CreatorImpl[+T](f: String => T) extends Prop.Creator[T] { + def apply(key: String): Prop[T] = new PropImpl[T](key, f) +} + diff --git a/library/src/scala/sys/ShutdownHookThread.scala b/library/src/scala/sys/ShutdownHookThread.scala new file mode 100644 index 000000000000..d9499d5e1ad1 --- /dev/null +++ b/library/src/scala/sys/ShutdownHookThread.scala @@ -0,0 +1,39 @@ +/* + * 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 sys + +import scala.language.`2.13` + +/** A minimal Thread wrapper to enhance shutdown hooks. It knows + * how to unregister itself. + */ +class ShutdownHookThread private (runnable: Runnable, name: String) extends Thread(runnable, name) { + def remove() = Runtime.getRuntime removeShutdownHook this +} + +object ShutdownHookThread { + private[this] var hookNameCount: Int = 0 + private def hookName(): String = synchronized { + hookNameCount += 1 + "shutdownHook" + hookNameCount + } + /** Creates, names, and registers a shutdown hook to run the + * given code. + */ + def apply(body: => Unit): ShutdownHookThread = { + val t = new ShutdownHookThread(() => body, hookName()) + Runtime.getRuntime addShutdownHook t + t + } +} diff --git a/library/src/scala/sys/SystemProperties.scala b/library/src/scala/sys/SystemProperties.scala new file mode 100644 index 000000000000..d1a5326e66f7 --- /dev/null +++ b/library/src/scala/sys/SystemProperties.scala @@ -0,0 +1,93 @@ +/* + * 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 sys + +import scala.language.`2.13` +import scala.collection.{mutable, Iterator} +import scala.jdk.CollectionConverters._ +import java.security.AccessControlException +import scala.language.implicitConversions + +/** A bidirectional map wrapping the java System properties. + * Changes to System properties will be immediately visible in the map, + * and modifications made to the map will be immediately applied to the + * System properties. If a security manager is in place which prevents + * the properties from being read or written, the AccessControlException + * will be caught and discarded. + * @define Coll `collection.mutable.Map` + * @define coll mutable map + */ +class SystemProperties +extends mutable.AbstractMap[String, String] { + + override def empty: mutable.Map[String, String] = mutable.Map[String, String]() + override def default(key: String): String = null + + def iterator: Iterator[(String, String)] = wrapAccess { + val ps = System.getProperties() + names map (k => (k, ps getProperty k)) filter (_._2 ne null) + } getOrElse Iterator.empty + + override def isEmpty: Boolean = iterator.isEmpty + def names: Iterator[String] = wrapAccess ( + System.getProperties().stringPropertyNames().asScala.iterator + ) getOrElse Iterator.empty + + def get(key: String): Option[String] = + wrapAccess(Option(System.getProperty(key))) flatMap (x => x) + override def contains(key: String): Boolean = + wrapAccess(super.contains(key)) exists (x => x) + + override def clear(): Unit = wrapAccess(System.getProperties().clear()) + def subtractOne (key: String): this.type = { wrapAccess(System.clearProperty(key)) ; this } + def addOne (kv: (String, String)): this.type = { wrapAccess(System.setProperty(kv._1, kv._2)) ; this } + + @annotation.nowarn("cat=deprecation") // AccessControlException is deprecated on JDK 17 + def wrapAccess[T](body: => T): Option[T] = + try Some(body) catch { case _: AccessControlException => None } +} + +/** The values in SystemProperties can be used to access and manipulate + * designated system properties. See `scala.sys.Prop` for particulars. + * @example {{{ + * if (!headless.isSet) headless.enable() + * }}} + */ +object SystemProperties { + /** An unenforceable, advisory only place to do some synchronization when + * mutating system properties. + */ + def exclusively[T](body: => T): T = this synchronized body + + implicit def systemPropertiesToCompanion(p: SystemProperties): SystemProperties.type = this + + private final val HeadlessKey = "java.awt.headless" + private final val PreferIPv4StackKey = "java.net.preferIPv4Stack" + private final val PreferIPv6AddressesKey = "java.net.preferIPv6Addresses" + private final val NoTraceSuppressionKey = "scala.control.noTraceSuppression" + + def help(key: String): String = key match { + case HeadlessKey => "system should not utilize a display device" + case PreferIPv4StackKey => "system should prefer IPv4 sockets" + case PreferIPv6AddressesKey => "system should prefer IPv6 addresses" + case NoTraceSuppressionKey => "scala should not suppress any stack trace creation" + case _ => "" + } + + lazy val headless: BooleanProp = BooleanProp.keyExists(HeadlessKey) + lazy val preferIPv4Stack: BooleanProp = BooleanProp.keyExists(PreferIPv4StackKey) + lazy val preferIPv6Addresses: BooleanProp = BooleanProp.keyExists(PreferIPv6AddressesKey) + lazy val noTraceSuppression: BooleanProp = BooleanProp.valueIsTrue(NoTraceSuppressionKey) +} + diff --git a/library/src/scala/sys/package.scala b/library/src/scala/sys/package.scala new file mode 100644 index 000000000000..3329e53e0d0b --- /dev/null +++ b/library/src/scala/sys/package.scala @@ -0,0 +1,98 @@ +/* + * 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.language.`2.13` +import scala.collection.immutable.ArraySeq +import scala.jdk.CollectionConverters._ + +/** The package object `scala.sys` contains methods for reading + * and altering core aspects of the virtual machine as well as the + * world outside of it. + */ +package object sys { + /** Throw a new RuntimeException with the supplied message. + * + * @return Nothing. + */ + def error(message: String): Nothing = throw new RuntimeException(message) + + /** Exit the JVM with the default status code. + * + * @return Nothing. + */ + def exit(): Nothing = exit(0) + + /** Exit the JVM with the given status code. + * + * @return Nothing. + */ + def exit(status: Int): Nothing = { + java.lang.System.exit(status) + throw new Throwable() + } + + /** A convenience method to get the current Runtime instance. + * + * @return the result of `java.lang.Runtime.getRuntime()` + */ + def runtime: Runtime = Runtime.getRuntime + + /** A bidirectional, mutable Map representing the current system Properties. + * + * @return a SystemProperties. + * @see [[scala.sys.SystemProperties]] + */ + def props: SystemProperties = new SystemProperties + + // TODO: consider whether layering a Map on top of Java's properties is really needed -- we could simply provide: + // def prop(p: String) = Option(System.getProperty(p)) + + /** An immutable Map representing the current system environment. + * + * If lookup fails, use `System.getenv(_)` for case-insensitive lookup + * on a certain platform. If that also fails, throw `NoSuchElementException`. + * + * @return a Map containing the system environment variables. + */ + def env: Map[String, String] = Map.from(System.getenv().asScala).withDefault { v => + val s = System.getenv(v) + if (s == null) throw new NoSuchElementException(v) + s + } + + /** Register a shutdown hook to be run when the VM exits. + * The hook is automatically registered: the returned value can be ignored, + * but is available in case the Thread requires further modification. + * It can also be unregistered by calling ShutdownHookThread#remove(). + * + * Note that shutdown hooks are NOT guaranteed to be run. + * + * @param body the body of code to run at shutdown + * @return the Thread which will run the shutdown hook. + * @see [[scala.sys.ShutdownHookThread]] + */ + def addShutdownHook(body: => Unit): ShutdownHookThread = ShutdownHookThread(body) + + /** Returns all active thread in the current thread's thread group and subgroups. + * + * @return an IndexedSeq containing the threads. + */ + def allThreads(): IndexedSeq[Thread] = { + val num = Thread.activeCount() + val tarray = new Array[Thread](num) + val got = Thread.enumerate(tarray) + + ArraySeq.unsafeWrapArray(tarray).take(got) + } +} diff --git a/library/src/scala/sys/process/BasicIO.scala b/library/src/scala/sys/process/BasicIO.scala new file mode 100644 index 000000000000..57710609451d --- /dev/null +++ b/library/src/scala/sys/process/BasicIO.scala @@ -0,0 +1,275 @@ +/* + * 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 sys +package process + +import scala.language.`2.13` +import processInternal._ +import java.io.{ BufferedReader, InputStreamReader, FilterInputStream, FilterOutputStream } +import java.util.concurrent.LinkedBlockingQueue +import scala.annotation.tailrec + +/** + * This object contains factories for [[scala.sys.process.ProcessIO]], + * which can be used to control the I/O of a [[scala.sys.process.Process]] + * when a [[scala.sys.process.ProcessBuilder]] is started with the `run` + * command. + * + * It also contains some helper methods that can be used to in the creation of + * `ProcessIO`. + * + * It is used by other classes in the package in the implementation of various + * features, but can also be used by client code. + */ +object BasicIO { + /** Size of the buffer used in all the functions that copy data */ + final val BufferSize = 8192 + + /** Used to separate lines in the `processFully` function that takes `Appendable`. */ + final val Newline = System.lineSeparator + + private[process] final class LazilyListed[T]( + val process: T => Unit, + val done: Int => Unit, + val lazyList: LazyList[T] + ) + + private[process] object LazilyListed { + def apply[T](nonzeroException: Boolean, capacity: Integer): LazilyListed[T] = { + val queue = new LinkedBlockingQueue[Either[Int, T]](capacity) + val ll = LazyList.unfold(queue) { q => + q.take() match { + case Left(0) => None + case Left(code) => if (nonzeroException) scala.sys.error("Nonzero exit code: " + code) else None + case Right(s) => Some((s, q)) + } + } + new LazilyListed((s: T) => queue put Right(s), code => queue put Left(code), ll) + } + } + + @deprecated("internal", since = "2.13.4") + private[process] final class Streamed[T]( + val process: T => Unit, + val done: Int => Unit, + val stream: () => Stream[T] + ) + + @deprecated("internal", since = "2.13.4") + private[process] object Streamed { + def apply[T](nonzeroException: Boolean, capacity: Integer): Streamed[T] = { + val q = new LinkedBlockingQueue[Either[Int, T]](capacity) + def next(): Stream[T] = q.take() match { + case Left(0) => Stream.empty + case Left(code) => if (nonzeroException) scala.sys.error("Nonzero exit code: " + code) else Stream.empty + case Right(s) => Stream.cons(s, next()) + } + new Streamed((s: T) => q put Right(s), code => q put Left(code), () => next()) + } + } + + private[process] trait Uncloseable extends Closeable { + final override def close(): Unit = () + } + private[process] object Uncloseable { + def apply(in: InputStream): InputStream = new FilterInputStream(in) with Uncloseable { } + def apply(out: OutputStream): OutputStream = new FilterOutputStream(out) with Uncloseable { } + def protect(in: InputStream): InputStream = if (in eq stdin) Uncloseable(in) else in + def protect(out: OutputStream): OutputStream = if ((out eq stdout) || (out eq stderr)) Uncloseable(out) else out + } + + /** Creates a `ProcessIO` from a function `String => Unit`. It can attach the + * process input to stdin, and it will either send the error stream to + * stderr, or to a `ProcessLogger`. + * + * For example, the `ProcessIO` created below will print all normal output + * while ignoring all error output. No input will be provided. + * {{{ + * import scala.sys.process.BasicIO + * val errToDevNull = BasicIO(false, println(_), None) + * }}} + * + * @param withIn True if the process input should be attached to stdin. + * @param output A function that will be called with the process output. + * @param log An optional `ProcessLogger` to which the output should be + * sent. If `None`, output will be sent to stderr. + * @return A `ProcessIO` with the characteristics above. + */ + def apply(withIn: Boolean, output: String => Unit, log: Option[ProcessLogger]) = + new ProcessIO(input(withIn), processFully(output), getErr(log)) + + /** Creates a `ProcessIO` that appends its output to an `Appendable`. It can + * attach the process input to stdin, and it will either send the error + * stream to stderr, or to a `ProcessLogger`. + * + * For example, the `ProcessIO` created by the function below will store the + * normal output on the buffer provided, and print all error on stderr. The + * input will be read from stdin. + * {{{ + * import scala.sys.process.{BasicIO, ProcessLogger} + * val printer = ProcessLogger(println(_)) + * def appendToBuffer(b: StringBuffer) = BasicIO(true, b, Some(printer)) + * }}} + * + * @param withIn True if the process input should be attached to stdin. + * @param buffer An `Appendable` which will receive the process normal + * output. + * @param log An optional `ProcessLogger` to which the output should be + * sent. If `None`, output will be sent to stderr. + * @return A `ProcessIO` with the characteristics above. + */ + def apply(withIn: Boolean, buffer: Appendable, log: Option[ProcessLogger]) = + new ProcessIO(input(withIn), processFully(buffer), getErr(log)) + + /** Creates a `ProcessIO` from a `ProcessLogger` . It can attach the + * process input to stdin. + * + * @param withIn True if the process input should be attached to stdin. + * @param log A `ProcessLogger` to receive all output, normal and error. + * @return A `ProcessIO` with the characteristics above. + */ + def apply(withIn: Boolean, log: ProcessLogger) = + new ProcessIO(input(withIn), processOutFully(log), processErrFully(log)) + + /** Returns a function `InputStream => Unit` given an optional + * `ProcessLogger`. If no logger is passed, the function will send the output + * to stderr. This function can be used to create a + * [[scala.sys.process.ProcessIO]]. + * + * @param log An optional `ProcessLogger` to which the contents of + * the `InputStream` will be sent. + * @return A function `InputStream => Unit` (used by + * [[scala.sys.process.ProcessIO]]) which will send the data to + * either the provided `ProcessLogger` or, if `None`, to stderr. + */ + def getErr(log: Option[ProcessLogger]) = log match { + case Some(lg) => processErrFully(lg) + case None => toStdErr + } + + private def processErrFully(log: ProcessLogger) = processFully(log err _) + private def processOutFully(log: ProcessLogger) = processFully(log out _) + + /** Closes a `Closeable` without throwing an exception */ + def close(c: Closeable) = try c.close() catch { case _: IOException => () } + + /** Returns a function `InputStream => Unit` that appends all data read to the + * provided `Appendable`. This function can be used to create a + * [[scala.sys.process.ProcessIO]]. The buffer will be appended line by line. + * + * @param buffer An `Appendable` such as `StringBuilder` or `StringBuffer`. + * @return A function `InputStream => Unit` (used by + * [[scala.sys.process.ProcessIO]] which will append all data read + * from the stream to the buffer. + */ + def processFully(buffer: Appendable): InputStream => Unit = processFully(appendLine(buffer)) + + /** Returns a function `InputStream => Unit` that will call the passed + * function with all data read. This function can be used to create a + * [[scala.sys.process.ProcessIO]]. The `processLine` function will be called + * with each line read, and `Newline` will be appended after each line. + * + * @param processLine A function that will be called with all data read from + * the stream. + * @return A function `InputStream => Unit` (used by + * [[scala.sys.process.ProcessIO]] which will call `processLine` + * with all data read from the stream. + */ + def processFully(processLine: String => Unit): InputStream => Unit = in => { + val reader = new BufferedReader(new InputStreamReader(in)) + try processLinesFully(processLine)(() => reader.readLine()) + finally reader.close() + } + + /** Calls `processLine` with the result of `readLine` until the latter returns + * `null` or the current thread is interrupted. + */ + def processLinesFully(processLine: String => Unit)(readLine: () => String): Unit = { + def working = !Thread.currentThread.isInterrupted + def halting = { Thread.currentThread.interrupt(); null } + @tailrec + def readFully(): Unit = + if (working) { + val line = + try readLine() + catch { + case _: InterruptedException => halting + case _: IOException if !working => halting + } + if (line != null) { + processLine(line) + readFully() + } + } + readFully() + } + + /** Copy contents of stdin to the `OutputStream`. */ + def connectToIn(o: OutputStream): Unit = transferFully(Uncloseable protect stdin, o) + + /** Returns a function `OutputStream => Unit` that either reads the content + * from stdin or does nothing but close the stream. This function can be used by + * [[scala.sys.process.ProcessIO]]. + */ + def input(connect: Boolean): OutputStream => Unit = if (connect) connectToStdIn else connectNoOp + + /** A sentinel value telling ProcessBuilderImpl to redirect. */ + private[process] val connectToStdIn: OutputStream => Unit = _ => () + + /** A sentinel value telling ProcessBuilderImpl not to process. */ + private[process] val connectNoOp: OutputStream => Unit = _ => () + + /** Returns a `ProcessIO` connected to stdout and stderr, and, optionally, stdin. */ + def standard(connectInput: Boolean): ProcessIO = standard(input(connectInput)) + + /** Returns a `ProcessIO` connected to stdout, stderr and the provided `in` */ + def standard(in: OutputStream => Unit): ProcessIO = new ProcessIO(in, toStdOut, toStdErr) + + /** Send all the input from the stream to stderr, and closes the input stream + * afterwards. + */ + def toStdErr = (in: InputStream) => transferFully(in, stderr) + + /** Send all the input from the stream to stdout, and closes the input stream + * afterwards. + */ + def toStdOut = (in: InputStream) => transferFully(in, stdout) + + /** Copy all input from the input stream to the output stream. Closes the + * input stream once it's all read. + */ + def transferFully(in: InputStream, out: OutputStream): Unit = + try transferFullyImpl(in, out) + catch onIOInterrupt(()) + + private[this] def appendLine(buffer: Appendable): String => Unit = line => { + buffer append line + buffer append Newline + } + + private[this] def transferFullyImpl(in: InputStream, out: OutputStream): Unit = { + val buffer = new Array[Byte](BufferSize) + @tailrec def loop(): Unit = { + val byteCount = in.read(buffer) + if (byteCount > 0) { + out.write(buffer, 0, byteCount) + // flush() will throw an exception once the process has terminated + val available = try { out.flush(); true } catch { case _: IOException => false } + if (available) loop() + } + } + loop() + in.close() + } +} diff --git a/library/src/scala/sys/process/Parser.scala b/library/src/scala/sys/process/Parser.scala new file mode 100644 index 000000000000..a1dbb7ec6320 --- /dev/null +++ b/library/src/scala/sys/process/Parser.scala @@ -0,0 +1,117 @@ +/* + * 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.sys.process + +import scala.language.`2.13` +import scala.annotation.tailrec + +/** A simple enough command line parser using shell quote conventions. + */ +private[scala] object Parser { + private final val DQ = '"' + private final val SQ = '\'' + private final val EOF = -1 + + /** Split the line into tokens separated by whitespace or quotes. + * + * @return either an error message or reverse list of tokens + */ + def tokenize(line: String, errorFn: String => Unit): List[String] = { + import Character.isWhitespace + import java.lang.{StringBuilder => Builder} + import collection.mutable.ArrayBuffer + + var accum: List[String] = Nil + var pos = 0 + var start = 0 + val qpos = new ArrayBuffer[Int](16) // positions of paired quotes + + def cur: Int = if (done) EOF else line.charAt(pos) + def bump() = pos += 1 + def done = pos >= line.length + + // Skip to the next quote as given. + def skipToQuote(q: Int): Boolean = { + var escaped = false + def terminal: Boolean = cur match { + case _ if escaped => escaped = false ; false + case '\\' => escaped = true ; false + case `q` | EOF => true + case _ => false + } + while (!terminal) bump() + !done + } + // Skip to a word boundary, where words can be quoted and quotes can be escaped + def skipToDelim(): Boolean = { + var escaped = false + def quote() = { qpos += pos ; bump() } + @tailrec def advance(): Boolean = cur match { + case _ if escaped => escaped = false ; bump() ; advance() + case '\\' => escaped = true ; bump() ; advance() + case q @ (DQ | SQ) => { quote() ; skipToQuote(q) } && { quote() ; advance() } + case EOF => true + case c if isWhitespace(c) => true + case _ => bump(); advance() + } + advance() + } + def skipWhitespace() = while (isWhitespace(cur)) bump() + def copyText() = { + val buf = new Builder + var p = start + var i = 0 + while (p < pos) { + if (i >= qpos.size) { + buf.append(line, p, pos) + p = pos + } else if (p == qpos(i)) { + buf.append(line, qpos(i)+1, qpos(i+1)) + p = qpos(i+1)+1 + i += 2 + } else { + buf.append(line, p, qpos(i)) + p = qpos(i) + } + } + buf.toString + } + def text() = { + val res = + if (qpos.isEmpty) line.substring(start, pos) + else if (qpos(0) == start && qpos(1) == pos) line.substring(start+1, pos-1) + else copyText() + qpos.clear() + res + } + def badquote() = errorFn(s"Unmatched quote [${qpos.last}](${line.charAt(qpos.last)})") + def badescape() = errorFn("trailing backslash") + + @tailrec def loop(): List[String] = { + skipWhitespace() + start = pos + if (done) accum.reverse + else if (!skipToDelim()) { badquote(); Nil } + else if (pos > line.length) { badescape(); Nil } + else { + accum ::= text() + loop() + } + } + loop() + } + + class ParseException(msg: String) extends RuntimeException(msg) + + def tokenize(line: String): List[String] = tokenize(line, x => throw new ParseException(x)) +} diff --git a/library/src/scala/sys/process/Process.scala b/library/src/scala/sys/process/Process.scala new file mode 100644 index 000000000000..be9a3490ebe4 --- /dev/null +++ b/library/src/scala/sys/process/Process.scala @@ -0,0 +1,223 @@ +/* + * 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 sys +package process + +import scala.language.`2.13` +import processInternal._ +import ProcessBuilder._ +import scala.language.implicitConversions + + +/** Represents a process that is running or has finished running. + * It may be a compound process with several underlying native processes (such as `a #&& b`). + * + * This trait is often not used directly, though its companion object contains + * factories for [[scala.sys.process.ProcessBuilder]], the main component of this + * package. + * + * It is used directly when calling the method `run` on a `ProcessBuilder`, + * which makes the process run in the background. The methods provided on `Process` + * make it possible for one to block until the process exits and get the exit value, + * or destroy the process altogether. + * + * @see [[scala.sys.process.ProcessBuilder]] + */ +trait Process { + /** Returns this process alive status */ + def isAlive(): Boolean + /** Blocks until this process exits and returns the exit code.*/ + def exitValue(): Int + /** Destroys this process. */ + def destroy(): Unit +} + +/** Methods for constructing simple commands that can then be combined. */ +object Process extends ProcessImpl with ProcessCreation { } + +/** Factories for creating [[scala.sys.process.ProcessBuilder]]. They can be + * found on and used through [[scala.sys.process.Process]]'s companion object. + */ +trait ProcessCreation { + /** Creates a [[scala.sys.process.ProcessBuilder]] from a `String`, including the + * parameters. + * + * @example {{{ apply("cat file.txt") }}} + */ + def apply(command: String): ProcessBuilder = apply(command, None) + + /** Creates a [[scala.sys.process.ProcessBuilder]] from a sequence of `String`, + * where the head is the command and each element of the tail is a parameter. + * + * @example {{{ apply("cat" :: files) }}} + */ + def apply(command: scala.collection.Seq[String]): ProcessBuilder = apply(command, None) + + /** Creates a [[scala.sys.process.ProcessBuilder]] from a command represented by a `String`, + * and a sequence of `String` representing the arguments. + * + * @example {{{ apply("cat", files) }}} + */ + def apply(command: String, arguments: scala.collection.Seq[String]): ProcessBuilder = apply(command +: arguments, None) + + /** Creates a [[scala.sys.process.ProcessBuilder]] with working dir set to `File` and extra + * environment variables. + * + * @example {{{ apply("java", new java.io.File("/opt/app"), "CLASSPATH" -> "library.jar") }}} + */ + def apply(command: String, cwd: File, extraEnv: (String, String)*): ProcessBuilder = + apply(command, Some(cwd), extraEnv: _*) + + /** Creates a [[scala.sys.process.ProcessBuilder]] with working dir set to `File` and extra + * environment variables. + * + * @example {{{ apply("java" :: javaArgs, new java.io.File("/opt/app"), "CLASSPATH" -> "library.jar") }}} + */ + def apply(command: scala.collection.Seq[String], cwd: File, extraEnv: (String, String)*): ProcessBuilder = + apply(command, Some(cwd), extraEnv: _*) + + /** Creates a [[scala.sys.process.ProcessBuilder]] with working dir optionally set to + * `File` and extra environment variables. + * + * @example {{{ apply("java", params.get("cwd"), "CLASSPATH" -> "library.jar") }}} + */ + def apply(command: String, cwd: Option[File], extraEnv: (String, String)*): ProcessBuilder = + apply(Parser.tokenize(command), cwd, extraEnv: _*) + + /** Creates a [[scala.sys.process.ProcessBuilder]] with working dir optionally set to + * `File` and extra environment variables. + * + * @example {{{ apply("java" :: javaArgs, params.get("cwd"), "CLASSPATH" -> "library.jar") }}} + */ + def apply(command: scala.collection.Seq[String], cwd: Option[File], extraEnv: (String, String)*): ProcessBuilder = { + val jpb = new JProcessBuilder(command.toArray: _*) + cwd foreach (jpb directory _) + extraEnv foreach { case (k, v) => jpb.environment.put(k, v) } + apply(jpb) + } + + /** Creates a [[scala.sys.process.ProcessBuilder]] from a `java.lang.ProcessBuilder`. + * + * @example {{{ + * apply((new java.lang.ProcessBuilder("ls", "-l")) directory new java.io.File(System.getProperty("user.home"))) + * }}} + */ + def apply(builder: JProcessBuilder): ProcessBuilder = new Simple(builder) + + /** Creates a [[scala.sys.process.ProcessBuilder]] from a `java.io.File`. This + * `ProcessBuilder` can then be used as a `Source` or a `Sink`, so one can + * pipe things from and to it. + */ + def apply(file: File): FileBuilder = new FileImpl(file) + + /** Creates a [[scala.sys.process.ProcessBuilder]] from a `java.net.URL`. This + * `ProcessBuilder` can then be used as a `Source`, so that one can pipe things + * from it. + */ + def apply(url: URL): URLBuilder = new URLImpl(url) + + /** Creates a [[scala.sys.process.ProcessBuilder]] from a `Boolean`. This can be + * to force an exit value. + */ + def apply(value: Boolean): ProcessBuilder = apply(value.toString, if (value) 0 else 1) + + /** Creates a [[scala.sys.process.ProcessBuilder]] from a `String` name and a + * `Boolean`. This can be used to force an exit value, with the name being + * used for `toString`. + */ + def apply(name: String, exitValue: => Int): ProcessBuilder = new Dummy(name, exitValue) + + /** Creates a sequence of [[scala.sys.process.ProcessBuilder.Source]] from a sequence of + * something else for which there's an implicit conversion to `Source`. + */ + def applySeq[T](builders: scala.collection.Seq[T])(implicit convert: T => Source): scala.collection.Seq[Source] = builders.map(convert) + + /** Creates a [[scala.sys.process.ProcessBuilder]] from one or more + * [[scala.sys.process.ProcessBuilder.Source]], which can then be + * piped to something else. + * + * This will concatenate the output of all sources. For example: + * + * {{{ + * import scala.sys.process._ + * import scala.sys.process.Process.cat + * import java.net.URL + * import java.io.File + * + * val spde = new URL("http://technically.us/spde.html") + * val dispatch = new URL("https://dispatchhttp.org/Dispatch.html") + * val build = new File("project/build.properties") + * cat(spde, dispatch, build) #| "grep -i scala" ! + * }}} + */ + def cat(file: Source, files: Source*): ProcessBuilder = cat(file +: files) + + /** Creates a [[scala.sys.process.ProcessBuilder]] from a non-empty sequence + * of [[scala.sys.process.ProcessBuilder.Source]], which can then be + * piped to something else. + * + * This will concatenate the output of all sources. + */ + def cat(files: scala.collection.Seq[Source]): ProcessBuilder = { + require(files.nonEmpty) + files.map(_.cat).reduceLeft(_ #&& _) + } +} + +/** Provide implicit conversions for the factories offered by [[scala.sys.process.Process]]'s + * companion object. These implicits can then be used to decrease the noise in a pipeline + * of commands, making it look more shell-like. They are available through the package object + * [[scala.sys.process]]. + */ +trait ProcessImplicits { + import Process._ + + /** Return a sequence of [[scala.sys.process.ProcessBuilder.Source]] from a sequence + * of values for which an implicit conversion to `Source` is available. + */ + implicit def buildersToProcess[T](builders: scala.collection.Seq[T])(implicit convert: T => Source): scala.collection.Seq[Source] = applySeq(builders) + + /** Implicitly convert a `java.lang.ProcessBuilder` into a Scala one. */ + implicit def builderToProcess(builder: JProcessBuilder): ProcessBuilder = apply(builder) + + /** Implicitly convert a `java.io.File` into a + * [[scala.sys.process.ProcessBuilder.FileBuilder]], which can be used as + * either input or output of a process. For example: + * {{{ + * import scala.sys.process._ + * "ls" #> new java.io.File("dirContents.txt") ! + * }}} + */ + implicit def fileToProcess(file: File): FileBuilder = apply(file) + + /** Implicitly convert a `java.net.URL` into a + * [[scala.sys.process.ProcessBuilder.URLBuilder]] , which can be used as + * input to a process. For example: + * {{{ + * import scala.sys.process._ + * Seq("xmllint", "--html", "-") #< new java.net.URL("https://www.scala-lang.org") #> new java.io.File("fixed.html") ! + * }}} + */ + implicit def urlToProcess(url: URL): URLBuilder = apply(url) + + /** Implicitly convert a `String` into a [[scala.sys.process.ProcessBuilder]]. */ + implicit def stringToProcess(command: String): ProcessBuilder = apply(command) + + /** Implicitly convert a sequence of `String` into a + * [[scala.sys.process.ProcessBuilder]]. The first argument will be taken to + * be the command to be executed, and the remaining will be its arguments. + * When using this, arguments may contain spaces. + */ + implicit def stringSeqToProcess(command: scala.collection.Seq[String]): ProcessBuilder = apply(command) +} diff --git a/library/src/scala/sys/process/ProcessBuilder.scala b/library/src/scala/sys/process/ProcessBuilder.scala new file mode 100644 index 000000000000..d4eb2b054375 --- /dev/null +++ b/library/src/scala/sys/process/ProcessBuilder.scala @@ -0,0 +1,480 @@ +/* + * 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 sys +package process + +import scala.language.`2.13` +import processInternal._ +import ProcessBuilder.{Sink, Source} + +/** Represents a sequence of one or more external processes that can be + * executed. A `ProcessBuilder` can be a single external process, or a + * combination of other `ProcessBuilder`. One can control where the + * output of an external process will go to, and where its input will come + * from, or leave that decision to whoever starts it. + * + * One creates a `ProcessBuilder` through factories provided in + * [[scala.sys.process.Process]]'s companion object, or implicit conversions + * based on these factories made available in the package object + * [[scala.sys.process]]. Here are some examples: + * {{{ + * import scala.sys.process._ + * + * // Executes "ls" and sends output to stdout + * "ls".! + * + * // Execute "ls" and assign a `LazyList[String]` of its output to "contents". + * val contents = Process("ls").lazyLines + * + * // Here we use a `Seq` to make the parameter whitespace-safe + * def contentsOf(dir: String): String = Seq("ls", dir).!! + * }}} + * + * The methods of `ProcessBuilder` are divided in three categories: the ones that + * combine two `ProcessBuilder` to create a third, the ones that redirect input + * or output of a `ProcessBuilder`, and the ones that execute + * the external processes associated with it. + * + * ==Combining `ProcessBuilder`== + * + * Two existing `ProcessBuilder` can be combined in the following ways: + * + * - They can be executed in parallel, with the output of the first being fed + * as input to the second, like Unix pipes. This is achieved with the `#|` + * method. + * - They can be executed in sequence, with the second starting as soon as + * the first ends. This is done by the `###` method. + * - The execution of the second one can be conditioned by the return code + * (exit status) of the first, either only when it's zero, or only when it's + * not zero. The methods `#&&` and `#||` accomplish these tasks. + * + * ==Redirecting Input/Output== + * + * Though control of input and output can be done when executing the process, + * there's a few methods that create a new `ProcessBuilder` with a + * pre-configured input or output. They are `#<`, `#>` and `#>>`, and may take + * as input either another `ProcessBuilder` (like the pipe described above), or + * something else such as a `java.io.File` or a `java.io.InputStream`. + * For example: + * {{{ + * new URL("https://databinder.net/dispatch/About") #> "grep JSON" #>> new File("About_JSON") ! + * }}} + * + * ==Starting Processes== + * + * To execute all external commands associated with a `ProcessBuilder`, one + * may use one of four groups of methods. Each of these methods have various + * overloads and variations to enable further control over the I/O. These + * methods are: + * + * - `run`: the most general method, it returns a + * [[scala.sys.process.Process]] immediately, and the external command + * executes concurrently. + * - `!`: blocks until all external commands exit, and returns the exit code + * of the last one in the chain of execution. + * - `!!`: blocks until all external commands exit, and returns a `String` + * with the output generated. + * - `lazyLines`: returns immediately like `run`, and the output being generated + * is provided through a `LazyList[String]`. Getting the next element of that + * `LazyList` may block until it becomes available. This method will throw an + * exception if the return code is different than zero -- if this is not + * desired, use the `lazyLines_!` method. + * + * ==Handling Input and Output== + * + * If not specified, the input of the external commands executed with `run` or + * `!` will not be tied to anything, and the output will be redirected to the + * stdout and stderr of the Scala process. For the methods `!!` and `lazyLines`, no + * input will be provided, and the output will be directed according to the + * semantics of these methods. + * + * Some methods will cause stdin to be used as input. Output can be controlled + * with a [[scala.sys.process.ProcessLogger]] -- `!!` and `lazyLines` will only + * redirect error output when passed a `ProcessLogger`. If one desires full + * control over input and output, then a [[scala.sys.process.ProcessIO]] can be + * used with `run`. + * + * For example, we could silence the error output from `lazyLines_!` like this: + * {{{ + * val etcFiles = "find /etc" lazyLines_! ProcessLogger(line => ()) + * }}} + * + * ==Extended Example== + * + * Let's examine in detail one example of usage: + * {{{ + * import scala.sys.process._ + * "find src -name *.scala -exec grep null {} ;" #| "xargs test -z" #&& "echo null-free" #|| "echo null detected" ! + * }}} + * Note that every `String` is implicitly converted into a `ProcessBuilder` + * through the implicits imported from [[scala.sys.process]]. These `ProcessBuilder` are then + * combined in three different ways. + * + * 1. `#|` pipes the output of the first command into the input of the second command. It + * mirrors a shell pipe (`|`). + * 1. `#&&` conditionally executes the second command if the previous one finished with + * exit value 0. It mirrors shell's `&&`. + * 1. `#||` conditionally executes the third command if the exit value of the previous + * command is different than zero. It mirrors shell's `||`. + * + * Finally, `!` at the end executes the commands, and returns the exit value. + * Whatever is printed will be sent to the Scala process standard output. If + * we wanted to capture it, we could run that with `!!` instead. + * + * Note: though it is not shown above, the equivalent of a shell's `;` would be + * `###`. The reason for this name is that `;` is a reserved token in Scala. + * + */ +trait ProcessBuilder extends Source with Sink { + /** Starts the process represented by this builder, blocks until it exits, and + * returns the output as a String. Standard error is sent to the console. If + * the exit code is non-zero, an exception is thrown. + */ + def !! : String + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the output as a String. Standard error is sent to the provided + * ProcessLogger. If the exit code is non-zero, an exception is thrown. + */ + def !!(log: ProcessLogger): String + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the output as a String. Standard error is sent to the console. If + * the exit code is non-zero, an exception is thrown. The newly started + * process reads from standard input of the current process. + */ + def !!< : String + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the output as a String. Standard error is sent to the provided + * ProcessLogger. If the exit code is non-zero, an exception is thrown. The + * newly started process reads from standard input of the current process. + */ + def !!<(log: ProcessLogger): String + + /** Starts the process represented by this builder. The output is returned as + * a LazyList that blocks when lines are not available but the process has not + * completed. Standard error is sent to the console. If the process exits + * with a non-zero value, the LazyList will provide all lines up to termination + * and then throw an exception. + */ + def lazyLines: LazyList[String] + + /** Starts the process represented by this builder. The output is returned as + * a LazyList that blocks when lines are not available but the process has not + * completed. + * The producer process will block if the given capacity of lines if filled + * without being consumed from the LazyList. + * Standard error is sent to the console. If the process exits + * with a non-zero value, the LazyList will provide all lines up to termination + * and then throw an exception. + */ + def lazyLines(capacity: Integer): LazyList[String] + + /** Starts the process represented by this builder. The output is returned as + * a LazyList that blocks when lines are not available but the process has not + * completed. Standard error is sent to the provided ProcessLogger. If the + * process exits with a non-zero value, the LazyList will provide all lines up + * to termination and then throw an exception. + */ + def lazyLines(log: ProcessLogger): LazyList[String] + + /** Starts the process represented by this builder. The output is returned as + * a LazyList that blocks when lines are not available but the process has not + * completed. + * The producer process will block if the given capacity of lines if filled + * without being consumed from the LazyList. + * Standard error is sent to the provided ProcessLogger. If the + * process exits with a non-zero value, the LazyList will provide all lines up + * to termination and then throw an exception. + */ + def lazyLines(log: ProcessLogger, capacity: Integer): LazyList[String] + + /** Starts the process represented by this builder. The output is returned as + * a LazyList that blocks when lines are not available but the process has not + * completed. Standard error is sent to the console. If the process exits + * with a non-zero value, the LazyList will provide all lines up to termination + * but will not throw an exception. + */ + def lazyLines_! : LazyList[String] + + /** Starts the process represented by this builder. The output is returned as + * a LazyList that blocks when lines are not available but the process has not + * completed. + * The producer process will block if the given capacity of lines if filled + * without being consumed from the stream. + * Standard error is sent to the console. If the process exits + * with a non-zero value, the LazyList will provide all lines up to termination + * but will not throw an exception. + */ + def lazyLines_!(capacity: Integer): LazyList[String] + + /** Starts the process represented by this builder. The output is returned as + * a LazyList that blocks when lines are not available but the process has not + * completed. Standard error is sent to the provided ProcessLogger. If the + * process exits with a non-zero value, the LazyList will provide all lines up + * to termination but will not throw an exception. + */ + def lazyLines_!(log: ProcessLogger): LazyList[String] + + /** Starts the process represented by this builder. The output is returned as + * a LazyList that blocks when lines are not available but the process has not + * completed. + * The producer process will block if the given capacity of lines if filled + * without being consumed from the stream. + * Standard error is sent to the provided ProcessLogger. If the + * process exits with a non-zero value, the LazyList will provide all lines up + * to termination but will not throw an exception. + */ + def lazyLines_!(log: ProcessLogger, capacity: Integer): LazyList[String] + + /** Starts the process represented by this builder. The output is returned as + * a Stream that blocks when lines are not available but the process has not + * completed. Standard error is sent to the console. If the process exits + * with a non-zero value, the Stream will provide all lines up to termination + * and then throw an exception. + */ + @deprecated("use lazyLines", since = "2.13.0") + def lineStream: Stream[String] + + /** Starts the process represented by this builder. The output is returned as + * a Stream that blocks when lines are not available but the process has not + * completed. + * The producer process will block if the given capacity of lines if filled + * without being consumed from the stream. + * Standard error is sent to the console. If the process exits + * with a non-zero value, the Stream will provide all lines up to termination + * and then throw an exception. + */ + @deprecated("use lazyLines", since = "2.13.0") + def lineStream(capacity: Integer): Stream[String] + + /** Starts the process represented by this builder. The output is returned as + * a Stream that blocks when lines are not available but the process has not + * completed. Standard error is sent to the provided ProcessLogger. If the + * process exits with a non-zero value, the Stream will provide all lines up + * to termination and then throw an exception. + */ + @deprecated("use lazyLines", since = "2.13.0") + def lineStream(log: ProcessLogger): Stream[String] + + /** Starts the process represented by this builder. The output is returned as + * a Stream that blocks when lines are not available but the process has not + * completed. + * The producer process will block if the given capacity of lines if filled + * without being consumed from the stream. + * Standard error is sent to the provided ProcessLogger. If the + * process exits with a non-zero value, the Stream will provide all lines up + * to termination and then throw an exception. + */ + @deprecated("use lazyLines", since = "2.13.0") + def lineStream(log: ProcessLogger, capacity: Integer): Stream[String] + + /** Starts the process represented by this builder. The output is returned as + * a Stream that blocks when lines are not available but the process has not + * completed. Standard error is sent to the console. If the process exits + * with a non-zero value, the Stream will provide all lines up to termination + * but will not throw an exception. + */ + @deprecated("use lazyLines_!", since = "2.13.0") + def lineStream_! : Stream[String] + + /** Starts the process represented by this builder. The output is returned as + * a Stream that blocks when lines are not available but the process has not + * completed. + * The producer process will block if the given capacity of lines if filled + * without being consumed from the stream. + * Standard error is sent to the console. If the process exits + * with a non-zero value, the Stream will provide all lines up to termination + * but will not throw an exception. + */ + @deprecated("use lazyLines_!", since = "2.13.0") + def lineStream_!(capacity: Integer): Stream[String] + + /** Starts the process represented by this builder. The output is returned as + * a Stream that blocks when lines are not available but the process has not + * completed. Standard error is sent to the provided ProcessLogger. If the + * process exits with a non-zero value, the Stream will provide all lines up + * to termination but will not throw an exception. + */ + @deprecated("use lazyLines_!", since = "2.13.0") + def lineStream_!(log: ProcessLogger): Stream[String] + + /** Starts the process represented by this builder. The output is returned as + * a Stream that blocks when lines are not available but the process has not + * completed. + * The producer process will block if the given capacity of lines if filled + * without being consumed from the stream. + * Standard error is sent to the provided ProcessLogger. If the + * process exits with a non-zero value, the Stream will provide all lines up + * to termination but will not throw an exception. + */ + @deprecated("use lazyLines_!", since = "2.13.0") + def lineStream_!(log: ProcessLogger, capacity: Integer): Stream[String] + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the exit code. Standard output and error are sent to the console. + */ + def ! : Int + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the exit code. Standard output and error are sent to the given + * ProcessLogger. + */ + def !(log: ProcessLogger): Int + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the exit code. Standard output and error are sent to the console. + * The newly started process reads from standard input of the current process. + */ + def !< : Int + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the exit code. Standard output and error are sent to the given + * ProcessLogger. The newly started process reads from standard input of the + * current process. + */ + def !<(log: ProcessLogger): Int + + /** Starts the process represented by this builder. Standard output and error + * are sent to the console.*/ + def run(): Process + + /** Starts the process represented by this builder. Standard output and error + * are sent to the given ProcessLogger. + */ + def run(log: ProcessLogger): Process + + /** Starts the process represented by this builder. I/O is handled by the + * given ProcessIO instance. + */ + def run(io: ProcessIO): Process + + /** Starts the process represented by this builder. Standard output and error + * are sent to the console. The newly started process reads from standard + * input of the current process if `connectInput` is true. + */ + def run(connectInput: Boolean): Process + + /** Starts the process represented by this builder. Standard output and error + * are sent to the given ProcessLogger. The newly started process reads from + * standard input of the current process if `connectInput` is true. + */ + def run(log: ProcessLogger, connectInput: Boolean): Process + + /** Constructs a command that runs this command first and then `other` if this + * command succeeds. + */ + def #&& (other: ProcessBuilder): ProcessBuilder + + /** Constructs a command that runs this command first and then `other` if this + * command does not succeed. + */ + def #|| (other: ProcessBuilder): ProcessBuilder + + /** Constructs a command that will run this command and pipes the output to + * `other`. `other` must be a simple command. + */ + def #| (other: ProcessBuilder): ProcessBuilder + + /** Constructs a command that will run this command and then `other`. The + * exit code will be the exit code of `other`. + */ + def ### (other: ProcessBuilder): ProcessBuilder + + + /** True if this command can be the target of a pipe. */ + def canPipeTo: Boolean + + /** True if this command has an exit code which should be propagated to the + * user. Given a pipe between A and B, if B.hasExitValue is true then the + * exit code will be the one from B; if it is false, the one from A. This + * exists to prevent output redirections (implemented as pipes) from masking + * useful process error codes. + */ + def hasExitValue: Boolean +} + +/** This object contains traits used to describe input and output sources. */ +object ProcessBuilder extends ProcessBuilderImpl { + /** Used when creating [[scala.sys.process.ProcessBuilder.Source]] from an URL. */ + trait URLBuilder extends Source { + + } + + /** Used when creating [[scala.sys.process.ProcessBuilder.Source]] and/or + * [[scala.sys.process.ProcessBuilder.Sink]] from a file. + */ + trait FileBuilder extends Sink with Source { + /** Append the contents of a `java.io.File` to this file */ + def #<<(f: File): ProcessBuilder + + /** Append the contents from a `java.net.URL` to this file */ + def #<<(u: URL): ProcessBuilder + + /** Append the contents of a `java.io.InputStream` to this file */ + def #<<(i: => InputStream): ProcessBuilder + + /** Append the contents of a [[scala.sys.process.ProcessBuilder]] to this file */ + def #<<(p: ProcessBuilder): ProcessBuilder + } + + /** Represents everything that can be used as an input to a + * [[scala.sys.process.ProcessBuilder]]. + */ + trait Source { + protected def toSource: ProcessBuilder + + /** Writes the output stream of this process to the given file. */ + def #> (f: File): ProcessBuilder = toFile(f, append = false) + + /** Appends the output stream of this process to the given file. */ + def #>> (f: File): ProcessBuilder = toFile(f, append = true) + + /** Writes the output stream of this process to the given OutputStream. The + * argument is call-by-name, so the stream is recreated, written, and closed each + * time this process is executed. + */ + def #>(out: => OutputStream): ProcessBuilder = #> (new OStreamBuilder(out, "")) + + /** Writes the output stream of this process to a [[scala.sys.process.ProcessBuilder]]. */ + def #>(b: ProcessBuilder): ProcessBuilder = new PipedBuilder(toSource, b, toError = false) + + /** Returns a [[scala.sys.process.ProcessBuilder]] representing this `Source`. */ + def cat = toSource + private def toFile(f: File, append: Boolean) = #> (new FileOutput(f, append)) + } + + /** Represents everything that can receive an output from a + * [[scala.sys.process.ProcessBuilder]]. + */ + trait Sink { + protected def toSink: ProcessBuilder + + /** Reads the given file into the input stream of this process. */ + def #< (f: File): ProcessBuilder = #< (new FileInput(f)) + + /** Reads the given URL into the input stream of this process. */ + def #< (f: URL): ProcessBuilder = #< (new URLInput(f)) + + /** Reads the given InputStream into the input stream of this process. The + * argument is call-by-name, so the stream is recreated, read, and closed each + * time this process is executed. + */ + def #<(in: => InputStream): ProcessBuilder = #< (new IStreamBuilder(in, "")) + + /** Reads the output of a [[scala.sys.process.ProcessBuilder]] into the input stream of this process. */ + def #<(b: ProcessBuilder): ProcessBuilder = new PipedBuilder(b, toSink, toError = false) + } +} diff --git a/library/src/scala/sys/process/ProcessBuilderImpl.scala b/library/src/scala/sys/process/ProcessBuilderImpl.scala new file mode 100644 index 000000000000..41b284dbd0bb --- /dev/null +++ b/library/src/scala/sys/process/ProcessBuilderImpl.scala @@ -0,0 +1,275 @@ +/* + * 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 sys +package process + +import scala.language.`2.13` +import processInternal._ +import Process._ +import BasicIO.{LazilyListed, Streamed, Uncloseable} +import Uncloseable.protect + +import java.io.{FileInputStream, FileOutputStream} +import java.util.concurrent.LinkedBlockingQueue + +import scala.util.control.NonFatal + +private[process] trait ProcessBuilderImpl { + self: ProcessBuilder.type => + + private[process] class DaemonBuilder(underlying: ProcessBuilder) extends AbstractBuilder { + final def run(io: ProcessIO): Process = underlying.run(io.daemonized()) + } + + private[process] class Dummy(override val toString: String, exitValue: => Int) extends AbstractBuilder { + override def run(io: ProcessIO): Process = new DummyProcess(exitValue) + override def canPipeTo = true + } + + private[process] class URLInput(url: URL) extends IStreamBuilder(url.openStream(), url.toString) + private[process] class FileInput(file: File) extends IStreamBuilder(new FileInputStream(file), file.getAbsolutePath) + private[process] class FileOutput(file: File, append: Boolean) extends OStreamBuilder(new FileOutputStream(file, append), file.getAbsolutePath) + + private[process] class OStreamBuilder( + stream: => OutputStream, + label: String + ) extends ThreadBuilder(label, _ writeInput protect(stream)) { + override def hasExitValue = false + } + + private[process] class IStreamBuilder( + stream: => InputStream, + label: String + ) extends ThreadBuilder(label, _ processOutput protect(stream)) { + override def hasExitValue = false + } + + private[process] abstract class ThreadBuilder( + override val toString: String, + runImpl: ProcessIO => Unit + ) extends AbstractBuilder { + + override def run(io: ProcessIO): Process = { + val success = new LinkedBlockingQueue[Boolean](1) + def go(): Unit = { + var ok = false + try { + runImpl(io) + ok = true + } finally success.put(ok) + } + val t = Spawn("ThreadProcess", io.daemonizeThreads)(go()) + new ThreadProcess(t, success) + } + } + + /** Represents a simple command without any redirection or combination. */ + private[process] class Simple(p: JProcessBuilder) extends AbstractBuilder { + override def run(io: ProcessIO): Process = { + import java.lang.ProcessBuilder.Redirect.{INHERIT => Inherit} + import io.{daemonizeThreads, processError, processOutput, writeInput} + + val inherit = writeInput eq BasicIO.connectToStdIn + if (inherit) p.redirectInput(Inherit) + + val process = p.start() // start the external process + + // spawn threads that process the input, output, and error streams using the functions defined in `io` + val inThread = + if (inherit || (writeInput eq BasicIO.connectNoOp)) null + else Spawn("Simple-input", daemon = true)(writeInput(process.getOutputStream)) + val outThread = Spawn("Simple-output", daemonizeThreads)(processOutput(process.getInputStream())) + val errorThread = + if (p.redirectErrorStream) Nil + else List(Spawn("Simple-error", daemonizeThreads)(processError(process.getErrorStream()))) + + new SimpleProcess(process, inThread, outThread :: errorThread) + } + override def toString = p.command.toString + override def canPipeTo = true + } + + private[scala] abstract class AbstractBuilder extends ProcessBuilder with Sink with Source { + protected def toSource: AbstractBuilder = this + protected def toSink: AbstractBuilder = this + + private[this] val defaultStreamCapacity = 4096 + + def #|(other: ProcessBuilder): ProcessBuilder = { + require(other.canPipeTo, "Piping to multiple processes is not supported.") + new PipedBuilder(this, other, toError = false) + } + def #||(other: ProcessBuilder): ProcessBuilder = new OrBuilder(this, other) + def #&&(other: ProcessBuilder): ProcessBuilder = new AndBuilder(this, other) + def ###(other: ProcessBuilder): ProcessBuilder = new SequenceBuilder(this, other) + + def run(): Process = run(connectInput = false) + def run(connectInput: Boolean): Process = run(BasicIO.standard(connectInput)) + def run(log: ProcessLogger): Process = run(log, connectInput = false) + def run(log: ProcessLogger, connectInput: Boolean): Process = run(BasicIO(connectInput, log)) + + def !! = slurp(None, withIn = false) + def !!(log: ProcessLogger) = slurp(Some(log), withIn = false) + def !!< = slurp(None, withIn = true) + def !!<(log: ProcessLogger) = slurp(Some(log), withIn = true) + + def lazyLines: LazyList[String] = lazyLines(withInput = false, nonZeroException = true, None, defaultStreamCapacity) + def lazyLines(log: ProcessLogger): LazyList[String] = lazyLines(withInput = false, nonZeroException = true, Some(log), defaultStreamCapacity) + def lazyLines_! : LazyList[String] = lazyLines(withInput = false, nonZeroException = false, None, defaultStreamCapacity) + def lazyLines_!(log: ProcessLogger): LazyList[String] = lazyLines(withInput = false, nonZeroException = false, Some(log), defaultStreamCapacity) + def lazyLines(capacity: Integer): LazyList[String] = lazyLines(withInput = false, nonZeroException = true, None, capacity) + def lazyLines(log: ProcessLogger, capacity: Integer): LazyList[String] = lazyLines(withInput = false, nonZeroException = true, Some(log), capacity) + def lazyLines_!(capacity: Integer) : LazyList[String] = lazyLines(withInput = false, nonZeroException = false, None, capacity) + def lazyLines_!(log: ProcessLogger, capacity: Integer): LazyList[String] = lazyLines(withInput = false, nonZeroException = false, Some(log), capacity) + + @deprecated("internal", since = "2.13.4") def lineStream: Stream[String] = lineStream(withInput = false, nonZeroException = true, None, defaultStreamCapacity) + @deprecated("internal", since = "2.13.4") def lineStream(log: ProcessLogger): Stream[String] = lineStream(withInput = false, nonZeroException = true, Some(log), defaultStreamCapacity) + @deprecated("internal", since = "2.13.4") def lineStream_! : Stream[String] = lineStream(withInput = false, nonZeroException = false, None, defaultStreamCapacity) + @deprecated("internal", since = "2.13.4") def lineStream_!(log: ProcessLogger): Stream[String] = lineStream(withInput = false, nonZeroException = false, Some(log), defaultStreamCapacity) + @deprecated("internal", since = "2.13.4") def lineStream(capacity: Integer): Stream[String] = lineStream(withInput = false, nonZeroException = true, None, capacity) + @deprecated("internal", since = "2.13.4") def lineStream(log: ProcessLogger, capacity: Integer): Stream[String] = lineStream(withInput = false, nonZeroException = true, Some(log), capacity) + @deprecated("internal", since = "2.13.4") def lineStream_!(capacity: Integer) : Stream[String] = lineStream(withInput = false, nonZeroException = false, None, capacity) + @deprecated("internal", since = "2.13.4") def lineStream_!(log: ProcessLogger, capacity: Integer): Stream[String] = lineStream(withInput = false, nonZeroException = false, Some(log), capacity) + + def ! = run(connectInput = false).exitValue() + def !(io: ProcessIO) = run(io).exitValue() + def !(log: ProcessLogger) = runBuffered(log, connectInput = false) + def !< = run(connectInput = true).exitValue() + def !<(log: ProcessLogger) = runBuffered(log, connectInput = true) + + /** Constructs a new builder which runs this command with all input/output threads marked + * as daemon threads. This allows the creation of a long running process while still + * allowing the JVM to exit normally. + * + * Note: not in the public API because it's not fully baked, but I need the capability + * for fsc. + */ + def daemonized(): ProcessBuilder = new DaemonBuilder(this) + + private[this] def slurp(log: Option[ProcessLogger], withIn: Boolean): String = { + val buffer = new StringBuffer + val code = this ! BasicIO(withIn, buffer, log) + + if (code == 0) buffer.toString + else scala.sys.error("Nonzero exit value: " + code) + } + + private[this] def lazyLines( + withInput: Boolean, + nonZeroException: Boolean, + log: Option[ProcessLogger], + capacity: Integer + ): LazyList[String] = { + val lazilyListed = LazilyListed[String](nonZeroException, capacity) + val process = run(BasicIO(withInput, lazilyListed.process, log)) + + // extract done from lazilyListed so that the anonymous function below closes over just the done and not the whole lazilyListed (see https://github.com/scala/bug/issues/12185) + val done = lazilyListed.done + + Spawn("LazyLines") { + done { + try process.exitValue() + catch { + case NonFatal(_) => -2 + } + } + } + lazilyListed.lazyList + } + + @deprecated("internal", since = "2.13.4") + private[this] def lineStream( + withInput: Boolean, + nonZeroException: Boolean, + log: Option[ProcessLogger], + capacity: Integer + ): Stream[String] = { + val streamed = Streamed[String](nonZeroException, capacity) + val process = run(BasicIO(withInput, streamed.process, log)) + + Spawn("LineStream")(streamed done process.exitValue()) + streamed.stream() + } + + private[this] def runBuffered(log: ProcessLogger, connectInput: Boolean) = + log buffer run(log, connectInput).exitValue() + + def canPipeTo = false + def hasExitValue = true + } + + private[process] class URLImpl(url: URL) extends URLBuilder with Source { + protected def toSource: URLInput = new URLInput(url) + } + private[process] class FileImpl(base: File) extends FileBuilder with Sink with Source { + protected def toSource: FileInput = new FileInput(base) + protected def toSink: FileOutput = new FileOutput(base, append = false) + + def #<<(f: File): ProcessBuilder = #<<(new FileInput(f)) + def #<<(u: URL): ProcessBuilder = #<<(new URLInput(u)) + def #<<(s: => InputStream): ProcessBuilder = #<<(new IStreamBuilder(s, "")) + def #<<(b: ProcessBuilder): ProcessBuilder = new PipedBuilder(b, new FileOutput(base, append = true), toError = false) + } + + private[process] abstract class BasicBuilder extends AbstractBuilder { + protected[this] def checkNotThis(a: ProcessBuilder) = require(a != this, "Compound process '" + a + "' cannot contain itself.") + final def run(io: ProcessIO): Process = { + val p = createProcess(io) + p.start() + p + } + protected[this] def createProcess(io: ProcessIO): BasicProcess + } + + private[process] abstract class SequentialBuilder( + a: ProcessBuilder, + b: ProcessBuilder, + operatorString: String + ) extends BasicBuilder { + + checkNotThis(a) + checkNotThis(b) + override def toString = " ( " + a + " " + operatorString + " " + b + " ) " + } + + private[process] class PipedBuilder( + first: ProcessBuilder, + second: ProcessBuilder, + toError: Boolean + ) extends SequentialBuilder(first, second, if (toError) "#|!" else "#|") { + + override def createProcess(io: ProcessIO): PipedProcesses = new PipedProcesses(first, second, io, toError) + } + + private[process] class AndBuilder( + first: ProcessBuilder, + second: ProcessBuilder + ) extends SequentialBuilder(first, second, "#&&") { + override def createProcess(io: ProcessIO): AndProcess = new AndProcess(first, second, io) + } + + private[process] class OrBuilder( + first: ProcessBuilder, + second: ProcessBuilder + ) extends SequentialBuilder(first, second, "#||") { + override def createProcess(io: ProcessIO): OrProcess = new OrProcess(first, second, io) + } + + private[process] class SequenceBuilder( + first: ProcessBuilder, + second: ProcessBuilder + ) extends SequentialBuilder(first, second, "###") { + override def createProcess(io: ProcessIO): ProcessSequence = new ProcessSequence(first, second, io) + } +} diff --git a/library/src/scala/sys/process/ProcessIO.scala b/library/src/scala/sys/process/ProcessIO.scala new file mode 100644 index 000000000000..f9f793732218 --- /dev/null +++ b/library/src/scala/sys/process/ProcessIO.scala @@ -0,0 +1,73 @@ +/* + * 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 sys +package process + +import scala.language.`2.13` +import processInternal._ + +/** This class is used to control the I/O of every + * [[scala.sys.process.Process]]. The functions used to create it will be + * called with the process streams once it has been started. It might not be + * necessary to use `ProcessIO` directly -- + * [[scala.sys.process.ProcessBuilder]] can return the process output to the + * caller, or use a [[scala.sys.process.ProcessLogger]] which avoids direct + * interaction with a stream. One can even use the factories at `BasicIO` to + * create a `ProcessIO`, or use its helper methods when creating one's own + * `ProcessIO`. + * + * When creating a `ProcessIO`, it is important to ''close all streams'' when + * finished, since the JVM might use system resources to capture the process + * input and output, and will not release them unless the streams are + * explicitly closed. + * + * `ProcessBuilder` will call `writeInput`, `processOutput` and `processError` + * in separate threads, and if daemonizeThreads is true, they will all be + * marked as daemon threads. + * + * @param writeInput Function that will be called with the `OutputStream` to + * which all input to the process must be written. This will + * be called in a newly spawned thread. + * @param processOutput Function that will be called with the `InputStream` + * from which all normal output of the process must be + * read from. This will be called in a newly spawned + * thread. + * @param processError Function that will be called with the `InputStream` from + * which all error output of the process must be read from. + * This will be called in a newly spawned thread. + * @param daemonizeThreads Indicates whether the newly spawned threads that + * will run `processOutput`, `processError` and + * `writeInput` should be marked as daemon threads. + * @note Failure to close the passed streams may result in resource leakage. + */ +final class ProcessIO( + val writeInput: OutputStream => Unit, + val processOutput: InputStream => Unit, + val processError: InputStream => Unit, + val daemonizeThreads: Boolean +) { + def this(in: OutputStream => Unit, out: InputStream => Unit, err: InputStream => Unit) = this(in, out, err, daemonizeThreads = false) + + /** Creates a new `ProcessIO` with a different handler for the process input. */ + def withInput(write: OutputStream => Unit): ProcessIO = new ProcessIO(write, processOutput, processError, daemonizeThreads) + + /** Creates a new `ProcessIO` with a different handler for the normal output. */ + def withOutput(process: InputStream => Unit): ProcessIO = new ProcessIO(writeInput, process, processError, daemonizeThreads) + + /** Creates a new `ProcessIO` with a different handler for the error output. */ + def withError(process: InputStream => Unit): ProcessIO = new ProcessIO(writeInput, processOutput, process, daemonizeThreads) + + /** Creates a new `ProcessIO`, with `daemonizeThreads` true. */ + def daemonized(): ProcessIO = new ProcessIO(writeInput, processOutput, processError, daemonizeThreads = true) +} diff --git a/library/src/scala/sys/process/ProcessImpl.scala b/library/src/scala/sys/process/ProcessImpl.scala new file mode 100644 index 000000000000..7e5a742d9b5b --- /dev/null +++ b/library/src/scala/sys/process/ProcessImpl.scala @@ -0,0 +1,288 @@ +/* + * 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.sys.process + +import scala.language.`2.13` +import processInternal._ + +import java.util.concurrent.LinkedBlockingQueue +import java.io.{PipedInputStream, PipedOutputStream} + +import scala.annotation.tailrec + +private[process] trait ProcessImpl { + self: Process.type => + + /** Runs provided code in a new Thread and returns the Thread instance. */ + private[process] object Spawn { + def apply(prefix: String, daemon: Boolean = false)(f: => Unit): Thread = { + val thread = new Thread() { override def run() = f } + thread.setName(prefix + "-spawn-" + thread.getName) + thread.setDaemon(daemon) + thread.start() + thread + } + } + private[process] object Future { + def apply[T](f: => T): (Thread, () => T) = { + val result = new LinkedBlockingQueue[Either[Throwable, T]](1) + def run(): Unit = { + val value = try Right(f) catch { case e: Exception => Left(e) } + result.put(value) + } + + val t = Spawn("Future")(run()) + + (t, () => result.take() match { + case Right(value) => value + case Left(exception) => throw exception + }) + } + } + + private[process] class AndProcess( + a: ProcessBuilder, + b: ProcessBuilder, + io: ProcessIO + ) extends SequentialProcess(a, b, io, _ == 0) + + private[process] class OrProcess( + a: ProcessBuilder, + b: ProcessBuilder, + io: ProcessIO + ) extends SequentialProcess(a, b, io, _ != 0) + + private[process] class ProcessSequence( + a: ProcessBuilder, + b: ProcessBuilder, + io: ProcessIO + ) extends SequentialProcess(a, b, io, _ => true) + + private[process] class SequentialProcess( + a: ProcessBuilder, + b: ProcessBuilder, + io: ProcessIO, + evaluateSecondProcess: Int => Boolean + ) extends CompoundProcess { + + protected[this] override def runAndExitValue() = { + val first = a.run(io) + runInterruptible(first.exitValue())(first.destroy()) flatMap { codeA => + if (evaluateSecondProcess(codeA)) { + val second = b.run(io) + runInterruptible(second.exitValue())(second.destroy()) + } + else Some(codeA) + } + } + } + + private[process] abstract class BasicProcess extends Process { + def start(): Unit + } + + private[process] abstract class CompoundProcess extends BasicProcess { + def isAlive() = processThread.isAlive() + def destroy() = destroyer() + def exitValue() = futureValue() getOrElse scala.sys.error("No exit code: process destroyed.") + def start() = { futureThread ;() } + + protected lazy val (processThread, (futureThread, futureValue), destroyer) = { + val code = new LinkedBlockingQueue[Option[Int]](1) + val thread = Spawn("CompoundProcess") { + var value: Option[Int] = None + try value = runAndExitValue() + catch { + case _: IndexOutOfBoundsException + | _: IOException + | _: NullPointerException + | _: SecurityException + | _: UnsupportedOperationException + => value = Some(-1) + } + finally code.put(value) + } + + ( + thread, + Future(code.take()), // thread.join() + () => thread.interrupt() + ) + } + + /** Start and block until the exit value is available and then return it in Some. Return None if destroyed (use 'run')*/ + protected[this] def runAndExitValue(): Option[Int] + + protected[this] def runInterruptible[T](action: => T)(destroyImpl: => Unit): Option[T] = { + try Some(action) + catch onInterrupt { destroyImpl; None } + } + } + + private[process] class PipedProcesses(a: ProcessBuilder, b: ProcessBuilder, defaultIO: ProcessIO, toError: Boolean) extends CompoundProcess { + protected def newSource: PipeSource = new PipeSource(a.toString) + protected def newSink: PipeSink = new PipeSink(b.toString) + protected[this] override def runAndExitValue() = runAndExitValue(newSource, newSink) + protected[this] def runAndExitValue(source: PipeSource, sink: PipeSink): Option[Int] = { + source connectOut sink + source.start() + sink.start() + + /* Release PipeSource, PipeSink and Process in the correct order. + * If once connect Process with Source or Sink, then the order of releasing them + * must be Source -> Sink -> Process, otherwise IOException will be thrown. + */ + def releaseResources(so: PipeSource, sk: PipeSink, ps: Process*) = { + so.release() + sk.release() + ps.foreach(_.destroy()) + } + + val firstIO = + if (toError) defaultIO.withError(source.connectIn) + else defaultIO.withOutput(source.connectIn) + val secondIO = defaultIO.withInput(sink.connectOut) + + val second = + try b.run(secondIO) + catch onError { err => + releaseResources(source, sink) + throw err + } + val first = + try a.run(firstIO) + catch onError { err => + releaseResources(source, sink, second) + throw err + } + runInterruptible { + val exit1 = first.exitValue() + source.done() + source.join() + val exit2 = second.exitValue() + sink.done() + // Since file redirection (e.g. #>) is implemented as a piped process, + // we ignore its exit value so cmd #> file doesn't always return 0. + if (b.hasExitValue) exit2 else exit1 + } { + releaseResources(source, sink, first, second) + } + } + } + + private[process] abstract class PipeThread(isSink: Boolean, labelFn: () => String) extends Thread { + def run(): Unit + + private[process] def runloop(src: InputStream, dst: OutputStream): Unit = { + try BasicIO.transferFully(src, dst) + catch ioFailure(ioHandler) + finally BasicIO close { + if (isSink) dst else src + } + } + private def ioHandler(e: IOException): Unit = e.printStackTrace() + } + + private[process] class PipeSource(label: => String) extends PipeThread(isSink = false, () => label) { + setName(s"PipeSource($label)-$getName") + protected[this] val pipe = new PipedOutputStream + protected[this] val source = new LinkedBlockingQueue[Option[InputStream]](1) + override final def run(): Unit = { + @tailrec def go(): Unit = + source.take() match { + case Some(in) => runloop(in, pipe) ; go() + case None => + } + try go() + catch onInterrupt(()) + finally BasicIO close pipe + } + def connectIn(in: InputStream): Unit = source.put(Some(in)) + def connectOut(sink: PipeSink): Unit = sink connectIn pipe + def release(): Unit = { + interrupt() + done() + join() + } + def done() = source.put(None) + } + private[process] class PipeSink(label: => String) extends PipeThread(isSink = true, () => label) { + setName(s"PipeSink($label)-$getName") + protected[this] val pipe = new PipedInputStream + protected[this] val sink = new LinkedBlockingQueue[Option[OutputStream]](1) + override def run(): Unit = { + @tailrec def go(): Unit = + sink.take() match { + case Some(out) => runloop(pipe, out) ; go() + case None => + } + try go() + catch onInterrupt(()) + finally BasicIO close pipe + } + def connectOut(out: OutputStream): Unit = sink.put(Some(out)) + def connectIn(pipeOut: PipedOutputStream): Unit = pipe connect pipeOut + def release(): Unit = { + interrupt() + done() + join() + } + def done() = sink.put(None) + } + + /** A thin wrapper around a java.lang.Process. `ioThreads` are the Threads created to do I/O. + * The implementation of `exitValue` waits until these threads die before returning. + */ + private[process] class DummyProcess(action: => Int) extends Process { + private[this] val (thread, value) = Future(action) + override def isAlive() = thread.isAlive() + override def exitValue() = value() + override def destroy(): Unit = { } + } + + /** A thin wrapper around a java.lang.Process. + * + * `outputThreads` are the Threads created to read from the + * output and error streams of the process. + * + * `inputThread` is the Thread created to write to the input stream of + * the process. It may be null if stdin was inherited. + * + * The implementation of `exitValue` interrupts `inputThread` + * and then waits until all I/O threads die before returning. + */ + private[process] class SimpleProcess(p: JProcess, inputThread: Thread, outputThreads: List[Thread]) extends Process { + override def isAlive() = p.isAlive() + override def exitValue() = { + try p.waitFor() // wait for the process to terminate + finally interrupt() + outputThreads foreach (_.join()) // this ensures that all output is complete before returning (waitFor does not ensure this) + + p.exitValue() + } + override def destroy() = { + try { + outputThreads foreach (_.interrupt()) // on destroy, don't bother consuming any more output + p.destroy() + } + finally interrupt() + } + // we interrupt the input thread to notify it that it can terminate + private[this] def interrupt(): Unit = if (inputThread != null) inputThread.interrupt() + } + private[process] final class ThreadProcess(thread: Thread, success: LinkedBlockingQueue[Boolean]) extends Process { + override def isAlive() = thread.isAlive() + override def exitValue() = if (success.take()) 0 else 1 // thread.join() + override def destroy() = thread.interrupt() + } +} diff --git a/library/src/scala/sys/process/ProcessLogger.scala b/library/src/scala/sys/process/ProcessLogger.scala new file mode 100644 index 000000000000..621451cb619c --- /dev/null +++ b/library/src/scala/sys/process/ProcessLogger.scala @@ -0,0 +1,106 @@ +/* + * 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 sys +package process + +import scala.language.`2.13` +import java.io._ + +/** Encapsulates the output and error streams of a running process. This is used + * by [[scala.sys.process.ProcessBuilder]] when starting a process, as an + * alternative to [[scala.sys.process.ProcessIO]], which can be more difficult + * to use. Note that a `ProcessLogger` will be used to create a `ProcessIO` + * anyway. The object `BasicIO` has some functions to do that. + * + * Here is an example that counts the number of lines in the normal and error + * output of a process: + * {{{ + * import scala.sys.process._ + * + * var normalLines = 0 + * var errorLines = 0 + * val countLogger = ProcessLogger(line => normalLines += 1, + * line => errorLines += 1) + * "find /etc" ! countLogger + * }}} + * + * @see [[scala.sys.process.ProcessBuilder]] + */ +trait ProcessLogger { + /** Will be called with each line read from the process output stream. + */ + def out(s: => String): Unit + + /** Will be called with each line read from the process error stream. + */ + def err(s: => String): Unit + + /** If a process is begun with one of these `ProcessBuilder` methods: + * {{{ + * def !(log: ProcessLogger): Int + * def !<(log: ProcessLogger): Int + * }}} + * The run will be wrapped in a call to buffer. This gives the logger + * an opportunity to set up and tear down buffering. At present the + * library implementations of `ProcessLogger` simply execute the body + * unbuffered. + */ + def buffer[T](f: => T): T +} + +/** A [[scala.sys.process.ProcessLogger]] that writes output to a file. */ +class FileProcessLogger(file: File) extends ProcessLogger with Closeable with Flushable { + private[this] val writer = ( + new PrintWriter( + new BufferedWriter( + new OutputStreamWriter( + new FileOutputStream(file, true) + ) + ) + ) + ) + def out(s: => String): Unit = writer println s + def err(s: => String): Unit = writer println s + def buffer[T](f: => T): T = f + def close(): Unit = writer.close() + def flush(): Unit = writer.flush() +} + +/** Provides factories to create [[scala.sys.process.ProcessLogger]], which + * are used to capture output of [[scala.sys.process.ProcessBuilder]] commands + * when run. + */ +object ProcessLogger { + /** Creates a [[scala.sys.process.ProcessLogger]] that redirects output to a `java.io.File`. */ + def apply(file: File): FileProcessLogger = new FileProcessLogger(file) + + /** Creates a [[scala.sys.process.ProcessLogger]] that sends all output, standard and error, + * to the passed function. + */ + def apply(fn: String => Unit): ProcessLogger = apply(fn, fn) + + /** Creates a [[scala.sys.process.ProcessLogger]] that sends all output to the corresponding + * function. + * + * @param fout This function will receive standard output. + * + * @param ferr This function will receive standard error. + */ + def apply(fout: String => Unit, ferr: String => Unit): ProcessLogger = + new ProcessLogger { + def out(s: => String): Unit = fout(s) + def err(s: => String): Unit = ferr(s) + def buffer[T](f: => T): T = f + } +} diff --git a/library/src/scala/sys/process/package.scala b/library/src/scala/sys/process/package.scala new file mode 100644 index 000000000000..bf0e534e2284 --- /dev/null +++ b/library/src/scala/sys/process/package.scala @@ -0,0 +1,263 @@ +/* + * 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. + */ + +// Developer note: +// scala -J-Dscala.process.debug +// for process debugging output. +// +package scala.sys +package process + +import scala.language.`2.13` + + /** This package handles the execution of external processes. The contents of + * this package can be divided in three groups, according to their + * responsibilities: + * + * - Indicating what to run and how to run it. + * - Handling a process input and output. + * - Running the process. + * + * For simple uses, the only group that matters is the first one. Running an + * external command can be as simple as `"ls".!`, or as complex as building a + * pipeline of commands such as this: + * + * {{{ + * import scala.sys.process._ + * "ls" #| "grep .scala" #&& Seq("sh", "-c", "scalac *.scala") #|| "echo nothing found" lazyLines + * }}} + * + * We describe below the general concepts and architecture of the package, + * and then take a closer look at each of the categories mentioned above. + * + * ==Concepts and Architecture== + * + * The underlying basis for the whole package is Java's `Process` and + * `ProcessBuilder` classes. While there's no need to use these Java classes, + * they impose boundaries on what is possible. One cannot, for instance, + * retrieve a ''process id'' for whatever is executing. + * + * When executing an external process, one can provide a command's name, + * arguments to it, the directory in which it will be executed and what + * environment variables will be set. For each executing process, one can + * feed its standard input through a `java.io.OutputStream`, and read from + * its standard output and standard error through a pair of + * `java.io.InputStream`. One can wait until a process finishes execution and + * then retrieve its return value, or one can kill an executing process. + * Everything else must be built on those features. + * + * This package provides a DSL for running and chaining such processes, + * mimicking Unix shells ability to pipe output from one process to the input + * of another, or control the execution of further processes based on the + * return status of the previous one. + * + * In addition to this DSL, this package also provides a few ways of + * controlling input and output of these processes, going from simple and + * easy to use to complex and flexible. + * + * When processes are composed, a new `ProcessBuilder` is created which, when + * run, will execute the `ProcessBuilder` instances it is composed of + * according to the manner of the composition. If piping one process to + * another, they'll be executed simultaneously, and each will be passed a + * `ProcessIO` that will copy the output of one to the input of the other. + * + * ==What to Run and How== + * + * The central component of the process execution DSL is the + * [[scala.sys.process.ProcessBuilder]] trait. It is `ProcessBuilder` that + * implements the process execution DSL, that creates the + * [[scala.sys.process.Process]] that will handle the execution, and return + * the results of such execution to the caller. We can see that DSL in the + * introductory example: `#|`, `#&&` and `#!!` are methods on + * `ProcessBuilder` used to create a new `ProcessBuilder` through + * composition. + * + * One creates a `ProcessBuilder` either through factories on the + * [[scala.sys.process.Process]]'s companion object, or through implicit + * conversions available in this package object itself. Implicitly, each + * process is created either out of a `String`, with arguments separated by + * spaces -- no escaping of spaces is possible -- or out of a + * [[scala.collection.Seq]], where the first element represents the command + * name, and the remaining elements are arguments to it. In this latter case, + * arguments may contain spaces. + * + * To further control what how the process will be run, such as specifying + * the directory in which it will be run, see the factories on + * [[scala.sys.process.Process]]'s companion object. + * + * Once the desired `ProcessBuilder` is available, it can be executed in + * different ways, depending on how one desires to control its I/O, and what + * kind of result one wishes for: + * + * - Return status of the process (`!` methods) + * - Output of the process as a `String` (`!!` methods) + * - Continuous output of the process as a `LazyList[String]` (`lazyLines` methods) + * - The `Process` representing it (`run` methods) + * + * Some simple examples of these methods: + * {{{ + * import scala.sys.process._ + * + * // This uses ! to get the exit code + * def fileExists(name: String) = Seq("test", "-f", name).! == 0 + * + * // This uses !! to get the whole result as a string + * val dirContents = "ls".!! + * + * // This "fire-and-forgets" the method, which can be lazily read through + * // a LazyList[String] + * def sourceFilesAt(baseDir: String): LazyList[String] = { + * val cmd = Seq("find", baseDir, "-name", "*.scala", "-type", "f") + * cmd.lazyLines + * } + * }}} + * + * We'll see more details about controlling I/O of the process in the next + * section. + * + * ==Handling Input and Output== + * + * In the underlying Java model, once a `Process` has been started, one can + * get `java.io.InputStream` and `java.io.OutputStream` representing its + * output and input respectively. That is, what one writes to an + * `OutputStream` is turned into input to the process, and the output of a + * process can be read from an `InputStream` -- of which there are two, one + * representing normal output, and the other representing error output. + * + * This model creates a difficulty, which is that the code responsible for + * actually running the external processes is the one that has to take + * decisions about how to handle its I/O. + * + * This package presents an alternative model: the I/O of a running process + * is controlled by a [[scala.sys.process.ProcessIO]] object, which can be + * passed _to_ the code that runs the external process. A `ProcessIO` will + * have direct access to the java streams associated with the process I/O. It + * must, however, close these streams afterwards. + * + * Simpler abstractions are available, however. The components of this + * package that handle I/O are: + * + * - [[scala.sys.process.ProcessIO]]: provides the low level abstraction. + * - [[scala.sys.process.ProcessLogger]]: provides a higher level abstraction + * for output, and can be created through its companion object. + * - [[scala.sys.process.BasicIO]]: a library of helper methods for the + * creation of `ProcessIO`. + * - This package object itself, with a few implicit conversions. + * + * Some examples of I/O handling: + * {{{ + * import scala.sys.process._ + * + * // An overly complex way of computing size of a compressed file + * def gzFileSize(name: String) = { + * val cat = Seq("zcat", name) + * var count = 0 + * def byteCounter(input: java.io.InputStream) = { + * while(input.read() != -1) count += 1 + * input.close() + * } + * val p = cat run new ProcessIO(_.close(), byteCounter, _.close()) + * p.exitValue() + * count + * } + * + * // This "fire-and-forgets" the method, which can be lazily read through + * // a LazyList[String], and accumulates all errors on a StringBuffer + * def sourceFilesAt(baseDir: String): (LazyList[String], StringBuffer) = { + * val buffer = new StringBuffer() + * val cmd = Seq("find", baseDir, "-name", "*.scala", "-type", "f") + * val lazyLines = cmd lazyLines_! ProcessLogger(buffer append _) + * (lazyLines, buffer) + * } + * }}} + * + * Instances of the java classes `java.io.File` and `java.net.URL` can both + * be used directly as input to other processes, and `java.io.File` can be + * used as output as well. One can even pipe one to the other directly + * without any intervening process, though that's not a design goal or + * recommended usage. For example, the following code will copy a web page to + * a file: + * {{{ + * import java.io.File + * import java.net.URL + * import scala.sys.process._ + * new URL("https://www.scala-lang.org/") #> new File("scala-lang.html") ! + * }}} + * + * More information about the other ways of controlling I/O can be found + * in the Scaladoc for the associated objects, traits and classes. + * + * ==Running the Process== + * + * Paradoxically, this is the simplest component of all, and the one least + * likely to be interacted with. It consists solely of + * [[scala.sys.process.Process]], and it provides only two methods: + * + * - `exitValue()`: blocks until the process exit, and then returns the exit + * value. This is what happens when one uses the `!` method of + * `ProcessBuilder`. + * - `destroy()`: this will kill the external process and close the streams + * associated with it. + */ + @annotation.nowarn("msg=package object inheritance") + object `package` extends ProcessImplicits { + /** The input stream of this process */ + def stdin = java.lang.System.in + /** The output stream of this process */ + def stdout = java.lang.System.out + /** The error stream of this process */ + def stderr = java.lang.System.err + } + // private val shell: String => Array[String] = + // if (isWin) Array("cmd.exe", "/C", _) + // else Array("sh", "-c", _) + + // These are in a nested object instead of at the package level + // due to the issues described in tickets #3160 and #3836. + private[process] object processInternal { + final val processDebug = props contains "scala.process.debug" + dbg("Initializing process package.") + + type =?>[-A, +B] = PartialFunction[A, B] + type Closeable = java.io.Closeable + type File = java.io.File + type IOException = java.io.IOException + type InterruptedIOException = java.io.InterruptedIOException + type InputStream = java.io.InputStream + type JProcess = java.lang.Process + type JProcessBuilder = java.lang.ProcessBuilder + type OutputStream = java.io.OutputStream + type URL = java.net.URL + + @deprecated("Use `java.util.concurrent.LinkedBlockingQueue with capacity 1` instead.", since = "2.13.4") + type SyncVar[T] = scala.concurrent.SyncVar[T] + + def onError[T](handler: Throwable => T): Throwable =?> T = { + case e @ _ => handler(e) + } + + def onIOInterrupt[T](handler: => T): Throwable =?> T = { + case _: InterruptedIOException => handler + } + + def onInterrupt[T](handler: => T): Throwable =?> T = { + case _: InterruptedException => handler + } + + def ioFailure[T](handler: IOException => T): Throwable =?> T = { + case e: IOException => handler(e) + } + + def dbg(msgs: Any*) = if (processDebug) { + Console.println("[process] " + (msgs mkString " ")) + } + } diff --git a/library/src/scala/throws.scala b/library/src/scala/throws.scala new file mode 100644 index 000000000000..898c745a4804 --- /dev/null +++ b/library/src/scala/throws.scala @@ -0,0 +1,30 @@ +/* + * 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.language.`2.13` + +/** + * Annotation for specifying the exceptions thrown by a method. + * For example: + * {{{ + * class Reader(fname: String) { + * private val in = new BufferedReader(new FileReader(fname)) + * @throws[IOException]("if the file doesn't exist") + * def read() = in.read() + * } + * }}} + */ +final class throws[T <: Throwable](cause: String = "") extends scala.annotation.StaticAnnotation { + def this(clazz: Class[T]) = this("") +} diff --git a/library/src/scala/transient.scala b/library/src/scala/transient.scala new file mode 100644 index 000000000000..3ca34fba9f69 --- /dev/null +++ b/library/src/scala/transient.scala @@ -0,0 +1,19 @@ +/* + * 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.language.`2.13` +import scala.annotation.meta._ + +@field +final class transient extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/typeConstraints.scala b/library/src/scala/typeConstraints.scala new file mode 100644 index 000000000000..3c7e15de67ca --- /dev/null +++ b/library/src/scala/typeConstraints.scala @@ -0,0 +1,244 @@ +/* + * 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.language.`2.13` +import scala.annotation.implicitNotFound + +/** An instance of `A <:< B` witnesses that `A` is a subtype of `B`. + * Requiring an implicit argument of the type `A <:< B` encodes + * the generalized constraint `A <: B`. + * + * To constrain any abstract type `T` that's in scope in a method's + * argument list (not just the method's own type parameters) simply + * add an implicit argument of type `T <:< U`, where `U` is the required + * upper bound; or for lower-bounds, use: `L <:< T`, where `L` is the + * required lower bound. + * + * In case of any confusion over which method goes in what direction, all the "Co" methods (including + * [[apply]]) go from left to right in the type ("with" the type), and all the "Contra" methods go + * from right to left ("against" the type). E.g., [[apply]] turns a `From` into a `To`, and + * [[substituteContra]] replaces the `To`s in a type with `From`s. + * + * In part contributed by Jason Zaugg. + * + * @tparam From a type which is proved a subtype of `To` + * @tparam To a type which is proved a supertype of `From` + * + * @example [[scala.Option#flatten]] + * {{{ + * sealed trait Option[+A] { + * // def flatten[B, A <: Option[B]]: Option[B] = ... + * // won't work, since the A in flatten shadows the class-scoped A. + * def flatten[B](implicit ev: A <:< Option[B]): Option[B] + * = if(isEmpty) None else ev(get) + * // Because (A <:< Option[B]) <: (A => Option[B]), ev can be called to turn the + * // A from get into an Option[B], and because ev is implicit, that call can be + * // left out and inserted automatically. + * } + * }}} + * + * @see [[=:=]] for expressing equality constraints + * + * @define isProof This method is impossible to implement without `throw`ing or otherwise "cheating" unless + * `From <: To`, so it ensures that this really represents a subtyping relationship. + * @define contraCo contravariant in the first argument and covariant in the second + * @define contraCon a contravariant type constructor + * @define coCon a covariant type constructor + * @define sameDiff but with a (potentially) different type + * @define tp <:< + */ +// All of these methods are reimplemented unsafely in =:=.singleton to avoid any indirection. +// They are here simply for reference as the "correct", safe implementations. +@implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.") +sealed abstract class <:<[-From, +To] extends (From => To) with Serializable { + /** Substitute `To` for `From` and `From` for `To` in the type `F[To, From]`, given that `F` is $contraCo. + * Essentially swaps `To` and `From` in `ftf`'s type. + * + * Equivalent in power to each of [[substituteCo]] and [[substituteContra]]. + * + * $isProof + * + * @return `ftf`, $sameDiff + */ + def substituteBoth[F[-_, +_]](ftf: F[To, From]): F[From, To] + // = substituteCo[({type G[+T] = F[From, T]})#G](substituteContra[({type G[-T] = F[T, From})#G](ftf)) + // = substituteContra[({type G[-T] = F[T, To]})#G](substituteCo[({type G[+T] = F[From, T]})#G](ftf)) + /** Substitute the `From` in the type `F[From]`, where `F` is $coCon, for `To`. + * + * Equivalent in power to each of [[substituteBoth]] and [[substituteContra]]. + * + * $isProof + * + * @return `ff`, $sameDiff + */ + def substituteCo[F[+_]](ff: F[From]): F[To] = { + type G[-_, +T] = F[T] + substituteBoth[G](ff) + } + // = substituteContra[({type G[-T] = F[T] => F[To]})#G](identity)(ff) + /** Substitute the `To` in the type `F[To]`, where `F` is $contraCon, for `From`. + * + * Equivalent in power to each of [[substituteBoth]] and [[substituteCo]]. + * + * $isProof + * + * @return `ft`, $sameDiff + */ + def substituteContra[F[-_]](ft: F[To]): F[From] = { + type G[-T, +_] = F[T] + substituteBoth[G](ft) + } + // = substituteCo[({type G[+T] = F[T] => F[From]})#G](identity)(ft) + + /** Coerce a `From` into a `To`. This is guaranteed to be the identity function. + * + * This method is often called implicitly as an implicit `A $tp B` doubles as an implicit view `A => B`. + * + * @param f some value of type `From` + * @return `f`, $sameDiff + */ + override def apply(f: From): To = { + type Id[+X] = X + substituteCo[Id](f) + } + + override def compose[C](r: C => From): C => To = { + type G[+T] = C => T + substituteCo[G](r) + } + /** If `From <: To` and `C <: From`, then `C <: To` (subtyping is transitive) */ + def compose[C](r: C <:< From): C <:< To = { + type G[+T] = C <:< T + substituteCo[G](r) + } + override def andThen[C](r: To => C): From => C = { + type G[-T] = T => C + substituteContra[G](r) + } + /** If `From <: To` and `To <: C`, then `From <: C` (subtyping is transitive) */ + def andThen[C](r: To <:< C): From <:< C = { + type G[-T] = T <:< C + substituteContra[G](r) + } + + /** Lift this evidence over $coCon `F`. */ + def liftCo[F[+_]]: F[From] <:< F[To] = { + type G[+T] = F[From] <:< F[T] + substituteCo[G](implicitly[G[From]]) + } + /** Lift this evidence over $contraCon `F`. */ + def liftContra[F[-_]]: F[To] <:< F[From] = { + type G[-T] = F[To] <:< F[T] + substituteContra[G](implicitly[G[To]]) + } +} + +object <:< { + // the only instance for <:< and =:=, used to avoid overhead + private val singleton: =:=[Any, Any] = new =:=[Any,Any] { + override def substituteBoth[F[_, _]](ftf: F[Any, Any]) = ftf + override def substituteCo [F[_]](ff: F[Any]) = ff + override def substituteContra[F[_]](ff: F[Any]) = ff + override def apply(x: Any) = x + override def flip: Any =:= Any = this + override def compose[C](r: C => Any) = r + override def compose[C](r: C <:< Any) = r + override def compose[C](r: C =:= Any) = r + override def andThen[C](r: Any => C) = r + override def andThen[C](r: Any <:< C) = r + override def andThen[C](r: Any =:= C) = r + override def liftCo [F[_]] = asInstanceOf[F[Any] =:= F[Any]] + override def liftContra[F[_]] = asInstanceOf[F[Any] =:= F[Any]] + override def toString = "generalized constraint" + } + + /** `A =:= A` for all `A` (equality is reflexive). This also provides implicit views `A <:< B` + * when `A <: B`, because `(A =:= A) <: (A <:< A) <: (A <:< B)`. + */ + implicit def refl[A]: A =:= A = singleton.asInstanceOf[A =:= A] + // = new =:=[A, A] { override def substituteBoth[F[_, _]](faa: F[A, A]): F[A, A] = faa } + + /** If `A <: B` and `B <: A`, then `A = B` (subtyping is antisymmetric) */ + def antisymm[A, B](implicit l: A <:< B, r: B <:< A): A =:= B = singleton.asInstanceOf[A =:= B] + // = ??? (I don't think this is possible to implement "safely") +} + +/** An instance of `A =:= B` witnesses that the types `A` and `B` are equal. It also acts as a `A <:< B`, + * but not a `B <:< A` (directly) due to restrictions on subclassing. + * + * In case of any confusion over which method goes in what direction, all the "Co" methods (including + * [[apply]]) go from left to right in the type ("with" the type), and all the "Contra" methods go + * from right to left ("against" the type). E.g., [[apply]] turns a `From` into a `To`, and + * [[substituteContra]] replaces the `To`s in a type with `From`s. + * + * @tparam From a type which is proved equal to `To` + * @tparam To a type which is proved equal to `From` + * + * @example An in-place variant of [[scala.collection.mutable.ArrayBuffer#transpose]] {{{ + * implicit class BufOps[A](private val buf: ArrayBuffer[A]) extends AnyVal { + * def inPlaceTranspose[E]()(implicit ev: A =:= ArrayBuffer[E]) = ??? + * // Because ArrayBuffer is invariant, we can't make do with just a A <:< ArrayBuffer[E] + * // Getting buffers *out* from buf would work, but adding them back *in* wouldn't. + * } + * }}} + * @see [[<:<]] for expressing subtyping constraints + * + * @define isProof This method is impossible to implement without `throw`ing or otherwise "cheating" unless + * `From = To`, so it ensures that this really represents a type equality. + * @define contraCo a type constructor of two arguments + * @define contraCon any type constructor + * @define coCon any type constructor + * @define tp =:= + */ +// Most of the notes on <:< above apply to =:= as well +@implicitNotFound(msg = "Cannot prove that ${From} =:= ${To}.") +sealed abstract class =:=[From, To] extends (From <:< To) with Serializable { + override def substituteBoth[F[_, _]](ftf: F[To, From]): F[From, To] + override def substituteCo[F[_]](ff: F[From]): F[To] = { + type G[_, T] = F[T] + substituteBoth[G](ff) + } + // = substituteContra[({type G[T] = F[T] => F[To]})#G](identity)(ff) + override def substituteContra[F[_]](ft: F[To]): F[From] = { + type G[T, _] = F[T] + substituteBoth[G](ft) + } + // = substituteCo[({type G[T] = F[T] => F[From]})#G](identity)(ft) + + /** @inheritdoc */ override def apply(f: From) = super.apply(f) + + /** If `From = To` then `To = From` (equality is symmetric) */ + def flip: To =:= From = { + type G[T, F] = F =:= T + substituteBoth[G](this) + } + + /** If `From = To` and `C = From`, then `C = To` (equality is transitive) */ + def compose[C](r: C =:= From): C =:= To = { + type G[T] = C =:= T + substituteCo[G](r) + } + /** If `From = To` and `To = C`, then `From = C` (equality is transitive) */ + def andThen[C](r: To =:= C): From =:= C = { + type G[T] = T =:= C + substituteContra[G](r) + } + + override def liftCo[F[_]]: F[From] =:= F[To] = { + type G[T] = F[T] =:= F[To] + substituteContra[G](implicitly[G[To]]) + } + /** Lift this evidence over the type constructor `F`, but flipped. */ + override def liftContra[F[_]]: F[To] =:= F[From] = liftCo[F].flip +} diff --git a/library/src/scala/unchecked.scala b/library/src/scala/unchecked.scala new file mode 100644 index 000000000000..1990bc23b819 --- /dev/null +++ b/library/src/scala/unchecked.scala @@ -0,0 +1,40 @@ +/* + * 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.language.`2.13` + +/** An annotation to designate that the annotated entity + * should not be considered for additional compiler checks. + * Specific applications include annotating the subject of + * a match expression to suppress exhaustiveness and reachability warnings, and + * annotating a type argument in a match case to suppress + * unchecked warnings. + * + * Such suppression should be used with caution, without which + * one may encounter [[scala.MatchError]] or [[java.lang.ClassCastException]] + * at runtime. In most cases one can and should address the + * warning instead of suppressing it. + * + * {{{ + * object Test extends App { + * // This would normally warn "match is not exhaustive" + * // because `None` is not covered. + * def f(x: Option[String]) = (x: @unchecked) match { case Some(y) => y } + * // This would normally warn "type pattern is unchecked" + * // but here will blindly cast the head element to String. + * def g(xs: Any) = xs match { case x: List[String @unchecked] => x.head } + * } + * }}} + */ +final class unchecked extends scala.annotation.Annotation {} diff --git a/library/src/scala/util/ChainingOps.scala b/library/src/scala/util/ChainingOps.scala new file mode 100644 index 000000000000..41526bffbb46 --- /dev/null +++ b/library/src/scala/util/ChainingOps.scala @@ -0,0 +1,66 @@ +/* + * 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 util + +import scala.language.`2.13` +import scala.language.implicitConversions + +trait ChainingSyntax { + @inline implicit final def scalaUtilChainingOps[A](a: A): ChainingOps[A] = new ChainingOps(a) +} + +/** Adds chaining methods `tap` and `pipe` to every type. + */ +final class ChainingOps[A](private val self: A) extends AnyVal { + /** Applies `f` to the value for its side effects, and returns the original value. + * + * {{{ + * scala> import scala.util.chaining._ + * + * scala> val xs = List(1, 2, 3).tap(ys => println("debug " + ys.toString)) + * debug List(1, 2, 3) + * xs: List[Int] = List(1, 2, 3) + * }}} + * + * @param f the function to apply to the value. + * @tparam U the result type of the function `f`. + * @return the original value `self`. + */ + def tap[U](f: A => U): A = { + f(self) + self + } + + /** Converts the value by applying the function `f`. + * + * {{{ + * scala> import scala.util.chaining._ + * + * scala> val times6 = (_: Int) * 6 + * times6: Int => Int = \$\$Lambda\$2023/975629453@17143b3b + * + * scala> val i = (1 - 2 - 3).pipe(times6).pipe(scala.math.abs) + * i: Int = 24 + * }}} + * + * Note: `(1 - 2 - 3).pipe(times6)` may have a small amount of overhead at + * runtime compared to the equivalent `{ val temp = 1 - 2 - 3; times6(temp) }`. + * + * @param f the function to apply to the value. + * @tparam B the result type of the function `f`. + * @return a new value resulting from applying the given function + * `f` to this value. + */ + def pipe[B](f: A => B): B = f(self) +} diff --git a/library/src/scala/util/DynamicVariable.scala b/library/src/scala/util/DynamicVariable.scala new file mode 100644 index 000000000000..1f05de8ab5ca --- /dev/null +++ b/library/src/scala/util/DynamicVariable.scala @@ -0,0 +1,70 @@ +/* + * 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 util + +import scala.language.`2.13` +import java.lang.InheritableThreadLocal + +/** `DynamicVariables` provide a binding mechanism where the current + * value is found through dynamic scope, but where access to the + * variable itself is resolved through static scope. + * + * The current value can be retrieved with the value method. New values + * should be pushed using the `withValue` method. Values pushed via + * `withValue` only stay valid while the `withValue`'s second argument, a + * parameterless closure, executes. When the second argument finishes, + * the variable reverts to the previous value. + * + * {{{ + * someDynamicVariable.withValue(newValue) { + * // ... code called in here that calls value ... + * // ... will be given back the newValue ... + * } + * }}} + * + * Each thread gets its own stack of bindings. When a + * new thread is created, the `DynamicVariable` gets a copy + * of the stack of bindings from the parent thread, and + * from then on the bindings for the new thread + * are independent of those for the original thread. + */ +class DynamicVariable[T](init: T) { + private[this] val tl = new InheritableThreadLocal[T] { + override def initialValue: T with AnyRef = init.asInstanceOf[T with AnyRef] + } + + /** Retrieve the current value */ + def value: T = tl.get.asInstanceOf[T] + + /** Set the value of the variable while executing the specified + * thunk. + * + * @param newval The value to which to set the variable + * @param thunk The code to evaluate under the new setting + */ + def withValue[S](newval: T)(thunk: => S): S = { + val oldval = value + tl set newval + + try thunk + finally tl set oldval + } + + /** Change the currently bound value, discarding the old value. + * Usually withValue() gives better semantics. + */ + def value_=(newval: T) = tl set newval + + override def toString: String = "DynamicVariable(" + value + ")" +} diff --git a/library/src/scala/util/Either.scala b/library/src/scala/util/Either.scala new file mode 100644 index 000000000000..7caa89189301 --- /dev/null +++ b/library/src/scala/util/Either.scala @@ -0,0 +1,840 @@ +/* + * 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 util + +import scala.language.`2.13` + +/** Represents a value of one of two possible types (a disjoint union). + * An instance of `Either` is an instance of either [[scala.util.Left]] or [[scala.util.Right]]. + * + * A common use of `Either` is as an alternative to [[scala.Option]] for dealing + * with possibly missing values. In this usage, [[scala.None]] is replaced + * with a [[scala.util.Left]] which can contain useful information. + * [[scala.util.Right]] takes the place of [[scala.Some]]. Convention dictates + * that `Left` is used for failure and `Right` is used for success. + * + * For example, you could use `Either[String, Int]` to indicate whether a + * received input is a `String` or an `Int`. + * + * {{{ + * import scala.io.StdIn._ + * val in = readLine("Type Either a string or an Int: ") + * val result: Either[String,Int] = + * try Right(in.toInt) + * catch { + * case e: NumberFormatException => Left(in) + * } + * + * result match { + * case Right(x) => s"You passed me the Int: \$x, which I will increment. \$x + 1 = \${x+1}" + * case Left(x) => s"You passed me the String: \$x" + * } + * }}} + * + * `Either` is right-biased, which means that `Right` is assumed to be the default case to + * operate on. If it is `Left`, operations like `map` and `flatMap` return the `Left` value unchanged: + * + * {{{ + * def doubled(i: Int) = i * 2 + * Right(42).map(doubled) // Right(84) + * Left(42).map(doubled) // Left(42) + * }}} + * + * Since `Either` defines the methods `map` and `flatMap`, it can also be used in for comprehensions: + * {{{ + * val right1 = Right(1) : Right[Double, Int] + * val right2 = Right(2) + * val right3 = Right(3) + * val left23 = Left(23.0) : Left[Double, Int] + * val left42 = Left(42.0) + * + * for { + * x <- right1 + * y <- right2 + * z <- right3 + * } yield x + y + z // Right(6) + * + * for { + * x <- right1 + * y <- right2 + * z <- left23 + * } yield x + y + z // Left(23.0) + * + * for { + * x <- right1 + * y <- left23 + * z <- right2 + * } yield x + y + z // Left(23.0) + * + * // Guard expressions are not supported: + * for { + * i <- right1 + * if i > 0 + * } yield i + * // error: value withFilter is not a member of Right[Double,Int] + * + * // Similarly, refutable patterns are not supported: + * for (x: Int <- right1) yield x + * // error: value withFilter is not a member of Right[Double,Int] + * + * // To use a filtered value, convert to an Option first, + * // which drops the Left case, as None contains no value: + * for { + * i <- right1.toOption + * if i > 0 + * } yield i + * + * }}} + * + * Since `for` comprehensions use `map` and `flatMap`, the types + * of function parameters used in the expression must be inferred. + * These types are constrained by the `Either` values. In particular, + * because of right-biasing, `Left` values may require an explicit + * type argument for type parameter `B`, the right value. Otherwise, + * it might be inferred as `Nothing`. + * + * {{{ + * for { + * x <- left23 + * y <- right1 + * z <- left42 // type at this position: Either[Double, Nothing] + * } yield x + y + z + * // ^ + * // error: ambiguous reference to overloaded definition, + * // both method + in class Int of type (x: Char)Int + * // and method + in class Int of type (x: Byte)Int + * // match argument types (Nothing) + * + * for (x <- right2 ; y <- left23) yield x + y // Left(23.0) + * for (x <- right2 ; y <- left42) yield x + y // error + * + * for { + * x <- right1 + * y <- left42 // type at this position: Either[Double, Nothing] + * z <- left23 + * } yield x + y + z + * // Left(42.0), but unexpectedly a `Either[Double,String]` + * }}} + */ +sealed abstract class Either[+A, +B] extends Product with Serializable { + /** Projects this `Either` as a `Left`. + * + * This allows for-comprehensions over the left side of `Either` instances, + * reversing `Either`'s usual right-bias. + * + * For example {{{ + * for (s <- Left("flower").left) yield s.length // Left(6) + * }}} + * + * Continuing the analogy with [[scala.Option]], a `LeftProjection` declares + * that `Left` should be analogous to `Some` in some code. + * + * {{{ + * // using Option + * def interactWithDB(x: Query): Option[Result] = + * try Some(getResultFromDatabase(x)) + * catch { + * case _: SQLException => None + * } + * + * // this will only be executed if interactWithDB returns a Some + * val report = for (result <- interactWithDB(someQuery)) yield generateReport(result) + * report match { + * case Some(r) => send(r) + * case None => log("report not generated, not sure why...") + * } + * + * // using Either + * def interactWithDB(x: Query): Either[Exception, Result] = + * try Right(getResultFromDatabase(x)) + * catch { + * case e: SQLException => Left(e) + * } + * + * // run a report only if interactWithDB returns a Right + * val report = for (result <- interactWithDB(someQuery)) yield generateReport(result) + * report match { + * case Right(r) => send(r) + * case Left(e) => log(s"report not generated, reason was \$e") + * } + * // only report errors + * for (e <- interactWithDB(someQuery).left) log(s"query failed, reason was \$e") + * }}} + */ + def left = Either.LeftProjection(this) + + /** Projects this `Either` as a `Right`. + * + * Because `Either` is right-biased, this method is not normally needed. + */ + @deprecated("Either is now right-biased, use methods directly on Either", "2.13.0") + def right = Either.RightProjection(this) + + /** Applies `fa` if this is a `Left` or `fb` if this is a `Right`. + * + * @example {{{ + * val result = util.Try("42".toInt).toEither + * result.fold( + * e => s"Operation failed with \$e", + * v => s"Operation produced value: \$v" + * ) + * }}} + * + * @param fa the function to apply if this is a `Left` + * @param fb the function to apply if this is a `Right` + * @return the results of applying the function + */ + def fold[C](fa: A => C, fb: B => C): C = this match { + case Right(b) => fb(b) + case Left(a) => fa(a) + } + + /** If this is a `Left`, then return the left value in `Right` or vice versa. + * + * @example {{{ + * val left: Either[String, Int] = Left("left") + * val right: Either[Int, String] = left.swap // Result: Right("left") + * }}} + * @example {{{ + * val right = Right(2) + * val left = Left(3) + * for { + * r1 <- right + * r2 <- left.swap + * } yield r1 * r2 // Right(6) + * }}} + */ + def swap: Either[B, A] = this match { + case Left(a) => Right(a) + case Right(b) => Left(b) + } + + /** Joins an `Either` through `Right`. + * + * This method requires that the right side of this `Either` is itself + * an `Either` type. That is, this must be some type like: {{{ + * Either[A, Either[A, C]] + * }}} (which respects the type parameter bounds, shown below.) + * + * If this instance is a `Right[Either[A, C]]` then the contained `Either[A, C]` + * will be returned, otherwise this value will be returned unmodified. + * + * @example {{{ + * Right[String, Either[String, Int]](Right(12)).joinRight // Result: Right(12) + * Right[String, Either[String, Int]](Left("flower")).joinRight // Result: Left("flower") + * Left[String, Either[String, Int]]("flower").joinRight // Result: Left("flower") + * }}} + * + * This method, and `joinLeft`, are analogous to `Option#flatten` + */ + def joinRight[A1 >: A, B1 >: B, C](implicit ev: B1 <:< Either[A1, C]): Either[A1, C] = this match { + case Right(b) => b + case _ => this.asInstanceOf[Either[A1, C]] + } + + /** Joins an `Either` through `Left`. + * + * This method requires that the left side of this `Either` is itself an + * `Either` type. That is, this must be some type like: {{{ + * Either[Either[C, B], B] + * }}} (which respects the type parameter bounds, shown below.) + * + * If this instance is a `Left[Either[C, B]]` then the contained `Either[C, B]` + * will be returned, otherwise this value will be returned unmodified. + * + * {{{ + * Left[Either[Int, String], String](Right("flower")).joinLeft // Result: Right("flower") + * Left[Either[Int, String], String](Left(12)).joinLeft // Result: Left(12) + * Right[Either[Int, String], String]("daisy").joinLeft // Result: Right("daisy") + * }}} + * + * This method, and `joinRight`, are analogous to `Option#flatten`. + */ + def joinLeft[A1 >: A, B1 >: B, C](implicit ev: A1 <:< Either[C, B1]): Either[C, B1] = this match { + case Left(a) => a + case _ => this.asInstanceOf[Either[C, B1]] + } + + /** Executes the given side-effecting function if this is a `Right`. + * + * {{{ + * Right(12).foreach(println) // prints "12" + * Left(12).foreach(println) // doesn't print + * }}} + * @param f The side-effecting function to execute. + */ + def foreach[U](f: B => U): Unit = this match { + case Right(b) => f(b) + case _ => + } + + /** Returns the value from this `Right` or the given argument if this is a `Left`. + * + * {{{ + * Right(12).getOrElse(17) // 12 + * Left(12).getOrElse(17) // 17 + * }}} + */ + def getOrElse[B1 >: B](or: => B1): B1 = this match { + case Right(b) => b + case _ => or + } + + /** Returns this `Right` or the given argument if this is a `Left`. + * + * {{{ + * Right(1) orElse Left(2) // Right(1) + * Left(1) orElse Left(2) // Left(2) + * Left(1) orElse Left(2) orElse Right(3) // Right(3) + * }}} + */ + def orElse[A1 >: A, B1 >: B](or: => Either[A1, B1]): Either[A1, B1] = this match { + case Right(_) => this + case _ => or + } + + /** Returns `true` if this is a `Right` and its value is equal to `elem` (as determined by `==`), + * returns `false` otherwise. + * + * {{{ + * // Returns true because value of Right is "something" which equals "something". + * Right("something") contains "something" + * + * // Returns false because value of Right is "something" which does not equal "anything". + * Right("something") contains "anything" + * + * // Returns false because it's not a Right value. + * Left("something") contains "something" + * }}} + * + * @param elem the element to test. + * @return `true` if this is a `Right` value equal to `elem`. + */ + final def contains[B1 >: B](elem: B1): Boolean = this match { + case Right(b) => b == elem + case _ => false + } + + /** Returns `true` if `Left` or returns the result of the application of + * the given predicate to the `Right` value. + * + * {{{ + * Right(12).forall(_ > 10) // true + * Right(7).forall(_ > 10) // false + * Left(12).forall(_ => false) // true + * }}} + */ + def forall(f: B => Boolean): Boolean = this match { + case Right(b) => f(b) + case _ => true + } + + /** Returns `false` if `Left` or returns the result of the application of + * the given predicate to the `Right` value. + * + * {{{ + * Right(12).exists(_ > 10) // true + * Right(7).exists(_ > 10) // false + * Left(12).exists(_ => true) // false + * }}} + */ + def exists(p: B => Boolean): Boolean = this match { + case Right(b) => p(b) + case _ => false + } + + /** Binds the given function across `Right`. + * + * @param f The function to bind across `Right`. + */ + def flatMap[A1 >: A, B1](f: B => Either[A1, B1]): Either[A1, B1] = this match { + case Right(b) => f(b) + case _ => this.asInstanceOf[Either[A1, B1]] + } + + + /** Returns the right value if this is right + * or this value if this is left + * + * @example {{{ + * val l: Either[String, Either[String, Int]] = Left("pancake") + * val rl: Either[String, Either[String, Int]] = Right(Left("flounder")) + * val rr: Either[String, Either[String, Int]] = Right(Right(7)) + * + * l.flatten //Either[String, Int]: Left("pancake") + * rl.flatten //Either[String, Int]: Left("flounder") + * rr.flatten //Either[String, Int]: Right(7) + * }}} + * + * Equivalent to `flatMap(id => id)` + */ + def flatten[A1 >: A, B1](implicit ev: B <:< Either[A1, B1]): Either[A1, B1] = flatMap(ev) + + /** The given function is applied if this is a `Right`. + * + * {{{ + * Right(12).map(x => "flower") // Result: Right("flower") + * Left(12).map(x => "flower") // Result: Left(12) + * }}} + */ + def map[B1](f: B => B1): Either[A, B1] = this match { + case Right(b) => Right(f(b)) + case _ => this.asInstanceOf[Either[A, B1]] + } + + /** Returns `Right` with the existing value of `Right` if this is a `Right` + * and the given predicate `p` holds for the right value, + * or `Left(zero)` if this is a `Right` and the given predicate `p` does not hold for the right value, + * or `Left` with the existing value of `Left` if this is a `Left`. + * + * {{{ + * Right(12).filterOrElse(_ > 10, -1) // Right(12) + * Right(7).filterOrElse(_ > 10, -1) // Left(-1) + * Left(7).filterOrElse(_ => false, -1) // Left(7) + * }}} + */ + def filterOrElse[A1 >: A](p: B => Boolean, zero: => A1): Either[A1, B] = this match { + case Right(b) if !p(b) => Left(zero) + case _ => this + } + + /** Returns a `Seq` containing the `Right` value if + * it exists or an empty `Seq` if this is a `Left`. + * + * {{{ + * Right(12).toSeq // Seq(12) + * Left(12).toSeq // Seq() + * }}} + */ + def toSeq: collection.immutable.Seq[B] = this match { + case Right(b) => collection.immutable.Seq(b) + case _ => collection.immutable.Seq.empty + } + + /** Returns a `Some` containing the `Right` value + * if it exists or a `None` if this is a `Left`. + * + * {{{ + * Right(12).toOption // Some(12) + * Left(12).toOption // None + * }}} + */ + def toOption: Option[B] = this match { + case Right(b) => Some(b) + case _ => None + } + + def toTry(implicit ev: A <:< Throwable): Try[B] = this match { + case Right(b) => Success(b) + case Left(a) => Failure(a) + } + + /** Returns `true` if this is a `Left`, `false` otherwise. + * + * {{{ + * Left("tulip").isLeft // true + * Right("venus fly-trap").isLeft // false + * }}} + */ + def isLeft: Boolean + + /** Returns `true` if this is a `Right`, `false` otherwise. + * + * {{{ + * Left("tulip").isRight // false + * Right("venus fly-trap").isRight // true + * }}} + */ + def isRight: Boolean +} + +/** The left side of the disjoint union, as opposed to the [[scala.util.Right]] side. + */ +final case class Left[+A, +B](value: A) extends Either[A, B] { + def isLeft = true + def isRight = false + + /** + * Upcasts this `Left[A, B]` to `Either[A, B1]` + * {{{ + * Left(1) // Either[Int, Nothing] + * Left(1).withRight[String] // Either[Int, String] + * }}} + */ + def withRight[B1 >: B]: Either[A, B1] = this + +} + +/** The right side of the disjoint union, as opposed to the [[scala.util.Left]] side. + */ +final case class Right[+A, +B](value: B) extends Either[A, B] { + def isLeft = false + def isRight = true + + /** + * Upcasts this `Right[A, B]` to `Either[A1, B]` + * {{{ + * Right("x") // Either[Nothing, String] + * Right("x").withLeft[Int] // Either[Int, String] + * }}} + */ + def withLeft[A1 >: A]: Either[A1, B] = this + +} + +object Either { + + /** If the condition is satisfied, return the given `B` in `Right`, + * otherwise, return the given `A` in `Left`. + * + * {{{ + * val userInput: String = readLine() + * Either.cond( + * userInput.forall(_.isDigit) && userInput.size == 10, + * PhoneNumber(userInput), + * s"The input (\$userInput) does not look like a phone number" + * }}} + */ + def cond[A, B](test: Boolean, right: => B, left: => A): Either[A, B] = + if (test) Right(right) else Left(left) + + /** Allows use of a `merge` method to extract values from Either instances + * regardless of whether they are Left or Right. + * + * {{{ + * val l = Left(List(1)): Either[List[Int], Vector[Int]] + * val r = Right(Vector(1)): Either[List[Int], Vector[Int]] + * l.merge: Seq[Int] // List(1) + * r.merge: Seq[Int] // Vector(1) + * }}} + */ + implicit class MergeableEither[A](private val x: Either[A, A]) extends AnyVal { + def merge: A = x match { + case Right(a) => a + case Left(a) => a + } + } + + /** Projects an `Either` into a `Left`. + * + * @see [[scala.util.Either#left]] + */ + final case class LeftProjection[+A, +B](e: Either[A, B]) { + /** Returns the value from this `Left` or throws `NoSuchElementException` + * if this is a `Right`. + * + * {{{ + * Left(12).left.get // 12 + * Right(12).left.get // NoSuchElementException + * }}} + * + * @throws NoSuchElementException if the projection is [[scala.util.Right]] + */ + @deprecated("use `Either.swap.getOrElse` instead", "2.13.0") + def get: A = e match { + case Left(a) => a + case _ => throw new NoSuchElementException("Either.left.get on Right") + } + + /** Executes the given side-effecting function if this is a `Left`. + * + * {{{ + * Left(12).left.foreach(x => println(x)) // prints "12" + * Right(12).left.foreach(x => println(x)) // doesn't print + * }}} + * @param f The side-effecting function to execute. + */ + def foreach[U](f: A => U): Unit = e match { + case Left(a) => f(a) + case _ => () + } + + /** Returns the value from this `Left` or the given argument if this is a `Right`. + * + * {{{ + * Left(12).left.getOrElse(17) // 12 + * Right(12).left.getOrElse(17) // 17 + * }}} + */ + def getOrElse[A1 >: A](or: => A1): A1 = e match { + case Left(a) => a + case _ => or + } + + /** Returns `true` if `Right` or returns the result of the application of + * the given function to the `Left` value. + * + * {{{ + * Left(12).left.forall(_ > 10) // true + * Left(7).left.forall(_ > 10) // false + * Right(12).left.forall(_ > 10) // true + * }}} + */ + def forall(p: A => Boolean): Boolean = e match { + case Left(a) => p(a) + case _ => true + } + + /** Returns `false` if `Right` or returns the result of the application of + * the given function to the `Left` value. + * + * {{{ + * Left(12).left.exists(_ > 10) // true + * Left(7).left.exists(_ > 10) // false + * Right(12).left.exists(_ > 10) // false + * }}} + */ + def exists(p: A => Boolean): Boolean = e match { + case Left(a) => p(a) + case _ => false + } + + /** Binds the given function across `Left`. + * + * {{{ + * Left(12).left.flatMap(x => Left("scala")) // Left("scala") + * Right(12).left.flatMap(x => Left("scala")) // Right(12) + * }}} + * @param f The function to bind across `Left`. + */ + def flatMap[A1, B1 >: B](f: A => Either[A1, B1]): Either[A1, B1] = e match { + case Left(a) => f(a) + case _ => e.asInstanceOf[Either[A1, B1]] + } + + /** Maps the function argument through `Left`. + * + * {{{ + * Left(12).left.map(_ + 2) // Left(14) + * Right[Int, Int](12).left.map(_ + 2) // Right(12) + * }}} + */ + def map[A1](f: A => A1): Either[A1, B] = e match { + case Left(a) => Left(f(a)) + case _ => e.asInstanceOf[Either[A1, B]] + } + + /** Returns `None` if this is a `Right` or if the given predicate + * `p` does not hold for the left value, otherwise, returns a `Left`. + * + * {{{ + * Left(12).left.filter(_ > 10) // Some(Left(12)) + * Left(7).left.filter(_ > 10) // None + * Right(12).left.filter(_ > 10) // None + * }}} + */ + @deprecated("Use `filterToOption`, which more accurately reflects the return type", "2.13.0") + def filter[B1](p: A => Boolean): Option[Either[A, B1]] = e match { + case x @ Left(a) if p(a) => Some(x.asInstanceOf[Either[A, B1]]) + case _ => None + } + + /** Returns `None` if this is a `Right` or if the given predicate + * `p` does not hold for the left value, otherwise, returns a `Left`. + * + * {{{ + * Left(12).left.filterToOption(_ > 10) // Some(Left(12)) + * Left(7).left.filterToOption(_ > 10) // None + * Right(12).left.filterToOption(_ > 10) // None + * }}} + */ + def filterToOption[B1](p: A => Boolean): Option[Either[A, B1]] = e match { + case x @ Left(a) if p(a) => Some(x.asInstanceOf[Either[A, B1]]) + case _ => None + } + + /** Returns a `Seq` containing the `Left` value if it exists or an empty + * `Seq` if this is a `Right`. + * + * {{{ + * Left(12).left.toSeq // Seq(12) + * Right(12).left.toSeq // Seq() + * }}} + */ + def toSeq: Seq[A] = e match { + case Left(a) => Seq(a) + case _ => Seq.empty + } + + /** Returns a `Some` containing the `Left` value if it exists or a + * `None` if this is a `Right`. + * + * {{{ + * Left(12).left.toOption // Some(12) + * Right(12).left.toOption // None + * }}} + */ + def toOption: Option[A] = e match { + case Left(a) => Some(a) + case _ => None + } + } + + /** Projects an `Either` into a `Right`. + * + * Because `Either` is already right-biased, this class is not normally needed. + * (It is retained in the library for now for easy cross-compilation between Scala + * 2.11 and 2.12.) + */ + @deprecated("Either is now right-biased, calls to `right` should be removed", "2.13.0") + final case class RightProjection[+A, +B](e: Either[A, B]) { + + /** Returns the value from this `Right` or throws + * `NoSuchElementException` if this is a `Left`. + * + * {{{ + * Right(12).right.get // 12 + * Left(12).right.get // NoSuchElementException + * }}} + * + * @throws NoSuchElementException if the projection is `Left`. + */ + @deprecated("Use `Either.toOption.get` instead", "2.13.0") + def get: B = e match { + case Right(b) => b + case _ => throw new NoSuchElementException("Either.right.get on Left") + } + + /** Executes the given side-effecting function if this is a `Right`. + * + * {{{ + * Right(12).right.foreach(x => println(x)) // prints "12" + * Left(12).right.foreach(x => println(x)) // doesn't print + * }}} + * @param f The side-effecting function to execute. + */ + def foreach[U](f: B => U): Unit = e match { + case Right(b) => f(b) + case _ => () + } + + /** Returns the value from this `Right` or the given argument if this is a `Left`. + * + * {{{ + * Right(12).right.getOrElse(17) // 12 + * Left(12).right.getOrElse(17) // 17 + * }}} + */ + def getOrElse[B1 >: B](or: => B1): B1 = e match { + case Right(b) => b + case _ => or + } + + /** Returns `true` if `Left` or returns the result of the application of + * the given function to the `Right` value. + * + * {{{ + * Right(12).right.forall(_ > 10) // true + * Right(7).right.forall(_ > 10) // false + * Left(12).right.forall(_ > 10) // true + * }}} + */ + def forall(f: B => Boolean): Boolean = e match { + case Right(b) => f(b) + case _ => true + } + + /** Returns `false` if `Left` or returns the result of the application of + * the given function to the `Right` value. + * + * {{{ + * Right(12).right.exists(_ > 10) // true + * Right(7).right.exists(_ > 10) // false + * Left(12).right.exists(_ > 10) // false + * }}} + */ + def exists(p: B => Boolean): Boolean = e match { + case Right(b) => p(b) + case _ => false + } + + /** Binds the given function across `Right`. + * + * @param f The function to bind across `Right`. + */ + def flatMap[A1 >: A, B1](f: B => Either[A1, B1]): Either[A1, B1] = e match { + case Right(b) => f(b) + case _ => e.asInstanceOf[Either[A1, B1]] + } + + /** The given function is applied if this is a `Right`. + * + * {{{ + * Right(12).right.map(x => "flower") // Result: Right("flower") + * Left(12).right.map(x => "flower") // Result: Left(12) + * }}} + */ + def map[B1](f: B => B1): Either[A, B1] = e match { + case Right(b) => Right(f(b)) + case _ => e.asInstanceOf[Either[A, B1]] + } + + /** Returns `None` if this is a `Left` or if the + * given predicate `p` does not hold for the right value, + * otherwise, returns a `Right`. + * + * {{{ + * Right(12).right.filter(_ > 10) // Some(Right(12)) + * Right(7).right.filter(_ > 10) // None + * Left(12).right.filter(_ > 10) // None + * }}} + */ + @deprecated("Use `filterToOption`, which more accurately reflects the return type", "2.13.0") + def filter[A1](p: B => Boolean): Option[Either[A1, B]] = e match { + case Right(b) if p(b) => Some(Right(b)) + case _ => None + } + + /** Returns `None` if this is a `Left` or if the + * given predicate `p` does not hold for the right value, + * otherwise, returns a `Right`. + * + * {{{ + * Right(12).right.filterToOption(_ > 10) // Some(Right(12)) + * Right(7).right.filterToOption(_ > 10) // None + * Left(12).right.filterToOption(_ > 10) // None + * }}} + */ + def filterToOption[A1](p: B => Boolean): Option[Either[A1, B]] = e match { + case r @ Right(b) if p(b) => Some(r.asInstanceOf[Either[A1, B]]) + case _ => None + } + + /** Returns a `Seq` containing the `Right` value if + * it exists or an empty `Seq` if this is a `Left`. + * + * {{{ + * Right(12).right.toSeq // Seq(12) + * Left(12).right.toSeq // Seq() + * }}} + */ + def toSeq: Seq[B] = e match { + case Right(b) => Seq(b) + case _ => Seq.empty + } + + /** Returns a `Some` containing the `Right` value + * if it exists or a `None` if this is a `Left`. + * + * {{{ + * Right(12).right.toOption // Some(12) + * Left(12).right.toOption // None + * }}} + */ + def toOption: Option[B] = e match { + case Right(b) => Some(b) + case _ => None + } + } +} diff --git a/library/src/scala/util/FromDigits.scala b/library/src/scala/util/FromDigits.scala index cb73782829ff..44532649868d 100644 --- a/library/src/scala/util/FromDigits.scala +++ b/library/src/scala/util/FromDigits.scala @@ -135,7 +135,7 @@ object FromDigits { case ex: NumberFormatException => throw MalformedNumber() } if (x.isInfinite) throw NumberTooLarge() - if (x == 0.0f && !zeroFloat.pattern.matcher(digits).nn.matches) throw NumberTooSmall() + if (x == 0.0f && !zeroFloat.pattern.matcher(digits).matches) throw NumberTooSmall() x } diff --git a/library/src/scala/util/Properties.scala b/library/src/scala/util/Properties.scala new file mode 100644 index 000000000000..364a66caa1e8 --- /dev/null +++ b/library/src/scala/util/Properties.scala @@ -0,0 +1,233 @@ +/* + * 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 util + +import scala.language.`2.13` +import java.io.{IOException, PrintWriter} +import java.util.jar.Attributes.{Name => AttributeName} +import scala.annotation.tailrec + +/** Loads `library.properties` from the jar. */ +object Properties extends PropertiesTrait { + protected def propCategory = "library" + protected def pickJarBasedOn: Class[Option[_]] = classOf[Option[_]] + + /** Scala manifest attributes. + */ + val ScalaCompilerVersion = new AttributeName("Scala-Compiler-Version") +} + +private[scala] trait PropertiesTrait { + protected def propCategory: String // specializes the remainder of the values + protected def pickJarBasedOn: Class[_] // props file comes from jar containing this + + /** The name of the properties file */ + protected val propFilename = "/" + propCategory + ".properties" + + /** The loaded properties */ + protected lazy val scalaProps: java.util.Properties = { + val props = new java.util.Properties + val stream = pickJarBasedOn getResourceAsStream propFilename + if (stream ne null) + quietlyDispose(props load stream, stream.close) + + props + } + + private def quietlyDispose(action: => Unit, disposal: => Unit) = + try { action } + finally { + try { disposal } + catch { case _: IOException => } + } + + def propIsSet(name: String) = System.getProperty(name) != null + def propIsSetTo(name: String, value: String) = propOrNull(name) == value + def propOrElse(name: String, alt: => String) = Option(System.getProperty(name)).getOrElse(alt) + def propOrEmpty(name: String) = propOrElse(name, "") + def propOrNull(name: String) = propOrElse(name, null) + def propOrNone(name: String) = Option(propOrNull(name)) + def propOrFalse(name: String) = propOrNone(name) exists (x => List("yes", "on", "true") contains x.toLowerCase) + def setProp(name: String, value: String) = System.setProperty(name, value) + def clearProp(name: String) = System.clearProperty(name) + + def envOrElse(name: String, alt: => String) = Option(System getenv name) getOrElse alt + def envOrNone(name: String) = Option(System getenv name) + + def envOrSome(name: String, alt: => Option[String]) = envOrNone(name) orElse alt + + // for values based on propFilename, falling back to System properties + def scalaPropOrElse(name: String, alt: => String): String = scalaPropOrNone(name).getOrElse(alt) + def scalaPropOrEmpty(name: String): String = scalaPropOrElse(name, "") + def scalaPropOrNone(name: String): Option[String] = Option(scalaProps.getProperty(name)).orElse(propOrNone("scala." + name)) + + /** The version of the Scala runtime, if this is not a snapshot. + */ + val releaseVersion = scalaPropOrNone("maven.version.number").filterNot(_.endsWith("-SNAPSHOT")) + + /** The version of the Scala runtime, if this is a snapshot. + */ + val developmentVersion = scalaPropOrNone("maven.version.number").filter(_.endsWith("-SNAPSHOT")).flatMap(_ => scalaPropOrNone("version.number")) + + /** The version of the Scala runtime, or the empty string if unknown. + * + * Note that the version of the Scala library need not correlate with the version of the Scala compiler + * used to emit either the library or user code. + * + * For example, Scala 3.0 and 3.1 use the Scala 2.13 library, which is reflected in this version string. + * For the Dotty version, see `dotty.tools.dotc.config.Properties.versionNumberString`. + */ + def versionNumberString = scalaPropOrEmpty("version.number") + + /** A verbose alternative to [[versionNumberString]]. + */ + val versionString = s"version ${scalaPropOrElse("version.number", "(unknown)")}" + val copyrightString = scalaPropOrElse("copyright.string", "Copyright 2002-2025, LAMP/EPFL and Lightbend, Inc. dba Akka") + + /** This is the encoding to use reading in source files, overridden with -encoding. + * Note that it uses "prop" i.e. looks in the scala jar, not the system properties. + */ + def sourceEncoding = scalaPropOrElse("file.encoding", "UTF-8") + def sourceReader = scalaPropOrElse("source.reader", "scala.tools.nsc.io.SourceReader") + + /** This is the default text encoding, overridden (unreliably) with + * `JAVA_OPTS="-Dfile.encoding=Foo"` + */ + def encodingString = propOrElse("file.encoding", "UTF-8") + + /** The default end of line character. + */ + def lineSeparator = System.lineSeparator() + + /* Various well-known properties. */ + def javaClassPath = propOrEmpty("java.class.path") + def javaHome = propOrEmpty("java.home") + def javaVendor = propOrEmpty("java.vendor") + def javaVersion = propOrEmpty("java.version") + def javaVmInfo = propOrEmpty("java.vm.info") + def javaVmName = propOrEmpty("java.vm.name") + def javaVmVendor = propOrEmpty("java.vm.vendor") + def javaVmVersion = propOrEmpty("java.vm.version") + def javaSpecVersion = propOrEmpty("java.specification.version") + def javaSpecVendor = propOrEmpty("java.specification.vendor") + def javaSpecName = propOrEmpty("java.specification.name") + def osName = propOrEmpty("os.name") + def scalaHome = propOrEmpty("scala.home") + def tmpDir = propOrEmpty("java.io.tmpdir") + def userDir = propOrEmpty("user.dir") + def userHome = propOrEmpty("user.home") + def userName = propOrEmpty("user.name") + + /* Some derived values. */ + /** Returns `true` iff the underlying operating system is a version of Microsoft Windows. */ + lazy val isWin = osName.startsWith("Windows") + // See https://mail.openjdk.java.net/pipermail/macosx-port-dev/2012-November/005148.html for + // the reason why we don't follow developer.apple.com/library/mac/#technotes/tn2002/tn2110. + /** Returns `true` iff the underlying operating system is a version of Apple Mac OSX. */ + lazy val isMac = osName.startsWith("Mac OS X") + /** Returns `true` iff the underlying operating system is a Linux distribution. */ + lazy val isLinux = osName.startsWith("Linux") + + /* Some runtime values. */ + private[scala] lazy val isAvian = javaVmName.contains("Avian") + + private[scala] def coloredOutputEnabled: Boolean = propOrElse("scala.color", "auto") match { + case "auto" => consoleIsTerminal + case s => "" == s || "true".equalsIgnoreCase(s) + } + + /** System.console.isTerminal, or just check for null console on JDK < 22 */ + private[scala] lazy val consoleIsTerminal: Boolean = { + import language.reflectiveCalls + val console = System.console + def isTerminal: Boolean = + try console.asInstanceOf[{ def isTerminal(): Boolean }].isTerminal() + catch { case _: NoSuchMethodException => false } + console != null && (!isJavaAtLeast("22") || isTerminal) + } + + // This is looking for javac, tools.jar, etc. + // Tries JDK_HOME first, then the more common but likely jre JAVA_HOME, + // and finally the system property based javaHome. + def jdkHome = envOrElse("JDK_HOME", envOrElse("JAVA_HOME", javaHome)) + + private[scala] def versionFor(command: String) = s"Scala $command $versionString -- $copyrightString" + + def versionMsg = versionFor(propCategory) + def scalaCmd = if (isWin) "scala.bat" else "scala" + def scalacCmd = if (isWin) "scalac.bat" else "scalac" + + /** Compares the given specification version to the specification version of the platform. + * + * @param version a specification version number (legacy forms acceptable) + * @return `true` if the specification version of the current runtime + * is equal to or higher than the version denoted by the given string. + * @throws NumberFormatException if the given string is not a version string + * + * @example {{{ + * // In this example, the runtime's Java specification is assumed to be at version 8. + * isJavaAtLeast("1.8") // true + * isJavaAtLeast("8") // true + * isJavaAtLeast("9") // false + * isJavaAtLeast("9.1") // false + * isJavaAtLeast("1.9") // throws + * }}} + */ + def isJavaAtLeast(version: String): Boolean = { + def versionOf(s: String, depth: Int): (Int, String) = + s.indexOf('.') match { + case 0 => + (-2, s.substring(1)) + case 1 if depth == 0 && s.charAt(0) == '1' => + val r0 = s.substring(2) + val (v, r) = versionOf(r0, 1) + val n = if (v > 8 || r0.isEmpty) -2 else v // accept 1.8, not 1.9 or 1. + (n, r) + case -1 => + val n = if (!s.isEmpty) s.toInt else if (depth == 0) -2 else 0 + (n, "") + case i => + val r = s.substring(i + 1) + val n = if (depth < 2 && r.isEmpty) -2 else s.substring(0, i).toInt + (n, r) + } + @tailrec + def compareVersions(s: String, v: String, depth: Int): Int = { + if (depth >= 3) 0 + else { + val (sn, srest) = versionOf(s, depth) + val (vn, vrest) = versionOf(v, depth) + if (vn < 0) -2 + else if (sn < vn) -1 + else if (sn > vn) 1 + else compareVersions(srest, vrest, depth + 1) + } + } + compareVersions(javaSpecVersion, version, 0) match { + case -2 => throw new NumberFormatException(s"Not a version: $version") + case i => i >= 0 + } + } + + /** Compares the given specification version to the major version of the platform. + * @param version a specification major version number + */ + def isJavaAtLeast(version: Int): Boolean = isJavaAtLeast(math.max(version, 0).toString) + + // provide a main method so version info can be obtained by running this + def main(args: Array[String]): Unit = { + val writer = new PrintWriter(Console.err, true) + writer println versionMsg + } +} diff --git a/library/src/scala/util/Random.scala b/library/src/scala/util/Random.scala new file mode 100644 index 000000000000..e79bd2f5dfcc --- /dev/null +++ b/library/src/scala/util/Random.scala @@ -0,0 +1,265 @@ +/* + * 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 util + +import scala.language.`2.13` +import scala.annotation.{migration, tailrec} +import scala.collection.mutable.ArrayBuffer +import scala.collection.BuildFrom +import scala.collection.immutable.LazyList +import scala.language.implicitConversions + +class Random(val self: java.util.Random) extends AnyRef with Serializable { + /** Creates a new random number generator using a single long seed. */ + def this(seed: Long) = this(new java.util.Random(seed)) + + /** Creates a new random number generator using a single integer seed. */ + def this(seed: Int) = this(seed.toLong) + + /** Creates a new random number generator. */ + def this() = this(new java.util.Random()) + + /** Returns the next pseudorandom, uniformly distributed boolean value + * from this random number generator's sequence. + */ + def nextBoolean(): Boolean = self.nextBoolean() + + /** Generates random bytes and places them into a user-supplied byte + * array. + */ + def nextBytes(bytes: Array[Byte]): Unit = { self.nextBytes(bytes) } + + /** Generates `n` random bytes and returns them in a new array. */ + def nextBytes(n: Int): Array[Byte] = { + val bytes = new Array[Byte](0 max n) + self.nextBytes(bytes) + bytes + } + + /** Returns the next pseudorandom, uniformly distributed double value + * between 0.0 and 1.0 from this random number generator's sequence. + */ + def nextDouble(): Double = self.nextDouble() + + /** Returns the next pseudorandom, uniformly distributed double value + * between min (inclusive) and max (exclusive) from this random number generator's sequence. + */ + def between(minInclusive: Double, maxExclusive: Double): Double = { + require(minInclusive < maxExclusive, "Invalid bounds") + + val next = nextDouble() * (maxExclusive - minInclusive) + minInclusive + if (next < maxExclusive) next + else Math.nextAfter(maxExclusive, Double.NegativeInfinity) + } + + /** Returns the next pseudorandom, uniformly distributed float value + * between 0.0 and 1.0 from this random number generator's sequence. + */ + def nextFloat(): Float = self.nextFloat() + + /** Returns the next pseudorandom, uniformly distributed float value + * between min (inclusive) and max (exclusive) from this random number generator's sequence. + */ + def between(minInclusive: Float, maxExclusive: Float): Float = { + require(minInclusive < maxExclusive, "Invalid bounds") + + val next = nextFloat() * (maxExclusive - minInclusive) + minInclusive + if (next < maxExclusive) next + else Math.nextAfter(maxExclusive, Float.NegativeInfinity) + } + + /** Returns the next pseudorandom, Gaussian ("normally") distributed + * double value with mean 0.0 and standard deviation 1.0 from this + * random number generator's sequence. + */ + def nextGaussian(): Double = self.nextGaussian() + + /** Returns the next pseudorandom, uniformly distributed int value + * from this random number generator's sequence. + */ + def nextInt(): Int = self.nextInt() + + /** Returns a pseudorandom, uniformly distributed int value between 0 + * (inclusive) and the specified value (exclusive), drawn from this + * random number generator's sequence. + */ + def nextInt(n: Int): Int = self.nextInt(n) + + /** Returns a pseudorandom, uniformly distributed int value between min + * (inclusive) and the specified value max (exclusive), drawn from this + * random number generator's sequence. + */ + def between(minInclusive: Int, maxExclusive: Int): Int = { + require(minInclusive < maxExclusive, "Invalid bounds") + + val difference = maxExclusive - minInclusive + if (difference >= 0) { + nextInt(difference) + minInclusive + } else { + /* The interval size here is greater than Int.MaxValue, + * so the loop will exit with a probability of at least 1/2. + */ + @tailrec + def loop(): Int = { + val n = nextInt() + if (n >= minInclusive && n < maxExclusive) n + else loop() + } + loop() + } + } + + /** Returns the next pseudorandom, uniformly distributed long value + * from this random number generator's sequence. + */ + def nextLong(): Long = self.nextLong() + + /** Returns a pseudorandom, uniformly distributed long value between 0 + * (inclusive) and the specified value (exclusive), drawn from this + * random number generator's sequence. + */ + def nextLong(n: Long): Long = { + require(n > 0, "n must be positive") + + /* + * Divide n by two until small enough for nextInt. On each + * iteration (at most 31 of them but usually much less), + * randomly choose both whether to include high bit in result + * (offset) and whether to continue with the lower vs upper + * half (which makes a difference only if odd). + */ + + var offset = 0L + var _n = n + + while (_n >= Integer.MAX_VALUE) { + val bits = nextInt(2) + val halfn = _n >>> 1 + val nextn = + if ((bits & 2) == 0) halfn + else _n - halfn + if ((bits & 1) == 0) + offset += _n - nextn + _n = nextn + } + offset + nextInt(_n.toInt) + } + + /** Returns a pseudorandom, uniformly distributed long value between min + * (inclusive) and the specified value max (exclusive), drawn from this + * random number generator's sequence. + */ + def between(minInclusive: Long, maxExclusive: Long): Long = { + require(minInclusive < maxExclusive, "Invalid bounds") + + val difference = maxExclusive - minInclusive + if (difference >= 0) { + nextLong(difference) + minInclusive + } else { + /* The interval size here is greater than Long.MaxValue, + * so the loop will exit with a probability of at least 1/2. + */ + @tailrec + def loop(): Long = { + val n = nextLong() + if (n >= minInclusive && n < maxExclusive) n + else loop() + } + loop() + } + } + + /** Returns a pseudorandomly generated String. This routine does + * not take any measures to preserve the randomness of the distribution + * in the face of factors like unicode's variable-length encoding, + * so please don't use this for anything important. It's primarily + * intended for generating test data. + * + * @param length the desired length of the String + * @return the String + */ + def nextString(length: Int): String = { + def safeChar(): Char = { + val surrogateStart: Int = 0xD800 + val res = nextInt(surrogateStart - 1) + 1 + res.toChar + } + if (length <= 0) { + "" + } else { + val arr = new Array[Char](length) + var i = 0 + while (i < length) { + arr(i) = safeChar() + i += 1 + } + new String(arr) + } + } + + /** Returns the next pseudorandom, uniformly distributed value + * from the ASCII range 33-126. + */ + def nextPrintableChar(): Char = { + val low = 33 + val high = 127 + (self.nextInt(high - low) + low).toChar + } + + def setSeed(seed: Long): Unit = { self.setSeed(seed) } + + /** Returns a new collection of the same type in a randomly chosen order. + * + * @return the shuffled collection + */ + def shuffle[T, C](xs: IterableOnce[T])(implicit bf: BuildFrom[xs.type, T, C]): C = { + val buf = new ArrayBuffer[T] ++= xs + + def swap(i1: Int, i2: Int): Unit = { + val tmp = buf(i1) + buf(i1) = buf(i2) + buf(i2) = tmp + } + + for (n <- buf.length to 2 by -1) { + val k = nextInt(n) + swap(n - 1, k) + } + + (bf.newBuilder(xs) ++= buf).result() + } + + /** Returns a LazyList of pseudorandomly chosen alphanumeric characters, + * equally chosen from A-Z, a-z, and 0-9. + */ + @migration("`alphanumeric` returns a LazyList instead of a Stream", "2.13.0") + def alphanumeric: LazyList[Char] = { + def nextAlphaNum: Char = { + val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + chars charAt (self nextInt chars.length) + } + + LazyList continually nextAlphaNum + } + +} + +/** The object `Random` offers a default implementation + * of scala.util.Random and random-related convenience methods. + */ +object Random extends Random { + + implicit def javaRandomToRandom(r: java.util.Random): Random = new Random(r) + +} diff --git a/library/src/scala/util/Sorting.scala b/library/src/scala/util/Sorting.scala new file mode 100644 index 000000000000..b1083664b336 --- /dev/null +++ b/library/src/scala/util/Sorting.scala @@ -0,0 +1,303 @@ +/* + * 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 util + +import scala.language.`2.13` +import scala.reflect.ClassTag +import scala.math.Ordering + +/** The `Sorting` object provides convenience wrappers for `java.util.Arrays.sort`. + * Methods that defer to `java.util.Arrays.sort` say that they do or under what + * conditions that they do. + * + * `Sorting` also implements a general-purpose quicksort and stable (merge) sort + * for those cases where `java.util.Arrays.sort` could only be used at the cost + * of a large memory penalty. If performance rather than memory usage is the + * primary concern, one may wish to find alternate strategies to use + * `java.util.Arrays.sort` directly e.g. by boxing primitives to use + * a custom ordering on them. + * + * `Sorting` provides methods where you can provide a comparison function, or + * can request a sort of items that are [[scala.math.Ordered]] or that + * otherwise have an implicit or explicit [[scala.math.Ordering]]. + * + * Note also that high-performance non-default sorts for numeric types + * are not provided. If this is required, it is advisable to investigate + * other libraries that cover this use case. + */ +object Sorting { + /** Sort an array of Doubles using `java.util.Arrays.sort`. */ + def quickSort(a: Array[Double]): Unit = java.util.Arrays.sort(a) + + /** Sort an array of Ints using `java.util.Arrays.sort`. */ + def quickSort(a: Array[Int]): Unit = java.util.Arrays.sort(a) + + /** Sort an array of Floats using `java.util.Arrays.sort`. */ + def quickSort(a: Array[Float]): Unit = java.util.Arrays.sort(a) + + private final val qsortThreshold = 16 + + /** Sort array `a` with quicksort, using the Ordering on its elements. + * This algorithm sorts in place, so no additional memory is used aside from + * what might be required to box individual elements during comparison. + */ + def quickSort[K: Ordering](a: Array[K]): Unit = { + // Must have iN >= i0 or math will fail. Also, i0 >= 0. + def inner(a: Array[K], i0: Int, iN: Int, ord: Ordering[K]): Unit = { + if (iN - i0 < qsortThreshold) insertionSort(a, i0, iN, ord) + else { + val iK = (i0 + iN) >>> 1 // Unsigned div by 2 + // Find index of median of first, central, and last elements + var pL = + if (ord.compare(a(i0), a(iN - 1)) <= 0) + if (ord.compare(a(i0), a(iK)) < 0) + if (ord.compare(a(iN - 1), a(iK)) < 0) iN - 1 else iK + else i0 + else + if (ord.compare(a(i0), a(iK)) < 0) i0 + else + if (ord.compare(a(iN - 1), a(iK)) <= 0) iK + else iN - 1 + val pivot = a(pL) + // pL is the start of the pivot block; move it into the middle if needed + if (pL != iK) { a(pL) = a(iK); a(iK) = pivot; pL = iK } + // Elements equal to the pivot will be in range pL until pR + var pR = pL + 1 + // Items known to be less than pivot are below iA (range i0 until iA) + var iA = i0 + // Items known to be greater than pivot are at or above iB (range iB until iN) + var iB = iN + // Scan through everything in the buffer before the pivot(s) + while (pL - iA > 0) { + val current = a(iA) + ord.compare(current, pivot) match { + case 0 => + // Swap current out with pivot block + a(iA) = a(pL - 1) + a(pL - 1) = current + pL -= 1 + case x if x < 0 => + // Already in place. Just update indices. + iA += 1 + case _ if iB > pR => + // Wrong side. There's room on the other side, so swap + a(iA) = a(iB - 1) + a(iB - 1) = current + iB -= 1 + case _ => + // Wrong side and there is no room. Swap by rotating pivot block. + a(iA) = a(pL - 1) + a(pL - 1) = a(pR - 1) + a(pR - 1) = current + pL -= 1 + pR -= 1 + iB -= 1 + } + } + // Get anything remaining in buffer after the pivot(s) + while (iB - pR > 0) { + val current = a(iB - 1) + ord.compare(current, pivot) match { + case 0 => + // Swap current out with pivot block + a(iB - 1) = a(pR) + a(pR) = current + pR += 1 + case x if x > 0 => + // Already in place. Just update indices. + iB -= 1 + case _ => + // Wrong side and we already know there is no room. Swap by rotating pivot block. + a(iB - 1) = a(pR) + a(pR) = a(pL) + a(pL) = current + iA += 1 + pL += 1 + pR += 1 + } + } + // Use tail recursion on large half (Sedgewick's method) so we don't blow up the stack if pivots are poorly chosen + if (iA - i0 < iN - iB) { + inner(a, i0, iA, ord) // True recursion + inner(a, iB, iN, ord) // Should be tail recursion + } + else { + inner(a, iB, iN, ord) // True recursion + inner(a, i0, iA, ord) // Should be tail recursion + } + } + } + inner(a, 0, a.length, implicitly[Ordering[K]]) + } + + private final val mergeThreshold = 32 + + // Ordering[T] might be slow especially for boxed primitives, so use binary search variant of insertion sort + // Caller must pass iN >= i0 or math will fail. Also, i0 >= 0. + private def insertionSort[@specialized T](a: Array[T], i0: Int, iN: Int, ord: Ordering[T]): Unit = { + val n = iN - i0 + if (n < 2) return + if (ord.compare(a(i0), a(i0+1)) > 0) { + val temp = a(i0) + a(i0) = a(i0+1) + a(i0+1) = temp + } + var m = 2 + while (m < n) { + // Speed up already-sorted case by checking last element first + val next = a(i0 + m) + if (ord.compare(next, a(i0+m-1)) < 0) { + var iA = i0 + var iB = i0 + m - 1 + while (iB - iA > 1) { + val ix = (iA + iB) >>> 1 // Use bit shift to get unsigned div by 2 + if (ord.compare(next, a(ix)) < 0) iB = ix + else iA = ix + } + val ix = iA + (if (ord.compare(next, a(iA)) < 0) 0 else 1) + var i = i0 + m + while (i > ix) { + a(i) = a(i-1) + i -= 1 + } + a(ix) = next + } + m += 1 + } + } + + // Caller is required to pass iN >= i0, else math will fail. Also, i0 >= 0. + private def mergeSort[@specialized T: ClassTag](a: Array[T], i0: Int, iN: Int, ord: Ordering[T], scratch: Array[T] = null): Unit = { + if (iN - i0 < mergeThreshold) insertionSort(a, i0, iN, ord) + else { + val iK = (i0 + iN) >>> 1 // Bit shift equivalent to unsigned math, no overflow + val sc = if (scratch eq null) new Array[T](iK - i0) else scratch + mergeSort(a, i0, iK, ord, sc) + mergeSort(a, iK, iN, ord, sc) + mergeSorted(a, i0, iK, iN, ord, sc) + } + } + + // Must have 0 <= i0 < iK < iN + private def mergeSorted[@specialized T](a: Array[T], i0: Int, iK: Int, iN: Int, ord: Ordering[T], scratch: Array[T]): Unit = { + // Check to make sure we're not already in order + if (ord.compare(a(iK-1), a(iK)) > 0) { + var i = i0 + val jN = iK - i0 + var j = 0 + while (i < iK) { + scratch (j) = a(i) + i += 1 + j += 1 + } + var k = i0 + j = 0 + while (i < iN && j < jN) { + if (ord.compare(a(i), scratch(j)) < 0) { a(k) = a(i); i += 1 } + else { a(k) = scratch(j); j += 1 } + k += 1 + } + while (j < jN) { a(k) = scratch(j); j += 1; k += 1 } + // Don't need to finish a(i) because it's already in place, k = i + } + } + + // Why would you even do this? + private def booleanSort(a: Array[Boolean], from: Int, until: Int): Unit = { + var i = from + var n = 0 + while (i < until) { + if (!a(i)) n += 1 + i += 1 + } + i = 0 + while (i < n) { + a(from + i) = false + i += 1 + } + while (from + i < until) { + a(from + i) = true + i += 1 + } + } + + // TODO: add upper bound: T <: AnyRef, propagate to callers below (not binary compatible) + // Maybe also rename all these methods to `sort`. + @inline private def sort[T](a: Array[T], from: Int, until: Int, ord: Ordering[T]): Unit = (a: @unchecked) match { + case _: Array[AnyRef] => + // Note that runtime matches are covariant, so could actually be any Array[T] s.t. T is not primitive (even boxed value classes) + if (a.length > 1 && (ord eq null)) throw new NullPointerException("Ordering") + java.util.Arrays.sort(a, from, until, ord) + case a: Array[Int] => if (ord eq Ordering.Int) java.util.Arrays.sort(a, from, until) else mergeSort[Int](a, from, until, ord) + case a: Array[Double] => mergeSort[Double](a, from, until, ord) // Because not all NaNs are identical, stability is meaningful! + case a: Array[Long] => if (ord eq Ordering.Long) java.util.Arrays.sort(a, from, until) else mergeSort[Long](a, from, until, ord) + case a: Array[Float] => mergeSort[Float](a, from, until, ord) // Because not all NaNs are identical, stability is meaningful! + case a: Array[Char] => if (ord eq Ordering.Char) java.util.Arrays.sort(a, from, until) else mergeSort[Char](a, from, until, ord) + case a: Array[Byte] => if (ord eq Ordering.Byte) java.util.Arrays.sort(a, from, until) else mergeSort[Byte](a, from, until, ord) + case a: Array[Short] => if (ord eq Ordering.Short) java.util.Arrays.sort(a, from, until) else mergeSort[Short](a, from, until, ord) + case a: Array[Boolean] => if (ord eq Ordering.Boolean) booleanSort(a, from, until) else mergeSort[Boolean](a, from, until, ord) + // Array[Unit] is matched as an Array[AnyRef] due to covariance in runtime matching. Not worth catching it as a special case. + case null => throw new NullPointerException + } + + /** Sort array `a` using the Ordering on its elements, preserving the original ordering where possible. + * Uses `java.util.Arrays.sort` unless `K` is a primitive type. This is the same as `stableSort(a, 0, a.length)`. */ + @`inline` def stableSort[K: Ordering](a: Array[K]): Unit = stableSort(a, 0, a.length) + + /** Sort array `a` or a part of it using the Ordering on its elements, preserving the original ordering where possible. + * Uses `java.util.Arrays.sort` unless `K` is a primitive type. + * + * @param a The array to sort + * @param from The first index in the array to sort + * @param until The last index (exclusive) in the array to sort + */ + def stableSort[K: Ordering](a: Array[K], from: Int, until: Int): Unit = sort(a, from, until, Ordering[K]) + + /** Sort array `a` using function `f` that computes the less-than relation for each element. + * Uses `java.util.Arrays.sort` unless `K` is a primitive type. This is the same as `stableSort(a, f, 0, a.length)`. */ + @`inline` def stableSort[K](a: Array[K], f: (K, K) => Boolean): Unit = stableSort(a, f, 0, a.length) + + // TODO: make this fast for primitive K (could be specialized if it didn't go through Ordering) + /** Sort array `a` or a part of it using function `f` that computes the less-than relation for each element. + * Uses `java.util.Arrays.sort` unless `K` is a primitive type. + * + * @param a The array to sort + * @param f A function that computes the less-than relation for each element + * @param from The first index in the array to sort + * @param until The last index (exclusive) in the array to sort + */ + def stableSort[K](a: Array[K], f: (K, K) => Boolean, from: Int, until: Int): Unit = sort(a, from, until, Ordering fromLessThan f) + + /** A sorted Array, using the Ordering for the elements in the sequence `a`. Uses `java.util.Arrays.sort` unless `K` is a primitive type. */ + def stableSort[K: ClassTag: Ordering](a: scala.collection.Seq[K]): Array[K] = { + val ret = a.toArray + sort(ret, 0, ret.length, Ordering[K]) + ret + } + + // TODO: make this fast for primitive K (could be specialized if it didn't go through Ordering) + /** A sorted Array, given a function `f` that computes the less-than relation for each item in the sequence `a`. Uses `java.util.Arrays.sort` unless `K` is a primitive type. */ + def stableSort[K: ClassTag](a: scala.collection.Seq[K], f: (K, K) => Boolean): Array[K] = { + val ret = a.toArray + sort(ret, 0, ret.length, Ordering fromLessThan f) + ret + } + + /** A sorted Array, given an extraction function `f` that returns an ordered key for each item in the sequence `a`. Uses `java.util.Arrays.sort` unless `K` is a primitive type. */ + def stableSort[K: ClassTag, M: Ordering](a: scala.collection.Seq[K], f: K => M): Array[K] = { + val ret = a.toArray + sort(ret, 0, ret.length, Ordering[M] on f) + ret + } +} diff --git a/library/src/scala/util/Try.scala b/library/src/scala/util/Try.scala new file mode 100644 index 000000000000..a5686fab01f5 --- /dev/null +++ b/library/src/scala/util/Try.scala @@ -0,0 +1,291 @@ +/* + * 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 util + +import scala.language.`2.13` +import scala.runtime.Statics +import scala.util.control.NonFatal + +/** + * The `Try` type represents a computation that may fail during evaluation by raising an exception. + * It holds either a successfully computed value or the exception that was thrown. + * This is similar to the [[scala.util.Either]] type, but with different semantics. + * + * Instances of `Try[T]` are an instance of either [[scala.util.Success]][T] or [[scala.util.Failure]][T]. + * + * For example, consider a computation that performs division on user-defined input. + * `Try` can reduce or eliminate the need for explicit exception handling in all of the places + * where an exception might be thrown. + * + * Example: + * {{{ + * import scala.io.StdIn + * import scala.util.{Try, Success, Failure} + * + * def divide: Try[Int] = { + * val dividend = Try(StdIn.readLine("Enter an Int that you'd like to divide:\n").toInt) + * val divisor = Try(StdIn.readLine("Enter an Int that you'd like to divide by:\n").toInt) + * val problem = dividend.flatMap(x => divisor.map(y => x/y)) + * problem match { + * case Success(v) => + * println("Result of " + dividend.get + "/"+ divisor.get +" is: " + v) + * Success(v) + * case Failure(e) => + * println("You must've divided by zero or entered something that's not an Int. Try again!") + * println("Info from the exception: " + e.getMessage) + * divide + * } + * } + * + * }}} + * + * An important property of `Try` shown in the above example is its ability to ''pipeline'', or chain, operations, + * catching exceptions along the way. The `flatMap` and `map` combinators in the above example each essentially + * pass off either their successfully completed value, wrapped in the `Success` type for it to be further operated + * upon by the next combinator in the chain, or the exception wrapped in the `Failure` type usually to be simply + * passed on down the chain. Combinators such as `recover` and `recoverWith` are designed to provide some type of + * default behavior in the case of failure. + * + * ''Note'': only non-fatal exceptions are caught by the combinators on `Try` (see [[scala.util.control.NonFatal]]). + * Serious system errors, on the other hand, will be thrown. + * + * ''Note:'': all Try combinators will catch exceptions and return failure unless otherwise specified in the documentation. + */ +sealed abstract class Try[+T] extends Product with Serializable { + + /** Returns `true` if the `Try` is a `Failure`, `false` otherwise. + */ + def isFailure: Boolean + + /** Returns `true` if the `Try` is a `Success`, `false` otherwise. + */ + def isSuccess: Boolean + + /** Returns the value from this `Success` or the given `default` argument if this is a `Failure`. + * + * ''Note:'': This will throw an exception if it is not a success and default throws an exception. + */ + def getOrElse[U >: T](default: => U): U + + /** Returns this `Try` if it's a `Success` or the given `default` argument if this is a `Failure`. + */ + def orElse[U >: T](default: => Try[U]): Try[U] + + /** Returns the value from this `Success` or throws the exception if this is a `Failure`. + */ + def get: T + + /** + * Applies the given function `f` if this is a `Success`, otherwise returns `Unit` if this is a `Failure`. + * + * ''Note:'' If `f` throws, then this method may throw an exception. + */ + def foreach[U](f: T => U): Unit + + /** + * Returns the given function applied to the value from this `Success` or returns this if this is a `Failure`. + */ + def flatMap[U](f: T => Try[U]): Try[U] + + /** + * Maps the given function to the value from this `Success` or returns this if this is a `Failure`. + */ + def map[U](f: T => U): Try[U] + + /** + * Applies the given partial function to the value from this `Success` or returns this if this is a `Failure`. + */ + def collect[U](pf: PartialFunction[T, U]): Try[U] + + /** + * Converts this to a `Failure` if the predicate is not satisfied. + */ + def filter(p: T => Boolean): Try[T] + + /** Creates a non-strict filter, which eventually converts this to a `Failure` + * if the predicate is not satisfied. + * + * Note: unlike filter, withFilter does not create a new Try. + * Instead, it restricts the domain of subsequent + * `map`, `flatMap`, `foreach`, and `withFilter` operations. + * + * As Try is a one-element collection, this may be a bit overkill, + * but it's consistent with withFilter on Option and the other collections. + * + * @param p the predicate used to test elements. + * @return an object of class `WithFilter`, which supports + * `map`, `flatMap`, `foreach`, and `withFilter` operations. + * All these operations apply to those elements of this Try + * which satisfy the predicate `p`. + */ + @inline final def withFilter(p: T => Boolean): WithFilter = new WithFilter(p) + + /** We need a whole WithFilter class to honor the "doesn't create a new + * collection" contract even though it seems unlikely to matter much in a + * collection with max size 1. + */ + final class WithFilter(p: T => Boolean) { + def map[U](f: T => U): Try[U] = Try.this filter p map f + def flatMap[U](f: T => Try[U]): Try[U] = Try.this filter p flatMap f + def foreach[U](f: T => U): Unit = Try.this filter p foreach f + def withFilter(q: T => Boolean): WithFilter = new WithFilter(x => p(x) && q(x)) + } + + /** + * Applies the given function `f` if this is a `Failure`, otherwise returns this if this is a `Success`. + * This is like `flatMap` for the exception. + */ + def recoverWith[U >: T](pf: PartialFunction[Throwable, Try[U]]): Try[U] + + /** + * Applies the given function `f` if this is a `Failure`, otherwise returns this if this is a `Success`. + * This is like map for the exception. + */ + def recover[U >: T](pf: PartialFunction[Throwable, U]): Try[U] + + /** + * Returns `None` if this is a `Failure` or a `Some` containing the value if this is a `Success`. + */ + def toOption: Option[T] + + /** + * Transforms a nested `Try`, ie, a `Try` of type `Try[Try[T]]`, + * into an un-nested `Try`, ie, a `Try` of type `Try[T]`. + */ + def flatten[U](implicit ev: T <:< Try[U]): Try[U] + + /** + * Inverts this `Try`. If this is a `Failure`, returns its exception wrapped in a `Success`. + * If this is a `Success`, returns a `Failure` containing an `UnsupportedOperationException`. + */ + def failed: Try[Throwable] + + /** Completes this `Try` by applying the function `f` to this if this is of type `Failure`, or conversely, by applying + * `s` if this is a `Success`. + */ + def transform[U](s: T => Try[U], f: Throwable => Try[U]): Try[U] + + /** + * Returns `Left` with `Throwable` if this is a `Failure`, otherwise returns `Right` with `Success` value. + */ + def toEither: Either[Throwable, T] + + /** + * Applies `fa` if this is a `Failure` or `fb` if this is a `Success`. + * If `fb` is initially applied and throws an exception, + * then `fa` is applied with this exception. + * + * @example {{{ + * val result: Try[Int] = Try { string.toInt } + * log(result.fold( + * ex => "Operation failed with " + ex, + * v => "Operation produced value: " + v + * )) + * }}} + * + * @param fa the function to apply if this is a `Failure` + * @param fb the function to apply if this is a `Success` + * @return the results of applying the function + */ + def fold[U](fa: Throwable => U, fb: T => U): U + +} + +object Try { + /** Constructs a `Try` using the by-name parameter as a result value. + * + * The evaluation of `r` is attempted once. + * + * Any non-fatal exception is caught and results in a `Failure` + * that holds the exception. + * + * @param r the result value to compute + * @return the result of evaluating the value, as a `Success` or `Failure` + */ + def apply[T](r: => T): Try[T] = + try { + val r1 = r + Success(r1) + } catch { + case NonFatal(e) => Failure(e) + } +} + +final case class Failure[+T](exception: Throwable) extends Try[T] { + override def isFailure: Boolean = true + override def isSuccess: Boolean = false + override def get: T = throw exception + override def getOrElse[U >: T](default: => U): U = default + override def orElse[U >: T](default: => Try[U]): Try[U] = + try default catch { case NonFatal(e) => Failure(e) } + override def flatMap[U](f: T => Try[U]): Try[U] = this.asInstanceOf[Try[U]] + override def flatten[U](implicit ev: T <:< Try[U]): Try[U] = this.asInstanceOf[Try[U]] + override def foreach[U](f: T => U): Unit = () + override def transform[U](s: T => Try[U], f: Throwable => Try[U]): Try[U] = + try f(exception) catch { case NonFatal(e) => Failure(e) } + override def map[U](f: T => U): Try[U] = this.asInstanceOf[Try[U]] + override def collect[U](pf: PartialFunction[T, U]): Try[U] = this.asInstanceOf[Try[U]] + override def filter(p: T => Boolean): Try[T] = this + override def recover[U >: T](pf: PartialFunction[Throwable, U]): Try[U] = { + val marker = Statics.pfMarker + try { + val v = pf.applyOrElse(exception, (x: Throwable) => marker) + if (marker ne v.asInstanceOf[AnyRef]) Success(v.asInstanceOf[U]) else this + } catch { case NonFatal(e) => Failure(e) } + } + override def recoverWith[U >: T](pf: PartialFunction[Throwable, Try[U]]): Try[U] = { + val marker = Statics.pfMarker + try { + val v = pf.applyOrElse(exception, (x: Throwable) => marker) + if (marker ne v.asInstanceOf[AnyRef]) v.asInstanceOf[Try[U]] else this + } catch { case NonFatal(e) => Failure(e) } + } + override def failed: Try[Throwable] = Success(exception) + override def toOption: Option[T] = None + override def toEither: Either[Throwable, T] = Left(exception) + override def fold[U](fa: Throwable => U, fb: T => U): U = fa(exception) +} + +final case class Success[+T](value: T) extends Try[T] { + override def isFailure: Boolean = false + override def isSuccess: Boolean = true + override def get = value + override def getOrElse[U >: T](default: => U): U = get + override def orElse[U >: T](default: => Try[U]): Try[U] = this + override def flatMap[U](f: T => Try[U]): Try[U] = + try f(value) catch { case NonFatal(e) => Failure(e) } + override def flatten[U](implicit ev: T <:< Try[U]): Try[U] = value + override def foreach[U](f: T => U): Unit = f(value) + override def transform[U](s: T => Try[U], f: Throwable => Try[U]): Try[U] = this flatMap s + override def map[U](f: T => U): Try[U] = Try[U](f(value)) + override def collect[U](pf: PartialFunction[T, U]): Try[U] = { + val marker = Statics.pfMarker + try { + val v = pf.applyOrElse(value, ((x: T) => marker).asInstanceOf[Function[T, U]]) + if (marker ne v.asInstanceOf[AnyRef]) Success(v) + else Failure(new NoSuchElementException("Predicate does not hold for " + value)) + } catch { case NonFatal(e) => Failure(e) } + } + override def filter(p: T => Boolean): Try[T] = + try { + if (p(value)) this else Failure(new NoSuchElementException("Predicate does not hold for " + value)) + } catch { case NonFatal(e) => Failure(e) } + override def recover[U >: T](pf: PartialFunction[Throwable, U]): Try[U] = this + override def recoverWith[U >: T](pf: PartialFunction[Throwable, Try[U]]): Try[U] = this + override def failed: Try[Throwable] = Failure(new UnsupportedOperationException("Success.failed")) + override def toOption: Option[T] = Some(value) + override def toEither: Either[Throwable, T] = Right(value) + override def fold[U](fa: Throwable => U, fb: T => U): U = + try { fb(value) } catch { case NonFatal(e) => fa(e) } +} diff --git a/library/src/scala/util/Using.scala b/library/src/scala/util/Using.scala new file mode 100644 index 000000000000..90ec6dc71d40 --- /dev/null +++ b/library/src/scala/util/Using.scala @@ -0,0 +1,433 @@ +/* + * 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.util + +import scala.language.`2.13` +import scala.util.control.{ControlThrowable, NonFatal} + +/** A utility for performing automatic resource management. It can be used to perform an + * operation using resources, after which it releases the resources in reverse order + * of their creation. + * + * ==Usage== + * + * There are multiple ways to automatically manage resources with `Using`. If you only need + * to manage a single resource, the [[Using.apply `apply`]] method is easiest; it wraps the + * resource opening, operation, and resource releasing in a `Try`. + * + * Example: + * {{{ + * import java.io.{BufferedReader, FileReader} + * import scala.util.{Try, Using} + * + * val lines: Try[Seq[String]] = + * Using(new BufferedReader(new FileReader("file.txt"))) { reader => + * Iterator.continually(reader.readLine()).takeWhile(_ != null).toSeq + * } + * }}} + * + * If you need to manage multiple resources, [[Using.Manager$.apply `Using.Manager`]] should + * be used. It allows the managing of arbitrarily many resources, whose creation, use, and + * release are all wrapped in a `Try`. + * + * Example: + * {{{ + * import java.io.{BufferedReader, FileReader} + * import scala.util.{Try, Using} + * + * val files = List("file1.txt", "file2.txt", "file3.txt", "file4.txt") + * val lines: Try[Seq[String]] = Using.Manager { use => + * // acquire resources + * def mkreader(filename: String) = use(new BufferedReader(new FileReader(filename))) + * + * // use your resources here + * def lines(reader: BufferedReader): Iterator[String] = + * Iterator.continually(reader.readLine()).takeWhile(_ != null) + * + * files.map(mkreader).flatMap(lines) + * } + * }}} + * + * Composed or "wrapped" resources may be acquired in order of construction, + * if "underlying" resources are not closed. Although redundant in this case, + * here is the previous example with a wrapped call to `use`: + * {{{ + * def mkreader(filename: String) = use(new BufferedReader(use(new FileReader(filename)))) + * }}} + * + * Custom resources can be registered on construction by requiring an implicit `Manager`. + * This ensures they will be released even if composition fails: + * {{{ + * import scala.util.Using + * + * case class X(x: String)(implicit mgr: Using.Manager) extends AutoCloseable { + * override def close() = println(s"CLOSE $x") + * mgr.acquire(this) + * } + * case class Y(y: String)(x: String)(implicit mgr: Using.Manager) extends AutoCloseable { + * val xres = X(x) + * override def close() = println(s"CLOSE $y") + * // an error during construction releases previously acquired resources + * require(y != null, "y is null") + * mgr.acquire(this) + * } + * + * Using.Manager { implicit mgr => + * val y = Y("Y")("X") + * println(s"USE $y") + * } + * println { + * Using.Manager { implicit mgr => + * Y(null)("X") + * } + * } // Failure(java.lang.IllegalArgumentException: requirement failed: y is null) + * }}} + * + * If you wish to avoid wrapping management and operations in a `Try`, you can use + * [[Using.resource `Using.resource`]], which throws any exceptions that occur. + * + * Example: + * {{{ + * import java.io.{BufferedReader, FileReader} + * import scala.util.Using + * + * val lines: Seq[String] = + * Using.resource(new BufferedReader(new FileReader("file.txt"))) { reader => + * Iterator.continually(reader.readLine()).takeWhile(_ != null).toSeq + * } + * }}} + * + * ==Suppression Behavior== + * + * If two exceptions are thrown (e.g., by an operation and closing a resource), + * one of them is re-thrown, and the other is + * [[java.lang.Throwable#addSuppressed added to it as a suppressed exception]]. + * If the two exceptions are of different 'severities' (see below), the one of a higher + * severity is re-thrown, and the one of a lower severity is added to it as a suppressed + * exception. If the two exceptions are of the same severity, the one thrown first is + * re-thrown, and the one thrown second is added to it as a suppressed exception. + * If an exception is a [[scala.util.control.ControlThrowable `ControlThrowable`]], or + * if it does not support suppression (see + * [[java.lang.Throwable `Throwable`'s constructor with an `enableSuppression` parameter]]), + * an exception that would have been suppressed is instead discarded. + * + * Exceptions are ranked from highest to lowest severity as follows: + * - `java.lang.VirtualMachineError` + * - `java.lang.LinkageError` + * - `java.lang.InterruptedException` and `java.lang.ThreadDeath` + * - [[scala.util.control.NonFatal fatal exceptions]], excluding `scala.util.control.ControlThrowable` + * - all other exceptions, excluding `scala.util.control.ControlThrowable` + * - `scala.util.control.ControlThrowable` + * + * When more than two exceptions are thrown, the first two are combined and + * re-thrown as described above, and each successive exception thrown is combined + * as it is thrown. + * + * @define suppressionBehavior See the main doc for [[Using `Using`]] for full details of + * suppression behavior. + */ +object Using { + /** Performs an operation using a resource, and then releases the resource, + * even if the operation throws an exception. + * + * $suppressionBehavior + * + * @return a [[Try]] containing an exception if one or more were thrown, + * or the result of the operation if no exceptions were thrown + */ + def apply[R: Releasable, A](resource: => R)(f: R => A): Try[A] = Try { Using.resource(resource)(f) } + + /** A resource manager. + * + * Resources can be registered with the manager by calling [[acquire `acquire`]]; + * such resources will be released in reverse order of their acquisition + * when the manager is closed, regardless of any exceptions thrown + * during use. + * + * $suppressionBehavior + * + * @note It is recommended for API designers to require an implicit `Manager` + * for the creation of custom resources, and to call `acquire` during those + * resources' construction. Doing so guarantees that the resource ''must'' be + * automatically managed, and makes it impossible to forget to do so. + * + * + * Example: + * {{{ + * class SafeFileReader(file: File)(implicit manager: Using.Manager) + * extends BufferedReader(new FileReader(file)) { + * + * def this(fileName: String)(implicit manager: Using.Manager) = this(new File(fileName)) + * + * manager.acquire(this) + * } + * }}} + */ + final class Manager private { + import Manager._ + + private var closed = false + private[this] var resources: List[Resource[_]] = Nil + + /** Registers the specified resource with this manager, so that + * the resource is released when the manager is closed, and then + * returns the (unmodified) resource. + */ + def apply[R: Releasable](resource: R): resource.type = { + acquire(resource) + resource + } + + /** Registers the specified resource with this manager, so that + * the resource is released when the manager is closed. + */ + def acquire[R: Releasable](resource: R): Unit = { + if (resource == null) throw new NullPointerException("null resource") + if (closed) throw new IllegalStateException("Manager has already been closed") + resources = new Resource(resource) :: resources + } + + private def manage[A](op: Manager => A): A = { + var toThrow: Throwable = null + try { + op(this) + } catch { + case t: Throwable => + toThrow = t + null.asInstanceOf[A] // compiler doesn't know `finally` will throw + } finally { + closed = true + var rs = resources + resources = null // allow GC, in case something is holding a reference to `this` + while (rs.nonEmpty) { + val resource = rs.head + rs = rs.tail + try resource.release() + catch { + case t: Throwable => + if (toThrow == null) toThrow = t + else toThrow = preferentiallySuppress(toThrow, t) + } + } + if (toThrow != null) throw toThrow + } + } + } + + object Manager { + /** Performs an operation using a `Manager`, then closes the `Manager`, + * releasing its resources (in reverse order of acquisition). + * + * Example: + * {{{ + * val lines = Using.Manager { use => + * use(new BufferedReader(new FileReader("file.txt"))).lines() + * } + * }}} + * + * If using resources which require an implicit `Manager` as a parameter, + * this method should be invoked with an `implicit` modifier before the function + * parameter: + * + * Example: + * {{{ + * val lines = Using.Manager { implicit use => + * new SafeFileReader("file.txt").lines() + * } + * }}} + * + * See the main doc for [[Using `Using`]] for full details of suppression behavior. + * + * @param op the operation to perform using the manager + * @tparam A the return type of the operation + * @return a [[Try]] containing an exception if one or more were thrown, + * or the result of the operation if no exceptions were thrown + */ + def apply[A](op: Manager => A): Try[A] = Try { (new Manager).manage(op) } + + private final class Resource[R](resource: R)(implicit releasable: Releasable[R]) { + def release(): Unit = releasable.release(resource) + } + } + + private def preferentiallySuppress(primary: Throwable, secondary: Throwable): Throwable = { + @annotation.nowarn("cat=deprecation") // avoid warning on mention of ThreadDeath + def score(t: Throwable): Int = t match { + case _: VirtualMachineError => 4 + case _: LinkageError => 3 + case _: InterruptedException | _: ThreadDeath => 2 + case _: ControlThrowable => -1 // below everything + case e if !NonFatal(e) => 1 // in case this method gets out of sync with NonFatal + case _ => 0 + } + @inline def suppress(t: Throwable, suppressed: Throwable): Throwable = { t.addSuppressed(suppressed); t } + + if (score(secondary) > score(primary)) suppress(secondary, primary) + else suppress(primary, secondary) + } + + /** Performs an operation using a resource, and then releases the resource, + * even if the operation throws an exception. This method behaves similarly + * to Java's try-with-resources. + * + * $suppressionBehavior + * + * @param resource the resource + * @param body the operation to perform with the resource + * @tparam R the type of the resource + * @tparam A the return type of the operation + * @return the result of the operation, if neither the operation nor + * releasing the resource throws + */ + def resource[R, A](resource: R)(body: R => A)(implicit releasable: Releasable[R]): A = { + if (resource == null) throw new NullPointerException("null resource") + + var toThrow: Throwable = null + try { + body(resource) + } catch { + case t: Throwable => + toThrow = t + null.asInstanceOf[A] // compiler doesn't know `finally` will throw + } finally { + if (toThrow eq null) releasable.release(resource) + else { + try releasable.release(resource) + catch { case other: Throwable => toThrow = preferentiallySuppress(toThrow, other) } + finally throw toThrow + } + } + } + + /** Performs an operation using two resources, and then releases the resources + * in reverse order, even if the operation throws an exception. This method + * behaves similarly to Java's try-with-resources. + * + * $suppressionBehavior + * + * @param resource1 the first resource + * @param resource2 the second resource + * @param body the operation to perform using the resources + * @tparam R1 the type of the first resource + * @tparam R2 the type of the second resource + * @tparam A the return type of the operation + * @return the result of the operation, if neither the operation nor + * releasing the resources throws + */ + def resources[R1: Releasable, R2: Releasable, A]( + resource1: R1, + resource2: => R2 + )(body: (R1, R2) => A + ): A = + resource(resource1) { r1 => + resource(resource2) { r2 => + body(r1, r2) + } + } + + /** Performs an operation using three resources, and then releases the resources + * in reverse order, even if the operation throws an exception. This method + * behaves similarly to Java's try-with-resources. + * + * $suppressionBehavior + * + * @param resource1 the first resource + * @param resource2 the second resource + * @param resource3 the third resource + * @param body the operation to perform using the resources + * @tparam R1 the type of the first resource + * @tparam R2 the type of the second resource + * @tparam R3 the type of the third resource + * @tparam A the return type of the operation + * @return the result of the operation, if neither the operation nor + * releasing the resources throws + */ + def resources[R1: Releasable, R2: Releasable, R3: Releasable, A]( + resource1: R1, + resource2: => R2, + resource3: => R3 + )(body: (R1, R2, R3) => A + ): A = + resource(resource1) { r1 => + resource(resource2) { r2 => + resource(resource3) { r3 => + body(r1, r2, r3) + } + } + } + + /** Performs an operation using four resources, and then releases the resources + * in reverse order, even if the operation throws an exception. This method + * behaves similarly to Java's try-with-resources. + * + * $suppressionBehavior + * + * @param resource1 the first resource + * @param resource2 the second resource + * @param resource3 the third resource + * @param resource4 the fourth resource + * @param body the operation to perform using the resources + * @tparam R1 the type of the first resource + * @tparam R2 the type of the second resource + * @tparam R3 the type of the third resource + * @tparam R4 the type of the fourth resource + * @tparam A the return type of the operation + * @return the result of the operation, if neither the operation nor + * releasing the resources throws + */ + def resources[R1: Releasable, R2: Releasable, R3: Releasable, R4: Releasable, A]( + resource1: R1, + resource2: => R2, + resource3: => R3, + resource4: => R4 + )(body: (R1, R2, R3, R4) => A + ): A = + resource(resource1) { r1 => + resource(resource2) { r2 => + resource(resource3) { r3 => + resource(resource4) { r4 => + body(r1, r2, r3, r4) + } + } + } + } + + /** A type class describing how to release a particular type of resource. + * + * A resource is anything which needs to be released, closed, or otherwise cleaned up + * in some way after it is finished being used, and for which waiting for the object's + * garbage collection to be cleaned up would be unacceptable. For example, an instance of + * [[java.io.OutputStream]] would be considered a resource, because it is important to close + * the stream after it is finished being used. + * + * An instance of `Releasable` is needed in order to automatically manage a resource + * with [[Using `Using`]]. An implicit instance is provided for all types extending + * [[java.lang.AutoCloseable]]. + * + * @tparam R the type of the resource + */ + trait Releasable[-R] { + /** Releases the specified resource. */ + def release(resource: R): Unit + } + + object Releasable { + // prefer explicit types 2.14 + //implicit val AutoCloseableIsReleasable: Releasable[AutoCloseable] = new Releasable[AutoCloseable] {} + /** An implicit `Releasable` for [[java.lang.AutoCloseable `AutoCloseable`s]]. */ + implicit object AutoCloseableIsReleasable extends Releasable[AutoCloseable] { + def release(resource: AutoCloseable): Unit = resource.close() + } + } + +} diff --git a/library/src/scala/util/control/Breaks.scala b/library/src/scala/util/control/Breaks.scala new file mode 100644 index 000000000000..601447e5d099 --- /dev/null +++ b/library/src/scala/util/control/Breaks.scala @@ -0,0 +1,126 @@ +/* + * 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.util.control + +import scala.language.`2.13` + +/** Provides the `break` control abstraction. + * + * The `break` method uses a `ControlThrowable` to transfer + * control up the stack to an enclosing `breakable`. + * + * It is typically used to abruptly terminate a `for` loop, + * but can be used to return from an arbitrary computation. + * + * Control resumes after the `breakable`. + * + * If there is no matching `breakable`, the `BreakControl` + * thrown by `break` is handled in the usual way: if not + * caught, it may terminate the current `Thread`. + * + * `BreakControl` carries no stack trace, so the default + * exception handler does not print useful diagnostic + * information; there is no compile-time warning if there + * is no matching `breakable`. + * + * A catch clause using `NonFatal` is safe to use with + * `break`; it will not short-circuit the transfer + * of control to the enclosing `breakable`. + * + * A `breakable` matches a call to `break` if the methods + * were invoked on the same receiver object, which may be the + * convenience value `Breaks`. + * + * Example usage: + * {{{ + * val mybreaks = new Breaks + * import mybreaks.{break, breakable} + * + * breakable { + * for (x <- xs) { + * if (done) break() + * f(x) + * } + * } + * }}} + * Calls to `break` from one instance of `Breaks` will never + * resume at the `breakable` of some other instance. + * + * Any intervening exception handlers should use `NonFatal`, + * or use `Try` for evaluation: + * {{{ + * val mybreaks = new Breaks + * import mybreaks.{break, breakable} + * + * breakable { + * for (x <- xs) Try { if (quit) break else f(x) }.foreach(println) + * } + * }}} + */ +class Breaks { + + private[this] val breakException = new BreakControl + + /** A block from which one can exit with a `break`. The `break` may be + * executed further down in the call stack provided that it is called on the + * exact same instance of `Breaks`. + */ + def breakable(op: => Unit): Unit = + try op catch { case ex: BreakControl if ex eq breakException => } + + sealed trait TryBlock[T] { + def catchBreak(onBreak: => T): T + } + + /** Try a computation that produces a value, supplying a default + * to be used if the computation terminates with a `break`. + * + * {{{ + * tryBreakable { + * (1 to 3).map(i => if (math.random < .5) break else i * 2) + * } catchBreak { + * Vector.empty + * } + * }}} + */ + def tryBreakable[T](op: => T): TryBlock[T] = + new TryBlock[T] { + def catchBreak(onBreak: => T) = + try op catch { case ex: BreakControl if ex eq breakException => onBreak } + } + + /** Break from the dynamically closest enclosing breakable block that also uses + * this `Breaks` instance. + * + * @note This might be different from the statically closest enclosing block! + * @note Invocation without parentheses relies on the conversion to "empty application". + */ + def break(): Nothing = throw breakException +} + +/** An object that can be used for the break control abstraction. + * + * Example usage: + * {{{ + * import Breaks.{break, breakable} + * + * breakable { + * for (...) { + * if (...) break + * } + * } + * }}} + */ +object Breaks extends Breaks + +private class BreakControl extends ControlThrowable diff --git a/library/src/scala/util/control/ControlThrowable.scala b/library/src/scala/util/control/ControlThrowable.scala new file mode 100644 index 000000000000..b3a3bf1006e2 --- /dev/null +++ b/library/src/scala/util/control/ControlThrowable.scala @@ -0,0 +1,49 @@ +/* + * 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.util.control + +import scala.language.`2.13` + +/** A parent class for throwable objects intended for flow control. + * + * Instances of `ControlThrowable` should not normally be caught. + * + * As a convenience, `NonFatal` does not match `ControlThrowable`. + * + * {{{ + * import scala.util.control.{Breaks, NonFatal}, Breaks.{break, breakable} + * + * breakable { + * for (v <- values) { + * try { + * if (p(v)) break + * else ??? + * } catch { + * case NonFatal(t) => log(t) // can't catch a break + * } + * } + * } + * }}} + * + * Suppression is disabled, because flow control should not suppress + * an exceptional condition. Stack traces are also disabled, allowing + * instances of `ControlThrowable` to be safely reused. + * + * Instances of `ControlThrowable` should not normally have a cause. + * Legacy subclasses may set a cause using `initCause`. + */ +abstract class ControlThrowable(message: String) extends Throwable( + message, /*cause*/ null, /*enableSuppression=*/ false, /*writableStackTrace*/ false) { + + def this() = this(message = null) +} diff --git a/library/src/scala/util/control/Exception.scala b/library/src/scala/util/control/Exception.scala new file mode 100644 index 000000000000..76861c23e853 --- /dev/null +++ b/library/src/scala/util/control/Exception.scala @@ -0,0 +1,375 @@ +/* + * 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 util +package control + +import scala.language.`2.13` +import scala.annotation.tailrec +import scala.reflect.{ClassTag, classTag} +import scala.language.implicitConversions + +/** Classes representing the components of exception handling. + * + * Each class is independently composable. + * + * This class differs from [[scala.util.Try]] in that it focuses on composing exception handlers rather than + * composing behavior. All behavior should be composed first and fed to a [[Catch]] object using one of the + * `opt`, `either` or `withTry` methods. Taken together the classes provide a DSL for composing catch and finally + * behaviors. + * + * === Examples === + * + * Create a `Catch` which handles specified exceptions. + * {{{ + * import scala.util.control.Exception._ + * import java.net._ + * + * val s = "https://www.scala-lang.org/" + * + * // Some(https://www.scala-lang.org/) + * val x1: Option[URL] = catching(classOf[MalformedURLException]) opt new URL(s) + * + * // Right(https://www.scala-lang.org/) + * val x2: Either[Throwable,URL] = + * catching(classOf[MalformedURLException], classOf[NullPointerException]) either new URL(s) + * + * // Success(https://www.scala-lang.org/) + * val x3: Try[URL] = catching(classOf[MalformedURLException], classOf[NullPointerException]) withTry new URL(s) + * + * val defaultUrl = new URL("http://example.com") + * // URL(http://example.com) because htt/xx throws MalformedURLException + * val x4: URL = failAsValue(classOf[MalformedURLException])(defaultUrl)(new URL("htt/xx")) + * }}} + * + * Create a `Catch` which logs exceptions using `handling` and `by`. + * {{{ + * def log(t: Throwable): Unit = t.printStackTrace + * + * val withThrowableLogging: Catch[Unit] = handling(classOf[MalformedURLException]) by (log) + * + * def printUrl(url: String) : Unit = { + * val con = new URL(url) openConnection() + * val source = scala.io.Source.fromInputStream(con.getInputStream()) + * source.getLines().foreach(println) + * } + * + * val badUrl = "htt/xx" + * // Prints stacktrace, + * // java.net.MalformedURLException: no protocol: htt/xx + * // at java.net.URL.(URL.java:586) + * withThrowableLogging { printUrl(badUrl) } + * + * val goodUrl = "https://www.scala-lang.org/" + * // Prints page content, + * // <!DOCTYPE html> + * // <html> + * withThrowableLogging { printUrl(goodUrl) } + * }}} + * + * Use `unwrapping` to create a `Catch` that unwraps exceptions before rethrowing. + * {{{ + * class AppException(cause: Throwable) extends RuntimeException(cause) + * + * val unwrappingCatch: Catch[Nothing] = unwrapping(classOf[AppException]) + * + * def calcResult: Int = throw new AppException(new NullPointerException) + * + * // Throws NPE not AppException, + * // java.lang.NullPointerException + * // at .calcResult(<console>:17) + * val result = unwrappingCatch(calcResult) + * }}} + * + * Use `failAsValue` to provide a default when a specified exception is caught. + * + * {{{ + * val inputDefaulting: Catch[Int] = failAsValue(classOf[NumberFormatException])(0) + * val candidatePick = "seven" // scala.io.StdIn.readLine() + * + * // Int = 0 + * val pick = inputDefaulting(candidatePick.toInt) + * }}} + * + * Compose multiple `Catch`s with `or` to build a `Catch` that provides default values varied by exception. + * {{{ + * val formatDefaulting: Catch[Int] = failAsValue(classOf[NumberFormatException])(0) + * val nullDefaulting: Catch[Int] = failAsValue(classOf[NullPointerException])(-1) + * val otherDefaulting: Catch[Int] = nonFatalCatch withApply(_ => -100) + * + * val combinedDefaulting: Catch[Int] = formatDefaulting or nullDefaulting or otherDefaulting + * + * def p(s: String): Int = s.length * s.toInt + * + * // Int = 0 + * combinedDefaulting(p("tenty-nine")) + * + * // Int = -1 + * combinedDefaulting(p(null: String)) + * + * // Int = -100 + * combinedDefaulting(throw new IllegalStateException) + * + * // Int = 22 + * combinedDefaulting(p("11")) + * }}} + * + * @groupname composition-catch Catch behavior composition + * @groupprio composition-catch 10 + * @groupdesc composition-catch Build Catch objects from exception lists and catch logic + * + * @groupname composition-finally Finally behavior composition + * @groupprio composition-finally 20 + * @groupdesc composition-finally Build Catch objects from finally logic + * + * @groupname canned-behavior General purpose catch objects + * @groupprio canned-behavior 30 + * @groupdesc canned-behavior Catch objects with predefined behavior. Use combinator methods to compose additional behavior. + * + * @groupname dsl DSL behavior composition + * @groupprio dsl 40 + * @groupdesc dsl Expressive Catch behavior composition + * + * @groupname composition-catch-promiscuously Promiscuous Catch behaviors + * @groupprio composition-catch-promiscuously 50 + * @groupdesc composition-catch-promiscuously Useful if catching `ControlThrowable` or `InterruptedException` is required. + * + * @groupname logic-container Logic Containers + * @groupprio logic-container 60 + * @groupdesc logic-container Containers for catch and finally behavior. + * + * @define protectedExceptions `ControlThrowable` or `InterruptedException` + */ + +object Exception { + type Catcher[+T] = PartialFunction[Throwable, T] + + def mkCatcher[Ex <: Throwable: ClassTag, T](isDef: Ex => Boolean, f: Ex => T): PartialFunction[Throwable, T] = new Catcher[T] { + private def downcast(x: Throwable): Option[Ex] = + if (classTag[Ex].runtimeClass.isAssignableFrom(x.getClass)) Some(x.asInstanceOf[Ex]) + else None + + def isDefinedAt(x: Throwable): Boolean = downcast(x) exists isDef + def apply(x: Throwable): T = f(downcast(x).get) + } + + def mkThrowableCatcher[T](isDef: Throwable => Boolean, f: Throwable => T): PartialFunction[Throwable, T] = mkCatcher[Throwable, T](isDef, f) + + implicit def throwableSubtypeToCatcher[Ex <: Throwable: ClassTag, T](pf: PartialFunction[Ex, T]): Catcher[T] = + mkCatcher(pf.isDefinedAt, pf.apply) + + /** !!! Not at all sure of every factor which goes into this, + * and/or whether we need multiple standard variations. + * @return true if `x` is $protectedExceptions otherwise false. + */ + def shouldRethrow(x: Throwable): Boolean = x match { + case _: ControlThrowable => true + case _: InterruptedException => true + // case _: java.lang.Error => true ? + case _ => false + } + + trait Described { + protected val name: String + private[this] var _desc: String = "" + def desc: String = _desc + def withDesc(s: String): this.type = { + _desc = s + this + } + override def toString(): String = name + "(" + desc + ")" + } + + /** A container class for finally code. + * @group logic-container + */ + class Finally private[Exception](body: => Unit) extends Described { + protected val name = "Finally" + + def and(other: => Unit): Finally = new Finally({ body ; other }) + def invoke(): Unit = { body } + } + + /** A container class for catch/finally logic. + * + * Pass a different value for rethrow if you want to probably + * unwisely allow catching control exceptions and other throwables + * which the rest of the world may expect to get through. + * @tparam T result type of bodies used in try and catch blocks + * @param pf Partial function used when applying catch logic to determine result value + * @param fin Finally logic which if defined will be invoked after catch logic + * @param rethrow Predicate on throwables determining when to rethrow a caught [[Throwable]] + * @group logic-container + */ + class Catch[+T]( + val pf: Catcher[T], + val fin: Option[Finally] = None, + val rethrow: Throwable => Boolean = shouldRethrow) + extends Described { + + protected val name = "Catch" + + /** Create a new Catch with additional exception handling logic. */ + def or[U >: T](pf2: Catcher[U]): Catch[U] = new Catch(pf orElse pf2, fin, rethrow) + def or[U >: T](other: Catch[U]): Catch[U] = or(other.pf) + + /** Apply this catch logic to the supplied body. */ + def apply[U >: T](body: => U): U = + try body + catch { + case x if rethrow(x) => throw x + case x if pf isDefinedAt x => pf(x) + } + finally fin foreach (_.invoke()) + + /** Create a new Catch container from this object and the supplied finally body. + * @param body The additional logic to apply after all existing finally bodies + */ + def andFinally(body: => Unit): Catch[T] = { + val appendedFin = fin map(_ and body) getOrElse new Finally(body) + new Catch(pf, Some(appendedFin), rethrow) + } + + /** Apply this catch logic to the supplied body, mapping the result + * into `Option[T]` - `None` if any exception was caught, `Some(T)` otherwise. + */ + def opt[U >: T](body: => U): Option[U] = toOption(Some(body)) + + /** Apply this catch logic to the supplied body, mapping the result + * into `Either[Throwable, T]` - `Left(exception)` if an exception was caught, + * `Right(T)` otherwise. + */ + def either[U >: T](body: => U): Either[Throwable, U] = toEither(Right(body)) + + /** Apply this catch logic to the supplied body, mapping the result + * into `Try[T]` - `Failure` if an exception was caught, `Success(T)` otherwise. + */ + def withTry[U >: T](body: => U): scala.util.Try[U] = toTry(Success(body)) + + /** Create a `Catch` object with the same `isDefinedAt` logic as this one, + * but with the supplied `apply` method replacing the current one. */ + def withApply[U](f: Throwable => U): Catch[U] = { + val pf2 = new Catcher[U] { + def isDefinedAt(x: Throwable): Boolean = pf isDefinedAt x + def apply(x: Throwable): U = f(x) + } + new Catch(pf2, fin, rethrow) + } + + /** Convenience methods. */ + def toOption: Catch[Option[T]] = withApply(_ => None) + def toEither: Catch[Either[Throwable, T]] = withApply(Left(_)) + def toTry: Catch[scala.util.Try[T]] = withApply(x => Failure(x)) + } + + final val nothingCatcher: Catcher[Nothing] = mkThrowableCatcher(_ => false, throw _) + final def nonFatalCatcher[T]: Catcher[T] = mkThrowableCatcher({ case NonFatal(_) => true; case _ => false }, throw _) + final def allCatcher[T]: Catcher[T] = mkThrowableCatcher(_ => true, throw _) + + /** The empty `Catch` object. + * @group canned-behavior + **/ + final val noCatch: Catch[Nothing] = new Catch(nothingCatcher) withDesc "" + + /** A `Catch` object which catches everything. + * @group canned-behavior + **/ + final def allCatch[T]: Catch[T] = new Catch(allCatcher[T]) withDesc "" + + /** A `Catch` object which catches non-fatal exceptions. + * @group canned-behavior + **/ + final def nonFatalCatch[T]: Catch[T] = new Catch(nonFatalCatcher[T]) withDesc "" + + /** Creates a `Catch` object which will catch any of the supplied exceptions. + * Since the returned `Catch` object has no specific logic defined and will simply + * rethrow the exceptions it catches, you will typically want to call `opt`, + * `either` or `withTry` on the return value, or assign custom logic by calling "withApply". + * + * Note that `Catch` objects automatically rethrow `ControlExceptions` and others + * which should only be caught in exceptional circumstances. If you really want + * to catch exactly what you specify, use `catchingPromiscuously` instead. + * @group composition-catch + */ + def catching[T](exceptions: Class[_]*): Catch[T] = + new Catch(pfFromExceptions(exceptions : _*)) withDesc (exceptions map (_.getName) mkString ", ") + + def catching[T](c: Catcher[T]): Catch[T] = new Catch(c) + + /** Creates a `Catch` object which will catch any of the supplied exceptions. + * Unlike "catching" which filters out those in shouldRethrow, this one will + * catch whatever you ask of it including $protectedExceptions. + * @group composition-catch-promiscuously + */ + def catchingPromiscuously[T](exceptions: Class[_]*): Catch[T] = catchingPromiscuously(pfFromExceptions(exceptions : _*)) + def catchingPromiscuously[T](c: Catcher[T]): Catch[T] = new Catch(c, None, _ => false) + + /** Creates a `Catch` object which catches and ignores any of the supplied exceptions. + * @group composition-catch + */ + def ignoring(exceptions: Class[_]*): Catch[Unit] = + catching(exceptions: _*) withApply (_ => ()) + + /** Creates a `Catch` object which maps all the supplied exceptions to `None`. + * @group composition-catch + */ + def failing[T](exceptions: Class[_]*): Catch[Option[T]] = + catching(exceptions: _*) withApply (_ => None) + + /** Creates a `Catch` object which maps all the supplied exceptions to the given value. + * @group composition-catch + */ + def failAsValue[T](exceptions: Class[_]*)(value: => T): Catch[T] = + catching(exceptions: _*) withApply (_ => value) + + class By[T,R](f: T => R) { + def by(x: T): R = f(x) + } + + /** Returns a partially constructed `Catch` object, which you must give + * an exception handler function as an argument to `by`. + * @example + * {{{ + * handling(classOf[MalformedURLException], classOf[NullPointerException]) by (_.printStackTrace) + * }}} + * @group dsl + */ + def handling[T](exceptions: Class[_]*): By[Throwable => T, Catch[T]] = { + def fun(f: Throwable => T): Catch[T] = catching(exceptions: _*) withApply f + new By[Throwable => T, Catch[T]](fun) + } + + /** Returns a `Catch` object with no catch logic and the argument as the finally logic. + * @group composition-finally + */ + def ultimately[T](body: => Unit): Catch[T] = noCatch andFinally body + + /** Creates a `Catch` object which unwraps any of the supplied exceptions. + * @group composition-catch + */ + def unwrapping[T](exceptions: Class[_]*): Catch[T] = { + @tailrec + def unwrap(x: Throwable): Throwable = + if (wouldMatch(x, exceptions) && x.getCause != null) unwrap(x.getCause) + else x + + catching(exceptions: _*) withApply (x => throw unwrap(x)) + } + + /** Private **/ + private def wouldMatch(x: Throwable, classes: scala.collection.Seq[Class[_]]): Boolean = + classes exists (_ isAssignableFrom x.getClass) + + private def pfFromExceptions(exceptions: Class[_]*): PartialFunction[Throwable, Nothing] = + { case x if wouldMatch(x, exceptions) => throw x } +} diff --git a/library/src/scala/util/control/NoStackTrace.scala b/library/src/scala/util/control/NoStackTrace.scala new file mode 100644 index 000000000000..59d53e02780c --- /dev/null +++ b/library/src/scala/util/control/NoStackTrace.scala @@ -0,0 +1,37 @@ +/* + * 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 util.control + +import scala.language.`2.13` + +/** A trait for exceptions which, for efficiency reasons, do not + * fill in the stack trace. Stack trace suppression can be disabled + * on a global basis via a system property wrapper in + * [[scala.sys.SystemProperties]]. + * + * @note Since JDK 1.7, a similar effect can be achieved with `class Ex extends Throwable(..., writableStackTrace = false)` + */ +trait NoStackTrace extends Throwable { + override def fillInStackTrace(): Throwable = + if (NoStackTrace.noSuppression) super.fillInStackTrace() + else this +} + +object NoStackTrace { + final def noSuppression = _noSuppression + + // two-stage init to make checkinit happy, since sys.SystemProperties.noTraceSuppression.value calls back into NoStackTrace.noSuppression + final private[this] var _noSuppression = false + _noSuppression = System.getProperty("scala.control.noTraceSuppression", "").equalsIgnoreCase("true") +} diff --git a/library/src/scala/util/control/NonFatal.scala b/library/src/scala/util/control/NonFatal.scala new file mode 100644 index 000000000000..9c56eef7cb02 --- /dev/null +++ b/library/src/scala/util/control/NonFatal.scala @@ -0,0 +1,51 @@ +/* + * 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 util.control + +import scala.language.`2.13` + +/** + * Extractor of non-fatal Throwables. Will not match fatal errors like `VirtualMachineError` + * (for example, `OutOfMemoryError` and `StackOverflowError`, subclasses of `VirtualMachineError`), `ThreadDeath`, + * `LinkageError`, `InterruptedException`, `ControlThrowable`. + * + * Note that [[scala.util.control.ControlThrowable]], an internal Throwable, is not matched by + * `NonFatal` (and would therefore be thrown). + * + * For example, all harmless Throwables can be caught by: + * {{{ + * try { + * // dangerous stuff + * } catch { + * case NonFatal(e) => log.error(e, "Something not that bad.") + * // or + * case e if NonFatal(e) => log.error(e, "Something not that bad.") + * } + * }}} + */ +object NonFatal { + /** + * Returns true if the provided `Throwable` is to be considered non-fatal, or false if it is to be considered fatal + */ + @annotation.nowarn("cat=deprecation") // avoid warning on mention of ThreadDeath + def apply(t: Throwable): Boolean = t match { + // VirtualMachineError includes OutOfMemoryError and other fatal errors + case _: VirtualMachineError | _: ThreadDeath | _: InterruptedException | _: LinkageError | _: ControlThrowable => false + case _ => true + } + /** + * Returns Some(t) if NonFatal(t) == true, otherwise None + */ + def unapply(t: Throwable): Option[Throwable] = if (apply(t)) Some(t) else None +} diff --git a/library/src/scala/util/control/TailCalls.scala b/library/src/scala/util/control/TailCalls.scala new file mode 100644 index 000000000000..76f0e2dd8e33 --- /dev/null +++ b/library/src/scala/util/control/TailCalls.scala @@ -0,0 +1,116 @@ +/* + * 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.util.control + +import scala.language.`2.13` +import annotation.tailrec + +/** Methods exported by this object implement tail calls via trampolining. + * + * Tail calling methods must either return their result using `done` or call the + * next method using `tailcall`. Both return an instance of `TailRec`. The result + * of evaluating a tailcalling function can be retrieved from a `TailRec` + * value using method `result`. + * + * Implemented as described in "Stackless Scala with Free Monads" + * [[https://blog.higher-order.com/assets/trampolines.pdf]] + * + * Here's a usage example: + * {{{ + * import scala.util.control.TailCalls._ + * + * def isEven(xs: List[Int]): TailRec[Boolean] = + * if (xs.isEmpty) done(true) else tailcall(isOdd(xs.tail)) + * + * def isOdd(xs: List[Int]): TailRec[Boolean] = + * if (xs.isEmpty) done(false) else tailcall(isEven(xs.tail)) + * + * isEven((1 to 100000).toList).result + * + * def fib(n: Int): TailRec[Int] = + * if (n < 2) done(n) else for { + * x <- tailcall(fib(n - 1)) + * y <- tailcall(fib(n - 2)) + * } yield x + y + * + * fib(40).result + * }}} + */ +object TailCalls { + + /** This class represents a tailcalling computation. + */ + sealed abstract class TailRec[+A] { + + /** Continue the computation with `f`. */ + final def map[B](f: A => B): TailRec[B] = flatMap(a => Call(() => Done(f(a)))) + + /** Continue the computation with `f` and merge the trampolining + * of this computation with that of `f`. */ + final def flatMap[B](f: A => TailRec[B]): TailRec[B] = this match { + case Done(a) => Call(() => f(a)) + case Call(_) => Cont(this, f) + // Take advantage of the monad associative law to optimize the size of the required stack + case c: Cont[a1, b1] => Cont(c.a, (x: a1) => c.f(x).flatMap(f)) + } + + /** Returns either the next step of the tailcalling computation, + * or the result if there are no more steps. */ + @tailrec final def resume: Either[() => TailRec[A], A] = this match { + case Done(a) => Right(a) + case Call(k) => Left(k) + case Cont(a, f) => a match { + case Done(v) => f(v).resume + case Call(k) => Left(() => k().flatMap(f)) + case Cont(b, g) => b.flatMap(x => g(x).flatMap(f)).resume + } + } + + /** Returns the result of the tailcalling computation. + */ + @tailrec final def result: A = this match { + case Done(a) => a + case Call(t) => t().result + case Cont(a, f) => a match { + case Done(v) => f(v).result + case Call(t) => t().flatMap(f).result + case Cont(b, g) => b.flatMap(x => g(x).flatMap(f)).result + } + } + } + + /** Internal class representing a tailcall. */ + protected case class Call[A](rest: () => TailRec[A]) extends TailRec[A] + + /** Internal class representing the final result returned from a tailcalling + * computation. */ + protected case class Done[A](value: A) extends TailRec[A] + + /** Internal class representing a continuation with function A => TailRec[B]. + * It is needed for the flatMap to be implemented. */ + protected case class Cont[A, B](a: TailRec[A], f: A => TailRec[B]) extends TailRec[B] + + /** Perform a tailcall. + * @param rest the expression to be evaluated in the tailcall + * @return a `TailRec` object representing the expression `rest` + */ + def tailcall[A](rest: => TailRec[A]): TailRec[A] = Call(() => rest) + + /** Return the final result from a tailcalling computation. + * @param `result` the result value + * @return a `TailRec` object representing a computation which immediately + * returns `result` + */ + def done[A](result: A): TailRec[A] = Done(result) + +} diff --git a/library/src/scala/util/hashing/ByteswapHashing.scala b/library/src/scala/util/hashing/ByteswapHashing.scala new file mode 100644 index 000000000000..699f385d3366 --- /dev/null +++ b/library/src/scala/util/hashing/ByteswapHashing.scala @@ -0,0 +1,37 @@ +/* + * 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 util.hashing + +import scala.language.`2.13` + +/** A fast multiplicative hash by Phil Bagwell. + */ +final class ByteswapHashing[T] extends Hashing[T] { + + def hash(v: T) = byteswap32(v.##) + +} + + +object ByteswapHashing { + + private class Chained[T](h: Hashing[T]) extends Hashing[T] { + def hash(v: T) = byteswap32(h.hash(v)) + } + + /** Composes another `Hashing` with the Byteswap hash. + */ + def chain[T](h: Hashing[T]): Hashing[T] = new Chained(h) + +} diff --git a/library/src/scala/util/hashing/Hashing.scala b/library/src/scala/util/hashing/Hashing.scala new file mode 100644 index 000000000000..62080f0ffb8f --- /dev/null +++ b/library/src/scala/util/hashing/Hashing.scala @@ -0,0 +1,43 @@ +/* + * 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 util.hashing + +import scala.language.`2.13` +import scala.annotation.implicitNotFound + +/** `Hashing` is a trait whose instances each represent a strategy for hashing + * instances of a type. + * + * `Hashing`'s companion object defines a default hashing strategy for all + * objects - it calls their `##` method. + * + * Note: when using a custom `Hashing`, make sure to use it with the `Equiv` + * such that if any two objects are equal, then their hash codes must be equal. + */ +@implicitNotFound(msg = "No implicit Hashing defined for ${T}.") +trait Hashing[T] extends Serializable { + def hash(x: T): Int +} + +object Hashing { + final class Default[T] extends Hashing[T] { + def hash(x: T) = x.## + } + + implicit def default[T]: Default[T] = new Default[T] + + def fromFunction[T](f: T => Int) = new Hashing[T] { + def hash(x: T) = f(x) + } +} diff --git a/library/src/scala/util/hashing/MurmurHash3.scala b/library/src/scala/util/hashing/MurmurHash3.scala new file mode 100644 index 000000000000..e828ff771819 --- /dev/null +++ b/library/src/scala/util/hashing/MurmurHash3.scala @@ -0,0 +1,483 @@ +/* + * 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 util.hashing + +import scala.language.`2.13` +import java.lang.Integer.{ rotateLeft => rotl } + +private[hashing] class MurmurHash3 { + /** Mix in a block of data into an intermediate hash value. */ + final def mix(hash: Int, data: Int): Int = { + var h = mixLast(hash, data) + h = rotl(h, 13) + h * 5 + 0xe6546b64 + } + + /** May optionally be used as the last mixing step. Is a little bit faster than mix, + * as it does no further mixing of the resulting hash. For the last element this is not + * necessary as the hash is thoroughly mixed during finalization anyway. */ + final def mixLast(hash: Int, data: Int): Int = { + var k = data + + k *= 0xcc9e2d51 + k = rotl(k, 15) + k *= 0x1b873593 + + hash ^ k + } + + /** Finalize a hash to incorporate the length and make sure all bits avalanche. */ + final def finalizeHash(hash: Int, length: Int): Int = avalanche(hash ^ length) + + /** Force all bits of the hash to avalanche. Used for finalizing the hash. */ + private final def avalanche(hash: Int): Int = { + var h = hash + + h ^= h >>> 16 + h *= 0x85ebca6b + h ^= h >>> 13 + h *= 0xc2b2ae35 + h ^= h >>> 16 + + h + } + + private[scala] def tuple2Hash(x: Int, y: Int, seed: Int): Int = { + var h = seed + h = mix(h, "Tuple2".hashCode) + h = mix(h, x) + h = mix(h, y) + finalizeHash(h, 2) + } + + // @deprecated("use `caseClassHash` instead", "2.13.17") + // The deprecation is commented because this method is called by the synthetic case class hashCode. + // In this case, the `seed` already has the case class name mixed in and `ignorePrefix` is set to true. + // Case classes compiled before 2.13.17 call this method with `productSeed` and `ignorePrefix = false`. + // See `productHashCode` in `SyntheticMethods` for details. + final def productHash(x: Product, seed: Int, ignorePrefix: Boolean = false): Int = { + val arr = x.productArity + if (arr == 0) + if (!ignorePrefix) x.productPrefix.hashCode else seed + else { + var h = seed + if (!ignorePrefix) h = mix(h, x.productPrefix.hashCode) + var i = 0 + while (i < arr) { + h = mix(h, x.productElement(i).##) + i += 1 + } + finalizeHash(h, arr) + } + } + + /** See the [[MurmurHash3.caseClassHash(x:Product,caseClassName:String)]] overload */ + final def caseClassHash(x: Product, seed: Int, caseClassName: String): Int = { + val arr = x.productArity + val aye = (if (caseClassName != null) caseClassName else x.productPrefix).hashCode + if (arr == 0) aye + else { + var h = seed + h = mix(h, aye) + var i = 0 + while (i < arr) { + h = mix(h, x.productElement(i).##) + i += 1 + } + finalizeHash(h, arr) + } + } + + + /** Compute the hash of a string */ + final def stringHash(str: String, seed: Int): Int = { + var h = seed + var i = 0 + while (i + 1 < str.length) { + val data = (str.charAt(i) << 16) + str.charAt(i + 1) + h = mix(h, data) + i += 2 + } + if (i < str.length) h = mixLast(h, str.charAt(i).toInt) + finalizeHash(h, str.length) + } + + /** Compute a hash that is symmetric in its arguments - that is a hash + * where the order of appearance of elements does not matter. + * This is useful for hashing sets, for example. + */ + final def unorderedHash(xs: IterableOnce[Any], seed: Int): Int = { + var a, b, n = 0 + var c = 1 + val iterator = xs.iterator + while (iterator.hasNext) { + val x = iterator.next() + val h = x.## + a += h + b ^= h + c *= h | 1 + n += 1 + } + var h = seed + h = mix(h, a) + h = mix(h, b) + h = mixLast(h, c) + finalizeHash(h, n) + } + + /** Compute a hash that depends on the order of its arguments. Potential range + * hashes are recognized to produce a hash that is compatible with rangeHash. + */ + final def orderedHash(xs: IterableOnce[Any], seed: Int): Int = { + val it = xs.iterator + var h = seed + if(!it.hasNext) return finalizeHash(h, 0) + val x0 = it.next() + if(!it.hasNext) return finalizeHash(mix(h, x0.##), 1) + val x1 = it.next() + + val initial = x0.## + h = mix(h, initial) + val h0 = h + var prev = x1.## + val rangeDiff = prev - initial + var i = 2 + while (it.hasNext) { + h = mix(h, prev) + val hash = it.next().## + if(rangeDiff != hash - prev || rangeDiff == 0) { + h = mix(h, hash) + i += 1 + while (it.hasNext) { + h = mix(h, it.next().##) + i += 1 + } + return finalizeHash(h, i) + } + prev = hash + i += 1 + } + avalanche(mix(mix(h0, rangeDiff), prev)) + + } + + /** Compute the hash of an array. Potential range hashes are recognized to produce a + * hash that is compatible with rangeHash. + */ + final def arrayHash[@specialized T](a: Array[T], seed: Int): Int = { + var h = seed + val l = a.length + l match { + case 0 => + finalizeHash(h, 0) + case 1 => + finalizeHash(mix(h, a(0).##), 1) + case _ => + val initial = a(0).## + h = mix(h, initial) + val h0 = h + var prev = a(1).## + val rangeDiff = prev - initial + var i = 2 + while (i < l) { + h = mix(h, prev) + val hash = a(i).## + if(rangeDiff != hash - prev || rangeDiff == 0) { + h = mix(h, hash) + i += 1 + while (i < l) { + h = mix(h, a(i).##) + i += 1 + } + return finalizeHash(h, l) + } + prev = hash + i += 1 + } + avalanche(mix(mix(h0, rangeDiff), prev)) + } + } + + /** Compute the hash of a Range with at least 2 elements. Ranges with fewer + * elements need to use seqHash instead. The `last` parameter must be the + * actual last element produced by a Range, not the nominal `end`. + */ + final def rangeHash(start: Int, step: Int, last: Int, seed: Int): Int = + avalanche(mix(mix(mix(seed, start), step), last)) + + /** Compute the hash of a byte array. Faster than arrayHash, because + * it hashes 4 bytes at once. Note that the result is not compatible with + * arrayHash! + */ + final def bytesHash(data: Array[Byte], seed: Int): Int = { + var len = data.length + var h = seed + + // Body + var i = 0 + while(len >= 4) { + var k = data(i + 0) & 0xFF + k |= (data(i + 1) & 0xFF) << 8 + k |= (data(i + 2) & 0xFF) << 16 + k |= (data(i + 3) & 0xFF) << 24 + + h = mix(h, k) + + i += 4 + len -= 4 + } + + // Tail + var k = 0 + if(len == 3) k ^= (data(i + 2) & 0xFF) << 16 + if(len >= 2) k ^= (data(i + 1) & 0xFF) << 8 + if(len >= 1) { + k ^= (data(i + 0) & 0xFF) + h = mixLast(h, k) + } + + // Finalization + finalizeHash(h, data.length) + } + + /** Compute the hash of an IndexedSeq. Potential range hashes are recognized to produce a + * hash that is compatible with rangeHash. + */ + final def indexedSeqHash(a: scala.collection.IndexedSeq[Any], seed: Int): Int = { + var h = seed + val l = a.length + l match { + case 0 => + finalizeHash(h, 0) + case 1 => + finalizeHash(mix(h, a(0).##), 1) + case _ => + val initial = a(0).## + h = mix(h, initial) + val h0 = h + var prev = a(1).## + val rangeDiff = prev - initial + var i = 2 + while (i < l) { + h = mix(h, prev) + val hash = a(i).## + if(rangeDiff != hash - prev || rangeDiff == 0) { + h = mix(h, hash) + i += 1 + while (i < l) { + h = mix(h, a(i).##) + i += 1 + } + return finalizeHash(h, l) + } + prev = hash + i += 1 + } + avalanche(mix(mix(h0, rangeDiff), prev)) + } + } + + /** Compute the hash of a List. Potential range hashes are recognized to produce a + * hash that is compatible with rangeHash. + */ + final def listHash(xs: scala.collection.immutable.List[_], seed: Int): Int = { + var n = 0 + var h = seed + var rangeState = 0 // 0 = no data, 1 = first elem read, 2 = has valid diff, 3 = invalid + var rangeDiff = 0 + var prev = 0 + var initial = 0 + var elems = xs + while (!elems.isEmpty) { + val head = elems.head + val tail = elems.tail + val hash = head.## + h = mix(h, hash) + rangeState match { + case 0 => + initial = hash + rangeState = 1 + case 1 => + rangeDiff = hash - prev + rangeState = 2 + case 2 => + if(rangeDiff != hash - prev || rangeDiff == 0) rangeState = 3 + case _ => + } + prev = hash + n += 1 + elems = tail + } + if(rangeState == 2) rangeHash(initial, rangeDiff, prev, seed) + else finalizeHash(h, n) + } +} + +/** + * An implementation of Austin Appleby's MurmurHash 3 algorithm + * (MurmurHash3_x86_32). This object contains methods that hash + * values of various types as well as means to construct `Hashing` + * objects. + * + * This algorithm is designed to generate well-distributed non-cryptographic + * hashes. It is designed to hash data in 32 bit chunks (ints). + * + * The mix method needs to be called at each step to update the intermediate + * hash value. For the last chunk to incorporate into the hash mixLast may + * be used instead, which is slightly faster. Finally finalizeHash needs to + * be called to compute the final hash value. + * + * This is based on the earlier MurmurHash3 code by Rex Kerr, but the + * MurmurHash3 algorithm was since changed by its creator Austin Appleby + * to remedy some weaknesses and improve performance. This represents the + * latest and supposedly final version of the algorithm (revision 136). Even + * so, test the generated hashes in between Scala versions, even for point + * releases, as fast, non-cryptographic hashing algorithms evolve rapidly. + * + * @see [[https://github.com/aappleby/smhasher]] + */ +object MurmurHash3 extends MurmurHash3 { + final val arraySeed = 0x3c074a61 + final val stringSeed = 0xf7ca7fd2 + final val productSeed = 0xcafebabe + final val symmetricSeed = 0xb592f7ae + final val traversableSeed = 0xe73a8b15 + final val seqSeed = "Seq".hashCode + final val mapSeed = "Map".hashCode + final val setSeed = "Set".hashCode + + def arrayHash[@specialized T](a: Array[T]): Int = arrayHash(a, arraySeed) + def bytesHash(data: Array[Byte]): Int = bytesHash(data, arraySeed) + def orderedHash(xs: IterableOnce[Any]): Int = orderedHash(xs, symmetricSeed) + def stringHash(x: String): Int = stringHash(x, stringSeed) + def unorderedHash(xs: IterableOnce[Any]): Int = unorderedHash(xs, traversableSeed) + def rangeHash(start: Int, step: Int, last: Int): Int = rangeHash(start, step, last, seqSeed) + + @deprecated("use `caseClassHash` instead", "2.13.17") + def productHash(x: Product): Int = caseClassHash(x, productSeed, null) + + /** + * Compute the `hashCode` of a case class instance. This method returns the same value as `x.hashCode` + * if `x` is an instance of a case class with the default, synthetic `hashCode`. + * + * This method can be used to implement case classes with a cached `hashCode`: + * {{{ + * case class C(data: Data) { + * override lazy val hashCode: Int = MurmurHash3.caseClassHash(this) + * } + * }}} + * + * '''NOTE''': For case classes (or subclasses) that override `productPrefix`, the `caseClassName` parameter + * needs to be specified in order to obtain the same result as the synthetic `hashCode`. Otherwise, the value + * is not in sync with the case class `equals` method (scala/bug#13033). + * + * {{{ + * scala> case class C(x: Int) { override def productPrefix = "Y" } + * + * scala> C(1).hashCode + * val res0: Int = -668012062 + * + * scala> MurmurHash3.caseClassHash(C(1)) + * val res1: Int = 1015658380 + * + * scala> MurmurHash3.caseClassHash(C(1), "C") + * val res2: Int = -668012062 + * }}} + */ + def caseClassHash(x: Product, caseClassName: String = null): Int = caseClassHash(x, productSeed, caseClassName) + + private[scala] def arraySeqHash[@specialized T](a: Array[T]): Int = arrayHash(a, seqSeed) + private[scala] def tuple2Hash(x: Any, y: Any): Int = tuple2Hash(x.##, y.##, productSeed) + + /** To offer some potential for optimization. + */ + def seqHash(xs: scala.collection.Seq[_]): Int = xs match { + case xs: scala.collection.IndexedSeq[_] => indexedSeqHash(xs, seqSeed) + case xs: List[_] => listHash(xs, seqSeed) + case xs => orderedHash(xs, seqSeed) + } + + def mapHash(xs: scala.collection.Map[_, _]): Int = { + if (xs.isEmpty) emptyMapHash + else { + class accum extends Function2[Any, Any, Unit] { + var a, b, n = 0 + var c = 1 + override def apply(k: Any, v: Any): Unit = { + val h = tuple2Hash(k, v) + a += h + b ^= h + c *= h | 1 + n += 1 + } + } + val accum = new accum + var h = mapSeed + xs.foreachEntry(accum) + h = mix(h, accum.a) + h = mix(h, accum.b) + h = mixLast(h, accum.c) + finalizeHash(h, accum.n) + } + } + + private[scala] val emptyMapHash = unorderedHash(Nil, mapSeed) + def setHash(xs: scala.collection.Set[_]): Int = unorderedHash(xs, setSeed) + + class ArrayHashing[@specialized T] extends Hashing[Array[T]] { + def hash(a: Array[T]) = arrayHash(a) + } + + def arrayHashing[@specialized T] = new ArrayHashing[T] + + def bytesHashing = new Hashing[Array[Byte]] { + def hash(data: Array[Byte]) = bytesHash(data) + } + + def orderedHashing = new Hashing[IterableOnce[Any]] { + def hash(xs: IterableOnce[Any]) = orderedHash(xs) + } + + @deprecated("use `caseClassHashing` instead", "2.13.17") + def productHashing = new Hashing[Product] { + def hash(x: Product) = caseClassHash(x) + } + + def caseClassHashing = new Hashing[Product] { + def hash(x: Product) = caseClassHash(x) + } + + def stringHashing = new Hashing[String] { + def hash(x: String) = stringHash(x) + } + + def unorderedHashing = new Hashing[IterableOnce[Any]] { + def hash(xs: IterableOnce[Any]) = unorderedHash(xs) + } + +// /** All this trouble and foreach still appears faster. +// * Leaving in place in case someone would like to investigate further. +// */ +// def linearSeqHash(xs: scala.collection.LinearSeq[_], seed: Int): Int = { +// var n = 0 +// var h = seed +// var elems = xs +// while (elems.nonEmpty) { +// h = mix(h, elems.head.##) +// n += 1 +// elems = elems.tail +// } +// finalizeHash(h, n) +// } +} diff --git a/library/src/scala/util/hashing/package.scala b/library/src/scala/util/hashing/package.scala new file mode 100644 index 000000000000..1524903bd992 --- /dev/null +++ b/library/src/scala/util/hashing/package.scala @@ -0,0 +1,37 @@ +/* + * 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 util + +import scala.language.`2.13` + +package object hashing { + + /** Fast multiplicative hash with a nice distribution. + */ + def byteswap32(v: Int): Int = { + var hc = v * 0x9e3775cd + hc = java.lang.Integer.reverseBytes(hc) + hc * 0x9e3775cd + } + + /** Fast multiplicative hash with a nice distribution + * for 64-bit values. + */ + def byteswap64(v: Long): Long = { + var hc = v * 0x9e3775cd9e3775cdL + hc = java.lang.Long.reverseBytes(hc) + hc * 0x9e3775cd9e3775cdL + } + +} diff --git a/library/src/scala/util/matching/Regex.scala b/library/src/scala/util/matching/Regex.scala new file mode 100644 index 000000000000..8e86234ebdb8 --- /dev/null +++ b/library/src/scala/util/matching/Regex.scala @@ -0,0 +1,923 @@ +/* + * 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. + */ + +/** + * This package is concerned with regular expression (regex) matching against strings, + * with the main goal of pulling out information from those matches, or replacing + * them with something else. + * + * [[scala.util.matching.Regex]] is the class users instantiate to do regular expression matching. + * + * The companion object to [[scala.util.matching.Regex]] contains supporting members: + * * [[scala.util.matching.Regex.Match]] makes more information about a match available. + * * [[scala.util.matching.Regex.MatchIterator]] is used to iterate over matched strings. + * * [[scala.util.matching.Regex.MatchData]] is just a base trait for the above classes. + * * [[scala.util.matching.Regex.Groups]] extracts group from a [[scala.util.matching.Regex.Match]] + * without recomputing the match. + */ +package scala.util.matching + +import scala.language.`2.13` +import scala.collection.AbstractIterator +import java.util.regex.{ Pattern, Matcher } + +/** A regular expression is used to determine whether a string matches a pattern + * and, if it does, to extract or transform the parts that match. + * + * === Usage === + + * This class delegates to the [[https://docs.oracle.com/javase/8/docs/api/java/util/regex/package-summary.html java.util.regex]] package of the Java Platform. + * See the documentation for [[java.util.regex.Pattern]] for details about + * the regular expression syntax for pattern strings. + * + * An instance of `Regex` represents a compiled regular expression pattern. + * Since compilation is expensive, frequently used `Regex`es should be constructed + * once, outside of loops and perhaps in a companion object. + * + * The canonical way to create a `Regex` is by using the method `r`, provided + * implicitly for strings: + * + * {{{ + * val date = raw"(\d{4})-(\d{2})-(\d{2})".r + * }}} + * + * Since escapes are not processed in multi-line string literals, using triple quotes + * avoids having to escape the backslash character, so that `"\\d"` can be written `"""\d"""`. + * The same result is achieved with certain interpolators, such as `raw"\d".r` or + * a custom interpolator `r"\d"` that also compiles the `Regex`. + * + * === Extraction === + * To extract the capturing groups when a `Regex` is matched, use it as + * an extractor in a pattern match: + * + * {{{ + * "2004-01-20" match { + * case date(year, month, day) => s"\$year was a good year for PLs." + * } + * }}} + * + * To check only whether the `Regex` matches, ignoring any groups, + * use a sequence wildcard: + * + * {{{ + * "2004-01-20" match { + * case date(_*) => "It's a date!" + * } + * }}} + * + * That works because a `Regex` extractor produces a sequence of strings. + * Extracting only the year from a date could also be expressed with + * a sequence wildcard: + * + * {{{ + * "2004-01-20" match { + * case date(year, _*) => s"\$year was a good year for PLs." + * } + * }}} + * + * In a pattern match, `Regex` normally matches the entire input. + * However, an unanchored `Regex` finds the pattern anywhere + * in the input. + * + * {{{ + * val embeddedDate = date.unanchored + * "Date: 2004-01-20 17:25:18 GMT (10 years, 28 weeks, 5 days, 17 hours and 51 minutes ago)" match { + * case embeddedDate("2004", "01", "20") => "A Scala is born." + * } + * }}} + * + * === Find Matches === + * To find or replace matches of the pattern, use the various find and replace methods. + * For each method, there is a version for working with matched strings and + * another for working with `Match` objects. + * + * For example, pattern matching with an unanchored `Regex`, as in the previous example, + * can also be accomplished using `findFirstMatchIn`. The `findFirst` methods return an `Option` + * which is non-empty if a match is found, or `None` for no match: + * + * {{{ + * val dates = "Important dates in history: 2004-01-20, 1958-09-05, 2010-10-06, 2011-07-15" + * val firstDate = date.findFirstIn(dates).getOrElse("No date found.") + * val firstYear = for (m <- date.findFirstMatchIn(dates)) yield m.group(1) + * }}} + * + * To find all matches: + * + * {{{ + * val allYears = for (m <- date.findAllMatchIn(dates)) yield m.group(1) + * }}} + * + * To check whether input is matched by the regex: + * + * {{{ + * date.matches("2018-03-01") // true + * date.matches("Today is 2018-03-01") // false + * date.unanchored.matches("Today is 2018-03-01") // true + * }}} + * + * To iterate over the matched strings, use `findAllIn`, which returns a special iterator + * that can be queried for the `MatchData` of the last match: + * + * {{{ + * val mi = date.findAllIn(dates) + * while (mi.hasNext) { + * val d = mi.next + * if (mi.group(1).toInt < 1960) println(s"\$d: An oldie but goodie.") + * } + * }}} + * + * Although the `MatchIterator` returned by `findAllIn` is used like any `Iterator`, + * with alternating calls to `hasNext` and `next`, `hasNext` has the additional + * side effect of advancing the underlying matcher to the next unconsumed match. + * This effect is visible in the `MatchData` representing the "current match". + * + * {{{ + * val r = "(ab+c)".r + * val s = "xxxabcyyyabbczzz" + * r.findAllIn(s).start // 3 + * val mi = r.findAllIn(s) + * mi.hasNext // true + * mi.start // 3 + * mi.next() // "abc" + * mi.start // 3 + * mi.hasNext // true + * mi.start // 9 + * mi.next() // "abbc" + * }}} + * + * The example shows that methods on `MatchData` such as `start` will advance to + * the first match, if necessary. It also shows that `hasNext` will advance to + * the next unconsumed match, if `next` has already returned the current match. + * + * The current `MatchData` can be captured using the `matchData` method. + * Alternatively, `findAllMatchIn` returns an `Iterator[Match]`, where there + * is no interaction between the iterator and `Match` objects it has already produced. + * + * Note that `findAllIn` finds matches that don't overlap. (See [[findAllIn]] for more examples.) + * + * {{{ + * val num = raw"(\d+)".r + * val all = num.findAllIn("123").toList // List("123"), not List("123", "23", "3") + * }}} + * + * === Replace Text === + * Text replacement can be performed unconditionally or as a function of the current match: + * + * {{{ + * val redacted = date.replaceAllIn(dates, "XXXX-XX-XX") + * val yearsOnly = date.replaceAllIn(dates, m => m.group(1)) + * val months = (0 to 11).map { i => val c = Calendar.getInstance; c.set(2014, i, 1); f"\$c%tb" } + * val reformatted = date.replaceAllIn(dates, _ match { case date(y,m,d) => f"\${months(m.toInt - 1)} \$d, \$y" }) + * }}} + * + * Pattern matching the `Match` against the `Regex` that created it does not reapply the `Regex`. + * In the expression for `reformatted`, each `date` match is computed once. But it is possible to apply a + * `Regex` to a `Match` resulting from a different pattern: + * + * {{{ + * val docSpree = """2011(?:-\d{2}){2}""".r + * val docView = date.replaceAllIn(dates, _ match { + * case docSpree() => "Historic doc spree!" + * case _ => "Something else happened" + * }) + * }}} + * + * @see [[java.util.regex.Pattern]] + * + * @param pattern The compiled pattern + * @param groupNames A mapping from names to indices in capture groups + * + * @define replacementString + * In the replacement String, a dollar sign (`\$`) followed by a number will be + * interpreted as a reference to a group in the matched pattern, with numbers + * 1 through 9 corresponding to the first nine groups, and 0 standing for the + * whole match. Any other character is an error. The backslash (`\`) character + * will be interpreted as an escape character and can be used to escape the + * dollar sign. Use `Regex.quoteReplacement` to escape these characters. + */ +@SerialVersionUID(-2094783597747625537L) +class Regex private[matching](val pattern: Pattern, groupNames: String*) extends Serializable { + outer => + + import Regex._ + + /** Compile a regular expression, supplied as a string, into a pattern that + * can be matched against inputs. + * + * If group names are supplied, they can be used this way: + * + * {{{ + * val namedDate = new Regex("""(\d\d\d\d)-(\d\d)-(\d\d)""", "year", "month", "day") + * val namedYears = for (m <- namedDate findAllMatchIn dates) yield m group "year" + * }}} + * + * Inline group names are preferred over group names supplied to the constructor + * when retrieving matched groups by name. Group names supplied to the constructor + * should be considered deprecated. + * + * This constructor does not support options as flags, which must be + * supplied as inline flags in the pattern string: `(?idmsuxU)`. + * + * @param regex The regular expression to compile. + * @param groupNames Names of capturing groups. + */ + // we cannot add the alternative `def this(regex: String)` in a forward binary compatible way: + // @deprecated("use inline group names like (?X) instead", "2.13.7") + def this(regex: String, groupNames: String*) = this(Pattern.compile(regex), groupNames: _*) + + /** Tries to match a [[java.lang.CharSequence]]. + * + * If the match succeeds, the result is a list of the matching + * groups (or a `null` element if a group did not match any input). + * If the pattern specifies no groups, then the result will be an empty list + * on a successful match. + * + * This method attempts to match the entire input by default; to find the next + * matching subsequence, use an unanchored `Regex`. + * + * For example: + * + * {{{ + * val p1 = "ab*c".r + * val p1Matches = "abbbc" match { + * case p1() => true // no groups + * case _ => false + * } + * val p2 = "a(b*)c".r + * val p2Matches = "abbbc" match { + * case p2(_*) => true // any groups + * case _ => false + * } + * val numberOfB = "abbbc" match { + * case p2(b) => Some(b.length) // one group + * case _ => None + * } + * val p3 = "b*".r.unanchored + * val p3Matches = "abbbc" match { + * case p3() => true // find the b's + * case _ => false + * } + * val p4 = "a(b*)(c+)".r + * val p4Matches = "abbbcc" match { + * case p4(_*) => true // multiple groups + * case _ => false + * } + * val allGroups = "abbbcc" match { + * case p4(all @ _*) => all mkString "/" // "bbb/cc" + * case _ => "" + * } + * val cGroup = "abbbcc" match { + * case p4(_, c) => c + * case _ => "" + * } + * }}} + * + * @param s The string to match + * @return The matches + */ + def unapplySeq(s: CharSequence): Option[List[String]] = { + val m = pattern.matcher(s) + if (runMatcher(m)) Some(List.tabulate(m.groupCount) { i => m.group(i + 1) }) + else None + } + + /** Tries to match the String representation of a [[scala.Char]]. + * + * If the match succeeds, the result is the first matching + * group if any groups are defined, or an empty Sequence otherwise. + * + * For example: + * + * {{{ + * val cat = "cat" + * // the case must consume the group to match + * val r = """(\p{Lower})""".r + * cat(0) match { case r(x) => true } + * cat(0) match { case r(_) => true } + * cat(0) match { case r(_*) => true } + * cat(0) match { case r() => true } // no match + * + * // there is no group to extract + * val r = """\p{Lower}""".r + * cat(0) match { case r(x) => true } // no match + * cat(0) match { case r(_) => true } // no match + * cat(0) match { case r(_*) => true } // matches + * cat(0) match { case r() => true } // matches + * + * // even if there are multiple groups, only one is returned + * val r = """((.))""".r + * cat(0) match { case r(_) => true } // matches + * cat(0) match { case r(_,_) => true } // no match + * }}} + * + * @param c The Char to match + * @return The match + */ + def unapplySeq(c: Char): Option[List[Char]] = { + val m = pattern matcher c.toString + if (runMatcher(m)) { + if (m.groupCount > 0) Some((m group 1).toList) else Some(Nil) + } else None + } + + /** Tries to match on a [[scala.util.matching.Regex.Match]]. + * + * A previously failed match results in None. + * + * If a successful match was made against the current pattern, then that result is used. + * + * Otherwise, this Regex is applied to the previously matched input, + * and the result of that match is used. + */ + def unapplySeq(m: Match): Option[List[String]] = + if (m.matched == null) None + else if (m.matcher.pattern == this.pattern) Regex.extractGroupsFromMatch(m) + else unapplySeq(m.matched) + + // @see UnanchoredRegex + protected def runMatcher(m: Matcher): Boolean = m.matches() + + /** Return all non-overlapping matches of this `Regex` in the given character + * sequence as a [[scala.util.matching.Regex.MatchIterator]], + * which is a special [[scala.collection.Iterator]] that returns the + * matched strings but can also be queried for more data about the last match, + * such as capturing groups and start position. + * + * A `MatchIterator` can also be converted into an iterator + * that returns objects of type [[scala.util.matching.Regex.Match]], + * such as is normally returned by `findAllMatchIn`. + * + * Where potential matches overlap, the first possible match is returned, + * followed by the next match that follows the input consumed by the + * first match: + * + * {{{ + * val hat = "hat[^a]+".r + * val hathaway = "hathatthattthatttt" + * val hats = hat.findAllIn(hathaway).toList // List(hath, hattth) + * val pos = hat.findAllMatchIn(hathaway).map(_.start).toList // List(0, 7) + * }}} + * + * To return overlapping matches, it is possible to formulate a regular expression + * with lookahead (`?=`) that does not consume the overlapping region. + * + * {{{ + * val madhatter = "(h)(?=(at[^a]+))".r + * val madhats = madhatter.findAllMatchIn(hathaway).map { + * case madhatter(x,y) => s"\$x\$y" + * }.toList // List(hath, hatth, hattth, hatttt) + * }}} + * + * Attempting to retrieve match information after exhausting the iterator + * results in [[java.lang.IllegalStateException]]. + * See [[scala.util.matching.Regex.MatchIterator]] for details. + * + * @param source The text to match against. + * @return A [[scala.util.matching.Regex.MatchIterator]] of matched substrings. + * @example {{{for (words <- """\w+""".r findAllIn "A simple example.") yield words}}} + */ + def findAllIn(source: CharSequence): MatchIterator = new Regex.MatchIterator(source, this, groupNames) + + /** Return all non-overlapping matches of this regexp in given character sequence as a + * [[scala.collection.Iterator]] of [[scala.util.matching.Regex.Match]]. + * + * @param source The text to match against. + * @return A [[scala.collection.Iterator]] of [[scala.util.matching.Regex.Match]] for all matches. + * @example {{{for (words <- """\w+""".r findAllMatchIn "A simple example.") yield words.start}}} + */ + def findAllMatchIn(source: CharSequence): Iterator[Match] = { + val matchIterator = findAllIn(source) + new AbstractIterator[Match] { + def hasNext = matchIterator.hasNext + def next(): Match = { + matchIterator.next() + new Match(matchIterator.source, matchIterator.matcher, matchIterator._groupNames).force + } + } + } + + /** Return an optional first matching string of this `Regex` in the given character sequence, + * or None if there is no match. + * + * @param source The text to match against. + * @return An [[scala.Option]] of the first matching string in the text. + * @example {{{"""\w+""".r findFirstIn "A simple example." foreach println // prints "A"}}} + */ + def findFirstIn(source: CharSequence): Option[String] = { + val m = pattern.matcher(source) + if (m.find) Some(m.group) else None + } + + /** Return an optional first match of this `Regex` in the given character sequence, + * or None if it does not exist. + * + * If the match is successful, the [[scala.util.matching.Regex.Match]] can be queried for + * more data. + * + * @param source The text to match against. + * @return A [[scala.Option]] of [[scala.util.matching.Regex.Match]] of the first matching string in the text. + * @example {{{("""[a-z]""".r findFirstMatchIn "A simple example.") map (_.start) // returns Some(2), the index of the first match in the text}}} + */ + def findFirstMatchIn(source: CharSequence): Option[Match] = { + val m = pattern.matcher(source) + if (m.find) Some(new Match(source, m, groupNames)) else None + } + + /** Return an optional match of this `Regex` at the beginning of the + * given character sequence, or None if it matches no prefix + * of the character sequence. + * + * Unlike `findFirstIn`, this method will only return a match at + * the beginning of the input. + * + * @param source The text to match against. + * @return A [[scala.Option]] of the matched prefix. + * @example {{{"""\p{Lower}""".r findPrefixOf "A simple example." // returns None, since the text does not begin with a lowercase letter}}} + */ + def findPrefixOf(source: CharSequence): Option[String] = { + val m = pattern.matcher(source) + if (m.lookingAt) Some(m.group) else None + } + + /** Return an optional match of this `Regex` at the beginning of the + * given character sequence, or None if it matches no prefix + * of the character sequence. + * + * Unlike `findFirstMatchIn`, this method will only return a match at + * the beginning of the input. + * + * @param source The text to match against. + * @return A [[scala.Option]] of the [[scala.util.matching.Regex.Match]] of the matched string. + * @example {{{"""\w+""".r findPrefixMatchOf "A simple example." map (_.after) // returns Some(" simple example.")}}} + */ + def findPrefixMatchOf(source: CharSequence): Option[Match] = { + val m = pattern.matcher(source) + if (m.lookingAt) Some(new Match(source, m, groupNames)) else None + } + + /** Returns whether this `Regex` matches the given character sequence. + * + * Like the extractor, this method takes anchoring into account. + * + * @param source The text to match against + * @return true if and only if `source` matches this `Regex`. + * @see [[Regex#unanchored]] + * @example {{{"""\d+""".r matches "123" // returns true}}} + */ + def matches(source: CharSequence): Boolean = + runMatcher(pattern.matcher(source)) + + /** Replaces all matches by a string. + * + * $replacementString + * + * @param target The string to match + * @param replacement The string that will replace each match + * @return The resulting string + * @example {{{"""\d+""".r replaceAllIn ("July 15", "") // returns "July "}}} + */ + def replaceAllIn(target: CharSequence, replacement: String): String = { + val m = pattern.matcher(target) + m.replaceAll(replacement) + } + + /** + * Replaces all matches using a replacer function. The replacer function takes a + * [[scala.util.matching.Regex.Match]] so that extra information can be obtained + * from the match. For example: + * + * {{{ + * import scala.util.matching.Regex + * val datePattern = new Regex("""(\d\d\d\d)-(\d\d)-(\d\d)""", "year", "month", "day") + * val text = "From 2011-07-15 to 2011-07-17" + * val repl = datePattern replaceAllIn (text, m => s"\${m group "month"}/\${m group "day"}") + * }}} + * + * $replacementString + * + * @param target The string to match. + * @param replacer The function which maps a match to another string. + * @return The target string after replacements. + */ + def replaceAllIn(target: CharSequence, replacer: Match => String): String = { + val it = new Regex.MatchIterator(target, this, groupNames).replacementData + it foreach (md => it replace replacer(md)) + it.replaced + } + + /** + * Replaces some of the matches using a replacer function that returns an [[scala.Option]]. + * The replacer function takes a [[scala.util.matching.Regex.Match]] so that extra + * information can be obtained from the match. For example: + * + * {{{ + * import scala.util.matching.Regex._ + * + * val vars = Map("x" -> "a var", "y" -> """some \$ and \ signs""") + * val text = "A text with variables %x, %y and %z." + * val varPattern = """%(\w+)""".r + * val mapper = (m: Match) => vars get (m group 1) map (quoteReplacement(_)) + * val repl = varPattern replaceSomeIn (text, mapper) + * }}} + * + * $replacementString + * + * @param target The string to match. + * @param replacer The function which optionally maps a match to another string. + * @return The target string after replacements. + */ + def replaceSomeIn(target: CharSequence, replacer: Match => Option[String]): String = { + val it = new Regex.MatchIterator(target, this, groupNames).replacementData + for (matchdata <- it ; replacement <- replacer(matchdata)) + it replace replacement + + it.replaced + } + + /** Replaces the first match by a string. + * + * $replacementString + * + * @param target The string to match + * @param replacement The string that will replace the match + * @return The resulting string + */ + def replaceFirstIn(target: CharSequence, replacement: String): String = { + val m = pattern.matcher(target) + m.replaceFirst(replacement) + } + + /** Splits the provided character sequence around matches of this regexp. + * + * @param toSplit The character sequence to split + * @return The array of strings computed by splitting the + * input around matches of this regexp + */ + def split(toSplit: CharSequence): Array[String] = + pattern.split(toSplit) + + /** Create a new Regex with the same pattern, but no requirement that + * the entire String matches in extractor patterns and [[Regex#matches]]. + * + * Normally, matching on `date` behaves as though the pattern were + * enclosed in anchors, `"^pattern\$"`. + * + * The unanchored `Regex` behaves as though those anchors were removed. + * + * Note that this method does not actually strip any matchers from the pattern. + * + * Calling `anchored` returns the original `Regex`. + * + * {{{ + * val date = """(\d\d\d\d)-(\d\d)-(\d\d)""".r.unanchored + * + * val date(year, month, day) = "Date 2011-07-15" // OK + * + * val copyright: String = "Date of this document: 2011-07-15" match { + * case date(year, month, day) => s"Copyright \$year" // OK + * case _ => "No copyright" + * } + * }}} + * + * @return The new unanchored regex + */ + def unanchored: UnanchoredRegex = new Regex(pattern, groupNames: _*) with UnanchoredRegex { override def anchored = outer } + def anchored: Regex = this + + def regex: String = pattern.pattern + + /** The string defining the regular expression */ + override def toString: String = regex +} + +/** A [[Regex]] that finds the first match when used in a pattern match. + * + * @see [[Regex#unanchored]] + */ +trait UnanchoredRegex extends Regex { + override protected def runMatcher(m: Matcher): Boolean = m.find() + override def unanchored: UnanchoredRegex = this +} + +/** This object defines inner classes that describe + * regex matches and helper objects. + */ +object Regex { + + /** This class provides methods to access + * the details of a match. + */ + trait MatchData { + + /** Basically, wraps a platform Matcher. */ + protected def matcher: Matcher + + /** The source from which the match originated */ + val source: CharSequence + + /** The names of the groups, or an empty sequence if none defined */ + @deprecated("groupNames does not include inline group names, and should not be used anymore", "2.13.7") + val groupNames: Seq[String] + + /** The number of capturing groups in the pattern. + * (For a given successful match, some of those groups may not have matched any input.) + */ + def groupCount: Int + + /** The index of the first matched character, or -1 if nothing was matched */ + def start: Int + + /** The index of the first matched character in group `i`, + * or -1 if nothing was matched for that group. + */ + def start(i: Int): Int + + /** The index following the last matched character, or -1 if nothing was matched. */ + def end: Int + + /** The index following the last matched character in group `i`, + * or -1 if nothing was matched for that group. + */ + def end(i: Int): Int + + /** The matched string, or `null` if nothing was matched. */ + def matched: String = + if (start >= 0) source.subSequence(start, end).toString + else null + + /** The matched string in group `i`, + * or `null` if nothing was matched. + */ + def group(i: Int): String = + if (start(i) >= 0) source.subSequence(start(i), end(i)).toString + else null + + /** All capturing groups, i.e., not including group(0). */ + def subgroups: List[String] = (1 to groupCount).toList map group + + /** The char sequence before first character of match, + * or `null` if nothing was matched. + */ + def before: CharSequence = + if (start >= 0) source.subSequence(0, start) + else null + + /** The char sequence before first character of match in group `i`, + * or `null` if nothing was matched for that group. + */ + def before(i: Int): CharSequence = + if (start(i) >= 0) source.subSequence(0, start(i)) + else null + + /** Returns char sequence after last character of match, + * or `null` if nothing was matched. + */ + def after: CharSequence = + if (end >= 0) source.subSequence(end, source.length) + else null + + /** The char sequence after last character of match in group `i`, + * or `null` if nothing was matched for that group. + */ + def after(i: Int): CharSequence = + if (end(i) >= 0) source.subSequence(end(i), source.length) + else null + + @scala.annotation.nowarn("msg=deprecated") + private def groupNamesNowarn: Seq[String] = groupNames + + private[this] lazy val nameToIndex: Map[String, Int] = + Map[String, Int]() ++ ("" :: groupNamesNowarn.toList).zipWithIndex + + /** Returns the group with the given name. + * + * Uses explicit group names when supplied; otherwise, + * queries the underlying implementation for inline named groups. + * Not all platforms support inline group names. + * + * @param id The group name + * @return The requested group + * @throws IllegalArgumentException if the requested group name is not defined + */ + def group(id: String): String = ( + if (groupNamesNowarn.isEmpty) + matcher group id + else + nameToIndex.get(id) match { + case Some(index) => group(index) + case None => matcher group id + } + ) + + /** The matched string; equivalent to `matched.toString`. */ + override def toString: String = matched + } + + /** Provides information about a successful match. */ + class Match(val source: CharSequence, + protected[matching] val matcher: Matcher, + _groupNames: Seq[String]) extends MatchData { + + @deprecated("groupNames does not include inline group names, and should not be used anymore", "2.13.7") + val groupNames: Seq[String] = _groupNames + + /** The index of the first matched character. */ + val start: Int = matcher.start + + /** The index following the last matched character. */ + val end: Int = matcher.end + + /** The number of subgroups. */ + def groupCount: Int = matcher.groupCount + + private[this] lazy val starts: Array[Int] = + Array.tabulate(groupCount + 1) { matcher.start } + private[this] lazy val ends: Array[Int] = + Array.tabulate(groupCount + 1) { matcher.end } + + /** The index of the first matched character in group `i`. */ + def start(i: Int): Int = starts(i) + + /** The index following the last matched character in group `i`. */ + def end(i: Int): Int = ends(i) + + /** The match itself with matcher-dependent lazy vals forced, + * so that match is valid even once matcher is advanced. + */ + def force: this.type = { starts; ends; this } + } + + /** An extractor object for Matches, yielding the matched string. + * + * This can be used to help writing replacer functions when you + * are not interested in match data. For example: + * + * {{{ + * import scala.util.matching.Regex.Match + * """\w+""".r replaceAllIn ("A simple example.", _ match { case Match(s) => s.toUpperCase }) + * }}} + * + */ + object Match { + def unapply(m: Match): Some[String] = Some(m.matched) + } + + /** An extractor object that yields the groups in the match. Using this extractor + * rather than the original `Regex` ensures that the match is not recomputed. + * + * {{{ + * import scala.util.matching.Regex.Groups + * + * val date = """(\d\d\d\d)-(\d\d)-(\d\d)""".r + * val text = "The doc spree happened on 2011-07-15." + * val day = date replaceAllIn(text, _ match { case Groups(_, month, day) => s"\$month/\$day" }) + * }}} + */ + object Groups { + def unapplySeq(m: Match): Option[Seq[String]] = { + if (m.groupCount > 0) extractGroupsFromMatch(m) else None + } + } + + @inline private def extractGroupsFromMatch(m: Match): Option[List[String]] = + Some(List.tabulate(m.groupCount) { i => m.group(i + 1) }) + + /** A class to step through a sequence of regex matches. + * + * This is an iterator that returns the matched strings. + * + * Queries about match data pertain to the current state of the underlying + * matcher, which is advanced by calling `hasNext` or `next`. + * + * When matches are exhausted, queries about match data will throw + * [[java.lang.IllegalStateException]]. + * + * @see [[java.util.regex.Matcher]] + */ + class MatchIterator(val source: CharSequence, val regex: Regex, private[Regex] val _groupNames: Seq[String]) + extends AbstractIterator[String] with MatchData { self => + + @deprecated("groupNames does not include inline group names, and should not be used anymore", "2.13.7") + val groupNames: Seq[String] = _groupNames + + protected[Regex] val matcher = regex.pattern.matcher(source) + + // 0 = not yet matched, 1 = matched, 2 = advanced to match, 3 = no more matches + private[this] var nextSeen = 0 + + /** Return true if `next` will find a match. + * As a side effect, advance the underlying matcher if necessary; + * queries about the current match data pertain to the underlying matcher. + */ + def hasNext: Boolean = { + nextSeen match { + case 0 => nextSeen = if (matcher.find()) 1 else 3 + case 1 => () + case 2 => nextSeen = 0 ; hasNext + case 3 => () + } + nextSeen == 1 // otherwise, 3 + } + + /** The next matched substring of `source`. + * As a side effect, advance the underlying matcher if necessary. + */ + def next(): String = { + nextSeen match { + case 0 => if (!hasNext) throw new NoSuchElementException ; next() + case 1 => nextSeen = 2 + case 2 => nextSeen = 0 ; next() + case 3 => throw new NoSuchElementException + } + matcher.group + } + + /** Report emptiness. */ + override def toString: String = super[AbstractIterator].toString + + // ensure we're at a match + private[this] def ensure(): Unit = nextSeen match { + case 0 => if (!hasNext) throw new IllegalStateException + case 1 => () + case 2 => () + case 3 => throw new IllegalStateException + } + + /** The index of the first matched character. */ + def start: Int = { ensure() ; matcher.start } + + /** The index of the first matched character in group `i`. */ + def start(i: Int): Int = { ensure() ; matcher.start(i) } + + /** The index of the last matched character. */ + def end: Int = { ensure() ; matcher.end } + + /** The index following the last matched character in group `i`. */ + def end(i: Int): Int = { ensure() ; matcher.end(i) } + + /** The number of subgroups. */ + def groupCount: Int = { ensure() ; matcher.groupCount } + + /** Convert to an iterator that yields MatchData elements instead of Strings. */ + def matchData: Iterator[Match] = new AbstractIterator[Match] { + def hasNext = self.hasNext + def next() = { self.next(); new Match(source, matcher, _groupNames).force } + } + + /** Convert to an iterator that yields MatchData elements instead of Strings and has replacement support. */ + private[matching] def replacementData = new AbstractIterator[Match] with Replacement { + def matcher = self.matcher + def hasNext = self.hasNext + def next() = { self.next(); new Match(source, matcher, _groupNames).force } + } + } + + /** + * A trait able to build a string with replacements assuming it has a matcher. + * Meant to be mixed in with iterators. + */ + private[matching] trait Replacement { + protected def matcher: Matcher + + private[this] val sb = new java.lang.StringBuffer + + def replaced = { + val newsb = new java.lang.StringBuffer(sb) + matcher.appendTail(newsb) + newsb.toString + } + + def replace(rs: String) = matcher.appendReplacement(sb, rs) + } + + /** Quotes strings to be used literally in regex patterns. + * + * All regex metacharacters in the input match themselves literally in the output. + * + * @example {{{List("US\$", "CAN\$").map(Regex.quote).mkString("|").r}}} + */ + def quote(text: String): String = Pattern quote text + + /** Quotes replacement strings to be used in replacement methods. + * + * Replacement methods give special meaning to backslashes (`\`) and + * dollar signs (`\$`) in replacement strings, so they are not treated + * as literals. This method escapes these characters so the resulting + * string can be used as a literal replacement representing the input + * string. + * + * @param text The string one wishes to use as literal replacement. + * @return A string that can be used to replace matches with `text`. + * @example {{{"CURRENCY".r.replaceAllIn(input, Regex quoteReplacement "US\$")}}} + */ + def quoteReplacement(text: String): String = Matcher quoteReplacement text +} diff --git a/library/src/scala/util/package.scala b/library/src/scala/util/package.scala new file mode 100644 index 000000000000..7d1fe27bdde8 --- /dev/null +++ b/library/src/scala/util/package.scala @@ -0,0 +1,22 @@ +/* + * 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.language.`2.13` + +package object util { + /** + * Adds chaining methods `tap` and `pipe` to every type. See [[ChainingOps]]. + */ + object chaining extends ChainingSyntax +} diff --git a/library/src/scala/volatile.scala b/library/src/scala/volatile.scala new file mode 100644 index 000000000000..75b615ee6c7d --- /dev/null +++ b/library/src/scala/volatile.scala @@ -0,0 +1,19 @@ +/* + * 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.language.`2.13` +import scala.annotation.meta._ + +@field +final class volatile extends scala.annotation.StaticAnnotation diff --git a/presentation-compiler-testcases/src/tests/macros/metals7460.scala b/presentation-compiler-testcases/src/tests/macros/metals7460.scala new file mode 100644 index 000000000000..0b9b1ca494b2 --- /dev/null +++ b/presentation-compiler-testcases/src/tests/macros/metals7460.scala @@ -0,0 +1,20 @@ +package tests.macros + +import scala.quoted.* + +object Macros7460 { + + transparent inline def foo: String = + ${ fooImpl } + + private def fooImpl(using Quotes): Expr[String] = + Expr("foo...") + + transparent inline def bar: String = + ${ barImpl } + + private def barImpl(using Quotes): Expr[String] = + quotes.reflect.Position.ofMacroExpansion.sourceFile.getJPath.get // this line is the culprit + Expr("bar...") + +} diff --git a/presentation-compiler/src/main/dotty/tools/pc/ApplyArgsExtractor.scala b/presentation-compiler/src/main/dotty/tools/pc/ApplyArgsExtractor.scala new file mode 100644 index 000000000000..9384a0b43e8b --- /dev/null +++ b/presentation-compiler/src/main/dotty/tools/pc/ApplyArgsExtractor.scala @@ -0,0 +1,269 @@ +package dotty.tools.pc + +import scala.util.Try + +import dotty.tools.dotc.ast.Trees.ValDef +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Flags +import dotty.tools.dotc.core.Flags.Method +import dotty.tools.dotc.core.Names.Name +import dotty.tools.dotc.core.StdNames.* +import dotty.tools.dotc.core.SymDenotations.NoDenotation +import dotty.tools.dotc.core.Symbols.defn +import dotty.tools.dotc.core.Symbols.NoSymbol +import dotty.tools.dotc.core.Symbols.Symbol +import dotty.tools.dotc.core.Types.* +import dotty.tools.pc.IndexedContext +import dotty.tools.pc.utils.InteractiveEnrichments.* +import scala.annotation.tailrec +import dotty.tools.dotc.core.Denotations.SingleDenotation +import dotty.tools.dotc.core.Denotations.MultiDenotation +import dotty.tools.dotc.util.Spans.Span + +object ApplyExtractor: + def unapply(path: List[Tree])(using Context): Option[Apply] = + path match + case ValDef(_, _, _) :: Block(_, app: Apply) :: _ + if !app.fun.isInfix => Some(app) + case rest => + def getApplyForContextFunctionParam(path: List[Tree]): Option[Apply] = + path match + // fun(arg@@) + case (app: Apply) :: _ => Some(app) + // fun(arg@@), where fun(argn: Context ?=> SomeType) + // recursively matched for multiple context arguments, e.g. Context1 ?=> Context2 ?=> SomeType + case (_: DefDef) :: Block(List(_), _: Closure) :: rest => + getApplyForContextFunctionParam(rest) + case _ => None + for + app <- getApplyForContextFunctionParam(rest) + if !app.fun.isInfix + yield app + end match + + +object ApplyArgsExtractor: + def getArgsAndParams( + optIndexedContext: Option[IndexedContext], + apply: Apply, + span: Span + )(using Context): List[(List[Tree], List[ParamSymbol])] = + def collectArgss(a: Apply): List[List[Tree]] = + def stripContextFuntionArgument(argument: Tree): List[Tree] = + argument match + case Block(List(d: DefDef), _: Closure) => + d.rhs match + case app: Apply => + app.args + case b @ Block(List(_: DefDef), _: Closure) => + stripContextFuntionArgument(b) + case _ => Nil + case v => List(v) + + val args = a.args.flatMap(stripContextFuntionArgument) + a.fun match + case app: Apply => collectArgss(app) :+ args + case _ => List(args) + end collectArgss + + val method = apply.fun + + val argss = collectArgss(apply) + + def fallbackFindApply(sym: Symbol) = + sym.info.member(nme.apply) match + case NoDenotation => Nil + case den => List(den.symbol) + + // fallback for when multiple overloaded methods match the supplied args + def fallbackFindMatchingMethods() = + def matchingMethodsSymbols( + indexedContext: IndexedContext, + method: Tree + ): List[Symbol] = + method match + case Ident(name) => indexedContext.findSymbol(name).getOrElse(Nil) + case Select(This(_), name) => indexedContext.findSymbol(name).getOrElse(Nil) + case sel @ Select(from, name) => + val symbol = from.symbol + val ownerSymbol = + if symbol.is(Method) && symbol.owner.isClass then + Some(symbol.owner) + else Try(symbol.info.classSymbol).toOption + ownerSymbol.map(sym => sym.info.member(name)).collect{ + case single: SingleDenotation => List(single.symbol) + case multi: MultiDenotation => multi.allSymbols + }.getOrElse(Nil) + case Apply(fun, _) => matchingMethodsSymbols(indexedContext, fun) + case _ => Nil + val matchingMethods = + for + indexedContext <- optIndexedContext.toList + potentialMatch <- matchingMethodsSymbols(indexedContext, method) + if potentialMatch.is(Flags.Method) && + potentialMatch.vparamss.length >= argss.length && + Try(potentialMatch.isAccessibleFrom(apply.symbol.info)).toOption + .getOrElse(false) && + potentialMatch.vparamss + .zip(argss) + .reverse + .zipWithIndex + .forall { case (pair, index) => + FuzzyArgMatcher(potentialMatch.tparams) + .doMatch(allArgsProvided = index != 0, span) + .tupled(pair) + } + yield potentialMatch + matchingMethods + end fallbackFindMatchingMethods + + val matchingMethods: List[Symbol] = + if method.symbol.paramSymss.nonEmpty then + val allArgsAreSupplied = + val vparamss = method.symbol.vparamss + vparamss.length == argss.length && vparamss + .zip(argss) + .lastOption + .exists { case (baseParams, baseArgs) => + baseArgs.length == baseParams.length + } + // ``` + // m(arg : Int) + // m(arg : Int, anotherArg : Int) + // m(a@@) + // ``` + // complier will choose the first `m`, so we need to manually look for the other one + if allArgsAreSupplied then + val foundPotential = fallbackFindMatchingMethods() + if foundPotential.contains(method.symbol) then foundPotential + else method.symbol :: foundPotential + else List(method.symbol) + else if method.symbol.is(Method) || method.symbol == NoSymbol then + fallbackFindMatchingMethods() + else fallbackFindApply(method.symbol) + end if + end matchingMethods + + matchingMethods.map { methodSym => + val vparamss = methodSym.vparamss + + // get params and args we are interested in + // e.g. + // in the following case, the interesting args and params are + // - params: [apple, banana] + // - args: [apple, b] + // ``` + // def curry(x: Int)(apple: String, banana: String) = ??? + // curry(1)(apple = "test", b@@) + // ``` + val (baseParams0, baseArgs) = + vparamss.zip(argss).lastOption.getOrElse((Nil, Nil)) + + val baseParams: List[ParamSymbol] = + def defaultBaseParams = baseParams0.map(JustSymbol(_)) + @tailrec + def getRefinedParams(refinedType: Type, level: Int): List[ParamSymbol] = + if level > 0 then + val resultTypeOpt = + refinedType match + case RefinedType(AppliedType(_, args), _, _) => args.lastOption + case AppliedType(_, args) => args.lastOption + case _ => None + resultTypeOpt match + case Some(resultType) => getRefinedParams(resultType, level - 1) + case _ => defaultBaseParams + else + refinedType match + case RefinedType(AppliedType(_, args), _, MethodType(ri)) => + baseParams0.zip(ri).zip(args).map { case ((sym, name), arg) => + RefinedSymbol(sym, name, arg) + } + case _ => defaultBaseParams + // finds param refinements for lambda expressions + // val hello: (x: Int, y: Int) => Unit = (x, _) => println(x) + @tailrec + def refineParams(method: Tree, level: Int): List[ParamSymbol] = + method match + case Select(Apply(f, _), _) => refineParams(f, level + 1) + case Select(h, name) => + // for Select(foo, name = apply) we want `foo.symbol` + if name == nme.apply then getRefinedParams(h.symbol.info, level) + else getRefinedParams(method.symbol.info, level) + case Apply(f, _) => + refineParams(f, level + 1) + case _ => getRefinedParams(method.symbol.info, level) + refineParams(method, 0) + end baseParams + (baseArgs, baseParams) + } + + extension (method: Symbol) + def vparamss(using Context) = method.filteredParamss(_.isTerm) + def tparams(using Context) = method.filteredParamss(_.isType).flatten + def filteredParamss(f: Symbol => Boolean)(using Context) = + method.paramSymss.filter(params => params.forall(f)) +sealed trait ParamSymbol: + def name: Name + def info: Type + def symbol: Symbol + def nameBackticked(using Context) = name.decoded.backticked + +case class JustSymbol(symbol: Symbol)(using Context) extends ParamSymbol: + def name: Name = symbol.name + def info: Type = symbol.info + +case class RefinedSymbol(symbol: Symbol, name: Name, info: Type) + extends ParamSymbol + + +class FuzzyArgMatcher(tparams: List[Symbol])(using Context): + + /** + * A heuristic for checking if the passed arguments match the method's arguments' types. + * For non-polymorphic methods we use the subtype relation (`<:<`) + * and for polymorphic methods we use a heuristic. + * We check the args types not the result type. + */ + def doMatch( + allArgsProvided: Boolean, + span: Span + )(expectedArgs: List[Symbol], actualArgs: List[Tree]) = + (expectedArgs.length == actualArgs.length || + (!allArgsProvided && expectedArgs.length >= actualArgs.length)) && + actualArgs.zipWithIndex.forall { + case (arg: Ident, _) if arg.span.contains(span) => true + case (NamedArg(name, arg), _) => + expectedArgs.exists { expected => + expected.name == name && (!arg.hasType || arg.typeOpt.unfold + .fuzzyArg_<:<(expected.info)) + } + case (arg, i) => + !arg.hasType || arg.typeOpt.unfold.fuzzyArg_<:<(expectedArgs(i).info) + } + + extension (arg: Type) + def fuzzyArg_<:<(expected: Type) = + if tparams.isEmpty then arg <:< expected + else arg <:< substituteTypeParams(expected) + def unfold = + arg match + case arg: TermRef => arg.underlying + case e => e + + private def substituteTypeParams(t: Type): Type = + t match + case e if tparams.exists(_ == e.typeSymbol) => + val matchingParam = tparams.find(_ == e.typeSymbol).get + matchingParam.info match + case b @ TypeBounds(_, _) => WildcardType(b) + case _ => WildcardType + case o @ OrType(e1, e2) => + OrType(substituteTypeParams(e1), substituteTypeParams(e2), o.isSoft) + case AndType(e1, e2) => + AndType(substituteTypeParams(e1), substituteTypeParams(e2)) + case AppliedType(et, eparams) => + AppliedType(et, eparams.map(substituteTypeParams)) + case _ => t + +end FuzzyArgMatcher diff --git a/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala index 0a6178eae106..97ec396abcf1 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala @@ -4,7 +4,7 @@ import java.nio.file.Paths import scala.collection.mutable import scala.jdk.CollectionConverters.* -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.internal.pc.AutoImportsResultImpl import scala.meta.pc.* diff --git a/presentation-compiler/src/main/dotty/tools/pc/CompilerSearchVisitor.scala b/presentation-compiler/src/main/dotty/tools/pc/CompilerSearchVisitor.scala index 9fb84ee1f513..aed8bf418490 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/CompilerSearchVisitor.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/CompilerSearchVisitor.scala @@ -4,7 +4,7 @@ import java.util.logging.Level import java.util.logging.Logger import scala.meta.internal.metals.Report -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.pc.* import scala.util.control.NonFatal @@ -19,7 +19,7 @@ class CompilerSearchVisitor( )(using ctx: Context, reports: ReportContext) extends SymbolSearchVisitor: - val logger: Logger = Logger.getLogger(classOf[CompilerSearchVisitor].getName().nn).nn + val logger: Logger = Logger.getLogger(classOf[CompilerSearchVisitor].getName()).nn private def isAccessibleImplicitClass(sym: Symbol) = val owner = sym.maybeOwner @@ -35,7 +35,7 @@ class CompilerSearchVisitor( false case NonFatal(e) => reports.incognito.create( - Report( + () => Report( "is_public", s"""Symbol: $sym""".stripMargin, e @@ -105,6 +105,6 @@ class CompilerSearchVisitor( override def isCancelled: Boolean = false private def normalizePackage(pkg: String): String = - pkg.replace("/", ".").nn.stripSuffix(".") + pkg.replace("/", ".").stripSuffix(".") end CompilerSearchVisitor diff --git a/presentation-compiler/src/main/dotty/tools/pc/CompletionItemResolver.scala b/presentation-compiler/src/main/dotty/tools/pc/CompletionItemResolver.scala index ffd7377c8181..f7fdb1c36e6d 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/CompletionItemResolver.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/CompletionItemResolver.scala @@ -62,7 +62,7 @@ object CompletionItemResolver extends ItemResolver: if companion == NoSymbol || gsym.is(JavaDefined) then if gsymDoc.isEmpty() then if gsym.isAliasType then - fullDocstring(gsym.info.deepDealias.typeSymbol, search) + fullDocstring(gsym.info.deepDealiasAndSimplify.typeSymbol, search) else if gsym.is(Method) then gsym.info.finalResultType match case tr @ TermRef(_, sym) => diff --git a/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala index 00cde67873d5..bd44878aa11a 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala @@ -2,7 +2,7 @@ package dotty.tools.pc import java.nio.file.Paths -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.internal.pc.ExtractMethodUtils import scala.meta.pc.OffsetParams import scala.meta.pc.RangeParams diff --git a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala index 746be65155d9..375a75d0307f 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala @@ -3,7 +3,7 @@ package dotty.tools.pc import java.util as ju import scala.meta.internal.metals.Report -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.internal.pc.ScalaHover import scala.meta.pc.ContentType import scala.meta.pc.HoverSignature @@ -13,6 +13,7 @@ import scala.meta.pc.SymbolSearch import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.core.Constants.* import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Decorators.* import dotty.tools.dotc.core.Flags.* import dotty.tools.dotc.core.Names.* import dotty.tools.dotc.core.StdNames.* @@ -88,7 +89,7 @@ object HoverProvider: s"$uri::$posId" ) end report - reportContext.unsanitized.create(report, ifVerbose = true) + reportContext.unsanitized.create(() => report, /*ifVerbose =*/ true) ju.Optional.empty().nn else val skipCheckOnName = @@ -110,22 +111,23 @@ object HoverProvider: if symbol.name == nme.selectDynamic || symbol.name == nme.applyDynamic => fallbackToDynamics(path, printer, contentType) case symbolTpes @ ((symbol, tpe, None) :: _) => - val exprTpw = tpe.widenTermRefExpr.deepDealias + val exprTpw = tpe.widenTermRefExpr.deepDealiasAndSimplify val hoverString = tpw match // https://github.com/scala/scala3/issues/8891 case tpw: ImportType => printer.hoverSymbol(symbol, symbol.paramRef) case _ => - val (tpe, sym) = + val (innerTpe, sym) = if symbol.isType then (symbol.typeRef, symbol) else enclosing.head.seenFrom(symbol) val finalTpe = - if tpe != NoType then tpe + if tpe.isNamedTupleType then tpe.widenTermRefExpr + else if innerTpe != NoType then innerTpe else tpw - printer.hoverSymbol(sym, finalTpe.deepDealias) + printer.hoverSymbol(sym, finalTpe.deepDealiasAndSimplify) end match end hoverString @@ -161,7 +163,7 @@ object HoverProvider: ju.Optional.empty().nn end match case (_, tpe, Some(namedTupleArg)) :: _ => - val exprTpw = tpe.widenTermRefExpr.deepDealias + val exprTpw = tpe.widenTermRefExpr.deepDealiasAndSimplify printer.expressionType(exprTpw) match case Some(tpe) => ju.Optional.of( @@ -194,7 +196,7 @@ object HoverProvider: val resultType = rest match case Select(_, asInstanceOf) :: TypeApply(_, List(tpe)) :: _ if asInstanceOf == nme.asInstanceOfPM => - tpe.tpe.widenTermRefExpr.deepDealias + tpe.tpe.widenTermRefExpr.deepDealiasAndSimplify case _ if n == nme.selectDynamic => tpe.resultType case _ => tpe @@ -220,12 +222,21 @@ object HoverProvider: findRefinement(parent) case _ => None - val refTpe = sel.typeOpt.widen.deepDealias match - case r: RefinedType => Some(r) - case t: (TermRef | TypeProxy) => Some(t.termSymbol.info.deepDealias) - case _ => None + def extractRefinements(t: Type): List[Type] = t match + case r: RefinedType => List(r) + case t: (TypeRef | AppliedType) => + // deepDealiasAndSimplify can succeed with no progress, so we have to avoid infinite loops + val t1 = t.deepDealiasAndSimplify + if t1 == t then Nil + else extractRefinements(t1) + case t: TermRef => extractRefinements(t.widen) + case t: TypeProxy => List(t.termSymbol.info.deepDealiasAndSimplify) + case AndType(l , r) => List(extractRefinements(l), extractRefinements(r)).flatten + case _ => Nil - refTpe.flatMap(findRefinement).asJava + val refTpe: List[Type] = extractRefinements(sel.typeOpt) + + refTpe.flatMap(findRefinement).headOption.asJava case _ => ju.Optional.empty().nn diff --git a/presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala b/presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala index 2db37f801349..cf22ac12a879 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala @@ -23,13 +23,13 @@ sealed trait IndexedContext: given ctx: Context def scopeSymbols: List[Symbol] def rename(sym: Symbol): Option[String] - def findSymbol(name: Name): Option[List[Symbol]] + def findSymbol(name: Name, fromPrefix: Option[Type] = None): Option[List[Symbol]] def findSymbolInLocalScope(name: String): Option[List[Symbol]] - final def lookupSym(sym: Symbol): Result = + final def lookupSym(sym: Symbol, fromPrefix: Option[Type] = None): Result = def all(symbol: Symbol): Set[Symbol] = Set(symbol, symbol.companionModule, symbol.companionClass, symbol.companion).filter(_ != NoSymbol) val isRelated = all(sym) ++ all(sym.dealiasType) - findSymbol(sym.name) match + findSymbol(sym.name, fromPrefix) match case Some(symbols) if symbols.exists(isRelated) => Result.InScope case Some(symbols) if symbols.exists(isTermAliasOf(_, sym)) => Result.InScope case Some(symbols) if symbols.map(_.dealiasType).exists(isRelated) => Result.InScope @@ -81,7 +81,7 @@ object IndexedContext: case object Empty extends IndexedContext: given ctx: Context = NoContext - def findSymbol(name: Name): Option[List[Symbol]] = None + def findSymbol(name: Name, fromPrefix: Option[Type]): Option[List[Symbol]] = None def findSymbolInLocalScope(name: String): Option[List[Symbol]] = None def scopeSymbols: List[Symbol] = List.empty def rename(sym: Symbol): Option[String] = None @@ -111,11 +111,31 @@ object IndexedContext: override def findSymbolInLocalScope(name: String): Option[List[Symbol]] = names.get(name).map(_.map(_.symbol).toList).filter(_.nonEmpty) - def findSymbol(name: Name): Option[List[Symbol]] = + def findSymbol(name: Name, fromPrefix: Option[Type]): Option[List[Symbol]] = names .get(name.show) - .map(_.map(_.symbol).toList) - .orElse(defaultScopes(name)) + .map { denots => + def skipThisType(tp: Type): Type = tp match + case ThisType(prefix) => skipThisType(prefix) + case _ => tp + + val filteredDenots = fromPrefix match + case Some(prefix) => + val target = skipThisType(prefix) + denots.filter { denot => + denot.prefix == NoPrefix || + (denot.prefix match + case tref: TermRef => + tref.termSymbol.info <:< target + case otherPrefix => + otherPrefix <:< target + ) + } + case None => denots + + filteredDenots.map(_.symbol).toList + } + .orElse(defaultScopes(name)).filter(_.nonEmpty) def scopeSymbols: List[Symbol] = names.values.flatten.map(_.symbol).toList diff --git a/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala index 075167f3f5c1..2aecbb7b36b6 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala @@ -1,15 +1,11 @@ package dotty.tools.pc -import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.tpd.* -import dotty.tools.dotc.core.Constants.Constant import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Flags import dotty.tools.dotc.core.StdNames -import dotty.tools.dotc.core.Symbols import dotty.tools.dotc.core.Symbols.defn import dotty.tools.dotc.core.Types.* -import dotty.tools.dotc.core.Types.Type import dotty.tools.dotc.interactive.Interactive import dotty.tools.dotc.interactive.InteractiveDriver import dotty.tools.dotc.typer.Applications.UnapplyArgs @@ -21,7 +17,7 @@ import dotty.tools.pc.printer.ShortenedTypePrinter import dotty.tools.pc.printer.ShortenedTypePrinter.IncludeDefaultParam import dotty.tools.pc.utils.InteractiveEnrichments.* -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.pc.OffsetParams import scala.meta.pc.SymbolSearch @@ -30,8 +26,8 @@ class InferExpectedType( driver: InteractiveDriver, params: OffsetParams )(implicit rc: ReportContext): - val uri = params.uri().nn - val code = params.text().nn + val uri: java.net.URI = params.uri() + val code: String = params.text() val sourceFile = SourceFile.virtual(uri, code) driver.run(uri, sourceFile) @@ -54,12 +50,12 @@ class InferExpectedType( val indexedCtx = IndexedContext(pos)(using locatedCtx) val printer = ShortenedTypePrinter(search, IncludeDefaultParam.ResolveLater)(using indexedCtx) - InterCompletionType.inferType(path)(using newctx).map{ + InferCompletionType.inferType(path)(using newctx).map{ tpe => printer.tpe(tpe) } case None => None -object InterCompletionType: +object InferCompletionType: def inferType(path: List[Tree])(using Context): Option[Type] = path match case (lit: Literal) :: Select(Literal(_), _) :: Apply(Select(Literal(_), _), List(s: Select)) :: rest if s.symbol == defn.Predef_undefined => inferType(rest, lit.span) @@ -76,7 +72,7 @@ object InterCompletionType: case Try(block, _, _) :: rest if block.span.contains(span) => inferType(rest, span) case CaseDef(_, _, body) :: Try(_, cases, _) :: rest if body.span.contains(span) && cases.exists(_.span.contains(span)) => inferType(rest, span) case If(cond, _, _) :: rest if !cond.span.contains(span) => inferType(rest, span) - case If(cond, _, _) :: rest if cond.span.contains(span) => Some(Symbols.defn.BooleanType) + case If(cond, _, _) :: rest if cond.span.contains(span) => Some(defn.BooleanType) case CaseDef(_, _, body) :: Match(_, cases) :: rest if body.span.contains(span) && cases.exists(_.span.contains(span)) => inferType(rest, span) case NamedArg(_, arg) :: rest if arg.span.contains(span) => inferType(rest, span) @@ -97,39 +93,8 @@ object InterCompletionType: if ind < 0 then None else Some(UnapplyArgs(fun.tpe.finalResultType, fun, pats, NoSourcePosition).argTypes(ind)) // f(@@) - case (app: Apply) :: rest => - val param = - for { - ind <- app.args.zipWithIndex.collectFirst { - case (arg, id) if arg.span.contains(span) => id - } - params <- app.symbol.paramSymss.find(!_.exists(_.isTypeParam)) - param <- params.get(ind) - } yield param.info - param match - // def f[T](a: T): T = ??? - // f[Int](@@) - // val _: Int = f(@@) - case Some(t : TypeRef) if t.symbol.is(Flags.TypeParam) => - for { - (typeParams, args) <- - app match - case Apply(TypeApply(fun, args), _) => - val typeParams = fun.symbol.paramSymss.headOption.filter(_.forall(_.isTypeParam)) - typeParams.map((_, args.map(_.tpe))) - // val f: (j: "a") => Int - // f(@@) - case Apply(Select(v, StdNames.nme.apply), _) => - v.symbol.info match - case AppliedType(des, args) => - Some((des.typeSymbol.typeParams, args)) - case _ => None - case _ => None - ind = typeParams.indexOf(t.symbol) - tpe <- args.get(ind) - if !tpe.isErroneous - } yield tpe - case Some(tpe) => Some(tpe) - case _ => None + case ApplyExtractor(app) => + val idx = app.args.indexWhere(_.span.contains(span)) + app.fun.tpe.widenTermRefExpr.paramInfoss.flatten.get(idx) case _ => None diff --git a/presentation-compiler/src/main/dotty/tools/pc/InferredMethodProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/InferredMethodProvider.scala new file mode 100644 index 000000000000..e6f27781bc64 --- /dev/null +++ b/presentation-compiler/src/main/dotty/tools/pc/InferredMethodProvider.scala @@ -0,0 +1,362 @@ +package dotty.tools.pc + +import java.nio.file.Paths + +import scala.annotation.tailrec + +import scala.meta.pc.OffsetParams +import scala.meta.pc.PresentationCompilerConfig +import scala.meta.pc.SymbolSearch +import scala.meta.pc.reports.ReportContext + +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Names.Name +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.core.Symbols.defn +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.interactive.Interactive +import dotty.tools.dotc.interactive.InteractiveDriver +import dotty.tools.dotc.util.SourceFile +import dotty.tools.dotc.util.SourcePosition +import dotty.tools.pc.printer.ShortenedTypePrinter +import dotty.tools.pc.printer.ShortenedTypePrinter.IncludeDefaultParam +import dotty.tools.pc.utils.InteractiveEnrichments.* + +import org.eclipse.lsp4j.TextEdit +import org.eclipse.lsp4j as l + +/** + * Tries to calculate edits needed to create a method that will fix missing symbol + * in all the places that it is possible such as: + * - apply inside method invocation `method(.., nonExistent(param), ...)` and `method(.., nonExistent, ...)` + * - method in val definition `val value: DefinedType = nonExistent(param)` and `val value: DefinedType = nonExistent` + * - simple method call `nonExistent(param)` and `nonExistent` + * - method call inside a container `container.nonExistent(param)` and `container.nonExistent` + * + * @param params position and actual source + * @param driver Scala 3 interactive compiler driver + * @param config presentation compiler configuration + * @param symbolSearch symbol search + */ +final class InferredMethodProvider( + params: OffsetParams, + driver: InteractiveDriver, + config: PresentationCompilerConfig, + symbolSearch: SymbolSearch +)(using ReportContext): + + case class AdjustTypeOpts( + text: String, + adjustedEndPos: l.Position + ) + + def inferredMethodEdits( + adjustOpt: Option[AdjustTypeOpts] = None + ): List[TextEdit] = + val uri = params.uri().nn + val filePath = Paths.get(uri).nn + + val sourceText = adjustOpt.map(_.text).getOrElse(params.text().nn) + val source = + SourceFile.virtual(filePath.toString(), sourceText) + driver.run(uri, source) + val unit = driver.currentCtx.run.nn.units.head + val pos = driver.sourcePosition(params) + val path = + Interactive.pathTo(driver.openedTrees(uri), pos)(using driver.currentCtx) + + given locatedCtx: Context = driver.localContext(params) + val indexedCtx = IndexedContext(pos)(using locatedCtx) + + val autoImportsGen = AutoImports.generator( + pos, + sourceText, + unit.tpdTree, + unit.comments, + indexedCtx, + config + ) + + val printer = ShortenedTypePrinter( + symbolSearch, + includeDefaultParam = IncludeDefaultParam.ResolveLater, + isTextEdit = true + )(using indexedCtx) + + def imports: List[TextEdit] = + printer.imports(autoImportsGen) + + def printType(tpe: Type): String = + printer.tpe(tpe) + + def printName(name: Name): String = + printer.nameString(name) + + def printParams(params: List[Type], startIndex: Int = 0): String = + params.zipWithIndex + .map { case (p, index) => + s"arg${index + startIndex}: ${printType(p)}" + } + .mkString(", ") + + def printSignature( + methodName: Name, + params: List[List[Type]], + retTypeOpt: Option[Type] + ): String = + val retTypeString = retTypeOpt match + case Some(retType) => + val printRetType = printType(retType) + if retType.isAny then "" + else s": $printRetType" + case _ => "" + + val (paramsString, _) = params.foldLeft(("", 0)){ + case ((acc, startIdx), paramList) => + val printed = s"(${printParams(paramList, startIdx)})" + (acc + printed, startIdx + paramList.size) + } + + s"def ${printName(methodName)}$paramsString$retTypeString = ???" + + @tailrec + def countIndent(text: String, index: Int, acc: Int): Int = + if index > 0 && text(index) != '\n' then countIndent(text, index - 1, acc + 1) + else acc + + def indentation(text: String, pos: Int): String = + if pos > 0 then + val isSpace = text(pos) == ' ' + val isTab = text(pos) == '\t' + val indent = countIndent(params.text(), pos, 0) + + if isSpace then " " * indent else if isTab then "\t" * indent else "" + else "" + + def insertPosition() = + val blockOrTemplateIndex = + path.tail.indexWhere { + case _: Block | _: Template => true + case _ => false + } + path(blockOrTemplateIndex).sourcePos + + /** + * Returns the position to insert the method signature for a container. + * If the container has an empty body, the position is the end of the container. + * If the container has a non-empty body, the position is the end of the last element in the body. + * + * @param container the container to insert the method signature for + * @return the position to insert the method signature for the container and a boolean indicating if the container has an empty body + */ + def insertPositionFor(container: Tree): Option[(SourcePosition, Boolean)] = + val typeSymbol = container.tpe.widenDealias.typeSymbol + if typeSymbol.exists then + val trees = driver.openedTrees(params.uri().nn) + val include = Interactive.Include.definitions | Interactive.Include.local + Interactive.findTreesMatching(trees, include, typeSymbol).headOption match + case Some(srcTree) => + srcTree.tree match + case classDef: TypeDef if classDef.rhs.isInstanceOf[Template] => + val template = classDef.rhs.asInstanceOf[Template] + val (pos, hasEmptyBody) = template.body.lastOption match + case Some(last) => (last.sourcePos, false) + case None => (classDef.sourcePos, true) + Some((pos, hasEmptyBody)) + case _ => None + case None => None + else None + + /** + * Extracts type information for a specific parameter in a method signature. + * If the parameter is a function type, extracts both the function's argument types + * and return type. Otherwise, extracts just the parameter type. + * + * @param methodType the method type to analyze + * @param argIndex the index of the parameter to extract information for + * @return a tuple of (argument types, return type) where: + * - argument types: Some(List[Type]) if parameter is a function, None otherwise + * - return type: Some(Type) representing either the function's return type or the parameter type itself + */ + def extractParameterTypeInfo(methodType: Type, argIndex: Int): (Option[List[Type]], Option[Type]) = + methodType match + case m @ MethodType(param) => + val expectedFunctionType = m.paramInfos(argIndex) + if defn.isFunctionType(expectedFunctionType) then + expectedFunctionType match + case defn.FunctionOf(argTypes, retType, _) => + (Some(argTypes), Some(retType)) + case _ => + (None, Some(expectedFunctionType)) + else + (None, Some(m.paramInfos(argIndex))) + case _ => (None, None) + + def signatureEdits(signature: String): List[TextEdit] = + val pos = insertPosition() + val indent = indentation(params.text(), pos.start - 1) + val lspPos = pos.toLsp + lspPos.setEnd(lspPos.getStart()) + + List( + TextEdit( + lspPos, + s"$signature\n$indent", + ) + ) ::: imports + + def signatureEditsForContainer(signature: String, container: Tree): List[TextEdit] = + insertPositionFor(container) match + case Some((pos, hasEmptyBody)) => + val lspPos = pos.toLsp + lspPos.setStart(lspPos.getEnd()) + val indent = indentation(params.text(), pos.start - 1) + + if hasEmptyBody then + List( + TextEdit( + lspPos, + s":\n $indent$signature", + ) + ) ::: imports + else + List( + TextEdit( + lspPos, + s"\n$indent$signature", + ) + ) ::: imports + case None => Nil + + path match + /** + * outerArgs + * --------------------------- + * method(..., errorMethod(args), ...) + * + */ + case (id @ Ident(errorMethod)) :: + (apply @ Apply(func, args)) :: + Apply(method, outerArgs) :: + _ if id.symbol == NoSymbol && func == id && method != apply => + + val argTypes = args.map(_.typeOpt.widenDealias) + + val argIndex = outerArgs.indexOf(apply) + val (allArgTypes, retTypeOpt) = + extractParameterTypeInfo(method.tpe.widenDealias, argIndex) match + case (Some(argTypes2), retTypeOpt) => (List(argTypes, argTypes2), retTypeOpt) + case (None, retTypeOpt) => (List(argTypes), retTypeOpt) + + val signature = printSignature(errorMethod, allArgTypes, retTypeOpt) + + signatureEdits(signature) + + /** + * outerArgs + * --------------------- + * method(..., errorMethod, ...) + * + */ + case (id @ Ident(errorMethod)) :: + Apply(method, outerArgs) :: + _ if id.symbol == NoSymbol && method != id => + + val argIndex = outerArgs.indexOf(id) + + val (argTypes, retTypeOpt) = extractParameterTypeInfo(method.tpe.widenDealias, argIndex) + + val allArgTypes = argTypes match + case Some(argTypes) => List(argTypes) + case None => Nil + + val signature = printSignature(errorMethod, allArgTypes, retTypeOpt) + + signatureEdits(signature) + + /** + * tpt body + * ----------- ---------------- + * val value: DefinedType = errorMethod(args) + * + */ + case (id @ Ident(errorMethod)) :: + (apply @ Apply(func, args)) :: + ValDef(_, tpt, body) :: + _ if id.symbol == NoSymbol && func == id && apply == body => + + val retType = tpt.tpe.widenDealias + val argTypes = args.map(_.typeOpt.widenDealias) + + val signature = printSignature(errorMethod, List(argTypes), Some(retType)) + signatureEdits(signature) + + /** + * tpt body + * ----------- ----------- + * val value: DefinedType = errorMethod + * + */ + case (id @ Ident(errorMethod)) :: + ValDef(_, tpt, body) :: + _ if id.symbol == NoSymbol && id == body => + + val retType = tpt.tpe.widenDealias + + val signature = printSignature(errorMethod, Nil, Some(retType)) + signatureEdits(signature) + + /** + * + * errorMethod(args) + * + */ + case (id @ Ident(errorMethod)) :: + (apply @ Apply(func, args)) :: + _ if id.symbol == NoSymbol && func == id => + + val argTypes = args.map(_.typeOpt.widenDealias) + + val signature = printSignature(errorMethod, List(argTypes), None) + signatureEdits(signature) + + /** + * + * errorMethod + * + */ + case (id @ Ident(errorMethod)) :: + _ if id.symbol == NoSymbol => + + val signature = printSignature(errorMethod, Nil, None) + signatureEdits(signature) + + /** + * + * container.errorMethod(args) + * + */ + case (select @ Select(container, errorMethod)) :: + (apply @ Apply(func, args)) :: + _ if select.symbol == NoSymbol && func == select => + + val argTypes = args.map(_.typeOpt.widenDealias) + val signature = printSignature(errorMethod, List(argTypes), None) + signatureEditsForContainer(signature, container) + + /** + * + * container.errorMethod + * + */ + case (select @ Select(container, errorMethod)) :: + _ if select.symbol == NoSymbol => + + val signature = printSignature(errorMethod, Nil, None) + signatureEditsForContainer(signature, container) + + case _ => Nil + + end inferredMethodEdits +end InferredMethodProvider diff --git a/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala index 2006774ae19b..af5a0e409d1a 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala @@ -3,7 +3,7 @@ package dotty.tools.pc import java.nio.file.Paths import scala.annotation.tailrec -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.pc.OffsetParams import scala.meta.pc.PresentationCompilerConfig import scala.meta.pc.SymbolSearch @@ -94,14 +94,14 @@ final class InferredTypeProvider( tpe match case tref: TypeRef => indexedCtx.lookupSym( - tref.currentSymbol + tref.currentSymbol, + Some(tref.prefix) ) == IndexedContext.Result.InScope case AppliedType(tycon, args) => isInScope(tycon) && args.forall(isInScope) case _ => true - if isInScope(tpe) - then tpe - else tpe.deepDealias + if isInScope(tpe) then tpe + else tpe.deepDealiasAndSimplify val printer = ShortenedTypePrinter( symbolSearch, @@ -137,7 +137,6 @@ final class InferredTypeProvider( findNamePos(sourceText, vl, keywordOffset).endPos.toLsp adjustOpt.foreach(adjust => endPos.setEnd(adjust.adjustedEndPos)) val spaceBefore = name.isOperatorName - new TextEdit( endPos, printTypeAscription(optDealias(tpt.typeOpt), spaceBefore) + { @@ -183,8 +182,7 @@ final class InferredTypeProvider( typeNameEdit ::: imports rhs match - case t: Tree[?] - if t.typeOpt.isErroneous && retryType && !tpt.sourcePos.span.isZeroExtent => + case t: Tree[?] if !tpt.sourcePos.span.isZeroExtent => inferredTypeEdits( Some( AdjustTypeOpts( @@ -224,8 +222,7 @@ final class InferredTypeProvider( while i >= 0 && sourceText(i) != ':' do i -= 1 i rhs match - case t: Tree[?] - if t.typeOpt.isErroneous && retryType && !tpt.sourcePos.span.isZeroExtent => + case t: Tree[?] if !tpt.sourcePos.span.isZeroExtent => inferredTypeEdits( Some( AdjustTypeOpts( diff --git a/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala b/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala index 8132b0cf95cb..9c7d0c3fbcf0 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala @@ -120,8 +120,9 @@ object MetalsInteractive: // For a named arg, find the target `DefDef` and jump to the param case NamedArg(name, _) :: Apply(fn, _) :: _ => val funSym = fn.symbol - if funSym.is(Synthetic) && funSym.owner.is(CaseClass) then - val sym = funSym.owner.info.member(name).symbol + lazy val owner = funSym.owner.companionClass + if funSym.is(Synthetic) && owner.is(CaseClass) then + val sym = owner.info.member(name).symbol List((sym, sym.info, None)) else val paramSymbol = @@ -130,12 +131,19 @@ object MetalsInteractive: val sym = paramSymbol.getOrElse(fn.symbol) List((sym, sym.info, None)) + case NamedArg(name, _) :: UnApply(s, _, _) :: _ => + lazy val owner = s.symbol.owner.companionClass + if s.symbol.is(Synthetic) && owner.is(CaseClass) then + val sym = owner.info.member(name).symbol + List((sym, sym.info, None)) + else Nil + case (_: untpd.ImportSelector) :: (imp: Import) :: _ => importedSymbols(imp, _.span.contains(pos.span)).map(sym => (sym, sym.info, None) ) - case (imp: Import) :: _ => + case (imp: ImportOrExport) :: _ => importedSymbols(imp, _.span.contains(pos.span)).map(sym => (sym, sym.info, None) ) @@ -206,7 +214,7 @@ object MetalsInteractive: // 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 => + fun.symbol.owner.exists && fun.symbol.owner == defn.NamedTupleModule.moduleClass => def getIndex(t: Tree): Option[Type] = t.tpe.dealias match case AppliedType(_, args) => args.get(i) diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala b/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala index d145272ba01d..5ca5b63df0ae 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala @@ -63,7 +63,8 @@ trait PcCollector[T]: o.span.exists && o.span.point == named.symbol.owner.span.point ) - def soughtOrOverride(sym: Symbol) = + def soughtOrOverride(sym0: Symbol) = + val sym = if sym0.is(Flags.Exported) then sym0.sourceSymbol else sym0 sought(sym) || sym.allOverriddenSymbols.exists(sought(_)) def soughtTreeFilter(tree: Tree): Boolean = @@ -76,7 +77,7 @@ trait PcCollector[T]: case df: NamedDefTree if soughtOrOverride(df.symbol) && !df.symbol.isSetter => true - case imp: Import if owners(imp.expr.symbol) => true + case imp: ImportOrExport if owners(imp.expr.symbol) => true case _ => false def soughtFilter(f: Symbol => Boolean): Boolean = @@ -115,11 +116,13 @@ trait PcCollector[T]: */ case ident: Ident if ident.isCorrectSpan && filter(ident) => // symbols will differ for params in different ext methods, but source pos will be the same - if soughtFilter(_.sourcePos == ident.symbol.sourcePos) + val symbol = if ident.symbol.is(Flags.Exported) then ident.symbol.sourceSymbol else ident.symbol + if soughtFilter(_.sourcePos == symbol.sourcePos) then occurrences + collect( ident, - ident.sourcePos + ident.sourcePos, + Some(symbol) ) else occurrences /** @@ -228,7 +231,7 @@ trait PcCollector[T]: * For traversing import selectors: * import scala.util.<> */ - case imp: Import if filter(imp) => + case imp: ImportOrExport if filter(imp) => imp.selectors .collect { case sel: ImportSelector @@ -316,21 +319,24 @@ object EndMarker: implicit ct: Context ): Option[SourcePosition] = val name = df.name.toString().stripSuffix("$") - val endMarkerLine = - sourceText.slice(df.span.start, df.span.end).split('\n').last - val index = endMarkerLine.length() - name.length() - if index < 0 then None - else - val (possiblyEndMarker, possiblyEndMarkerName) = - endMarkerLine.splitAt(index) - Option.when( - possiblyEndMarkerName == name && - endMarkerRegex.matches(possiblyEndMarker) - )( - pos - .withStart(df.span.end - name.length()) - .withEnd(df.span.end) - ) + val lines = sourceText.slice(df.span.start, df.span.end).split('\n') + + if lines.nonEmpty then + val endMarkerLine = lines.last + val index = endMarkerLine.length() - name.length() + if index < 0 then None + else + val (possiblyEndMarker, possiblyEndMarkerName) = + endMarkerLine.splitAt(index) + Option.when( + possiblyEndMarkerName == name && + endMarkerRegex.matches(possiblyEndMarker) + )( + pos + .withStart(df.span.end - name.length()) + .withEnd(df.span.end) + ) + else None end getPosition end EndMarker diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcConvertToNamedLambdaParameters.scala b/presentation-compiler/src/main/dotty/tools/pc/PcConvertToNamedLambdaParameters.scala new file mode 100644 index 000000000000..ca5f336b7bcd --- /dev/null +++ b/presentation-compiler/src/main/dotty/tools/pc/PcConvertToNamedLambdaParameters.scala @@ -0,0 +1,153 @@ +package dotty.tools.pc + +import java.nio.file.Paths +import java.util as ju + +import scala.jdk.CollectionConverters.* +import scala.meta.pc.OffsetParams + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Flags +import dotty.tools.dotc.interactive.Interactive +import dotty.tools.dotc.interactive.InteractiveDriver +import dotty.tools.dotc.util.SourceFile +import dotty.tools.dotc.util.SourcePosition +import org.eclipse.lsp4j as l +import dotty.tools.pc.utils.InteractiveEnrichments.* +import dotty.tools.pc.utils.TermNameInference.* + +/** + * Facilitates the code action that converts a wildcard lambda to a lambda with named parameters + * e.g. + * + * List(1, 2).map(<<_>> + 1) => List(1, 2).map(i => i + 1) + */ +final class PcConvertToNamedLambdaParameters( + driver: InteractiveDriver, + params: OffsetParams +): + import PcConvertToNamedLambdaParameters._ + + def convertToNamedLambdaParameters: ju.List[l.TextEdit] = { + val uri = params.uri + val filePath = Paths.get(uri) + driver.run( + uri, + SourceFile.virtual(filePath.toString, params.text), + ) + given newctx: Context = driver.localContext(params) + val pos = driver.sourcePosition(params) + val trees = driver.openedTrees(uri) + val treeList = Interactive.pathTo(trees, pos) + // Extractor for a lambda function (needs context, so has to be defined here) + val LambdaExtractor = Lambda(using newctx) + // select the most inner wildcard lambda + val firstLambda = treeList.collectFirst { + case LambdaExtractor(params, rhsFn) if params.forall(isWildcardParam) => + params -> rhsFn + } + + firstLambda match { + case Some((params, lambda)) => + // avoid names that are either defined or referenced in the lambda + val namesToAvoid = allDefAndRefNamesInTree(lambda) + // compute parameter names based on the type of the parameter + val computedParamNames: List[String] = + params.foldLeft(List.empty[String]) { (acc, param) => + val name = singleLetterNameStream(param.tpe.typeSymbol.name.toString()) + .find(n => !namesToAvoid.contains(n) && !acc.contains(n)) + acc ++ name.toList + } + if computedParamNames.size == params.size then + val paramReferenceEdits = params.zip(computedParamNames).flatMap { (param, paramName) => + val paramReferencePosition = findParamReferencePosition(param, lambda) + paramReferencePosition.toList.map { pos => + val position = pos.toLsp + val range = new l.Range( + position.getStart(), + position.getEnd() + ) + new l.TextEdit(range, paramName) + } + } + val paramNamesStr = computedParamNames.mkString(", ") + val paramDefsStr = + if params.size == 1 then paramNamesStr + else s"($paramNamesStr)" + val defRange = new l.Range( + lambda.sourcePos.toLsp.getStart(), + lambda.sourcePos.toLsp.getStart() + ) + val paramDefinitionEdits = List( + new l.TextEdit(defRange, s"$paramDefsStr => ") + ) + (paramDefinitionEdits ++ paramReferenceEdits).asJava + else + List.empty.asJava + case _ => + List.empty.asJava + } + } + +end PcConvertToNamedLambdaParameters + +object PcConvertToNamedLambdaParameters: + val codeActionId = "ConvertToNamedLambdaParameters" + + class Lambda(using Context): + def unapply(tree: tpd.Block): Option[(List[tpd.ValDef], tpd.Tree)] = tree match { + case tpd.Block((ddef @ tpd.DefDef(_, tpd.ValDefs(params) :: Nil, _, body: tpd.Tree)) :: Nil, tpd.Closure(_, meth, _)) + if ddef.symbol == meth.symbol => + params match { + case List(param) => + // lambdas with multiple wildcard parameters are represented as a single parameter function and a block with wildcard valdefs + Some(multipleUnderscoresFromBody(param, body)) + case _ => Some(params -> body) + } + case _ => None + } + end Lambda + + private def multipleUnderscoresFromBody(param: tpd.ValDef, body: tpd.Tree)(using Context): (List[tpd.ValDef], tpd.Tree) = body match { + case tpd.Block(defs, expr) if param.symbol.is(Flags.Synthetic) => + val wildcardParamDefs = defs.collect { + case valdef: tpd.ValDef if isWildcardParam(valdef) => valdef + } + if wildcardParamDefs.size == defs.size then wildcardParamDefs -> expr + else List(param) -> body + case _ => List(param) -> body + } + + def isWildcardParam(param: tpd.ValDef)(using Context): Boolean = + param.name.toString.startsWith("_$") + + def findParamReferencePosition(param: tpd.ValDef, lambda: tpd.Tree)(using Context): Option[SourcePosition] = + var pos: Option[SourcePosition] = None + object FindParamReference extends tpd.TreeTraverser: + override def traverse(tree: tpd.Tree)(using Context): Unit = + tree match + case ident @ tpd.Ident(_) if ident.symbol == param.symbol => + pos = Some(tree.sourcePos) + case _ => + traverseChildren(tree) + FindParamReference.traverse(lambda) + pos + end findParamReferencePosition + + def allDefAndRefNamesInTree(tree: tpd.Tree)(using Context): List[String] = + object FindDefinitionsAndRefs extends tpd.TreeAccumulator[List[String]]: + override def apply(x: List[String], tree: tpd.Tree)(using Context): List[String] = + tree match + case tpd.DefDef(name, _, _, _) => + super.foldOver(x :+ name.toString, tree) + case tpd.ValDef(name, _, _) => + super.foldOver(x :+ name.toString, tree) + case tpd.Ident(name) => + super.foldOver(x :+ name.toString, tree) + case _ => + super.foldOver(x, tree) + FindDefinitionsAndRefs.foldOver(Nil, tree) + end allDefAndRefNamesInTree + +end PcConvertToNamedLambdaParameters diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala index 8718eaf58a88..71aa1626bb18 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala @@ -5,7 +5,7 @@ import java.nio.file.Paths import scala.annotation.tailrec -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import dotty.tools.pc.utils.InteractiveEnrichments.* import dotty.tools.pc.printer.ShortenedTypePrinter import scala.meta.internal.pc.InlayHints @@ -17,6 +17,9 @@ import scala.meta.pc.SymbolSearch import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Flags +import dotty.tools.dotc.core.NameOps.fieldName +import dotty.tools.dotc.core.Names.Name +import dotty.tools.dotc.core.NameKinds.DefaultGetterName import dotty.tools.dotc.core.StdNames.* import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.core.Types.* @@ -36,11 +39,11 @@ class PcInlayHintsProvider( symbolSearch: SymbolSearch, )(using ReportContext): - val uri = params.uri().nn - val filePath = Paths.get(uri).nn - val sourceText = params.text().nn - val text = sourceText.toCharArray().nn - val source = + val uri: java.net.URI = params.uri() + val filePath: java.nio.file.Path = Paths.get(uri) + val sourceText: String = params.text() + val text: Array[Char] = sourceText.toCharArray() + val source: SourceFile = SourceFile.virtual(filePath.toString, sourceText) driver.run(uri, source) given InlayHintsParams = params @@ -51,7 +54,7 @@ class PcInlayHintsProvider( val pos = driver.sourcePosition(params) def provide(): List[InlayHint] = - val deepFolder = DeepFolder[InlayHints](collectDecorations) + val deepFolder = PcCollector.DeepFolderWithParent[InlayHints](collectDecorations) Interactive .pathTo(driver.openedTrees(uri), pos)(using driver.currentCtx) .headOption @@ -65,11 +68,23 @@ class PcInlayHintsProvider( def collectDecorations( inlayHints: InlayHints, tree: Tree, + parent: Option[Tree] ): InlayHints = + // XRay hints are not mutually exclusive with other hints, so they must be matched separately + val firstPassHints = (tree, parent) match { + case XRayModeHint(tpe, pos) => + inlayHints.addToBlock( + adjustPos(pos).toLsp, + LabelPart(": ") :: toLabelParts(tpe, pos), + InlayHintKind.Type + ) + case _ => inlayHints + } + tree match case ImplicitConversion(symbol, range) => val adjusted = adjustPos(range) - inlayHints + firstPassHints .add( adjusted.startPos.toLsp, labelPart(symbol, symbol.decodedName) :: LabelPart("(") :: Nil, @@ -81,17 +96,17 @@ class PcInlayHintsProvider( InlayHintKind.Parameter, ) case ImplicitParameters(trees, pos) => - inlayHints.add( + firstPassHints.add( adjustPos(pos).toLsp, ImplicitParameters.partsFromImplicitArgs(trees).map((label, maybeSymbol) => maybeSymbol match case Some(symbol) => labelPart(symbol, label) case None => LabelPart(label) ), - InlayHintKind.Parameter + InlayHintKind.Parameter, ) case ValueOf(label, pos) => - inlayHints.add( + firstPassHints.add( adjustPos(pos).toLsp, LabelPart("(") :: LabelPart(label) :: List(LabelPart(")")), InlayHintKind.Parameter, @@ -99,7 +114,7 @@ class PcInlayHintsProvider( case TypeParameters(tpes, pos, sel) if !syntheticTupleApply(sel) => val label = tpes.map(toLabelParts(_, pos)).separated("[", ", ", "]") - inlayHints.add( + firstPassHints.add( adjustPos(pos).endPos.toLsp, label, InlayHintKind.Type, @@ -107,16 +122,55 @@ class PcInlayHintsProvider( case InferredType(tpe, pos, defTree) if !isErrorTpe(tpe) => val adjustedPos = adjustPos(pos).endPos - if inlayHints.containsDef(adjustedPos.start) then inlayHints + if firstPassHints.containsDef(adjustedPos.start) then firstPassHints else - inlayHints + firstPassHints .add( adjustedPos.toLsp, LabelPart(": ") :: toLabelParts(tpe, pos), InlayHintKind.Type, ) .addDefinition(adjustedPos.start) - case _ => inlayHints + case Parameters(isInfixFun, args) => + def isNamedParam(pos: SourcePosition): Boolean = + val start = text.indexWhere(!_.isWhitespace, pos.start) + val end = text.lastIndexWhere(!_.isWhitespace, pos.end - 1) + + text.slice(start, end).contains('=') + + def isBlockParam(pos: SourcePosition): Boolean = + val start = text.indexWhere(!_.isWhitespace, pos.start) + val end = text.lastIndexWhere(!_.isWhitespace, pos.end - 1) + val startsWithBrace = text.lift(start).contains('{') + val endsWithBrace = text.lift(end).contains('}') + + startsWithBrace && endsWithBrace + + def adjustBlockParamPos(pos: SourcePosition): SourcePosition = + pos.withStart(pos.start + 1) + + + args.foldLeft(firstPassHints) { + case (ih, (name, pos0, isByName)) => + val pos = adjustPos(pos0) + val isBlock = isBlockParam(pos) + val namedLabel = + if params.namedParameters() && !isInfixFun && !isBlock && !isNamedParam(pos) then s"${name} = " else "" + val byNameLabel = + if params.byNameParameters() && isByName && (!isInfixFun || isBlock) then "=> " else "" + + val labelStr = s"${namedLabel}${byNameLabel}" + val hintPos = if isBlock then adjustBlockParamPos(pos) else pos + + if labelStr.nonEmpty then + ih.add( + hintPos.startPos.toLsp, + List(LabelPart(labelStr)), + InlayHintKind.Parameter, + ) + else ih + } + case _ => firstPassHints private def toLabelParts( tpe: Type, @@ -140,7 +194,7 @@ class PcInlayHintsProvider( isInScope(tycon) && args.forall(isInScope) case _ => true if isInScope(tpe) then tpe - else tpe.deepDealias(using indexedCtx.ctx) + else tpe.deepDealiasAndSimplify(using indexedCtx.ctx) val dealiased = optDealias(tpe) val tpeStr = printer.tpe(dealiased) @@ -388,3 +442,116 @@ object InferredType: index >= 0 && index < afterDef.size && afterDef(index) == '@' end InferredType + +object Parameters: + def unapply(tree: Tree)(using params: InlayHintsParams, ctx: Context): Option[(Boolean, List[(Name, SourcePosition, Boolean)])] = + def shouldSkipFun(fun: Tree)(using Context): Boolean = + fun match + case sel: Select => isForComprehensionMethod(sel) || sel.symbol.name == nme.unapply || sel.symbol.is(Flags.JavaDefined) + case _ => false + + def isInfixFun(fun: Tree, args: List[Tree])(using Context): Boolean = + val isInfixSelect = fun match + case Select(sel, _) => sel.isInfix + case _ => false + val source = fun.source + if args.isEmpty then isInfixSelect + else + (!(fun.span.end until args.head.span.start) + .map(source.apply) + .contains('.') && fun.symbol.is(Flags.ExtensionMethod)) || isInfixSelect + + def isRealApply(tree: Tree) = + !tree.symbol.isOneOf(Flags.GivenOrImplicit) && !tree.span.isZeroExtent + + def getUnderlyingFun(tree: Tree): Tree = + tree match + case Apply(fun, _) => getUnderlyingFun(fun) + case TypeApply(fun, _) => getUnderlyingFun(fun) + case t => t + + @tailrec + def isDefaultArg(arg: Tree): Boolean = arg match + case Ident(name) => name.is(DefaultGetterName) + case Select(_, name) => name.is(DefaultGetterName) + case Apply(fun, _) => isDefaultArg(fun) + case _ => false + + if (params.namedParameters() || params.byNameParameters()) then + tree match + case Apply(fun, args) if isRealApply(fun) => + val underlyingFun = getUnderlyingFun(fun) + if shouldSkipFun(underlyingFun) then + None + else + val funTp = fun.typeOpt.widenTermRefExpr + val paramNames = funTp.paramNamess.flatten + val paramInfos = funTp.paramInfoss.flatten + + Some( + isInfixFun(fun, args) || underlyingFun.isInfix, + ( + args + .zip(paramNames) + .zip(paramInfos) + .collect { + case ((arg, paramName), paramInfo) if !arg.span.isZeroExtent && !isDefaultArg(arg) => + (paramName.fieldName, arg.sourcePos, paramInfo.isByName) + } + ) + ) + case _ => None + else None +end Parameters + +object XRayModeHint: + def unapply(trees: (Tree, Option[Tree]))(using params: InlayHintsParams, ctx: Context): Option[(Type, SourcePosition)] = + if params.hintsXRayMode() then + val (tree, parent) = trees + val isParentApply = parent match + case Some(_: Apply) => true + case _ => false + val isParentOnSameLine = parent match + case Some(sel: Select) if sel.isForComprehensionMethod => false + case Some(par) if par.sourcePos.exists && par.sourcePos.line == tree.sourcePos.line => true + case _ => false + + tree match + /* + anotherTree + .innerSelect() + */ + case a @ Apply(inner, _) + if inner.sourcePos.exists && !isParentOnSameLine && !isParentApply && + endsInSimpleSelect(a) && isEndOfLine(tree.sourcePos) => + Some((a.tpe.widen.deepDealiasAndSimplify, tree.sourcePos)) + /* + innerTree + .select + */ + case select @ Select(innerTree, _) + if innerTree.sourcePos.exists && !isParentOnSameLine && !isParentApply && + isEndOfLine(tree.sourcePos) => + Some((select.tpe.widen.deepDealiasAndSimplify, tree.sourcePos)) + case _ => None + else None + + @tailrec + private def endsInSimpleSelect(ap: Tree)(using ctx: Context): Boolean = + ap match + case Apply(sel: Select, _) => + sel.name != nme.apply && !isInfix(sel) + case Apply(TypeApply(sel: Select, _), _) => + sel.name != nme.apply && !isInfix(sel) + case Apply(innerTree @ Apply(_, _), _) => + endsInSimpleSelect(innerTree) + case _ => false + + private def isEndOfLine(pos: SourcePosition): Boolean = + if pos.exists then + val source = pos.source + val end = pos.end + end >= source.length || source(end) == '\n' || source(end) == '\r' + else false + +end XRayModeHint diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcSymbolSearch.scala b/presentation-compiler/src/main/dotty/tools/pc/PcSymbolSearch.scala index fd3d74f16c16..7d1e53e1ddb2 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcSymbolSearch.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcSymbolSearch.scala @@ -49,7 +49,7 @@ trait PcSymbolSearch: lazy val soughtSymbols: Option[(Set[Symbol], SourcePosition)] = soughtSymbols(path) - def soughtSymbols(path: List[Tree]): Option[(Set[Symbol], SourcePosition)] = + private def soughtSymbols(path: List[Tree]): Option[(Set[Symbol], SourcePosition)] = val sought = path match /* reference of an extension paramter * extension [EF](<>: List[EF]) @@ -148,7 +148,7 @@ trait PcSymbolSearch: /* Import selectors: * import scala.util.Tr@@y */ - case (imp: Import) :: _ if imp.span.contains(pos.span) => + case (imp: ImportOrExport) :: _ if imp.span.contains(pos.span) => imp .selector(pos.span) .map(sym => (symbolAlternatives(sym), sym.sourcePos)) diff --git a/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerAccess.scala b/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerAccess.scala index 1443fbcf37cc..f6fc48e5ae67 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerAccess.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerAccess.scala @@ -3,7 +3,7 @@ package dotty.tools.pc import java.util.concurrent.ScheduledExecutorService import scala.concurrent.ExecutionContextExecutor -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.internal.pc.CompilerAccess import scala.meta.pc.PresentationCompilerConfig diff --git a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala index 0359e21dae89..81517524e3a6 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala @@ -15,11 +15,10 @@ import scala.jdk.CollectionConverters._ 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.pc.reports.EmptyReportContext import scala.meta.internal.metals.PcQueryContext -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.internal.metals.ReportLevel -import scala.meta.internal.metals.StdReportContext import scala.meta.internal.mtags.CommonMtagsEnrichments.* import scala.meta.internal.pc.CompilerAccess import scala.meta.internal.pc.DefinitionResultImpl @@ -54,14 +53,19 @@ case class ScalaPresentationCompiler( folderPath: Option[Path] = None, reportsLevel: ReportLevel = ReportLevel.Info, completionItemPriority: CompletionItemPriority = (_: String) => 0, + reportContext: ReportContext = EmptyReportContext() ) extends PresentationCompiler: + given ReportContext = reportContext + override def supportedCodeActions(): ju.List[String] = List( CodeActionId.ConvertToNamedArguments, CodeActionId.ImplementAbstractMembers, CodeActionId.ExtractMethod, CodeActionId.InlineValue, - CodeActionId.InsertInferredType + CodeActionId.InsertInferredType, + CodeActionId.InsertInferredMethod, + PcConvertToNamedLambdaParameters.codeActionId ).asJava def this() = this("", None, Nil, Nil) @@ -71,10 +75,6 @@ case class ScalaPresentationCompiler( private val forbiddenOptions = Set("-print-lines", "-print-tasty") private val forbiddenDoubleOptions = Set.empty[String] - given ReportContext = - folderPath - .map(StdReportContext(_, _ => buildTargetName, reportsLevel)) - .getOrElse(EmptyReportContext) override def codeAction[T]( params: OffsetParams, @@ -82,26 +82,32 @@ case class ScalaPresentationCompiler( 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")) + 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.InsertInferredMethod, _) => + insertInferredMethod(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 (PcConvertToNamedLambdaParameters.codeActionId, _) => + compilerAccess.withNonInterruptableCompiler(List.empty[l.TextEdit].asJava, params.token) { + access => PcConvertToNamedLambdaParameters(access.compiler(), params).convertToNamedLambdaParameters + }(params.toQueryContext) + case (id, _) => failedFuture(new IllegalArgumentException(s"Unsupported action id $id")) private def failedFuture[T](e: Throwable): CompletableFuture[T] = val f = new CompletableFuture[T]() @@ -113,7 +119,7 @@ case class ScalaPresentationCompiler( ): PresentationCompiler = copy(completionItemPriority = priority) - override def withBuildTargetName(buildTargetName: String) = + override def withBuildTargetName(buildTargetName: String): PresentationCompiler = copy(buildTargetName = Some(buildTargetName)) override def withReportsLoggerLevel(level: String): PresentationCompiler = @@ -349,6 +355,19 @@ case class ScalaPresentationCompiler( .asJava }(params.toQueryContext) + def insertInferredMethod( + params: OffsetParams + ): CompletableFuture[ju.List[l.TextEdit]] = + val empty: ju.List[l.TextEdit] = new ju.ArrayList[l.TextEdit]() + compilerAccess.withNonInterruptableCompiler( + empty, + params.token() + ) { pc => + new InferredMethodProvider(params, pc.compiler(), config, search) + .inferredMethodEdits() + .asJava + }(params.toQueryContext) + override def inlineValue( params: OffsetParams ): CompletableFuture[ju.List[l.TextEdit]] = @@ -508,6 +527,9 @@ case class ScalaPresentationCompiler( def withSearch(search: SymbolSearch): PresentationCompiler = copy(search = search) + override def withReportContext(reportContext: ReportContext): PresentationCompiler = + copy(reportContext = reportContext) + def withWorkspace(workspace: Path): PresentationCompiler = copy(folderPath = Some(workspace)) diff --git a/presentation-compiler/src/main/dotty/tools/pc/SelectionRangeProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/SelectionRangeProvider.scala index 7973f4103ff6..09c44b105555 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/SelectionRangeProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/SelectionRangeProvider.scala @@ -6,7 +6,8 @@ import java.util as ju import scala.jdk.CollectionConverters._ import scala.meta.pc.OffsetParams -import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.untpd.* +import dotty.tools.dotc.ast.NavigateAST import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.interactive.Interactive import dotty.tools.dotc.interactive.InteractiveDriver @@ -23,10 +24,7 @@ import org.eclipse.lsp4j.SelectionRange * @param compiler Metals Global presentation compiler wrapper. * @param params offset params converted from the selectionRange params. */ -class SelectionRangeProvider( - driver: InteractiveDriver, - params: ju.List[OffsetParams] -): +class SelectionRangeProvider(driver: InteractiveDriver, params: ju.List[OffsetParams]): /** * Get the seletion ranges for the provider params @@ -44,10 +42,13 @@ class SelectionRangeProvider( val source = SourceFile.virtual(filePath.toString, text) driver.run(uri, source) val pos = driver.sourcePosition(param) - val path = - Interactive.pathTo(driver.openedTrees(uri), pos)(using ctx) + val unit = driver.compilationUnits(uri) - val bareRanges = path + val untpdPath: List[Tree] = NavigateAST + .pathTo(pos.span, List(unit.untpdTree), true).collect: + case untpdTree: Tree => untpdTree + + val bareRanges = untpdPath .flatMap(selectionRangesFromTree(pos)) val comments = @@ -78,31 +79,43 @@ class SelectionRangeProvider( end selectionRange /** Given a tree, create a seq of [[SelectionRange]]s corresponding to that tree. */ - private def selectionRangesFromTree(pos: SourcePosition)(tree: tpd.Tree)(using Context) = + private def selectionRangesFromTree(pos: SourcePosition)(tree: Tree)(using Context) = def toSelectionRange(srcPos: SourcePosition) = val selectionRange = new SelectionRange() selectionRange.setRange(srcPos.toLsp) selectionRange - val treeSelectionRange = toSelectionRange(tree.sourcePos) - - tree match - case tpd.DefDef(name, paramss, tpt, rhs) => - // If source position is within a parameter list, add a selection range covering that whole list. - val selectedParams = - paramss - .iterator - .flatMap: // parameter list to a sourcePosition covering the whole list - case Seq(param) => Some(param.sourcePos) - case params @ Seq(head, tail*) => - val srcPos = head.sourcePos - val lastSpan = tail.last.span - Some(SourcePosition(srcPos.source, srcPos.span union lastSpan, srcPos.outer)) - case Seq() => None - .find(_.contains(pos)) - .map(toSelectionRange) - selectedParams ++ Seq(treeSelectionRange) - case _ => Seq(treeSelectionRange) + def maybeToSelectionRange(srcPos: SourcePosition): Option[SelectionRange] = + if srcPos.contains(pos) then Some(toSelectionRange(srcPos)) else None + + val treeSelectionRange = Seq(toSelectionRange(tree.sourcePos)) + + def allArgsSelectionRange(args: List[Tree]): Option[SelectionRange] = + args match + case Nil => None + case list => + val srcPos = list.head.sourcePos + val lastSpan = list.last.span + val allArgsSrcPos = SourcePosition(srcPos.source, srcPos.span union lastSpan, srcPos.outer) + maybeToSelectionRange(allArgsSrcPos) + + val allSelectionRanges: Iterable[SelectionRange] = tree match + case vdef @ ValDef(_, _, _) => + maybeToSelectionRange(vdef.namePos) + case tdef @ TypeDef(_, _) => + maybeToSelectionRange(tdef.namePos) + case mdef @ ModuleDef(_, _) => + maybeToSelectionRange(mdef.namePos) + case DefDef(_, paramss, _, _) => + paramss.flatMap(allArgsSelectionRange) + case Apply(_, args) => + allArgsSelectionRange(args) + case TypeApply(_, args) => + allArgsSelectionRange(args) + case Function(args, _) => + allArgsSelectionRange(args) + case _ => Seq.empty + allSelectionRanges ++ treeSelectionRange private def setParent( child: SelectionRange, diff --git a/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala index 5f925ea80ee7..4fab29159bfb 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala @@ -13,7 +13,7 @@ import dotty.tools.pc.utils.InteractiveEnrichments.* import org.eclipse.lsp4j as l import scala.jdk.CollectionConverters.* -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.pc.OffsetParams import scala.meta.pc.SymbolDocumentation import scala.meta.pc.SymbolSearch @@ -84,7 +84,7 @@ object SignatureHelpProvider: case Some(paramDoc) => val newName = if isJavaSymbol && head.name.startsWith("x$") then - paramDoc.nn.displayName() + paramDoc.displayName() else head.name head.copy(name = newName.nn, doc = Some(paramDoc.docstring.nn)) :: rest case _ => head :: rest diff --git a/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala index 8bed605a87f8..49e6973af1b1 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala @@ -11,7 +11,7 @@ import dotty.tools.dotc.core.Flags import dotty.tools.dotc.core.Names.* import dotty.tools.dotc.core.StdNames.nme import dotty.tools.dotc.core.Symbols.* -import dotty.tools.pc.utils.InteractiveEnrichments.deepDealias +import dotty.tools.pc.utils.InteractiveEnrichments.deepDealiasAndSimplify import dotty.tools.pc.SemanticdbSymbols import dotty.tools.pc.utils.InteractiveEnrichments.allSymbols import dotty.tools.pc.utils.InteractiveEnrichments.stripBackticks @@ -51,7 +51,7 @@ class SymbolInformationProvider(using Context): collect(classSym) visited.toList.map(SemanticdbSymbols.symbolName) val dealisedSymbol = - if sym.isAliasType then sym.info.deepDealias.typeSymbol else sym + if sym.isAliasType then sym.info.deepDealiasAndSimplify.typeSymbol else sym val classOwner = sym.ownersIterator.drop(1).find(s => s.isClass || s.is(Flags.Module)) val overridden = sym.denot.allOverriddenSymbols.toList @@ -111,7 +111,7 @@ object SymbolProvider: catch case NonFatal(e) => Nil private def normalizePackage(pkg: String): String = - pkg.replace("/", ".").nn.stripSuffix(".") + pkg.replace("/", ".").stripSuffix(".") private def toSymbols(info: SymbolInfo.SymbolParts)(using Context): List[Symbol] = def collectSymbols(denotation: Denotation): List[Symbol] = diff --git a/presentation-compiler/src/main/dotty/tools/pc/TastyUtils.scala b/presentation-compiler/src/main/dotty/tools/pc/TastyUtils.scala index 62a947aeb50b..0c2afad5f92a 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/TastyUtils.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/TastyUtils.scala @@ -40,7 +40,7 @@ object TastyUtils: end htmlTasty private def tastyHtmlPageTitle(file: URI) = - val filename = Paths.get(file).nn.getFileName().toString + val filename = Paths.get(file).getFileName().toString s"TASTy for $filename" private val standaloneHtmlStyles = diff --git a/presentation-compiler/src/main/dotty/tools/pc/WithCompilationUnit.scala b/presentation-compiler/src/main/dotty/tools/pc/WithCompilationUnit.scala index 8110db269b3b..5ba89324fca8 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/WithCompilationUnit.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/WithCompilationUnit.scala @@ -22,11 +22,11 @@ class WithCompilationUnit( val driver: InteractiveDriver, params: VirtualFileParams, ): - val uri = params.uri() - val filePath = Paths.get(uri) - val sourceText = params.text - val text = sourceText.toCharArray() - val source = + val uri: java.net.URI = params.uri() + val filePath: java.nio.file.Path = Paths.get(uri) + val sourceText: String = params.text + val text: Array[Char] = sourceText.toCharArray() + val source: SourceFile = SourceFile.virtual(filePath.toString, sourceText) driver.run(uri, source) given ctx: Context = driver.currentCtx @@ -76,7 +76,9 @@ class WithCompilationUnit( } else Set.empty val all = - if sym.is(Flags.ModuleClass) then + if sym.is(Flags.Exported) then + Set(sym, sym.sourceSymbol) + else if sym.is(Flags.ModuleClass) then Set(sym, sym.companionModule, sym.companionModule.companion) else if sym.isClass then Set(sym, sym.companionModule, sym.companion.moduleClass) diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/AmmoniteFileCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/AmmoniteFileCompletions.scala index 81337c7d8dcb..7c25f8a69174 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/AmmoniteFileCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/AmmoniteFileCompletions.scala @@ -39,10 +39,10 @@ object AmmoniteFileCompletions: rawFileName: String )(using Context): List[CompletionValue] = val fileName: Option[String] = Option(rawFileName) - .flatMap(_.split("/").nn.lastOption.map(_.nn.stripSuffix(".amm.sc.scala"))) + .flatMap(_.split("/").lastOption.map(_.stripSuffix(".amm.sc.scala"))) val split: List[String] = Option(rawPath) - .fold(Nil)(_.split("\\$file").nn.toList.map(_.nn)) + .fold(Nil)(_.split("\\$file").toList.map(_.nn)) val editRange = selector.headOption.map { sel => if sel.sourcePos.span.isZeroExtent then posRange @@ -63,7 +63,7 @@ object AmmoniteFileCompletions: ) def matches(file: Path): Boolean = - (Files.isDirectory(file) || file.toAbsolutePath().toString.isAmmoniteScript) && + (Files.isDirectory(file) || file.toAbsolutePath().toString.isScalaScript) && query.exists(q => CompletionFuzzy.matches(q.nn, file.getFileName().toString)) (split, workspace) match @@ -71,10 +71,10 @@ object AmmoniteFileCompletions: // drop / or \ val current = workspace.resolve(script.drop(1)) val importPath = translateImportToPath(select).drop(1) - val currentPath = current.nn.getParent().nn.resolve(importPath).nn.toAbsolutePath() + val currentPath = current.getParent().resolve(importPath).toAbsolutePath() val parentTextEdit = - if query.exists(_.nn.isEmpty()) && - Files.exists(currentPath.nn.getParent()) && Files.isDirectory( + if query.exists(_.isEmpty()) && + Files.exists(currentPath.getParent()) && Files.isDirectory( currentPath ) then List(parent) @@ -84,7 +84,7 @@ object AmmoniteFileCompletions: .iterator().nn .asScala .toList - .filter(path => !fileName.contains(path.nn.getFileName().toString.stripSuffix(".sc"))) + .filter(path => !fileName.contains(path.getFileName().toString.stripSuffix(".sc"))) .collect { case file if matches(file) => CompletionValue.FileSystemMember( diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionAffix.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionAffix.scala index 4ed58c773a7c..78f9f5f68bfb 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionAffix.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionAffix.scala @@ -56,6 +56,7 @@ case class CompletionAffix( private def loopPrefix(prefixes: List[PrefixKind]): String = prefixes match case PrefixKind.New :: tail => "new " + loopPrefix(tail) + case PrefixKind.Using :: tail => "using " + loopPrefix(tail) case _ => "" /** @@ -87,7 +88,7 @@ enum SuffixKind: case Brace, Bracket, Template, NoSuffix enum PrefixKind: - case New + case New, Using type Suffix = Affix[SuffixKind] type Prefix = Affix[PrefixKind] 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 40f1ccd2e797..ed181695922e 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala @@ -62,7 +62,7 @@ object CompletionPos: CompletionPos( start, identEnd, - query.nn, + query, sourcePos, offsetParams.uri.nn, wasCursorApplied, 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 ef9f77eb58fc..d3509466040d 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala @@ -4,7 +4,7 @@ package completions import java.nio.file.Path import scala.jdk.CollectionConverters._ -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.pc.OffsetParams import scala.meta.pc.PresentationCompilerConfig import scala.meta.pc.SymbolSearch @@ -174,11 +174,12 @@ class CompletionProvider( */ private def applyCompletionCursor(params: OffsetParams): (Boolean, String) = val text = params.text().nn - val offset = params.offset().nn + val offset = params.offset() val query = Completion.naiveCompletionPrefix(text, offset) - - if offset > 0 && text.charAt(offset - 1).isUnicodeIdentifierPart - && !CompletionProvider.allKeywords.contains(query) then false -> text + def isValidLastChar = + val lastChar = text.charAt(offset - 1) + lastChar.isUnicodeIdentifierPart || lastChar == '.' + if offset > 0 && isValidLastChar && !CompletionProvider.allKeywords.contains(query) then false -> text else val isStartMultilineComment = diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala index 4b2e76807895..b594c6fbdb2f 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -5,7 +5,7 @@ import java.nio.file.Path import java.nio.file.Paths import scala.collection.mutable -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.internal.mtags.CoursierComplete import scala.meta.internal.pc.{IdentifierComparator, MemberOrdering, CompletionFuzzy} import scala.meta.pc.* @@ -15,6 +15,7 @@ import dotty.tools.dotc.ast.untpd import dotty.tools.dotc.core.Comments.Comment import dotty.tools.dotc.core.Constants.Constant import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Decorators.toTermName import dotty.tools.dotc.core.Denotations.SingleDenotation import dotty.tools.dotc.core.Flags import dotty.tools.dotc.core.Flags.* @@ -74,7 +75,15 @@ class Completions( case tpe :: (appl: AppliedTypeTree) :: _ if appl.tpt == tpe => false case sel :: (funSel @ Select(fun, name)) :: (appl: GenericApply) :: _ if appl.fun == funSel && sel == fun => false - case _ => true) + case _ => true) && + (adjustedPath match + /* In case of `class X derives TC@@` we shouldn't add `[]` + */ + case Ident(_) :: (templ: untpd.DerivingTemplate) :: _ => + val pos = completionPos.toSourcePosition + !templ.derived.exists(_.sourcePos.contains(pos)) + case _ => true + ) private lazy val isNew: Boolean = Completion.isInNewContext(adjustedPath) @@ -193,13 +202,23 @@ class Completions( ) end isAbstractType - private def findSuffix(symbol: Symbol): CompletionAffix = + private def findSuffix(symbol: Symbol, adjustedPath: List[untpd.Tree]): CompletionAffix = CompletionAffix.empty .chain { suffix => // for [] suffix if shouldAddSuffix && symbol.info.typeParams.nonEmpty then suffix.withNewSuffixSnippet(Affix(SuffixKind.Bracket)) else suffix } + .chain{ suffix => + adjustedPath match + case (ident: Ident) :: (app@Apply(_, List(arg))) :: _ => + app.symbol.info match + case mt@MethodType(termNames) if app.symbol.paramSymss.last.exists(_.is(Given)) && + !text.substring(app.fun.span.start, arg.span.end).contains("using") => + suffix.withNewPrefix(Affix(PrefixKind.Using)) + case _ => suffix + case _ => suffix + } .chain { suffix => // for () suffix if shouldAddSuffix && symbol.is(Flags.Method) then val paramss = getParams(symbol) @@ -271,7 +290,7 @@ class Completions( val existsApply = extraMethodDenots.exists(_.symbol.name == nme.apply) extraMethodDenots.map { methodDenot => - val suffix = findSuffix(methodDenot.symbol) + val suffix = findSuffix(methodDenot.symbol, adjustedPath) val affix = if methodDenot.symbol.isConstructor && existsApply then adjustedPath match case (select @ Select(qual, _)) :: _ => @@ -293,7 +312,7 @@ class Completions( if skipOriginalDenot then extraCompletionValues else - val suffix = findSuffix(denot.symbol) + val suffix = findSuffix(denot.symbol, adjustedPath) val name = undoBacktick(label) val denotCompletionValue = toCompletionValue(name, denot, suffix) denotCompletionValue :: extraCompletionValues @@ -520,7 +539,7 @@ class Completions( config.isCompletionSnippetsEnabled() ) (args, false) - val singletonCompletions = InterCompletionType.inferType(path).map( + val singletonCompletions = InferCompletionType.inferType(path).map( SingletonCompletions.contribute(path, _, completionPos) ).getOrElse(Nil) (singletonCompletions ++ advanced, exclusive) @@ -588,7 +607,7 @@ class Completions( else false, ) Some(search.search(query, buildTargetIdentifier, visitor).nn) - else if completionMode.is(Mode.Member) then + else if completionMode.is(Mode.Member) && query.nonEmpty then val visitor = new CompilerSearchVisitor(sym => def isExtensionMethod = sym.is(ExtensionMethod) && qualType.widenDealias <:< sym.extensionParam.info.widenDealias @@ -734,15 +753,25 @@ class Completions( defn.Object_notifyAll, defn.Object_notify, defn.Predef_undefined, - defn.ObjectClass.info.member(nme.wait_).symbol, // NOTE(olafur) IntelliJ does not complete the root package and without this filter // then `_root_` would appear as a completion result in the code `foobar(_)` defn.RootPackage, // NOTE(gabro) valueOf was added as a Predef member in 2.13. We filter it out since is a niche // use case and it would appear upon typing 'val' - defn.ValueOfClass.info.member(nme.valueOf).symbol, - defn.ScalaPredefModule.requiredMethod(nme.valueOf) - ).flatMap(_.alternatives.map(_.symbol)).toSet + defn.ValueOfClass + ) ++ ( + Set( + defn.ObjectClass.info.member(nme.wait_), + defn.ScalaPredefModule.info.member(nme.valueOf) + ).flatMap(_.alternatives.map(_.symbol)).toSet + ) + + private lazy val EqualsClass: ClassSymbol = requiredClass("scala.Equals") + private lazy val ArrowAssocClass: ClassSymbol = requiredClass("scala.Predef.ArrowAssoc") + private lazy val EnsuringClass: ClassSymbol = requiredClass("scala.Predef.Ensuring") + private lazy val StringFormatClass: ClassSymbol = requiredClass("scala.Predef.StringFormat") + private lazy val nnMethod: Symbol = defn.ScalaPredefModule.info.member("nn".toTermName).symbol + private lazy val runtimeCheckedMethod: Symbol = defn.ScalaPredefModule.info.member("runtimeChecked".toTermName).symbol private def isNotLocalForwardReference(sym: Symbol)(using Context): Boolean = !sym.isLocalToBlock || @@ -762,6 +791,17 @@ class Completions( (sym.isField && !isJavaClass && !isModuleOrClass) || sym.getter != NoSymbol catch case _ => false + def isInheritedFromScalaLibrary(sym: Symbol) = + sym.owner == defn.AnyClass || + sym.owner == defn.ObjectClass || + sym.owner == defn.ProductClass || + sym.owner == EqualsClass || + sym.owner == ArrowAssocClass || + sym.owner == EnsuringClass || + sym.owner == StringFormatClass || + sym == nnMethod || + sym == runtimeCheckedMethod + def symbolRelevance(sym: Symbol): Int = var relevance = 0 // symbols defined in this file are more relevant @@ -779,7 +819,7 @@ class Completions( case _ => // symbols whose owner is a base class are less relevant - if sym.owner == defn.AnyClass || sym.owner == defn.ObjectClass + if isInheritedFromScalaLibrary(sym) then relevance |= IsInheritedBaseMethod // symbols not provided via an implicit are more relevant if sym.is(Implicit) || @@ -791,7 +831,7 @@ class Completions( // accessors of case class members are more relevant if !sym.is(CaseAccessor) then relevance |= IsNotCaseAccessor // public symbols are more relevant - if !sym.isPublic then relevance |= IsNotCaseAccessor + if !sym.isPublic then relevance |= IsNotPublic // synthetic symbols are less relevant (e.g. `copy` on case classes) if sym.is(Synthetic) && !sym.isAllOf(EnumCase) then relevance |= IsSynthetic @@ -885,7 +925,7 @@ class Completions( application: CompletionApplication ): Ordering[CompletionValue] = new Ordering[CompletionValue]: - val queryLower = completionPos.query.toLowerCase() + val queryLower: String = completionPos.query.toLowerCase() val fuzzyCache = mutable.Map.empty[CompletionValue, Int] def compareLocalSymbols(s1: Symbol, s2: Symbol): Int = diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/InterpolatorCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/InterpolatorCompletions.scala index da46e5167834..9cceff7310c6 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/InterpolatorCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/InterpolatorCompletions.scala @@ -1,7 +1,7 @@ package dotty.tools.pc.completions import scala.collection.mutable.ListBuffer -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.internal.pc.CompletionFuzzy import scala.meta.internal.pc.InterpolationSplice import scala.meta.pc.PresentationCompilerConfig diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala index 4fbf22e2294c..2e89b4e5bb99 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala @@ -6,7 +6,7 @@ import java.net.URI import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.jdk.CollectionConverters._ -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.internal.pc.CompletionFuzzy import scala.meta.pc.PresentationCompilerConfig import scala.meta.pc.SymbolSearch @@ -93,7 +93,7 @@ object CaseKeywordCompletion: case (Ident(v), tpe) => v.decoded == value case (Select(_, v), tpe) => v.decoded == value case t => false - .map((_, id) => argPts(id).widen.deepDealias) + .map((_, id) => argPts(id).widen.deepDealiasAndSimplify) /* Parent is a function expecting a case match expression */ case TreeApply(fun, _) if !fun.tpe.isErroneous => fun.tpe.paramInfoss match @@ -103,12 +103,12 @@ object CaseKeywordCompletion: ) => val args = head.argTypes.init if args.length > 1 then - Some(definitions.tupleType(args).widen.deepDealias) - else args.headOption.map(_.widen.deepDealias) + Some(definitions.tupleType(args).widen.deepDealiasAndSimplify) + else args.headOption.map(_.widen.deepDealiasAndSimplify) case _ => None case _ => None case sel => - Some(sel.tpe.widen.deepDealias) + Some(sel.tpe.widen.deepDealiasAndSimplify) selTpe .collect { case selTpe if selTpe != NoType => @@ -279,8 +279,8 @@ object CaseKeywordCompletion: clientSupportsSnippets ) - val tpeStr = printer.tpe(selector.tpe.widen.deepDealias.bounds.hi) - val tpe = selector.typeOpt.widen.deepDealias.bounds.hi match + val tpeStr = printer.tpe(selector.tpe.widen.deepDealiasAndSimplify.bounds.hi) + val tpe = selector.typeOpt.widen.deepDealiasAndSimplify.bounds.hi match case tr @ TypeRef(_, _) => tr.underlying case t => t diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala index 7b88d8edfbc8..faf6d715d8cf 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala @@ -2,37 +2,23 @@ package dotty.tools.pc.completions import scala.util.Try -import dotty.tools.dotc.ast.Trees.ValDef import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.ast.untpd import dotty.tools.dotc.core.Constants.Constant -import dotty.tools.dotc.core.ContextOps.localContext import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Flags -import dotty.tools.dotc.core.Flags.Method import dotty.tools.dotc.core.NameKinds.DefaultGetterName import dotty.tools.dotc.core.Names.Name import dotty.tools.dotc.core.StdNames.* -import dotty.tools.dotc.core.SymDenotations.NoDenotation -import dotty.tools.dotc.core.Symbols import dotty.tools.dotc.core.Symbols.defn -import dotty.tools.dotc.core.Symbols.NoSymbol import dotty.tools.dotc.core.Symbols.Symbol -import dotty.tools.dotc.core.Types.AndType -import dotty.tools.dotc.core.Types.AppliedType -import dotty.tools.dotc.core.Types.MethodType -import dotty.tools.dotc.core.Types.OrType -import dotty.tools.dotc.core.Types.RefinedType -import dotty.tools.dotc.core.Types.TermRef -import dotty.tools.dotc.core.Types.Type -import dotty.tools.dotc.core.Types.TypeBounds -import dotty.tools.dotc.core.Types.WildcardType +import dotty.tools.dotc.core.Types.* import dotty.tools.pc.IndexedContext import dotty.tools.pc.utils.InteractiveEnrichments.* import scala.annotation.tailrec -import dotty.tools.dotc.core.Denotations.Denotation -import dotty.tools.dotc.core.Denotations.MultiDenotation -import dotty.tools.dotc.core.Denotations.SingleDenotation +import dotty.tools.pc.ApplyArgsExtractor +import dotty.tools.pc.ParamSymbol +import dotty.tools.pc.ApplyExtractor object NamedArgCompletions: @@ -43,36 +29,13 @@ object NamedArgCompletions: clientSupportsSnippets: Boolean, )(using ctx: Context): List[CompletionValue] = path match - case (ident: Ident) :: ValDef(_, _, _) :: Block(_, app: Apply) :: _ - if !app.fun.isInfix => + case (ident: Ident) :: ApplyExtractor(app) => contribute( - Some(ident), + ident, app, indexedContext, clientSupportsSnippets, ) - case (ident: Ident) :: rest => - def getApplyForContextFunctionParam(path: List[Tree]): Option[Apply] = - path match - // fun(arg@@) - case (app: Apply) :: _ => Some(app) - // fun(arg@@), where fun(argn: Context ?=> SomeType) - // recursively matched for multiple context arguments, e.g. Context1 ?=> Context2 ?=> SomeType - case (_: DefDef) :: Block(List(_), _: Closure) :: rest => - getApplyForContextFunctionParam(rest) - case _ => None - val contribution = - for - app <- getApplyForContextFunctionParam(rest) - if !app.fun.isInfix - yield - contribute( - Some(ident), - app, - indexedContext, - clientSupportsSnippets, - ) - contribution.getOrElse(Nil) case (app: Apply) :: _ => /** * def foo(aaa: Int, bbb: Int, ccc: Int) = ??? @@ -86,7 +49,7 @@ object NamedArgCompletions: untypedPath match case (ident: Ident) :: (app: Apply) :: _ => contribute( - Some(ident), + ident, app, indexedContext, clientSupportsSnippets, @@ -99,7 +62,7 @@ object NamedArgCompletions: end contribute private def contribute( - ident: Option[Ident], + ident: Ident, apply: Apply, indexedContext: IndexedContext, clientSupportsSnippets: Boolean, @@ -110,155 +73,14 @@ object NamedArgCompletions: case Literal(Constant(null)) => true // nullLiteral case _ => false - def collectArgss(a: Apply): List[List[Tree]] = - def stripContextFuntionArgument(argument: Tree): List[Tree] = - argument match - case Block(List(d: DefDef), _: Closure) => - d.rhs match - case app: Apply => - app.args - case b @ Block(List(_: DefDef), _: Closure) => - stripContextFuntionArgument(b) - case _ => Nil - case v => List(v) - - val args = a.args.flatMap(stripContextFuntionArgument) - a.fun match - case app: Apply => collectArgss(app) :+ args - case _ => List(args) - end collectArgss - - val method = apply.fun - - val argss = collectArgss(apply) - - def fallbackFindApply(sym: Symbol) = - sym.info.member(nme.apply) match - case NoDenotation => Nil - case den => List(den.symbol) - - // fallback for when multiple overloaded methods match the supplied args - def fallbackFindMatchingMethods() = - def matchingMethodsSymbols( - method: Tree - ): List[Symbol] = - method match - case Ident(name) => indexedContext.findSymbol(name).getOrElse(Nil) - case Select(This(_), name) => indexedContext.findSymbol(name).getOrElse(Nil) - case sel @ Select(from, name) => - val symbol = from.symbol - val ownerSymbol = - if symbol.is(Method) && symbol.owner.isClass then - Some(symbol.owner) - else Try(symbol.info.classSymbol).toOption - ownerSymbol.map(sym => sym.info.member(name)).collect{ - case single: SingleDenotation => List(single.symbol) - case multi: MultiDenotation => multi.allSymbols - }.getOrElse(Nil) - case Apply(fun, _) => matchingMethodsSymbols(fun) - case _ => Nil - val matchingMethods = - for - potentialMatch <- matchingMethodsSymbols(method) - if potentialMatch.is(Flags.Method) && - potentialMatch.vparamss.length >= argss.length && - Try(potentialMatch.isAccessibleFrom(apply.symbol.info)).toOption - .getOrElse(false) && - potentialMatch.vparamss - .zip(argss) - .reverse - .zipWithIndex - .forall { case (pair, index) => - FuzzyArgMatcher(potentialMatch.tparams) - .doMatch(allArgsProvided = index != 0, ident) - .tupled(pair) - } - yield potentialMatch - matchingMethods - end fallbackFindMatchingMethods - - val matchingMethods: List[Symbols.Symbol] = - if method.symbol.paramSymss.nonEmpty then - val allArgsAreSupplied = - val vparamss = method.symbol.vparamss - vparamss.length == argss.length && vparamss - .zip(argss) - .lastOption - .exists { case (baseParams, baseArgs) => - baseArgs.length == baseParams.length - } - // ``` - // m(arg : Int) - // m(arg : Int, anotherArg : Int) - // m(a@@) - // ``` - // complier will choose the first `m`, so we need to manually look for the other one - if allArgsAreSupplied then - val foundPotential = fallbackFindMatchingMethods() - if foundPotential.contains(method.symbol) then foundPotential - else method.symbol :: foundPotential - else List(method.symbol) - else if method.symbol.is(Method) || method.symbol == NoSymbol then - fallbackFindMatchingMethods() - else fallbackFindApply(method.symbol) - end if - end matchingMethods - - val allParams = matchingMethods.flatMap { methodSym => - val vparamss = methodSym.vparamss + val argsAndParams = ApplyArgsExtractor.getArgsAndParams( + Some(indexedContext), + apply, + ident.span + ) - // get params and args we are interested in - // e.g. - // in the following case, the interesting args and params are - // - params: [apple, banana] - // - args: [apple, b] - // ``` - // def curry(x: Int)(apple: String, banana: String) = ??? - // curry(1)(apple = "test", b@@) - // ``` - val (baseParams0, baseArgs) = - vparamss.zip(argss).lastOption.getOrElse((Nil, Nil)) - - val baseParams: List[ParamSymbol] = - def defaultBaseParams = baseParams0.map(JustSymbol(_)) - @tailrec - def getRefinedParams(refinedType: Type, level: Int): List[ParamSymbol] = - if level > 0 then - val resultTypeOpt = - refinedType match - case RefinedType(AppliedType(_, args), _, _) => args.lastOption - case AppliedType(_, args) => args.lastOption - case _ => None - resultTypeOpt match - case Some(resultType) => getRefinedParams(resultType, level - 1) - case _ => defaultBaseParams - else - refinedType match - case RefinedType(AppliedType(_, args), _, MethodType(ri)) => - baseParams0.zip(ri).zip(args).map { case ((sym, name), arg) => - RefinedSymbol(sym, name, arg) - } - case _ => defaultBaseParams - // finds param refinements for lambda expressions - // val hello: (x: Int, y: Int) => Unit = (x, _) => println(x) - @tailrec - def refineParams(method: Tree, level: Int): List[ParamSymbol] = - method match - case Select(Apply(f, _), _) => refineParams(f, level + 1) - case Select(h, name) => - // for Select(foo, name = apply) we want `foo.symbol` - if name == nme.apply then getRefinedParams(h.symbol.info, level) - else getRefinedParams(method.symbol.info, level) - case Apply(f, _) => - refineParams(f, level + 1) - case _ => getRefinedParams(method.symbol.info, level) - refineParams(method, 0) - end baseParams - - val args = ident - .map(i => baseArgs.filterNot(_ == i)) - .getOrElse(baseArgs) - .filterNot(isUselessLiteral) + val allParams = argsAndParams.flatMap { case (baseArgs, baseParams) => + val args = baseArgs.filterNot( a => a == ident || isUselessLiteral(a)) @tailrec def isDefaultArg(t: Tree): Boolean = t match @@ -293,9 +115,8 @@ object NamedArgCompletions: ) } - val prefix = ident - .map(_.name.toString) - .getOrElse("") + val prefix = + ident.name.toString .replace(Cursor.value, "") .nn @@ -330,7 +151,7 @@ object NamedArgCompletions: allParams.exists(param => param.name.startsWith(prefix)) def isExplicitlyCalled = suffix.startsWith(prefix) def hasParamsToFill = allParams.count(!_.symbol.is(Flags.HasDefault)) > 1 - if clientSupportsSnippets && matchingMethods.length == 1 && (shouldShow || isExplicitlyCalled) && hasParamsToFill + if clientSupportsSnippets && argsAndParams.length == 1 && (shouldShow || isExplicitlyCalled) && hasParamsToFill then val editText = allParams.zipWithIndex .collect { @@ -375,73 +196,4 @@ object NamedArgCompletions: ) ::: findPossibleDefaults() ::: fillAllFields() end contribute - extension (method: Symbols.Symbol) - def vparamss(using Context) = method.filteredParamss(_.isTerm) - def tparams(using Context) = method.filteredParamss(_.isType).flatten - def filteredParamss(f: Symbols.Symbol => Boolean)(using Context) = - method.paramSymss.filter(params => params.forall(f)) end NamedArgCompletions - -class FuzzyArgMatcher(tparams: List[Symbols.Symbol])(using Context): - - /** - * A heuristic for checking if the passed arguments match the method's arguments' types. - * For non-polymorphic methods we use the subtype relation (`<:<`) - * and for polymorphic methods we use a heuristic. - * We check the args types not the result type. - */ - def doMatch( - allArgsProvided: Boolean, - ident: Option[Ident] - )(expectedArgs: List[Symbols.Symbol], actualArgs: List[Tree]) = - (expectedArgs.length == actualArgs.length || - (!allArgsProvided && expectedArgs.length >= actualArgs.length)) && - actualArgs.zipWithIndex.forall { - case (arg: Ident, _) if ident.contains(arg) => true - case (NamedArg(name, arg), _) => - expectedArgs.exists { expected => - expected.name == name && (!arg.hasType || arg.typeOpt.unfold - .fuzzyArg_<:<(expected.info)) - } - case (arg, i) => - !arg.hasType || arg.typeOpt.unfold.fuzzyArg_<:<(expectedArgs(i).info) - } - - extension (arg: Type) - def fuzzyArg_<:<(expected: Type) = - if tparams.isEmpty then arg <:< expected - else arg <:< substituteTypeParams(expected) - def unfold = - arg match - case arg: TermRef => arg.underlying - case e => e - - private def substituteTypeParams(t: Type): Type = - t match - case e if tparams.exists(_ == e.typeSymbol) => - val matchingParam = tparams.find(_ == e.typeSymbol).get - matchingParam.info match - case b @ TypeBounds(_, _) => WildcardType(b) - case _ => WildcardType - case o @ OrType(e1, e2) => - OrType(substituteTypeParams(e1), substituteTypeParams(e2), o.isSoft) - case AndType(e1, e2) => - AndType(substituteTypeParams(e1), substituteTypeParams(e2)) - case AppliedType(et, eparams) => - AppliedType(et, eparams.map(substituteTypeParams)) - case _ => t - -end FuzzyArgMatcher - -sealed trait ParamSymbol: - def name: Name - def info: Type - def symbol: Symbol - def nameBackticked(using Context) = name.decoded.backticked - -case class JustSymbol(symbol: Symbol)(using Context) extends ParamSymbol: - def name: Name = symbol.name - def info: Type = symbol.info - -case class RefinedSymbol(symbol: Symbol, name: Name, info: Type) - extends ParamSymbol diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala index 8123bc8fa216..f01a1e9b8cd8 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala @@ -4,7 +4,7 @@ package completions import java.util as ju import scala.jdk.CollectionConverters._ -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.pc.OffsetParams import scala.meta.pc.PresentationCompilerConfig import scala.meta.pc.PresentationCompilerConfig.OverrideDefFormat @@ -511,10 +511,10 @@ object OverrideCompletions: Context ): Option[Int] = defn match - case td: TypeDef if text.charAt(td.rhs.span.end) == ':' => + case td: TypeDef if (td.rhs.span.end < text.length) && text.charAt(td.rhs.span.end) == ':' => Some(td.rhs.span.end) case TypeDef(_, temp : Template) => - temp.parentsOrDerived.lastOption.map(_.span.end).filter(text.charAt(_) == ':') + temp.parentsOrDerived.lastOption.map(_.span.end).filter(idx => text.length > idx && text.charAt(idx) == ':') case _ => None private def fallbackFromParent(parent: Tree, name: String)(using Context) = diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala index 53c4e01980bc..621ef2e5c158 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala @@ -42,7 +42,7 @@ object SingletonCompletions: } yield value private def collectSingletons(tpe: Type)(using Context): List[Constant] = - tpe.deepDealias match + tpe.deepDealiasAndSimplify match case ConstantType(value) => List(value) case OrType(tpe1, tpe2) => collectSingletons(tpe1) ++ collectSingletons(tpe2) diff --git a/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala b/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala index d42795ffd60d..5dee96c6133c 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala @@ -2,7 +2,7 @@ package dotty.tools.pc.printer import scala.collection.mutable import scala.meta.internal.jdk.CollectionConverters.* -import scala.meta.internal.metals.ReportContext +import scala.meta.pc.reports.ReportContext import scala.meta.internal.mtags.KeywordWrapper import scala.meta.pc.SymbolDocumentation import scala.meta.pc.SymbolSearch @@ -17,8 +17,7 @@ import dotty.tools.dotc.core.Names import dotty.tools.dotc.core.Names.Name import dotty.tools.dotc.core.Names.NameOrdering import dotty.tools.dotc.core.StdNames -import dotty.tools.dotc.core.Symbols.NoSymbol -import dotty.tools.dotc.core.Symbols.Symbol +import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.core.Types.Type import dotty.tools.dotc.printing.RefinedPrinter @@ -174,14 +173,13 @@ class ShortenedTypePrinter( res.toPrefixText } - override def toTextPrefixOf(tp: NamedType): Text = controlled { val maybeRenamedPrefix: Option[Text] = findRename(tp) def trimmedPrefix: Text = if !tp.designator.isInstanceOf[Symbol] && tp.typeSymbol == NoSymbol then super.toTextPrefixOf(tp) else - indexedCtx.lookupSym(tp.symbol) match + indexedCtx.lookupSym(tp.symbol, Some(tp.prefix)) match case _ if indexedCtx.rename(tp.symbol).isDefined => Text() // symbol is missing and is accessible statically, we can import it and add proper prefix case Result.Missing if isAccessibleStatically(tp.symbol) => @@ -215,7 +213,9 @@ class ShortenedTypePrinter( case ConstantType(const) => toText(const) case _ => toTextRef(tp) ~ ".type" - def tpe(tpe: Type): String = toText(tpe).mkString(defaultWidth, false) + def tpe(tpe: Type): String = + val dealiased = if (tpe.isNamedTupleType) tpe.deepDealiasAndSimplify else tpe + toText(dealiased).mkString(defaultWidth, false) def hoverSymbol(sym: Symbol, info: Type)(using Context): String = val typeSymbol = info.typeSymbol @@ -255,7 +255,6 @@ class ShortenedTypePrinter( end hoverSymbol def isImportedByDefault(sym: Symbol): Boolean = - import dotty.tools.dotc.core.Symbols.defn lazy val effectiveOwner = sym.effectiveOwner sym.isType && (effectiveOwner == defn.ScalaPackageClass || effectiveOwner == defn.ScalaPredefModuleClass) @@ -350,7 +349,9 @@ class ShortenedTypePrinter( val paramLabelss = label(methodParams) val extLabelss = label(extParams) - val returnType = tpe(gtpe.finalResultType) + val retType = gtpe.finalResultType + val simplified = if (retType.typeSymbol.isAliasType) retType else retType.deepDealiasAndSimplify + val returnType = tpe(simplified) def extensionSignatureString = val extensionSignature = paramssString(extLabelss, extParams) if extParams.nonEmpty then @@ -495,9 +496,9 @@ class ShortenedTypePrinter( val info = nameToInfo .get(param.name) .flatMap { info => - // In some cases, paramInfo becomes Nothing (e.g. CompletionOverrideSuite#cake) + // In some cases, paramInfo becomes `... & Nothing` (e.g. CompletionOverrideSuite#cake) // which is meaningless, in that case, fallback to param.info - if info.isNothingType then None + if info <:< defn.NothingType then None else Some(info) } .getOrElse(param.info) 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 f5c18160b5fd..15715143b217 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/utils/InteractiveEnrichments.scala @@ -178,7 +178,7 @@ object InteractiveEnrichments extends CommonMtagsEnrichments: if sym.is(Module) then sym.companionClass else sym.companionModule def dealiasType: Symbol = - if sym.isType then sym.info.deepDealias.typeSymbol else sym + if sym.isType then sym.info.deepDealiasAndSimplify.typeSymbol else sym def nameBackticked: String = nameBackticked(Set.empty[String]) @@ -339,7 +339,7 @@ object InteractiveEnrichments extends CommonMtagsEnrichments: end enclosedChildren end extension - extension (imp: Import) + extension (imp: ImportOrExport) def selector(span: Span)(using Context): Option[Symbol] = for sel <- imp.selectors.find(_.span.contains(span)) yield imp.expr.symbol.info.member(sel.name).symbol @@ -402,17 +402,18 @@ object InteractiveEnrichments extends CommonMtagsEnrichments: end extension extension (tpe: Type) - def deepDealias(using Context): Type = - tpe.dealias match + def deepDealiasAndSimplify(using Context): Type = + val dealiased = tpe.dealias match case app @ AppliedType(tycon, params) => - AppliedType(tycon, params.map(_.deepDealias)) + AppliedType(tycon, params.map(_.deepDealiasAndSimplify)) case aliasingBounds: AliasingBounds => - aliasingBounds.derivedAlias(aliasingBounds.alias.dealias) + aliasingBounds.derivedAlias(aliasingBounds.alias.deepDealiasAndSimplify) case TypeBounds(lo, hi) => TypeBounds(lo.dealias, hi.dealias) case RefinedType(parent, name, refinedInfo) => - RefinedType(parent.dealias, name, refinedInfo.deepDealias) + RefinedType(parent.dealias, name, refinedInfo.deepDealiasAndSimplify) case dealised => dealised + dealiased.simplified extension[T] (list: List[T]) def get(n: Int): Option[T] = if 0 <= n && n < list.size then Some(list(n)) else None diff --git a/presentation-compiler/src/main/dotty/tools/pc/utils/TermNameInference.scala b/presentation-compiler/src/main/dotty/tools/pc/utils/TermNameInference.scala new file mode 100644 index 000000000000..a1a09968325c --- /dev/null +++ b/presentation-compiler/src/main/dotty/tools/pc/utils/TermNameInference.scala @@ -0,0 +1,56 @@ +package dotty.tools.pc.utils + +/** + * Helpers for generating variable names based on the desired types. + */ +object TermNameInference { + + /** Single character names for types. (`Int` => `i`, `i1`, `i2`, ...) */ + def singleLetterNameStream(typeName: String): LazyList[String] = { + sanitizeInput(typeName).fold(saneNamesStream) { typeName1 => + val firstCharStr = typeName1.headOption.getOrElse('x').toLower.toString + numberedStreamFromName(firstCharStr) + } + } + + /** Names only from upper case letters (`OnDemandSymbolIndex` => `odsi`, `odsi1`, `odsi2`, ...) */ + def shortNameStream(typeName: String): LazyList[String] = { + sanitizeInput(typeName).fold(saneNamesStream) { typeName1 => + val upperCases = typeName1.filter(_.isUpper).map(_.toLower) + val name = if (upperCases.isEmpty) typeName1 else upperCases + numberedStreamFromName(name) + } + } + + /** Names from lower case letters (`OnDemandSymbolIndex` => `onDemandSymbolIndex`, `onDemandSymbolIndex1`, ...) */ + def fullNameStream(typeName: String): LazyList[String] = { + sanitizeInput(typeName).fold(saneNamesStream) { typeName1 => + val withFirstLower = + typeName1.headOption.map(_.toLower).getOrElse('x').toString + typeName1.drop(1) + numberedStreamFromName(withFirstLower) + } + } + + /** A lazy list of names: a, b, ..., z, aa, ab, ..., az, ba, bb, ... */ + def saneNamesStream: LazyList[String] = { + val letters = ('a' to 'z').map(_.toString) + def computeNext(acc: String): String = { + if (acc.last == 'z') + computeNext(acc.init) + letters.head + else + acc.init + letters(letters.indexOf(acc.last) + 1) + } + def loop(acc: String): LazyList[String] = + acc #:: loop(computeNext(acc)) + loop("a") + } + + private def sanitizeInput(typeName: String): Option[String] = + val typeName1 = typeName.filter(_.isLetterOrDigit) + Option.when(typeName1.nonEmpty)(typeName1) + + private def numberedStreamFromName(name: String): LazyList[String] = { + val rest = LazyList.from(1).map(name + _) + name #:: rest + } +} diff --git a/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala b/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala index 7d29e6c4dda9..431e4908013a 100644 --- a/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala @@ -30,12 +30,15 @@ class BaseInlayHintsSuite extends BasePCSuite { withPkg.length() ) val pcParams = CompilerInlayHintsParams( - rangeParams, - true, - true, - true, - true, - hintsInPatternMatch + rangeParams = rangeParams, + inferredTypes = true, + typeParameters = true, + implicitParameters = true, + hintsXRayMode = true, + byNameParameters = true, + implicitConversions = true, + namedParameters = true, + hintsInPatternMatch = hintsInPatternMatch ) val inlayHints = presentationCompiler diff --git a/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala b/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala index a26c31ef084d..5128303d105f 100644 --- a/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala @@ -25,7 +25,7 @@ import org.junit.runner.RunWith import scala.meta.pc.CompletionItemPriority object TestResources: - val classpath = BuildInfo.ideTestsDependencyClasspath.map(_.toPath).toSeq + val classpath: Seq[Path] = BuildInfo.ideTestsDependencyClasspath.map(_.toPath).toSeq val classpathSearch = ClasspathSearch.fromClasspath(classpath, ExcludedPackagesHandler.default) @@ -34,7 +34,7 @@ abstract class BasePCSuite extends PcAssertions: val completionItemPriority: CompletionItemPriority = (_: String) => 0 private val isDebug = ManagementFactory.getRuntimeMXBean.getInputArguments.toString.contains("-agentlib:jdwp") - val tmp = Files.createTempDirectory("stable-pc-tests") + val tmp: Path = Files.createTempDirectory("stable-pc-tests") val executorService: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor() val testingWorkspaceSearch = TestingWorkspaceSearch( diff --git a/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala index b2d837e2ff50..9a6f2af24efb 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala @@ -8,9 +8,9 @@ import org.junit.{Before, Test} import scala.language.unsafeNulls 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.meta.pc.reports.EmptyReportContext import scala.concurrent.Future import scala.concurrent.Await import scala.meta.pc.VirtualFileParams @@ -42,7 +42,7 @@ class CompilerCachingSuite extends BasePCSuite: }(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) + private def emptyQueryContext = PcQueryContext(None, () => "")(using EmptyReportContext()) @Before def beforeEach: Unit = @@ -83,6 +83,22 @@ class CompilerCachingSuite extends BasePCSuite: assert(contextPostCursor == contextPostSecond) checkCompilationCount(4) + @Test + def `dot-compilation-does-not-corrupt-cache`: Unit = + val contextPreCompilation = getContext() + + val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = 1.", 14, EmptyCancelToken) + presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit) + val contextPostFirst = getContext() + assert(contextPreCompilation != contextPostFirst) + checkCompilationCount(4) + + presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit) + val contextPostSecond = getContext() + assert(contextPreCompilation != contextPostFirst) + assert(contextPostSecond == contextPostFirst) + checkCompilationCount(4) + @Test def `compilation-for-same-snippet-is-cached`: Unit = val contextPreCompilation = getContext() diff --git a/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala index ccdc68ef1cad..b796f44f12ca 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala @@ -47,6 +47,24 @@ class InferExpectedTypeSuite extends BasePCSuite: |""".stripMargin ) + @Test def `basic-params` = + check( + """|def paint(c: Int, f: String, d: List[String]) = ??? + |val _ = paint(1, "aa", @@) + |""".stripMargin, + """|List[String] + |""".stripMargin + ) + + @Test def `basic-type-param` = + check( + """|def paint[T](c: T) = ??? + |val _ = paint[Int](@@) + |""".stripMargin, + """|Int + |""".stripMargin + ) + @Test def `type-ascription` = check( """|def doo = (@@ : Double) @@ -290,3 +308,105 @@ class InferExpectedTypeSuite extends BasePCSuite: """|C |""".stripMargin // ideally A ) + + @Test def `multiple-args-lists` = + check( + """|def m(i: Int)(s: String) = ??? + |val x = m(@@) + |""".stripMargin, + """|Int + |""".stripMargin + ) + + @Test def `multiple-args-lists-2` = + check( + """|def m(i: Int)(s: String) = ??? + |val x = m(1)(@@) + |""".stripMargin, + """|String + |""".stripMargin + ) + + @Test def `extension-methods` = + check( + """|extension (i: Int) { + | def method(s: String): Unit = () + |} + | + |def testIt = + | 7.method(@@) + |""".stripMargin, + """|String + |""".stripMargin + ) + + @Test def `implicit-methods` = + check( + """|object I + |implicit class Xtension(i: I.type) { + | def method(s: String): Unit = () + |} + | + |def testIt = + | I.method(@@) + |""".stripMargin, + """|String + |""".stripMargin + ) + + @Test def using = + check( + """|def go(using Ordering[Int])(x: Int, y: Int): Int = + | Ordering[Int].compare(x, y) + | + |def test = + | go(???, @@) + |""".stripMargin, + """|Int + |""".stripMargin + ) + + @Test def `apply-dynamic` = + check( + """|object TypedHoleApplyDynamic { + | val obj: reflect.Selectable { + | def method(x: Int): Unit + | } = new reflect.Selectable { + | def method(x: Int): Unit = () + | } + | + | obj.method(@@) + |} + |""".stripMargin, + "Int" + ) + + @Test def `apply-dynamic-2` = + check( + """|object TypedHoleApplyDynamic { + | val obj: reflect.Selectable { + | def method[T](x: Int, y: T): Unit + | } = new reflect.Selectable { + | def method[T](x: Int, y: T): Unit = () + | } + | + | obj.method[String](1, @@) + |} + |""".stripMargin, + "String" + ) + + @Test def `apply-dynamic-3` = + check( + """|object TypedHoleApplyDynamic { + | val obj: reflect.Selectable { + | def method[T](a: Int)(x: Int, y: T): Unit + | } = new reflect.Selectable { + | def method[T](a: Int)(x: Int, y: T): Unit = () + | } + | + | obj.method[String](1)(1, @@) + |} + |""".stripMargin, + "String" + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/SelectionRangeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/SelectionRangeSuite.scala index 143d998a0ec1..18879301a8fb 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/SelectionRangeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/SelectionRangeSuite.scala @@ -75,6 +75,12 @@ class SelectionRangeSuite extends BaseSelectionRangeSuite: | b <- Some(2) | } yield a + b |}""".stripMargin, + """|object Main extends App { + | val total = for { + | >>region>>a <- Some(1)<>region>>for { | a <- Some(1) @@ -102,13 +108,17 @@ class SelectionRangeSuite extends BaseSelectionRangeSuite: ) ) - @Test def `function params` = + @Test def `function-params-1` = check( """|object Main extends App { | def func(a@@: Int, b: Int) = | a + b |}""".stripMargin, List[String]( + """|object Main extends App { + | def func(>>region>>a<>region>>a: Int< + | a + b + |}""".stripMargin, + List[String]( + """|object Main extends App { + | val func = (>>region>>a< + | a + b + |}""".stripMargin, + """|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<>region>>Type <: T1<>region>>Type <: T1, B<>region>>def foo[Type <: T1, B](hi: Int, b: Int, c:Int) = ???<>region>>56<>region>>34 + 56<>region>>(34 + 56)<>region>>12 * (34 + 56)< ???", + List( + "val hello = (aaa: Int, >>region>>bbb< ???", + "val hello = (aaa: Int, >>region>>bbb: Int< ???", + "val hello = (>>region>>aaa: Int, bbb: Int, ccc: Int< ???", + "val hello = >>region>>(aaa: Int, bbb: Int, ccc: Int) => ???<>region>>val hello = (aaa: Int, bbb: Int, ccc: Int) => ???<>region>>bbb<>region>>bbb: Int<>region>>aaa: Int, bbb: Int, ccc: Int<>region>>def hello(aaa: Int, bbb: Int, ccc: Int) = ???<>region>>222<>region>>111, 222, 333<>region>>List(111, 222, 333)<>region>>def hello = List(111, 222, 333)<>region>>Int<>region>>String, Int<>region>>Map[String, Int]<>region>>Map[String, Int]()<>region>>def hello = Map[String, Int]()<>region>>bbb<>region>>aaa, bbb, ccc<>region>>List(aaa, bbb, ccc)<>region>>val List(aaa, bbb, ccc) = List(111, 222, 333)<>region>>222<>region>>List(222)<>region>>def hello = List(222)<>region>>arg<>region>>val arg: Int<>region>>Foo Bar Baz<>region>>object `Foo Bar Baz` extends SomeTrait<: Bar](): Class[? <: X0] + |hashCode(): Int + |isInstanceOf[X0]: Boolean + |productArity: Int + |productElement(n: Int): Any + |productElementName(n: Int): String + |productElementNames: Iterator[String] + |productIterator: Iterator[Any] + |productPrefix: String + |synchronized[X0](x$0: X0): X0 + |toString(): String + |->[B](y: B): (Bar, B) + |ensuring(cond: Boolean): Bar + |ensuring(cond: Bar => Boolean): Bar + |ensuring(cond: Boolean, msg: => Any): Bar + |ensuring(cond: Bar => Boolean, msg: => Any): Bar + |nn: `?1`.type + |runtimeChecked: `?2`.type + |formatted(fmtstr: String): String + |→[B](y: B): (Bar, B) + | """.stripMargin + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionOverrideSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionOverrideSuite.scala index 94c444b0feb9..3720d170eb26 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionOverrideSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionOverrideSuite.scala @@ -284,7 +284,8 @@ class CompletionOverrideSuite extends BaseCompletionSuite: includeDetail = false ) - @Test def `mutable` = + // Disabled since the test is flaky @Test + def `mutable` = checkEdit( """|abstract class Mutable { | def foo: scala.collection.mutable.Set[Int] diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala index e9f58ee45df8..08e1b293a4ce 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -94,33 +94,24 @@ class CompletionSuite extends BaseCompletionSuite: |newBuilder[A]: Builder[A, List[A]] |apply[A](elems: A*): List[A] |concat[A](xss: Iterable[A]*): List[A] - |fill[A](n1: Int, n2: Int)(elem: => A): List[List[A] @uncheckedVariance] - |fill[A](n1: Int, n2: Int, n3: Int)(elem: => A): List[List[List[A]] @uncheckedVariance] - |fill[A](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => A): List[List[List[List[A]]] @uncheckedVariance] - |fill[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => A): List[List[List[List[List[A]]]] @uncheckedVariance] + |fill[A](n1: Int, n2: Int)(elem: => A): List[List[A]] + |fill[A](n1: Int, n2: Int, n3: Int)(elem: => A): List[List[List[A]]] + |fill[A](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => A): List[List[List[List[A]]]] + |fill[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => A): List[List[List[List[List[A]]]]] |fill[A](n: Int)(elem: => A): List[A] |iterate[A](start: A, len: Int)(f: A => A): List[A] |range[A: Integral](start: A, end: A): List[A] |range[A: Integral](start: A, end: A, step: A): List[A] - |tabulate[A](n1: Int, n2: Int)(f: (Int, Int) => A): List[List[A] @uncheckedVariance] - |tabulate[A](n1: Int, n2: Int, n3: Int)(f: (Int, Int, Int) => A): List[List[List[A]] @uncheckedVariance] - |tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int)(f: (Int, Int, Int, Int) => A): List[List[List[List[A]]] @uncheckedVariance] - |tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(f: (Int, Int, Int, Int, Int) => A): List[List[List[List[List[A]]]] @uncheckedVariance] + |tabulate[A](n1: Int, n2: Int)(f: (Int, Int) => A): List[List[A]] + |tabulate[A](n1: Int, n2: Int, n3: Int)(f: (Int, Int, Int) => A): List[List[List[A]]] + |tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int)(f: (Int, Int, Int, Int) => A): List[List[List[List[A]]]] + |tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(f: (Int, Int, Int, Int, Int) => A): List[List[List[List[List[A]]]]] |tabulate[A](n: Int)(f: Int => A): List[A] |unapplySeq[A](x: List[A] @uncheckedVariance): UnapplySeqWrapper[A] |unfold[A, S](init: S)(f: S => Option[(A, S)]): List[A] - |->[B](y: B): (List.type, B) - |ensuring(cond: Boolean): List.type - |ensuring(cond: List.type => Boolean): List.type - |ensuring(cond: Boolean, msg: => Any): List.type - |ensuring(cond: List.type => Boolean, msg: => Any): List.type |fromSpecific(from: Any)(it: IterableOnce[Nothing]): List[Nothing] |fromSpecific(it: IterableOnce[Nothing]): List[Nothing] - |nn: List.type & List.type - |runtimeChecked scala.collection.immutable |toFactory(from: Any): Factory[Nothing, List[Nothing]] - |formatted(fmtstr: String): String - |→[B](y: B): (List.type, B) |iterableFactory[A]: Factory[A, List[A]] |asInstanceOf[X0]: X0 |equals(x$0: Any): Boolean @@ -129,9 +120,15 @@ class CompletionSuite extends BaseCompletionSuite: |isInstanceOf[X0]: Boolean |synchronized[X0](x$0: X0): X0 |toString(): String - |wait(): Unit - |wait(x$0: Long): Unit - |wait(x$0: Long, x$1: Int): Unit + |->[B](y: B): (List.type, B) + |ensuring(cond: Boolean): List.type + |ensuring(cond: List.type => Boolean): List.type + |ensuring(cond: Boolean, msg: => Any): List.type + |ensuring(cond: List.type => Boolean, msg: => Any): List.type + |nn: List.type + |runtimeChecked scala.collection.immutable + |formatted(fmtstr: String): String + |→[B](y: B): (List.type, B) |""".stripMargin ) @@ -2041,6 +2038,43 @@ class CompletionSuite extends BaseCompletionSuite: filter = _.contains("name") ) + @Test def `namedTuple-completions-2` = + check( + """|import scala.NamedTuple.* + | + |def hello = (path = ".", num = 5)++ (line = 1) + |val hello2 = (path = ".", num = 5)++ (line = 1) + | + |@main def bla = + | hello@@ + |""".stripMargin, + """|hello2: (path : String, num : Int, line : Int) + |hello: (path : String, num : Int, line : Int) + """.stripMargin, + ) + + @Test def `namedTuple completions-3` = + check( + """|import scala.NamedTuple.* + | + |val person = (name = "Jakub", city = "Wrocław") + | + |val n = person.@@name""".stripMargin, + "name: String", + filter = _ == "name: String" + ) + + @Test def `namedTuple completions-4` = + check( + """|import scala.NamedTuple.* + | + |val person = (name = "Jakub", city = "Wrocław") + | + |val n = person.n@@ame""".stripMargin, + "name: String", + filter = _ == "name: String" + ) + @Test def `Selectable with namedTuple Fields member` = check( """|import scala.NamedTuple.* @@ -2235,3 +2269,49 @@ class CompletionSuite extends BaseCompletionSuite: |""".stripMargin, "" ) + + @Test def `no-completions-on-package-selection` = + check( + """package one.@@ + |""".stripMargin, + "" + ) + + @Test def `no-extension-completion-on-packages` = + check( + """object M: + | scala.runt@@ + |""".stripMargin, + """runtime scala + |PartialFunction scala""".stripMargin // those are the actual members of scala + ) + + @Test def `no-extension-completions-on-package-objects` = + check( + """package object magic { def test: Int = ??? } + |object M: + | magic.@@ + |""".stripMargin, + "test: Int" + ) + + @Test def `macros` = + check( + """ + |object Macro: + | import scala.quoted.* + | def dbgImpl[A](a: Expr[A])(using Quotes): Expr[A] = + | import quotes.reflect.* + | a.asTer@@ + | + |""".stripMargin, + "asTerm: Term" + ) + + @Test def `derives-no-square-brackets` = + check( + """ + |case class Miau(y: Int) derives Ordering, CanEqu@@ + |""".stripMargin, + "CanEqual scala" + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionTypeSuite.scala index 46760b5d9465..b74041450cdf 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionTypeSuite.scala @@ -21,3 +21,33 @@ class CompletionTypeSuite extends BaseCompletionSuite: s"""|method[T <: FS](a: T): Nothing |""".stripMargin ) + + @Test def `long-types-match` = + check( + s"""|@main + |def run = + | (1, 2).to@@ + |""".stripMargin, + s"""|toString(): String + |toArray: Array[Object] + |toIArray: IArray[Object] + |toList: List[Int] + |productIterator: Iterator[Any] + |asInstanceOf[X0]: X0 + |isInstanceOf[X0]: Boolean + |""".stripMargin + ) + + @Test def `union-types` = + check( + s"""| + |def itsAUnionType(): Int | String = ??? + | + |def hello = + | its@@ + | + | + |""".stripMargin, + s"""|itsAUnionType(): Int | String + |""".stripMargin + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/SingletonCompletionsSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/SingletonCompletionsSuite.scala index 25d1418900fd..17e4ad2ad9f3 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/SingletonCompletionsSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/SingletonCompletionsSuite.scala @@ -297,4 +297,30 @@ class SingletonCompletionsSuite extends BaseCompletionSuite { """|"foo": "foo" |""".stripMargin ) + + @Test def `type-apply` = + check( + """|class Consumer[A]: + | def eat(a: A) = () + | + |def test = + | Consumer[7].eat(@@) + |""".stripMargin, + "7: 7", + topLines = Some(1) + ) + + @Test def `type-apply-2` = + check( + """|class Consumer[A]: + | def eat(a: A) = () + | + |object Consumer7 extends Consumer[7] + | + |def test = + | Consumer7.eat(@@) + |""".stripMargin, + "7: 7", + topLines = Some(1) + ) } 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 32aaf695bcaa..108e74738f71 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala @@ -28,7 +28,8 @@ class PcDefinitionSuite extends BasePcDefinitionSuite: MockLocation("scala/Predef.Ensuring#ensuring(+2).", "Predef.scala"), MockLocation("scala/Predef.Ensuring#ensuring(+3).", "Predef.scala"), MockLocation("scala/collection/immutable/List#`::`().", "List.scala"), - MockLocation("scala/package.List.", "package.scala") + MockLocation("scala/package.List.", "package.scala"), + MockLocation("scala/collection/immutable/Vector.", "Vector.scala"), ) override def definitions(offsetParams: OffsetParams): List[Location] = @@ -611,3 +612,69 @@ class PcDefinitionSuite extends BasePcDefinitionSuite: |""".stripMargin ) + @Test def i7256 = + check( + """|object Test: + | def <>: Unit = ??? + |export Test.me@@thodA + |""".stripMargin + ) + + @Test def `i7256-2` = + check( + """|object Test: + | def <>: Unit = ??? + | def methodB: Unit = ??? + |export Test.{me@@thodA, methodB} + |""".stripMargin + ) + + @Test def `i7256-3` = + check( + """|object Test: + | def <>: Unit = ??? + | def methodB: Unit = ??? + |export Test.{methodA, methodB} + | + |val i = met@@hodA + |""".stripMargin + ) + + @Test def i7427 = + check( + """|package a + |object Repro: + | export scala.collection.immutable.V/*scala/collection/immutable/Vector. Vector.scala*/@@ector + |""".stripMargin + ) + + @Test def i7763 = + check( + """|case class MyItem(<>: String) + | + |def handle(item: MyItem) = + | item match { + | case MyItem(na@@me = n2) => println(n2) + | } + |""".stripMargin + ) + + @Test def `i7763-neg` = + check( + """|object MyItem: + | def unapply(name: String): Option[Int] = ??? + | + |def handle(item: String) = + | item match { + | case MyItem(na@@me = n2) => println(n2) + | } + |""".stripMargin + ) + + @Test def `i7763-apply` = + check( + """|case class MyItem(<>: String) + | + |def handle(item: String) = MyItem(na@@me = item) + |""".stripMargin + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImplementAbstractMembersSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImplementAbstractMembersSuite.scala index 2df69cc85af2..a566ca35d3bc 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImplementAbstractMembersSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImplementAbstractMembersSuite.scala @@ -75,39 +75,59 @@ class AutoImplementAbstractMembersSuite extends BaseCodeActionSuite: |""".stripMargin ) - @Test def `empty-lines-between-members` = + @Test def `no-new-line` = checkEdit( """|package a - | - |object A { - | trait Base { - | def foo(x: Int): Int - | def bar(x: String): String - | } - | class <> extends Base { - | - | def bar(x: String): String = ??? - | - | } - |} - |""".stripMargin, + | + |trait X: + | def foo: Unit + | + |class <> extends X""".stripMargin, """|package a | - |object A { - | trait Base { - | def foo(x: Int): Int - | def bar(x: String): String - | } - | class Concrete extends Base { - | + |trait X: + | def foo: Unit | - | override def foo(x: Int): Int = ??? + |class Y extends X { | - | def bar(x: String): String = ??? + | override def foo: Unit = ??? | - | } - |} - |""".stripMargin + |}""".stripMargin, + ) + + @Test def `empty-lines-between-members` = + checkEdit( + """|package a + | + |object A { + | trait Base { + | def foo(x: Int): Int + | def bar(x: String): String + | } + | class <> extends Base { + | + | def bar(x: String): String = ??? + | + | } + |} + |""".stripMargin, + """|package a + | + |object A { + | trait Base { + | def foo(x: Int): Int + | def bar(x: String): String + | } + | class Concrete extends Base { + | + | + | override def foo(x: Int): Int = ??? + | + | def bar(x: String): String = ??? + | + | } + |} + |""".stripMargin ) @Test def `objectdef` = diff --git a/presentation-compiler/test/dotty/tools/pc/tests/edit/ConvertToNamedLambdaParametersSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/edit/ConvertToNamedLambdaParametersSuite.scala new file mode 100644 index 000000000000..c1ab229fc216 --- /dev/null +++ b/presentation-compiler/test/dotty/tools/pc/tests/edit/ConvertToNamedLambdaParametersSuite.scala @@ -0,0 +1,187 @@ +package dotty.tools.pc.tests.edit + +import java.net.URI +import java.util.Optional + +import scala.meta.internal.jdk.CollectionConverters.* +import scala.meta.internal.metals.CompilerOffsetParams +import scala.meta.pc.CodeActionId +import scala.meta.pc.DisplayableException +import scala.language.unsafeNulls + +import dotty.tools.pc.base.BaseCodeActionSuite +import dotty.tools.pc.utils.TextEdits +import dotty.tools.pc.PcConvertToNamedLambdaParameters + +import org.eclipse.lsp4j as l +import org.junit.{Test, Ignore} + +class ConvertToNamedLambdaParametersSuite extends BaseCodeActionSuite: + + @Test def `Int => Int function in map` = + checkEdit( + """|object A{ + | val a = List(1, 2).map(<<_>> + 1) + |}""".stripMargin, + """|object A{ + | val a = List(1, 2).map(i => i + 1) + |}""".stripMargin + ) + + @Test def `Int => Int function in map with another wildcard lambda` = + checkEdit( + """|object A{ + | val a = List(1, 2).map(<<_>> + 1).map(_ + 1) + |}""".stripMargin, + """|object A{ + | val a = List(1, 2).map(i => i + 1).map(_ + 1) + |}""".stripMargin + ) + + @Test def `String => String function in map` = + checkEdit( + """|object A{ + | val a = List("a", "b").map(<<_>> + "c") + |}""".stripMargin, + """|object A{ + | val a = List("a", "b").map(s => s + "c") + |}""".stripMargin + ) + + @Test def `Person => Person function to custom method` = + checkEdit( + """|object A{ + | case class Person(name: String, age: Int) + | val bob = Person("Bob", 30) + | def m[A](f: Person => A): A = f(bob) + | m(_<<.>>name) + |} + |""".stripMargin, + """|object A{ + | case class Person(name: String, age: Int) + | val bob = Person("Bob", 30) + | def m[A](f: Person => A): A = f(bob) + | m(p => p.name) + |} + |""".stripMargin + ) + + @Test def `(String, Int) => Int function in map with multiple underscores` = + checkEdit( + """|object A{ + | val a = List(("a", 1), ("b", 2)).map(<<_>> + _) + |}""".stripMargin, + """|object A{ + | val a = List(("a", 1), ("b", 2)).map((s, i) => s + i) + |}""".stripMargin + ) + + @Test def `Int => Int function in map with multiple underscores` = + checkEdit( + """|object A{ + | val a = List(1, 2).map(x => x -> (x + 1)).map(<<_>> + _) + |}""".stripMargin, + """|object A{ + | val a = List(1, 2).map(x => x -> (x + 1)).map((i, i1) => i + i1) + |}""".stripMargin + ) + + @Test def `Int => Float function in nested lambda 1` = + checkEdit( + """|object A{ + | val a = List(1, 2).flatMap(List(_).flatMap(v => List(v, v + 1).map(<<_>>.toFloat))) + |}""".stripMargin, + """|object A{ + | val a = List(1, 2).flatMap(List(_).flatMap(v => List(v, v + 1).map(i => i.toFloat))) + |}""".stripMargin + ) + + @Test def `Int => Float function in nested lambda 2` = + checkEdit( + """|object A{ + | val a = List(1, 2).flatMap(List(<<_>>).flatMap(v => List(v, v + 1).map(_.toFloat))) + |}""".stripMargin, + """|object A{ + | val a = List(1, 2).flatMap(i => List(i).flatMap(v => List(v, v + 1).map(_.toFloat))) + |}""".stripMargin + ) + + @Test def `Int => Float function in nested lambda with shadowing` = + checkEdit( + """|object A{ + | val a = List(1, 2).flatMap(List(<<_>>).flatMap(i => List(i, i + 1).map(_.toFloat))) + |}""".stripMargin, + """|object A{ + | val a = List(1, 2).flatMap(i1 => List(i1).flatMap(i => List(i, i + 1).map(_.toFloat))) + |}""".stripMargin + ) + + @Test def `(String, String, String, String, String, String, String) => String function in map` = + checkEdit( + """|object A{ + | val a = List( + | ("a", "b", "c", "d", "e", "f", "g"), + | ("h", "i", "j", "k", "l", "m", "n") + | ).map(_<< >>+ _ + _ + _ + _ + _ + _) + |}""".stripMargin, + """|object A{ + | val a = List( + | ("a", "b", "c", "d", "e", "f", "g"), + | ("h", "i", "j", "k", "l", "m", "n") + | ).map((s, s1, s2, s3, s4, s5, s6) => s + s1 + s2 + s3 + s4 + s5 + s6) + |}""".stripMargin + ) + + @Test def `Long => Long with match and wildcard pattern` = + checkEdit( + """|object A{ + | val a = List(1L, 2L).map(_ match { + | case 1L => 1L + | case _ => <<2L>> + | }) + |}""".stripMargin, + """|object A{ + | val a = List(1L, 2L).map(l => l match { + | case 1L => 1L + | case _ => 2L + | }) + |}""".stripMargin + ) + + @Ignore + @Test def `Int => Int eta-expansion in map` = + checkEdit( + """|object A{ + | def f(x: Int): Int = x + 1 + | val a = List(1, 2).map(<>) + |}""".stripMargin, + """|object A{ + | def f(x: Int): Int = x + 1 + | val a = List(1, 2).map(i => f(i)) + |}""".stripMargin + ) + + def checkEdit( + original: String, + expected: String, + compat: Map[String, String] = Map.empty + ): Unit = + val edits = convertToNamedLambdaParameters(original) + val (code, _, _) = params(original) + val obtained = TextEdits.applyEdits(code, edits) + assertNoDiff(expected, obtained) + + def convertToNamedLambdaParameters( + original: String, + filename: String = "file:/A.scala" + ): List[l.TextEdit] = { + val (code, _, offset) = params(original) + val result = presentationCompiler + .codeAction( + CompilerOffsetParams(URI.create(filename), code, offset, cancelToken), + PcConvertToNamedLambdaParameters.codeActionId, + Optional.empty() + ) + .get() + result.asScala.toList + } diff --git a/presentation-compiler/test/dotty/tools/pc/tests/edit/InsertInferredMethodSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/edit/InsertInferredMethodSuite.scala new file mode 100644 index 000000000000..2b8e2ef32ef5 --- /dev/null +++ b/presentation-compiler/test/dotty/tools/pc/tests/edit/InsertInferredMethodSuite.scala @@ -0,0 +1,519 @@ +package dotty.tools.pc.tests.edit + +import java.net.URI +import java.util.Optional + +import scala.meta.internal.jdk.CollectionConverters.* +import scala.meta.internal.metals.CompilerOffsetParams +import scala.meta.pc.CodeActionId +import scala.language.unsafeNulls + +import dotty.tools.pc.base.BaseCodeActionSuite +import dotty.tools.pc.utils.TextEdits + +import org.eclipse.lsp4j as l +import org.junit.Test + +class InsertInferredMethodSuite extends BaseCodeActionSuite: + + @Test def `simple` = + checkEdit( + """| + |trait Main { + | def method1(s : String) = 123 + | + | method1(<>(1)) + |} + | + |""".stripMargin, + """|trait Main { + | def method1(s : String) = 123 + | + | def otherMethod(arg0: Int): String = ??? + | method1(otherMethod(1)) + |} + |""".stripMargin + ) + + @Test def `simple-2` = + checkEdit( + """| + |trait Main { + | def method1(s : String) = 123 + | + | <>(1) + |} + | + |""".stripMargin, + """|trait Main { + | def method1(s : String) = 123 + | + | def otherMethod(arg0: Int) = ??? + | otherMethod(1) + |} + |""".stripMargin + ) + + @Test def `simple-3` = + checkEdit( + """| + |trait Main { + | def method1(s : String) = 123 + | + | <>((1 + 123).toDouble) + |} + | + |""".stripMargin, + """|trait Main { + | def method1(s : String) = 123 + | + | def otherMethod(arg0: Double) = ??? + | otherMethod((1 + 123).toDouble) + |} + |""".stripMargin + ) + + @Test def `simple-4` = + checkEdit( + """| + |trait Main { + | def method1(s : String) = 123 + | + | method1(<>()) + |} + | + |""".stripMargin, + """|trait Main { + | def method1(s : String) = 123 + | + | def otherMethod(): String = ??? + | method1(otherMethod()) + |} + |""".stripMargin + ) + + @Test def `backtick-method-name` = + checkEdit( + """| + |trait Main { + | <<`met ? hod`>>(10) + |} + |""".stripMargin, + """|trait Main { + | def `met ? hod`(arg0: Int) = ??? + | `met ? hod`(10) + |} + |""".stripMargin + ) + + @Test def `custom-type` = + checkEdit( + """| + |trait Main { + | def method1(b: Double, s : String) = 123 + | + | case class User(i : Int) + | val user = User(1) + | + | method1(0.0, <>(user, 1)) + |} + |""".stripMargin, + """| + |trait Main { + | def method1(b: Double, s : String) = 123 + | + | case class User(i : Int) + | val user = User(1) + | + | def otherMethod(arg0: User, arg1: Int): String = ??? + | method1(0.0, otherMethod(user, 1)) + |} + |""".stripMargin + ) + + @Test def `custom-type-2` = + checkEdit( + """| + |trait Main { + | def method1(b: Double, s : String) = 123 + | + | case class User(i : Int) + | val user = User(1) + | <>(user, 1) + |} + |""".stripMargin, + """| + |trait Main { + | def method1(b: Double, s : String) = 123 + | + | case class User(i : Int) + | val user = User(1) + | def otherMethod(arg0: User, arg1: Int) = ??? + | otherMethod(user, 1) + |} + |""".stripMargin + ) + + @Test def `custom-type-advanced` = + checkEdit( + """| + |trait Main { + | def method1(b: Double, s : String) = 123 + | + | case class User(i : Int) + | + | <>(User(1), 1) + |} + | + |""".stripMargin, + """|trait Main { + | def method1(b: Double, s : String) = 123 + | + | case class User(i : Int) + | + | def otherMethod(arg0: User, arg1: Int) = ??? + | otherMethod(User(1), 1) + |} + |""".stripMargin + ) + + @Test def `custom-type-advanced-2` = + checkEdit( + """| + |trait Main { + | def method1(b: Double, s : String) = 123 + | + | case class User(i : Int) + | + | <>(List(Set(User(1))), Map("1" -> 1)) + |} + | + |""".stripMargin, + """|trait Main { + | def method1(b: Double, s : String) = 123 + | + | case class User(i : Int) + | + | def otherMethod(arg0: List[Set[User]], arg1: Map[String, Int]) = ??? + | otherMethod(List(Set(User(1))), Map("1" -> 1)) + |} + |""".stripMargin + ) + + @Test def `with-imports` = + checkEdit( + """|import java.nio.file.Files + | + |trait Main { + | def main() = { + | def method1(s : String) = 123 + | val path = Files.createTempDirectory("") + | method1(<>(path)) + | } + |} + | + |""".stripMargin, + """|import java.nio.file.Files + |import java.nio.file.Path + | + |trait Main { + | def main() = { + | def method1(s : String) = 123 + | val path = Files.createTempDirectory("") + | def otherMethod(arg0: Path): String = ??? + | method1(otherMethod(path)) + | } + |} + |""".stripMargin + ) + + @Test def `val-definition` = + checkEdit( + """| + |trait Main { + | val result: String = <>(42, "hello") + |} + | + |""".stripMargin, + """|trait Main { + | def nonExistent(arg0: Int, arg1: String): String = ??? + | val result: String = nonExistent(42, "hello") + |} + |""".stripMargin + ) + + @Test def `val-definition-no-args` = + checkEdit( + """| + |trait Main { + | val result: Int = <> + |} + | + |""".stripMargin, + """|trait Main { + | def getValue: Int = ??? + | val result: Int = getValue + |} + |""".stripMargin + ) + + @Test def `lambda-expression` = + checkEdit( + """| + |trait Main { + | val list = List(1, 2, 3) + | list.map(<>) + |} + | + |""".stripMargin, + """|trait Main { + | val list = List(1, 2, 3) + | def transform(arg0: Int) = ??? + | list.map(transform) + |} + |""".stripMargin + ) + + @Test def `lambda-expression-2` = + checkEdit( + """| + |trait Main { + | val list = List(1, 2, 3) + | list.map(<>(10, "test")) + |} + | + |""".stripMargin, + """|trait Main { + | val list = List(1, 2, 3) + | def transform(arg0: Int, arg1: String)(arg2: Int) = ??? + | list.map(transform(10, "test")) + |} + |""".stripMargin + ) + + @Test def `lambda-expression-3` = + checkEdit( + """| + |trait Main { + | val list = List("a", "b", "c") + | list.map(<>) + |} + | + |""".stripMargin, + """|trait Main { + | val list = List("a", "b", "c") + | def process(arg0: String) = ??? + | list.map(process) + |} + |""".stripMargin + ) + + @Test def `lambda-expression-4` = + checkEdit( + """| + |trait Main { + | List((1, 2, 3)).filter(_ => true).map(<>) + |} + | + |""".stripMargin, + """|trait Main { + | def otherMethod(arg0: (Int, Int, Int)) = ??? + | List((1, 2, 3)).filter(_ => true).map(otherMethod) + |} + |""".stripMargin + ) + + @Test def `lambda-expression-5` = + checkEdit( + """| + |trait Main { + | val list = List(1, 2, 3) + | list.filter(<>) + |} + | + |""".stripMargin, + """|trait Main { + | val list = List(1, 2, 3) + | def otherMethod(arg0: Int): Boolean = ??? + | list.filter(otherMethod) + |} + |""".stripMargin + ) + + @Test def `simple-method-no-args` = + checkEdit( + """| + |trait Main { + | <> + |} + | + |""".stripMargin, + """|trait Main { + | def missingMethod = ??? + | missingMethod + |} + |""".stripMargin + ) + + @Test def `simple-method-no-args-2` = + checkEdit( + """| + |trait Main { + | def method1(s : String) = 123 + | method1(<>) + |} + | + |""".stripMargin, + """|trait Main { + | def method1(s : String) = 123 + | def missingMethod: String = ??? + | method1(missingMethod) + |} + |""".stripMargin + ) + + @Test def `nested-val-definition` = + checkEdit( + """| + |trait Main { + | def someMethod(): Unit = { + | val data: List[String] = <>(10) + | } + |} + | + |""".stripMargin, + """|trait Main { + | def someMethod(): Unit = { + | def generateData(arg0: Int): List[String] = ??? + | val data: List[String] = generateData(10) + | } + |} + |""".stripMargin + ) + + @Test def `simple-class-definition` = + checkEdit( + """| + |class User: + | val name: String = "John" + | + |object Main: + | val user = User() + | user.<> + | + |""".stripMargin, + """| + |class User: + | val name: String = "John" + | def otherMethod = ??? + | + |object Main: + | val user = User() + | user.otherMethod + |""".stripMargin, + ) + + @Test def `simple-class-definition-2` = + checkEdit( + """| + |class User: + | val name: String = "John" + | + |object Main: + | val user = User() + | user.<>(10) + | + |""".stripMargin, + """| + |class User: + | val name: String = "John" + | def otherMethod(arg0: Int) = ??? + | + |object Main: + | val user = User() + | user.otherMethod(10) + |""".stripMargin, + ) + + @Test def `simple-object-definition` = + checkEdit( + """| + |object User: + | val name: String = "John" + | + |object Main: + | User.<> + | + |""".stripMargin, + """| + |object User: + | val name: String = "John" + | def otherMethod = ??? + | + |object Main: + | User.otherMethod + |""".stripMargin, + ) + + @Test def `simple-object-definition-2` = + checkEdit( + """| + |object User: + | val name: String = "John" + | + |object Main: + | User.<>(10) + | + |""".stripMargin, + """| + |object User: + | val name: String = "John" + | def otherMethod(arg0: Int) = ??? + | + |object Main: + | User.otherMethod(10) + |""".stripMargin, + ) + + @Test def `class-definition-without-body` = + checkEdit( + """| + |class User + | + |object Main: + | val user = User() + | user.<> + | + |""".stripMargin, + """| + |class User: + | def otherMethod = ??? + | + |object Main: + | val user = User() + | user.otherMethod + |""".stripMargin, + ) + + def checkEdit( + original: String, + expected: String + ): Unit = + val edits = getAutoImplement(original) + val (code, _, _) = params(original) + val obtained = TextEdits.applyEdits(code, edits) + assertNoDiff(expected, obtained) + + def getAutoImplement( + original: String, + filename: String = "file:/A.scala" + ): List[l.TextEdit] = + val (code, _, offset) = params(original) + val result = presentationCompiler + .codeAction( + CompilerOffsetParams(URI.create(filename), code, offset, cancelToken), + CodeActionId.InsertInferredMethod, + Optional.empty() + ) + .get() + result.asScala.toList 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 c1a84d6abb79..e9060738db54 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/edit/InsertInferredTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/edit/InsertInferredTypeSuite.scala @@ -657,14 +657,13 @@ class InsertInferredTypeSuite extends BaseCodeActionSuite: |object O{ | val <> = A.Foo(new A.`x-x`) |}""".stripMargin, - """|import A.Foo - |import A.`x-x` + """|import A.`x-x` |object A{ | class `x-x` | case class Foo[A](i: A) |} |object O{ - | val foo: Foo[`x-x`] = A.Foo(new A.`x-x`) + | val foo: A.Foo[`x-x`] = A.Foo(new A.`x-x`) |} |""".stripMargin ) @@ -970,6 +969,156 @@ class InsertInferredTypeSuite extends BaseCodeActionSuite: |""".stripMargin ) + @Test def `named-tuples` = + checkEdit( + """|def hello = (path = ".", num = 5) + | + |def <> = + | hello ++ (line = 1) + | + |@main def bla = + | val x: (path: String, num: Int, line: Int) = test + |""".stripMargin, + """|def hello = (path = ".", num = 5) + | + |def test: (path : String, num : Int, line : Int) = + | hello ++ (line = 1) + | + |@main def bla = + | val x: (path: String, num: Int, line: Int) = test + |""".stripMargin + ) + + @Test def `enums` = + checkEdit( + """|object EnumerationValue: + | object Day extends Enumeration { + | type Day = Value + | val Weekday, Weekend = Value + | } + | object Bool extends Enumeration { + | type Bool = Value + | val True, False = Value + | } + | import Bool._ + | def day(d: Day.Value): Unit = ??? + | val <> = + | if (true) Day.Weekday + | else Day.Weekend + |""".stripMargin, + """|object EnumerationValue: + | object Day extends Enumeration { + | type Day = Value + | val Weekday, Weekend = Value + | } + | object Bool extends Enumeration { + | type Bool = Value + | val True, False = Value + | } + | import Bool._ + | def day(d: Day.Value): Unit = ??? + | val d: EnumerationValue.Day.Value = + | if (true) Day.Weekday + | else Day.Weekend + |""".stripMargin + ) + + @Test def `enums2` = + checkEdit( + """|object EnumerationValue: + | object Day extends Enumeration { + | type Day = Value + | val Weekday, Weekend = Value + | } + | object Bool extends Enumeration { + | type Bool = Value + | val True, False = Value + | } + | import Bool._ + | val <> = + | if (true) True + | else False + |""".stripMargin, + """|object EnumerationValue: + | object Day extends Enumeration { + | type Day = Value + | val Weekday, Weekend = Value + | } + | object Bool extends Enumeration { + | type Bool = Value + | val True, False = Value + | } + | import Bool._ + | val b: Value = + | if (true) True + | else False + |""".stripMargin + ) + + @Test def `Adjust type for val` = + checkEdit( + """|object A{ + | val <>:String = 123 + |}""".stripMargin, + + """|object A{ + | val alpha: Int = 123 + |}""".stripMargin, + ) + + @Test def `Adjust type for val2` = + checkEdit( + """|object A{ + | val <>:Int = 123 + |}""".stripMargin, + """|object A{ + | val alpha: Int = 123 + |}""".stripMargin, + ) + + @Test def `Adjust type for val3` = + checkEdit( + """|object A{ + | val <>: Int = 123 + |}""".stripMargin, + """|object A{ + | val alpha: Int = 123 + |}""".stripMargin, + ) + + @Test def `Adjust type for def` = + checkEdit( + """|object A{ + | def <>:String = 123 + |}""".stripMargin, + + """|object A{ + | def alpha: Int = 123 + |}""".stripMargin, + ) + + @Test def `Adjust type for def2` = + checkEdit( + """|object A{ + | def <>:Int = 123 + |}""".stripMargin, + """|object A{ + | def alpha: Int = 123 + |}""".stripMargin, + ) + + + @Test def `Adjust type for def3` = + checkEdit( + """|object A{ + | def <>: Int = 123 + |}""".stripMargin, + """|object A{ + | def alpha: Int = 123 + |}""".stripMargin, + ) + + def checkEdit( original: String, expected: String 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 4809bc5cd5b8..5c29b18fee28 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala @@ -1486,5 +1486,45 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite: |""".stripMargin ) + @Test def i7256 = + check( + """|package a + |object Test: + | def <>: Unit = ??? + |export Test.<> + |val i = <> + |""".stripMargin + ) + + @Test def `i7256-2` = + check( + """|object Test: + | def <>: Unit = ??? + | def methodB: Unit = ??? + |export Test.{<>, methodB} + |val i = <> + |""".stripMargin + ) + + + @Test def `i7256-3` = + check( + """|object Test: + | def <>: Unit = ??? + | def methodB: Unit = ??? + |export Test.<> + |val i = <> + |val j = <> + |""".stripMargin + ) + + @Test def `i7256-4` = + check( + """|object Test: + | class <> + | object <> + |export Test.<> + |""".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 34aca39dcf50..bdc3b4543383 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverDefnSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverDefnSuite.scala @@ -262,3 +262,68 @@ class HoverDefnSuite extends BaseHoverSuite: """|type Numeric: Numeric |""".stripMargin.hover ) + + @Test def i7256 = + check( + """|object Test: + | def methodA: Unit = ??? + |export Test.me@@thodA + |""".stripMargin, + """|**Expression type**: + |```scala + |=> Unit + |``` + |**Symbol signature**: + |```scala + |def methodA: Unit + |``` + |""".stripMargin + ) + + @Test def `annotation` = + check( + """| + |@ma@@in + |def example() = + | println("test") + |""".stripMargin, + """|```scala + |def this(): main + |```""".stripMargin.hover + ) + + @Test def `annotation-2` = + check( + """| + |@ma@@in + |def example() = + | List("test") + |""".stripMargin, + """|```scala + |def this(): main + |```""".stripMargin.hover + ) + + @Test def `annotation-3` = + check( + """| + |@ma@@in + |def example() = + | Array("test") + |""".stripMargin, + """|```scala + |def this(): main + |```""".stripMargin.hover + ) + + @Test def `annotation-4` = + check( + """| + |@ma@@in + |def example() = + | Array(1, 2) + |""".stripMargin, + """|```scala + |def this(): main + |```""".stripMargin.hover + ) 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 2fcb4e068ee0..16d92e0e2282 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala @@ -6,6 +6,18 @@ import org.junit.Test class HoverTermSuite extends BaseHoverSuite: + @Test def `n-ary lamba` = + check( + """|object testRepor { + | val listOfTuples = List(1 -> 1, 2 -> 2, 3 -> 3) + | + | listOfTuples.map((k@@ey, value) => key + value) + |} + |""".stripMargin, + """|val key: Int + |""".stripMargin.hover + ) + @Test def `map` = check( """object a { @@ -745,6 +757,47 @@ class HoverTermSuite extends BaseHoverSuite: "name: String".hover ) + + @Test def `named-tuples3`: Unit = + check( + """|def hello = (path = ".", num = 5) + | + |def test = + | hello ++ (line = 1) + | + |@main def bla = + | val x: (path: String, num: Int, line: Int) = t@@est + |""".stripMargin, + "def test: (path : String, num : Int, line : Int)".hover + ) + + + @Test def `named-tuples4`: Unit = + check( + """|def hello = (path = ".", num = 5) + | + |def test = + | hel@@lo ++ (line = 1) + | + |@main def bla = + | val x: (path: String, num: Int, line: Int) = test + |""".stripMargin, + "def hello: (path : String, num : Int)".hover + ) + + @Test def `named-tuples5`: Unit = + check( + """|def hello = (path = ".", num = 5) + | + |def test(x: (path: String, num: Int)) = + | x ++ (line = 1) + | + |@main def bla = + | val x: (path: String, num: Int, line: Int) = t@@est(hello) + |""".stripMargin, + "def test(x: (path : String, num : Int)): (path : String, num : Int, line : Int)".hover + ) + @Test def `value-of`: Unit = check( """|enum Foo(val key: String) { @@ -758,3 +811,142 @@ class HoverTermSuite extends BaseHoverSuite: |""".stripMargin, "def valueOf($name: String): Foo".hover ) + + @Test def `i7460` = + check( + """|package tests.macros + |def m = Macros7460.foo.sub@@string(2, 4) + |""".stripMargin, + "def substring(x$0: Int, x$1: Int): String".hover + ) + + @Test def `i7460-2` = + check( + """|package tests.macros + |def m = Macros7460.bar.sub@@string(2, 4) + |""".stripMargin, + "def substring(x$0: Int, x$1: Int): String".hover + ) + + @Test def `multiple-valdefs-1` = + check( + """|object O { + | val x@@x, yy, zz = 1 + |} + |""".stripMargin, + "val xx: Int".hover + ) + + @Test def `multiple-valdefs-2` = + check( + """|object O { + | val xx, y@@y, zz = 1 + |} + |""".stripMargin, + "val yy: Int".hover + ) + + @Test def `multiple-valdefs-3` = + check( + """|object O { + | val xx, yy, z@@z = 1 + |} + |""".stripMargin, + "val zz: Int".hover + ) + + @Test def `multiple-valdefs-4` = + check( + """|object O { + | val xx, thisIsAVeryLongNa@@me, zz = 1 + |} + |""".stripMargin, + "val thisIsAVeryLongName: Int".hover + ) + + @Test def `intersection_of_selectable-1` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type A = Record { val aa: Int } + |type B = Record { val bb: String } + |type AB = A & B + | + |val ab: AB = Record().asInstanceOf[AB] + |val ab_a = ab.a@@a + |""".stripMargin, + "val aa: Int".hover + ) + + @Test def `intersection_of_selectable-2` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type A = Record { val aa: Int } + |type B = Record { val aa: String } + |type AB = A & B + | + |val ab: AB = Record().asInstanceOf[AB] + |val ab_a = ab.a@@a + |""".stripMargin, + "val aa: Int & String".hover + ) + + @Test def `intersection_of_selectable-3` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type A = Record { val aa: Int } + |type B = Record { val bb: String } + |type AB = A & B + | + |val ab: AB = Record().asInstanceOf[AB] + |val ab_a = ab.b@@b + |""".stripMargin, + "val bb: String".hover + ) + + @Test def `intersection_of_selectable-4` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type A = Record { val aa: Int } + |type B = Record { val bb: String } + |type C = Record { val cc: Float } + |type AB = A & B + |type ABC = AB & C + | + |val abc: ABC = Record().asInstanceOf[ABC] + |val abc_a = abc.a@@a + |""".stripMargin, + "val aa: Int".hover + ) + + @Test def `intersection_of_selectable-5` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type AL = List[Int] & Record { val aa: Int } + | + |val al: AL = ???.asInstanceOf[ABC] + |val al_a = al.a@@a + |""".stripMargin, + "val aa: Int".hover + ) + + @Test def i7763 = + check( + """|case class MyItem(name: String) + | + |def handle(item: MyItem) = + | item match { + | case MyItem(na@@me = n2) => println(n2) + | } + |""".stripMargin, + "val name: String".hover + ) 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 bf917f05669b..daad8b974620 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala @@ -19,7 +19,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { | def foo()/*: Unit<>*/ = { | implicit val imp: Int = 2 | def addOne(x: Int)(implicit one: Int)/*: Int<>*/ = x + one - | val x/*: Int<>*/ = addOne(1)/*(using imp<<(3:17)>>)*/ + | val x/*: Int<>*/ = addOne(/*x = */1)/*(using imp<<(3:17)>>)*/ | } |} |""".stripMargin @@ -34,7 +34,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |""".stripMargin, """|object Main { | def hello[T](t: T)/*: T<<(2:12)>>*/ = t - | val x/*: List<>[Int<>]*/ = hello/*[List<>[Int<>]]*/(List/*[Int<>]*/(1)) + | val x/*: List<>[Int<>]*/ = hello/*[List<>[Int<>]]*/(/*t = */List/*[Int<>]*/(/*elems = */1)) |} |""".stripMargin ) @@ -48,7 +48,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |""".stripMargin, """|object Main { | def hello[T](t: T)/*: T<<(2:12)>>*/ = t - | val x/*: Map<>[Int<>, String<>]*/ = hello/*[Map<>[Int<>, String<>]]*/(Map/*[Int<>, String<>]*/((1,"abc"))) + | val x/*: Map<>[Int<>, String<>]*/ = hello/*[Map<>[Int<>, String<>]]*/(/*t = */Map/*[Int<>, String<>]*/(/*elems = */(1,"abc"))) |} |""".stripMargin, ) @@ -66,7 +66,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |object Main { | implicit val imp: Int = 2 | def addOne(x: Int)(implicit one: Int)/*: Int<>*/ = x + one - | val x/*: Int<>*/ = addOne(1)/*(using imp<<(3:15)>>)*/ + | val x/*: Int<>*/ = addOne(/*x = */1)/*(using imp<<(3:15)>>)*/ |} |""".stripMargin ) @@ -81,7 +81,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |""".stripMargin, """|case class User(name: String) |object Main { - | implicit def intToUser(x: Int): User = new User(x.toString) + | implicit def intToUser(x: Int): User = new User(/*name = */x.toString) | val y: User = /*intToUser<<(3:15)>>(*/1/*)*/ |} |""".stripMargin @@ -100,7 +100,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |object Main { | implicit val imp: Int = 2 | def addOne(x: Int)(using one: Int)/*: Int<>*/ = x + one - | val x/*: Int<>*/ = addOne(1)/*(using imp<<(3:15)>>)*/ + | val x/*: Int<>*/ = addOne(/*x = */1)/*(using imp<<(3:15)>>)*/ |} |""".stripMargin ) @@ -115,7 +115,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |""".stripMargin, """|case class User(name: String) |object Main { - | given intToUser: Conversion[Int, User] = User(_.toString) + | given intToUser: Conversion[Int, User] = User(/*name = */_.toString) | val y: User = /*intToUser<<(3:8)>>(*/1/*)*/ |} |""".stripMargin @@ -158,7 +158,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |} |""".stripMargin, """|object Main { - | val foo/*: List<>[Int<>]*/ = List[Int](123) + | val foo/*: List<>[Int<>]*/ = List[Int](/*elems = */123) |} |""".stripMargin ) @@ -170,7 +170,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |} |""".stripMargin, """|object O { - | def m/*: List<>[Int<>]*/ = 1 :: List/*[Int<>]*/(1) + | def m/*: List<>[Int<>]*/ = 1 :: List/*[Int<>]*/(/*elems = */1) |} |""".stripMargin ) @@ -182,7 +182,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |} |""".stripMargin, """|object Main { - | val foo/*: Map<>[Int<>, String<>]*/ = Map/*[Int<>, String<>]*/((1, "abc")) + | val foo/*: Map<>[Int<>, String<>]*/ = Map/*[Int<>, String<>]*/(/*elems = */(1, "abc")) |} |""".stripMargin, ) @@ -206,7 +206,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |} |""".stripMargin, """|object Main { - | val foo/*: Buffer<>[String<>]*/ = List[String]("").toBuffer[String] + | val foo/*: Buffer<>[String<>]*/ = List[String](/*elems = */"").toBuffer[String] |} |""".stripMargin, ) @@ -398,7 +398,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |} |""".stripMargin, """|object Main { - | val x/*: (Int<>, Int<>)*/ = Tuple2.apply/*[Int<>, Int<>]*/(1, 2) + | val x/*: (Int<>, Int<>)*/ = Tuple2.apply/*[Int<>, Int<>]*/(/*_1 = */1, /*_2 = */2) |} |""".stripMargin ) @@ -410,7 +410,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |} |""".stripMargin, """|object Main { - | val x/*: (Int<>, Int<>)*/ = Tuple2/*[Int<>, Int<>]*/(1, 2) + | val x/*: (Int<>, Int<>)*/ = Tuple2/*[Int<>, Int<>]*/(/*_1 = */1, /*_2 = */2) |} |""".stripMargin ) @@ -437,7 +437,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |} |""".stripMargin, """|object Main { - | val hd :: tail = List/*[Int<>]*/(1, 2) + | val hd :: tail = List/*[Int<>]*/(/*elems = */1, 2) |} |""".stripMargin, ) @@ -451,7 +451,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |} |""".stripMargin, """|object Main { - | val x/*: Int<>*/ = List/*[Int<>]*/(1, 2) match { + | val x/*: Int<>*/ = List/*[Int<>]*/(/*elems = */1, 2) match { | case hd :: tail => hd | } |} @@ -467,7 +467,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |""".stripMargin, """|object Main { |case class Foo[A](x: A, y: A) - | val Foo(fst/*: Int<>*/, snd/*: Int<>*/) = Foo/*[Int<>]*/(1, 2) + | val Foo(fst/*: Int<>*/, snd/*: Int<>*/) = Foo/*[Int<>]*/(/*x = */1, /*y = */2) |} |""".stripMargin, hintsInPatternMatch = true @@ -521,7 +521,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |} |""".stripMargin, """|object Main { - | List/*[Int<>]*/(1).collect/*[Int<>]*/ { case x => x } + | List/*[Int<>]*/(/*elems = */1).collect/*[Int<>]*/ { case x => x } | val x: PartialFunction[Int, Int] = { | case 1 => 2 | } @@ -611,17 +611,17 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |class DemoSpec { | import ScalatestMock._ | - | /*StringTestOps<<(6:17)>>(*/"foo"/*)*/ should { - | /*StringTestOps<<(6:17)>>(*/"checkThing1"/*)*/ in { + | /*StringTestOps<<(6:17)>>(*/"foo"/*)*/ should {/*=> */ + | /*StringTestOps<<(6:17)>>(*/"checkThing1"/*)*/ in {/*=> */ | checkThing1[String]/*(using instancesString<<(10:15)>>)*/ | }/*(using here<<(5:15)>>)*/ - | /*StringTestOps<<(6:17)>>(*/"checkThing2"/*)*/ in { + | /*StringTestOps<<(6:17)>>(*/"checkThing2"/*)*/ in {/*=> */ | checkThing2[String]/*(using instancesString<<(10:15)>>, instancesString<<(10:15)>>)*/ | }/*(using here<<(5:15)>>)*/ | }/*(using subjectRegistrationFunction<<(3:15)>>)*/ | - | /*StringTestOps<<(6:17)>>(*/"bar"/*)*/ should { - | /*StringTestOps<<(6:17)>>(*/"checkThing1"/*)*/ in { + | /*StringTestOps<<(6:17)>>(*/"bar"/*)*/ should {/*=> */ + | /*StringTestOps<<(6:17)>>(*/"checkThing1"/*)*/ in {/*=> */ | checkThing1[String]/*(using instancesString<<(10:15)>>)*/ | }/*(using here<<(5:15)>>)*/ | }/*(using subjectRegistrationFunction<<(3:15)>>)*/ @@ -651,11 +651,11 @@ class InlayHintsSuite extends BaseInlayHintsSuite { | |object Main { | def test(d: S[Int], f: S[Char]): AB[Int, String] = { - | val x/*: S<>[String<>]*/ = d.map/*[String<>]*/(_.toString) + | val x/*: S<>[String<>]*/ = d.map/*[String<>]*/(/*f = */_.toString) | val y/*: S<>[Char<>]*/ = f | ??? | } - | val x/*: AB<>[Int<>, String<>]*/ = test(Set/*[Int<>]*/(1), Set/*[Char<>]*/('a')) + | val x/*: AB<>[Int<>, String<>]*/ = test(/*d = */Set/*[Int<>]*/(/*elems = */1), /*f = */Set/*[Char<>]*/(/*elems = */'a')) |} |""".stripMargin, ) @@ -697,7 +697,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { | |given Ord[String] with | def compare(x: String, y: String)/*: Int<>*/ = - | /*augmentString<>(*/x/*)*/.compare(y) + | /*augmentString<>(*/x/*)*/.compare(/*that = */y) | |""".stripMargin ) @@ -715,7 +715,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |object O { | given Int = 1 | def test[T: Ordering](x: T)(using Int)/*: Nothing<>*/ = ??? - | test/*[Int<>]*/(1)/*(using Int<>, given_Int<<(2:8)>>)*/ + | test/*[Int<>]*/(/*x = */1)/*(using Int<>, given_Int<<(2:8)>>)*/ |} |""".stripMargin ) @@ -731,7 +731,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { """|package example |object O { | def test[T: Ordering](x: T)/*: Nothing<>*/ = ??? - | test/*[Int<>]*/(1)/*(using Int<>)*/ + | test/*[Int<>]*/(/*x = */1)/*(using Int<>)*/ |} |""".stripMargin ) @@ -747,7 +747,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { """|package example |object O { | def test[T: Ordering](x: T)(using Int)/*: Nothing<>*/ = ??? - | test/*[Int<>]*/(1)/*(using Int<>)*/ + | test/*[Int<>]*/(/*x = */1)/*(using Int<>)*/ |} |""".stripMargin ) @@ -765,7 +765,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |object O { | implicit val i: Int = 123 | def test[T: Ordering](x: T)(implicit v: Int)/*: Nothing<>*/ = ??? - | test/*[Int<>]*/(1)/*(using Int<>, i<<(2:15)>>)*/ + | test/*[Int<>]*/(/*x = */1)/*(using Int<>, i<<(2:15)>>)*/ |} |""".stripMargin ) @@ -795,19 +795,19 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |""".stripMargin, """|package example |object O { - | val head :: tail = List/*[Int<>]*/(1) - | List/*[Int<>]*/(1) match { + | val head :: tail = List/*[Int<>]*/(/*elems = */1) + | List/*[Int<>]*/(/*elems = */1) match { | case head :: next => | case Nil => | } - | Option/*[Option<>[Int<>]]*/(Option/*[Int<>]*/(1)) match { + | Option/*[Option<>[Int<>]]*/(/*x = */Option/*[Int<>]*/(/*x = */1)) match { | case Some(Some(value)) => | case None => | } | val (local, _) = ("", 1.0) - | val Some(x) = Option/*[Int<>]*/(1) + | val Some(x) = Option/*[Int<>]*/(/*x = */1) | for { - | x <- List/*[(Int<>, Int<>)]*/((1,2)) + | x <- List/*[(Int<>, Int<>)]*/(/*elems = */(1,2)) | (z, y) = x | } yield { | x @@ -842,19 +842,19 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |""".stripMargin, """|package example |object O { - | val head/*: Int<>*/ :: tail/*: List<>[Int<>]*/ = List/*[Int<>]*/(1) - | List/*[Int<>]*/(1) match { + | val head/*: Int<>*/ :: tail/*: List<>[Int<>]*/ = List/*[Int<>]*/(/*elems = */1) + | List/*[Int<>]*/(/*elems = */1) match { | case head/*: Int<>*/ :: next/*: List<>[Int<>]*/ => | case Nil => | } - | Option/*[Option<>[Int<>]]*/(Option/*[Int<>]*/(1)) match { + | Option/*[Option<>[Int<>]]*/(/*x = */Option/*[Int<>]*/(/*x = */1)) match { | case Some(Some(value/*: Int<>*/)) => | case None => | } | val (local/*: String<>*/, _) = ("", 1.0) - | val Some(x/*: Int<>*/) = Option/*[Int<>]*/(1) + | val Some(x/*: Int<>*/) = Option/*[Int<>]*/(/*x = */1) | for { - | x/*: (Int<>, Int<>)*/ <- List/*[(Int<>, Int<>)]*/((1,2)) + | x/*: (Int<>, Int<>)*/ <- List/*[(Int<>, Int<>)]*/(/*elems = */(1,2)) | (z/*: Int<>*/, y/*: Int<>*/) = x | } yield { | x @@ -956,7 +956,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { | 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)>>)*/ + | val g: String = foo(/*b = */B())/*(using theA<<(4:15)>>)*/ |} |""".stripMargin, ) @@ -975,7 +975,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |""".stripMargin, """|object Main{ | def hello()(implicit string: String, integer: Int, long: Long): String = { - | println(s"Hello $string, $long, $integer!") + | println(/*x = */s"Hello $string, $long, $integer!") | } | implicit def theString(implicit i: Int): String = i.toString | implicit def theInt(implicit l: Long): Int = l @@ -999,7 +999,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |""".stripMargin, """|object Main{ | def hello()(implicit string: String, integer: Int, long: Long): String = { - | println(s"Hello $string, $long, $integer!") + | println(/*x = */s"Hello $string, $long, $integer!") | } | implicit def theString(implicit i: Int): String = i.toString | implicit def theInt: Int = 43 @@ -1055,4 +1055,617 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |} |""".stripMargin ) + + @Test def `named-tuples` = + check( + """|def hello = (path = ".", num = 5) + | + |def test = + | hello ++ (line = 1) + | + |@main def bla = + | val x: (path: String, num: Int, line: Int) = test + |""".stripMargin, + """|def hello/*: (path : String<>, num : Int<>)*/ = (path = ".", num = 5)/*[(String<>, Int<>)]*/ + | + |def test/*: (path : String<>, num : Int<>, line : Int<>)*/ = + | hello ++/*[Tuple1<>["line"], Tuple1<>[Int<>]]*/ (line = 1)/*(using refl<>)*//*[Tuple1<>[Int<>]]*/ + | + |@main def bla/*: Unit<>*/ = + | val x: (path: String, num: Int, line: Int) = test + |""".stripMargin + ) + + @Test def `named-tuple-false-negative` = + check( + """|def hello(test: Int) = (path = ".", num = test) + | + |@main def test2 = + | val x = hello(7) + |""".stripMargin, + """|def hello(test: Int)/*: (path : String<>, num : Int<>)*/ = (path = ".", num = test)/*[(String<>, Int<>)]*/ + | + |@main def test2/*: Unit<>*/ = + | val x/*: (path : String<>, num : Int<>)*/ = hello(/*test = */7) + |""".stripMargin + ) + + @Test def `by-name-regular` = + check( + """|object Main: + | def foo(x: => Int, y: Int, z: => Int)(w: Int, v: => Int): Unit = () + | foo(1, 2, 3)(4, 5) + |""".stripMargin, + """|object Main: + | def foo(x: => Int, y: Int, z: => Int)(w: Int, v: => Int): Unit = () + | foo(/*x = => */1, /*y = */2, /*z = => */3)(/*w = */4, /*v = => */5) + |""".stripMargin + ) + + @Test def `by-name-block` = + check( + """|object Main: + | def Future[A](arg: => A): A = arg + | + | Future(1 + 2) + | Future { + | 1 + 2 + | } + | Future { + | val x = 1 + | val y = 2 + | x + y + | } + | Some(Option(2) + | .getOrElse { + | List(1,2) + | .headOption + | }) + |""".stripMargin, + """|object Main: + | def Future[A](arg: => A): A = arg + | + | Future/*[Int<>]*/(/*arg = => */1 + 2) + | Future/*[Int<>]*/ {/*=> */ + | 1 + 2 + | } + | Future/*[Int<>]*/ {/*=> */ + | val x/*: Int<>*/ = 1 + | val y/*: Int<>*/ = 2 + | x + y + | } + | Some/*[Int<> | Option<>[Int<>]]*/(/*value = */Option/*[Int<>]*/(/*x = */2) + | .getOrElse/*[Int<> | Option<>[Int<>]]*/ {/*=> */ + | List/*[Int<>]*/(/*elems = */1,2) + | .headOption + | }) + |""".stripMargin + ) + + @Test def `by-name-for-comprehension` = + check( + """|object Main: + | case class Test[A](v: A): + | def flatMap(f: => (A => Test[Int])): Test[Int] = f(v) + | def map(f: => (A => Int)): Test[Int] = Test(f(v)) + | + | def main(args: Array[String]): Unit = + | val result: Test[Int] = for { + | a <- Test(10) + | b <- Test(20) + | } yield a + b + | + |""".stripMargin, + """|object Main: + | case class Test[A](v: A): + | def flatMap(f: => (A => Test[Int])): Test[Int] = f(/*v1 = */v) + | def map(f: => (A => Int)): Test[Int] = Test/*[Int<>]*/(/*v = */f(/*v1 = */v)) + | + | def main(args: Array[String]): Unit = + | val result: Test[Int] = for { + | a <- Test/*[Int<>]*/(/*v = */10) + | b <- Test/*[Int<>]*/(/*v = */20) + | } yield a + b + | + |""".stripMargin, + ) + + @Test def `by-name-for-comprehension-generic` = + check( + """|object Main: + | case class Test[A](v: A): + | def flatMap[B](f: => (A => Test[B])): Test[B] = f(v) + | def map[B](f: => (A => B)): Test[B] = Test(f(v)) + | + | def main(args: Array[String]): Unit = + | val result: Test[Int] = for { + | a <- Test(10) + | b <- Test(20) + | } yield a + b + | + |""".stripMargin, + """|object Main: + | case class Test[A](v: A): + | def flatMap[B](f: => (A => Test[B])): Test[B] = f(/*v1 = */v) + | def map[B](f: => (A => B)): Test[B] = Test/*[B<<(4:13)>>]*/(/*v = */f(/*v1 = */v)) + | + | def main(args: Array[String]): Unit = + | val result: Test[Int] = for { + | a <- Test/*[Int<>]*/(/*v = */10) + | b <- Test/*[Int<>]*/(/*v = */20) + | } yield a + b + | + |""".stripMargin, + ) + + @Test def `by-name-method-infix-extension` = + check( + """|case class A[T, U](dummy: Int, name: U): + | def compute: Int = 1 + | + |object A: + | extension [T, U](a: A[T, U]) + | def ++(other: => A[T, U]): Int = a.dummy + other.dummy + a.compute + | + |object Main: + | val a = A[Int, String](0, "foo") + | val res = a ++ a + |""".stripMargin, + """|case class A[T, U](dummy: Int, name: U): + | def compute: Int = 1 + | + |object A: + | extension [T, U](a: A[T, U]) + | def ++(other: => A[T, U]): Int = a.dummy + other.dummy + a.compute + | + |object Main: + | val a/*: A<<(1:11)>>[Int<>, String<>]*/ = A[Int, String](/*dummy = */0, /*name = */"foo") + | val res/*: Int<>*/ = a ++/*[Int<>, String<>]*/ a + |""".stripMargin + ) + + @Test def `by-name-method-infix-extension-2` = + check( + """|case class A[T, U](dummy: Int, name: U): + | def compute: Int = 1 + | + |extension [T, U](a: A[T, U]) + | def ++(other: => A[T, U]): Int = a.dummy + other.dummy + a.compute + | + |object Main: + | val a = A[Int, String](0, "foo") + | val res = a ++ a + |""".stripMargin, + """|case class A[T, U](dummy: Int, name: U): + | def compute: Int = 1 + | + |extension [T, U](a: A[T, U]) + | def ++(other: => A[T, U]): Int = a.dummy + other.dummy + a.compute + | + |object Main: + | val a/*: A<<(1:11)>>[Int<>, String<>]*/ = A[Int, String](/*dummy = */0, /*name = */"foo") + | val res/*: Int<>*/ = a ++/*[Int<>, String<>]*/ a + |""".stripMargin + ) + + @Test def `named-parameter` = + check( + """|object Main{ + | def hello[T](arg: T) = arg + | val x = hello(arg = List(1)) + |} + |""".stripMargin, + """|object Main{ + | def hello[T](arg: T)/*: T<<(2:12)>>*/ = arg + | val x/*: List<>[Int<>]*/ = hello/*[List<>[Int<>]]*/(arg = List/*[Int<>]*/(/*elems = */1)) + |} + |""".stripMargin + ) + + @Test def `java-method-call` = + check( + """|object Main { + | val str = "hello" + | val sub = str.substring(1, 3) + | val replaced = str.replace('l', 'x') + |} + |""".stripMargin, + """|object Main { + | val str/*: String<>*/ = "hello" + | val sub/*: String<>*/ = str.substring(1, 3) + | val replaced/*: String<>*/ = str.replace('l', 'x') + |} + |""".stripMargin + ) + + @Test def `default-parameter` = + check( + """|object Main { + | def foo(a: Int, b: Int = 2) = a + b + | val x = foo(1) + |} + |""".stripMargin, + """|object Main { + | def foo(a: Int, b: Int = 2)/*: Int<>*/ = a + b + | val x/*: Int<>*/ = foo(/*a = */1) + |} + |""".stripMargin + ) + + @Test def `default-parameter-2` = + check( + """|object Main { + | def foo(a: Int = 10, b: Int = 2) = a + b + | val x = foo(b = 1) + |} + |""".stripMargin, + """|object Main { + | def foo(a: Int = 10, b: Int = 2)/*: Int<>*/ = a + b + | val x/*: Int<>*/ = foo(b = 1) + |} + |""".stripMargin + ) + + @Test def `default-parameter-3` = + check( + """|object Main { + | def foo(a: Int, b: Int = 2, c: Int) = a + b + c + | val x = foo(a = 1, c = 2) + |} + |""".stripMargin, + """|object Main { + | def foo(a: Int, b: Int = 2, c: Int)/*: Int<>*/ = a + b + c + | val x/*: Int<>*/ = foo(a = 1, c = 2) + |} + |""".stripMargin + ) + + @Test def `default-parameter-4` = + check( + """|object Main { + | def foo(a: Int, b: Int = 2, c: Int) = a + b + c + | val x = foo(1, 2, 3) + |} + |""".stripMargin, + """|object Main { + | def foo(a: Int, b: Int = 2, c: Int)/*: Int<>*/ = a + b + c + | val x/*: Int<>*/ = foo(/*a = */1, /*b = */2, /*c = */3) + |} + |""".stripMargin + ) + + @Test def `xray-single-chain-same-line` = + check( + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar.bar + |val thing2: Foo = foo.foo() + |} + |""".stripMargin, + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar.bar + |val thing2: Foo = foo.foo() + |} + |""".stripMargin + ) + + @Test def `xray-multi-chain-same-line` = + check( + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar.bar.bar + |val thing2: Foo = foo.foo().foo() + |} + |""".stripMargin, + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar.bar.bar + |val thing2: Foo = foo.foo().foo() + |} + |""".stripMargin + ) + + @Test def `xray-single-chain-new-line` = + check( + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar + | .bar + |val thing2: Foo = foo + | .foo() + |} + |""".stripMargin, + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar + | .bar + |val thing2: Foo = foo + | .foo() + |} + |""".stripMargin + ) + + @Test def `xray-simple-chain` = + check( + """|object Main{ + | trait Foo { + | def bar: Bar + | } + | + | trait Bar { + | def foo(): Foo + | } + | + |val foo: Foo = ??? + | + |val thingy: Bar = foo + | .bar + | .foo() + | .bar + |} + |""".stripMargin, + """|object Main{ + | trait Foo { + | def bar: Bar + | } + | + | trait Bar { + | def foo(): Foo + | } + | + |val foo: Foo = ??? + | + |val thingy: Bar = foo + | .bar/* : Bar<<(6:8)>>*/ + | .foo()/*: Foo<<(2:8)>>*/ + | .bar/* : Bar<<(6:8)>>*/ + |} + |""".stripMargin + ) + + @Test def `xray-long-chain` = + check( + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String*): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify + | .stringListify( + | "Hello", + | "World" + | ) + | .stringListify( + | "Hello", + | "World" + | ) + | .intify + | .intify + |} + |""".stripMargin, + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String*): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + | .stringListify( + | /*s = */"Hello", + | "World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .stringListify( + | /*s = */"Hello", + | "World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + |} + |""".stripMargin + ) + + @Test def `xray-long-chain-same-line` = + check( + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String*): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify + | .stringListify( + | "Hello", + | "World" + | ) + | .stringListify( + | "Hello", + | "World" + | ) + | .intify.intify + |} + |""".stripMargin, + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String*): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify/* : Foo<<(2:8)>>[Int<>]*/ + | .stringListify( + | /*s = */"Hello", + | "World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .stringListify( + | /*s = */"Hello", + | "World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .intify.intify/*: Foo<<(2:8)>>[Int<>]*/ + |} + |""".stripMargin + ) + + @Test def `xray-tikka-masala-curried` = + check( + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String)(s2: String): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify + | .stringListify( + | "Hello" + | )( + | "World" + | ) + | .stringListify( + | "Hello" + | )( + | "World" + | ) + | .intify + | .intify + |} + |""".stripMargin, + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String)(s2: String): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + | .stringListify( + | /*s = */"Hello" + | )( + | /*s2 = */"World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .stringListify( + | /*s = */"Hello" + | )( + | /*s2 = */"World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + |} + |""".stripMargin + ) + + @Test def `xray-for-comprehension` = + check( + """|object Main{ + |trait Foo[A]{ + | def flatMap[B](f: A => Foo[B]): Foo[B] + | def map[B](f: A => B): Foo[B] + | def bar(s: String): Foo[A] + |} + |val foo1: Foo[String] = ??? + |val foo2: Foo[Int] = ??? + |val result = for { + | foo <- foo1 + | bar <- foo2 + | .bar(s = foo) + | .bar(s = foo) + | .bar(s = foo) + |} yield bar + |} + |""".stripMargin, + """|object Main{ + |trait Foo[A]{ + | def flatMap[B](f: A => Foo[B]): Foo[B] + | def map[B](f: A => B): Foo[B] + | def bar(s: String): Foo[A] + |} + |val foo1: Foo[String] = ??? + |val foo2: Foo[Int] = ??? + |val result/*: Foo<<(2:6)>>[Int<>]*/ = for { + | foo <- foo1 + | bar <- foo2 + | .bar(s = foo)/*: Foo<<(2:6)>>[Int<>]*/ + | .bar(s = foo)/*: Foo<<(2:6)>>[Int<>]*/ + | .bar(s = foo)/*: Foo<<(2:6)>>[Int<>]*/ + |} yield bar + |} + |""".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 42e76dfeb114..6179f610fed3 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/tokens/SemanticTokensSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/tokens/SemanticTokensSuite.scala @@ -463,3 +463,11 @@ class SemanticTokensSuite extends BaseSemanticTokensSuite: | end <>/*class,definition*/ |""".stripMargin ) + + @Test def i7256 = + check( + """|object <>/*class*/: + | def <>/*method,definition*/: <>/*class,abstract*/ = <>/*method*/ + |export <>/*class*/.<>/*method*/ + |""".stripMargin + ) diff --git a/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala b/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala index 459c41e3c8e5..735ee846be2d 100644 --- a/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala +++ b/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala @@ -66,7 +66,7 @@ class MockSymbolSearch( override def documentation( symbol: String, parents: ParentSymbols - ) = documentation(symbol, parents, ContentType.MARKDOWN) + ): Optional[SymbolDocumentation] = documentation(symbol, parents, ContentType.MARKDOWN) override def documentation( symbol: String, diff --git a/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala b/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala index 5fb38ad88e19..c0064b94355a 100644 --- a/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala +++ b/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala @@ -36,7 +36,7 @@ object TestInlayHints { InlayHints.fromData(inlayHint.getData().asInstanceOf[JsonElement])._2 buffer += "/*" labels.zip(data).foreach { case (label, data) => - buffer += label.nn + buffer += label buffer ++= readData(data) } buffer += "*/" diff --git a/project/Build.scala b/project/Build.scala index 3ea865fb00db..db4966cb4231 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -24,10 +24,11 @@ import sbt.PublishBinPlugin.autoImport._ import dotty.tools.sbtplugin.RepublishPlugin import dotty.tools.sbtplugin.RepublishPlugin.autoImport._ import dotty.tools.sbtplugin.ScalaLibraryPlugin +import dotty.tools.sbtplugin.DottyJSPlugin +import dotty.tools.sbtplugin.DottyJSPlugin.autoImport._ import sbt.plugins.SbtPlugin import sbt.ScriptedPlugin.autoImport._ -import xerial.sbt.Sonatype.autoImport._ import com.typesafe.tools.mima.plugin.MimaPlugin.autoImport._ import org.scalajs.sbtplugin.ScalaJSPlugin import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ @@ -40,58 +41,6 @@ import sbttastymima.TastyMiMaPlugin.autoImport._ import scala.util.Properties.isJavaAtLeast import org.portablescala.sbtplatformdeps.PlatformDepsPlugin.autoImport._ -import org.scalajs.linker.interface.{ModuleInitializer, StandardConfig} - -object DottyJSPlugin extends AutoPlugin { - import Build._ - - object autoImport { - val switchToESModules: StandardConfig => StandardConfig = - config => config.withModuleKind(ModuleKind.ESModule) - } - - val writePackageJSON = taskKey[Unit]( - "Write package.json to configure module type for Node.js") - - override def requires: Plugins = ScalaJSPlugin - - override def projectSettings: Seq[Setting[_]] = Def.settings( - commonBootstrappedSettings, - - /* #11709 Remove the dependency on scala3-library that ScalaJSPlugin adds. - * Instead, in this build, we use `.dependsOn` relationships to depend on - * the appropriate, locally-defined, scala3-library-bootstrappedJS. - */ - libraryDependencies ~= { - _.filter(!_.name.startsWith("scala3-library_sjs1")) - }, - - // Replace the JVM JUnit dependency by the Scala.js one - libraryDependencies ~= { - _.filter(!_.name.startsWith("junit-interface")) - }, - libraryDependencies += - ("org.scala-js" %% "scalajs-junit-test-runtime" % scalaJSVersion % "test").cross(CrossVersion.for3Use2_13), - - // Typecheck the Scala.js IR found on the classpath - scalaJSLinkerConfig ~= (_.withCheckIR(true)), - - Compile / jsEnvInput := (Compile / jsEnvInput).dependsOn(writePackageJSON).value, - Test / jsEnvInput := (Test / jsEnvInput).dependsOn(writePackageJSON).value, - - writePackageJSON := { - val packageType = scalaJSLinkerConfig.value.moduleKind match { - case ModuleKind.NoModule => "commonjs" - case ModuleKind.CommonJSModule => "commonjs" - case ModuleKind.ESModule => "module" - } - - val path = target.value / "package.json" - - IO.write(path, s"""{"type": "$packageType"}\n""") - }, - ) -} object Build { import ScaladocConfigs._ @@ -103,7 +52,7 @@ object Build { * * Warning: Change of this variable needs to be consulted with `expectedTastyVersion` */ - val referenceVersion = "3.7.0-RC4" + val referenceVersion = "3.7.3" /** Version of the Scala compiler targeted in the current release cycle * Contains a version without RC/SNAPSHOT/NIGHTLY specific suffixes @@ -114,7 +63,7 @@ object Build { * * Warning: Change of this variable might require updating `expectedTastyVersion` */ - val developedVersion = "3.7.1" + val developedVersion = "3.7.4" /** The version of the compiler including the RC prefix. * Defined as common base before calculating environment specific suffixes in `dottyVersion` @@ -123,7 +72,7 @@ object Build { * During release candidate cycle incremented by the release officer before publishing a subsequent RC version; * During final, stable release is set exactly to `developedVersion`. */ - val baseVersion = s"$developedVersion-RC1" + val baseVersion = developedVersion /** The version of TASTY that should be emitted, checked in runtime test * For defails on how TASTY version should be set see related discussions: @@ -141,7 +90,7 @@ object Build { * - in release candidate branch is experimental if {patch == 0} * - in stable release is always non-experimetnal */ - val expectedTastyVersion = "28.7-experimental-1" + val expectedTastyVersion = "28.7" checkReleasedTastyVersion() /** Final version of Scala compiler, controlled by environment variables. */ @@ -176,9 +125,8 @@ object Build { * For a developedVersion `3.M.P` the mimaPreviousDottyVersion should be set to: * - `3.M.0` if `P > 0` * - `3.(M-1).0` if `P = 0` - * 3.6.2 is an exception from this rule - 3.6.0 was a broken release, 3.6.1 was hotfix (unstable) release */ - val mimaPreviousDottyVersion = "3.7.0-RC1" + val mimaPreviousDottyVersion = "3.7.0" /** LTS version against which we check binary compatibility. * @@ -189,7 +137,7 @@ object Build { val mimaPreviousLTSDottyVersion = "3.3.0" /** Version of Scala CLI to download */ - val scalaCliLauncherVersion = "1.7.1" + val scalaCliLauncherVersion = "1.9.1" /** Version of Coursier to download for initializing the local maven repo of Scala command */ val coursierJarVersion = "2.1.24" @@ -346,9 +294,6 @@ object Build { buildScan .withPublishing(Publishing.onlyIf(_.authenticated)) .withBackgroundUpload(!isInsideCI) - .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( @@ -398,7 +343,7 @@ object Build { for { username <- sys.env.get("SONATYPE_USER") password <- sys.env.get("SONATYPE_PW") - } yield Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org", username, password) + } yield Credentials("Sonatype Nexus Repository Manager", "central.sonatype.com", username, password) ).toList, PgpKeys.pgpPassphrase := sys.env.get("PGP_PW").map(_.toCharArray()), PgpKeys.useGpgPinentry := true, @@ -536,7 +481,7 @@ object Build { "scala2-library-tasty" ) - val enableBspAllProjects = false + val enableBspAllProjects = sys.env.get("ENABLE_BSP_ALL_PROJECTS").map(_.toBoolean).getOrElse(false) // Settings used when compiling dotty with a non-bootstrapped dotty lazy val commonBootstrappedSettings = commonDottySettings ++ Seq( @@ -725,6 +670,13 @@ object Build { recur(lines, false) } + /** Replaces JDK 9+ methods with their JDK 8 alterantive */ + def replaceJDKIncompatibilities(lines: List[String]): List[String] = { + lines.map( + _.replaceAll("""(\").repeat\((\w+)\)""", """$1 * $2""") + ) + } + /** replace imports of `com.google.protobuf.*` with compiler implemented version */ def replaceProtobuf(lines: List[String]): List[String] = { def recur(ls: List[String]): List[String] = ls match { @@ -1132,7 +1084,8 @@ object Build { 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(_.getCanonicalPath).distinct.mkString(File.pathSeparator), + // NOTE: Do not use `sourceDirectories` since `sources` are currently pinned until `3.8.0` + "-sourcepath", (Compile / sources).value.map(_.getCanonicalPath).distinct.mkString(File.pathSeparator), "-Yexplicit-nulls", ), (Compile / doc / scalacOptions) ++= ScaladocConfigs.DefaultGenerationSettings.value.settings, @@ -1143,7 +1096,1134 @@ object Build { ) lazy val `scala3-library` = project.in(file("library")).asDottyLibrary(NonBootstrapped) + .settings( + // Note: extracted using `print scala3-library / Compile / sources` + // Only keep scala3 files until 3.8.0 + Compile / sources := Seq( + file(s"${baseDirectory.value}/src/scala/Precise.scala"), + file(s"${baseDirectory.value}/src/scala/CanEqual.scala"), + file(s"${baseDirectory.value}/src/scala/Conversion.scala"), + file(s"${baseDirectory.value}/src/scala/PolyFunction.scala"), + file(s"${baseDirectory.value}/src/scala/Pure.scala"), + file(s"${baseDirectory.value}/src/scala/IArray.scala"), + file(s"${baseDirectory.value}/src/scala/CanThrow.scala"), + file(s"${baseDirectory.value}/src/scala/Tuple.scala"), + file(s"${baseDirectory.value}/src/scala/Selectable.scala"), + file(s"${baseDirectory.value}/src/scala/main.scala"), + file(s"${baseDirectory.value}/src/scala/NamedTuple.scala"), + file(s"${baseDirectory.value}/src/scala/util/FromDigits.scala"), + file(s"${baseDirectory.value}/src/scala/util/CommandLineParser.scala"), + file(s"${baseDirectory.value}/src/scala/util/TupledFunction.scala"), + file(s"${baseDirectory.value}/src/scala/util/NotGiven.scala"), + file(s"${baseDirectory.value}/src/scala/util/boundary.scala"), + file(s"${baseDirectory.value}/src/scala/caps/package.scala"), + file(s"${baseDirectory.value}/src/scala/reflect/TypeTest.scala"), + file(s"${baseDirectory.value}/src/scala/reflect/Selectable.scala"), + file(s"${baseDirectory.value}/src/scala/reflect/Typeable.scala"), + file(s"${baseDirectory.value}/src/scala/reflect/Enum.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/TupleMirror.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/TypeBox.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/Arrays.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/TupledFunctions.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/FunctionXXL.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/Scala3RunTime.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/$$throws.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/LazyVals.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/EnumValue.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/TupleXXL.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/Tuples.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/MatchCase.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/retains.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/capability.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/static.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/transparentTrait.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/RefiningAnnotation.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/retainsByName.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/threadUnsafe.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/constructorOnly.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/experimental.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/MacroAnnotation.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/alpha.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/publicInBinary.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/init.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/unroll.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/targetName.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/stableNull.scala"), + file(s"${baseDirectory.value}/src/scala/deriving/Mirror.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/package.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/Type.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/Varargs.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/Quotes.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/Expr.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/ExprMap.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/FromExpr.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/Exprs.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/ToExpr.scala"), + file(s"${baseDirectory.value}/src/scala/util/control/NonLocalReturns.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/stdLibPatches/language.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/stdLibPatches/Predef.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure8.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure10.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure4.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure5.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure11.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure9.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure2.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure20.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure16.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure17.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure3.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure21.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure18.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure22.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure0.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure14.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure15.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure1.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure19.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure12.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure6.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure7.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure13.java"), + file(s"${baseDirectory.value}/src/scala/runtime/coverage/Invoker.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/ErasedParam.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/RuntimeChecked.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/CaptureChecked.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/ContextResultCount.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/TASTYSignature.java"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/Alias.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/MappedAlternative.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/Repeated.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/WithPureFuns.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/Child.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/ProvisionalSuperClass.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/WitnessNames.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/AssignedNonLocally.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/preview.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/InlineParam.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/SourceFile.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/reachCapability.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/$$into.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/TASTYLongSignature.java"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/readOnlyCapability.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/unshared.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/AnnotationDefault.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/sharable.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/Body.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/requiresCapability.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/unchecked/uncheckedCaptures.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/unchecked/uncheckedCapabilityLeaks.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/testing/Error.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/testing/ErrorKind.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/testing/package.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/ops/long.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/ops/any.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/ops/int.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/ops/string.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/ops/double.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/ops/boolean.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/ops/float.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/runtime/QuoteUnpickler.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/runtime/QuoteMatching.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/runtime/Expr.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/runtime/Patterns.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/runtime/SplicedType.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/runtime/StopMacroExpansion.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/Erased.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/onlyCapability.scala"), + ) + ) lazy val `scala3-library-bootstrapped`: Project = project.in(file("library")).asDottyLibrary(Bootstrapped) + .settings( + // Note: extracted using `print scala3-library-bootstrapped / Compile / sources` + // Only keep scala3 files until 3.8.0 + Compile / sources := Seq( + file(s"${baseDirectory.value}/src/scala/Precise.scala"), + file(s"${baseDirectory.value}/src/scala/CanEqual.scala"), + file(s"${baseDirectory.value}/src/scala/Conversion.scala"), + file(s"${baseDirectory.value}/src/scala/PolyFunction.scala"), + file(s"${baseDirectory.value}/src/scala/Pure.scala"), + file(s"${baseDirectory.value}/src/scala/IArray.scala"), + file(s"${baseDirectory.value}/src/scala/CanThrow.scala"), + file(s"${baseDirectory.value}/src/scala/Tuple.scala"), + file(s"${baseDirectory.value}/src/scala/Selectable.scala"), + file(s"${baseDirectory.value}/src/scala/main.scala"), + file(s"${baseDirectory.value}/src/scala/NamedTuple.scala"), + file(s"${baseDirectory.value}/src/scala/util/FromDigits.scala"), + file(s"${baseDirectory.value}/src/scala/util/CommandLineParser.scala"), + file(s"${baseDirectory.value}/src/scala/util/TupledFunction.scala"), + file(s"${baseDirectory.value}/src/scala/util/NotGiven.scala"), + file(s"${baseDirectory.value}/src/scala/util/boundary.scala"), + file(s"${baseDirectory.value}/src/scala/caps/package.scala"), + file(s"${baseDirectory.value}/src/scala/reflect/TypeTest.scala"), + file(s"${baseDirectory.value}/src/scala/reflect/Selectable.scala"), + file(s"${baseDirectory.value}/src/scala/reflect/Typeable.scala"), + file(s"${baseDirectory.value}/src/scala/reflect/Enum.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/TupleMirror.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/TypeBox.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/Arrays.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/TupledFunctions.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/FunctionXXL.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/Scala3RunTime.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/$$throws.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/LazyVals.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/EnumValue.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/TupleXXL.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/Tuples.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/MatchCase.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/retains.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/capability.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/static.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/transparentTrait.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/RefiningAnnotation.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/retainsByName.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/threadUnsafe.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/constructorOnly.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/experimental.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/MacroAnnotation.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/alpha.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/publicInBinary.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/init.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/unroll.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/targetName.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/stableNull.scala"), + file(s"${baseDirectory.value}/src/scala/deriving/Mirror.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/package.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/Type.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/Varargs.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/Quotes.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/Expr.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/ExprMap.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/FromExpr.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/Exprs.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/ToExpr.scala"), + file(s"${baseDirectory.value}/src/scala/util/control/NonLocalReturns.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/stdLibPatches/language.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/stdLibPatches/Predef.scala"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure8.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure10.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure4.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure5.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure11.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure9.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure2.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure20.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure16.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure17.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure3.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure21.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure18.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure22.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure0.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure14.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure15.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure1.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure19.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure12.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure6.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure7.java"), + file(s"${baseDirectory.value}/src/scala/runtime/function/JProcedure13.java"), + file(s"${baseDirectory.value}/src/scala/runtime/coverage/Invoker.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/ErasedParam.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/RuntimeChecked.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/CaptureChecked.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/ContextResultCount.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/TASTYSignature.java"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/Alias.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/MappedAlternative.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/Repeated.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/WithPureFuns.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/Child.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/ProvisionalSuperClass.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/WitnessNames.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/AssignedNonLocally.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/preview.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/InlineParam.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/SourceFile.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/reachCapability.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/$$into.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/TASTYLongSignature.java"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/readOnlyCapability.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/unshared.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/AnnotationDefault.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/sharable.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/Body.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/requiresCapability.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/unchecked/uncheckedCaptures.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/unchecked/uncheckedCapabilityLeaks.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/testing/Error.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/testing/ErrorKind.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/testing/package.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/ops/long.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/ops/any.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/ops/int.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/ops/string.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/ops/double.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/ops/boolean.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/ops/float.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/runtime/QuoteUnpickler.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/runtime/QuoteMatching.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/runtime/Expr.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/runtime/Patterns.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/runtime/SplicedType.scala"), + file(s"${baseDirectory.value}/src/scala/quoted/runtime/StopMacroExpansion.scala"), + file(s"${baseDirectory.value}/src/scala/compiletime/Erased.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/internal/onlyCapability.scala"), + ) + ) + + // ============================================================================================== + // ================================= NON-BOOTSTRAPPED PROJECTS ================================== + // ============================================================================================== + + lazy val `scala3-nonbootstrapped` = project + .aggregate(`scala3-interfaces`, `scala3-library-nonbootstrapped` , `scala-library-nonbootstrapped`, + `tasty-core-nonbootstrapped`, `scala3-compiler-nonbootstrapped`, `scala3-sbt-bridge-nonbootstrapped`) + .settings( + name := "scala3-nonbootstrapped", + moduleName := "scala3-nonbootstrapped", + version := dottyNonBootstrappedVersion, + // Nothing to be published by this project, it is only an aggregate + Compile / publishArtifact := false, + Test / publishArtifact := false, + // Nothing to be published by this project + publish / skip := true, + // Project specific target folder. sbt doesn't like having two projects using the same target folder + target := target.value / "scala3-nonbootstrapped", + ) + + /* Configuration of the org.scala-lang:scala3-sbt-bridge:*.**.**-nonbootstrapped project */ + lazy val `scala3-sbt-bridge-nonbootstrapped` = project.in(file("sbt-bridge")) + .dependsOn(`scala3-compiler-nonbootstrapped`) // TODO: Would this actually evict the reference compiler in scala-tool? + .settings( + name := "scala3-sbt-bridge-nonbootstrapped", + moduleName := "scala3-sbt-bridge", + version := dottyNonBootstrappedVersion, + versionScheme := Some("semver-spec"), + scalaVersion := referenceVersion, // nonbootstrapped artifacts are compiled with the reference compiler (already officially published) + crossPaths := false, // org.scala-lang:scala3-sbt-bridge doesn't have a crosspath + autoScalaLibrary := false, // do not add a dependency to stdlib, we depend transitively on the stdlib from `scala3-compiler-nonbootstrapped` + // Add the source directories for the sbt-bridge (non-boostrapped) + Compile / unmanagedSourceDirectories := Seq(baseDirectory.value / "src"), + Test / unmanagedSourceDirectories := Seq(baseDirectory.value / "test"), + Compile / resourceDirectory := baseDirectory.value / "resources", + // NOTE: The only difference here is that we drop `-Werror` and semanticDB for now + Compile / scalacOptions := Seq("-deprecation", "-feature", "-unchecked", "-encoding", "UTF8", "-language:implicitConversions"), + // Make sure that the produced artifacts have the minimum JVM version in the bytecode + Compile / javacOptions ++= Seq("--target", Versions.minimumJVMVersion), + Compile / scalacOptions ++= Seq("--java-output-version", Versions.minimumJVMVersion), + // Add all the project's external dependencies + libraryDependencies ++= Seq( + ("org.scala-sbt" %% "zinc-apiinfo" % "1.8.0" % Test).cross(CrossVersion.for3Use2_13), + "com.github.sbt" % "junit-interface" % "0.13.3" % Test, + ), + // Exclude the transitive dependencies from `zinc-apiinfo` that causes issues at the moment + excludeDependencies ++= Seq( + "org.scala-lang" % "scala-reflect", + "org.scala-lang" % "scala-compiler", + ), + // Packaging configuration of the stdlib + Compile / packageBin / publishArtifact := true, + Compile / packageDoc / publishArtifact := false, + Compile / packageSrc / publishArtifact := true, + // Only publish compilation artifacts, no test artifacts + Test / publishArtifact := false, + // non-bootstrapped stdlib is publishable (only locally) + publish / skip := false, + // Project specific target folder. sbt doesn't like having two projects using the same target folder + target := target.value / "scala3-sbt-bridge-nonbootstrapped", + // sbt adds all the projects to scala-tool config which breaks building the scalaInstance + // as a workaround, I build it manually by only adding the compiler + scalaInstance := { + val lm = dependencyResolution.value + val log = streams.value.log + val retrieveDir = streams.value.cacheDirectory / "scala3-compiler" / scalaVersion.value + val comp = lm.retrieve("org.scala-lang" % "scala3-compiler_3" % + scalaVersion.value, scalaModuleInfo = None, retrieveDir, log) + .fold(w => throw w.resolveException, identity) + Defaults.makeScalaInstance( + scalaVersion.value, + Array.empty, + comp.toSeq, + Seq.empty, + state.value, + scalaInstanceTopLoader.value, + )}, + ) + + // ============================================================================================== + // =================================== BOOTSTRAPPED PROJECTS ==================================== + // ============================================================================================== + + lazy val `scala3-bootstrapped-new` = project + .aggregate(`scala3-interfaces`, `scala3-library-bootstrapped-new` , `scala-library-bootstrapped`, + `tasty-core-bootstrapped-new`, `scala3-compiler-bootstrapped-new`, `scala3-sbt-bridge-bootstrapped`, + `scala3-staging-new`, `scala3-tasty-inspector-new`) + .settings( + name := "scala3-bootstrapped", + moduleName := "scala3-bootstrapped", + version := dottyVersion, + // Nothing to be published by this project, it is only an aggregate + Compile / publishArtifact := false, + Test / publishArtifact := false, + // Nothing to be published by this project + publish / skip := true, + // Project specific target folder. sbt doesn't like having two projects using the same target folder + target := target.value / "scala3-bootstrapped", + ) + + /* Configuration of the org.scala-lang:scala3-sbt-bridge:*.**.**-bootstrapped project */ + lazy val `scala3-sbt-bridge-bootstrapped` = project.in(file("sbt-bridge")) + .dependsOn(`scala3-compiler-bootstrapped-new`) // TODO: Would this actually evict the reference compiler in scala-tool? + .settings( + name := "scala3-sbt-bridge-bootstrapped", + moduleName := "scala3-sbt-bridge", + version := dottyVersion, + versionScheme := Some("semver-spec"), + scalaVersion := referenceVersion, // nonbootstrapped artifacts are compiled with the reference compiler (already officially published) + crossPaths := false, // org.scala-lang:scala3-sbt-bridge doesn't have a crosspath + autoScalaLibrary := false, // do not add a dependency to stdlib, we depend transitively on the stdlib from `scala3-compiler-nonbootstrapped` + // Add the source directories for the sbt-bridge (boostrapped) + Compile / unmanagedSourceDirectories := Seq(baseDirectory.value / "src"), + Test / unmanagedSourceDirectories := Seq(baseDirectory.value / "test"), + Compile / resourceDirectory := baseDirectory.value / "resources", + // NOTE: The only difference here is that we drop `-Werror` and semanticDB for now + Compile / scalacOptions := Seq("-deprecation", "-feature", "-unchecked", "-encoding", "UTF8", "-language:implicitConversions"), + // Make sure that the produced artifacts have the minimum JVM version in the bytecode + Compile / javacOptions ++= Seq("--target", Versions.minimumJVMVersion), + Compile / scalacOptions ++= Seq("--java-output-version", Versions.minimumJVMVersion), + // Add all the project's external dependencies + libraryDependencies ++= Seq( + ("org.scala-sbt" %% "zinc-apiinfo" % "1.8.0" % Test).cross(CrossVersion.for3Use2_13), + "com.github.sbt" % "junit-interface" % "0.13.3" % Test, + ), + // Exclude the transitive dependencies from `zinc-apiinfo` that causes issues at the moment + excludeDependencies ++= Seq( + "org.scala-lang" % "scala-reflect", + "org.scala-lang" % "scala-compiler", + ), + // Packaging configuration of `scala3-sbt-bridge` + Compile / packageBin / publishArtifact := true, + Compile / packageDoc / publishArtifact := false, + Compile / packageSrc / publishArtifact := true, + // Only publish compilation artifacts, no test artifacts + Test / publishArtifact := false, + // non-bootstrapped stdlib is publishable (only locally) + publish / skip := false, + // Project specific target folder. sbt doesn't like having two projects using the same target folder + target := target.value / "scala3-sbt-bridge-bootstrapped", + // Configure to use the non-bootstrapped compiler + scalaInstance := { + val externalCompilerDeps = (`scala3-compiler-nonbootstrapped` / Compile / externalDependencyClasspath).value.map(_.data).toSet + + // IMPORTANT: We need to use actual jars to form the ScalaInstance and not + // just directories containing classfiles because sbt maintains a cache of + // compiler instances. This cache is invalidated based on timestamps + // however this is only implemented on jars, directories are never + // invalidated. + val tastyCore = (`tasty-core-nonbootstrapped` / Compile / packageBin).value + val scalaLibrary = (`scala-library-nonbootstrapped` / Compile / packageBin).value + val scala3Interfaces = (`scala3-interfaces` / Compile / packageBin).value + val scala3Compiler = (`scala3-compiler-nonbootstrapped` / Compile / packageBin).value + + Defaults.makeScalaInstance( + dottyNonBootstrappedVersion, + libraryJars = Array(scalaLibrary), + allCompilerJars = Seq(tastyCore, scala3Interfaces, scala3Compiler) ++ externalCompilerDeps, + allDocJars = Seq.empty, + state.value, + scalaInstanceTopLoader.value + ) + }, + scalaCompilerBridgeBinaryJar := { + Some((`scala3-sbt-bridge-nonbootstrapped` / Compile / packageBin).value) + }, + ) + + /* Configuration of the org.scala-lang:scala3-staging:*.**.**-bootstrapped project */ + lazy val `scala3-staging-new` = project.in(file("staging")) + // We want the compiler to be present in the compiler classpath when compiling this project but not + // when compiling a project that depends on scala3-staging (see sbt-test/sbt-dotty/quoted-example-project), + // but we always need it to be present on the JVM classpath at runtime. + .dependsOn(`scala3-compiler-bootstrapped-new` % "provided; compile->runtime; test->test") + .settings( + name := "scala3-staging", + moduleName := "scala3-staging", + version := dottyVersion, + versionScheme := Some("semver-spec"), + scalaVersion := referenceVersion, + crossPaths := true, // org.scala-lang:scala3-staging has a crosspath + autoScalaLibrary := false, // do not add a dependency to stdlib, we depend transitively on the stdlib from `scala3-compiler-bootstrapped` + // Add the source directories for the sbt-bridge (boostrapped) + Compile / unmanagedSourceDirectories := Seq(baseDirectory.value / "src"), + Test / unmanagedSourceDirectories := Seq(baseDirectory.value / "test"), + // NOTE: The only difference here is that we drop `-Werror` and semanticDB for now + Compile / scalacOptions := Seq("-deprecation", "-feature", "-unchecked", "-encoding", "UTF8", "-language:implicitConversions"), + // Make sure that the produced artifacts have the minimum JVM version in the bytecode + Compile / javacOptions ++= Seq("--target", Versions.minimumJVMVersion), + Compile / scalacOptions ++= Seq("--java-output-version", Versions.minimumJVMVersion), + // Packaging configuration of `scala3-staging` + Compile / packageBin / publishArtifact := true, + Compile / packageDoc / publishArtifact := false, + Compile / packageSrc / publishArtifact := true, + // Only publish compilation artifacts, no test artifacts + Test / publishArtifact := false, + publish / skip := false, + // Configure to use the non-bootstrapped compiler + scalaInstance := { + val externalCompilerDeps = (`scala3-compiler-nonbootstrapped` / Compile / externalDependencyClasspath).value.map(_.data).toSet + + // IMPORTANT: We need to use actual jars to form the ScalaInstance and not + // just directories containing classfiles because sbt maintains a cache of + // compiler instances. This cache is invalidated based on timestamps + // however this is only implemented on jars, directories are never + // invalidated. + val tastyCore = (`tasty-core-nonbootstrapped` / Compile / packageBin).value + val scalaLibrary = (`scala-library-nonbootstrapped` / Compile / packageBin).value + val scala3Interfaces = (`scala3-interfaces` / Compile / packageBin).value + val scala3Compiler = (`scala3-compiler-nonbootstrapped` / Compile / packageBin).value + + Defaults.makeScalaInstance( + dottyNonBootstrappedVersion, + libraryJars = Array(scalaLibrary), + allCompilerJars = Seq(tastyCore, scala3Interfaces, scala3Compiler) ++ externalCompilerDeps, + allDocJars = Seq.empty, + state.value, + scalaInstanceTopLoader.value + ) + }, + scalaCompilerBridgeBinaryJar := { + Some((`scala3-sbt-bridge-nonbootstrapped` / Compile / packageBin).value) + }, + ) + + /* Configuration of the org.scala-lang:scala3-tasty-inspector:*.**.**-bootstrapped project */ + lazy val `scala3-tasty-inspector-new` = project.in(file("tasty-inspector")) + // We want the compiler to be present in the compiler classpath when compiling this project but not + // when compiling a project that depends on scala3-tasty-inspector (see sbt-test/sbt-dotty/tasty-inspector-example-project), + // but we always need it to be present on the JVM classpath at runtime. + .dependsOn(`scala3-compiler-bootstrapped-new` % "provided; compile->runtime; test->test") + .settings( + name := "scala3-tasty-inspector", + moduleName := "scala3-tasty-inspector", + version := dottyVersion, + versionScheme := Some("semver-spec"), + scalaVersion := referenceVersion, + crossPaths := true, // org.scala-lang:scala3-tasty-inspector has a crosspath + autoScalaLibrary := false, // do not add a dependency to stdlib, we depend transitively on the stdlib from `scala3-compiler-bootstrapped` + // Add the source directories for the sbt-bridge (boostrapped) + Compile / unmanagedSourceDirectories := Seq(baseDirectory.value / "src"), + Test / unmanagedSourceDirectories := Seq(baseDirectory.value / "test"), + // NOTE: The only difference here is that we drop `-Werror` and semanticDB for now + Compile / scalacOptions := Seq("-deprecation", "-feature", "-unchecked", "-encoding", "UTF8", "-language:implicitConversions"), + // Make sure that the produced artifacts have the minimum JVM version in the bytecode + Compile / javacOptions ++= Seq("--target", Versions.minimumJVMVersion), + Compile / scalacOptions ++= Seq("--java-output-version", Versions.minimumJVMVersion), + // Packaging configuration of `scala3-staging` + Compile / packageBin / publishArtifact := true, + Compile / packageDoc / publishArtifact := false, + Compile / packageSrc / publishArtifact := true, + // Only publish compilation artifacts, no test artifacts + Test / publishArtifact := false, + publish / skip := false, + // Configure to use the non-bootstrapped compiler + scalaInstance := { + val externalCompilerDeps = (`scala3-compiler-nonbootstrapped` / Compile / externalDependencyClasspath).value.map(_.data).toSet + + // IMPORTANT: We need to use actual jars to form the ScalaInstance and not + // just directories containing classfiles because sbt maintains a cache of + // compiler instances. This cache is invalidated based on timestamps + // however this is only implemented on jars, directories are never + // invalidated. + val tastyCore = (`tasty-core-nonbootstrapped` / Compile / packageBin).value + val scalaLibrary = (`scala-library-nonbootstrapped` / Compile / packageBin).value + val scala3Interfaces = (`scala3-interfaces` / Compile / packageBin).value + val scala3Compiler = (`scala3-compiler-nonbootstrapped` / Compile / packageBin).value + + Defaults.makeScalaInstance( + dottyNonBootstrappedVersion, + libraryJars = Array(scalaLibrary), + allCompilerJars = Seq(tastyCore, scala3Interfaces, scala3Compiler) ++ externalCompilerDeps, + allDocJars = Seq.empty, + state.value, + scalaInstanceTopLoader.value + ) + }, + scalaCompilerBridgeBinaryJar := { + Some((`scala3-sbt-bridge-nonbootstrapped` / Compile / packageBin).value) + }, + ) + + // ============================================================================================== + // =================================== SCALA STANDARD LIBRARY =================================== + // ============================================================================================== + + /* Configuration of the org.scala-lang:scala-library:*.**.**-nonbootstrapped project */ + lazy val `scala-library-nonbootstrapped` = project.in(file("library")) + .enablePlugins(ScalaLibraryPlugin) + .settings( + name := "scala-library-nonbootstrapped", + moduleName := "scala-library", + version := dottyNonBootstrappedVersion, + versionScheme := Some("semver-spec"), + scalaVersion := referenceVersion, // nonbootstrapped artifacts are compiled with the reference compiler (already officially published) + crossPaths := false, // org.scala-lang:scala-library doesn't have a crosspath + autoScalaLibrary := false, // do not add a dependency to stdlib + // Add the source directories for the stdlib (non-boostrapped) + Compile / unmanagedSourceDirectories := Seq(baseDirectory.value / "src"), + Compile / unmanagedSourceDirectories += baseDirectory.value / "src-non-bootstrapped", + // NOTE: The only difference here is that we drop `-Werror` and semanticDB for now + Compile / scalacOptions := Seq("-deprecation", "-feature", "-unchecked", "-encoding", "UTF8", "-language:implicitConversions"), + Compile / scalacOptions += "-Yno-stdlib-patches", + 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(_.getCanonicalPath).distinct.mkString(File.pathSeparator), + ), + // Make sure that the produced artifacts have the minimum JVM version in the bytecode + Compile / javacOptions ++= Seq("--target", Versions.minimumJVMVersion), + Compile / scalacOptions ++= Seq("--java-output-version", Versions.minimumJVMVersion), + // Packaging configuration of the stdlib + Compile / packageBin / publishArtifact := true, + Compile / packageDoc / publishArtifact := false, + Compile / packageSrc / publishArtifact := true, + // Only publish compilation artifacts, no test artifacts + Test / publishArtifact := false, + // non-bootstrapped stdlib is publishable (only locally) + publish / skip := false, + // Project specific target folder. sbt doesn't like having two projects using the same target folder + target := target.value / "scala-library-nonbootstrapped", + ) + + /* Configuration of the org.scala-lang:scala3-library_3:*.**.**-nonbootstrapped project */ + lazy val `scala3-library-nonbootstrapped` = project.in(file("library")) + .dependsOn(`scala-library-nonbootstrapped`) + .settings( + name := "scala3-library-nonbootstrapped", + moduleName := "scala3-library", + version := dottyNonBootstrappedVersion, + versionScheme := Some("semver-spec"), + scalaVersion := referenceVersion, // nonbootstrapped artifacts are compiled with the reference compiler (already officially published) + crossPaths := true, // org.scala-lang:scala3-library has a crosspath + // Do not depend on the `org.scala-lang:scala3-library` automatically, we manually depend on `scala-library-nonbootstrapped` + autoScalaLibrary := false, + // Drop all the scala tools in this project, so we can never generate any bytecode, or documentation + managedScalaInstance := false, + // This Project only has a dependency to `org.scala-lang:scala-library:*.**.**-nonbootstrapped` + Compile / sources := Seq(), + Compile / resources := Seq(), + Test / sources := Seq(), + Test / resources := Seq(), + // Bridge the common task to call the ones of the actual library project + Compile / compile := (`scala-library-nonbootstrapped` / Compile / compile).value, + Compile / doc := (`scala-library-nonbootstrapped` / Compile / doc).value, + Compile / run := (`scala-library-nonbootstrapped` / Compile / run).evaluated, + Test / compile := (`scala-library-nonbootstrapped` / Test / compile).value, + Test / doc := (`scala-library-nonbootstrapped` / Test / doc).value, + Test / run := (`scala-library-nonbootstrapped` / Test / run).evaluated, + Test / test := (`scala-library-nonbootstrapped` / Test / test).value, + // Packaging configuration of the stdlib + Compile / packageBin / publishArtifact := true, + Compile / packageDoc / publishArtifact := false, + Compile / packageSrc / publishArtifact := true, + // Only publish compilation artifacts, no test artifacts + Test / publishArtifact := false, + // Do not allow to publish this project for now + publish / skip := false, + // Project specific target folder. sbt doesn't like having two projects using the same target folder + target := target.value / "scala3-library-nonbootstrapped", + ) + + /* Configuration of the org.scala-lang:scala-library:*.**.**-bootstrapped project */ + lazy val `scala-library-bootstrapped` = project.in(file("library")) + .enablePlugins(ScalaLibraryPlugin) + .settings( + name := "scala-library-bootstrapped", + moduleName := "scala-library", + version := dottyVersion, + versionScheme := Some("semver-spec"), + // sbt defaults to scala 2.12.x and metals will report issues as it doesn't consider the project a scala 3 project + // (not the actual version we use to compile the project) + scalaVersion := referenceVersion, + crossPaths := false, // org.scala-lang:scala-library doesn't have a crosspath + // Add the source directories for the stdlib (non-boostrapped) + Compile / unmanagedSourceDirectories := Seq(baseDirectory.value / "src"), + Compile / unmanagedSourceDirectories += baseDirectory.value / "src-bootstrapped", + // NOTE: The only difference here is that we drop `-Werror` and semanticDB for now + Compile / scalacOptions := Seq("-deprecation", "-feature", "-unchecked", "-encoding", "UTF8", "-language:implicitConversions"), + Compile / scalacOptions += "-Yno-stdlib-patches", + 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(_.getCanonicalPath).distinct.mkString(File.pathSeparator), + ), + // Make sure that the produced artifacts have the minimum JVM version in the bytecode + Compile / javacOptions ++= Seq("--target", Versions.minimumJVMVersion), + Compile / scalacOptions ++= Seq("--java-output-version", Versions.minimumJVMVersion), + // Packaging configuration of the stdlib + Compile / packageBin / publishArtifact := true, + Compile / packageDoc / publishArtifact := false, + Compile / packageSrc / publishArtifact := true, + // Only publish compilation artifacts, no test artifacts + Test / publishArtifact := false, + // Do not allow to publish this project for now + publish / skip := false, + // Project specific target folder. sbt doesn't like having two projects using the same target folder + target := target.value / "scala-library-bootstrapped", + // we need to have the `scala-library` artifact in the classpath for `ScalaLibraryPlugin` to work + // this was the only way to not get the artifact evicted by sbt. Even a custom configuration didn't work + // NOTE: true is the default value, just making things clearer here + managedScalaInstance := true, + // Configure the nonbootstrapped compiler + scalaInstance := { + val externalCompilerDeps = (`scala3-compiler-nonbootstrapped` / Compile / externalDependencyClasspath).value.map(_.data).toSet + + // IMPORTANT: We need to use actual jars to form the ScalaInstance and not + // just directories containing classfiles because sbt maintains a cache of + // compiler instances. This cache is invalidated based on timestamps + // however this is only implemented on jars, directories are never + // invalidated. + val tastyCore = (`tasty-core-nonbootstrapped` / Compile / packageBin).value + val scalaLibrary = (`scala-library-nonbootstrapped` / Compile / packageBin).value + val scala3Interfaces = (`scala3-interfaces` / Compile / packageBin).value + val scala3Compiler = (`scala3-compiler-nonbootstrapped` / Compile / packageBin).value + + Defaults.makeScalaInstance( + dottyNonBootstrappedVersion, + libraryJars = Array(scalaLibrary), + allCompilerJars = Seq(tastyCore, scala3Interfaces, scala3Compiler) ++ externalCompilerDeps, + allDocJars = Seq.empty, + state.value, + scalaInstanceTopLoader.value + ) + }, + scalaCompilerBridgeBinaryJar := { + Some((`scala3-sbt-bridge-nonbootstrapped` / Compile / packageBin).value) + }, + ) + + /* Configuration of the org.scala-lang:scala3-library_3:*.**.**-bootstrapped project */ + lazy val `scala3-library-bootstrapped-new` = project.in(file("library")) + .dependsOn(`scala-library-bootstrapped`) + .settings( + name := "scala3-library-bootstrapped", + moduleName := "scala3-library", + version := dottyVersion, + versionScheme := Some("semver-spec"), + // sbt defaults to scala 2.12.x and metals will report issues as it doesn't consider the project a scala 3 project + // (not the actual version we use to compile the project) + scalaVersion := referenceVersion, + crossPaths := true, // org.scala-lang:scala3-library has a crosspath + // Do not depend on the `org.scala-lang:scala3-library` automatically, we manually depend on `scala-library-bootstrapped` + autoScalaLibrary := false, + // Drop all the scala tools in this project, so we can never generate any bytecode, or documentation + managedScalaInstance := false, + // This Project only has a dependency to `org.scala-lang:scala-library:*.**.**-bootstrapped` + Compile / sources := Seq(), + Compile / resources := Seq(), + Test / sources := Seq(), + Test / resources := Seq(), + // Bridge the common task to call the ones of the actual library project + Compile / compile := (`scala-library-bootstrapped` / Compile / compile).value, + Compile / doc := (`scala-library-bootstrapped` / Compile / doc).value, + Compile / run := (`scala-library-bootstrapped` / Compile / run).evaluated, + Test / compile := (`scala-library-bootstrapped` / Test / compile).value, + Test / doc := (`scala-library-bootstrapped` / Test / doc).value, + Test / run := (`scala-library-bootstrapped` / Test / run).evaluated, + // Packaging configuration of the stdlib + Compile / packageBin / publishArtifact := true, + Compile / packageDoc / publishArtifact := false, + Compile / packageSrc / publishArtifact := true, + // Only publish compilation artifacts, no test artifacts + Test / publishArtifact := false, + // Do not allow to publish this project for now + publish / skip := false, + // Project specific target folder. sbt doesn't like having two projects using the same target folder + target := target.value / "scala3-library-bootstrapped", + ) + + // ============================================================================================== + // ===================================== TASTY CORE LIBRARY ===================================== + // ============================================================================================== + + /* Configuration of the org.scala-lang:tasty-core_3:*.**.**-nonbootstrapped project */ + lazy val `tasty-core-nonbootstrapped` = project.in(file("tasty")) + .dependsOn(`scala3-library-nonbootstrapped`) + .settings( + name := "tasty-core-nonbootstrapped", + moduleName := "tasty-core", + version := dottyNonBootstrappedVersion, + versionScheme := Some("semver-spec"), + scalaVersion := referenceVersion, // nonbootstrapped artifacts are compiled with the reference compiler (already officially published) + crossPaths := true, // org.scala-lang:tasty-core has a crosspath + // sbt shouldn't add stdlib automatically, we depend on `scala3-library-nonbootstrapped` + autoScalaLibrary := false, + // Add the source directories for the stdlib (non-boostrapped) + Compile / unmanagedSourceDirectories := Seq(baseDirectory.value / "src"), + Test / unmanagedSourceDirectories := Seq(baseDirectory.value / "test"), + Compile / unmanagedSourceDirectories += baseDirectory.value / "src-non-bootstrapped", + // NOTE: The only difference here is that we drop `-Werror` and semanticDB for now + Compile / scalacOptions := Seq("-deprecation", "-feature", "-unchecked", "-encoding", "UTF8", "-language:implicitConversions"), + // Make sure that the produced artifacts have the minimum JVM version in the bytecode + Compile / javacOptions ++= Seq("--target", Versions.minimumJVMVersion), + Compile / scalacOptions ++= Seq("--java-output-version", Versions.minimumJVMVersion), + // Add all the project's external dependencies + libraryDependencies ++= Seq( + "com.github.sbt" % "junit-interface" % "0.13.3" % Test, + ), + // Packaging configuration of the stdlib + Compile / packageBin / publishArtifact := true, + Compile / packageDoc / publishArtifact := false, + Compile / packageSrc / publishArtifact := true, + // Only publish compilation artifacts, no test artifacts + Test / publishArtifact := false, + // Do not allow to publish this project for now + publish / skip := false, + // Project specific target folder. sbt doesn't like having two projects using the same target folder + target := target.value / "tasty-core-nonbootstrapped", + // sbt adds all the projects to scala-tool config which breaks building the scalaInstance + // as a workaround, I build it manually by only adding the compiler + scalaInstance := { + val lm = dependencyResolution.value + val log = streams.value.log + val retrieveDir = streams.value.cacheDirectory / "scala3-compiler" / scalaVersion.value + val comp = lm.retrieve("org.scala-lang" % "scala3-compiler_3" % + scalaVersion.value, scalaModuleInfo = None, retrieveDir, log) + .fold(w => throw w.resolveException, identity) + Defaults.makeScalaInstance( + scalaVersion.value, + Array.empty, + comp.toSeq, + Seq.empty, + state.value, + scalaInstanceTopLoader.value, + )}, + // Add configuration of the test + Test / envVars ++= Map( + "EXPECTED_TASTY_VERSION" -> expectedTastyVersion, + ), + + ) + + /* Configuration of the org.scala-lang:tasty-core_3:*.**.**-bootstrapped project */ + lazy val `tasty-core-bootstrapped-new` = project.in(file("tasty")) + .dependsOn(`scala3-library-bootstrapped-new`) + .settings( + name := "tasty-core-bootstrapped", + moduleName := "tasty-core", + version := dottyVersion, + versionScheme := Some("semver-spec"), + scalaVersion := referenceVersion, // nonbootstrapped artifacts are compiled with the reference compiler (already officially published) + crossPaths := true, // org.scala-lang:tasty-core has a crosspath + // sbt shouldn't add stdlib automatically, we depend on `scala3-library-nonbootstrapped` + autoScalaLibrary := false, + // Add the source directories for the stdlib (non-boostrapped) + Compile / unmanagedSourceDirectories := Seq(baseDirectory.value / "src"), + Test / unmanagedSourceDirectories := Seq(baseDirectory.value / "test"), + Compile / unmanagedSourceDirectories += baseDirectory.value / "src-bootstrapped", + // NOTE: The only difference here is that we drop `-Werror` and semanticDB for now + Compile / scalacOptions := Seq("-deprecation", "-feature", "-unchecked", "-encoding", "UTF8", "-language:implicitConversions"), + // Make sure that the produced artifacts have the minimum JVM version in the bytecode + Compile / javacOptions ++= Seq("--target", Versions.minimumJVMVersion), + Compile / scalacOptions ++= Seq("--java-output-version", Versions.minimumJVMVersion), + // Add all the project's external dependencies + libraryDependencies ++= Seq( + "com.github.sbt" % "junit-interface" % "0.13.3" % Test, + ), + // Packaging configuration of the stdlib + Compile / packageBin / publishArtifact := true, + Compile / packageDoc / publishArtifact := false, + Compile / packageSrc / publishArtifact := true, + // Only publish compilation artifacts, no test artifacts + Test / publishArtifact := false, + // Do not allow to publish this project for now + publish / skip := false, + // Project specific target folder. sbt doesn't like having two projects using the same target folder + target := target.value / "tasty-core-bootstrapped", + // Configure to use the non-bootstrapped compiler + scalaInstance := { + val externalCompilerDeps = (`scala3-compiler-nonbootstrapped` / Compile / externalDependencyClasspath).value.map(_.data).toSet + + // IMPORTANT: We need to use actual jars to form the ScalaInstance and not + // just directories containing classfiles because sbt maintains a cache of + // compiler instances. This cache is invalidated based on timestamps + // however this is only implemented on jars, directories are never + // invalidated. + val tastyCore = (`tasty-core-nonbootstrapped` / Compile / packageBin).value + val scalaLibrary = (`scala-library-nonbootstrapped` / Compile / packageBin).value + val scala3Interfaces = (`scala3-interfaces` / Compile / packageBin).value + val scala3Compiler = (`scala3-compiler-nonbootstrapped` / Compile / packageBin).value + + Defaults.makeScalaInstance( + dottyNonBootstrappedVersion, + libraryJars = Array(scalaLibrary), + allCompilerJars = Seq(tastyCore, scala3Interfaces, scala3Compiler) ++ externalCompilerDeps, + allDocJars = Seq.empty, + state.value, + scalaInstanceTopLoader.value + ) + }, + scalaCompilerBridgeBinaryJar := { + Some((`scala3-sbt-bridge-nonbootstrapped` / Compile / packageBin).value) + }, + // Add configuration of the test + Test / envVars ++= Map( + "EXPECTED_TASTY_VERSION" -> expectedTastyVersion, + ), + ) + + // ============================================================================================== + // ======================================= SCALA COMPILER ======================================= + // ============================================================================================== + + /* Configuration of the org.scala-lang:scala3-compiler_3:*.**.**-nonbootstrapped project */ + lazy val `scala3-compiler-nonbootstrapped` = project.in(file("compiler")) + .dependsOn(`scala3-interfaces`, `tasty-core-nonbootstrapped`, `scala3-library-nonbootstrapped`) + .settings( + name := "scala3-compiler-nonbootstrapped", + moduleName := "scala3-compiler", + version := dottyNonBootstrappedVersion, + versionScheme := Some("semver-spec"), + scalaVersion := referenceVersion, // nonbootstrapped artifacts are compiled with the reference compiler (already officially published) + crossPaths := true, // org.scala-lang:scala3-compiler has a crosspath + // sbt shouldn't add stdlib automatically, we depend on `scala3-library-nonbootstrapped` + autoScalaLibrary := false, + // Add the source directories for the stdlib (non-boostrapped) + Compile / unmanagedSourceDirectories := Seq(baseDirectory.value / "src"), + Compile / unmanagedSourceDirectories += baseDirectory.value / "src-non-bootstrapped", + // All the dependencies needed by the compiler + libraryDependencies ++= Seq( + "org.scala-lang.modules" % "scala-asm" % "9.8.0-scala-1", + Dependencies.compilerInterface, + "org.jline" % "jline-reader" % "3.29.0", + "org.jline" % "jline-terminal" % "3.29.0", + "org.jline" % "jline-terminal-jni" % "3.29.0", + //("io.get-coursier" %% "coursier" % "2.0.16" % Test).cross(CrossVersion.for3Use2_13), + ), + // NOTE: The only difference here is that we drop `-Werror` and semanticDB for now + Compile / scalacOptions := Seq("-deprecation", "-feature", "-unchecked", "-encoding", "UTF8", "-language:implicitConversions"), + // TODO: Enable these flags when the new stdlib is explicitelly null checked + //Compile / scalacOptions ++= Seq("-Yexplicit-nulls", "-Wsafe-init"), + // Make sure that the produced artifacts have the minimum JVM version in the bytecode + Compile / javacOptions ++= Seq("--target", Versions.minimumJVMVersion), + Compile / scalacOptions ++= Seq("--java-output-version", Versions.minimumJVMVersion), + // Packaging configuration of the stdlib + Compile / packageBin / publishArtifact := true, + Compile / packageDoc / publishArtifact := false, + Compile / packageSrc / publishArtifact := true, + // Only publish compilation artifacts, no test artifacts + Test / publishArtifact := false, + // Do not allow to publish this project for now + publish / skip := false, + // Project specific target folder. sbt doesn't like having two projects using the same target folder + target := target.value / "scala3-compiler-nonbootstrapped", + // Generate compiler.properties, used by sbt + Compile / resourceGenerators += Def.task { + import java.util._ + import java.text._ + val file = (Compile / resourceManaged).value / "compiler.properties" + val dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss") + dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")) + val contents = //2.11.11.v20170413-090219-8a413ba7cc + s"""version.number=${version.value} + |maven.version.number=${version.value} + |git.hash=${VersionUtil.gitHash} + |copyright.string=Copyright 2002-$currentYear, LAMP/EPFL + """.stripMargin + + if (!(file.exists && IO.read(file) == contents)) { + IO.write(file, contents) + } + + Seq(file) + }.taskValue, + // sbt adds all the projects to scala-tool config which breaks building the scalaInstance + // as a workaround, I build it manually by only adding the compiler + scalaInstance := { + val lm = dependencyResolution.value + val log = streams.value.log + val retrieveDir = streams.value.cacheDirectory / "scala3-compiler" / scalaVersion.value + val comp = lm.retrieve("org.scala-lang" % "scala3-compiler_3" % + scalaVersion.value, scalaModuleInfo = None, retrieveDir, log) + .fold(w => throw w.resolveException, identity) + Defaults.makeScalaInstance( + scalaVersion.value, + Array.empty, + comp.toSeq, + Seq.empty, + state.value, + scalaInstanceTopLoader.value, + )}, + /* Add the sources of scalajs-ir. + * To guarantee that dotty can bootstrap without depending on a version + * of scalajs-ir built with a different Scala compiler, we add its + * sources instead of depending on the binaries. + */ + ivyConfigurations += SourceDeps.hide, + transitiveClassifiers := Seq("sources"), + libraryDependencies += + ("org.scala-js" %% "scalajs-ir" % scalaJSVersion % "sourcedeps").cross(CrossVersion.for3Use2_13), + Compile / sourceGenerators += Def.task { + val s = streams.value + val cacheDir = s.cacheDirectory + val trgDir = (Compile / sourceManaged).value / "scalajs-ir-src" + + val report = updateClassifiers.value + val scalaJSIRSourcesJar = report.select( + configuration = configurationFilter("sourcedeps"), + module = (_: ModuleID).name.startsWith("scalajs-ir_"), + artifact = artifactFilter(`type` = "src")).headOption.getOrElse { + sys.error(s"Could not fetch scalajs-ir sources") + } + + FileFunction.cached(cacheDir / s"fetchScalaJSIRSource", + FilesInfo.lastModified, FilesInfo.exists) { dependencies => + s.log.info(s"Unpacking scalajs-ir sources to $trgDir...") + if (trgDir.exists) + IO.delete(trgDir) + IO.createDirectory(trgDir) + IO.unzip(scalaJSIRSourcesJar, trgDir) + + val sjsSources = (trgDir ** "*.scala").get.toSet + sjsSources.foreach(f => { + val lines = IO.readLines(f) + val linesWithPackage = replacePackage(lines) { + case "org.scalajs.ir" => "dotty.tools.sjs.ir" + } + IO.writeLines(f, insertUnsafeNullsImport(linesWithPackage)) + }) + sjsSources + } (Set(scalaJSIRSourcesJar)).toSeq + }.taskValue, + ) + + /* Configuration of the org.scala-lang:scala3-compiler_3:*.**.**-bootstrapped project */ + lazy val `scala3-compiler-bootstrapped-new` = project.in(file("compiler")) + .dependsOn(`scala3-interfaces`, `tasty-core-bootstrapped-new`, `scala3-library-bootstrapped-new`) + .settings( + name := "scala3-compiler-bootstrapped", + moduleName := "scala3-compiler", + version := dottyVersion, + versionScheme := Some("semver-spec"), + scalaVersion := referenceVersion, // nonbootstrapped artifacts are compiled with the reference compiler (already officially published) + crossPaths := true, // org.scala-lang:scala3-compiler has a crosspath + // sbt shouldn't add stdlib automatically, we depend on `scala3-library-nonbootstrapped` + autoScalaLibrary := false, + // Add the source directories for the stdlib (non-boostrapped) + Compile / unmanagedSourceDirectories := Seq(baseDirectory.value / "src"), + Compile / unmanagedSourceDirectories += baseDirectory.value / "src-bootstrapped", + // All the dependencies needed by the compiler + libraryDependencies ++= Seq( + "org.scala-lang.modules" % "scala-asm" % "9.8.0-scala-1", + Dependencies.compilerInterface, + "org.jline" % "jline-reader" % "3.29.0", + "org.jline" % "jline-terminal" % "3.29.0", + "org.jline" % "jline-terminal-jni" % "3.29.0", + //("io.get-coursier" %% "coursier" % "2.0.16" % Test).cross(CrossVersion.for3Use2_13), + ), + // NOTE: The only difference here is that we drop `-Werror` and semanticDB for now + Compile / scalacOptions := Seq("-deprecation", "-feature", "-unchecked", "-encoding", "UTF8", "-language:implicitConversions"), + // TODO: Enable these flags when the new stdlib is explicitelly null checked + //Compile / scalacOptions ++= Seq("-Yexplicit-nulls", "-Wsafe-init"), + // Make sure that the produced artifacts have the minimum JVM version in the bytecode + Compile / javacOptions ++= Seq("--target", Versions.minimumJVMVersion), + Compile / scalacOptions ++= Seq("--java-output-version", Versions.minimumJVMVersion), + // Packaging configuration of the stdlib + Compile / packageBin / publishArtifact := true, + Compile / packageDoc / publishArtifact := false, + Compile / packageSrc / publishArtifact := true, + // Only publish compilation artifacts, no test artifacts + Test / publishArtifact := false, + // Do not allow to publish this project for now + publish / skip := false, + // Project specific target folder. sbt doesn't like having two projects using the same target folder + target := target.value / "scala3-compiler-bootstrapped", + // Generate compiler.properties, used by sbt + Compile / resourceGenerators += Def.task { + import java.util._ + import java.text._ + val file = (Compile / resourceManaged).value / "compiler.properties" + val dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss") + dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")) + val contents = //2.11.11.v20170413-090219-8a413ba7cc + s"""version.number=${version.value} + |maven.version.number=${version.value} + |git.hash=${VersionUtil.gitHash} + |copyright.string=Copyright 2002-$currentYear, LAMP/EPFL + """.stripMargin + + if (!(file.exists && IO.read(file) == contents)) { + IO.write(file, contents) + } + + Seq(file) + }.taskValue, + // Configure to use the non-bootstrapped compiler + scalaInstance := { + val externalCompilerDeps = (`scala3-compiler-nonbootstrapped` / Compile / externalDependencyClasspath).value.map(_.data).toSet + + // IMPORTANT: We need to use actual jars to form the ScalaInstance and not + // just directories containing classfiles because sbt maintains a cache of + // compiler instances. This cache is invalidated based on timestamps + // however this is only implemented on jars, directories are never + // invalidated. + val tastyCore = (`tasty-core-nonbootstrapped` / Compile / packageBin).value + val scalaLibrary = (`scala-library-nonbootstrapped` / Compile / packageBin).value + val scala3Interfaces = (`scala3-interfaces` / Compile / packageBin).value + val scala3Compiler = (`scala3-compiler-nonbootstrapped` / Compile / packageBin).value + + Defaults.makeScalaInstance( + dottyNonBootstrappedVersion, + libraryJars = Array(scalaLibrary), + allCompilerJars = Seq(tastyCore, scala3Interfaces, scala3Compiler) ++ externalCompilerDeps, + allDocJars = Seq.empty, + state.value, + scalaInstanceTopLoader.value + ) + }, + scalaCompilerBridgeBinaryJar := { + Some((`scala3-sbt-bridge-nonbootstrapped` / Compile / packageBin).value) + }, + /* Add the sources of scalajs-ir. + * To guarantee that dotty can bootstrap without depending on a version + * of scalajs-ir built with a different Scala compiler, we add its + * sources instead of depending on the binaries. + */ + ivyConfigurations += SourceDeps.hide, + transitiveClassifiers := Seq("sources"), + libraryDependencies += + ("org.scala-js" %% "scalajs-ir" % scalaJSVersion % "sourcedeps").cross(CrossVersion.for3Use2_13), + Compile / sourceGenerators += Def.task { + val s = streams.value + val cacheDir = s.cacheDirectory + val trgDir = (Compile / sourceManaged).value / "scalajs-ir-src" + + val report = updateClassifiers.value + val scalaJSIRSourcesJar = report.select( + configuration = configurationFilter("sourcedeps"), + module = (_: ModuleID).name.startsWith("scalajs-ir_"), + artifact = artifactFilter(`type` = "src")).headOption.getOrElse { + sys.error(s"Could not fetch scalajs-ir sources") + } + + FileFunction.cached(cacheDir / s"fetchScalaJSIRSource", + FilesInfo.lastModified, FilesInfo.exists) { dependencies => + s.log.info(s"Unpacking scalajs-ir sources to $trgDir...") + if (trgDir.exists) + IO.delete(trgDir) + IO.createDirectory(trgDir) + IO.unzip(scalaJSIRSourcesJar, trgDir) + + val sjsSources = (trgDir ** "*.scala").get.toSet + sjsSources.foreach(f => { + val lines = IO.readLines(f) + val linesWithPackage = replacePackage(lines) { + case "org.scalajs.ir" => "dotty.tools.sjs.ir" + } + IO.writeLines(f, insertUnsafeNullsImport(linesWithPackage)) + }) + sjsSources + } (Set(scalaJSIRSourcesJar)).toSeq + }.taskValue, + ) def dottyLibrary(implicit mode: Mode): Project = mode match { case NonBootstrapped => `scala3-library` @@ -1163,8 +2243,16 @@ object Build { asDottyLibrary(Bootstrapped). enablePlugins(DottyJSPlugin). settings( + commonBootstrappedSettings, libraryDependencies += ("org.scala-js" %% "scalajs-library" % scalaJSVersion).cross(CrossVersion.for3Use2_13), + // NOTE: Until 3.8.0, we pin the source files to be used by the scala3 library + Compile / sources := (`scala3-library-bootstrapped` / Compile / sources).value, + Compile / sources ++= Seq( + file(s"${baseDirectory.value}/src/scala/scalajs/js/internal/UnitOps.scala"), + file(s"${baseDirectory.value}/src/scala/scalajs/runtime/AnonFunctionXXL.scala"), + ), + // NOTE: We keep this so that the mappings are correct when packaging Compile / unmanagedSourceDirectories ++= (`scala3-library-bootstrapped` / Compile / unmanagedSourceDirectories).value, @@ -1239,7 +2327,7 @@ object Build { settings(scala2LibraryBootstrappedSettings). settings( moduleName := "scala2-library-cc", - scalacOptions += "-Ycheck:all", + scalacOptions += "-language:experimental.separationChecking" // for separation checking ) lazy val scala2LibraryBootstrappedSettings = Seq( @@ -1499,7 +2587,7 @@ object Build { BuildInfoPlugin.buildInfoDefaultSettings lazy val presentationCompilerSettings = { - val mtagsVersion = "1.5.1" + val mtagsVersion = "1.6.2" Seq( libraryDependencies ++= Seq( "org.lz4" % "lz4-java" % "1.8.0", @@ -1513,6 +2601,10 @@ object Build { ivyConfigurations += SourceDeps.hide, transitiveClassifiers := Seq("sources"), scalacOptions ++= Seq("-source", "3.3"), // To avoid fatal migration warnings + publishLocal := publishLocal.dependsOn( // It is best to publish all together. It is not rare to make changes in both compiler / presentation compiler and it can get misaligned + `scala3-compiler-bootstrapped` / publishLocal, + `scala3-library-bootstrapped` / publishLocal, + ).value, Compile / scalacOptions ++= Seq("-Yexplicit-nulls", "-Wsafe-init"), Compile / sourceGenerators += Def.task { val s = streams.value @@ -1538,7 +2630,7 @@ object Build { val mtagsSharedSources = (targetDir ** "*.scala").get.toSet mtagsSharedSources.foreach(f => { val lines = IO.readLines(f) - val substitutions = (replaceProtobuf(_)) andThen (insertUnsafeNullsImport(_)) + val substitutions = (replaceProtobuf(_)) andThen (insertUnsafeNullsImport(_)) andThen (replaceJDKIncompatibilities(_)) IO.writeLines(f, substitutions(lines)) }) mtagsSharedSources @@ -1599,6 +2691,7 @@ object Build { enablePlugins(DottyJSPlugin). dependsOn(`scala3-library-bootstrappedJS`). settings( + commonBootstrappedSettings, // Required to run Scala.js tests. Test / fork := false, @@ -1616,6 +2709,7 @@ object Build { enablePlugins(DottyJSPlugin). dependsOn(`scala3-library-bootstrappedJS`). settings( + commonBootstrappedSettings, bspEnabled := false, scalacOptions --= Seq("-Werror", "-deprecation"), @@ -1859,12 +2953,15 @@ object Build { lazy val `scaladoc-js-common` = project.in(file("scaladoc-js/common")). enablePlugins(DottyJSPlugin). dependsOn(`scala3-library-bootstrappedJS`). - settings(libraryDependencies += ("org.scala-js" %%% "scalajs-dom" % "2.8.0")) + settings( + commonBootstrappedSettings, + libraryDependencies += ("org.scala-js" %%% "scalajs-dom" % "2.8.0")) lazy val `scaladoc-js-main` = project.in(file("scaladoc-js/main")). enablePlugins(DottyJSPlugin). dependsOn(`scaladoc-js-common`). settings( + commonBootstrappedSettings, scalaJSUseMainModuleInitializer := true, Test / fork := false ) @@ -1873,6 +2970,7 @@ object Build { enablePlugins(DottyJSPlugin). dependsOn(`scaladoc-js-common`). settings( + commonBootstrappedSettings, Test / fork := false, scalaJSUseMainModuleInitializer := true, libraryDependencies += ("org.scala-js" %%% "scalajs-dom" % "2.8.0") @@ -2190,7 +3288,11 @@ object Build { lazy val publishSettings = Seq( publishMavenStyle := true, isSnapshot := version.value.contains("SNAPSHOT"), - publishTo := sonatypePublishToBundle.value, + publishTo := { + val centralSnapshots = "https://central.sonatype.com/repository/maven-snapshots/" + if (isSnapshot.value) Some("central-snapshots" at centralSnapshots) + else localStaging.value + }, publishConfiguration ~= (_.withOverwrite(true)), publishLocalConfiguration ~= (_.withOverwrite(true)), projectID ~= {id => diff --git a/project/DottyJSPlugin.scala b/project/DottyJSPlugin.scala new file mode 100644 index 000000000000..89a876c21e66 --- /dev/null +++ b/project/DottyJSPlugin.scala @@ -0,0 +1,58 @@ +package dotty.tools.sbtplugin + +import sbt.* +import sbt.Keys.* + +import org.scalajs.sbtplugin.ScalaJSPlugin +import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ + +import org.scalajs.linker.interface.StandardConfig + +object DottyJSPlugin extends AutoPlugin { + + object autoImport { + val switchToESModules: StandardConfig => StandardConfig = + config => config.withModuleKind(ModuleKind.ESModule) + } + + val writePackageJSON = taskKey[Unit]( + "Write package.json to configure module type for Node.js") + + override def requires: Plugins = ScalaJSPlugin + + override def projectSettings: Seq[Setting[_]] = Def.settings( + + /* #11709 Remove the dependency on scala3-library that ScalaJSPlugin adds. + * Instead, in this build, we use `.dependsOn` relationships to depend on + * the appropriate, locally-defined, scala3-library-bootstrappedJS. + */ + libraryDependencies ~= { + _.filter(!_.name.startsWith("scala3-library_sjs1")) + }, + + // Replace the JVM JUnit dependency by the Scala.js one + libraryDependencies ~= { + _.filter(!_.name.startsWith("junit-interface")) + }, + libraryDependencies += + ("org.scala-js" %% "scalajs-junit-test-runtime" % scalaJSVersion % "test").cross(CrossVersion.for3Use2_13), + + // Typecheck the Scala.js IR found on the classpath + scalaJSLinkerConfig ~= (_.withCheckIR(true)), + + Compile / jsEnvInput := (Compile / jsEnvInput).dependsOn(writePackageJSON).value, + Test / jsEnvInput := (Test / jsEnvInput).dependsOn(writePackageJSON).value, + + writePackageJSON := { + val packageType = scalaJSLinkerConfig.value.moduleKind match { + case ModuleKind.NoModule => "commonjs" + case ModuleKind.CommonJSModule => "commonjs" + case ModuleKind.ESModule => "module" + } + + val path = target.value / "package.json" + + IO.write(path, s"""{"type": "$packageType"}\n""") + }, + ) +} diff --git a/project/GithubEnv.scala b/project/GithubEnv.scala deleted file mode 100644 index 7e629d53f3a7..000000000000 --- a/project/GithubEnv.scala +++ /dev/null @@ -1,30 +0,0 @@ -import scala.util.Properties -import sbt.url -import java.net.URL - - -// https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/variables#default-environment-variables -object GithubEnv { - lazy val repositoryVar: Option[(String, String)] = envVar("GITHUB_REPOSITORY") - lazy val runIdVar: Option[(String, String)] = envVar("GITHUB_RUN_ID") - lazy val shaVar: Option[(String, String)] = envVar("GITHUB_SHA") - lazy val workflowVar: Option[(String, String)] = envVar("GITHUB_WORKFLOW") - - lazy val runUrl: Option[(String, URL)] = - for { - (_, repository) <- repositoryVar - (_, runId) <- runIdVar - } yield "GitHub Run" -> url(s"https://github.com/$repository/actions/runs/$runId") - lazy val treeUrl: Option[(String, URL)] = - for { - (_, repository) <- repositoryVar - (_, sha) <- shaVar - } yield "GitHub Commit" -> url(s"https://github.com/$repository/tree/$sha") - - - def develocityValues: Seq[(String, String)] = repositoryVar.toSeq ++ shaVar ++ workflowVar - def develocityLinks: Seq[(String, URL)] = runUrl.toSeq ++ treeUrl - - private def envVar(key: String): Option[(String, String)] = - Properties.envOrNone(key).map(key -> _) -} diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index a7f857e8a719..5a4be70987a5 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -9,10 +9,21 @@ object MiMaFilters { // Additions that require a new minor version of the library Build.mimaPreviousDottyVersion -> Seq( ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.readOnlyCapability"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.onlyCapability"), // Scala.js-only class ProblemFilters.exclude[FinalClassProblem]("scala.scalajs.runtime.AnonFunctionXXL"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.scalajs.runtime.AnonFunctionXXL.this"), + + ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language.2.13"), + ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$2$u002E13$"), + ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.separationChecking"), + ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$separationChecking$"), + + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.Conversion.underlying"), + ProblemFilters.exclude[MissingClassProblem]("scala.Conversion$"), + + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.stableNull"), ), // Additions since last LTS diff --git a/project/ScalaLibraryPlugin.scala b/project/ScalaLibraryPlugin.scala index 2eac7271644a..a0d4dda50883 100644 --- a/project/ScalaLibraryPlugin.scala +++ b/project/ScalaLibraryPlugin.scala @@ -4,13 +4,14 @@ import sbt.* import sbt.Keys.* import scala.jdk.CollectionConverters.* import java.nio.file.Files +import xsbti.VirtualFileRef +import sbt.internal.inc.Stamper 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 := { @@ -37,17 +38,29 @@ object ScalaLibraryPlugin extends AutoPlugin { } (Set(scalaLibraryBinaryJar)), target) }, - (Compile / compile) := { + (Compile / manipulateBytecode) := { 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 previous = (Compile / manipulateBytecode).value + val analysis = previous.analysis match { + case analysis: sbt.internal.inc.Analysis => analysis + case _ => sys.error("Unexpected analysis type") + } + + var stamps = analysis.stamps + for (file <- files; + id <- file.relativeTo(reference); + if filesToCopy(id.toString()); // Only Override Some Very Specific Files + dest = target / (id.toString); + ref <- dest.relativeTo((LocalRootProject / baseDirectory).value) + ) { + // Copy the files to the classDirectory + IO.copyFile(file, dest) + // Update the timestamp in the analysis + stamps = stamps.markProduct( + VirtualFileRef.of(s"$${BASE}/$ref"), + Stamper.forFarmHashP(dest.toPath())) } val overwrittenBinaries = Files.walk((Compile / classDirectory).value.toPath()) @@ -56,13 +69,18 @@ object ScalaLibraryPlugin extends AutoPlugin { .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 - }) + // Copy all the specialized classes in the stdlib + // no need to update any stamps as these classes exist nowhere in the analysis + for (orig <- diff; dest <- orig.relativeTo(reference)) { + IO.copyFile(orig, ((Compile / classDirectory).value / dest.toString())) + } - analysis + previous + .withAnalysis(analysis.copy(stamps = stamps)) // update the analysis with the correct stamps + .withHasModified(true) // mark it as updated for sbt to update its caches } ) diff --git a/project/ScaladocGeneration.scala b/project/ScaladocGeneration.scala index aac9f187a888..c06122f5fadf 100644 --- a/project/ScaladocGeneration.scala +++ b/project/ScaladocGeneration.scala @@ -141,6 +141,10 @@ object ScaladocGeneration { def key: String = "-dynamic-side-menu" } + case class SuppressCC(value: Boolean) extends Arg[Boolean] { + def key: String = "-suppressCC" + } + import _root_.scala.reflect._ trait GenerationConfig { diff --git a/project/Versions.scala b/project/Versions.scala new file mode 100644 index 000000000000..3fba907e9d68 --- /dev/null +++ b/project/Versions.scala @@ -0,0 +1,6 @@ +object Versions { + + /* The Minimum JVM version the artifact should be able to use */ + val minimumJVMVersion = "17" + +} diff --git a/project/build.properties b/project/build.properties index 73df629ac1a7..e480c675f2fd 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.10.7 +sbt.version=1.11.5 diff --git a/project/plugins.sbt b/project/plugins.sbt index b61ec009db45..510afef8d8aa 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -8,8 +8,6 @@ libraryDependencySchemes += addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.19.0") -addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.21") - addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.5") @@ -22,6 +20,8 @@ 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.2") +addSbtPlugin("com.gradle" % "sbt-develocity" % "1.3.1") + +addSbtPlugin("com.gradle" % "sbt-develocity-common-custom-user-data" % "1.1") addSbtPlugin("com.github.sbt" % "sbt-jdi-tools" % "1.2.0") diff --git a/project/resources/referenceReplacements/sidebar.yml b/project/resources/referenceReplacements/sidebar.yml index 2e84b0b5e433..4e40642858c9 100644 --- a/project/resources/referenceReplacements/sidebar.yml +++ b/project/resources/referenceReplacements/sidebar.yml @@ -150,6 +150,8 @@ subsection: - page: reference/experimental/explicit-nulls.md - page: reference/experimental/main-annotation.md - page: reference/experimental/cc.md + - page: reference/experimental/cc-advanced.md + hidden: true - page: reference/experimental/tupled-function.md - page: reference/syntax.md - title: Language Versions diff --git a/project/scripts/bisect.scala b/project/scripts/bisect.scala index 54757f7a8244..e721831e77cf 100755 --- a/project/scripts/bisect.scala +++ b/project/scripts/bisect.scala @@ -13,6 +13,8 @@ import java.io.File import java.nio.file.attribute.PosixFilePermissions import java.nio.charset.StandardCharsets import java.nio.file.Files +import java.time.LocalDate +import java.time.format.DateTimeFormatter val usageMessage = """ |Usage: @@ -156,7 +158,6 @@ case class ReleasesRange(first: Option[String], last: Option[String]): val index = releases.indexWhere(_.version == version) assert(index > 0, s"${version} matches no nightly compiler release") index - val startIdx = first.map(releaseIndex(_)).getOrElse(0) val endIdx = last.map(releaseIndex(_) + 1).getOrElse(releases.length) val filtered = releases.slice(startIdx, endIdx).toVector @@ -183,12 +184,15 @@ object Releases: re.findAllMatchIn(xml.mkString) .flatMap{ m => Option(m.group(1)).map(Release.apply) } .toVector + .sortBy: release => + (release.version, release.date) def fromRange(range: ReleasesRange): Vector[Release] = range.filter(allReleases) case class Release(version: String): private val re = raw".+-bin-(\d{8})-(\w{7})-NIGHTLY".r - def date: String = + def date: LocalDate = LocalDate.parse(dateString, DateTimeFormatter.BASIC_ISO_DATE) + def dateString: String = version match case re(date, _) => date case _ => sys.error(s"Could not extract date from release name: $version") diff --git a/project/scripts/expected-links/reference-expected-links.txt b/project/scripts/expected-links/reference-expected-links.txt index 8be7dba8d4d0..3eb5df97bf14 100644 --- a/project/scripts/expected-links/reference-expected-links.txt +++ b/project/scripts/expected-links/reference-expected-links.txt @@ -68,6 +68,7 @@ ./enums/enums.html ./enums/index.html ./experimental/canthrow.html +./experimental/cc-advanced.html ./experimental/cc.html ./experimental/erased-defs-spec.html ./experimental/erased-defs.html diff --git a/project/scripts/options b/project/scripts/options index 86d2c8fd8edb..c4f132d6750a 100644 --- a/project/scripts/options +++ b/project/scripts/options @@ -1,2 +1,2 @@ --Xprint:frontend -Ylog:frontend +-Vprint:frontend -Ylog:frontend -Ycheck:all diff --git a/project/scripts/scala2-library-tasty-mima.sh b/project/scripts/scala2-library-tasty-mima.sh index 7118ee28c2f3..ddb3a6e09e69 100755 --- a/project/scripts/scala2-library-tasty-mima.sh +++ b/project/scripts/scala2-library-tasty-mima.sh @@ -18,6 +18,6 @@ setTastyVersion $MINOR_TASTY_VERSION_SUPPORTED_BY_TASTY_MIMA 0 # We clean before to make sure all sources are recompiled using the new TASTY version. # We clean after to make sure no other test will use the TASTy generated with this version. # We set -Ycheck:all to check that -Ycompile-scala2-library does not gererate inconsistent trees. -"$SBT" 'clean; scala2-library-bootstrapped/clean; reload; set `scala2-library-bootstrapped`/scalacOptions += "-Ycheck:all"; scala2-library-bootstrapped/tastyMiMaReportIssues; clean; scala2-library-bootstrapped/clean' +"$SBT" 'clean; scala2-library-bootstrapped/clean; reload; scala2-library-bootstrapped/tastyMiMaReportIssues; clean; scala2-library-bootstrapped/clean' setTastyVersion $MINOR_TASTY_VERSION $EXPERIMENTAL_TASTY_VERSION diff --git a/sbt-test/scala2-compat/i13332/build.sbt b/sbt-test/scala2-compat/i13332/build.sbt index 3d24ba92abe1..1482eca02955 100644 --- a/sbt-test/scala2-compat/i13332/build.sbt +++ b/sbt-test/scala2-compat/i13332/build.sbt @@ -10,5 +10,5 @@ lazy val app = project.in(file("app")) .dependsOn(lib) .settings( scalaVersion := scala3Version, - scalacOptions += "-Xprint:inlining" - ) \ No newline at end of file + scalacOptions += "-Vprint:inlining" + ) diff --git a/sbt-test/tasty-compat/add-param-unroll/build.sbt b/sbt-test/tasty-compat/add-param-unroll/build.sbt index 7ea07075632f..24bf7f140cd1 100644 --- a/sbt-test/tasty-compat/add-param-unroll/build.sbt +++ b/sbt-test/tasty-compat/add-param-unroll/build.sbt @@ -29,7 +29,7 @@ lazy val c = project.in(file("c")) .settings(commonSettings) .settings(printSettings) .settings( - // scalacOptions ++= Seq("-from-tasty", "-Ycheck:readTasty", "-Xfatal-warnings", "-Xprint:readTasty", "-Xprint-types"), + // scalacOptions ++= Seq("-from-tasty", "-Ycheck:readTasty", "-Xfatal-warnings", "-Vprint: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/scala2-library-cc/src/scala/collection/IndexedSeqView.scala b/scala2-library-cc/src/scala/collection/IndexedSeqView.scala index 78f8abb8e327..07698bc09951 100644 --- a/scala2-library-cc/src/scala/collection/IndexedSeqView.scala +++ b/scala2-library-cc/src/scala/collection/IndexedSeqView.scala @@ -160,7 +160,7 @@ object IndexedSeqView { @SerialVersionUID(3L) class Reverse[A](underlying: SomeIndexedSeqOps[A]^) extends SeqView.Reverse[A](underlying) with IndexedSeqView[A] { - override def reverse: IndexedSeqView[A] = underlying match { + override def reverse: IndexedSeqView[A]^{underlying} = underlying match { case x: IndexedSeqView[A] => x case _ => super.reverse } diff --git a/scala2-library-cc/src/scala/collection/Iterable.scala b/scala2-library-cc/src/scala/collection/Iterable.scala index 6556f31d378d..c5d10211e3ab 100644 --- a/scala2-library-cc/src/scala/collection/Iterable.scala +++ b/scala2-library-cc/src/scala/collection/Iterable.scala @@ -17,6 +17,7 @@ import scala.annotation.unchecked.uncheckedVariance import scala.collection.mutable.Builder import scala.collection.View.{LeftPartitionMapped, RightPartitionMapped} import language.experimental.captureChecking +import scala.caps.unsafe.unsafeAssumePure /** Base trait for generic collections. * @@ -302,7 +303,8 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with Iterable * this.sizeIs > size // this.sizeCompare(size) > 0 * }}} */ - @inline final def sizeIs: IterableOps.SizeCompareOps^{this} = new IterableOps.SizeCompareOps(this) + @inline final def sizeIs: IterableOps.SizeCompareOps^{this} = new IterableOps.SizeCompareOps(this.unsafeAssumePure) + // CC Problem: unsafeAssumePure needed since we had to change the argument signatire of SizeCompareOps /** Compares the size of this $coll to the size of another `Iterable`. * @@ -866,8 +868,15 @@ object IterableOps { * These operations are implemented in terms of * [[scala.collection.IterableOps.sizeCompare(Int) `sizeCompare(Int)`]]. */ - final class SizeCompareOps private[collection](val it: IterableOps[_, AnyConstr, _]^) extends AnyVal { - this: SizeCompareOps^{it} => + final class SizeCompareOps private[collection](val it: IterableOps[_, AnyConstr, _]/*^*/) extends AnyVal { + this: SizeCompareOps/*^*/ => + // CC Problem: if we add the logically needed `^`s to the `it` parameter and the + // self type, separation checking fails in the compiler-generated equals$extends + // method of the value class. There seems to be something wrong how pattern + // matching interacts with separation checking. A minimized test case + // is pending/pos-custom-args/captures/SizeCompareOps-redux.scala. + // Without the `^`s, the `sizeIs` method needs an unsafeAssumePure. + /** Tests if the size of the collection is less than some value. */ @inline def <(size: Int): Boolean = it.sizeCompare(size) < 0 /** Tests if the size of the collection is less than or equal to some value. */ @@ -985,7 +994,7 @@ trait SortedSetFactoryDefaults[+A, +WithFilterCC[x] <: IterableOps[x, WithFilterCC, WithFilterCC[x]] with Set[x]] extends SortedSetOps[A @uncheckedVariance, CC, CC[A @uncheckedVariance]] { self: IterableOps[A, WithFilterCC, _] => - override protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]^): CC[A @uncheckedVariance]^{coll} = sortedIterableFactory.from(coll)(using ordering) + override protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]^): CC[A @uncheckedVariance] = sortedIterableFactory.from(coll)(using ordering) override protected def newSpecificBuilder: mutable.Builder[A @uncheckedVariance, CC[A @uncheckedVariance]] = sortedIterableFactory.newBuilder[A](using ordering) override def empty: CC[A @uncheckedVariance] = sortedIterableFactory.empty(using ordering) @@ -1041,7 +1050,7 @@ trait SortedMapFactoryDefaults[K, +V, self: IterableOps[(K, V), WithFilterCC, _] => override def empty: CC[K, V @uncheckedVariance] = sortedMapFactory.empty(using ordering) - override protected def fromSpecific(coll: IterableOnce[(K, V @uncheckedVariance)]^): CC[K, V @uncheckedVariance]^{coll} = sortedMapFactory.from(coll)(using ordering) + override protected def fromSpecific(coll: IterableOnce[(K, V @uncheckedVariance)]^): CC[K, V @uncheckedVariance] = sortedMapFactory.from(coll)(using ordering) override protected def newSpecificBuilder: mutable.Builder[(K, V @uncheckedVariance), CC[K, V @uncheckedVariance]] = sortedMapFactory.newBuilder[K, V](using ordering) override def withFilter(p: ((K, V)) => Boolean): collection.SortedMapOps.WithFilter[K, V, WithFilterCC, UnsortedCC, CC]^{p} = diff --git a/scala2-library-cc/src/scala/collection/View.scala b/scala2-library-cc/src/scala/collection/View.scala index 72a073836e77..b8de97159f06 100644 --- a/scala2-library-cc/src/scala/collection/View.scala +++ b/scala2-library-cc/src/scala/collection/View.scala @@ -152,15 +152,13 @@ object View extends IterableFactory[View] { def apply[A](underlying: Iterable[A]^, p: A => Boolean, isFlipped: Boolean): Filter[A]^{underlying, p} = underlying match { case filter: Filter[A] if filter.isFlipped == isFlipped => - new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) - .asInstanceOf[Filter[A]^{underlying, p}] + unsafeAssumeSeparate: + new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) + .asInstanceOf[Filter[A]^{underlying, p}] // !!! asInstanceOf needed once paths were added, see path-patmat-should-be-pos.scala for minimization - //case filter: Filter[A]^{underlying} if filter.isFlipped == isFlipped => - // unsafeAssumeSeparate: // See filter-iterable.scala for a test where a variant of Filter // works without the unsafeAssumeSeparate. But it requires significant // changes compared to the version here. See also Filter in colltest5.CollectionStrawManCC5_1. - // new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) case _ => new Filter(underlying, p, isFlipped) } } diff --git a/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala b/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala index 45186e7c13b2..726b011c6929 100644 --- a/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala +++ b/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala @@ -25,7 +25,7 @@ import scala.runtime.Statics import language.experimental.captureChecking import annotation.unchecked.uncheckedCaptures import caps.cap -import caps.unsafe.{unsafeAssumeSeparate, untrackedCaptures} +import caps.unsafe.{unsafeAssumeSeparate, unsafeAssumePure, untrackedCaptures} /** This class implements an immutable linked list. We call it "lazy" * because it computes its elements only when they are needed. @@ -989,7 +989,12 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { private sealed trait State[+A] extends Serializable { def head: A - def tail: LazyListIterable[A]^ + def tail: LazyListIterable[A]/*^*/ + // should be ^ but this fails checking. The problem is that + // the ^ in LazyListIterable[A]^ is treated as a result reference. + // But then it cannot be subsumed by + // val tail: LazyListIterable[A]^ + // in class State.Cons. } private object State { @@ -1000,14 +1005,15 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { } @SerialVersionUID(3L) - final class Cons[A](val head: A, val tail: LazyListIterable[A]^) extends State[A] + final class Cons[A](val head: A, val tail: LazyListIterable[A]/*^*/) extends State[A] } /** Creates a new LazyListIterable. */ @inline private def newLL[A](state: => State[A]^): LazyListIterable[A]^{state} = new LazyListIterable[A](() => state) /** Creates a new State.Cons. */ - @inline private def sCons[A](hd: A, tl: LazyListIterable[A]^): State[A]^{tl} = new State.Cons[A](hd, tl) + @inline private def sCons[A](hd: A, tl: LazyListIterable[A]^): State[A]^{tl} = + new State.Cons[A](hd, tl.unsafeAssumePure) private val anyToMarker: Any => Any = _ => Statics.pfMarker diff --git a/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala b/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala index 1c3f669f5358..89e3dfb78d8e 100644 --- a/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala +++ b/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala @@ -101,7 +101,7 @@ private[mutable] object CheckedIndexedSeqView { @SerialVersionUID(3L) class Reverse[A](underlying: SomeIndexedSeqOps[A]^)(protected val mutationCount: () ->{cap.rd} Int) extends IndexedSeqView.Reverse[A](underlying) with CheckedIndexedSeqView[A] { - override def reverse: IndexedSeqView[A] = underlying match { + override def reverse: IndexedSeqView[A]^{underlying} = underlying match { case x: IndexedSeqView[A] => x case _ => super.reverse } diff --git a/scaladoc-testcases/src/tests/22265/macro.scala b/scaladoc-testcases/src/tests/22265/macro.scala new file mode 100644 index 000000000000..a2b9f118ceb5 --- /dev/null +++ b/scaladoc-testcases/src/tests/22265/macro.scala @@ -0,0 +1,13 @@ +import scala.quoted._ + +object TestBuilder: + // transparent is needed + transparent inline def apply(inline expr: Unit): Any = + ${ TestBuilder.processTests('expr) } + + def processTests(using Quotes)(body: Expr[Unit]): Expr[Any] = + import quotes.reflect._ + body.asTerm match { + case Inlined(_, _, bindings) => + '{ ${bindings.asExpr}; () } // can also be List(${bindings}) + } diff --git a/scaladoc-testcases/src/tests/22265/test.scala b/scaladoc-testcases/src/tests/22265/test.scala new file mode 100644 index 000000000000..7642047a175b --- /dev/null +++ b/scaladoc-testcases/src/tests/22265/test.scala @@ -0,0 +1,4 @@ +object breaks { + TestBuilder: + import List.empty +} diff --git a/scaladoc-testcases/src/tests/classSignatureTestSource.scala b/scaladoc-testcases/src/tests/classSignatureTestSource.scala index 4d4ebf9578ec..a176a3592f5e 100644 --- a/scaladoc-testcases/src/tests/classSignatureTestSource.scala +++ b/scaladoc-testcases/src/tests/classSignatureTestSource.scala @@ -17,6 +17,8 @@ abstract class Documentation[T, A <: Int, B >: String, -X, +Y](c1: String, val c def this(x: T) = this() + //expected: def toArray[B >: T : ClassTag]: Array[B] + class innerDocumentationClass { @@ -24,7 +26,9 @@ abstract class Documentation[T, A <: Int, B >: String, -X, +Y](c1: String, val c sealed trait CaseImplementThis(id: Int) - case class IAmACaseClass(x: T, id: Int) extends CaseImplementThis/*<-*/(id)/*->*/ + case class IAmACaseClass(x: T, id: Int) extends CaseImplementThis(id) //expected: case class IAmACaseClass(x: Documentation.this.T, id: Int) extends CaseImplementThis + + case class IAmACaseClassWithParam[T](x: Documentation.this.T, id: T) case object IAmACaseObject extends CaseImplementThis/*<-*/(0)/*->*/ diff --git a/scaladoc-testcases/src/tests/contextBounds.scala b/scaladoc-testcases/src/tests/contextBounds.scala index 794af0b8b8f8..7d20ba9d8216 100644 --- a/scaladoc-testcases/src/tests/contextBounds.scala +++ b/scaladoc-testcases/src/tests/contextBounds.scala @@ -4,6 +4,16 @@ package contextBounds import scala.reflect.ClassTag class A: + type :+:[X, Y] = [Z] =>> Map[Z, (X, Y)] + + extension [T : ([X] =>> String) : ([X] =>> Int)](x: Int) + def foo[U : ([X] =>> String)](y: Int): Nothing + = ??? + def bar[W : T match { case String => List case Int => Option } : Set]: Nothing + = ??? + def baz[V : Int :+: String : Option]: Nothing + = ??? + def basic[A : ClassTag]: A = ??? @@ -35,5 +45,5 @@ class A: // = 1 class Outer[A]: - def falsePositiveInner[T](implicit evidence$3: ClassTag[A]): Int - = 1 \ No newline at end of file + def falsePositiveInner[T](implicit evidence$3: ClassTag[A]): Int //expected: def falsePositiveInner[T]: Int + = 1 diff --git a/scaladoc-testcases/src/tests/encoded.name.scala b/scaladoc-testcases/src/tests/encoded.name.scala new file mode 100644 index 000000000000..d47bf83bfe15 --- /dev/null +++ b/scaladoc-testcases/src/tests/encoded.name.scala @@ -0,0 +1,2 @@ + +def exampleMember = "hello, world" diff --git a/scaladoc-testcases/src/tests/exports1.scala b/scaladoc-testcases/src/tests/exports1.scala index f719bca35eb1..2c71b42e8da3 100644 --- a/scaladoc-testcases/src/tests/exports1.scala +++ b/scaladoc-testcases/src/tests/exports1.scala @@ -14,15 +14,15 @@ class A: //unexpected = 1 var aVar1: 1 = 1 - type HKT[T[_], X] //expected: final type HKT = [T[_], X] =>> a.HKT[T, X] + type HKT[T[_], X] //expected: final type HKT = a.HKT = T[X] type SomeRandomType = (List[?] | Seq[?]) & String //expected: final type SomeRandomType = a.SomeRandomType - def x[T[_], X](x: X): HKT[T, X] //expected: def x[T[_], X](x: X): A.this.HKT[T, X] + def x[T[_], X](x: X): HKT[T, X] = ??? def fn[T, U]: T => U = ??? object Object //expected: val Obj: Object.type - val x: HKT[List, Int] //expected: val x: A.this.HKT[List, Int] + val x: HKT[List, Int] = ??? class Class(val a: Int, val b: Int) extends Serializable //expected: final type Class = a.Class enum Enum: //expected: final type Enum = a.Enum diff --git a/scaladoc-testcases/src/tests/extendsCall.scala b/scaladoc-testcases/src/tests/extendsCall.scala index b90af8162e15..3ccd70de4216 100644 --- a/scaladoc-testcases/src/tests/extendsCall.scala +++ b/scaladoc-testcases/src/tests/extendsCall.scala @@ -3,4 +3,4 @@ package extendsCall class Impl() extends Base(Seq.empty, c = "-") //expected: class Impl() extends Base -class Base(val a: Seq[String], val b: String = "", val c: String = "") //expected: class Base(val a: Seq[String], val b: String, val c: String) +class Base(val a: Seq[String], val b: String = "", val c: String = "") //expected: class Base(val a: Seq[String], val b: String = ..., val c: String = ...) diff --git a/scaladoc-testcases/src/tests/extensionMethodSignatures.scala b/scaladoc-testcases/src/tests/extensionMethodSignatures.scala index 34237da32983..fd8312f78d00 100644 --- a/scaladoc-testcases/src/tests/extensionMethodSignatures.scala +++ b/scaladoc-testcases/src/tests/extensionMethodSignatures.scala @@ -44,4 +44,23 @@ case class ClassTwo(a: String, b: String) } -class ClassOneTwo extends ClassOne \ No newline at end of file +class ClassOneTwo extends ClassOne + +trait C[T] +trait Equiv[T]: + extension [U : C](x: U) + def ><[V](y: V): Nothing + = ??? + +trait Monoid[T]: + extension (a: T) + def \:[U](b: U): Nothing + = ??? + extension [U](a: T) + def \\:(b: U): Nothing + = ??? + +class Clazz[U]: + extension [T : ([X] =>> String) : ([X] =>> String)](x: Int) + def bar[U : ([X] =>> String) : List](y: Int): Nothing + = ??? diff --git a/scaladoc-testcases/src/tests/innerClasses.scala b/scaladoc-testcases/src/tests/innerClasses.scala new file mode 100644 index 000000000000..50b2df41a826 --- /dev/null +++ b/scaladoc-testcases/src/tests/innerClasses.scala @@ -0,0 +1,25 @@ +package tests +package innerClasses + +trait A: + def baz: B + = baz2 + def baz2: A.this.B //expected: def baz2: B + = baz + type B + class C extends A: + def foo: A.this.B + = ??? + def foo2: B + = ??? + def bar: B + = ??? + +class T1: + trait T + class T2: + trait T + class Impl extends T1.this.T //expected: class Impl extends T + // we get rid of the this-type above, + // as ambiguity created by removing this-types is alleviated by links + // (but this can be changed if needed) diff --git a/scaladoc-testcases/src/tests/optionalParams.scala b/scaladoc-testcases/src/tests/optionalParams.scala new file mode 100644 index 000000000000..551e14f7f811 --- /dev/null +++ b/scaladoc-testcases/src/tests/optionalParams.scala @@ -0,0 +1,23 @@ +package tests +package optionalParams + +class C(val a: Seq[String], val b: String = "", var c: String = "") //expected: class C(val a: Seq[String], val b: String = ..., var c: String = ...) +{ + def m(x: Int, s: String = "a"): Nothing //expected: def m(x: Int, s: String = ...): Nothing + = ??? +} + +def f(x: Int, s: String = "a"): Nothing //expected: def f(x: Int, s: String = ...): Nothing + = ??? + +extension (y: Int) + def ext(x: Int = 0): Int //expected: def ext(x: Int = ...): Int + = 0 + +def byname(s: => String = "a"): Int //expected: def byname(s: => String = ...): Int + = 0 + +enum E(val x: Int = 0) //expected: enum E(val x: Int = ...) +{ + case E1(y: Int = 10) extends E(y) //expected: final case class E1(y: Int = ...) extends E +} diff --git a/scaladoc-testcases/src/tests/thisType.scala b/scaladoc-testcases/src/tests/thisType.scala index 28cb55fcc49e..2d68e70c43ac 100644 --- a/scaladoc-testcases/src/tests/thisType.scala +++ b/scaladoc-testcases/src/tests/thisType.scala @@ -1,8 +1,35 @@ package tests package thisType -// issue 16024 class X[Map[_, _[_]]]: + // issue 16024 inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] = ??? + +sealed trait Tuple[Y[_]]: + def ++[This >: this.type <: Tuple[Y]](that: Y[Tuple[Y]]): Any + = ??? + +sealed trait NonEmptyTuple extends Tuple[Option] +//expected: def ++[This >: this.type <: Tuple[Option]](that: Option[Tuple[Option]]): Any + +trait Foo[X]: + def foo0[T <: Foo.this.type](x: X): Foo.this.type //expected: def foo0[T <: this.type](x: X): this.type + = bar0[T](x) + def bar0[T <: this.type](x: X): this.type + = foo0[T](x) + + sealed abstract class Nested[+H, +T <: (Tuple), A <: Tuple[List]] extends NonEmptyTuple, Foo[Int]: + // ^^^^^^^ TODO fix + //expected: def ++[This >: this.type <: Tuple[Option]](that: Option[Tuple[Option]]): Any + + //expected: def foo0[T <: this.type](x: Int): this.type + + //expected: def bar0[T <: this.type](x: Int): this.type + + def foo1[T <: Foo.this.type]: Nothing + = ??? + + def foo2[T <: this.type]: Nothing + = ??? diff --git a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala index a2485085a927..50e7589d92fe 100644 --- a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala +++ b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala @@ -47,6 +47,7 @@ object Scaladoc: defaultTemplate: Option[String] = None, quickLinks: List[QuickLink] = List.empty, dynamicSideMenu: Boolean = false, + suppressCC: Boolean = false, // suppress rendering anything related to experimental capture checking ) def run(args: Array[String], rootContext: CompilerContext): Reporter = @@ -231,6 +232,7 @@ object Scaladoc: defaultTemplate.nonDefault, quickLinksParsed, dynamicSideMenu.get, + suppressCC.get, ) (Some(docArgs), newContext) } diff --git a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala index 5acfac03d52c..8189d4f45286 100644 --- a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala +++ b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala @@ -144,5 +144,8 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings: val dynamicSideMenu: Setting[Boolean] = BooleanSetting(RootSetting, "dynamic-side-menu", "Generate side menu via JS instead of embedding it in every html file", false) + val suppressCC: Setting[Boolean] = + BooleanSetting(RootSetting, "suppressCC", "Suppress rendering anything related to experimental capture checking", false) + def scaladocSpecificSettings: Set[Setting[?]] = - Set(sourceLinks, legacySourceLink, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, generateInkuire, defaultTemplate, scastieConfiguration, quickLinks, dynamicSideMenu) + Set(sourceLinks, legacySourceLink, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, generateInkuire, defaultTemplate, scastieConfiguration, quickLinks, dynamicSideMenu, suppressCC) diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index b39fdf157347..41ccd8fb2280 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -44,6 +44,7 @@ enum Modifier(val name: String, val prefix: Boolean): case Transparent extends Modifier("transparent", true) case Infix extends Modifier("infix", true) case AbsOverride extends Modifier("abstract override", true) + case Update extends Modifier("update", true) case class ExtensionTarget(name: String, typeParams: Seq[TypeParameter], argsLists: Seq[TermParameterList], signature: Signature, dri: DRI, position: Long) case class ImplicitConversion(from: DRI, to: DRI) @@ -69,7 +70,7 @@ enum Kind(val name: String): case Var extends Kind("var") case Val extends Kind("val") case Exported(base: Kind) extends Kind("export") - case Type(concreate: Boolean, opaque: Boolean, typeParams: Seq[TypeParameter]) + case Type(concreate: Boolean, opaque: Boolean, typeParams: Seq[TypeParameter], isCaptureVar: Boolean = false) extends Kind("type") // should we handle opaque as modifier? case Given(kind: Def | Class | Val.type, as: Option[Signature], conversion: Option[ImplicitConversion]) extends Kind("given") with ImplicitConversionProvider @@ -120,7 +121,8 @@ case class TypeParameter( variance: "" | "+" | "-", name: String, dri: DRI, - signature: Signature + signature: Signature, + isCaptureVar: Boolean = false // under capture checking ) case class Link(name: String, dri: DRI) diff --git a/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala b/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala new file mode 100644 index 000000000000..626162db55ec --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala @@ -0,0 +1,225 @@ +package dotty.tools.scaladoc + +package cc + +import scala.quoted._ + +object CaptureDefs: + // these should become part of the reflect API in the distant future + def retains(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.retains") + def retainsCap(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.retainsCap") + def retainsByName(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.retainsByName") + def CapsModule(using qctx: Quotes) = + qctx.reflect.Symbol.requiredPackage("scala.caps") + def captureRoot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredPackage("scala.caps.cap") + def Caps_Capability(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.Capability") + def Caps_CapSet(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.CapSet") + def Caps_Mutable(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.Mutable") + def Caps_SharedCapability(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.SharedCapability") + def UseAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.use") + def ConsumeAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.consume") + def ReachCapabilityAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.internal.reachCapability") + def RootCapabilityAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.internal.rootCapability") + def ReadOnlyCapabilityAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.internal.readOnlyCapability") + def RequiresCapabilityAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.internal.requiresCapability") + def OnlyCapabilityAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.internal.onlyCapability") + + def LanguageExperimental(using qctx: Quotes) = + qctx.reflect.Symbol.requiredPackage("scala.language.experimental") + + def ImpureFunction1(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.ImpureFunction1") + + def ImpureContextFunction1(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.ImpureContextFunction1") + + def Function1(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.Function1") + + def ContextFunction1(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.ContextFunction1") + + val useAnnotFullName: String = "scala.caps.use." + val consumeAnnotFullName: String = "scala.caps.consume." + val ccImportSelector = "captureChecking" +end CaptureDefs + +extension (using qctx: Quotes)(ann: qctx.reflect.Symbol) + /** This symbol is one of `retains` or `retainsCap` */ + def isRetains: Boolean = + ann == CaptureDefs.retains || ann == CaptureDefs.retainsCap + + /** This symbol is one of `retains`, `retainsCap`, or `retainsByName` */ + def isRetainsLike: Boolean = + ann.isRetains || ann == CaptureDefs.retainsByName + + def isReachCapabilityAnnot: Boolean = + ann == CaptureDefs.ReachCapabilityAnnot + + def isReadOnlyCapabilityAnnot: Boolean = + ann == CaptureDefs.ReadOnlyCapabilityAnnot + + def isOnlyCapabilityAnnot: Boolean = + ann == CaptureDefs.OnlyCapabilityAnnot +end extension + +extension (using qctx: Quotes)(tpe: qctx.reflect.TypeRepr) // FIXME clean up and have versions on Symbol for those + def isCaptureRoot: Boolean = + import qctx.reflect.* + tpe match + case TermRef(ThisType(TypeRef(NoPrefix(), "caps")), "cap") => true + case TermRef(TermRef(ThisType(TypeRef(NoPrefix(), "scala")), "caps"), "cap") => true + case TermRef(TermRef(TermRef(TermRef(NoPrefix(), "_root_"), "scala"), "caps"), "cap") => true + case _ => false + + // NOTE: There's something horribly broken with Symbols, and we can't rely on tests like .isContextFunctionType either, + // so we do these lame string comparisons instead. + def isImpureFunction1: Boolean = tpe.typeSymbol.fullName == "scala.ImpureFunction1" + + def isImpureContextFunction1: Boolean = tpe.typeSymbol.fullName == "scala.ImpureContextFunction1" + + def isFunction1: Boolean = tpe.typeSymbol.fullName == "scala.Function1" + + def isContextFunction1: Boolean = tpe.typeSymbol.fullName == "scala.ContextFunction1" + + def isAnyImpureFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.ImpureFunction") + + def isAnyImpureContextFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.ImpureContextFunction") + + def isAnyFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.Function") + + def isAnyContextFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.ContextFunction") + + def isCapSet: Boolean = tpe.typeSymbol == CaptureDefs.Caps_CapSet + + def isCapSetPure: Boolean = + tpe.isCapSet && tpe.match + case CapturingType(_, refs) => refs.isEmpty + case _ => true + + def isCapSetCap: Boolean = + tpe.isCapSet && tpe.match + case CapturingType(_, List(ref)) => ref.isCaptureRoot + case _ => false + + def isPureClass(from: qctx.reflect.ClassDef): Boolean = + import qctx.reflect._ + def check(sym: Tree): Boolean = sym match + case ClassDef(name, _, _, Some(ValDef(_, tt, _)), _) => tt.tpe match + case CapturingType(_, refs) => refs.isEmpty + case _ => true + case _ => false + + // Horrible hack to basically grab tpe1.asSeenFrom(from) + val tpe1 = from.symbol.typeRef.select(tpe.typeSymbol).simplified + val tpe2 = tpe1.classSymbol.map(_.typeRef).getOrElse(tpe1) + + // println(s"${tpe.show} -> (${tpe.typeSymbol} from ${from.symbol}) ${tpe1.show} -> ${tpe2} -> ${tpe2.baseClasses.filter(_.isClassDef)}") + val res = tpe2.baseClasses.exists(c => c.isClassDef && check(c.tree)) + // println(s"${tpe.show} is pure class = $res") + res +end extension + +extension (using qctx: Quotes)(typedef: qctx.reflect.TypeDef) + def derivesFromCapSet: Boolean = + import qctx.reflect.* + typedef.rhs.match + case t: TypeTree => t.tpe.derivesFrom(CaptureDefs.Caps_CapSet) + case t: TypeBoundsTree => t.tpe.derivesFrom(CaptureDefs.Caps_CapSet) + case _ => false +end extension + +/** Matches `import scala.language.experimental.captureChecking` */ +object CCImport: + def unapply(using qctx: Quotes)(tree: qctx.reflect.Tree): Boolean = + import qctx.reflect._ + tree match + case imprt: Import if imprt.expr.tpe.termSymbol == CaptureDefs.LanguageExperimental => + imprt.selectors.exists { + case SimpleSelector(s) if s == CaptureDefs.ccImportSelector => true + case _ => false + } + case _ => false + end unapply +end CCImport + +object ReachCapability: + def unapply(using qctx: Quotes)(ty: qctx.reflect.TypeRepr): Option[qctx.reflect.TypeRepr] = + import qctx.reflect._ + ty match + case AnnotatedType(base, Apply(Select(New(annot), _), Nil)) if annot.symbol.isReachCapabilityAnnot => + Some(base) + case _ => None +end ReachCapability + +object ReadOnlyCapability: + def unapply(using qctx: Quotes)(ty: qctx.reflect.TypeRepr): Option[qctx.reflect.TypeRepr] = + import qctx.reflect._ + ty match + case AnnotatedType(base, Apply(Select(New(annot), _), Nil)) if annot.symbol.isReadOnlyCapabilityAnnot => + Some(base) + case _ => None +end ReadOnlyCapability + +object OnlyCapability: + def unapply(using qctx: Quotes)(ty: qctx.reflect.TypeRepr): Option[(qctx.reflect.TypeRepr, qctx.reflect.Symbol)] = + import qctx.reflect._ + ty match + case AnnotatedType(base, app @ Apply(TypeApply(Select(New(annot), _), _), Nil)) if annot.tpe.typeSymbol.isOnlyCapabilityAnnot => + app.tpe.typeArgs.head.classSymbol.match + case Some(clazzsym) => Some((base, clazzsym)) + case None => None + case _ => None +end OnlyCapability + +/** Decompose capture sets in the union-type-encoding into the sequence of atomic `TypeRepr`s. + * Returns `None` if the type is not a capture set. +*/ +def decomposeCaptureRefs(using qctx: Quotes)(typ0: qctx.reflect.TypeRepr): Option[List[qctx.reflect.TypeRepr]] = + import qctx.reflect._ + val buffer = collection.mutable.ListBuffer.empty[TypeRepr] + def include(t: TypeRepr): Boolean = { buffer += t; true } + def traverse(typ: TypeRepr): Boolean = + typ match + case t if t.typeSymbol == defn.NothingClass => true + case OrType(t1, t2) => traverse(t1) && traverse(t2) + case t @ ThisType(_) => include(t) + case t @ TermRef(_, _) => include(t) + case t @ ParamRef(_, _) => include(t) + case t @ ReachCapability(_) => include(t) + case t @ ReadOnlyCapability(_) => include(t) + case t @ OnlyCapability(_, _) => include(t) + case t : TypeRef => include(t) + case _ => report.warning(s"Unexpected type tree $typ while trying to extract capture references from $typ0"); false + if traverse(typ0) then Some(buffer.toList) else None +end decomposeCaptureRefs + +object CaptureSetType: + def unapply(using qctx: Quotes)(tt: qctx.reflect.TypeTree): Option[List[qctx.reflect.TypeRepr]] = decomposeCaptureRefs(tt.tpe) +end CaptureSetType + +object CapturingType: + def unapply(using qctx: Quotes)(typ: qctx.reflect.TypeRepr): Option[(qctx.reflect.TypeRepr, List[qctx.reflect.TypeRepr])] = + import qctx.reflect._ + typ match + case AnnotatedType(base, Apply(TypeApply(Select(New(annot), _), List(CaptureSetType(refs))), Nil)) if annot.symbol.isRetainsLike => + Some((base, refs)) + case AnnotatedType(base, Apply(Select(New(annot), _), Nil)) if annot.symbol == CaptureDefs.retainsCap => + Some((base, List(CaptureDefs.captureRoot.termRef))) + case _ => None +end CapturingType diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala index af39870d87b5..119ec4fca3f8 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala @@ -177,7 +177,8 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext cls := s"documentableName $depStyle", ) - val signature: MemberSignature = signatureProvider.rawSignature(member)() + val ctx = summon[DocContext] + val signature: MemberSignature = signatureProvider.rawSignature(member)(!ctx.args.suppressCC)() val isSubtype = signature.suffix.exists { case Keyword(keyword) => keyword.contains("extends") case _ => false diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala index 3e49af2e0576..af30c3479d81 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala @@ -213,7 +213,7 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: val (res, pageName) = page.content match case m: Member if m.kind != Kind.RootPackage => def processMember(member: Member, fqName: List[String]): Seq[(JSON, Seq[String])] = - val signature: MemberSignature = signatureProvider.rawSignature(member)() + val signature: MemberSignature = signatureProvider.rawSignature(member)(!ctx.args.suppressCC)() val sig = Signature(Plain(member.name)) ++ signature.suffix val descr = if member.kind == Kind.Package then "" else fqName.mkString(".") val extraDescr = member.docs.map(d => docPartRenderPlain(d.body)).getOrElse("") diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala index 81415377beeb..06fe658b850e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala @@ -3,6 +3,7 @@ package tasty import scala.jdk.CollectionConverters._ import dotty.tools.scaladoc._ +import dotty.tools.scaladoc.cc.CaptureDefs import scala.quoted._ import SymOps._ @@ -42,7 +43,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 fqNameAllowlist = Set( + val fqNameAllowlist0 = Set( "scala.specialized", "scala.throws", "scala.transient", @@ -52,8 +53,12 @@ trait BasicSupport: "scala.annotation.static", "scala.annotation.targetName", "scala.annotation.threadUnsafe", - "scala.annotation.varargs" + "scala.annotation.varargs", ) + val fqNameAllowlist = + if ccEnabled then + fqNameAllowlist0 + CaptureDefs.useAnnotFullName + CaptureDefs.consumeAnnotFullName + else fqNameAllowlist0 val documentedSymbol = summon[Quotes].reflect.Symbol.requiredClass("java.lang.annotation.Documented") val annotations = sym.annotations.filter { a => a.tpe.typeSymbol.hasAnnotation(documentedSymbol) || fqNameAllowlist.contains(a.symbol.fullName) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index ee12755c7f98..1a64625d491c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -1,9 +1,9 @@ package dotty.tools.scaladoc.tasty -import scala.jdk.CollectionConverters._ import dotty.tools.scaladoc._ import dotty.tools.scaladoc.{Signature => DSignature} -import dotty.tools.scaladoc.Inkuire + +import dotty.tools.scaladoc.cc.* import scala.quoted._ @@ -21,6 +21,15 @@ trait ClassLikeSupport: private given qctx.type = qctx + extension (symbol: Symbol) { + def getExtraModifiers(): Seq[Modifier] = + val mods = SymOps.getExtraModifiers(symbol)() + if ccEnabled && symbol.flags.is(Flags.Mutable)then + mods :+ Modifier.Update + else + mods + } + private def bareClasslikeKind(using Quotes)(symbol: reflect.Symbol): Kind = import reflect._ if symbol.flags.is(Flags.Module) then Kind.Object @@ -89,7 +98,7 @@ trait ClassLikeSupport: def getSupertypesGraph(link: LinkToType, to: Seq[Tree]): Seq[(LinkToType, LinkToType)] = to.flatMap { case tree => val symbol = if tree.symbol.isClassConstructor then tree.symbol.owner else tree.symbol - val signature = signatureWithName(tree.asSignature(classDef)) + val signature = signatureWithName(tree.asSignature(classDef, classDef.symbol)) val superLink = LinkToType(signature, symbol.dri, bareClasslikeKind(symbol)) val nextTo = unpackTreeToClassDef(tree).parents if symbol.isHiddenByVisibility then getSupertypesGraph(link, nextTo) @@ -100,16 +109,17 @@ trait ClassLikeSupport: .filterNot((s, t) => s.isHiddenByVisibility) .map { case (symbol, tpe) => - val signature = signatureWithName(tpe.asSignature(classDef)) + val signature = signatureWithName(tpe.asSignature(classDef, classDef.symbol)) LinkToType(signature, symbol.dri, bareClasslikeKind(symbol)) } val selfType = classDef.self.map { (valdef: ValDef) => val symbol = valdef.symbol val tpe = valdef.tpt.tpe - val signature = signatureWithName(tpe.asSignature(classDef)) + val owner = if symbol.exists then symbol.owner else Symbol.noSymbol + val signature = signatureWithName(tpe.asSignature(classDef, owner)) LinkToType(signature, symbol.dri, Kind.Type(false, false, Seq.empty)) } - val selfSignature: DSignature = signatureWithName(typeForClass(classDef).asSignature(classDef)) + val selfSignature: DSignature = signatureWithName(typeForClass(classDef).asSignature(classDef, classDef.symbol)) val graph = HierarchyGraph.withEdges( getSupertypesGraph(LinkToType(selfSignature, classDef.symbol.dri, bareClasslikeKind(classDef.symbol)), unpackTreeToClassDef(classDef).parents) @@ -148,23 +158,49 @@ trait ClassLikeSupport: private def isDocumentableExtension(s: Symbol) = !s.isHiddenByVisibility && !s.isSyntheticFunc && s.isExtensionMethod + private def isEvidence(tpc: TermParamClause) = + (tpc.isGiven || tpc.isImplicit) && tpc.params.forall(_.name.startsWith(NameKinds.ContextBoundParamName.separator)) + + private def extractEvidences(tpcs: List[TermParamClause]): (Map[Symbol, List[TypeRepr]], List[TermParamClause]) = + val (evidenceParams, termParams) = tpcs.partition(isEvidence) + val evidenceMap = evidenceParams.flatMap(_.params).map(p => (p.tpt, p.tpt.tpe)).collect { + case (Applied(bound, List(arg: TypeTree)), _) => (arg.tpe.typeSymbol, bound.tpe) + case (_, AppliedType(bound, List(arg))) => (arg.typeSymbol, bound) + // It seems like here we could do: + // (...).map(_.tpt.tpe).collect { + // case AppliedType(bound, List(arg)) => (arg.typeSymbol, bound) + // or: + // (...).map(_.tpt).collect { + // case Applied(bound, List(arg: TypeTree)) => (arg.tpe.typeSymbol, bound.tpe) + // + // First one doesn't always work because .tpe in some cases causes type lambda reductions, eg: + // def foo[T : ([X] =>> String)] + // after desugaring: + // def foo[T](implicit ecidence$1 : ([X] =>> String)[T]) + // tree for this evidence looks like: ([X] =>> String)[T] + // but type repr looks like: String + // (see scaladoc-testcases/src/tests/contextBounds.scala) + // + // Second one doesn't always work, because the tree is sometimes `Inferred` + // (see toArray inherited in scaladoc-testcases/src/tests/classSignatureTestSource.scala) + // + // TODO: check if those two cases can occur at the same time + }.groupMap(_._1)(_._2).withDefaultValue(Nil) + (evidenceMap, termParams) + private def parseMember(c: ClassDef)(s: Tree): Option[Member] = processTreeOpt(s) { s match case dd: DefDef if isDocumentableExtension(dd.symbol) => dd.symbol.extendedSymbol.map { extSym => - val memberInfo = unwrapMemberInfo(c, dd.symbol) - val typeParams = dd.symbol.extendedTypeParams.map(mkTypeArgument(_, c, memberInfo.genericTypes)) - val termParams = dd.symbol.extendedTermParamLists.zipWithIndex.flatMap { case (termParamList, index) => - memberInfo.termParamLists(index) match - case MemberInfo.EvidenceOnlyParameterList => None - case MemberInfo.RegularParameterList(info) => - Some(api.TermParameterList(termParamList.params.map(mkParameter(_, c, memberInfo = info)), paramListModifier(termParamList.params))) - case _ => assert(false, "memberInfo.termParamLists contains a type parameter list !") - } + val (evidenceMap, termParamClauses) = extractEvidences(dd.symbol.extendedTermParamLists) + val termParams = termParamClauses.map: tpc => + api.TermParameterList(tpc.params.map(mkParameter(_, c)), paramListModifier(tpc.params)) + val typeParams = dd.symbol.extendedTypeParams.map(td => mkTypeArgument(td, c, evidenceMap(td.symbol))) + val target = ExtensionTarget( extSym.symbol.normalizedName, typeParams, termParams, - extSym.tpt.asSignature(c), + extSym.tpt.asSignature(c, extSym.symbol.owner), extSym.tpt.symbol.dri, extSym.symbol.pos.get.start ) @@ -272,7 +308,7 @@ trait ClassLikeSupport: def getParentsAsLinkToTypes: List[LinkToType] = c.getParentsAsTreeSymbolTuples.map { - (tree, symbol) => LinkToType(tree.asSignature(c), symbol.dri, bareClasslikeKind(symbol)) + (tree, symbol) => LinkToType(tree.asSignature(c, c.symbol, skipThisTypePrefix = true), symbol.dri, bareClasslikeKind(symbol)) } def getParentsAsTreeSymbolTuples: List[(Tree, Symbol)] = @@ -351,45 +387,20 @@ trait ClassLikeSupport: specificKind: (Kind.Def => Kind) = identity ): Member = val method = methodSymbol.tree.asInstanceOf[DefDef] - val paramLists = methodSymbol.nonExtensionParamLists - - val memberInfo = unwrapMemberInfo(c, methodSymbol) - - val unshuffledMemberInfoParamLists = - if methodSymbol.isExtensionMethod && methodSymbol.isRightAssoc then - // Taken from RefinedPrinter.scala - // If you change the names of the clauses below, also change them in right-associative-extension-methods.md - val (leftTyParams, rest1) = memberInfo.paramLists match - case fst :: tail if fst.isType => (List(fst), tail) - case other => (List(), other) - val (leadingUsing, rest2) = rest1.span(_.isUsing) - val (rightTyParams, rest3) = rest2.span(_.isType) - val (rightParam, rest4) = rest3.splitAt(1) - val (leftParam, rest5) = rest4.splitAt(1) - val (trailingUsing, rest6) = rest5.span(_.isUsing) - if leftParam.nonEmpty then - // leftTyParams ::: leadingUsing ::: leftParam ::: trailingUsing ::: rightTyParams ::: rightParam ::: rest6 - // because of takeRight after, this is equivalent to the following: - rightTyParams ::: rightParam ::: rest6 - else - memberInfo.paramLists // it wasn't a binary operator, after all. - else - memberInfo.paramLists - - val croppedUnshuffledMemberInfoParamLists = unshuffledMemberInfoParamLists.takeRight(paramLists.length) - - val basicDefKind: Kind.Def = Kind.Def( - paramLists.zip(croppedUnshuffledMemberInfoParamLists).flatMap{ - case (_: TermParamClause, MemberInfo.EvidenceOnlyParameterList) => Nil - case (pList: TermParamClause, MemberInfo.RegularParameterList(info)) => - Some(Left(api.TermParameterList(pList.params.map( - mkParameter(_, c, paramPrefix, memberInfo = info)), paramListModifier(pList.params) - ))) - case (TypeParamClause(genericTypeList), MemberInfo.TypeParameterList(memInfoTypes)) => - Some(Right(genericTypeList.map(mkTypeArgument(_, c, memInfoTypes, memberInfo.contextBounds)))) - case (_,_) => - assert(false, s"croppedUnshuffledMemberInfoParamLists and SymOps.nonExtensionParamLists disagree on whether this clause is a type or term one") - } + val paramLists = methodSymbol.nonExtensionParamLists.filter: + case TypeParamClause(_) => true + case tpc@TermParamClause(_) => !isEvidence(tpc) + + val evidenceMap = extractEvidences(method.termParamss)._1 + + val basicDefKind: Kind.Def = Kind.Def(paramLists.map: + case TermParamClause(vds) => + Left(api.TermParameterList( + vds.map(mkParameter(_, c, paramPrefix)), + paramListModifier(vds) + )) + case TypeParamClause(genericTypeList) => + Right(genericTypeList.map(td => mkTypeArgument(td, c, evidenceMap(td.symbol)))) ) val methodKind = @@ -408,7 +419,7 @@ trait ClassLikeSupport: )) case _ => Kind.Implicit(basicDefKind, None) - else if methodSymbol.flags.is(Flags.Given) then Kind.Given(basicDefKind, Some(method.returnTpt.tpe.asSignature(c)), extractImplicitConversion(method.returnTpt.tpe)) + else if methodSymbol.flags.is(Flags.Given) then Kind.Given(basicDefKind, Some(method.returnTpt.tpe.asSignature(c, methodSymbol.owner)), extractImplicitConversion(method.returnTpt.tpe)) else specificKind(basicDefKind) val origin = if !methodSymbol.isOverridden then Origin.RegularlyDefined else @@ -424,7 +435,7 @@ trait ClassLikeSupport: mkMember( methodSymbol, methodKind, - method.returnTpt.tpe.asSignature(c), + method.returnTpt.tpe.asSignature(c, methodSymbol), )( modifiers = modifiers, origin = origin, @@ -438,95 +449,109 @@ trait ClassLikeSupport: prefix: Symbol => String = _ => "", isExtendedSymbol: Boolean = false, isGrouped: Boolean = false, - memberInfo: Map[String, TypeRepr] = Map.empty, ) = - val inlinePrefix = if argument.symbol.flags.is(Flags.Inline) then "inline " else "" - val nameIfNotSynthetic = Option.when(!argument.symbol.flags.is(Flags.Synthetic))(argument.symbol.normalizedName) - val name = argument.symbol.normalizedName + val symbol = argument.symbol + val inlinePrefix = if symbol.flags.is(Flags.Inline) then "inline " else "" + val name = symbol.normalizedName + val nameIfNotSynthetic = Option.when(!symbol.flags.is(Flags.Synthetic))(name) + val defaultValue = Option.when(symbol.flags.is(Flags.HasDefault))(Plain(" = ...")) api.TermParameter( - argument.symbol.getAnnotations(), - inlinePrefix + prefix(argument.symbol), + symbol.getAnnotations(), + inlinePrefix + prefix(symbol), nameIfNotSynthetic, - argument.symbol.dri, - memberInfo.get(name).fold(argument.tpt.asSignature(classDef))(_.asSignature(classDef)), - isExtendedSymbol, - isGrouped + symbol.dri, + argument.tpt.asSignature(classDef, symbol.owner) :++ defaultValue, + isExtendedSymbol = isExtendedSymbol, + isGrouped = isGrouped ) def mkTypeArgument( argument: TypeDef, classDef: ClassDef, - memberInfo: Map[String, TypeBounds] = Map.empty, - contextBounds: Map[String, DSignature] = Map.empty, + contextBounds: List[TypeRepr] = Nil, ): TypeParameter = + val symbol = argument.symbol val variancePrefix: "+" | "-" | "" = - if argument.symbol.flags.is(Flags.Covariant) then "+" - else if argument.symbol.flags.is(Flags.Contravariant) then "-" + if symbol.flags.is(Flags.Covariant) then "+" + else if symbol.flags.is(Flags.Contravariant) then "-" else "" - val name = argument.symbol.normalizedName + val name = symbol.normalizedName + val isCaptureVar = ccEnabled && argument.derivesFromCapSet + val normalizedName = if name.matches("_\\$\\d*") then "_" else name - val boundsSignature = memberInfo.get(name).fold(argument.rhs.asSignature(classDef))(_.asSignature(classDef)) - val signature = contextBounds.get(name) match - case None => boundsSignature - case Some(contextBoundsSignature) => - boundsSignature ++ DSignature(Plain(" : ")) ++ contextBoundsSignature + val boundsSignature = argument.rhs.asSignature(classDef, symbol.owner) + val signature = boundsSignature ++ contextBounds.flatMap(tr => + val wrap = tr match + case _: TypeLambda => true + case _ => false + Plain(" : ") +: inParens(tr.asSignature(classDef, symbol.owner), wrap) + ) TypeParameter( - argument.symbol.getAnnotations(), + symbol.getAnnotations(), variancePrefix, normalizedName, - argument.symbol.dri, - signature + symbol.dri, + signature, + isCaptureVar, ) def parseTypeDef(typeDef: TypeDef, classDef: ClassDef): Member = + val symbol = typeDef.symbol def isTreeAbstract(typ: Tree): Boolean = typ match { case TypeBoundsTree(_, _) => true case LambdaTypeTree(params, body) => isTreeAbstract(body) case _ => false } + + val isCaptureVar = ccEnabled && typeDef.derivesFromCapSet + val (generics, tpeTree) = typeDef.rhs match case LambdaTypeTree(params, body) => (params.map(mkTypeArgument(_, classDef)), body) case tpe => (Nil, tpe) - val defaultKind = Kind.Type(!isTreeAbstract(typeDef.rhs), typeDef.symbol.isOpaque, generics).asInstanceOf[Kind.Type] - val kind = if typeDef.symbol.flags.is(Flags.Enum) then Kind.EnumCase(defaultKind) + val defaultKind = Kind.Type(!isTreeAbstract(typeDef.rhs), symbol.isOpaque, generics).asInstanceOf[Kind.Type] + val kind = if symbol.flags.is(Flags.Enum) then Kind.EnumCase(defaultKind) else defaultKind - if typeDef.symbol.flags.is(Flags.Exported) + if symbol.flags.is(Flags.Exported) then { val origin = Some(tpeTree).flatMap { case TypeBoundsTree(l: TypeTree, h: TypeTree) if l.tpe == h.tpe => Some(Link(l.tpe.typeSymbol.owner.name, l.tpe.typeSymbol.owner.dri)) case _ => None } - mkMember(typeDef.symbol, Kind.Exported(kind), tpeTree.asSignature(classDef))( - deprecated = typeDef.symbol.isDeprecated(), + mkMember(symbol, Kind.Exported(kind), tpeTree.asSignature(classDef, symbol.owner))( + deprecated = symbol.isDeprecated(), origin = Origin.ExportedFrom(origin), - experimental = typeDef.symbol.isExperimental() + experimental = symbol.isExperimental() ) } - else mkMember(typeDef.symbol, kind, tpeTree.asSignature(classDef))(deprecated = typeDef.symbol.isDeprecated()) + else mkMember(symbol, kind, tpeTree.asSignature(classDef, symbol.owner))(deprecated = symbol.isDeprecated()) def parseValDef(c: ClassDef, valDef: ValDef): Member = - def defaultKind = if valDef.symbol.flags.is(Flags.Mutable) then Kind.Var else Kind.Val - val memberInfo = unwrapMemberInfo(c, valDef.symbol) - val kind = if valDef.symbol.flags.is(Flags.Implicit) then Kind.Implicit(Kind.Val, extractImplicitConversion(valDef.tpt.tpe)) - else if valDef.symbol.flags.is(Flags.Given) then Kind.Given(Kind.Val, Some(memberInfo.res.asSignature(c)), extractImplicitConversion(valDef.tpt.tpe)) - else if valDef.symbol.flags.is(Flags.Enum) then Kind.EnumCase(Kind.Val) + val symbol = valDef.symbol + def defaultKind = if symbol.flags.is(Flags.Mutable) then Kind.Var else Kind.Val + val sig = valDef.tpt.tpe.asSignature(c, symbol.owner) + val kind = if symbol.flags.is(Flags.Implicit) then Kind.Implicit(Kind.Val, extractImplicitConversion(valDef.tpt.tpe)) + else if symbol.flags.is(Flags.Given) then Kind.Given(Kind.Val, Some(sig), extractImplicitConversion(valDef.tpt.tpe)) + else if symbol.flags.is(Flags.Enum) then Kind.EnumCase(Kind.Val) else defaultKind val modifiers = kind match - case _: Kind.Given => valDef.symbol + case _: Kind.Given => symbol .getExtraModifiers() .filterNot(m => m == Modifier.Lazy || m == Modifier.Final) - case _ => valDef.symbol.getExtraModifiers() - - mkMember(valDef.symbol, kind, memberInfo.res.asSignature(c))( - modifiers = modifiers, - deprecated = valDef.symbol.isDeprecated(), - experimental = valDef.symbol.isExperimental() + case _ => symbol.getExtraModifiers() + + mkMember(symbol, kind, sig)( + // Due to how capture checking encodes update methods (recycling the mutable flag for methods), + // we need to filter out the update modifier here. Otherwise, mutable fields will + // be documented as having the update modifier, which is not correct. + modifiers = modifiers.filterNot(_ == Modifier.Update), + deprecated = symbol.isDeprecated(), + experimental = symbol.isExperimental() ) def mkMember(symbol: Symbol, kind: Kind, signature: DSignature)( @@ -554,102 +579,6 @@ trait ClassLikeSupport: experimental = experimental ) - - case class MemberInfo( - paramLists: List[MemberInfo.ParameterList], - res: TypeRepr, - contextBounds: Map[String, DSignature] = Map.empty, - ){ - val genericTypes: Map[String, TypeBounds] = paramLists.collect{ case MemberInfo.TypeParameterList(types) => types }.headOption.getOrElse(Map()) - - val termParamLists: List[MemberInfo.ParameterList] = paramLists.filter(_.isTerm) - } - - object MemberInfo: - enum ParameterList(val isTerm: Boolean, val isUsing: Boolean): - inline def isType = !isTerm - case EvidenceOnlyParameterList extends ParameterList(isTerm = true, isUsing = false) - case RegularParameterList(m: Map[String, TypeRepr])(isUsing: Boolean) extends ParameterList(isTerm = true, isUsing) - case TypeParameterList(m: Map[String, TypeBounds]) extends ParameterList(isTerm = false, isUsing = false) - - export ParameterList.{RegularParameterList, EvidenceOnlyParameterList, TypeParameterList} - - - - def unwrapMemberInfo(c: ClassDef, symbol: Symbol): MemberInfo = - val qualTypeRepr = if c.symbol.isClassDef then This(c.symbol).tpe else typeForClass(c) - val baseTypeRepr = qualTypeRepr.memberType(symbol) - - def isSyntheticEvidence(name: String) = - if !name.startsWith(NameKinds.ContextBoundParamName.separator) then false else - // This assumes that every parameter that starts with `evidence$` and is implicit is generated by compiler to desugar context bound. - // Howrever, this is just a heuristic, so - // `def foo[A](evidence$1: ClassTag[A]) = 1` - // will be documented as - // `def foo[A: ClassTag] = 1`. - // Scala spec states that `$` should not be used in names and behaviour may be undefiend in such case. - // Documenting method slightly different then its definition is withing the 'undefiend behaviour'. - symbol.paramSymss.flatten.find(_.name == name).exists(p => - p.flags.is(Flags.Given) || p.flags.is(Flags.Implicit)) - - def handlePolyType(memberInfo: MemberInfo, polyType: PolyType): MemberInfo = - val typeParamList = MemberInfo.TypeParameterList(polyType.paramNames.zip(polyType.paramBounds).toMap) - MemberInfo(memberInfo.paramLists :+ typeParamList, polyType.resType) - - def handleMethodType(memberInfo: MemberInfo, methodType: MethodType): MemberInfo = - val rawParams = methodType.paramNames.zip(methodType.paramTypes).toMap - val isUsing = methodType.isImplicit - val (evidences, notEvidences) = rawParams.partition(e => isSyntheticEvidence(e._1)) - - def findParamRefs(t: TypeRepr): Seq[ParamRef] = t match - case paramRef: ParamRef => Seq(paramRef) - case AppliedType(_, args) => args.flatMap(findParamRefs) - case MatchType(bound, scrutinee, cases) => - findParamRefs(bound) ++ findParamRefs(scrutinee) - case _ => Nil - - def nameForRef(ref: ParamRef): String = - val PolyType(names, _, _) = ref.binder: @unchecked - names(ref.paramNum) - - val (paramsThatLookLikeContextBounds, contextBounds) = - evidences.partitionMap { - case (_, AppliedType(tpe, List(typeParam: ParamRef))) => - Right(nameForRef(typeParam) -> tpe.asSignature(c)) - case (name, original) => - findParamRefs(original) match - case Nil => Left((name, original)) - case typeParam :: _ => - val name = nameForRef(typeParam) - val signature = Seq( - Plain("(["), - dotty.tools.scaladoc.Type(name, None), - Plain("]"), - Keyword(" =>> "), - ) ++ original.asSignature(c) ++ Seq(Plain(")")) - Right(name -> signature.toList) - } - - val newParams = notEvidences ++ paramsThatLookLikeContextBounds - - val termParamList = if newParams.isEmpty && contextBounds.nonEmpty - then MemberInfo.EvidenceOnlyParameterList - else MemberInfo.RegularParameterList(newParams)(isUsing) - - - MemberInfo(memberInfo.paramLists :+ termParamList, methodType.resType, contextBounds.toMap) - - def handleByNameType(memberInfo: MemberInfo, byNameType: ByNameType): MemberInfo = - MemberInfo(memberInfo.paramLists, byNameType.underlying) - - def recursivelyCalculateMemberInfo(memberInfo: MemberInfo): MemberInfo = memberInfo.res match - case p: PolyType => recursivelyCalculateMemberInfo(handlePolyType(memberInfo, p)) - case m: MethodType => recursivelyCalculateMemberInfo(handleMethodType(memberInfo, m)) - case b: ByNameType => handleByNameType(memberInfo, b) - case _ => memberInfo - - recursivelyCalculateMemberInfo(MemberInfo(List.empty, baseTypeRepr)) - private def paramListModifier(parameters: Seq[ValDef]): String = if parameters.size > 0 then if parameters(0).symbol.flags.is(Flags.Given) then "using " diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala index 196c3e056b36..8f42a28c2c35 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala @@ -17,7 +17,7 @@ object NameNormalizer { val escaped = escapedName(constructorNormalizedName) escaped } - + def ownerNameChain: List[String] = { import reflect.* if s.isNoSymbol then List.empty @@ -25,8 +25,8 @@ object NameNormalizer { else if s == defn.RootPackage then List.empty else if s == defn.RootClass then List.empty else s.owner.ownerNameChain :+ s.normalizedName - } - + } + def normalizedFullName: String = s.ownerNameChain.mkString(".") diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala index c0308336a2bf..234c2c3cc4f2 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala @@ -5,6 +5,8 @@ import scala.jdk.CollectionConverters._ import SymOps._ +import dotty.tools.scaladoc.cc.CCImport + trait PackageSupport: self: TastyParser => import qctx.reflect._ @@ -13,6 +15,11 @@ trait PackageSupport: def parsePackage(pck: PackageClause): (String, Member) = val name = pck.symbol.fullName + ccFlag = false + pck.stats.foreach { + case CCImport() => ccFlag = true + case _ => + } (name, Member(name, "", pck.symbol.dri, Kind.Package)) def parsePackageObject(pckObj: ClassDef): (String, Member) = diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala index 1a8337e0c6b7..f7cdf62c5458 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala @@ -187,6 +187,9 @@ case class TastyParser( private given qctx.type = qctx + protected var ccFlag: Boolean = false + def ccEnabled: Boolean = !ctx.args.suppressCC && ccFlag + val intrinsicClassDefs = Set( defn.AnyClass, defn.MatchableClass, diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 110ee498a3ac..f347513120b8 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -1,11 +1,13 @@ package dotty.tools.scaladoc package tasty -import scala.jdk.CollectionConverters._ - -import scala.quoted._ +import scala.annotation.* +import scala.jdk.CollectionConverters.* +import scala.quoted.* import scala.util.control.NonFatal +import dotty.tools.scaladoc.cc.* + import NameNormalizer._ import SyntheticsSupport._ @@ -16,36 +18,51 @@ trait TypesSupport: given TreeSyntax: AnyRef with extension (using Quotes)(tpeTree: reflect.Tree) - def asSignature(elideThis: reflect.ClassDef): SSignature = + def asSignature(elideThis: reflect.ClassDef, originalOwner: reflect.Symbol, skipThisTypePrefix: Boolean): SSignature = import reflect._ tpeTree match - case TypeBoundsTree(low, high) => typeBoundsTreeOfHigherKindedType(low.tpe, high.tpe)(using elideThis) - case tpeTree: TypeTree => topLevelProcess(tpeTree.tpe)(using elideThis) - case term: Term => topLevelProcess(term.tpe)(using elideThis) + case TypeBoundsTree(low, high) => typeBoundsTreeOfHigherKindedType(low.tpe, high.tpe, skipThisTypePrefix)(using elideThis, originalOwner, inCC = None) + case tpeTree: TypeTree => topLevelProcess(tpeTree.tpe, skipThisTypePrefix)(using elideThis, originalOwner) + case term: Term => topLevelProcess(term.tpe, skipThisTypePrefix)(using elideThis, originalOwner) + def asSignature(elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + tpeTree.asSignature(elideThis, originalOwner, skipThisTypePrefix = false) given TypeSyntax: AnyRef with extension (using Quotes)(tpe: reflect.TypeRepr) - def asSignature(elideThis: reflect.ClassDef): SSignature = - topLevelProcess(tpe)(using elideThis) + def asSignature(elideThis: reflect.ClassDef, originalOwner: reflect.Symbol, skipThisTypePrefix: Boolean): SSignature = + topLevelProcess(tpe, skipThisTypePrefix)(using elideThis, originalOwner) + def asSignature(elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + tpe.asSignature(elideThis, originalOwner, skipThisTypePrefix = false) private def plain(str: String): SignaturePart = Plain(str) private def keyword(str: String): SignaturePart = Keyword(str) - private def tpe(str: String, dri: DRI): SignaturePart = dotty.tools.scaladoc.Type(str, Some(dri)) + private def tpe(str: String, dri: DRI)(using inCC: Option[Any]): SignaturePart = + if ccEnabled && inCC.isDefined then + dotty.tools.scaladoc.Plain(str) + else + dotty.tools.scaladoc.Type(str, Some(dri)) - private def tpe(str: String): SignaturePart = dotty.tools.scaladoc.Type(str, None) + private def tpe(str: String)(using inCC: Option[Any]): SignaturePart = + if ccEnabled && inCC.isDefined then + dotty.tools.scaladoc.Plain(str) + else + dotty.tools.scaladoc.Type(str, None) - private def inParens(s: SSignature, wrap: Boolean = true) = + protected def inParens(s: SSignature, wrap: Boolean = true) = if wrap then plain("(").l ++ s ++ plain(")").l else s extension (on: SignaturePart) def l: List[SignaturePart] = List(on) - private def tpe(using Quotes)(symbol: reflect.Symbol): SSignature = + private def tpe(using Quotes)(symbol: reflect.Symbol)(using inCC: Option[Any]): SSignature = import SymOps._ val dri: Option[DRI] = Option(symbol).filterNot(_.isHiddenByVisibility).map(_.dri) - dotty.tools.scaladoc.Type(symbol.normalizedName, dri).l + if ccEnabled && inCC.isDefined then // we are in the context of a capture set and want paths to be rendered plainly + dotty.tools.scaladoc.Plain(symbol.normalizedName).l + else + dotty.tools.scaladoc.Type(symbol.normalizedName, dri).l private def commas(lists: List[SSignature]) = lists match case List(single) => single @@ -67,24 +84,28 @@ trait TypesSupport: case _ => false case _ => false - private def topLevelProcess(using Quotes)(tp: reflect.TypeRepr)(using elideThis: reflect.ClassDef): SSignature = + private def topLevelProcess(using Quotes)(tp: reflect.TypeRepr, skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = import reflect._ tp match case ThisType(tpe) => val suffix = List(keyword("this"), plain("."), keyword("type")) - if skipPrefix(tp, elideThis) then suffix - else inner(tpe) ++ plain(".").l ++ suffix - case tpe => inner(tpe) + if skipPrefix(tp, elideThis, originalOwner, skipThisTypePrefix) then suffix + else inner(tpe, skipThisTypePrefix) ++ plain(".").l ++ suffix + case tpe => inner(tpe, skipThisTypePrefix) // TODO #23 add support for all types signatures that make sense private def inner( - using Quotes, + using qctx: Quotes, )( tp: reflect.TypeRepr, + skipThisTypePrefix: Boolean )(using elideThis: reflect.ClassDef, + originalOwner: reflect.Symbol, indent: Int = 0, skipTypeSuffix: Boolean = false, + // inCC means in capture-checking context. If defined, it carries the current capture-set contents. + inCC: Option[List[reflect.TypeRepr]] = None, ): SSignature = import reflect._ def noSupported(name: String): SSignature = @@ -92,80 +113,100 @@ trait TypesSupport: plain(s"Unsupported[$name]").l tp match case OrType(left, right) => - inParens(inner(left), shouldWrapInParens(left, tp, true)) + inParens(inner(left, skipThisTypePrefix), shouldWrapInParens(left, tp, true)) ++ keyword(" | ").l - ++ inParens(inner(right), shouldWrapInParens(right, tp, false)) + ++ inParens(inner(right, skipThisTypePrefix), shouldWrapInParens(right, tp, false)) case AndType(left, right) => - inParens(inner(left), shouldWrapInParens(left, tp, true)) + inParens(inner(left, skipThisTypePrefix), shouldWrapInParens(left, tp, true)) ++ keyword(" & ").l - ++ inParens(inner(right), shouldWrapInParens(right, tp, false)) - case ByNameType(tpe) => keyword("=> ") :: inner(tpe) + ++ inParens(inner(right, skipThisTypePrefix), shouldWrapInParens(right, tp, false)) + case ByNameType(CapturingType(tpe, refs)) => + emitByNameArrow(using qctx)(Some(refs), skipThisTypePrefix) ++ (plain(" ") :: inner(tpe, skipThisTypePrefix)) + case ByNameType(tpe) => + emitByNameArrow(using qctx)(None, skipThisTypePrefix) ++ (plain(" ") :: inner(tpe, skipThisTypePrefix)) case ConstantType(constant) => plain(constant.show).l case ThisType(tpe) => - val prefix = findSupertype(elideThis, tpe.typeSymbol) match - case Some(_) => Nil - case None => inner(tpe) ++ plain(".").l + val prefix = if skipPrefix(tp, elideThis, originalOwner, skipThisTypePrefix) then Nil else inner(tpe, skipThisTypePrefix) ++ plain(".").l val suffix = if skipTypeSuffix then Nil else List(plain("."), keyword("type")) prefix ++ keyword("this").l ++ suffix case AnnotatedType(AppliedType(_, Seq(tpe)), annotation) if isRepeatedAnnotation(annotation) => - inner(tpe) :+ plain("*") + inner(tpe, skipThisTypePrefix) :+ plain("*") case AppliedType(repeatedClass, Seq(tpe)) if isRepeated(repeatedClass) => - inner(tpe) :+ plain("*") + inner(tpe, skipThisTypePrefix) :+ plain("*") + case CapturingType(base, refs) if ccEnabled => + base match + case t @ AppliedType(base, args) if t.isFunctionType => + functionType(base, args, skipThisTypePrefix)(using inCC = Some(refs)) + case t : Refinement if t.isFunctionType => + inner(base, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = skipTypeSuffix, inCC = Some(refs)) + case t if t.isCapSet => emitCaptureSet(refs, skipThisTypePrefix, omitCap = false) + case t if t.isPureClass(elideThis) => inner(base, skipThisTypePrefix) + case t => inner(base, skipThisTypePrefix) ++ emitCapturing(refs, skipThisTypePrefix) case AnnotatedType(tpe, _) => - inner(tpe) + inner(tpe, skipThisTypePrefix) case tl @ TypeLambda(params, paramBounds, AppliedType(tpe, args)) - if paramBounds.map(inner).forall(_.isEmpty) && params.zip(args.map(inner).flatten.map(_.name)).forall(_ == _) => - inner(tpe) + if paramBounds.forall { case TypeBounds(low, hi) => low.typeSymbol == defn.NothingClass && hi.typeSymbol == defn.AnyClass } + && params.length == args.length + && args.zipWithIndex.forall(_ == tl.param(_)) => + // simplify type lambdas such as [X, Y] =>> Map[X, Y] to just Map + inner(tpe, skipThisTypePrefix) case tl @ TypeLambda(params, paramBounds, resType) => plain("[").l ++ commas(params.zip(paramBounds).map { (name, typ) => val normalizedName = if name.matches("_\\$\\d*") then "_" else name - tpe(normalizedName).l ++ inner(typ) + val suffix = if ccEnabled && typ.derivesFrom(CaptureDefs.Caps_CapSet) then List(Keyword("^")) else Nil + tpe(normalizedName).l ++ suffix ++ inner(typ, skipThisTypePrefix) }) ++ plain("]").l ++ keyword(" =>> ").l - ++ inner(resType) + ++ inner(resType, skipThisTypePrefix) case Refinement(parent, "apply", mt : MethodType) if isPolyOrEreased(parent) => val isCtx = isContextualMethod(mt) val sym = defn.FunctionClass(mt.paramTypes.length, isCtx) val at = sym.typeRef.appliedTo(mt.paramTypes :+ mt.resType) - inner(Refinement(at, "apply", mt)) + inner(Refinement(at, "apply", mt), skipThisTypePrefix) case r: Refinement => { //(parent, name, info) + val inCC0 = inCC + given Option[List[TypeRepr]] = None // do not propagate capture set beyond this point def getRefinementInformation(t: TypeRepr): List[TypeRepr] = t match { case r: Refinement => getRefinementInformation(r.parent) :+ r case t => List(t) } def getParamBounds(t: PolyType): SSignature = commas( - t.paramNames.zip(t.paramBounds.map(inner(_))) - .map(b => tpe(b(0)).l ++ b(1)) + t.paramNames.zip(t.paramBounds.map(inner(_, skipThisTypePrefix))).zipWithIndex + .map { case ((name, bound), idx) => + val suffix = if ccEnabled && t.param(idx).derivesFrom(CaptureDefs.Caps_CapSet) then List(Keyword("^")) else Nil + tpe(name).l ++ suffix ++ bound + } ) def getParamList(m: MethodType): SSignature = plain("(").l - ++ m.paramNames.zip(m.paramTypes).map{ case (name, tp) => plain(s"$name: ").l ++ inner(tp)} + ++ m.paramNames.zip(m.paramTypes).map{ case (name, tp) => plain(s"$name: ").l ++ inner(tp, skipThisTypePrefix)} .reduceLeftOption((acc: SSignature, elem: SSignature) => acc ++ plain(", ").l ++ elem).getOrElse(List()) ++ plain(")").l - def parseRefinedElem(name: String, info: TypeRepr, polyTyped: SSignature = Nil): SSignature = ( info match { + def parseRefinedElem(name: String, info: TypeRepr, polyTyped: SSignature = Nil): SSignature = + val ssig = info match case m: MethodType => { val paramList = getParamList(m) - keyword("def ").l ++ plain(name).l ++ polyTyped ++ paramList ++ plain(": ").l ++ inner(m.resType) + keyword("def ").l ++ plain(name).l ++ polyTyped ++ paramList ++ plain(": ").l ++ inner(m.resType, skipThisTypePrefix) } - case t: PolyType => { + case t: PolyType => val paramBounds = getParamBounds(t) - val parsedMethod = parseRefinedElem(name, t.resType) - if (!paramBounds.isEmpty){ + if !paramBounds.isEmpty then parseRefinedElem(name, t.resType, plain("[").l ++ paramBounds ++ plain("]").l) - } else parseRefinedElem(name, t.resType) - } - case ByNameType(tp) => keyword("def ").l ++ plain(s"$name: ").l ++ inner(tp) - case t: TypeBounds => keyword("type ").l ++ plain(name).l ++ inner(t) - case t: TypeRef => keyword("val ").l ++ plain(s"$name: ").l ++ inner(t) - case t: TermRef => keyword("val ").l ++ plain(s"$name: ").l ++ inner(t) + else + parseRefinedElem(name, t.resType, polyTyped = Nil) + case ByNameType(tp) => keyword("def ").l ++ plain(s"$name: ").l ++ inner(tp, skipThisTypePrefix) + case t: TypeBounds => keyword("type ").l ++ plain(name).l ++ inner(t, skipThisTypePrefix) + case t: TypeRef => keyword("val ").l ++ plain(s"$name: ").l ++ inner(t, skipThisTypePrefix) + case t: TermRef => keyword("val ").l ++ plain(s"$name: ").l ++ inner(t, skipThisTypePrefix) case other => noSupported(s"Not supported type in refinement $info") - } ) ++ plain("; ").l + + ssig ++ plain("; ").l def parsePolyFunction(info: TypeRepr): SSignature = info match { case t: PolyType => @@ -181,19 +222,26 @@ trait TypesSupport: val isCtx = isContextualMethod(m) if isDependentMethod(m) then val paramList = getParamList(m) - val arrow = keyword(if isCtx then " ?=> " else " => ").l - val resType = inner(m.resType) - paramList ++ arrow ++ resType + val arrPrefix = if isCtx then "?" else "" + val arrow = + if ccEnabled then + inCC0 match + case None | Some(Nil) => keyword(arrPrefix + "->").l + case Some(List(c)) if c.isCaptureRoot => keyword(arrPrefix + "=>").l + case Some(refs) => keyword(arrPrefix + "->") :: emitCaptureSet(refs, skipThisTypePrefix) + else keyword(arrPrefix + "=>").l + val resType = inner(m.resType, skipThisTypePrefix) + paramList ++ (plain(" ") :: arrow) ++ (plain(" ") :: resType) else val sym = defn.FunctionClass(m.paramTypes.length, isCtx) - inner(sym.typeRef.appliedTo(m.paramTypes :+ m.resType)) + inner(sym.typeRef.appliedTo(m.paramTypes :+ m.resType), skipThisTypePrefix) case other => noSupported("Dependent function type without MethodType refinement") } val refinementInfo = getRefinementInformation(r) val refinedType = refinementInfo.head val refinedElems = refinementInfo.tail.collect{ case r: Refinement => r }.toList - val prefix = if refinedType.typeSymbol != defn.ObjectClass then inner(refinedType) ++ plain(" ").l else Nil + val prefix = if refinedType.typeSymbol != defn.ObjectClass then inner(refinedType, skipThisTypePrefix) ++ plain(" ").l else Nil if (refinedType.typeSymbol.fullName == "scala.PolyFunction" && refinedElems.size == 1) { parsePolyFunction(refinedElems.head.info) } @@ -206,7 +254,7 @@ trait TypesSupport: } case AppliedType(tpe, args) if defn.isTupleClass(tpe.typeSymbol) && args.length > 1 => - inParens(commas(args.map(inner(_)))) + inParens(commas(args.map(inner(_, skipThisTypePrefix)))) case AppliedType(namedTuple, List(AppliedType(tuple1, names), AppliedType(tuple2, types))) if namedTuple.typeSymbol == Symbol.requiredModule("scala.NamedTuple").typeMember("NamedTuple") @@ -215,78 +263,82 @@ trait TypesSupport: val elems = names .collect { case ConstantType(StringConstant(s)) => s } .zip(types) - .map((name, tpe) => plain(name) +: plain(": ") +: inner(tpe)) + .map((name, tpe) => plain(name) +: plain(": ") +: inner(tpe, skipThisTypePrefix)) inParens(commas(elems)) case t @ AppliedType(tpe, List(lhs, rhs)) if isInfix(t) => - inParens(inner(lhs), shouldWrapInParens(lhs, t, true)) + inParens(inner(lhs, skipThisTypePrefix), shouldWrapInParens(lhs, t, true)) ++ plain(" ").l - ++ inner(tpe) + ++ inner(tpe, skipThisTypePrefix) ++ plain(" ").l - ++ inParens(inner(rhs), shouldWrapInParens(rhs, t, false)) + ++ inParens(inner(rhs, skipThisTypePrefix), shouldWrapInParens(rhs, t, false)) case t @ AppliedType(tpe, args) if t.isFunctionType => - val arrow = if t.isContextFunctionType then " ?=> " else " => " - args match - case Nil => Nil - case List(rtpe) => plain("()").l ++ keyword(arrow).l ++ inner(rtpe) - case List(arg, rtpe) => - val wrapInParens = stripAnnotated(arg) match - case _: TermRef | _: TypeRef | _: ConstantType | _: ParamRef => false - case at: AppliedType if !isInfix(at) && !at.isFunctionType && !at.isTupleN => false - case _ => true - inParens(inner(arg), wrapInParens) ++ keyword(arrow).l ++ inner(rtpe) - case _ => - plain("(").l ++ commas(args.init.map(inner(_))) ++ plain(")").l ++ keyword(arrow).l ++ inner(args.last) + val dealiased = t.dealiasKeepOpaques + if t == dealiased then + functionType(tpe, args, skipThisTypePrefix) + else + val AppliedType(tpe, args) = dealiased.asInstanceOf[AppliedType] + functionType(tpe, args, skipThisTypePrefix) case t @ AppliedType(tpe, typeList) => - inner(tpe) ++ plain("[").l ++ commas(typeList.map { t => t match - case _: TypeBounds => keyword("_").l ++ inner(t) - case _ => topLevelProcess(t) + inner(tpe, skipThisTypePrefix) ++ plain("[").l ++ commas(typeList.map { t => t match + case _: TypeBounds => keyword("_").l ++ inner(t, skipThisTypePrefix) + case _ => topLevelProcess(t, skipThisTypePrefix) }) ++ plain("]").l + case t : TypeRef if ccEnabled && t.isCapSet => emitCaptureSet(Nil, skipThisTypePrefix) + case tp @ TypeRef(qual, typeName) => + inline def wrapping = shouldWrapInParens(inner = qual, outer = tp, isLeft = true) qual match { case r: RecursiveThis => tpe(s"this.$typeName").l - case t if skipPrefix(t, elideThis) => - tpe(tp.typeSymbol) - case _: TermRef | _: ParamRef => - val suffix = if tp.typeSymbol == Symbol.noSymbol then tpe(typeName).l else tpe(tp.typeSymbol) - inner(qual)(using skipTypeSuffix = true) ++ plain(".").l ++ suffix case ThisType(tr) => - findSupertype(elideThis, tr.typeSymbol) match + val typeFromSupertypeConstructor = findSupertype(elideThis, tr.typeSymbol) match case Some((sym, AppliedType(tr2, args))) => sym.tree.asInstanceOf[ClassDef].constructor.paramss.headOption match case Some(TypeParamClause(tpc)) => tpc.zip(args).collectFirst { case (TypeDef(name, _), arg) if name == typeName => arg - } match - case Some(tr) => inner(tr) - case None => tpe(tp.typeSymbol) - case _ => tpe(tp.typeSymbol) - case Some(_) => tpe(tp.typeSymbol) - case None => - val sig = inParens(inner(qual)(using skipTypeSuffix = true), shouldWrapInParens(qual, tp, true)) - sig ++ plain(".").l ++ tpe(tp.typeSymbol) + }.map(inner(_, skipThisTypePrefix)) + case _ => None + case _ => None + typeFromSupertypeConstructor.getOrElse: + if skipPrefix(qual, elideThis, originalOwner, skipThisTypePrefix) then + tpe(tp.typeSymbol) + else + val sig = inParens( + inner(qual, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true, inCC = inCC), wrapping) + sig + ++ plain(".").l + ++ tpe(tp.typeSymbol) + + case t if skipPrefix(t, elideThis, originalOwner, skipThisTypePrefix) => + tpe(tp.typeSymbol) + case _: TermRef | _: ParamRef => + val suffix = if tp.typeSymbol == Symbol.noSymbol then tpe(typeName).l else tpe(tp.typeSymbol) + inner(qual, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true, inCC = inCC) + ++ plain(".").l + ++ suffix case _ => - val sig = inParens(inner(qual), shouldWrapInParens(qual, tp, true)) + val sig = inParens(inner(qual, skipThisTypePrefix), wrapping) sig ++ keyword("#").l ++ tpe(tp.typeSymbol) } case tr @ TermRef(qual, typeName) => val prefix = qual match - case t if skipPrefix(t, elideThis) => Nil - case tp => inner(tp)(using skipTypeSuffix = true) ++ plain(".").l + case t if skipPrefix(t, elideThis, originalOwner, skipThisTypePrefix) => Nil + case tp => inner(tp, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true, inCC = inCC) ++ plain(".").l val suffix = if skipTypeSuffix then Nil else List(plain("."), keyword("type")) val typeSig = tr.termSymbol.tree match case vd: ValDef if tr.termSymbol.flags.is(Flags.Module) => - inner(vd.tpt.tpe) + inner(vd.tpt.tpe, skipThisTypePrefix) case _ => plain(typeName).l prefix ++ typeSig ++ suffix case TypeBounds(low, hi) => - if(low == hi) keyword(" = ").l ++ inner(low) - else typeBoundsTreeOfHigherKindedType(low, hi) + if(low == hi) keyword(" = ").l ++ inner(low, skipThisTypePrefix) + else typeBoundsTreeOfHigherKindedType(low, hi, skipThisTypePrefix) case NoPrefix() => Nil @@ -295,11 +347,19 @@ trait TypesSupport: val spaces = " " * (indent) val casesTexts = cases.flatMap { case MatchCase(from, to) => - keyword(caseSpaces + "case ").l ++ inner(from) ++ keyword(" => ").l ++ inner(to)(using indent = indent + 2) ++ plain("\n").l + keyword(caseSpaces + "case ").l + ++ inner(from, skipThisTypePrefix) + ++ keyword(" => ").l + ++ inner(to, skipThisTypePrefix)(using indent = indent + 2, skipTypeSuffix = skipTypeSuffix, inCC = inCC) + ++ plain("\n").l case TypeLambda(_, _, MatchCase(from, to)) => - keyword(caseSpaces + "case ").l ++ inner(from) ++ keyword(" => ").l ++ inner(to)(using indent = indent + 2) ++ plain("\n").l + keyword(caseSpaces + "case ").l + ++ inner(from, skipThisTypePrefix) + ++ keyword(" => ").l + ++ inner(to, skipThisTypePrefix)(using indent = indent + 2, skipTypeSuffix = skipTypeSuffix, inCC = inCC) + ++ plain("\n").l } - inner(sc) ++ keyword(" match ").l ++ plain("{\n").l ++ casesTexts ++ plain(spaces + "}").l + inner(sc, skipThisTypePrefix) ++ keyword(" match ").l ++ plain("{\n").l ++ casesTexts ++ plain(spaces + "}").l case ParamRef(m: MethodType, i) => val suffix = if skipTypeSuffix then Nil else List(plain("."), keyword("type")) @@ -307,13 +367,13 @@ trait TypesSupport: case ParamRef(binder: LambdaType, i) => tpe(binder.paramNames(i)).l - case RecursiveType(tp) => inner(tp) + case RecursiveType(tp) => inner(tp, skipThisTypePrefix) case MatchCase(pattern, rhs) => - keyword("case ").l ++ inner(pattern) ++ keyword(" => ").l ++ inner(rhs) + keyword("case ").l ++ inner(pattern, skipThisTypePrefix) ++ keyword(" => ").l ++ inner(rhs, skipThisTypePrefix) case t: dotty.tools.dotc.core.Types.LazyRef => try { - inner(t.ref(using ctx.compilerContext).asInstanceOf[TypeRepr]) + inner(t.ref(using ctx.compilerContext).asInstanceOf[TypeRepr], skipThisTypePrefix) } catch { case e: AssertionError => tpe("LazyRef(...)").l } @@ -323,28 +383,55 @@ trait TypesSupport: s"${tpe.show(using Printer.TypeReprStructure)}" throw MatchError(msg) - private def typeBound(using Quotes)(t: reflect.TypeRepr, low: Boolean)(using elideThis: reflect.ClassDef) = + private def functionType(using qctx: Quotes)(funTy: reflect.TypeRepr, args: List[reflect.TypeRepr], skipThisTypePrefix: Boolean)(using + elideThis: reflect.ClassDef, + originalOwner: reflect.Symbol, + indent: Int, + skipTypeSuffix: Boolean, + inCC: Option[List[reflect.TypeRepr]], + ): SSignature = + import reflect._ + val arrow = plain(" ") :: (emitFunctionArrow(using qctx)(funTy, inCC, skipThisTypePrefix) ++ plain(" ").l) + given Option[List[TypeRepr]] = None // do not propagate capture set beyond this point + args match + case Nil => Nil + case List(rtpe) => plain("()").l ++ arrow ++ inner(rtpe, skipThisTypePrefix) + case List(arg, rtpe) => + val wrapInParens = stripAnnotated(arg) match + case _: TermRef | _: TypeRef | _: ConstantType | _: ParamRef => false + case at: AppliedType if !isInfix(at) && !at.isFunctionType && !at.isTupleN => false + case _ => true + inParens(inner(arg, skipThisTypePrefix), wrapInParens) ++ arrow ++ inner(rtpe, skipThisTypePrefix) + case _ => + plain("(").l ++ commas(args.init.map(inner(_, skipThisTypePrefix))) ++ plain(")").l ++ arrow ++ inner(args.last, skipThisTypePrefix) + + private def typeBound(using Quotes)(t: reflect.TypeRepr, low: Boolean, skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol) = import reflect._ - val ignore = if (low) t.typeSymbol == defn.NothingClass else t.typeSymbol == defn.AnyClass + val ignore = low && (ccEnabled && t.isCapSetPure + || t.typeSymbol == defn.NothingClass) + || !low && (ccEnabled && t.isCapSetCap + || t.typeSymbol == defn.AnyClass) val prefix = keyword(if low then " >: " else " <: ") t match { - case l: TypeLambda => prefix :: inParens(inner(l)(using elideThis)) - case p: ParamRef => prefix :: inner(p)(using elideThis) - case other if !ignore => prefix :: topLevelProcess(other)(using elideThis) + case l: TypeLambda => prefix :: inParens(inner(l, skipThisTypePrefix)(using elideThis, originalOwner)) + case p: ParamRef => prefix :: inner(p, skipThisTypePrefix)(using elideThis, originalOwner) + case other if !ignore => prefix :: topLevelProcess(other, skipThisTypePrefix)(using elideThis, originalOwner) case _ => Nil } - private def typeBoundsTreeOfHigherKindedType(using Quotes)(low: reflect.TypeRepr, high: reflect.TypeRepr)(using elideThis: reflect.ClassDef) = + private def typeBoundsTreeOfHigherKindedType(using Quotes)(low: reflect.TypeRepr, high: reflect.TypeRepr, skipThisTypePrefix: Boolean)( + using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol, inCC: Option[List[reflect.TypeRepr]] + ) = import reflect._ def regularTypeBounds(low: TypeRepr, high: TypeRepr) = - if low == high then keyword(" = ").l ++ inner(low)(using elideThis) - else typeBound(low, low = true)(using elideThis) ++ typeBound(high, low = false)(using elideThis) + if low == high then keyword(" = ").l ++ inner(low, skipThisTypePrefix)(using elideThis, originalOwner, inCC = inCC) + else typeBound(low, low = true, skipThisTypePrefix)(using elideThis, originalOwner) ++ typeBound(high, low = false, skipThisTypePrefix)(using elideThis, originalOwner) high.match case TypeLambda(params, paramBounds, resType) => if resType.typeSymbol == defn.AnyClass then plain("[").l ++ commas(params.zip(paramBounds).map { (name, typ) => val normalizedName = if name.matches("_\\$\\d*") then "_" else name - tpe(normalizedName).l ++ inner(typ)(using elideThis) + tpe(normalizedName)(using inCC).l ++ inner(typ, skipThisTypePrefix)(using elideThis, originalOwner, inCC = inCC) }) ++ plain("]").l else regularTypeBounds(low, high) @@ -353,18 +440,20 @@ trait TypesSupport: private def findSupertype(using Quotes)(c: reflect.ClassDef, sym: reflect.Symbol) = getSupertypes(c).find((s, t) => s == sym) - private def skipPrefix(using Quotes)(tr: reflect.TypeRepr, elideThis: reflect.ClassDef) = + private def skipPrefix(using Quotes)(tr: reflect.TypeRepr, elideThis: reflect.ClassDef, originalOwner: reflect.Symbol, skipThisTypePrefix: Boolean) = import reflect._ - def collectOwners(owners: Set[Symbol], sym: Symbol): Set[Symbol] = - if sym.flags.is(Flags.Package) then owners - else collectOwners(owners + sym, sym.owner) - val owners = collectOwners(Set.empty, elideThis.symbol) + def findClassOwner(s: Symbol): Symbol = + if s.isClassDef then s + else if s.exists then findClassOwner(s.owner) + else Symbol.noSymbol + + val classOwner = findClassOwner(originalOwner) tr match case NoPrefix() => true - case ThisType(tp) if owners(tp.typeSymbol) => true - case tp if owners(tp.typeSymbol) => true + case ThisType(tp) if tp.typeSymbol == classOwner || tp.typeSymbol == elideThis.symbol => true + case ThisType(_) if skipThisTypePrefix => true case _ => val flags = tr.typeSymbol.flags flags.is(Flags.Module) || flags.is(Flags.Package) @@ -424,3 +513,66 @@ trait TypesSupport: tr match case AnnotatedType(tr, _) => stripAnnotated(tr) case other => other + + private def emitCapability(using Quotes)(ref: reflect.TypeRepr, skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + import reflect._ + ref match + case ReachCapability(c) => emitCapability(c, skipThisTypePrefix) :+ Keyword("*") + case ReadOnlyCapability(c) => emitCapability(c, skipThisTypePrefix) :+ Keyword(".rd") + case OnlyCapability(c, cls) => emitCapability(c, skipThisTypePrefix) ++ List(Plain("."), Keyword("only"), Plain("[")) ++ inner(cls.typeRef, skipThisTypePrefix) :+ Plain("]") + case ThisType(_) => List(Keyword("this")) + case t => inner(t, skipThisTypePrefix)(using skipTypeSuffix = true, inCC = Some(Nil)) + + private def emitCaptureSet(using Quotes)(refs: List[reflect.TypeRepr], skipThisTypePrefix: Boolean, omitCap: Boolean = true)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + import reflect._ + refs match + case List(ref) if omitCap && ref.isCaptureRoot => Nil + case refs => + val res0 = refs.map(x => emitCapability(x, skipThisTypePrefix)) + val res1 = res0 match + case Nil => Nil + case other => other.reduce((r, e) => r ++ (List(Plain(", ")) ++ e)) + Plain("{") :: (res1 ++ List(Plain("}"))) + + // Within the context of `elideThis`, some capabilities can actually be pure. + private def isCapturedInContext(using Quotes)(ref: reflect.TypeRepr)(using elideThis: reflect.ClassDef): Boolean = + import reflect._ + ref match + case t if t.isCaptureRoot => true + case ReachCapability(c) => isCapturedInContext(c) + case ReadOnlyCapability(c) => isCapturedInContext(c) + case ThisType(tr) => !elideThis.symbol.typeRef.isPureClass(elideThis) /* is the current class pure? */ + case t => !t.isPureClass(elideThis) + + private def emitCapturing(using Quotes)(refs: List[reflect.TypeRepr], skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + import reflect._ + val refs0 = refs.filter(isCapturedInContext) + if refs0.isEmpty then Nil else Keyword("^") :: emitCaptureSet(refs0, skipThisTypePrefix) + + private def emitFunctionArrow(using Quotes)(funTy: reflect.TypeRepr, captures: Option[List[reflect.TypeRepr]], skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + import reflect._ + val isContextFun = funTy.isAnyContextFunction || funTy.isAnyImpureContextFunction + val prefix = if isContextFun then "?" else "" + if !ccEnabled then + List(Keyword(prefix + "=>")) + else + val isPureFun = funTy.isAnyFunction || funTy.isAnyContextFunction + val isImpureFun = funTy.isAnyImpureFunction || funTy.isAnyImpureContextFunction + captures match + case None => // means an explicit retains* annotation is missing + if isPureFun then + List(Keyword(prefix + "->")) + else if isImpureFun then + List(Keyword(prefix + "=>")) + else + report.error(s"Cannot emit function arrow: expected a (Context)Function* or Impure(Context)Function*, but got: ${funTy.show}") + Nil + case Some(refs) => + // there is some capture set + refs match + case Nil => List(Keyword(prefix + "->")) + case List(ref) if ref.isCaptureRoot => List(Keyword(prefix + "=>")) + case refs => Keyword(prefix + "->") :: emitCaptureSet(refs, skipThisTypePrefix) + + private def emitByNameArrow(using Quotes)(captures: Option[List[reflect.TypeRepr]], skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + emitFunctionArrow(CaptureDefs.Function1.typeRef, captures, skipThisTypePrefix) diff --git a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala index a3ce15d70c64..31d57fe9a697 100644 --- a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala +++ b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala @@ -6,12 +6,12 @@ import scala.util.chaining._ class ScalaSignatureProvider: val builder = SignatureBuilder() given Conversion[SignatureBuilder, Signature] = bdr => bdr.content - def rawSignature(documentable: Member)(kind: Kind = documentable.kind): MemberSignature = + def rawSignature(documentable: Member)(allowCC: Boolean /*capture checking enabled?*/)(kind: Kind = documentable.kind): MemberSignature = kind match case Kind.Extension(_, m) => extensionSignature(documentable, m) case Kind.Exported(d) => - rawSignature(documentable)(d) + rawSignature(documentable)(allowCC)(d) case d: Kind.Def => methodSignature(documentable, d) case Kind.Constructor(d) => @@ -39,7 +39,7 @@ class ScalaSignatureProvider: case Kind.Val | Kind.Var | Kind.Implicit(Kind.Val, _) => fieldSignature(documentable, kind) case tpe: Kind.Type => - typeSignature(tpe, documentable) + typeSignature(tpe, documentable)(allowCC) case Kind.Package => MemberSignature( Nil, @@ -145,11 +145,11 @@ class ScalaSignatureProvider: case _ => fieldLikeSignature(field, field.kind, None) - private def typeSignature(tpe: Kind.Type, typeDef: Member): MemberSignature = + private def typeSignature(tpe: Kind.Type, typeDef: Member)(allowCC: Boolean = false): MemberSignature = MemberSignature( builder.modifiersAndVisibility(typeDef), builder.kind(tpe), - builder.name(typeDef.name, typeDef.dri), + builder.name(typeDef.name, typeDef.dri, isCaptureVar = allowCC && tpe.isCaptureVar), builder.typeParamList(tpe.typeParams).pipe { bdr => if (!tpe.opaque) { (if tpe.concreate then bdr.plain(" = ") else bdr) diff --git a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala index d28dd6ca18fe..7b3f2fa44acf 100644 --- a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala +++ b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala @@ -3,8 +3,12 @@ package translators case class SignatureBuilder(content: Signature = Nil) extends ScalaSignatureUtils: def plain(str: String): SignatureBuilder = copy(content = content :+ Plain(str)) - def name(str: String, dri: DRI): SignatureBuilder = copy(content = content :+ Name(str, dri)) - def tpe(text: String, dri: Option[DRI]): SignatureBuilder = copy(content = content :+ Type(text, dri)) + def name(str: String, dri: DRI, isCaptureVar: Boolean = false/*under CC*/): SignatureBuilder = + val suffix = if isCaptureVar then List(Keyword("^")) else Nil + copy(content = content ++ (Name(str, dri) :: suffix)) + def tpe(text: String, dri: Option[DRI], isCaptureVar: Boolean = false/*under CC*/): SignatureBuilder = + val suffix = if isCaptureVar then List(Keyword("^")) else Nil + copy(content = content ++ (Type(text, dri) :: suffix)) def keyword(str: String): SignatureBuilder = copy(content = content :+ Keyword(str)) def tpe(text: String, dri: DRI): SignatureBuilder = copy(content = content :+ Type(text, Some(dri))) def signature(s: Signature): SignatureBuilder = copy(content = content ++ s) @@ -90,7 +94,7 @@ case class SignatureBuilder(content: Signature = Nil) extends ScalaSignatureUtil } def typeParamList(on: TypeParameterList) = list(on.toList, List(Plain("[")), List(Plain("]"))){ (bdr, e) => - bdr.annotationsInline(e).keyword(e.variance).tpe(e.name, Some(e.dri)).signature(e.signature) + bdr.annotationsInline(e).keyword(e.variance).tpe(e.name, Some(e.dri), e.isCaptureVar).signature(e.signature) } def functionTermParameters(paramss: Seq[TermParameterList]) = diff --git a/scaladoc/src/scala/tasty/inspector/TastyInspector.scala b/scaladoc/src/scala/tasty/inspector/TastyInspector.scala index 190be6a588a1..315261525a8a 100644 --- a/scaladoc/src/scala/tasty/inspector/TastyInspector.scala +++ b/scaladoc/src/scala/tasty/inspector/TastyInspector.scala @@ -11,7 +11,7 @@ import scala.quoted.runtime.impl.QuotesImpl import dotty.tools.dotc.Compiler import dotty.tools.dotc.Driver import dotty.tools.dotc.Run -import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Contexts.{Context, ctx} import dotty.tools.dotc.core.Mode import dotty.tools.dotc.core.Phases.Phase import dotty.tools.dotc.fromtasty._ @@ -53,21 +53,19 @@ object ScaladocInternalTastyInspector: tastyFiles.foreach(checkFile(_, "tasty")) jars.foreach(checkFile(_, "jar")) - /** - * Added for Scaladoc-only. - * Meant to fix regressions introduces by the switch from old to new TastyInspector: - * https://github.com/scala/scala3/issues/18231 - * https://github.com/scala/scala3/issues/20476 - * Stable TastyInspector API does not support passing compiler context. - */ + /** Added for Scaladoc-only. + * Meant to fix regressions introduces by the switch from old to new TastyInspector: + * - https://github.com/scala/scala3/issues/18231 + * - https://github.com/scala/scala3/issues/20476 + * Stable TastyInspector API does not support passing compiler context. + */ def inspectAllTastyFilesInContext(tastyFiles: List[String], jars: List[String], dependenciesClasspath: List[String])(inspector: Inspector)(using Context): Boolean = checkFiles(tastyFiles, jars) val classes = tastyFiles ::: jars - classes match - case Nil => true - case _ => - val reporter = inspectorDriver(inspector).process(inspectorArgs(dependenciesClasspath, classes), summon[Context]) - !reporter.hasErrors + classes.isEmpty + || !inspectorDriver(inspector) + .process(inspectorArgs(dependenciesClasspath, classes), ctx) + .hasErrors /** Load and process TASTy files using TASTy reflect * @@ -90,7 +88,7 @@ object ScaladocInternalTastyInspector: override def phaseName: String = "tastyInspector" override def runOn(units: List[CompilationUnit])(using ctx0: Context): List[CompilationUnit] = - // NOTE: although this is a phase, do not expect this to be ran with an xsbti.CompileProgress + // NOTE: although this is a phase, do not expect this to be run with an xsbti.CompileProgress val ctx = QuotesCache.init(ctx0.fresh) runOnImpl(units)(using ctx) diff --git a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala index adb9397f1bcd..98656d3b5c4a 100644 --- a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala +++ b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala @@ -128,3 +128,7 @@ class RefinedFunctionTypes extends SignatureTest("refinedFunctionTypes", Signatu class RightAssocExtension extends SignatureTest("rightAssocExtension", SignatureTest.all) class NamedTuples extends SignatureTest("namedTuples", SignatureTest.all) + +class InnerClasses extends SignatureTest("innerClasses", SignatureTest.all) + +class OptionalParams extends SignatureTest("optionalParams", SignatureTest.all) diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index de3700c667a4..4c1453243450 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -340,7 +340,7 @@ object TastyFormat { * is able to read final TASTy documents if the file's * `MinorVersion` is strictly less than the current value. */ - final val ExperimentalVersion: Int = 1 + final val ExperimentalVersion: Int = 0 /**This method implements a binary relation (`<:<`) between two TASTy versions. * diff --git a/tests/coverage/pos/Constructor.scoverage.check b/tests/coverage/pos/Constructor.scoverage.check index a95bb21488b8..ed9ac68752bb 100644 --- a/tests/coverage/pos/Constructor.scoverage.check +++ b/tests/coverage/pos/Constructor.scoverage.check @@ -110,6 +110,23 @@ C Class covtest.C x +161 +162 +13 + +Literal +false +0 +false +1 + +6 +Constructor.scala +covtest +C +Class +covtest.C +x 153 158 13 @@ -120,7 +137,7 @@ false false def x -6 +7 Constructor.scala covtest C @@ -137,7 +154,7 @@ false false f(x) -7 +8 Constructor.scala covtest C @@ -154,7 +171,24 @@ false false x -8 +9 +Constructor.scala +covtest +C +Class +covtest.C +g +188 +189 +16 + +Literal +false +0 +false +2 + +10 Constructor.scala covtest C @@ -171,7 +205,7 @@ false false def g -9 +11 Constructor.scala covtest O @@ -188,7 +222,24 @@ false false def g -10 +12 +Constructor.scala +covtest +O +Object +covtest.O +y +231 +232 +20 + +Literal +false +0 +false +1 + +13 Constructor.scala covtest O @@ -205,7 +256,7 @@ false false def y -11 +14 Constructor.scala covtest O @@ -222,20 +273,3 @@ false false g(y) -12 -Constructor.scala -covtest -O -Object -covtest.O - -237 -238 -21 -y -Ident -false -0 -false -y - diff --git a/tests/coverage/pos/ContextFunctions.scoverage.check b/tests/coverage/pos/ContextFunctions.scoverage.check index 0d616d811ffe..ee5c40b9b1ac 100644 --- a/tests/coverage/pos/ContextFunctions.scoverage.check +++ b/tests/coverage/pos/ContextFunctions.scoverage.check @@ -41,6 +41,23 @@ covtest Imperative Class covtest.Imperative +$anonfun +178 +184 +9 + +Literal +false +0 +false +"name" + +2 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative readName2 121 134 @@ -52,24 +69,24 @@ false false def readName2 -2 +3 ContextFunctions.scala covtest Imperative Class covtest.Imperative readPerson -252 -309 -14 -onError -Apply +243 +247 +13 + +Literal false 0 false -OnError((e) => readName2(using e)(using s)).onError(None) +null -3 +4 ContextFunctions.scala covtest Imperative @@ -77,16 +94,16 @@ Class covtest.Imperative readPerson 252 -295 +309 14 - +onError Apply false 0 false -OnError((e) => readName2(using e)(using s)) +OnError((e) => readName2(using e)(using s)).onError(None) -4 +5 ContextFunctions.scala covtest Imperative @@ -103,7 +120,7 @@ false false readName2(using e)(using s) -5 +6 ContextFunctions.scala covtest Imperative diff --git a/tests/coverage/pos/Enum.scoverage.check b/tests/coverage/pos/Enum.scoverage.check index 6806934e0dec..4ad02971f6cf 100644 --- a/tests/coverage/pos/Enum.scoverage.check +++ b/tests/coverage/pos/Enum.scoverage.check @@ -24,6 +24,57 @@ covtest Planet Class covtest.Planet + +322 +333 +14 + +Literal +false +0 +false +6.67300E-11 + +1 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +359 +386 +15 +/ +Apply +false +0 +false +G * mass / (radius * radius + +2 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +371 +386 +15 +* +Apply +false +0 +false +radius * radius + +3 +Enum.scala +covtest +Planet +Class +covtest.Planet surfaceGravity 338 356 @@ -35,7 +86,24 @@ false false def surfaceGravity -1 +4 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceWeight +432 +458 +16 +* +Apply +false +0 +false +otherMass * surfaceGravity + +5 Enum.scala covtest Planet @@ -52,7 +120,7 @@ false false surfaceGravity -2 +6 Enum.scala covtest Planet @@ -69,7 +137,381 @@ false false def surfaceWeight +7 +Enum.scala +covtest +$anon +Class +covtest.$anon + +492 +501 +18 + +Literal +false +0 +false +3.303e+23 + +8 +Enum.scala +covtest +$anon +Class +covtest.$anon + +503 +511 +18 + +Literal +false +0 +false +2.4397e6 + +9 +Enum.scala +covtest +$anon +Class +covtest.$anon + +545 +554 +19 + +Literal +false +0 +false +4.869e+24 + +10 +Enum.scala +covtest +$anon +Class +covtest.$anon + +556 +564 +19 + +Literal +false +0 +false +6.0518e6 + +11 +Enum.scala +covtest +$anon +Class +covtest.$anon + +598 +607 +20 + +Literal +false +0 +false +5.976e+24 + +12 +Enum.scala +covtest +$anon +Class +covtest.$anon + +609 +618 +20 + +Literal +false +0 +false +6.37814e6 + +13 +Enum.scala +covtest +$anon +Class +covtest.$anon + +652 +661 +21 + +Literal +false +0 +false +6.421e+23 + +14 +Enum.scala +covtest +$anon +Class +covtest.$anon + +663 +671 +21 + +Literal +false +0 +false +3.3972e6 + +15 +Enum.scala +covtest +$anon +Class +covtest.$anon + +705 +712 +22 + +Literal +false +0 +false +1.9e+27 + +16 +Enum.scala +covtest +$anon +Class +covtest.$anon + +716 +724 +22 + +Literal +false +0 +false +7.1492e7 + +17 +Enum.scala +covtest +$anon +Class +covtest.$anon + +758 +767 +23 + +Literal +false +0 +false +5.688e+26 + +18 +Enum.scala +covtest +$anon +Class +covtest.$anon + +769 +777 +23 + +Literal +false +0 +false +6.0268e7 + +19 +Enum.scala +covtest +$anon +Class +covtest.$anon + +811 +820 +24 + +Literal +false +0 +false +8.686e+25 + +20 +Enum.scala +covtest +$anon +Class +covtest.$anon + +822 +830 +24 + +Literal +false +0 +false +2.5559e7 + +21 +Enum.scala +covtest +$anon +Class +covtest.$anon + +864 +873 +25 + +Literal +false +0 +false +1.024e+26 + +22 +Enum.scala +covtest +$anon +Class +covtest.$anon + +875 +883 +25 + +Literal +false +0 +false +2.4746e7 + +23 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +test +970 +1038 +30 +apply +Apply +false +0 +false +ListEnum.Cons(1, ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty))) + +24 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +test +984 +985 +30 + +Literal +false +0 +false +1 + +25 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +test +987 +1037 +30 +apply +Apply +false +0 +false +ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty)) + +26 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +test +1001 +1002 +30 + +Literal +false +0 +false +2 + +27 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +test +1004 +1036 +30 +apply +Apply +false +0 +false +ListEnum.Cons(3, ListEnum.Empty) + +28 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +test +1018 +1019 +30 + +Literal +false +0 +false 3 + +29 Enum.scala covtest EnumTypes @@ -86,7 +528,7 @@ false false println("Example 1: \\n"+emptyList) -4 +30 Enum.scala covtest EnumTypes @@ -103,7 +545,24 @@ false false "Example 1: \\n"+emptyList -5 +31 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +test +1051 +1066 +31 + +Literal +false +0 +false +"Example 1: \\n" + +32 Enum.scala covtest EnumTypes @@ -120,7 +579,7 @@ false false println(s"${list}\\n") -6 +33 Enum.scala covtest EnumTypes @@ -137,7 +596,24 @@ false false s"${list}\\n" -7 +34 +Enum.scala +covtest +EnumTypes +Object +covtest.EnumTypes +calculateEarthWeightOnPlanets +1183 +1222 +35 +/ +Apply +false +0 +false +earthWeight/Planet.Earth.surfaceGravity + +35 Enum.scala covtest EnumTypes @@ -154,7 +630,7 @@ false false Planet.Earth.surfaceGravity -8 +36 Enum.scala covtest EnumTypes @@ -171,7 +647,7 @@ false false for p <- Planet.values do\n println(s"Your weight on $p is ${p.surfaceWeight(mass)}") -9 +37 Enum.scala covtest EnumTypes @@ -181,14 +657,14 @@ calculateEarthWeightOnPlanets 1238 1251 36 -refArrayOps -Apply +values +Select false 0 false Planet.values -10 +38 Enum.scala covtest EnumTypes @@ -205,7 +681,7 @@ false false println(s"Your weight on $p is ${p.surfaceWeight(mass)}") -11 +39 Enum.scala covtest EnumTypes @@ -222,7 +698,7 @@ false false s"Your weight on $p is ${p.surfaceWeight(mass)}" -12 +40 Enum.scala covtest EnumTypes @@ -239,7 +715,7 @@ false false p.surfaceWeight(mass) -13 +41 Enum.scala covtest EnumTypes @@ -256,7 +732,7 @@ false false def calculateEarthWeightOnPlanets -14 +42 Enum.scala covtest EnumTypes @@ -273,7 +749,7 @@ false false println("Example 2:") -15 +43 Enum.scala covtest EnumTypes @@ -290,7 +766,7 @@ false false calculateEarthWeightOnPlanets(80) -16 +44 Enum.scala covtest EnumTypes diff --git a/tests/coverage/pos/ExcludeClass.scoverage.check b/tests/coverage/pos/ExcludeClass.scoverage.check index 5e77f0ce21a1..370410b68091 100644 --- a/tests/coverage/pos/ExcludeClass.scoverage.check +++ b/tests/coverage/pos/ExcludeClass.scoverage.check @@ -25,6 +25,23 @@ Klass2 Class covtest.Klass2 abs +202 +207 +15 +> +Apply +false +0 +false +i > 0 + +1 +ExcludeClass.scala +covtest +Klass2 +Class +covtest.Klass2 +abs 219 220 16 @@ -35,7 +52,24 @@ true false i -1 +2 +ExcludeClass.scala +covtest +Klass2 +Class +covtest.Klass2 +abs +236 +238 +18 +unary_- +Select +false +0 +false +-i + +3 ExcludeClass.scala covtest Klass2 @@ -52,7 +86,7 @@ true false -i -2 +4 ExcludeClass.scala covtest Klass2 diff --git a/tests/coverage/pos/For.scoverage.check b/tests/coverage/pos/For.scoverage.check index 6eeab746b4c5..9942da22d02e 100644 --- a/tests/coverage/pos/For.scoverage.check +++ b/tests/coverage/pos/For.scoverage.check @@ -41,40 +41,6 @@ covtest For$package Object covtest.For$package -testForLoop -52 -59 -4 -to -Apply -false -0 -false -1 to 10 - -2 -For.scala -covtest -For$package -Object -covtest.For$package -testForLoop -52 -53 -4 -intWrapper -Apply -false -0 -false -1 - -3 -For.scala -covtest -For$package -Object -covtest.For$package $anonfun 67 77 @@ -86,7 +52,7 @@ false false println(i) -4 +2 For.scala covtest For$package @@ -103,92 +69,58 @@ false false def testForLoop -5 +3 For.scala covtest For$package Object covtest.For$package f -109 -114 +134 +138 8 -f -DefDef -false -0 -false -def f - -6 -For.scala -covtest -For$package -Object -covtest.For$package -testForAdvanced -141 -183 -9 -foreach -Apply + +Literal false 0 false -for j <- 1 to 10 if f(j) do\n println(j) +true -7 +4 For.scala covtest For$package Object covtest.For$package -testForAdvanced -145 -165 -9 -withFilter -Apply -false -0 -false -j <- 1 to 10 if f(j) - +f +109 +114 8 -For.scala -covtest -For$package -Object -covtest.For$package -testForAdvanced -150 -157 -9 -to -Apply +f +DefDef false 0 false -1 to 10 +def f -9 +5 For.scala covtest For$package Object covtest.For$package testForAdvanced -150 -151 +141 +183 9 -intWrapper +foreach Apply false 0 false -1 +for j <- 1 to 10 if f(j) do\n println(j) -10 +6 For.scala covtest For$package @@ -205,7 +137,7 @@ false false f(j) -11 +7 For.scala covtest For$package @@ -222,7 +154,7 @@ false false println(j) -12 +8 For.scala covtest For$package @@ -239,7 +171,7 @@ false false def testForAdvanced -13 +9 For.scala covtest For$package @@ -256,7 +188,7 @@ false false Nil.foreach(_ => println("user code here")) -14 +10 For.scala covtest For$package @@ -273,7 +205,7 @@ false false println("user code here") -15 +11 For.scala covtest For$package diff --git a/tests/coverage/pos/Givens.scoverage.check b/tests/coverage/pos/Givens.scoverage.check index 4442f329c6b2..b09e369ee076 100644 --- a/tests/coverage/pos/Givens.scoverage.check +++ b/tests/coverage/pos/Givens.scoverage.check @@ -42,6 +42,40 @@ Givens Class covtest.Givens test +182 +190 +11 +== +Apply +false +0 +false +3 == "3" + +2 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +182 +183 +11 + +Literal +false +0 +false +3 + +3 +Givens.scala +covtest +Givens +Class +covtest.Givens +test 196 213 12 @@ -52,7 +86,41 @@ false false println(3 == 5.1) -2 +4 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +204 +212 +12 +== +Apply +false +0 +false +3 == 5.1 + +5 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +204 +205 +12 + +Literal +false +0 +false +3 + +6 Givens.scala covtest Givens @@ -69,7 +137,7 @@ false false def test -3 +7 Givens.scala covtest Givens @@ -86,7 +154,7 @@ false false println(msg) -4 +8 Givens.scala covtest Givens @@ -103,7 +171,7 @@ false false println(ctx.id) -5 +9 Givens.scala covtest Givens @@ -120,7 +188,24 @@ false false def printContext -6 +10 +Givens.scala +covtest +Givens +Class +covtest.Givens +getMessage +348 +358 +18 +toString +Apply +false +0 +false +i.toString + +11 Givens.scala covtest Givens @@ -137,7 +222,7 @@ false false def getMessage -7 +12 Givens.scala covtest Givens @@ -154,7 +239,7 @@ false false Context(0) -8 +13 Givens.scala covtest Givens @@ -171,7 +256,7 @@ false false printContext("test")(using c) -9 +14 Givens.scala covtest Givens @@ -188,7 +273,7 @@ false false printContext(getMessage(123)) -10 +15 Givens.scala covtest Givens @@ -205,7 +290,7 @@ false false getMessage(123) -11 +16 Givens.scala covtest Givens diff --git a/tests/coverage/pos/Inlined.scala b/tests/coverage/pos/Inlined.scala index 5e51f037220f..37519e9f63e3 100644 --- a/tests/coverage/pos/Inlined.scala +++ b/tests/coverage/pos/Inlined.scala @@ -1,6 +1,7 @@ package covtest // Checks that we use the new positions of the inlined code properly +// NOTE (12.08.2025): After recent changes, the inlined nodes will not be tagged in coverage def testInlined(): Unit = val l = 1 assert(l == 1) diff --git a/tests/coverage/pos/Inlined.scoverage.check b/tests/coverage/pos/Inlined.scoverage.check index c74868219b67..cf8d1bcdb0f7 100644 --- a/tests/coverage/pos/Inlined.scoverage.check +++ b/tests/coverage/pos/Inlined.scoverage.check @@ -25,230 +25,26 @@ Inlined$package Object covtest.Inlined$package testInlined -288 -330 -11 -assertFailed -Apply -false -0 -false -scala.runtime.Scala3RunTime.assertFailed() - -1 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -288 -330 -11 -assertFailed -Apply -true -0 -false -scala.runtime.Scala3RunTime.assertFailed() - -2 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -330 -330 -11 - -Literal -true -0 -false - - -3 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -155 -162 -7 -apply -Apply -false -0 -false -List(l) - -4 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -155 -169 -7 -length -Select -false -0 -false -List(l).length - -5 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -288 -330 -11 -assertFailed -Apply -false -0 -false -scala.runtime.Scala3RunTime.assertFailed() - +215 +216 6 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -288 -330 -11 -assertFailed -Apply -true -0 -false -scala.runtime.Scala3RunTime.assertFailed() - -7 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -330 -330 -11 Literal -true -0 -false - - -8 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -180 -187 -8 -apply -Apply false 0 false -List(l) +1 -9 +1 Inlined.scala covtest Inlined$package Object covtest.Inlined$package testInlined -180 +179 194 -8 -length -Select -false -0 -false -List(l).length - -10 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -288 -330 -11 -assertFailed -Apply -false -0 -false -scala.runtime.Scala3RunTime.assertFailed() - -11 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -288 -330 -11 -assertFailed -Apply -true -0 -false -scala.runtime.Scala3RunTime.assertFailed() - -12 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -330 -330 -11 - -Literal -true -0 -false - - -13 -Inlined.scala -covtest -Inlined$package -Object -covtest.Inlined$package -testInlined -86 -101 -4 +5 testInlined DefDef false diff --git a/tests/coverage/pos/InlinedFromLib.scala b/tests/coverage/pos/InlinedFromLib.scala index 1b05e11b7558..7607ded865d7 100644 --- a/tests/coverage/pos/InlinedFromLib.scala +++ b/tests/coverage/pos/InlinedFromLib.scala @@ -2,6 +2,7 @@ package covtest // assert is a `transparent inline` in Predef, // but its source path should not appear in the coverage report. +// NOTE (12.08.2025): After recent changes, the inlined nodes will not be tagged in coverage def testInlined(): Unit = val l = 1 assert(l == 1) diff --git a/tests/coverage/pos/InlinedFromLib.scoverage.check b/tests/coverage/pos/InlinedFromLib.scoverage.check index 5aff5473f6d9..bd7bd1a65b6f 100644 --- a/tests/coverage/pos/InlinedFromLib.scoverage.check +++ b/tests/coverage/pos/InlinedFromLib.scoverage.check @@ -25,230 +25,26 @@ InlinedFromLib$package Object covtest.InlinedFromLib$package testInlined -169 -183 +258 +259 7 -assertFailed -Apply -false -0 -false -assert(l == 1) - -1 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -169 -183 -7 -assertFailed -Apply -true -0 -false -assert(l == 1) - -2 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -169 -183 -7 - -Literal -true -0 -false -assert(l == 1) - -3 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -198 -205 -8 -apply -Apply -false -0 -false -List(l) - -4 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -198 -212 -8 -length -Select -false -0 -false -List(l).length - -5 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -186 -213 -8 -assertFailed -Apply -false -0 -false -assert(l == List(l).length) - -6 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -186 -213 -8 -assertFailed -Apply -true -0 -false -assert(l == List(l).length) - -7 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -186 -213 -8 Literal -true -0 -false -assert(l == List(l).length) - -8 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -223 -230 -9 -apply -Apply false 0 false -List(l) +1 -9 +1 InlinedFromLib.scala covtest InlinedFromLib$package Object covtest.InlinedFromLib$package testInlined -223 +222 237 -9 -length -Select -false -0 -false -List(l).length - -10 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -216 -243 -9 -assertFailed -Apply -false -0 -false -assert(List(l).length == 1) - -11 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -216 -243 -9 -assertFailed -Apply -true -0 -false -assert(List(l).length == 1) - -12 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -216 -243 -9 - -Literal -true -0 -false -assert(List(l).length == 1) - -13 -InlinedFromLib.scala -covtest -InlinedFromLib$package -Object -covtest.InlinedFromLib$package -testInlined -129 -144 -5 +6 testInlined DefDef false diff --git a/tests/coverage/pos/Lift.scoverage.check b/tests/coverage/pos/Lift.scoverage.check index cce8c18c6254..516692cd2503 100644 --- a/tests/coverage/pos/Lift.scoverage.check +++ b/tests/coverage/pos/Lift.scoverage.check @@ -25,6 +25,23 @@ SomeFunctions Class covtest.SomeFunctions f +56 +58 +4 + +Literal +false +0 +false +() + +1 +Lift.scala +covtest +SomeFunctions +Class +covtest.SomeFunctions +f 40 45 4 @@ -35,7 +52,24 @@ false false def f -1 +2 +Lift.scala +covtest +SomeFunctions +Class +covtest.SomeFunctions +g +71 +72 +5 + +Literal +false +0 +false +0 + +3 Lift.scala covtest SomeFunctions @@ -52,7 +86,7 @@ false false def g -2 +4 Lift.scala covtest SomeFunctions @@ -69,7 +103,7 @@ false false SomeFunctions() -3 +5 Lift.scala covtest SomeFunctions @@ -86,7 +120,7 @@ false false def c -4 +6 Lift.scala covtest SomeFunctions @@ -103,24 +137,7 @@ false false c.f(g()) -5 -Lift.scala -covtest -SomeFunctions -Class -covtest.SomeFunctions -test -113 -114 -8 -c -Select -false -0 -false -c - -6 +7 Lift.scala covtest SomeFunctions @@ -137,7 +154,7 @@ false false g() -7 +8 Lift.scala covtest SomeFunctions diff --git a/tests/coverage/pos/Literals.scoverage.check b/tests/coverage/pos/Literals.scoverage.check index cd58a841d5b6..c6ea348844ca 100644 --- a/tests/coverage/pos/Literals.scoverage.check +++ b/tests/coverage/pos/Literals.scoverage.check @@ -42,6 +42,57 @@ Literals$package Object covtest.Literals$package block +119 +121 +5 + +Literal +false +0 +false +12 + +2 +Literals.scala +covtest +Literals$package +Object +covtest.Literals$package +block +124 +128 +6 + +Literal +false +0 +false +true + +3 +Literals.scala +covtest +Literals$package +Object +covtest.Literals$package +block +131 +135 +7 + +Literal +false +0 +false +null + +4 +Literals.scala +covtest +Literals$package +Object +covtest.Literals$package +block 17 26 3 @@ -52,7 +103,7 @@ false false def block -2 +5 Literals.scala covtest Literals$package @@ -69,7 +120,7 @@ false false ??? -3 +6 Literals.scala covtest Literals$package @@ -86,7 +137,7 @@ false false def f -4 +7 Literals.scala covtest Literals$package @@ -103,7 +154,7 @@ false false f(0,1,2)(3) -5 +8 Literals.scala covtest Literals$package diff --git a/tests/coverage/pos/MatchNumbers.scoverage.check b/tests/coverage/pos/MatchNumbers.scoverage.check index 43e01018f0ac..74dd5db8fc87 100644 --- a/tests/coverage/pos/MatchNumbers.scoverage.check +++ b/tests/coverage/pos/MatchNumbers.scoverage.check @@ -25,6 +25,40 @@ MatchNumbers Object covtest.MatchNumbers f +121 +126 +7 +< +Apply +false +0 +false +x < 0 + +1 +MatchNumbers.scala +covtest +MatchNumbers +Object +covtest.MatchNumbers +f +130 +132 +7 + +Literal +false +0 +false +-1 + +2 +MatchNumbers.scala +covtest +MatchNumbers +Object +covtest.MatchNumbers +f 127 132 7 @@ -35,7 +69,7 @@ true false => -1 -1 +3 MatchNumbers.scala covtest MatchNumbers @@ -52,7 +86,24 @@ true false => x -2 +4 +MatchNumbers.scala +covtest +MatchNumbers +Object +covtest.MatchNumbers +f +174 +181 +9 +toInt +Select +false +0 +false +y.toInt + +5 MatchNumbers.scala covtest MatchNumbers @@ -69,7 +120,7 @@ true false => y.toInt -3 +6 MatchNumbers.scala covtest MatchNumbers @@ -86,7 +137,7 @@ false false def f -4 +7 MatchNumbers.scala covtest MatchNumbers @@ -103,7 +154,7 @@ false false f(0) -5 +8 MatchNumbers.scala covtest MatchNumbers diff --git a/tests/coverage/pos/PolymorphicExtensions.scoverage.check b/tests/coverage/pos/PolymorphicExtensions.scoverage.check index 64795070b34f..6eebb8cc94fa 100644 --- a/tests/coverage/pos/PolymorphicExtensions.scoverage.check +++ b/tests/coverage/pos/PolymorphicExtensions.scoverage.check @@ -110,15 +110,15 @@ PolyExt Object covtest.PolyExt -177 -186 -11 -foo +277 +287 +12 +get Apply false 0 false -"str".foo +123.get(0) 6 PolymorphicExtensions.scala @@ -126,16 +126,16 @@ covtest PolyExt Object covtest.PolyExt - -277 -287 -12 -get -Apply +foo +385 +387 +14 + +Literal false 0 false -123.get(0) +42 7 PolymorphicExtensions.scala @@ -177,40 +177,6 @@ covtest PolyExt Object covtest.PolyExt -bar -405 -412 -15 -tap -Apply -false -0 -false -foo.tap - -10 -PolymorphicExtensions.scala -covtest -PolyExt -Object -covtest.PolyExt -bar -405 -408 -15 -foo -Ident -false -0 -false -foo - -11 -PolymorphicExtensions.scala -covtest -PolyExt -Object -covtest.PolyExt $anonfun 413 420 @@ -222,7 +188,7 @@ false false println -12 +10 PolymorphicExtensions.scala covtest PolyExt diff --git a/tests/coverage/pos/PolymorphicMethods.scoverage.check b/tests/coverage/pos/PolymorphicMethods.scoverage.check index b66aaeb92661..01cc80f6a4dc 100644 --- a/tests/coverage/pos/PolymorphicMethods.scoverage.check +++ b/tests/coverage/pos/PolymorphicMethods.scoverage.check @@ -72,19 +72,19 @@ C[String]().f("str", 0) 3 PolymorphicMethods.scala covtest -PolyMeth -Object -covtest.PolyMeth - -147 -158 -7 - -Apply +C +Class +covtest.C +f +221 +223 +10 + +Literal false 0 false -C[String]() +() 4 PolymorphicMethods.scala diff --git a/tests/coverage/pos/Select.scoverage.check b/tests/coverage/pos/Select.scoverage.check index cfe719552e44..a4830c1a6d5c 100644 --- a/tests/coverage/pos/Select.scoverage.check +++ b/tests/coverage/pos/Select.scoverage.check @@ -195,23 +195,6 @@ Select$package Object covtest.Select$package test -263 -273 -19 -instance -Select -false -0 -false -a.instance - -11 -Select.scala -covtest -Select$package -Object -covtest.Select$package -test 345 354 20 @@ -222,7 +205,7 @@ false false a.print() -12 +11 Select.scala covtest Select$package diff --git a/tests/coverage/pos/SimpleMethods.scoverage.check b/tests/coverage/pos/SimpleMethods.scoverage.check index 067bd744177b..03d3c1a440ec 100644 --- a/tests/coverage/pos/SimpleMethods.scoverage.check +++ b/tests/coverage/pos/SimpleMethods.scoverage.check @@ -42,6 +42,23 @@ C Class covtest.C b +60 +66 +5 + +Literal +false +0 +false +return + +2 +SimpleMethods.scala +covtest +C +Class +covtest.C +b 46 51 5 @@ -52,7 +69,24 @@ false false def b -2 +3 +SimpleMethods.scala +covtest +C +Class +covtest.C +c +83 +85 +6 + +Literal +false +0 +false +() + +4 SimpleMethods.scala covtest C @@ -69,7 +103,24 @@ false false def c -3 +5 +SimpleMethods.scala +covtest +C +Class +covtest.C +d +101 +103 +7 + +Literal +false +0 +false +12 + +6 SimpleMethods.scala covtest C @@ -86,7 +137,24 @@ false false def d -4 +7 +SimpleMethods.scala +covtest +C +Class +covtest.C +e +120 +124 +8 + +Literal +false +0 +false +null + +8 SimpleMethods.scala covtest C @@ -103,7 +171,41 @@ false false def e -5 +9 +SimpleMethods.scala +covtest +C +Class +covtest.C +block +149 +158 +11 + +Literal +false +0 +false +"literal" + +10 +SimpleMethods.scala +covtest +C +Class +covtest.C +block +163 +164 +12 + +Literal +false +0 +false +0 + +11 SimpleMethods.scala covtest C @@ -120,7 +222,41 @@ false false def block -6 +12 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +195 +200 +15 + +Literal +false +0 +false +false + +13 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +206 +210 +15 + +Literal +false +0 +false +true + +14 SimpleMethods.scala covtest C @@ -137,7 +273,24 @@ true false true -7 +15 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +220 +225 +16 + +Literal +false +0 +false +false + +16 SimpleMethods.scala covtest C @@ -154,7 +307,7 @@ true false false -8 +17 SimpleMethods.scala covtest C @@ -171,7 +324,41 @@ false false def cond -9 +18 +SimpleMethods.scala +covtest +C +Class +covtest.C +partialCond +260 +265 +19 + +Literal +false +0 +false +false + +19 +SimpleMethods.scala +covtest +C +Class +covtest.C +partialCond +271 +273 +19 + +Literal +false +0 +false +() + +20 SimpleMethods.scala covtest C @@ -188,7 +375,7 @@ true false () -10 +21 SimpleMethods.scala covtest C @@ -205,7 +392,7 @@ true false -11 +22 SimpleMethods.scala covtest C @@ -222,7 +409,7 @@ false false def partialCond -12 +23 SimpleMethods.scala covtest C @@ -239,7 +426,24 @@ false false def new1 -13 +24 +SimpleMethods.scala +covtest +C +Class +covtest.C +tryCatch +330 +332 +24 + +Literal +false +0 +false +() + +25 SimpleMethods.scala covtest C @@ -256,7 +460,24 @@ true false () -14 +26 +SimpleMethods.scala +covtest +C +Class +covtest.C +tryCatch +370 +371 +26 + +Literal +false +0 +false +1 + +27 SimpleMethods.scala covtest C @@ -273,7 +494,7 @@ true false => 1 -15 +28 SimpleMethods.scala covtest C diff --git a/tests/coverage/pos/StructuralTypes.scoverage.check b/tests/coverage/pos/StructuralTypes.scoverage.check index a487ac29c9de..2f0390044f04 100644 --- a/tests/coverage/pos/StructuralTypes.scoverage.check +++ b/tests/coverage/pos/StructuralTypes.scoverage.check @@ -41,6 +41,23 @@ covtest Record Class covtest.Record +$anonfun +159 +171 +6 +== +Apply +false +0 +false +_._1 == name + +2 +StructuralTypes.scala +covtest +Record +Class +covtest.Record selectDynamic 148 176 @@ -52,7 +69,7 @@ false false elems.find(_._1 == name).get -2 +3 StructuralTypes.scala covtest Record @@ -69,7 +86,24 @@ false false def selectDynamic -3 +4 +StructuralTypes.scala +covtest +StructuralTypes +Object +covtest.StructuralTypes +test +270 +307 +13 +apply +Apply +false +0 +false +Record("name" -> "Emma", "age" -> 42) + +5 StructuralTypes.scala covtest StructuralTypes @@ -86,7 +120,7 @@ false false "name" -> "Emma" -4 +6 StructuralTypes.scala covtest StructuralTypes @@ -103,7 +137,7 @@ false false "age" -> 42 -5 +7 StructuralTypes.scala covtest StructuralTypes @@ -120,7 +154,7 @@ false false person.name -6 +8 StructuralTypes.scala covtest StructuralTypes diff --git a/tests/coverage/pos/TypeLambdas.scoverage.check b/tests/coverage/pos/TypeLambdas.scoverage.check index de519038c367..f5779a45268b 100644 --- a/tests/coverage/pos/TypeLambdas.scoverage.check +++ b/tests/coverage/pos/TypeLambdas.scoverage.check @@ -76,6 +76,23 @@ TypeLambdas Object covtest.TypeLambdas test +367 +377 +17 +apply +Apply +false +0 +false +("a", "b") + +4 +TypeLambdas.scala +covtest +TypeLambdas +Object +covtest.TypeLambdas +test 382 396 18 @@ -86,7 +103,7 @@ false false println(tuple) -4 +5 TypeLambdas.scala covtest TypeLambdas diff --git a/tests/coverage/pos/i21695/A.scala b/tests/coverage/pos/i21695/A.scala new file mode 100644 index 000000000000..afe534eac46e --- /dev/null +++ b/tests/coverage/pos/i21695/A.scala @@ -0,0 +1,9 @@ +package example + +trait A { + def x1: Builder[?] + def x2: Service + + def create: String = + x1.addService(x2).build() +} diff --git a/tests/coverage/pos/i21695/Builder.java b/tests/coverage/pos/i21695/Builder.java new file mode 100644 index 000000000000..c7685043ec00 --- /dev/null +++ b/tests/coverage/pos/i21695/Builder.java @@ -0,0 +1,6 @@ +package example; + +public abstract class Builder> { + public abstract String build(); + public abstract T addService(Service service); +} diff --git a/tests/coverage/pos/i21695/Service.java b/tests/coverage/pos/i21695/Service.java new file mode 100644 index 000000000000..624e3c04f67f --- /dev/null +++ b/tests/coverage/pos/i21695/Service.java @@ -0,0 +1,3 @@ +package example; + +public class Service{} diff --git a/tests/coverage/pos/i21695/test.scoverage.check b/tests/coverage/pos/i21695/test.scoverage.check new file mode 100644 index 000000000000..d9ee3a172e3f --- /dev/null +++ b/tests/coverage/pos/i21695/test.scoverage.check @@ -0,0 +1,71 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +i21695/A.scala +example +A +Trait +example.A +create +94 +119 +8 +build +Apply +false +0 +false +x1.addService(x2).build() + +1 +i21695/A.scala +example +A +Trait +example.A +create +108 +110 +8 +x2 +Select +false +0 +false +x2 + +2 +i21695/A.scala +example +A +Trait +example.A +create +69 +79 +7 +create +DefDef +false +0 +false +def create + diff --git a/tests/coverage/pos/i21877.scala b/tests/coverage/pos/i21877.scala new file mode 100644 index 000000000000..13d4c9dfc8e2 --- /dev/null +++ b/tests/coverage/pos/i21877.scala @@ -0,0 +1,21 @@ + + +class Schema[T] +object Schema { + inline def derived[T]: Schema[T] = new Schema[T] +} + +case class Bar(x: Int) + +object Foo { + def foo(x: Int): String = { + val bar = Bar(x) + if (x == 5) "5" else "idk" + bar.toString + } + + implicit val schema: Schema[Bar] = Schema.derived +} + +@main def test() = + Foo.foo(4) + Foo.foo(5) diff --git a/tests/coverage/pos/i21877.scoverage.check b/tests/coverage/pos/i21877.scoverage.check new file mode 100644 index 000000000000..7f95bcb7c010 --- /dev/null +++ b/tests/coverage/pos/i21877.scoverage.check @@ -0,0 +1,224 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +i21877.scala + +Foo +Object +.Foo +foo +169 +175 +12 +apply +Apply +false +0 +false +Bar(x) + +1 +i21877.scala + +Foo +Object +.Foo +foo +184 +190 +13 +== +Apply +false +0 +false +x == 5 + +2 +i21877.scala + +Foo +Object +.Foo +foo +192 +195 +13 + +Literal +false +0 +false +"5" + +3 +i21877.scala + +Foo +Object +.Foo +foo +192 +195 +13 + +Literal +true +0 +false +"5" + +4 +i21877.scala + +Foo +Object +.Foo +foo +201 +221 +13 ++ +Apply +false +0 +false +"idk" + bar.toString + +5 +i21877.scala + +Foo +Object +.Foo +foo +201 +206 +13 + +Literal +false +0 +false +"idk" + +6 +i21877.scala + +Foo +Object +.Foo +foo +209 +221 +13 +toString +Apply +false +0 +false +bar.toString + +7 +i21877.scala + +Foo +Object +.Foo +foo +201 +221 +13 ++ +Apply +true +0 +false +"idk" + bar.toString + +8 +i21877.scala + +Foo +Object +.Foo +foo +127 +134 +11 +foo +DefDef +false +0 +false +def foo + +9 +i21877.scala + +i21877$package +Object +.i21877$package +test +303 +313 +20 +foo +Apply +false +0 +false +Foo.foo(4) + +10 +i21877.scala + +i21877$package +Object +.i21877$package +test +316 +326 +21 +foo +Apply +false +0 +false +Foo.foo(5) + +11 +i21877.scala + +i21877$package +Object +.i21877$package +test +282 +296 +19 +test +DefDef +false +0 +false +@main def test + diff --git a/tests/coverage/pos/macro-late-suspend/test.scoverage.check b/tests/coverage/pos/macro-late-suspend/test.scoverage.check index f962705ed2ce..051f30e08e67 100644 --- a/tests/coverage/pos/macro-late-suspend/test.scoverage.check +++ b/tests/coverage/pos/macro-late-suspend/test.scoverage.check @@ -53,23 +53,6 @@ false private def mkVisitorTypeImpl 3 -macro-late-suspend/Test.scala -example -Test -Object -example.Test - -102 -121 -8 - -Apply -false -0 -false -mkVisitorType[Test] - -4 macro-late-suspend/UsesTest.scala example UsesTest$package diff --git a/tests/coverage/pos/scoverage-samples-case.scoverage.check b/tests/coverage/pos/scoverage-samples-case.scoverage.check index 4b67fa77541c..a0fa8febcd99 100644 --- a/tests/coverage/pos/scoverage-samples-case.scoverage.check +++ b/tests/coverage/pos/scoverage-samples-case.scoverage.check @@ -24,6 +24,23 @@ org.scoverage.samples PriceEngine Class org.scoverage.samples.PriceEngine + +293 +298 +14 + +Literal +false +0 +false +"abc" + +1 +scoverage-samples-case.scala +org.scoverage.samples +PriceEngine +Class +org.scoverage.samples.PriceEngine $anonfun 362 368 @@ -35,7 +52,7 @@ false false stop() -1 +2 scoverage-samples-case.scala org.scoverage.samples PriceEngine @@ -52,7 +69,7 @@ true false =>\n stop() -2 +3 scoverage-samples-case.scala org.scoverage.samples PriceEngine @@ -69,7 +86,7 @@ false false stop() -3 +4 scoverage-samples-case.scala org.scoverage.samples PriceEngine @@ -86,7 +103,7 @@ true false =>\n stop() -4 +5 scoverage-samples-case.scala org.scoverage.samples PriceEngine @@ -103,7 +120,24 @@ false false def receive -5 +6 +scoverage-samples-case.scala +org.scoverage.samples +PriceEngine +Class +org.scoverage.samples.PriceEngine +stop +443 +462 +25 +!= +Apply +false +0 +false +cancellable != null + +7 scoverage-samples-case.scala org.scoverage.samples PriceEngine @@ -120,7 +154,7 @@ false false println("stop") -6 +8 scoverage-samples-case.scala org.scoverage.samples PriceEngine @@ -137,7 +171,7 @@ true false println("stop") -7 +9 scoverage-samples-case.scala org.scoverage.samples PriceEngine @@ -154,7 +188,7 @@ true false -8 +10 scoverage-samples-case.scala org.scoverage.samples PriceEngine diff --git a/tests/coverage/pos/scoverage-samples-implicit-class.scoverage.check b/tests/coverage/pos/scoverage-samples-implicit-class.scoverage.check index f9bb9e0cd6a3..1c349a5de5c8 100644 --- a/tests/coverage/pos/scoverage-samples-implicit-class.scoverage.check +++ b/tests/coverage/pos/scoverage-samples-implicit-class.scoverage.check @@ -25,15 +25,15 @@ CreditEngine Class org.scoverage.samples.CreditEngine $anonfun -263 -276 -11 -! +245 +255 +10 +< Apply false 0 false -"if 1" ! "xd" +req < 2000 1 scoverage-samples-implicit-class.scala @@ -43,14 +43,14 @@ Class org.scoverage.samples.CreditEngine $anonfun 263 -269 +276 11 -StringOpssssss +! Apply false 0 false -"if 1" +"if 1" ! "xd" 2 scoverage-samples-implicit-class.scala @@ -161,23 +161,6 @@ StringOpssssss Class org.scoverage.samples.StringOpssssss ! -160 -167 -5 -+ -Apply -false -0 -false -s + "!" - -9 -scoverage-samples-implicit-class.scala -org.scoverage.samples -StringOpssssss -Class -org.scoverage.samples.StringOpssssss -! 124 129 5 @@ -188,7 +171,7 @@ false false def ! -10 +9 scoverage-samples-implicit-class.scala org.scoverage.samples scoverage-samples-implicit-class$package diff --git a/tests/coverage/run/currying/test.scoverage.check b/tests/coverage/run/currying/test.scoverage.check index abc1876942db..5d1b4233a226 100644 --- a/tests/coverage/run/currying/test.scoverage.check +++ b/tests/coverage/run/currying/test.scoverage.check @@ -25,6 +25,23 @@ Test Object .Test f1 +48 +53 +2 ++ +Apply +false +0 +false +a+b+c + +1 +currying/test.scala + +Test +Object +.Test +f1 15 21 2 @@ -35,7 +52,24 @@ false false def f1 -1 +2 +currying/test.scala + +Test +Object +.Test +$anonfun +105 +110 +4 ++ +Apply +false +0 +false +a+b+c + +3 currying/test.scala Test @@ -52,7 +86,24 @@ false false def f2 -2 +4 +currying/test.scala + +Test +Object +.Test +$anonfun +166 +171 +7 ++ +Apply +false +0 +false +a+b+c + +5 currying/test.scala Test @@ -69,7 +120,24 @@ false false def g1 -3 +6 +currying/test.scala + +Test +Object +.Test +g2 +226 +231 +9 ++ +Apply +false +0 +false +a+b+c + +7 currying/test.scala Test @@ -86,7 +154,7 @@ false false def g2 -4 +8 currying/test.scala Test @@ -103,7 +171,7 @@ false false println(f1(0)(1)(2)) -5 +9 currying/test.scala Test @@ -120,7 +188,7 @@ false false f1(0)(1)(2) -6 +10 currying/test.scala Test @@ -137,7 +205,7 @@ false false println(f2(0)(1)(2)) -7 +11 currying/test.scala Test @@ -154,58 +222,7 @@ false false f2(0)(1)(2) -8 -currying/test.scala - -Test -Object -.Test -main -310 -318 -13 -apply -Apply -false -0 -false -f2(0)(1) - -9 -currying/test.scala - -Test -Object -.Test -main -310 -315 -13 -apply -Apply -false -0 -false -f2(0) - -10 -currying/test.scala - -Test -Object -.Test -main -310 -312 -13 -f2 -Ident -false -0 -false -f2 - -11 +12 currying/test.scala Test @@ -222,7 +239,7 @@ false false println(g1(using 0)(using 1)(using 2)) -12 +13 currying/test.scala Test @@ -239,7 +256,7 @@ false false g1(using 0)(using 1)(using 2) -13 +14 currying/test.scala Test @@ -256,7 +273,7 @@ false false println(g2(using 0)(using 1)(using 2)) -14 +15 currying/test.scala Test @@ -273,7 +290,7 @@ false false g2(using 0)(using 1)(using 2) -15 +16 currying/test.scala Test diff --git a/tests/coverage/run/erased/test.scala b/tests/coverage/run/erased/test.scala index 6645020cac80..caab36b34066 100644 --- a/tests/coverage/run/erased/test.scala +++ b/tests/coverage/run/erased/test.scala @@ -1,8 +1,8 @@ import scala.language.experimental.erasedDefinitions -erased def parameterless: String = "y" +inline def parameterless: String = "y" -erased def e(erased x: String): String = "x" +inline def e(erased x: String): String = "x" def foo(erased a: String)(b: String): String = println(s"foo(a)($b)") b diff --git a/tests/coverage/run/extend-case-class/test.scoverage.check b/tests/coverage/run/extend-case-class/test.scoverage.check index b355140d2520..107cef2cc371 100644 --- a/tests/coverage/run/extend-case-class/test.scoverage.check +++ b/tests/coverage/run/extend-case-class/test.scoverage.check @@ -21,6 +21,91 @@ 0 extend-case-class/test.scala +DecimalConf +Object +.DecimalConf + +194 +198 +4 + +Literal +false +0 +false +6178 + +1 +extend-case-class/test.scala + +DecimalConf +Object +.DecimalConf + +200 +203 +4 + +Literal +false +0 +false +308 + +2 +extend-case-class/test.scala + +test$package +Object +.test$package +Test +239 +279 +8 +apply +Apply +false +0 +false +DecimalConf(MathContext.DECIMAL32, 1, 0) + +3 +extend-case-class/test.scala + +test$package +Object +.test$package +Test +274 +275 +8 + +Literal +false +0 +false +1 + +4 +extend-case-class/test.scala + +test$package +Object +.test$package +Test +277 +278 +8 + +Literal +false +0 +false +0 + +5 +extend-case-class/test.scala + test$package Object .test$package @@ -35,7 +120,7 @@ false false println(c.scaleLimit) -1 +6 extend-case-class/test.scala test$package @@ -52,7 +137,7 @@ false false println(DecimalConf.scaleLimit) -2 +7 extend-case-class/test.scala test$package diff --git a/tests/coverage/run/i16940/test.scoverage.check b/tests/coverage/run/i16940/test.scoverage.check index 357080ba9da8..eb8cd5d7d61c 100644 --- a/tests/coverage/run/i16940/test.scoverage.check +++ b/tests/coverage/run/i16940/test.scoverage.check @@ -59,23 +59,6 @@ Test Object .Test -371 -454 -20 -sequence -Apply -false -0 -false -Future.sequence(Seq(brokenSynchronizedBlock(false), brokenSynchronizedBlock(true))) - -3 -i16940/i16940.scala - -Test -Object -.Test - 387 453 20 @@ -86,7 +69,7 @@ false false Seq(brokenSynchronizedBlock(false), brokenSynchronizedBlock(true)) -4 +3 i16940/i16940.scala Test @@ -103,7 +86,7 @@ false false brokenSynchronizedBlock(false) -5 +4 i16940/i16940.scala Test @@ -120,7 +103,7 @@ false false brokenSynchronizedBlock(true) -6 +5 i16940/i16940.scala Test @@ -137,75 +120,58 @@ false false println(test) -7 +6 i16940/i16940.scala Test Object .Test -$anonfun -508 -525 -23 -assertFailed + +539 +540 +25 +DurationInt Apply false 0 false -assert(test == 2) +3 -8 +7 i16940/i16940.scala Test Object .Test -$anonfun -508 -525 -23 -assertFailed -Apply -true -0 + +539 +548 +25 +seconds +Select false -assert(test == 2) - -9 -i16940/i16940.scala - -Test -Object -.Test -$anonfun -508 -525 -23 - -Literal -true 0 false -assert(test == 2) +3.seconds -10 +8 i16940/i16940.scala -Test +i16940$package Object -.Test +.i16940$package -539 -548 -25 -seconds -Select +125 +126 +5 + +Literal false 0 false -3.seconds +0 -11 +9 i16940/i16940.scala i16940$package @@ -222,7 +188,7 @@ false false Future {\n if (option) {\n Thread.sleep(500)\n }\n synchronized {\n val tmp = test\n Thread.sleep(1000)\n test = tmp + 1\n }\n} -12 +10 i16940/i16940.scala i16940$package @@ -239,7 +205,7 @@ false false Thread.sleep(500) -13 +11 i16940/i16940.scala i16940$package @@ -256,7 +222,7 @@ true false {\n Thread.sleep(500)\n } -14 +12 i16940/i16940.scala i16940$package @@ -273,7 +239,7 @@ true false -15 +13 i16940/i16940.scala i16940$package @@ -290,7 +256,7 @@ false false synchronized {\n val tmp = test\n Thread.sleep(1000)\n test = tmp + 1\n } -16 +14 i16940/i16940.scala i16940$package @@ -307,7 +273,24 @@ false false Thread.sleep(1000) -17 +15 +i16940/i16940.scala + +i16940$package +Object +.i16940$package +brokenSynchronizedBlock +310 +317 +14 ++ +Apply +false +0 +false +tmp + 1 + +16 i16940/i16940.scala i16940$package diff --git a/tests/coverage/run/i18233-min/test.scoverage.check b/tests/coverage/run/i18233-min/test.scoverage.check index 7570ebaaed96..6bc38ed61a85 100644 --- a/tests/coverage/run/i18233-min/test.scoverage.check +++ b/tests/coverage/run/i18233-min/test.scoverage.check @@ -42,23 +42,6 @@ Test Object .Test -139 -144 -11 -aList -Ident -false -0 -false -aList - -2 -i18233-min/i18233-min.scala - -Test -Object -.Test - 148 168 12 @@ -69,24 +52,7 @@ false false println(anotherList) -3 -i18233-min/i18233-min.scala - -Test -Object -.Test - -156 -167 -12 -anotherList -Ident -false -0 -false -anotherList - -4 +2 i18233-min/i18233-min.scala i18233-min$package @@ -103,7 +69,7 @@ false false List(Array[String]()*) -5 +3 i18233-min/i18233-min.scala i18233-min$package @@ -120,7 +86,7 @@ false false Array[String]() -6 +4 i18233-min/i18233-min.scala i18233-min$package @@ -137,7 +103,7 @@ false false def aList -7 +5 i18233-min/i18233-min.scala i18233-min$package @@ -154,6 +120,40 @@ false false Array("abc", "def") +6 +i18233-min/i18233-min.scala + +i18233-min$package +Object +.i18233-min$package +arr +56 +61 +5 + +Literal +false +0 +false +"abc" + +7 +i18233-min/i18233-min.scala + +i18233-min$package +Object +.i18233-min$package +arr +63 +68 +5 + +Literal +false +0 +false +"def" + 8 i18233-min/i18233-min.scala diff --git a/tests/coverage/run/inheritance/test.scoverage.check b/tests/coverage/run/inheritance/test.scoverage.check index 387a080463e2..48b8e02d59e1 100644 --- a/tests/coverage/run/inheritance/test.scoverage.check +++ b/tests/coverage/run/inheritance/test.scoverage.check @@ -21,6 +21,23 @@ 0 inheritance/test.scala +B +Class +.B + +61 +62 +2 + +Literal +false +0 +false +0 + +1 +inheritance/test.scala + C1 Class .C1 @@ -35,7 +52,24 @@ false false println("block") +2 +inheritance/test.scala + +C1 +Class +.C1 + +102 +103 +3 + +Literal +false +0 +false 1 + +3 inheritance/test.scala C2 @@ -52,7 +86,7 @@ false false A(2,2) -2 +4 inheritance/test.scala test$package @@ -69,7 +103,7 @@ false false println(C1().x) -3 +5 inheritance/test.scala test$package @@ -86,7 +120,7 @@ false false C1() -4 +6 inheritance/test.scala test$package @@ -103,7 +137,7 @@ false false println(C2().x) -5 +7 inheritance/test.scala test$package @@ -120,7 +154,7 @@ false false C2() -6 +8 inheritance/test.scala test$package diff --git a/tests/coverage/run/inline-def/test.scoverage.check b/tests/coverage/run/inline-def/test.scoverage.check index 17fa7c049107..7ca855c41008 100644 --- a/tests/coverage/run/inline-def/test.scoverage.check +++ b/tests/coverage/run/inline-def/test.scoverage.check @@ -76,23 +76,6 @@ test$package Object .test$package Test -134 -148 -8 -toString -Apply -false -0 -false -"foo".toString - -4 -inline-def/test.scala - -test$package -Object -.test$package -Test 263 277 16 @@ -103,24 +86,7 @@ false false println(a.bar) -5 -inline-def/test.scala - -test$package -Object -.test$package -Test -176 -190 -9 -toString -Apply -false -0 -false -"bar".toString - -6 +4 inline-def/test.scala test$package @@ -137,7 +103,7 @@ false false println(b.foo) -7 +5 inline-def/test.scala test$package @@ -154,7 +120,7 @@ false false b.foo -8 +6 inline-def/test.scala test$package diff --git a/tests/coverage/run/interpolation/test.scoverage.check b/tests/coverage/run/interpolation/test.scoverage.check index 37562dab5509..3147a19a61a0 100644 --- a/tests/coverage/run/interpolation/test.scoverage.check +++ b/tests/coverage/run/interpolation/test.scoverage.check @@ -127,15 +127,15 @@ Test Object .Test main -229 -278 -11 -map -Apply +200 +203 +10 + +Literal false 0 false -xs.zipWithIndex.map((s, i) => println(s"$i: $s")) +"d" 7 interpolation/test.scala @@ -144,17 +144,85 @@ Test Object .Test main +205 +208 +10 + +Literal +false +0 +false +"o" + +8 +interpolation/test.scala + +Test +Object +.Test +main +210 +213 +10 + +Literal +false +0 +false +"t" + +9 +interpolation/test.scala + +Test +Object +.Test +main +215 +218 +10 + +Literal +false +0 +false +"t" + +10 +interpolation/test.scala + +Test +Object +.Test +main +220 +223 +10 + +Literal +false +0 +false +"y" + +11 +interpolation/test.scala + +Test +Object +.Test +main 229 -244 +278 11 -zipWithIndex -Select +map +Apply false 0 false -xs.zipWithIndex +xs.zipWithIndex.map((s, i) => println(s"$i: $s")) -8 +12 interpolation/test.scala Test @@ -171,7 +239,7 @@ false false println(s"$i: $s") -9 +13 interpolation/test.scala Test @@ -188,7 +256,7 @@ false false s"$i: $s" -10 +14 interpolation/test.scala Test @@ -205,7 +273,7 @@ false false println(simple(1, "abc")) -11 +15 interpolation/test.scala Test @@ -222,7 +290,7 @@ false false simple(1, "abc") -12 +16 interpolation/test.scala Test @@ -239,7 +307,7 @@ false false println(hexa(127)) -13 +17 interpolation/test.scala Test @@ -256,7 +324,7 @@ false false hexa(127) -14 +18 interpolation/test.scala Test @@ -273,7 +341,7 @@ false false println(raw"a\\nb") -15 +19 interpolation/test.scala Test @@ -290,7 +358,7 @@ false false raw"a\\nb" -16 +20 interpolation/test.scala Test diff --git a/tests/coverage/run/java-methods/test.scoverage.check b/tests/coverage/run/java-methods/test.scoverage.check index 891af1804831..679a5b7dbff4 100644 --- a/tests/coverage/run/java-methods/test.scoverage.check +++ b/tests/coverage/run/java-methods/test.scoverage.check @@ -58,6 +58,23 @@ java-methods/test.scala test$package Object .test$package +$anonfun +124 +126 +6 + +Literal +false +0 +false +() + +3 +java-methods/test.scala + +test$package +Object +.test$package Test 140 152 @@ -69,7 +86,7 @@ false false JavaObject() -3 +4 java-methods/test.scala test$package @@ -86,7 +103,7 @@ false false obj.f() -4 +5 java-methods/test.scala test$package @@ -103,7 +120,7 @@ false false println(obj.identity[Int](0)) -5 +6 java-methods/test.scala test$package @@ -120,7 +137,7 @@ false false obj.identity[Int](0) -6 +7 java-methods/test.scala test$package @@ -137,7 +154,7 @@ false false println("ok!") -7 +8 java-methods/test.scala test$package diff --git a/tests/coverage/run/lifting-bool/test.scoverage.check b/tests/coverage/run/lifting-bool/test.scoverage.check index 5eb3d864939f..7ed81d8b535c 100644 --- a/tests/coverage/run/lifting-bool/test.scoverage.check +++ b/tests/coverage/run/lifting-bool/test.scoverage.check @@ -76,6 +76,40 @@ test$package Object .test$package Test +101 +120 +8 +|| +Apply +false +0 +false +true || notCalled() + +4 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +101 +105 +8 + +Literal +false +0 +false +true + +5 +lifting-bool/test.scala + +test$package +Object +.test$package +Test 109 120 8 @@ -86,7 +120,41 @@ false false notCalled() -4 +6 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +150 +170 +9 +&& +Apply +false +0 +false +false && notCalled() + +7 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +150 +155 +9 + +Literal +false +0 +false +false + +8 lifting-bool/test.scala test$package @@ -103,7 +171,41 @@ false false notCalled() -5 +9 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +200 +230 +10 +|| +Apply +false +0 +false +(true || false) || notCalled() + +10 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +201 +205 +10 + +Literal +false +0 +false +true + +11 lifting-bool/test.scala test$package @@ -120,7 +222,75 @@ false false notCalled() -6 +12 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +249 +278 +11 +&& +Apply +false +0 +false +true && (false && notCalled() + +13 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +249 +253 +11 + +Literal +false +0 +false +true + +14 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +258 +278 +11 +&& +Apply +false +0 +false +false && notCalled() + +15 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +258 +263 +11 + +Literal +false +0 +false +false + +16 lifting-bool/test.scala test$package @@ -137,7 +307,41 @@ false false notCalled() -7 +17 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +299 +329 +12 +&& +Apply +false +0 +false +(true && false) && notCalled() + +18 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +300 +304 +12 + +Literal +false +0 +false +true + +19 lifting-bool/test.scala test$package @@ -154,7 +358,7 @@ false false notCalled() -8 +20 lifting-bool/test.scala test$package @@ -171,7 +375,7 @@ false false println(s"$a $b $c $d $e") -9 +21 lifting-bool/test.scala test$package @@ -188,7 +392,7 @@ false false s"$a $b $c $d $e" -10 +22 lifting-bool/test.scala test$package @@ -205,7 +409,7 @@ false false f(true, false) -11 +23 lifting-bool/test.scala test$package @@ -222,7 +426,7 @@ false false println(x) -12 +24 lifting-bool/test.scala test$package @@ -239,7 +443,41 @@ false false f(true || notCalled(), false && notCalled()) -13 +25 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +424 +443 +18 +|| +Apply +false +0 +false +true || notCalled() + +26 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +424 +428 +18 + +Literal +false +0 +false +true + +27 lifting-bool/test.scala test$package @@ -256,7 +494,41 @@ false false notCalled() -14 +28 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +445 +465 +18 +&& +Apply +false +0 +false +false && notCalled() + +29 +lifting-bool/test.scala + +test$package +Object +.test$package +Test +445 +450 +18 + +Literal +false +0 +false +false + +30 lifting-bool/test.scala test$package @@ -273,7 +545,7 @@ false false notCalled() -15 +31 lifting-bool/test.scala test$package @@ -290,7 +562,7 @@ false false println(x) -16 +32 lifting-bool/test.scala test$package diff --git a/tests/coverage/run/lifting/test.scoverage.check b/tests/coverage/run/lifting/test.scoverage.check index 136b8e2e4fbb..f876ba013f46 100644 --- a/tests/coverage/run/lifting/test.scoverage.check +++ b/tests/coverage/run/lifting/test.scoverage.check @@ -42,15 +42,15 @@ Vals Class .Vals -41 -57 -3 -:: -Apply +27 +28 +2 + +Literal false 0 false -l :: List(1,2,3) +1 2 lifting/test.scala @@ -59,66 +59,66 @@ Vals Class .Vals -46 +41 57 3 -apply +:: Apply false 0 false -List(1,2,3) +l :: List(1,2,3) 3 lifting/test.scala -A +Vals Class -.A -msg -104 -136 -6 -+ -Apply +.Vals + +51 +52 +3 + +Literal false 0 false -"string" + a + "." + b + "." + c +1 4 lifting/test.scala -A +Vals Class -.A -msg -104 -132 -6 -+ -Apply +.Vals + +53 +54 +3 + +Literal false 0 false -"string" + a + "." + b + "." +2 5 lifting/test.scala -A +Vals Class -.A -msg -104 -126 -6 -+ -Apply +.Vals + +55 +56 +3 + +Literal false 0 false -"string" + a + "." + b +3 6 lifting/test.scala @@ -128,14 +128,14 @@ Class .A msg 104 -122 +136 6 + Apply false 0 false -"string" + a + "." +"string" + a + "." + b + "." + c 7 lifting/test.scala @@ -145,14 +145,14 @@ Class .A msg 104 -116 +112 6 -+ -Apply + +Literal false 0 false -"string" + a +"string" 8 lifting/test.scala @@ -178,6 +178,23 @@ A Class .A integer +158 +159 +7 + +Literal +false +0 +false +0 + +10 +lifting/test.scala + +A +Class +.A +integer 139 150 7 @@ -188,7 +205,7 @@ false false def integer -10 +11 lifting/test.scala A @@ -205,7 +222,7 @@ false false def ex -11 +12 lifting/test.scala test$package @@ -222,7 +239,41 @@ false false A() -12 +13 +lifting/test.scala + +test$package +Object +.test$package +Test +235 +238 +13 + +Literal +false +0 +false +123 + +14 +lifting/test.scala + +test$package +Object +.test$package +f +251 +253 +14 + +Literal +false +0 +false +-1 + +15 lifting/test.scala test$package @@ -239,7 +290,7 @@ false false def f -13 +16 lifting/test.scala test$package @@ -256,7 +307,24 @@ false false a.msg(i, 0, a.integer) -14 +17 +lifting/test.scala + +test$package +Object +.test$package +Test +273 +274 +15 + +Literal +false +0 +false +0 + +18 lifting/test.scala test$package @@ -273,7 +341,7 @@ false false a.integer -15 +19 lifting/test.scala test$package @@ -290,7 +358,7 @@ false false println(x) -16 +20 lifting/test.scala test$package @@ -307,24 +375,24 @@ false false a.ex.msg(i, 0, a.ex.integer) -17 +21 lifting/test.scala test$package Object .test$package Test -306 -310 +318 +319 17 -ex -Select + +Literal false 0 false -a.ex +0 -18 +22 lifting/test.scala test$package @@ -341,7 +409,7 @@ false false a.ex -19 +23 lifting/test.scala test$package @@ -358,7 +426,7 @@ false false a.ex.integer -20 +24 lifting/test.scala test$package @@ -375,7 +443,7 @@ false false println(x) -21 +25 lifting/test.scala test$package @@ -392,7 +460,7 @@ false false a.msg(f(), 0, i) -22 +26 lifting/test.scala test$package @@ -409,7 +477,24 @@ false false f() -23 +27 +lifting/test.scala + +test$package +Object +.test$package +Test +365 +366 +19 + +Literal +false +0 +false +0 + +28 lifting/test.scala test$package @@ -426,7 +511,7 @@ false false println(x) -24 +29 lifting/test.scala test$package diff --git a/tests/coverage/run/macro-suspend/test.scoverage.check b/tests/coverage/run/macro-suspend/test.scoverage.check index 759897eb7747..4cae5a319598 100644 --- a/tests/coverage/run/macro-suspend/test.scoverage.check +++ b/tests/coverage/run/macro-suspend/test.scoverage.check @@ -76,6 +76,23 @@ Greeting Object .Greeting greet +252 +259 +8 + +Literal +false +0 +false +"hello" + +4 +macro-suspend/Macro.scala + +Greeting +Object +.Greeting +greet 238 247 8 @@ -86,7 +103,7 @@ false false def greet -4 +5 macro-suspend/Test.scala Test @@ -103,23 +120,6 @@ false false println(Macro.decorate(Greeting.greet())) -5 -macro-suspend/Test.scala - -Test -Object -.Test -main -65 -97 -3 -+ -Apply -false -0 -false -Macro.decorate(Greeting.greet()) - 6 macro-suspend/Test.scala @@ -127,40 +127,6 @@ Test Object .Test main -65 -97 -3 -+ -Apply -false -0 -false -Macro.decorate(Greeting.greet()) - -7 -macro-suspend/Test.scala - -Test -Object -.Test -main -80 -96 -3 -greet -Apply -false -0 -false -Greeting.greet() - -8 -macro-suspend/Test.scala - -Test -Object -.Test -main 15 23 2 diff --git a/tests/coverage/run/parameterless/test.scoverage.check b/tests/coverage/run/parameterless/test.scoverage.check index 5050180e7886..b792ce9c73ea 100644 --- a/tests/coverage/run/parameterless/test.scoverage.check +++ b/tests/coverage/run/parameterless/test.scoverage.check @@ -42,6 +42,23 @@ O Object .O f +51 +60 +4 + +Literal +false +0 +false +"O.f_res" + +2 +parameterless/test.scala + +O +Object +.O +f 12 17 2 @@ -52,7 +69,7 @@ false false def f -2 +3 parameterless/test.scala O @@ -69,7 +86,24 @@ false false println("O.g") -3 +4 +parameterless/test.scala + +O +Object +.O +g +106 +115 +8 + +Literal +false +0 +false +"O.g_res" + +5 parameterless/test.scala O @@ -86,7 +120,7 @@ false false def g -4 +6 parameterless/test.scala test$package @@ -103,7 +137,24 @@ false false println("f") -5 +7 +parameterless/test.scala + +test$package +Object +.test$package +f +179 +186 +14 + +Literal +false +0 +false +"f_res" + +8 parameterless/test.scala test$package @@ -120,7 +171,7 @@ false false def f -6 +9 parameterless/test.scala test$package @@ -137,7 +188,24 @@ false false println("g") -7 +10 +parameterless/test.scala + +test$package +Object +.test$package +g +230 +237 +18 + +Literal +false +0 +false +"g_res" + +11 parameterless/test.scala test$package @@ -154,7 +222,7 @@ false false def g -8 +12 parameterless/test.scala test$package @@ -171,7 +239,7 @@ false false f -9 +13 parameterless/test.scala test$package @@ -188,7 +256,7 @@ false false g -10 +14 parameterless/test.scala test$package @@ -205,24 +273,7 @@ false false println(f) -11 -parameterless/test.scala - -test$package -Object -.test$package -Test -273 -274 -22 -f -Ident -false -0 -false -f - -12 +15 parameterless/test.scala test$package @@ -239,7 +290,7 @@ false false println(g) -13 +16 parameterless/test.scala test$package @@ -256,7 +307,7 @@ false false g -14 +17 parameterless/test.scala test$package @@ -273,7 +324,7 @@ false false println(O.f) -15 +18 parameterless/test.scala test$package @@ -290,7 +341,7 @@ false false O.f -16 +19 parameterless/test.scala test$package @@ -307,7 +358,7 @@ false false println(O.g) -17 +20 parameterless/test.scala test$package @@ -324,7 +375,7 @@ false false O.g -18 +21 parameterless/test.scala test$package diff --git a/tests/coverage/run/trait/test.scoverage.check b/tests/coverage/run/trait/test.scoverage.check index 19a88ebc7f6f..1f921a3bfae8 100644 --- a/tests/coverage/run/trait/test.scoverage.check +++ b/tests/coverage/run/trait/test.scoverage.check @@ -25,6 +25,23 @@ T1 Trait .T1 x +20 +21 +2 + +Literal +false +0 +false +0 + +1 +trait/test.scala + +T1 +Trait +.T1 +x 12 17 2 @@ -35,7 +52,24 @@ false false def x -1 +2 +trait/test.scala + +Impl2 +Class +.Impl2 + +94 +100 +7 + +Literal +false +0 +false +"test" + +3 trait/test.scala Impl3 @@ -52,7 +86,7 @@ false false Impl2() -2 +4 trait/test.scala test$package @@ -69,7 +103,7 @@ false false println(Impl1().x) -3 +5 trait/test.scala test$package @@ -86,7 +120,7 @@ false false Impl1() -4 +6 trait/test.scala test$package @@ -103,7 +137,7 @@ false false Impl1().x -5 +7 trait/test.scala test$package @@ -120,7 +154,7 @@ false false println(Impl2().p) -6 +8 trait/test.scala test$package @@ -137,7 +171,7 @@ false false Impl2() -7 +9 trait/test.scala test$package @@ -154,7 +188,7 @@ false false println(Impl3().p) -8 +10 trait/test.scala test$package @@ -171,7 +205,7 @@ false false Impl3() -9 +11 trait/test.scala test$package diff --git a/tests/coverage/run/type-apply/test.measurement.check b/tests/coverage/run/type-apply/test.measurement.check index f1d6ee365359..e199fee6b817 100644 --- a/tests/coverage/run/type-apply/test.measurement.check +++ b/tests/coverage/run/type-apply/test.measurement.check @@ -1,5 +1,7 @@ -4 +6 1 2 3 +4 +5 0 diff --git a/tests/coverage/run/type-apply/test.scoverage.check b/tests/coverage/run/type-apply/test.scoverage.check index 7d76b11f2f8b..c20e88c0351d 100644 --- a/tests/coverage/run/type-apply/test.scoverage.check +++ b/tests/coverage/run/type-apply/test.scoverage.check @@ -59,15 +59,15 @@ test$package Object .test$package Test -171 -182 +176 +177 5 -apply -Apply + +Literal false 0 false -List(1,2,3) +1 3 type-apply/test.scala @@ -75,6 +75,40 @@ type-apply/test.scala test$package Object .test$package +Test +178 +179 +5 + +Literal +false +0 +false +2 + +4 +type-apply/test.scala + +test$package +Object +.test$package +Test +180 +181 +5 + +Literal +false +0 +false +3 + +5 +type-apply/test.scala + +test$package +Object +.test$package $anonfun 192 199 @@ -86,7 +120,7 @@ false false List(a) -4 +6 type-apply/test.scala test$package diff --git a/tests/coverage/run/varargs/test.scoverage.check b/tests/coverage/run/varargs/test.scoverage.check index 3c31f9388409..024156dcfeb6 100644 --- a/tests/coverage/run/varargs/test.scoverage.check +++ b/tests/coverage/run/varargs/test.scoverage.check @@ -25,6 +25,23 @@ test_1$package Object .test_1$package repeated +75 +77 +4 + +Literal +false +0 +false +() + +1 +varargs/test_1.scala + +test_1$package +Object +.test_1$package +repeated 48 60 4 @@ -35,7 +52,7 @@ false false def repeated -1 +2 varargs/test_1.scala test_1$package @@ -52,7 +69,7 @@ false false def f -2 +3 varargs/test_1.scala test_1$package @@ -69,7 +86,7 @@ false false repeated() -3 +4 varargs/test_1.scala test_1$package @@ -86,7 +103,7 @@ false false repeated(f(""), "b") -4 +5 varargs/test_1.scala test_1$package @@ -103,7 +120,24 @@ false false f("") -5 +6 +varargs/test_1.scala + +test_1$package +Object +.test_1$package +Test +149 +152 +11 + +Literal +false +0 +false +"b" + +7 varargs/test_1.scala test_1$package @@ -120,7 +154,7 @@ false false JavaVarargs_1.method() -6 +8 varargs/test_1.scala test_1$package @@ -137,7 +171,24 @@ false false JavaVarargs_1.method("") -7 +9 +varargs/test_1.scala + +test_1$package +Object +.test_1$package +Test +202 +204 +13 + +Literal +false +0 +false +"" + +10 varargs/test_1.scala test_1$package @@ -154,7 +205,24 @@ false false JavaVarargs_1.multiple("first") -8 +11 +varargs/test_1.scala + +test_1$package +Object +.test_1$package +Test +240 +247 +15 + +Literal +false +0 +false +"first" + +12 varargs/test_1.scala test_1$package @@ -171,7 +239,7 @@ false false println(m) -9 +13 varargs/test_1.scala test_1$package @@ -188,7 +256,7 @@ false false JavaVarargs_1.multiple(f("first")) -10 +14 varargs/test_1.scala test_1$package @@ -205,7 +273,7 @@ false false f("first") -11 +15 varargs/test_1.scala test_1$package @@ -222,7 +290,7 @@ false false println(m) -12 +16 varargs/test_1.scala test_1$package @@ -239,7 +307,7 @@ false false JavaVarargs_1.multiple(f("first"), "a", "b", "c") -13 +17 varargs/test_1.scala test_1$package @@ -256,7 +324,58 @@ false false f("first") -14 +18 +varargs/test_1.scala + +test_1$package +Object +.test_1$package +Test +357 +360 +19 + +Literal +false +0 +false +"a" + +19 +varargs/test_1.scala + +test_1$package +Object +.test_1$package +Test +362 +365 +19 + +Literal +false +0 +false +"b" + +20 +varargs/test_1.scala + +test_1$package +Object +.test_1$package +Test +367 +370 +19 + +Literal +false +0 +false +"c" + +21 varargs/test_1.scala test_1$package @@ -273,7 +392,7 @@ false false println(m) -15 +22 varargs/test_1.scala test_1$package diff --git a/tests/disabled/neg-custom-args/captures/capt-wf.scala b/tests/disabled/neg-custom-args/captures/capt-wf.scala index 302202064ac0..fe04d6709f91 100644 --- a/tests/disabled/neg-custom-args/captures/capt-wf.scala +++ b/tests/disabled/neg-custom-args/captures/capt-wf.scala @@ -1,17 +1,17 @@ // No longer valid class C -type Cap = C @retains(caps.cap) -type Top = Any @retains(caps.cap) +type Cap = C @retains[caps.cap.type] +type Top = Any @retains[caps.cap.type] -type T = (x: Cap) => List[String @retains(x)] => Unit // error -val x: (x: Cap) => Array[String @retains(x)] = ??? // error +type T = (x: Cap) => List[String @retains[x.type]] => Unit // error +val x: (x: Cap) => Array[String @retains[x.type]] = ??? // error val y = x def test: Unit = def f(x: Cap) = // ok - val g = (xs: List[String @retains(x)]) => () + val g = (xs: List[String @retains[x.type]]) => () g - def f2(x: Cap)(xs: List[String @retains(x)]) = () + def f2(x: Cap)(xs: List[String @retains[x.type]]) = () val x = f // error val x2 = f2 // error val y = f(C()) // ok diff --git a/tests/disabled/neg-custom-args/captures/try2.scala b/tests/disabled/neg-custom-args/captures/try2.scala index 43e17d8c9eef..0687173b4d3d 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.cap) +type Top = Any @retains[caps.cap.type] infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?=> R diff --git a/tests/disabled/partest/run/delambdafy_t6028.scala b/tests/disabled/partest/run/delambdafy_t6028.scala index 0b7ef48c3df8..981b6b759ebc 100644 --- a/tests/disabled/partest/run/delambdafy_t6028.scala +++ b/tests/disabled/partest/run/delambdafy_t6028.scala @@ -3,7 +3,7 @@ import java.io.{Console => _, _} object Test extends DirectTest { - override def extraSettings: String = "-usejavacp -Ydelambdafy:method -Xprint:lambdalift -d " + testOutput.path + override def extraSettings: String = "-usejavacp -Ydelambdafy:method -Vprint:lambdalift -d " + testOutput.path override def code = """class T(classParam: Int) { | val field: Int = 0 diff --git a/tests/disabled/partest/run/delambdafy_t6555.scala b/tests/disabled/partest/run/delambdafy_t6555.scala index a1dcfe790c3b..b40e1c0e4faf 100644 --- a/tests/disabled/partest/run/delambdafy_t6555.scala +++ b/tests/disabled/partest/run/delambdafy_t6555.scala @@ -3,7 +3,7 @@ import java.io.{Console => _, _} object Test extends DirectTest { - override def extraSettings: String = "-usejavacp -Xprint:specialize -Ydelambdafy:method -d " + testOutput.path + override def extraSettings: String = "-usejavacp -Vprint:specialize -Ydelambdafy:method -d " + testOutput.path override def code = "class Foo { val f = (param: Int) => param } " diff --git a/tests/disabled/partest/run/delambdafy_uncurry_byname_inline.scala b/tests/disabled/partest/run/delambdafy_uncurry_byname_inline.scala index 8f480fa80488..f8729e3c6710 100644 --- a/tests/disabled/partest/run/delambdafy_uncurry_byname_inline.scala +++ b/tests/disabled/partest/run/delambdafy_uncurry_byname_inline.scala @@ -3,7 +3,7 @@ import java.io.{Console => _, _} object Test extends DirectTest { - override def extraSettings: String = "-usejavacp -Xprint:uncurry -Ydelambdafy:inline -d " + testOutput.path + override def extraSettings: String = "-usejavacp -Vprint:uncurry -Ydelambdafy:inline -d " + testOutput.path override def code = """class Foo { | def bar(x: => Int) = x diff --git a/tests/disabled/partest/run/delambdafy_uncurry_byname_method.scala b/tests/disabled/partest/run/delambdafy_uncurry_byname_method.scala index 1adeec843390..0c21c232ae2a 100644 --- a/tests/disabled/partest/run/delambdafy_uncurry_byname_method.scala +++ b/tests/disabled/partest/run/delambdafy_uncurry_byname_method.scala @@ -3,7 +3,7 @@ import java.io.{Console => _, _} object Test extends DirectTest { - override def extraSettings: String = "-usejavacp -Xprint:uncurry -Ydelambdafy:method -Ystop-after:uncurry -d " + testOutput.path + override def extraSettings: String = "-usejavacp -Vprint:uncurry -Ydelambdafy:method -Ystop-after:uncurry -d " + testOutput.path override def code = """class Foo { | def bar(x: => Int) = x diff --git a/tests/disabled/partest/run/delambdafy_uncurry_inline.scala b/tests/disabled/partest/run/delambdafy_uncurry_inline.scala index b42b65f5bbda..b397189271d9 100644 --- a/tests/disabled/partest/run/delambdafy_uncurry_inline.scala +++ b/tests/disabled/partest/run/delambdafy_uncurry_inline.scala @@ -3,7 +3,7 @@ import java.io.{Console => _, _} object Test extends DirectTest { - override def extraSettings: String = "-usejavacp -Xprint:uncurry -Ydelambdafy:inline -d " + testOutput.path + override def extraSettings: String = "-usejavacp -Vprint:uncurry -Ydelambdafy:inline -d " + testOutput.path override def code = """class Foo { | def bar = { diff --git a/tests/disabled/partest/run/delambdafy_uncurry_method.scala b/tests/disabled/partest/run/delambdafy_uncurry_method.scala index a988fb2ee7bf..db2ca7061985 100644 --- a/tests/disabled/partest/run/delambdafy_uncurry_method.scala +++ b/tests/disabled/partest/run/delambdafy_uncurry_method.scala @@ -3,7 +3,7 @@ import java.io.{Console => _, _} object Test extends DirectTest { - override def extraSettings: String = "-usejavacp -Xprint:uncurry -Ydelambdafy:method -Ystop-after:uncurry -d " + testOutput.path + override def extraSettings: String = "-usejavacp -Vprint:uncurry -Ydelambdafy:method -Ystop-after:uncurry -d " + testOutput.path override def code = """class Foo { | def bar = { diff --git a/tests/disabled/partest/run/dynamic-applyDynamic.scala b/tests/disabled/partest/run/dynamic-applyDynamic.scala index b06041194c1e..65457a8b7b42 100644 --- a/tests/disabled/partest/run/dynamic-applyDynamic.scala +++ b/tests/disabled/partest/run/dynamic-applyDynamic.scala @@ -3,7 +3,7 @@ import scala.tools.partest.DirectTest object Test extends DirectTest { override def extraSettings: String = - s"-usejavacp -Xprint-pos -Xprint:typer -Yrangepos -Ystop-after:typer -d ${testOutput.path}" + s"-usejavacp -Xprint-pos -Vprint:typer -Yrangepos -Ystop-after:typer -d ${testOutput.path}" override def code = """ object X { @@ -23,4 +23,4 @@ object Test extends DirectTest { import language.dynamics class D extends Dynamic { def applyDynamic(name: String)(value: Any) = ??? -} \ No newline at end of file +} diff --git a/tests/disabled/partest/run/dynamic-applyDynamicNamed.scala b/tests/disabled/partest/run/dynamic-applyDynamicNamed.scala index cc59f9058be9..929485982d82 100644 --- a/tests/disabled/partest/run/dynamic-applyDynamicNamed.scala +++ b/tests/disabled/partest/run/dynamic-applyDynamicNamed.scala @@ -3,7 +3,7 @@ import scala.tools.partest.DirectTest object Test extends DirectTest { override def extraSettings: String = - s"-usejavacp -Xprint-pos -Xprint:typer -Yrangepos -Ystop-after:typer -d ${testOutput.path}" + s"-usejavacp -Xprint-pos -Vprint:typer -Yrangepos -Ystop-after:typer -d ${testOutput.path}" override def code = """ object X { diff --git a/tests/disabled/partest/run/dynamic-selectDynamic.scala b/tests/disabled/partest/run/dynamic-selectDynamic.scala index bd6c138c5002..2b92bcbb7cdd 100644 --- a/tests/disabled/partest/run/dynamic-selectDynamic.scala +++ b/tests/disabled/partest/run/dynamic-selectDynamic.scala @@ -3,7 +3,7 @@ import scala.tools.partest.DirectTest object Test extends DirectTest { override def extraSettings: String = - s"-usejavacp -Xprint-pos -Xprint:typer -Yrangepos -Ystop-after:typer -d ${testOutput.path}" + s"-usejavacp -Xprint-pos -Vprint:typer -Yrangepos -Ystop-after:typer -d ${testOutput.path}" override def code = """ object X { diff --git a/tests/disabled/partest/run/dynamic-updateDynamic.scala b/tests/disabled/partest/run/dynamic-updateDynamic.scala index 80fe0ea35f4c..5d1518550f39 100644 --- a/tests/disabled/partest/run/dynamic-updateDynamic.scala +++ b/tests/disabled/partest/run/dynamic-updateDynamic.scala @@ -3,7 +3,7 @@ import scala.tools.partest.DirectTest object Test extends DirectTest { override def extraSettings: String = - s"-usejavacp -Xprint-pos -Xprint:typer -Yrangepos -Ystop-after:typer -d ${testOutput.path}" + s"-usejavacp -Xprint-pos -Vprint:typer -Yrangepos -Ystop-after:typer -d ${testOutput.path}" override def code = """ object X { diff --git a/tests/disabled/partest/run/existential-rangepos.scala b/tests/disabled/partest/run/existential-rangepos.scala index 7d2b0810d342..50335ac62cea 100644 --- a/tests/disabled/partest/run/existential-rangepos.scala +++ b/tests/disabled/partest/run/existential-rangepos.scala @@ -1,7 +1,7 @@ import scala.tools.partest._ object Test extends DirectTest { - override def extraSettings: String = "-usejavacp -Yrangepos -Xprint:patmat -Xprint-pos -d " + testOutput.path + override def extraSettings: String = "-usejavacp -Yrangepos -Vprint:patmat -Xprint-pos -d " + testOutput.path override def code = """ abstract class A[T] { diff --git a/tests/disabled/partest/run/t4287inferredMethodTypes.scala b/tests/disabled/partest/run/t4287inferredMethodTypes.scala index f14e672da88a..cc9c057899ad 100644 --- a/tests/disabled/partest/run/t4287inferredMethodTypes.scala +++ b/tests/disabled/partest/run/t4287inferredMethodTypes.scala @@ -3,7 +3,7 @@ import scala.tools.partest.DirectTest object Test extends DirectTest { override def extraSettings: String = - s"-usejavacp -Yinfer-argument-types -Xprint-pos -Xprint:typer -Yrangepos -Ystop-after:typer -d ${testOutput.path}" + s"-usejavacp -Yinfer-argument-types -Xprint-pos -Vprint:typer -Yrangepos -Ystop-after:typer -d ${testOutput.path}" override def code = """ class A(a: Int = A.a) @@ -22,4 +22,4 @@ class B extends A { compile() } } -} \ No newline at end of file +} diff --git a/tests/disabled/partest/run/t5603.scala b/tests/disabled/partest/run/t5603.scala index 77c2775cc353..c047fe7896b5 100644 --- a/tests/disabled/partest/run/t5603.scala +++ b/tests/disabled/partest/run/t5603.scala @@ -7,7 +7,7 @@ import scala.tools.nsc.reporters.ConsoleReporter object Test extends DirectTest { - override def extraSettings: String = "-usejavacp -Xprint:parser -Ystop-after:parser -d " + testOutput.path + override def extraSettings: String = "-usejavacp -Vprint:parser -Ystop-after:parser -d " + testOutput.path override def code = """ trait Greeting { diff --git a/tests/disabled/partest/run/t5699.scala b/tests/disabled/partest/run/t5699.scala index ec3b1d26b490..bdcdfed93583 100755 --- a/tests/disabled/partest/run/t5699.scala +++ b/tests/disabled/partest/run/t5699.scala @@ -7,7 +7,7 @@ object Test extends DirectTest { |public @interface MyAnnotation { String value(); } """.stripMargin - override def extraSettings: String = "-usejavacp -Ystop-after:typer -Xprint:parser" + override def extraSettings: String = "-usejavacp -Ystop-after:typer -Vprint:parser" override def show(): Unit = { // redirect err to out, for logging diff --git a/tests/disabled/partest/run/t6028.scala b/tests/disabled/partest/run/t6028.scala index a6f920c5bb67..6e4e179f1dfd 100644 --- a/tests/disabled/partest/run/t6028.scala +++ b/tests/disabled/partest/run/t6028.scala @@ -3,7 +3,7 @@ import java.io.{Console => _, _} object Test extends DirectTest { - override def extraSettings: String = "-usejavacp -Ydelambdafy:inline -Xprint:lambdalift -d " + testOutput.path + override def extraSettings: String = "-usejavacp -Ydelambdafy:inline -Vprint:lambdalift -d " + testOutput.path override def code = """class T(classParam: Int) { | val field: Int = 0 diff --git a/tests/disabled/partest/run/t6288.scala b/tests/disabled/partest/run/t6288.scala index cf5865e95a0e..242555ac75a5 100644 --- a/tests/disabled/partest/run/t6288.scala +++ b/tests/disabled/partest/run/t6288.scala @@ -3,7 +3,7 @@ import java.io.{Console => _, _} object Test extends DirectTest { - override def extraSettings: String = "-usejavacp -Xprint:patmat -Xprint-pos -d " + testOutput.path + override def extraSettings: String = "-usejavacp -Vprint:patmat -Xprint-pos -d " + testOutput.path override def code = """ diff --git a/tests/disabled/partest/run/t6555.scala b/tests/disabled/partest/run/t6555.scala index cc0e4d1bfadb..e1db058da1d7 100644 --- a/tests/disabled/partest/run/t6555.scala +++ b/tests/disabled/partest/run/t6555.scala @@ -3,7 +3,7 @@ import java.io.{Console => _, _} object Test extends DirectTest { - override def extraSettings: String = "-usejavacp -Xprint:specialize -Ydelambdafy:inline -d " + testOutput.path + override def extraSettings: String = "-usejavacp -Vprint:specialize -Ydelambdafy:inline -d " + testOutput.path override def code = "class Foo { val f = (param: Int) => param } " diff --git a/tests/disabled/partest/run/t7271.scala b/tests/disabled/partest/run/t7271.scala index 69d5ea377ea4..6eadb7816c0e 100644 --- a/tests/disabled/partest/run/t7271.scala +++ b/tests/disabled/partest/run/t7271.scala @@ -7,7 +7,7 @@ import scala.reflect.internal.Positions object Test extends DirectTest { - override def extraSettings: String = "-usejavacp -Xprint:parser -Ystop-after:parser -d " + testOutput.path + override def extraSettings: String = "-usejavacp -Vprint:parser -Ystop-after:parser -d " + testOutput.path override def code = """ class C { diff --git a/tests/explicit-nulls/neg/equal1.scala b/tests/explicit-nulls/neg/equal1.scala index 3fab8ba4bf8a..45ec3084fd47 100644 --- a/tests/explicit-nulls/neg/equal1.scala +++ b/tests/explicit-nulls/neg/equal1.scala @@ -1,5 +1,8 @@ // Test what can be compared for equality against null. -class Foo { + +case class VC(x: Int) extends AnyVal + +def test = // Null itself val x0: Null = null x0 != x0 @@ -9,21 +12,21 @@ class Foo { null == null null != null - // Non-nullable types: error + // Non-nullable types: OK. val x1: String = "hello" - x1 != null // error - x1 == null // error - null == x1 // error - null != x1 // error - x1 == x0 // error - x0 != x1 // error - x1.asInstanceOf[String|Null] == null - x1.asInstanceOf[String|Null] == x0 + x1 != null + x1 == null + null == x1 + null != x1 + x1 == x0 + x0 != x1 + x1.asInstanceOf[String | Null] == null + x1.asInstanceOf[String | Null] == x0 x1.asInstanceOf[Any] == null x1.asInstanceOf[Any] == x0 // Nullable types: OK - val x2: String|Null = null + val x2: String | Null = null x2 == null null == x2 x2 == x0 @@ -41,4 +44,10 @@ class Foo { null == false // error 'a' == null // error null == 'b' // error -} + + // Nullable value types: OK. + val x3: Int | Null = null + x3 == null + null == x3 + x3 == x0 + x3 != x0 diff --git a/tests/explicit-nulls/neg/equal2.scala b/tests/explicit-nulls/neg/equal2.scala index 32cf21d918ac..299d29c103ab 100644 --- a/tests/explicit-nulls/neg/equal2.scala +++ b/tests/explicit-nulls/neg/equal2.scala @@ -1,5 +1,5 @@ -// Test that we can't compare for equality `null` with classes. -// This rule is for both regular classes and value classes. +// Test that we can compare values of regular classes against null, +// but not values of value classes. class Foo(x: Int) class Bar(x: Int) extends AnyVal @@ -7,12 +7,11 @@ class Bar(x: Int) extends AnyVal class Test { locally { val foo: Foo = new Foo(15) - foo == null // error: Values of types Null and Foo cannot be compared - null == foo // error - foo != null // error - null != foo // error + foo == null + null == foo + foo != null + null != foo - // To test against null, make the type nullable. val foo2: Foo | Null = foo // ok foo2 == null diff --git a/tests/explicit-nulls/neg/flow-match.scala b/tests/explicit-nulls/neg/flow-match.scala index e385758261cd..cbf77bfd0196 100644 --- a/tests/explicit-nulls/neg/flow-match.scala +++ b/tests/explicit-nulls/neg/flow-match.scala @@ -3,13 +3,11 @@ object MatchTest { def f6(s: String | Null): String = s match { case s2 => s2 // error - case null => "other" // error - case s3 => s3 + case s3 => s3 // OK since not null } def f7(s: String | Null): String = s match { case null => "other" - case null => "other" // error - case s3 => s3 + case s3 => s3 // OK since not null } -} +} \ No newline at end of file diff --git a/tests/explicit-nulls/neg/flow-strip-null.scala b/tests/explicit-nulls/neg/flow-strip-null.scala index 997d272d008f..7c79e1310f16 100644 --- a/tests/explicit-nulls/neg/flow-strip-null.scala +++ b/tests/explicit-nulls/neg/flow-strip-null.scala @@ -1,25 +1,21 @@ // Test we are correctly striping nulls from nullable unions. -class Foo { +class Foo: class B1 class B2 - locally { + + locally: val x: (Null | String) | Null | (B1 | (Null | B2)) = ??? - if (x != null) { + if x != null then val _: String | B1 | B2 = x // ok: can remove all nullable unions - } - } - locally { + locally: val x: (Null | String) & (Null | B1) = ??? - if (x != null) { + if x != null then val _: String & B1 = x // ok: can remove null from embedded intersection - } - } - locally { + locally: val x: (Null | B1) & B2 = ??? - if (x != null) {} // error: the type of x is not a nullable union, so we cannot remove the Null - } -} + if x != null then + val _: B1 & B2 = x // error: the type of x is not a nullable union, so we cannot remove the Null diff --git a/tests/explicit-nulls/unsafe-common/unsafe-equal.scala b/tests/explicit-nulls/pos/anyref-equal-nulls.scala similarity index 56% rename from tests/explicit-nulls/unsafe-common/unsafe-equal.scala rename to tests/explicit-nulls/pos/anyref-equal-nulls.scala index 493aaebfbff2..f7609425e894 100644 --- a/tests/explicit-nulls/unsafe-common/unsafe-equal.scala +++ b/tests/explicit-nulls/pos/anyref-equal-nulls.scala @@ -11,10 +11,10 @@ class S { null == s1 null != s1 - s2 == null // error - s2 != null // error - null == s2 // error - null != s2 // error + s2 == null + s2 != null + null == s2 + null != s2 s1 == s2 s1 != s2 @@ -27,21 +27,21 @@ class S { null != n s1 == n - s2 == n // error + s2 == n n != s1 - n != s2 // error + n != s2 } locally { - ss1 == null // error - ss1 != null // error - null == ss1 // error - null != ss1 // error - - ss1 == n // error - ss1 != n // error - n == ss1 // error - n != ss1 // error + ss1 == null + ss1 != null + null == ss1 + null != ss1 + + ss1 == n + ss1 != n + n == ss1 + n != ss1 ss1 == ss2 ss2 != ss1 diff --git a/tests/explicit-nulls/pos/force-track-var-fields.scala b/tests/explicit-nulls/pos/force-track-var-fields.scala new file mode 100644 index 000000000000..27f0448b8023 --- /dev/null +++ b/tests/explicit-nulls/pos/force-track-var-fields.scala @@ -0,0 +1,16 @@ +package scala + +import scala.annotation.stableNull + +class A: + @stableNull var s: String | Null = null + def getS: String = + if s == null then s = "" + s + +def test(a: A): String = + if a.s == null then + a.s = "" + a.s + else + a.s \ No newline at end of file diff --git a/tests/explicit-nulls/pos/pattern-matching.scala b/tests/explicit-nulls/pos/pattern-matching.scala deleted file mode 100644 index 7e84fb8cd513..000000000000 --- a/tests/explicit-nulls/pos/pattern-matching.scala +++ /dev/null @@ -1,38 +0,0 @@ - -class Foo { - val s: String = ??? - s match { - case s: String => 100 // warning: type test will always succeed - case _ => 200 // warning: unreachable - } - - s match { - case s: String => 100 // warning: type test will always succeed - case _ => 200 // warning: unreachable - } - - sealed trait Animal - case class Dog(name: String) extends Animal - case object Cat extends Animal - - val a: Animal = ??? - a match { - case Dog(name) => 100 - case Cat => 200 - case _ => 300 // warning: unreachable - } - - val a2: Animal|Null = ??? - a2 match { - case Dog(_) => 100 - case Cat => 200 - case _ => 300 // warning: only matches null - } - - val a3: Animal|Null = ??? - a3 match { - case Dog(_) => 100 - case Cat => 200 - case null => 300 // ok - } -} diff --git a/tests/explicit-nulls/run/pattern-matching.scala b/tests/explicit-nulls/run/pattern-matching.scala new file mode 100644 index 000000000000..bc893f8f40e2 --- /dev/null +++ b/tests/explicit-nulls/run/pattern-matching.scala @@ -0,0 +1,37 @@ +object Test: + + def main(args: Array[String]): Unit = + + val s: String = null.asInstanceOf[String] + + val r1 = s match + case s: String => 100 + case _ => 200 + assert(r1 == 200) + + val r2 = s match + case s: String => 100 + case null => 200 + assert(r2 == 200) + + val r3 = s match + case null => 100 + case _ => 200 + assert(r3 == 100) + + val s2: String | Null = null + + val r4 = s2 match + case s2: String => 100 + case _ => 200 + assert(r4 == 200) + + val r5 = s2 match + case s2: String => 100 + case null => 200 + assert(r5 == 200) + + val r6 = s2 match + case null => 200 + case s2: String => 100 + assert(r6 == 200) diff --git a/tests/explicit-nulls/unsafe-common/unsafe-match-null.scala b/tests/explicit-nulls/unsafe-common/unsafe-match-null.scala deleted file mode 100644 index fc76de6ddd3a..000000000000 --- a/tests/explicit-nulls/unsafe-common/unsafe-match-null.scala +++ /dev/null @@ -1,11 +0,0 @@ -def test1 = - val s: String = ??? - s match - case _: String => - case null => // error: Values of types Null and String cannot be compared - -def test2 = - val s: String | Null = ??? - s match - case _: String => - case null => diff --git a/tests/explicit-nulls/warn/expose-flexible-types.check b/tests/explicit-nulls/warn/expose-flexible-types.check new file mode 100644 index 000000000000..5e0f8519b7e7 --- /dev/null +++ b/tests/explicit-nulls/warn/expose-flexible-types.check @@ -0,0 +1,56 @@ +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:4:6 -------------------------------------------------- +4 | def f = s.trim // warn + | ^ + | method f exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:8:6 -------------------------------------------------- +8 | def h = (s.trim, s.length) // warn + | ^ + |method h exposes a flexible type in its inferred result type ((String)?, Int). Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:9:16 ------------------------------------------------- +9 | protected def i = s.trim // warn + | ^ + | method i exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:11:19 ------------------------------------------------ +11 | private[foo] def k = s.trim // warn + | ^ + | method k exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:12:6 ------------------------------------------------- +12 | val ss = s.replace("a", "A") // warn + | ^ + | value ss exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:13:6 ------------------------------------------------- +13 | val ss2 = Seq(s.trim) // warn + | ^ + |value ss2 exposes a flexible type in its inferred result type Seq[(String)?]. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:21:6 ------------------------------------------------- +21 | def f = s2.trim // warn + | ^ + | method f exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:25:6 ------------------------------------------------- +25 | def h = (s2.trim, s2.length) // warn + | ^ + |method h exposes a flexible type in its inferred result type ((String)?, Int). Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:26:6 ------------------------------------------------- +26 | val ss = s2.replace("a", "A") // warn + | ^ + | value ss exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:27:6 ------------------------------------------------- +27 | val ss2 = Seq(s2.trim) // warn + | ^ + |value ss2 exposes a flexible type in its inferred result type Seq[(String)?]. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:32:4 ------------------------------------------------- +32 |def f = s2.trim // warn + | ^ + | method f exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:36:4 ------------------------------------------------- +36 |def h = (s2.trim, s2.length) // warn + | ^ + |method h exposes a flexible type in its inferred result type ((String)?, Int). Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:37:4 ------------------------------------------------- +37 |val ss = s2.replace("a", "A") // warn + | ^ + | value ss exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly +-- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:38:4 ------------------------------------------------- +38 |val ss2 = Seq(s2.trim) // warn + | ^ + |value ss2 exposes a flexible type in its inferred result type Seq[(String)?]. Consider annotating the type explicitly diff --git a/tests/explicit-nulls/warn/expose-flexible-types.scala b/tests/explicit-nulls/warn/expose-flexible-types.scala new file mode 100644 index 000000000000..9908e6228cd4 --- /dev/null +++ b/tests/explicit-nulls/warn/expose-flexible-types.scala @@ -0,0 +1,41 @@ +package foo + +class C(s: String): + def f = s.trim // warn + def g: String = + val s2 = s.trim + s2 + def h = (s.trim, s.length) // warn + protected def i = s.trim // warn + private def j = s.trim + private[foo] def k = s.trim // warn + val ss = s.replace("a", "A") // warn + val ss2 = Seq(s.trim) // warn + val ss3: Seq[String] = + val s3 = s.trim + Seq(s3) + +val s2: String = "" + +object O: + def f = s2.trim // warn + def g: String = + val s3 = s2.trim + s3 + def h = (s2.trim, s2.length) // warn + val ss = s2.replace("a", "A") // warn + val ss2 = Seq(s2.trim) // warn + val ss3: Seq[String] = + val s3 = s2.trim + Seq(s3) + +def f = s2.trim // warn +def g: String = + val s3 = s2.trim + s3 +def h = (s2.trim, s2.length) // warn +val ss = s2.replace("a", "A") // warn +val ss2 = Seq(s2.trim) // warn +val ss3: Seq[String] = + val s3 = s2.trim + Seq(s3) \ No newline at end of file diff --git a/tests/explicit-nulls/warn/flow-match.check b/tests/explicit-nulls/warn/flow-match.check new file mode 100644 index 000000000000..dd17e45e97bf --- /dev/null +++ b/tests/explicit-nulls/warn/flow-match.check @@ -0,0 +1,16 @@ +-- [E030] Match case Unreachable Warning: tests/explicit-nulls/warn/flow-match.scala:6:9 ------------------------------- +6 | case null => "other" // warn + | ^^^^ + | Unreachable case +-- [E030] Match case Unreachable Warning: tests/explicit-nulls/warn/flow-match.scala:7:9 ------------------------------- +7 | case s3 => s3 // warn + | ^^ + | Unreachable case +-- [E030] Match case Unreachable Warning: tests/explicit-nulls/warn/flow-match.scala:12:9 ------------------------------ +12 | case null => "other" // warn + | ^^^^ + | Unreachable case +-- [E030] Match case Unreachable Warning: tests/explicit-nulls/warn/flow-match.scala:14:9 ------------------------------ +14 | case s4 => s4 // warn + | ^^ + | Unreachable case diff --git a/tests/explicit-nulls/warn/flow-match.scala b/tests/explicit-nulls/warn/flow-match.scala new file mode 100644 index 000000000000..2d619a400fff --- /dev/null +++ b/tests/explicit-nulls/warn/flow-match.scala @@ -0,0 +1,16 @@ +// Test unreachable matches in presence of nulls + +object MatchTest2 { + def f6(s: String | Null): String = s match { + case s2 => "string" + case null => "other" // warn + case s3 => s3 // warn + } + + def f7(s: String | Null): String = s match { + case null => "other" + case null => "other" // warn + case s3: String => s3 + case s4 => s4 // warn + } +} \ No newline at end of file diff --git a/tests/explicit-nulls/warn/i21577.check b/tests/explicit-nulls/warn/i21577.check index b548a5bedc30..97f4ef346a43 100644 --- a/tests/explicit-nulls/warn/i21577.check +++ b/tests/explicit-nulls/warn/i21577.check @@ -15,7 +15,7 @@ | ^ | Unreachable case -- [E029] Pattern Match Exhaustivity Warning: tests/explicit-nulls/warn/i21577.scala:29:27 ----------------------------- -29 |def f7(s: String | Null) = s match // warn: not exhuastive +29 |def f7(s: String | Null) = s match // warn: not exhaustive | ^ | match may not be exhaustive. | @@ -23,10 +23,10 @@ | | longer explanation available when compiling with `-explain` -- [E029] Pattern Match Exhaustivity Warning: tests/explicit-nulls/warn/i21577.scala:36:33 ----------------------------- -36 |def f9(s: String | Int | Null) = s match // warn: not exhuastive +36 |def f9(s: String | Int | Null) = s match // warn: not exhaustive | ^ | match may not be exhaustive. | | It would fail on pattern case: _: Int | - | longer explanation available when compiling with `-explain` + | longer explanation available when compiling with `-explain` \ No newline at end of file diff --git a/tests/explicit-nulls/warn/i21577.scala b/tests/explicit-nulls/warn/i21577.scala index 1bba8f4da01f..a4fc26f85f2b 100644 --- a/tests/explicit-nulls/warn/i21577.scala +++ b/tests/explicit-nulls/warn/i21577.scala @@ -26,13 +26,13 @@ def f6(s: String) = s.trim() match def f61(s: String) = s.trim() match case _: String => -def f7(s: String | Null) = s match // warn: not exhuastive +def f7(s: String | Null) = s match // warn: not exhaustive case _: String => def f8(s: String | Null) = s match case _: String => case null => -def f9(s: String | Int | Null) = s match // warn: not exhuastive +def f9(s: String | Int | Null) = s match // warn: not exhaustive case _: String => - case null => \ No newline at end of file + case null => diff --git a/tests/explicit-nulls/warn/pattern-matching.check b/tests/explicit-nulls/warn/pattern-matching.check new file mode 100644 index 000000000000..b7b12b2a9215 --- /dev/null +++ b/tests/explicit-nulls/warn/pattern-matching.check @@ -0,0 +1,24 @@ +-- [E030] Match case Unreachable Warning: tests/explicit-nulls/warn/pattern-matching.scala:7:9 ------------------------- +7 | case _ => 200 // warn: unreachable + | ^ + | Unreachable case +-- [E030] Match case Unreachable Warning: tests/explicit-nulls/warn/pattern-matching.scala:11:9 ------------------------ +11 | case null => 200 // warn: unreachable + | ^^^^ + | Unreachable case +-- [E030] Match case Unreachable Warning: tests/explicit-nulls/warn/pattern-matching.scala:14:9 ------------------------ +14 | case null => 100 // warn: unreachable + | ^^^^ + | Unreachable case +-- [E121] Pattern Match Warning: tests/explicit-nulls/warn/pattern-matching.scala:21:9 --------------------------------- +21 | case _ => 200 // warn: unreachable case except for null + | ^ + | Unreachable case except for null (if this is intentional, consider writing case null => instead). +-- [E030] Match case Unreachable Warning: tests/explicit-nulls/warn/pattern-matching.scala:39:9 ------------------------ +39 | case _ => 300 // warn: unreachable case except for null + | ^ + | Unreachable case +-- [E121] Pattern Match Warning: tests/explicit-nulls/warn/pattern-matching.scala:45:9 --------------------------------- +45 | case _ => 300 // warn: unreachable case except for null + | ^ + | Unreachable case except for null (if this is intentional, consider writing case null => instead). diff --git a/tests/explicit-nulls/warn/pattern-matching.scala b/tests/explicit-nulls/warn/pattern-matching.scala new file mode 100644 index 000000000000..f1cabc302c87 --- /dev/null +++ b/tests/explicit-nulls/warn/pattern-matching.scala @@ -0,0 +1,50 @@ +class Foo: + + val s: String = ??? + + s match + case s: String => 100 + case _ => 200 // warn: unreachable + + s match + case s: String => 100 + case null => 200 // warn: unreachable + + s match + case null => 100 // warn: unreachable + case _ => 200 + + val s2: String | Null = ??? + + s2 match + case s2: String => 100 + case _ => 200 // warn: unreachable case except for null + + s2 match + case s2: String => 100 + case null => 200 + + s2 match + case null => 200 + case s2: String => 100 + + sealed trait Animal + case class Dog(name: String) extends Animal + case object Cat extends Animal + + val a: Animal = ??? + a match + case Dog(name) => 100 + case Cat => 200 + case _ => 300 // warn: unreachable case except for null + + val a2: Animal | Null = ??? + a2 match + case Dog(_) => 100 + case Cat => 200 + case _ => 300 // warn: unreachable case except for null + + a2 match + case Dog(_) => 100 + case Cat => 200 + case null => 300 // ok diff --git a/tests/explicit-nulls/warn/unnecessary-nn.scala b/tests/explicit-nulls/warn/unnecessary-nn.scala new file mode 100644 index 000000000000..82d87e75c0a5 --- /dev/null +++ b/tests/explicit-nulls/warn/unnecessary-nn.scala @@ -0,0 +1,21 @@ +def f1(s: String): String = s.nn // warn +def f2(s: String|Null): String|Null = s.nn // warn +def f3(s: String|Null): Any = s.nn // warn +def f4(s: String|Null): String = s.nn + +def f5[T >: String](s: String|Null): T = s.nn +def f6[T >: String|Null](s: String|Null): T = s.nn // warn + +def f5a[T <: String](s: T): String = s.nn // warn + +// flexible types +def f7(s: String|Null): String = "".concat(s.nn) // warn +def f8(s: String): String = s.trim().nn // OK because the .nn could be useful as a dynamic null check + + +def f9(s: String|Null): String = + if(s == null) "default" + else s.nn // warn + +def f10(x: String|Null): ((String|Null) @deprecated) = x.nn // warn +def f10b(x: String|Null): (String|Null) = x.nn // warn \ No newline at end of file diff --git a/tests/init-global/pos/anon-class.scala b/tests/init-global/pos/anon-class.scala new file mode 100644 index 000000000000..c94cd28e4b6a --- /dev/null +++ b/tests/init-global/pos/anon-class.scala @@ -0,0 +1,20 @@ +abstract class Source[A] { self => + def consume(a: A): Int + def contramap[B](f: B => A): Source[B] = { + new Source[B] { // OfClass($anon).outerValue = {OfClass(Source), OfClass($anon)} ??? + override def consume(b: B) = self.consume(f(b)) + } + } +} + +object O { + val identity: Source[Int] = new Source[Int] { + override def consume(a: Int): Int = a + } // OfClass(Source[A]) + val longToInt: Source[Long] = identity.contramap((l: Long) => l.toInt) // longToInt.outer == identity + val doubleToLongToInt: Source[Double] = longToInt.contramap((d: Double) => (d + 2.4).toLong) // doubleToLongToInt == longToInt + // OfClass(Source[Double]).outer = {LocalEnv(contramap)}; + // LocalEnv(contramap).outer = {OfClass(Source[Long]), OfClass(Source[Double])} + println(doubleToLongToInt.consume(3.5)) +} + diff --git a/tests/init-global/pos/cyclic-param.scala b/tests/init-global/pos/cyclic-param.scala new file mode 100644 index 000000000000..af9ce29d2e53 --- /dev/null +++ b/tests/init-global/pos/cyclic-param.scala @@ -0,0 +1,7 @@ +class B {} + +class C(c: B) extends B + +object O: + def f(param: B): Int = f(new C(param)) + val a = f(new B) \ No newline at end of file diff --git a/tests/init-global/pos/enum.scala b/tests/init-global/pos/enum.scala new file mode 100644 index 000000000000..40687c8f9cf6 --- /dev/null +++ b/tests/init-global/pos/enum.scala @@ -0,0 +1,17 @@ +enum FileExtension(val toLowerCase: String): + case Tasty extends FileExtension("tasty") + case Betasty extends FileExtension("betasty") + case Class extends FileExtension("class") + case Jar extends FileExtension("jar") + case Scala extends FileExtension("scala") + case ScalaScript extends FileExtension("sc") + case Java extends FileExtension("java") + case Zip extends FileExtension("zip") + case Inc extends FileExtension("inc") + case Empty extends FileExtension("") + + /** Fallback extension */ + case External(override val toLowerCase: String) extends FileExtension(toLowerCase) + +object O: + val a = FileExtension.Empty \ No newline at end of file diff --git a/tests/init-global/pos/get-local-val-from-other-owner.scala b/tests/init-global/pos/get-local-val-from-other-owner.scala new file mode 100644 index 000000000000..f16c4989895e --- /dev/null +++ b/tests/init-global/pos/get-local-val-from-other-owner.scala @@ -0,0 +1,9 @@ +object O: + val a = m(2) + def m(f: Int) = + val a = f + () => a + +object O2: + val func = O.a + val a = func() \ No newline at end of file diff --git a/tests/init-global/pos/global-recursion3.scala b/tests/init-global/pos/global-recursion3.scala new file mode 100644 index 000000000000..84fee697d5e8 --- /dev/null +++ b/tests/init-global/pos/global-recursion3.scala @@ -0,0 +1,4 @@ +class A(val a: A) +object B: + val a: A = loop().a + def loop(): A = new A(loop()) \ No newline at end of file diff --git a/tests/init-global/pos/inner-extends-outer.scala b/tests/init-global/pos/inner-extends-outer.scala new file mode 100644 index 000000000000..4631010e524f --- /dev/null +++ b/tests/init-global/pos/inner-extends-outer.scala @@ -0,0 +1,13 @@ +class Outer(val f: Int) { + class Inner extends Outer(5) { + def g(): Int = this.f + } +} + +object O { + def foo(i: Outer): Unit = + val i2 = new i.Inner // i2.outer should always be OfClass(Outer) + println("i2.g = " + i2.g()) + + foo(new Outer(6)) +} diff --git a/tests/init-global/pos/local-class.scala b/tests/init-global/pos/local-class.scala new file mode 100644 index 000000000000..0c5e89c7b63b --- /dev/null +++ b/tests/init-global/pos/local-class.scala @@ -0,0 +1,22 @@ +class Outer { + def foo = { + val y = 5 + class C { + val x = y + } + class D { + new C + } + + new D + } + + foo + + val n = 10 // warn +} + +object O { + val c = new Outer + val d: Object = c.foo +} \ No newline at end of file diff --git a/tests/init-global/warn/widen.scala b/tests/init-global/pos/lookup-outer.scala similarity index 91% rename from tests/init-global/warn/widen.scala rename to tests/init-global/pos/lookup-outer.scala index 157434a0f3e4..390030968e33 100644 --- a/tests/init-global/warn/widen.scala +++ b/tests/init-global/pos/lookup-outer.scala @@ -10,7 +10,7 @@ object O: def bar(t: T) = { class A { class B { - t.foo() // warn + t.foo() } val b = new B diff --git a/tests/init-global/pos/multiple-outers.scala b/tests/init-global/pos/multiple-outers.scala new file mode 100644 index 000000000000..0562e602b05c --- /dev/null +++ b/tests/init-global/pos/multiple-outers.scala @@ -0,0 +1,48 @@ +class A(val x: Int) { + class B { + println("A.this = " + A.this.hashCode()) // `a` + println("A.this.x = " + A.this.x) // B --> outer A (42 or 46) + def fooz = x + def fooz2 = x + class D { + println("B.this = " + B.this.hashCode()) // `c` in `foo` + def bar = fooz // expands to B.this.fooz, calls fooz in class B + def bar2 = fooz2 // expands to B.this.fooz, calls fooz2 in class C + } + } +} +class AA(y: Int) extends A(y+1) { + class E {} + def foo = { + val a = if true then new A(42) else new AA(46) + println("a = " + a.hashCode()) + class C /*outer: AA(44) (`Main.aa`)*/ extends a.B /*outer: A(42) or AA(46) (`a`)*/ { + println("AA.this = " + AA.this.hashCode()) // Main.aa + println("AA.this.x = " + x) // C --> outer AA --> parent A (44) + override def fooz2 = x // 44 + val z = fooz // (A.this.x) + println("z = " + z) + + } + class B extends AA.this.E {} + val c: C = new C + println("c = " + c.hashCode()) + val d = new c.D // outer: C (`c`) + println("d.bar = " + d.bar + ", d.bar2 = " + d.bar2) + d.bar + d.bar2 + } +} + +object O { + val aa = new AA(44) + val f = aa.foo + println("aa = " + aa.hashCode()) + println("f = " + f) +} + +object Main { + def main(args: Array[String]) = { + O + () + } +} \ No newline at end of file diff --git a/tests/init-global/pos/resolve-outer-of-parent.scala b/tests/init-global/pos/resolve-outer-of-parent.scala new file mode 100644 index 000000000000..c0b7ae4dd167 --- /dev/null +++ b/tests/init-global/pos/resolve-outer-of-parent.scala @@ -0,0 +1,20 @@ +class A { + val field_a = 5 + def bar(): Int = A.this.field_a +} + +class B extends A { + val field_b = field_a + class C { + def bar2() = B.this.field_b + val field_c = bar() // expands to B.this.bar() + val field_c2 = field_a // C --> outer B --> parent A + } +} + +object O: + val b = new B + class D extends b.C { // D --> parent C --> outer B + val field_d = bar2() + } + val d = new D diff --git a/tests/init-global/pos/unapplySeq-product-sequence-match.scala b/tests/init-global/pos/unapplySeq-product-sequence-match.scala new file mode 100644 index 000000000000..2105fb63dc6e --- /dev/null +++ b/tests/init-global/pos/unapplySeq-product-sequence-match.scala @@ -0,0 +1,29 @@ +trait Node { + val prefix = 5 + val child = Array(3) +} + +class SpecialNode extends Node + +class Group extends Node + +class C extends Node + +object Elem { + def apply(prefix: Int, children: Int*) = new C + def unapplySeq(n: Node) = n match { + case _: SpecialNode | _: Group => None + case _ => Some((n.prefix, n.child.toSeq)) + } +} + +object O { + def updateNode(node: Node): Node = + node match { + case Elem(prefix, children @ _*) => + Elem(prefix, children*) + case other => other + } + + val a = updateNode(new Group) +} diff --git a/tests/init-global/pos/virtual-method.scala b/tests/init-global/pos/virtual-method.scala new file mode 100644 index 000000000000..6e3820bea6a8 --- /dev/null +++ b/tests/init-global/pos/virtual-method.scala @@ -0,0 +1,19 @@ +abstract class T { + def foo() = { + def bar() = 5 + bar() + } +} + +class A extends T {} +class B extends T {} +class C extends T {} + +object O { + val a = new A + val b = new B + val c = new C + val d = a.foo() + val e = b.foo() + val f = c.foo() +} \ No newline at end of file diff --git a/tests/init-global/warn/global-irrelevance3.check b/tests/init-global/warn/global-irrelevance3.check index dba945224f97..9b6f8c539bf7 100644 --- a/tests/init-global/warn/global-irrelevance3.check +++ b/tests/init-global/warn/global-irrelevance3.check @@ -9,3 +9,8 @@ |│ ^^^^^^^ |└── (() => x) // warn [ global-irrelevance3.scala:9 ] | ^ + |The mutable state is created through: + |├── object A: [ global-irrelevance3.scala:1 ] + |│ ^ + |└── val p: Pair = foo() [ global-irrelevance3.scala:3 ] + | ^^^^^ diff --git a/tests/init-global/warn/global-irrelevance4.check b/tests/init-global/warn/global-irrelevance4.check index 7253e5b39688..1862ae3e1986 100644 --- a/tests/init-global/warn/global-irrelevance4.check +++ b/tests/init-global/warn/global-irrelevance4.check @@ -9,3 +9,8 @@ | │ ^^^^^^^^^ | └── (y => x = y), // warn [ global-irrelevance4.scala:8 ] | ^^^^^^^^^^ + | The mutable state is created through: + | ├── object A: [ global-irrelevance4.scala:1 ] + | │ ^ + | └── val p: Pair = foo() [ global-irrelevance4.scala:3 ] + | ^^^^^ diff --git a/tests/init-global/warn/inner-extends-outer.check b/tests/init-global/warn/inner-extends-outer.check new file mode 100644 index 000000000000..a6bbcaa24911 --- /dev/null +++ b/tests/init-global/warn/inner-extends-outer.check @@ -0,0 +1,16 @@ +-- Warning: tests/init-global/warn/inner-extends-outer.scala:22:19 ----------------------------------------------------- +22 | def bar(): Int = f2 // warn + | ^^ + | Access uninitialized field value f2. Calling trace: + | ├── object O extends T { [ inner-extends-outer.scala:15 ] + | │ ^ + | ├── val f1 = foo(new Outer(this)) [ inner-extends-outer.scala:20 ] + | │ ^^^^^^^^^^^^^^^^^^^^ + | ├── def foo(i: Outer): Int = [ inner-extends-outer.scala:16 ] + | │ ^ + | ├── i2.g() [ inner-extends-outer.scala:18 ] + | │ ^^^^^^ + | ├── def g(): Int = Outer.this.t.bar() [ inner-extends-outer.scala:11 ] + | │ ^^^^^^^^^^^^^^^^^^ + | └── def bar(): Int = f2 // warn [ inner-extends-outer.scala:22 ] + | ^^ diff --git a/tests/init-global/warn/inner-extends-outer.scala b/tests/init-global/warn/inner-extends-outer.scala new file mode 100644 index 000000000000..369c129fbc7d --- /dev/null +++ b/tests/init-global/warn/inner-extends-outer.scala @@ -0,0 +1,23 @@ +trait T { + def bar(): Int +} + +class C extends T { + def bar(): Int = 5 +} + +class Outer(val t: T) { + class Inner extends Outer(new C) { + def g(): Int = Outer.this.t.bar() + } +} + +object O extends T { + def foo(i: Outer): Int = + val i2 = new i.Inner // i2.outer should always be OfClass(Outer) + i2.g() + + val f1 = foo(new Outer(this)) + val f2 = 5 + def bar(): Int = f2 // warn +} \ No newline at end of file diff --git a/tests/init-global/warn/local-class.check b/tests/init-global/warn/local-class.check new file mode 100644 index 000000000000..8710a3c1df8a --- /dev/null +++ b/tests/init-global/warn/local-class.check @@ -0,0 +1,22 @@ +-- Warning: tests/init-global/warn/local-class.scala:2:14 -------------------------------------------------------------- +2 | def m() = O.f2 // warn + | ^^^^ + | Access uninitialized field value f2. Calling trace: + | ├── object O { [ local-class.scala:5 ] + | │ ^ + | ├── val f1 = foo() [ local-class.scala:19 ] + | │ ^^^^^ + | ├── def foo(): Int = { [ local-class.scala:6 ] + | │ ^ + | ├── val d = new D [ local-class.scala:15 ] + | │ ^^^^^ + | ├── class D { [ local-class.scala:8 ] + | │ ^ + | ├── val f = bar() [ local-class.scala:13 ] + | │ ^^^^^ + | ├── def bar() = { [ local-class.scala:9 ] + | │ ^ + | ├── c.m() [ local-class.scala:10 ] + | │ ^^^^^ + | └── def m() = O.f2 // warn [ local-class.scala:2 ] + | ^^^^ diff --git a/tests/init-global/warn/local-class.scala b/tests/init-global/warn/local-class.scala new file mode 100644 index 000000000000..0c0b05626119 --- /dev/null +++ b/tests/init-global/warn/local-class.scala @@ -0,0 +1,21 @@ +class C { + def m() = O.f2 // warn +} + +object O { + def foo(): Int = { + val c = new C + class D { + def bar() = { + c.m() + } + + val f = bar() + } + val d = new D + d.f + } + + val f1 = foo() + val f2: Int = 5 +} \ No newline at end of file diff --git a/tests/init-global/warn/mutable-array.check b/tests/init-global/warn/mutable-array.check index 1bc9146ceb4d..b2faa897162f 100644 --- a/tests/init-global/warn/mutable-array.check +++ b/tests/init-global/warn/mutable-array.check @@ -10,7 +10,5 @@ |The mutable state is created through: |├── object A: [ mutable-array.scala:1 ] |│ ^ - |├── val box: Box = new Box(0) [ mutable-array.scala:3 ] - |│ ^^^^^^^^^^ - |└── class Box(var value: Int) [ mutable-array.scala:2 ] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ + |└── val box: Box = new Box(0) [ mutable-array.scala:3 ] + | ^^^^^^^^^^ diff --git a/tests/init-global/warn/mutable-read1.check b/tests/init-global/warn/mutable-read1.check index 4862b4399de4..0327203208f2 100644 --- a/tests/init-global/warn/mutable-read1.check +++ b/tests/init-global/warn/mutable-read1.check @@ -10,7 +10,5 @@ |The mutable state is created through: |├── object A: [ mutable-read1.scala:3 ] |│ ^ - |├── val box: Box = new Box(4) [ mutable-read1.scala:4 ] - |│ ^^^^^^^^^^ - |└── class Box(var value: Int) [ mutable-read1.scala:1 ] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ + |└── val box: Box = new Box(4) [ mutable-read1.scala:4 ] + | ^^^^^^^^^^ diff --git a/tests/init-global/warn/mutable-read2.check b/tests/init-global/warn/mutable-read2.check index 7339e76672ef..368137ea6ba3 100644 --- a/tests/init-global/warn/mutable-read2.check +++ b/tests/init-global/warn/mutable-read2.check @@ -10,7 +10,5 @@ |The mutable state is created through: |├── object A: [ mutable-read2.scala:1 ] |│ ^ - |├── val box: Box = new Box(0) [ mutable-read2.scala:5 ] - |│ ^^^^^^^^^^ - |└── class Box(var value: Int) { [ mutable-read2.scala:2 ] - | ^ + |└── val box: Box = new Box(0) [ mutable-read2.scala:5 ] + | ^^^^^^^^^^ diff --git a/tests/init-global/warn/mutable-read3.check b/tests/init-global/warn/mutable-read3.check index ecaaac65994b..6ebc71818665 100644 --- a/tests/init-global/warn/mutable-read3.check +++ b/tests/init-global/warn/mutable-read3.check @@ -10,7 +10,5 @@ |The mutable state is created through: |├── object A: [ mutable-read3.scala:1 ] |│ ^ - |├── val box: Box = new Box(0) [ mutable-read3.scala:3 ] - |│ ^^^^^^^^^^ - |└── class Box(var value: Int) [ mutable-read3.scala:2 ] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ + |└── val box: Box = new Box(0) [ mutable-read3.scala:3 ] + | ^^^^^^^^^^ diff --git a/tests/init-global/warn/mutable-read4.check b/tests/init-global/warn/mutable-read4.check index e66fd8235a61..c77249b90a89 100644 --- a/tests/init-global/warn/mutable-read4.check +++ b/tests/init-global/warn/mutable-read4.check @@ -10,7 +10,5 @@ |The mutable state is created through: |├── object A: [ mutable-read4.scala:3 ] |│ ^ - |├── val box: Box = new Box(4) [ mutable-read4.scala:4 ] - |│ ^^^^^^^^^^ - |└── class Box(var value: Int) [ mutable-read4.scala:1 ] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ + |└── val box: Box = new Box(4) [ mutable-read4.scala:4 ] + | ^^^^^^^^^^ diff --git a/tests/init-global/warn/mutable-read6.check b/tests/init-global/warn/mutable-read6.check index 7a4798456acb..e6417b3eb327 100644 --- a/tests/init-global/warn/mutable-read6.check +++ b/tests/init-global/warn/mutable-read6.check @@ -12,7 +12,5 @@ |The mutable state is created through: |├── object Contexts: [ mutable-read6.scala:3 ] |│ ^ - |├── val NoContext: Context = new Context [ mutable-read6.scala:4 ] - |│ ^^^^^^^^^^^ - |└── class Context: [ mutable-read6.scala:5 ] - | ^ + |└── val NoContext: Context = new Context [ mutable-read6.scala:4 ] + | ^^^^^^^^^^^ diff --git a/tests/init-global/warn/resolve-outer-of-parent.check b/tests/init-global/warn/resolve-outer-of-parent.check new file mode 100644 index 000000000000..00a116906a51 --- /dev/null +++ b/tests/init-global/warn/resolve-outer-of-parent.check @@ -0,0 +1,16 @@ +-- Warning: tests/init-global/warn/resolve-outer-of-parent.scala:7:16 -------------------------------------------------- +7 | def foo() = O.d // warn + | ^^^ + | Access uninitialized field value d. Calling trace: + | ├── object O: [ resolve-outer-of-parent.scala:14 ] + | │ ^ + | ├── val d = new D [ resolve-outer-of-parent.scala:19 ] + | │ ^^^^^ + | ├── class D extends b.C { // D --> parent C --> outer B [ resolve-outer-of-parent.scala:16 ] + | │ ^ + | ├── val field_d = bar2() [ resolve-outer-of-parent.scala:17 ] + | │ ^^^^^^ + | ├── def bar2() = B.this.foo() [ resolve-outer-of-parent.scala:9 ] + | │ ^^^^^^^^^^^^ + | └── def foo() = O.d // warn [ resolve-outer-of-parent.scala:7 ] + | ^^^ diff --git a/tests/init-global/warn/resolve-outer-of-parent.scala b/tests/init-global/warn/resolve-outer-of-parent.scala new file mode 100644 index 000000000000..8af70290dec0 --- /dev/null +++ b/tests/init-global/warn/resolve-outer-of-parent.scala @@ -0,0 +1,19 @@ +class A { + val field_a = 5 + def bar(): Int = A.this.field_a +} + +class B extends A { + def foo() = O.d // warn + class C { + def bar2() = B.this.foo() + val field_c = bar() // expands to B.this.bar() + } +} + +object O: + val b = new B + class D extends b.C { // D --> parent C --> outer B + val field_d = bar2() + } + val d = new D diff --git a/tests/init-global/warn/resolve-parent-this.check b/tests/init-global/warn/resolve-parent-this.check new file mode 100644 index 000000000000..58299cca167e --- /dev/null +++ b/tests/init-global/warn/resolve-parent-this.check @@ -0,0 +1,8 @@ +-- Warning: tests/init-global/warn/resolve-parent-this.scala:7:21 ------------------------------------------------------ +7 | val a: Int = foo().a // warn + | ^^^^^^^ + | Access uninitialized field value a. Calling trace: + | ├── object O extends Delegate { [ resolve-parent-this.scala:6 ] + | │ ^ + | └── val a: Int = foo().a // warn [ resolve-parent-this.scala:7 ] + | ^^^^^^^ diff --git a/tests/init-global/warn/resolve-parent-this.scala b/tests/init-global/warn/resolve-parent-this.scala new file mode 100644 index 000000000000..02f7a6266437 --- /dev/null +++ b/tests/init-global/warn/resolve-parent-this.scala @@ -0,0 +1,8 @@ +class Delegate { + def foo() = f + val f: O.type = O +} + +object O extends Delegate { + val a: Int = foo().a // warn +} \ No newline at end of file diff --git a/tests/init-global/warn/unsoundness.check b/tests/init-global/warn/unsoundness.check new file mode 100644 index 000000000000..da27dc45ac1d --- /dev/null +++ b/tests/init-global/warn/unsoundness.check @@ -0,0 +1,20 @@ +-- Warning: tests/init-global/warn/unsoundness.scala:12:6 -------------------------------------------------------------- +12 | O.x // warn + | ^^^ + | Access uninitialized field value x. Calling trace: + | ├── object O: [ unsoundness.scala:15 ] + | │ ^ + | ├── f(if m > 5 then Box(A(3)) else Box(B(4))) [ unsoundness.scala:17 ] + | │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ├── def f(a: Box[Base[Int]]): Unit = [ unsoundness.scala:21 ] + | │ ^ + | ├── h(a.value) [ unsoundness.scala:22 ] + | │ ^^^^^^^^^^ + | ├── def h(a: Base[Int]): Unit = [ unsoundness.scala:24 ] + | │ ^ + | ├── a.update(10) [ unsoundness.scala:25 ] + | │ ^^^^^^^^^^^^ + | ├── def update(n: T) = [ unsoundness.scala:11 ] + | │ ^ + | └── O.x // warn [ unsoundness.scala:12 ] + | ^^^ diff --git a/tests/init-global/warn/unsoundness.scala b/tests/init-global/warn/unsoundness.scala new file mode 100644 index 000000000000..882c7e4d8e80 --- /dev/null +++ b/tests/init-global/warn/unsoundness.scala @@ -0,0 +1,25 @@ +class Box[T](val value: T) + +abstract class Base[T]: + def update(n: T): Unit + +class A[T](var a: T) extends Base[T]: + def update(n: T) = + a = n + +class B[T](var b: T) extends Base[T]: + def update(n: T) = + O.x // warn + b = n + +object O: + val m: Int = 3 + f(if m > 5 then Box(A(3)) else Box(B(4))) + + val x: Int = 10 + + def f(a: Box[Base[Int]]): Unit = + h(a.value) + + def h(a: Base[Int]): Unit = + a.update(10) \ No newline at end of file diff --git a/tests/init-global/warn/widen.check b/tests/init-global/warn/widen.check deleted file mode 100644 index b3191d023110..000000000000 --- a/tests/init-global/warn/widen.check +++ /dev/null @@ -1,20 +0,0 @@ --- 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/warn/inner30.scala b/tests/init/warn/inner30.scala index d9b1eec3d6b1..1fd112579263 100644 --- a/tests/init/warn/inner30.scala +++ b/tests/init/warn/inner30.scala @@ -8,7 +8,7 @@ class Scanners { class Scanner { def foo() = - Conc(Run('a', 3), Run('b', 4)) + Conc(Run('a', 3), Run('b', 4)) // warn new LookAheadScanner class LookAheadScanner() extends Scanner diff --git a/tests/neg/erased-1.scala b/tests/invalid/neg/erased-1.scala similarity index 100% rename from tests/neg/erased-1.scala rename to tests/invalid/neg/erased-1.scala diff --git a/tests/neg/erased-2.scala b/tests/invalid/neg/erased-2.scala similarity index 100% rename from tests/neg/erased-2.scala rename to tests/invalid/neg/erased-2.scala diff --git a/tests/neg/erased-3.scala b/tests/invalid/neg/erased-3.scala similarity index 100% rename from tests/neg/erased-3.scala rename to tests/invalid/neg/erased-3.scala diff --git a/tests/neg/erased-args-lifted.scala b/tests/invalid/neg/erased-args-lifted.scala similarity index 87% rename from tests/neg/erased-args-lifted.scala rename to tests/invalid/neg/erased-args-lifted.scala index dfa7b74ee3d4..a2f6a654429f 100644 --- a/tests/neg/erased-args-lifted.scala +++ b/tests/invalid/neg/erased-args-lifted.scala @@ -2,7 +2,7 @@ object Test { def foo(a: Int)(b: Int, c: Int) = 42 - erased def bar(erased i: Int): Int = { + inline def bar(erased i: Int): Int = { println(1) 42 } diff --git a/tests/neg/erased-implicit.scala b/tests/invalid/neg/erased-implicit.scala similarity index 100% rename from tests/neg/erased-implicit.scala rename to tests/invalid/neg/erased-implicit.scala diff --git a/tests/neg/erased-inheritance.scala b/tests/invalid/neg/erased-inheritance.scala similarity index 100% rename from tests/neg/erased-inheritance.scala rename to tests/invalid/neg/erased-inheritance.scala diff --git a/tests/neg/erased-params.scala b/tests/invalid/neg/erased-params.scala similarity index 100% rename from tests/neg/erased-params.scala rename to tests/invalid/neg/erased-params.scala diff --git a/tests/neg/safeThrowsStrawman2.scala b/tests/invalid/neg/safeThrowsStrawman2.scala similarity index 94% rename from tests/neg/safeThrowsStrawman2.scala rename to tests/invalid/neg/safeThrowsStrawman2.scala index 8d95494e30e0..c6ef62317c6e 100644 --- a/tests/neg/safeThrowsStrawman2.scala +++ b/tests/invalid/neg/safeThrowsStrawman2.scala @@ -1,7 +1,7 @@ import language.experimental.erasedDefinitions object scalax: - erased class CanThrow[E <: Exception] + class CanThrow[E <: Exception] extends compiletime.Erased type CTF = CanThrow[Fail] infix type raises[R, E <: Exception] = CanThrow[E] ?=> R diff --git a/tests/pos/i11743.scala b/tests/invalid/pos/i11743.scala similarity index 81% rename from tests/pos/i11743.scala rename to tests/invalid/pos/i11743.scala index ae524ca01ad6..3114383b3433 100644 --- a/tests/pos/i11743.scala +++ b/tests/invalid/pos/i11743.scala @@ -2,7 +2,7 @@ import language.experimental.erasedDefinitions import scala.compiletime.erasedValue type UnivEq[A] object UnivEq: - erased def force[A]: UnivEq[A] = erasedValue + inline def force[A]: UnivEq[A] = erasedValue extension [A](erased proof: UnivEq[A]) inline def univEq(a: A, b: A): Boolean = a == b diff --git a/tests/pos/i17584a.scala b/tests/invalid/pos/i17584a.scala similarity index 100% rename from tests/pos/i17584a.scala rename to tests/invalid/pos/i17584a.scala diff --git a/tests/run/erased-1.check b/tests/invalid/run/erased-1.check similarity index 100% rename from tests/run/erased-1.check rename to tests/invalid/run/erased-1.check diff --git a/tests/run/erased-1.scala b/tests/invalid/run/erased-1.scala similarity index 100% rename from tests/run/erased-1.scala rename to tests/invalid/run/erased-1.scala diff --git a/tests/run/erased-10.check b/tests/invalid/run/erased-10.check similarity index 100% rename from tests/run/erased-10.check rename to tests/invalid/run/erased-10.check diff --git a/tests/run/erased-10.scala b/tests/invalid/run/erased-10.scala similarity index 92% rename from tests/run/erased-10.scala rename to tests/invalid/run/erased-10.scala index 004d07b4de37..ce8c8a42de4c 100644 --- a/tests/run/erased-10.scala +++ b/tests/invalid/run/erased-10.scala @@ -10,7 +10,7 @@ object Test { println("pacFun4") } - erased def inky: Int = { + inline def inky: Int = { println("inky") // in erased function 42 } diff --git a/tests/run/erased-11.check b/tests/invalid/run/erased-11.check similarity index 100% rename from tests/run/erased-11.check rename to tests/invalid/run/erased-11.check diff --git a/tests/run/erased-11.scala b/tests/invalid/run/erased-11.scala similarity index 100% rename from tests/run/erased-11.scala rename to tests/invalid/run/erased-11.scala diff --git a/tests/run/erased-12.check b/tests/invalid/run/erased-12.check similarity index 100% rename from tests/run/erased-12.check rename to tests/invalid/run/erased-12.check diff --git a/tests/run/erased-12.scala b/tests/invalid/run/erased-12.scala similarity index 100% rename from tests/run/erased-12.scala rename to tests/invalid/run/erased-12.scala diff --git a/tests/run/erased-13.check b/tests/invalid/run/erased-13.check similarity index 100% rename from tests/run/erased-13.check rename to tests/invalid/run/erased-13.check diff --git a/tests/run/erased-13.scala b/tests/invalid/run/erased-13.scala similarity index 100% rename from tests/run/erased-13.scala rename to tests/invalid/run/erased-13.scala diff --git a/tests/run/erased-14.check b/tests/invalid/run/erased-14.check similarity index 100% rename from tests/run/erased-14.check rename to tests/invalid/run/erased-14.check diff --git a/tests/run/erased-14.scala b/tests/invalid/run/erased-14.scala similarity index 100% rename from tests/run/erased-14.scala rename to tests/invalid/run/erased-14.scala diff --git a/tests/run/erased-16.check b/tests/invalid/run/erased-16.check similarity index 100% rename from tests/run/erased-16.check rename to tests/invalid/run/erased-16.check diff --git a/tests/run/erased-16.scala b/tests/invalid/run/erased-16.scala similarity index 100% rename from tests/run/erased-16.scala rename to tests/invalid/run/erased-16.scala diff --git a/tests/run/erased-17.check b/tests/invalid/run/erased-17.check similarity index 100% rename from tests/run/erased-17.check rename to tests/invalid/run/erased-17.check diff --git a/tests/run/erased-17.scala b/tests/invalid/run/erased-17.scala similarity index 100% rename from tests/run/erased-17.scala rename to tests/invalid/run/erased-17.scala diff --git a/tests/run/erased-22.check b/tests/invalid/run/erased-22.check similarity index 100% rename from tests/run/erased-22.check rename to tests/invalid/run/erased-22.check diff --git a/tests/run/erased-22.scala b/tests/invalid/run/erased-22.scala similarity index 100% rename from tests/run/erased-22.scala rename to tests/invalid/run/erased-22.scala diff --git a/tests/run/erased-27.check b/tests/invalid/run/erased-27.check similarity index 100% rename from tests/run/erased-27.check rename to tests/invalid/run/erased-27.check diff --git a/tests/run/erased-27.scala b/tests/invalid/run/erased-27.scala similarity index 100% rename from tests/run/erased-27.scala rename to tests/invalid/run/erased-27.scala diff --git a/tests/run/erased-28.check b/tests/invalid/run/erased-28.check similarity index 100% rename from tests/run/erased-28.check rename to tests/invalid/run/erased-28.check diff --git a/tests/run/erased-28.scala b/tests/invalid/run/erased-28.scala similarity index 100% rename from tests/run/erased-28.scala rename to tests/invalid/run/erased-28.scala diff --git a/tests/run/erased-3.check b/tests/invalid/run/erased-3.check similarity index 100% rename from tests/run/erased-3.check rename to tests/invalid/run/erased-3.check diff --git a/tests/run/erased-3.scala b/tests/invalid/run/erased-3.scala similarity index 100% rename from tests/run/erased-3.scala rename to tests/invalid/run/erased-3.scala diff --git a/tests/run/erased-4.check b/tests/invalid/run/erased-4.check similarity index 100% rename from tests/run/erased-4.check rename to tests/invalid/run/erased-4.check diff --git a/tests/run/erased-4.scala b/tests/invalid/run/erased-4.scala similarity index 100% rename from tests/run/erased-4.scala rename to tests/invalid/run/erased-4.scala diff --git a/tests/run/erased-5.check b/tests/invalid/run/erased-5.check similarity index 100% rename from tests/run/erased-5.check rename to tests/invalid/run/erased-5.check diff --git a/tests/run/erased-5.scala b/tests/invalid/run/erased-5.scala similarity index 100% rename from tests/run/erased-5.scala rename to tests/invalid/run/erased-5.scala diff --git a/tests/run/erased-6.check b/tests/invalid/run/erased-6.check similarity index 100% rename from tests/run/erased-6.check rename to tests/invalid/run/erased-6.check diff --git a/tests/run/erased-6.scala b/tests/invalid/run/erased-6.scala similarity index 100% rename from tests/run/erased-6.scala rename to tests/invalid/run/erased-6.scala diff --git a/tests/run/erased-8.check b/tests/invalid/run/erased-8.check similarity index 100% rename from tests/run/erased-8.check rename to tests/invalid/run/erased-8.check diff --git a/tests/run/erased-8.scala b/tests/invalid/run/erased-8.scala similarity index 100% rename from tests/run/erased-8.scala rename to tests/invalid/run/erased-8.scala diff --git a/tests/run/erased-9.check b/tests/invalid/run/erased-9.check similarity index 100% rename from tests/run/erased-9.check rename to tests/invalid/run/erased-9.check diff --git a/tests/run/erased-9.scala b/tests/invalid/run/erased-9.scala similarity index 100% rename from tests/run/erased-9.scala rename to tests/invalid/run/erased-9.scala diff --git a/tests/run/erased-class-are-erased.check b/tests/invalid/run/erased-class-are-erased.check similarity index 100% rename from tests/run/erased-class-are-erased.check rename to tests/invalid/run/erased-class-are-erased.check diff --git a/tests/run/erased-class-are-erased.scala b/tests/invalid/run/erased-class-are-erased.scala similarity index 100% rename from tests/run/erased-class-are-erased.scala rename to tests/invalid/run/erased-class-are-erased.scala diff --git a/tests/run/erased-frameless.check b/tests/invalid/run/erased-frameless.check similarity index 100% rename from tests/run/erased-frameless.check rename to tests/invalid/run/erased-frameless.check diff --git a/tests/run/erased-frameless.scala b/tests/invalid/run/erased-frameless.scala similarity index 88% rename from tests/run/erased-frameless.scala rename to tests/invalid/run/erased-frameless.scala index fe654639492a..a366e705840c 100644 --- a/tests/run/erased-frameless.scala +++ b/tests/invalid/run/erased-frameless.scala @@ -28,7 +28,7 @@ trait Dataset[T] { // Use c.label to do an untyped select on actual Spark Dataset, and // cast the result to TypedDataset[A] - def col[S <: String, A](s: S) (using erased ev: Exists[T, s.type, A]) = + inline def col[S <: String, A](s: S) (using erased ev: Exists[T, s.type, A]) = new Column[T, A](s) // ev is only here to check than this is safe, it's never used at runtime! def collect(): Vector[T] @@ -71,17 +71,17 @@ case class Column[T, A](label: String) trait Exists[T, K, V] object Exists { - implicit def derive[T, H <: HList, K, V](implicit g: LabelledGeneric[T] { type Repr = H }, s: Selector[H, K, V]): Exists[T, K, V] = { + inline implicit def derive[T, H <: HList, K, V](implicit g: LabelledGeneric[T] { type Repr = H }, s: Selector[H, K, V]): Exists[T, K, V] = { println("Exists.derive") null } - implicit def caseFound[T <: HList, K <: String, V]: Selector[R[K, V] :: T, K, V] = { + inline implicit def caseFound[T <: HList, K <: String, V]: Selector[R[K, V] :: T, K, V] = { println("Selector.caseFound") null } - implicit def caseRecur[H, T <: HList, K <: String, V](implicit i: Selector[T, K, V]): Selector[H :: T, K, V] = { + inline implicit def caseRecur[H, T <: HList, K <: String, V](implicit i: Selector[T, K, V]): Selector[H :: T, K, V] = { println("Selector.caseRecur") null } diff --git a/tests/run/erased-select-prefix.check b/tests/invalid/run/erased-select-prefix.check similarity index 100% rename from tests/run/erased-select-prefix.check rename to tests/invalid/run/erased-select-prefix.check diff --git a/tests/run/erased-select-prefix.scala b/tests/invalid/run/erased-select-prefix.scala similarity index 77% rename from tests/run/erased-select-prefix.scala rename to tests/invalid/run/erased-select-prefix.scala index b877a0d209d7..06ed46d5ccce 100644 --- a/tests/run/erased-select-prefix.scala +++ b/tests/invalid/run/erased-select-prefix.scala @@ -29,9 +29,9 @@ object Test { def bar(erased i: Int): Unit = () - erased def foo0: Int = 0 - erased def foo1(): Int = 1 - erased def foo2[T]: Int = 2 - erased def foo3[T](): Int = 3 + inline def foo0: Int = 0 + inline def foo1(): Int = 1 + inline def foo2[T]: Int = 2 + inline def foo3[T](): Int = 3 } diff --git a/tests/run/erased-value-class.check b/tests/invalid/run/erased-value-class.check similarity index 100% rename from tests/run/erased-value-class.check rename to tests/invalid/run/erased-value-class.check diff --git a/tests/run/erased-value-class.scala b/tests/invalid/run/erased-value-class.scala similarity index 100% rename from tests/run/erased-value-class.scala rename to tests/invalid/run/erased-value-class.scala diff --git a/tests/run/polymorphic-erased-functions.scala b/tests/invalid/run/polymorphic-erased-functions.scala similarity index 100% rename from tests/run/polymorphic-erased-functions.scala rename to tests/invalid/run/polymorphic-erased-functions.scala diff --git a/tests/neg-custom-args/boxmap.scala b/tests/neg-custom-args/boxmap.scala index 1696ac3505e4..5a91947edba3 100644 --- a/tests/neg-custom-args/boxmap.scala +++ b/tests/neg-custom-args/boxmap.scala @@ -1,5 +1,5 @@ import annotation.retains -type Top = Any @retains(caps.cap) +type Top = Any @retains[caps.cap.type] type Box[+T <: Top] = ([K <: Top] -> (T => K) -> K) diff --git a/tests/neg-custom-args/captures/boundschecks3.check b/tests/neg-custom-args/captures/boundschecks3.check index 035d327e3d71..57bcf8cbc32a 100644 --- a/tests/neg-custom-args/captures/boundschecks3.check +++ b/tests/neg-custom-args/captures/boundschecks3.check @@ -1,24 +1,32 @@ -- [E057] Type Mismatch Error: tests/neg-custom-args/captures/boundschecks3.scala:9:11 --------------------------------- 9 | val foo: C[Tree^] = ??? // error | ^ - | Type argument box test.Tree^ does not conform to upper bound test.Tree in inferred type test.C[box test.Tree^] + | Type argument test.Tree^ does not conform to upper bound test.Tree in inferred type test.C[test.Tree^] + | + | where: ^ refers to a fresh root capability in the type of value foo | | longer explanation available when compiling with `-explain` -- [E057] Type Mismatch Error: tests/neg-custom-args/captures/boundschecks3.scala:10:11 -------------------------------- 10 | type T = C[Tree^] // error | ^ - | Type argument box test.Tree^ does not conform to upper bound test.Tree in inferred type test.C[box test.Tree^] + | Type argument test.Tree^ does not conform to upper bound test.Tree in inferred type test.C[test.Tree^] + | + | where: ^ refers to a fresh root capability in the type of type T | | longer explanation available when compiling with `-explain` -- [E057] Type Mismatch Error: tests/neg-custom-args/captures/boundschecks3.scala:11:11 -------------------------------- 11 | val bar: T -> T = ??? // error | ^ - |Type argument box test.Tree^ does not conform to upper bound test.Tree in subpart test.C[box test.Tree^] of inferred type test.C[box test.Tree^] -> test.C[box test.Tree^] + |Type argument test.Tree^ does not conform to upper bound test.Tree in subpart test.C[test.Tree^] of inferred type test.C[test.Tree^] -> test.C[test.Tree^] + | + |where: ^ refers to the universal root capability | | longer explanation available when compiling with `-explain` -- [E057] Type Mismatch Error: tests/neg-custom-args/captures/boundschecks3.scala:12:11 -------------------------------- 12 | val baz: C[Tree^] -> Unit = ??? // error | ^ - |Type argument box test.Tree^ does not conform to upper bound test.Tree in subpart test.C[box test.Tree^] of inferred type test.C[box test.Tree^] -> Unit + |Type argument test.Tree^ does not conform to upper bound test.Tree in subpart test.C[test.Tree^] of inferred type test.C[test.Tree^] -> Unit + | + |where: ^ refers to the universal root capability | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/box-adapt-cases.check b/tests/neg-custom-args/captures/box-adapt-cases.check index c89f7a09f293..330fd196023b 100644 --- a/tests/neg-custom-args/captures/box-adapt-cases.check +++ b/tests/neg-custom-args/captures/box-adapt-cases.check @@ -1,21 +1,14 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/box-adapt-cases.scala:8:10 ------------------------------- -8 | x.value(cap => cap.use()) // error, was OK - | ^^^^^^^^^^^^^^^^ - | Found: (cap: box Cap^?) => Int - | Required: (cap: box Cap^) ->{fresh} Int - | - | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/box-adapt-cases.scala:15:10 ------------------------------ 15 | x.value(cap => cap.use()) // error | ^^^^^^^^^^^^^^^^ - | Found: (cap: box Cap^{io}) ->{io} Int - | Required: (cap: box Cap^{io}) -> Int + | Found: (cap: Cap^{io}) ->{io} Int + | Required: Cap^{io} -> Int | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/box-adapt-cases.scala:29:10 ------------------------------ 29 | x.value(cap => cap.use()) // error | ^^^^^^^^^^^^^^^^ - | Found: (cap: box Cap^?) ->{io, fs} Int - | Required: (cap: box Cap^{io, fs}) ->{io} Int + | Found: (cap: Cap^?) ->{io, fs} Int + | Required: 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 7db58318ed05..af79e1fcb6f6 100644 --- a/tests/neg-custom-args/captures/box-adapt-cases.scala +++ b/tests/neg-custom-args/captures/box-adapt-cases.scala @@ -5,7 +5,7 @@ def test1(): Unit = { class Id[X](val value: [T] -> (op: X => T) -> T) val x: Id[Cap^] = ??? - x.value(cap => cap.use()) // error, was OK + x.value(cap => cap.use()) } def test2(io: Cap^): Unit = { diff --git a/tests/neg-custom-args/captures/box-adapt-contra.check b/tests/neg-custom-args/captures/box-adapt-contra.check index fd36f23aa999..a598656743fc 100644 --- a/tests/neg-custom-args/captures/box-adapt-contra.check +++ b/tests/neg-custom-args/captures/box-adapt-contra.check @@ -1,15 +1,18 @@ -- Error: tests/neg-custom-args/captures/box-adapt-contra.scala:9:52 --------------------------------------------------- 9 | val f: (Cap^{c} -> Unit) -> Unit = useCap[Cap^{c}](c) // error | ^^^^^^^^^^^^^^^^^^ - | Cap^{c} -> Unit cannot be box-converted to box Cap^{c} ->{c} Unit - | since the additional capture set {c} resulting from box conversion is not allowed in box Cap^{c} -> Unit + | Cap^{c} -> Unit cannot be box-converted to Cap^{c} ->{c} Unit + | since the additional capture set {c} resulting from box conversion is not allowed in Cap^{c} -> Unit -- Error: tests/neg-custom-args/captures/box-adapt-contra.scala:13:57 -------------------------------------------------- 13 | val f1: (Cap^{c} => Unit) ->{c} Unit = useCap1[Cap^{c}](c) // error, was ok when cap was a root | ^^^^^^^^^^^^^^^^^^^ - | Cap^{c} => Unit cannot be box-converted to box Cap^{c} ->{cap, c} Unit - | since the additional capture set {c} resulting from box conversion is not allowed in box Cap^{c} => Unit + | Cap^{c} => Unit cannot be box-converted to Cap^{c} ->{cap, c} Unit + | since the additional capture set {c} resulting from box conversion is not allowed in Cap^{c} => Unit + | + | where: => refers to the universal root capability + | cap is the universal root capability -- Error: tests/neg-custom-args/captures/box-adapt-contra.scala:19:54 -------------------------------------------------- 19 | val f3: (Cap^{c} -> Unit) => Unit = useCap3[Cap^{c}](c) // error | ^^^^^^^^^^^^^^^^^^^ - | Cap^{c} -> Unit cannot be box-converted to box Cap^{c} ->{d, c} Unit - | since the additional capture set {c} resulting from box conversion is not allowed in box Cap^{c} ->{d} Unit + | Cap^{c} -> Unit cannot be box-converted to Cap^{c} ->{d, c} Unit + | since the additional capture set {c} resulting from box conversion is not allowed in Cap^{c} ->{d} Unit diff --git a/tests/neg-custom-args/captures/branding.scala b/tests/neg-custom-args/captures/branding.scala new file mode 100644 index 000000000000..b59677487eb5 --- /dev/null +++ b/tests/neg-custom-args/captures/branding.scala @@ -0,0 +1,39 @@ +import language.experimental.captureChecking +import caps.* + + +def main() = + trait Channel[T] extends caps.Capability: + def send(msg: T): Unit + def recv(): T + + trait Logger extends caps.Capability: + def log(msg: String): Unit + + // we can close over anything subsumed by the 'trusted' brand capability, but nothing else + def runSecure[C^ >: {trusted} <: {trusted}](block: () ->{C} Unit): Unit = block() + + // This is a 'brand" capability to mark what can be mentioned in trusted code + object trusted extends caps.Capability + + val trustedLogger: Logger^{trusted} = ??? + val trustedChannel: Channel[String]^{trusted} = ??? + + val untrustedLogger: Logger^ = ??? + val untrustedChannel: Channel[String]^ = ??? + + runSecure: () => + trustedLogger.log("Hello from trusted code") // ok + + runSecure: () => + trustedChannel.send("I can send") + trustedLogger.log(trustedChannel.recv()) // ok + + runSecure: () => + "I am pure" // ok + + runSecure: () => // error + untrustedLogger.log("I can't be used here") + + runSecure: () => // error + untrustedChannel.send("I can't be used here") \ No newline at end of file diff --git a/tests/neg-custom-args/captures/branding2.scala b/tests/neg-custom-args/captures/branding2.scala new file mode 100644 index 000000000000..c3091befdc25 --- /dev/null +++ b/tests/neg-custom-args/captures/branding2.scala @@ -0,0 +1,39 @@ +import language.experimental.captureChecking +import caps.* + + +def main() = + trait Channel[T] extends caps.Capability: + def send(msg: T): Unit + def recv(): T + + trait Logger extends caps.Capability: + def log(msg: String): Unit + + // we can close over anything subsumed by the 'trusted' brand capability, but nothing else + def runSecure(block: () ->{trusted} Unit): Unit = block() + + // This is a 'brand" capability to mark what can be mentioned in trusted code + object trusted extends caps.Capability + + val trustedLogger: Logger^{trusted} = ??? + val trustedChannel: Channel[String]^{trusted} = ??? + + val untrustedLogger: Logger^ = ??? + val untrustedChannel: Channel[String]^ = ??? + + runSecure: () => + trustedLogger.log("Hello from trusted code") // ok + + runSecure: () => + trustedChannel.send("I can send") + trustedLogger.log(trustedChannel.recv()) // ok + + runSecure: () => + "I am pure" : Unit // ok + + runSecure: () => // error + untrustedLogger.log("I can't be used here") + + runSecure: () => // error + untrustedChannel.send("I can't be used here") \ No newline at end of file diff --git a/tests/neg-custom-args/captures/byname.check b/tests/neg-custom-args/captures/byname.check index a6d04354bbbf..f3ce717db467 100644 --- a/tests/neg-custom-args/captures/byname.check +++ b/tests/neg-custom-args/captures/byname.check @@ -7,8 +7,10 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:10:6 ---------------------------------------- 10 | h(f2()) // error | ^^^^ - | Found: () ?->{cap1} Int ->{cap1} Int - | Required: () ?=> Int ->{cap2} Int + |Found: () ?->{cap1} Int ->{cap1} Int + |Required: () ?=> Int ->{cap2} Int + | + |where: ?=> refers to a fresh root capability created in method test when checking argument to parameter ff of method h | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:19:5 ---------------------------------------- diff --git a/tests/neg-custom-args/captures/cap-paramlists6.check b/tests/neg-custom-args/captures/cap-paramlists6.check new file mode 100644 index 000000000000..ad14cc7f27f7 --- /dev/null +++ b/tests/neg-custom-args/captures/cap-paramlists6.check @@ -0,0 +1,8 @@ +-- Error: tests/neg-custom-args/captures/cap-paramlists6.scala:10:71 --------------------------------------------------- +10 | val baz = () => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E} : Ctx, // error + | ^^^ + | Capture variable `F` cannot have a context bound. +-- Error: tests/neg-custom-args/captures/cap-paramlists6.scala:11:51 --------------------------------------------------- +11 | G >: {} <: {}, H >: {} <: {} : Ctx] => (x: Int) => 1 // error + | ^^^ + | Capture variable `H` cannot have a context bound. diff --git a/tests/neg-custom-args/captures/cap-paramlists6.scala b/tests/neg-custom-args/captures/cap-paramlists6.scala new file mode 100644 index 000000000000..a83164f5466b --- /dev/null +++ b/tests/neg-custom-args/captures/cap-paramlists6.scala @@ -0,0 +1,11 @@ +import language.experimental.captureChecking + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + val baz = () => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E} : Ctx, // error + G >: {} <: {}, H >: {} <: {} : Ctx] => (x: Int) => 1 // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/caps-reach.check b/tests/neg-custom-args/captures/caps-reach.check new file mode 100644 index 000000000000..701c3a834bb7 --- /dev/null +++ b/tests/neg-custom-args/captures/caps-reach.check @@ -0,0 +1,20 @@ +-- [E040] Syntax Error: tests/neg-custom-args/captures/caps-reach.scala:9:46 ------------------------------------------- +9 | val consumers4 = ListBuffer.empty[() ->{f.rd*} Unit] // error + | ^ + | '}' expected, but identifier found +-- [E040] Syntax Error: tests/neg-custom-args/captures/caps-reach.scala:10:46 ------------------------------------------ +10 | val consumers5 = ListBuffer.empty[() ->{f.rd.rd} Unit] // error + | ^ + | '}' expected, but '.' found +-- [E040] Syntax Error: tests/neg-custom-args/captures/caps-reach.scala:11:46 ------------------------------------------ +11 | val consumers6 = ListBuffer.empty[() ->{f * *} Unit] // error + | ^ + | '}' expected, but identifier found +-- Error: tests/neg-custom-args/captures/caps-reach.scala:6:42 --------------------------------------------------------- +6 | val consumers1 = ListBuffer.empty[() ->{caps.cap*} Unit] // error + | ^^^^^^^^^ + | Cannot form a reach capability from `cap` +-- Error: tests/neg-custom-args/captures/caps-reach.scala:7:42 --------------------------------------------------------- +7 | val consumers2 = ListBuffer.empty[() ->{caps.cap*.rd} Unit] // error + | ^^^^^^^^^^^^ + | Cannot form a reach capability from `cap` diff --git a/tests/neg-custom-args/captures/caps-reach.scala b/tests/neg-custom-args/captures/caps-reach.scala new file mode 100644 index 000000000000..c37a58b79c0d --- /dev/null +++ b/tests/neg-custom-args/captures/caps-reach.scala @@ -0,0 +1,11 @@ +import language.experimental.captureChecking +import scala.collection.mutable.ListBuffer + +class MyContainer: + val f: Object^ = ??? + val consumers1 = ListBuffer.empty[() ->{caps.cap*} Unit] // error + val consumers2 = ListBuffer.empty[() ->{caps.cap*.rd} Unit] // error + val consumers3 = ListBuffer.empty[() ->{f*.rd} Unit] // ok + val consumers4 = ListBuffer.empty[() ->{f.rd*} Unit] // error + val consumers5 = ListBuffer.empty[() ->{f.rd.rd} Unit] // error + val consumers6 = ListBuffer.empty[() ->{f * *} Unit] // error diff --git a/tests/neg-custom-args/captures/capset-bound.scala b/tests/neg-custom-args/captures/capset-bound.scala index c00f61240dea..ad79e1a8e6c1 100644 --- a/tests/neg-custom-args/captures/capset-bound.scala +++ b/tests/neg-custom-args/captures/capset-bound.scala @@ -1,3 +1,4 @@ +import language.experimental.captureChecking import caps.* class IO @@ -5,14 +6,14 @@ class IO case class File(io: IO^) def test(io1: IO^, io2: IO^) = - def f[C >: CapSet^{io1} <: CapSet^](file: File^{C^}) = ??? + def f[C^ >: {io1}](file: File^{C}) = ??? val f1: File^{io1} = ??? val f2: File^{io2} = ??? val f3: File^{io1, io2} = ??? - f[CapSet^{io1}](f1) - f[CapSet^{io1}](f2) // error - f[CapSet^{io1}](f3) // error - f[CapSet^{io2}](f2) // error - f[CapSet^{io1, io2}](f1) - f[CapSet^{io1, io2}](f2) - f[CapSet^{io1, io2}](f3) \ No newline at end of file + f[{io1}](f1) + f[{io1}](f2) // error + f[{io1}](f3) // error + f[{io2}](f2) // error + f[{io1, io2}](f1) + f[{io1, io2}](f2) + f[{io1, io2}](f3) \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-bound2.scala b/tests/neg-custom-args/captures/capset-bound2.scala index 679606f0e43c..803ec6419a21 100644 --- a/tests/neg-custom-args/captures/capset-bound2.scala +++ b/tests/neg-custom-args/captures/capset-bound2.scala @@ -2,12 +2,11 @@ import caps.* class IO -def f[C^](io: IO^{C^}) = ??? +def f[C^](io: IO^{C}) = ??? def test = - f[CapSet](???) - f[CapSet^{}](???) - f[CapSet^](???) + f[{}](???) + f[{}](???) + f[{cap}](???) f[Nothing](???) // error f[String](???) // error - \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-members-nohat.scala b/tests/neg-custom-args/captures/capset-members-nohat.scala new file mode 100644 index 000000000000..3d931200f326 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members-nohat.scala @@ -0,0 +1,30 @@ +import language.experimental.captureChecking +import caps.* + +trait Abstract[X^]: + type C^ >: {X} + // Don't test the return type using Unit, because it is a pure type. + def boom(): AnyRef^{C} + +class Concrete extends Abstract[{}]: + type C^ = {} + // TODO: Why do we get error without the return type here? + def boom(): AnyRef = new Object + +class Concrete2 extends Abstract[{}]: + type C^ = {} + def boom(): AnyRef^ = new Object // error + +class Concrete3 extends Abstract[{}]: + def boom(): AnyRef = new Object + +class Concrete4(a: AnyRef^) extends Abstract[{a}]: + type C^ = {} // error + def boom(): AnyRef^{a} = a // error + +class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[{a}]: + type C^ = {a} + def boom(): AnyRef^{b} = b // error + +class Concrete6(a: AnyRef^, b: AnyRef^) extends Abstract[{a}]: + def boom(): AnyRef^{b} = b // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-members.scala b/tests/neg-custom-args/captures/capset-members.scala index 540216852a43..1dddef3e5ca3 100644 --- a/tests/neg-custom-args/captures/capset-members.scala +++ b/tests/neg-custom-args/captures/capset-members.scala @@ -1,30 +1,30 @@ +import language.experimental.captureChecking import caps.* trait Abstract[X^]: - type C >: X <: CapSet^ + type C^ >: {X} // Don't test the return type using Unit, because it is a pure type. - def boom(): AnyRef^{C^} + def boom(): AnyRef^{C} -class Concrete extends Abstract[CapSet^{}]: - type C = CapSet^{} +class Concrete extends Abstract[{}]: + type C^ = {} // TODO: Why do we get error without the return type here? def boom(): AnyRef = new Object -class Concrete2 extends Abstract[CapSet^{}]: - type C = CapSet^{} +class Concrete2 extends Abstract[{}]: + type C^ = {} def boom(): AnyRef^ = new Object // error -class Concrete3 extends Abstract[CapSet^{}]: +class Concrete3 extends Abstract[{}]: def boom(): AnyRef = new Object -class Concrete4(a: AnyRef^) extends Abstract[CapSet^{a}]: - type C = CapSet // error +class Concrete4(a: AnyRef^) extends Abstract[{a}]: + type C^ = {} // error def boom(): AnyRef^{a} = a // error -class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]: - type C = CapSet^{a} +class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[{a}]: + type C^ = {a} def boom(): AnyRef^{b} = b // error -class Concrete6(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]: +class Concrete6(a: AnyRef^, b: AnyRef^) extends Abstract[{a}]: def boom(): AnyRef^{b} = b // error - \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-members2.check b/tests/neg-custom-args/captures/capset-members2.check new file mode 100644 index 000000000000..903f875432a4 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members2.check @@ -0,0 +1,12 @@ +-- Error: tests/neg-custom-args/captures/capset-members2.scala:5:10 ---------------------------------------------------- +5 | type C^[T] // error + | ^ + | `C` cannot have type parameters, because it ranges over capture sets +-- Error: tests/neg-custom-args/captures/capset-members2.scala:7:13 ---------------------------------------------------- +7 | def foo[C^[_]]: Int // error + | ^ + | `C` cannot have type parameters, because it ranges over capture sets +-- Error: tests/neg-custom-args/captures/capset-members2.scala:9:20 ---------------------------------------------------- +9 | def bar [M[_], C^ <: M]: Int // error + | ^^^^ + | Illegal type bounds: >: scala.caps.CapSet <: M. C^ can only have capture sets as bounds diff --git a/tests/neg-custom-args/captures/capset-members2.scala b/tests/neg-custom-args/captures/capset-members2.scala new file mode 100644 index 000000000000..727dd3e1e742 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members2.scala @@ -0,0 +1,9 @@ +import language.experimental.captureChecking +import caps.* + +trait Foo: + type C^[T] // error + + def foo[C^[_]]: Int // error + + def bar [M[_], C^ <: M]: Int // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-members3.check b/tests/neg-custom-args/captures/capset-members3.check new file mode 100644 index 000000000000..246bced980d6 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members3.check @@ -0,0 +1,6 @@ +-- [E095] Syntax Error: tests/neg-custom-args/captures/capset-members3.scala:5:10 -------------------------------------- +5 | type C^ _ // error + | ^ + | =, >:, or <: expected, but '_' found + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/capset-members3.scala b/tests/neg-custom-args/captures/capset-members3.scala new file mode 100644 index 000000000000..c8b767ae167c --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members3.scala @@ -0,0 +1,6 @@ +import language.experimental.captureChecking +import caps.* + +trait Foo: + type C^ _ // error + diff --git a/tests/neg-custom-args/captures/capset-members4.scala b/tests/neg-custom-args/captures/capset-members4.scala new file mode 100644 index 000000000000..698ef28c1fb4 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members4.scala @@ -0,0 +1,18 @@ +import language.experimental.captureChecking +import caps.* + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + val z: Any^ = ??? + def onlyWithZ[C^](using c: Contains[C, z.type]) = ??? + + trait Foo: + type C^ >: {z,x} <: {x,y,z} + + val foo: Foo = ??? + onlyWithZ[{foo.C}] + onlyWithZ[{z}] + onlyWithZ[{x,z}] + onlyWithZ[{x,y,z}] + onlyWithZ[{x,y}] // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-members5.scala b/tests/neg-custom-args/captures/capset-members5.scala new file mode 100644 index 000000000000..f79eb9df5c7c --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members5.scala @@ -0,0 +1,31 @@ +import language.experimental.captureChecking +import caps.* + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + trait CaptureSet: + type A^ >: {y} <: {x} + type B^ = {x} + type C^ <: {x} + type D^ : Ctx // error + type E^ <: {C} + type F^ <: {C} + type G^ <: {x, y} + type H^ >: {x} <: {x,y} : Ctx // error + type I^ = {y, G, H} + type J = {O.z} + type K^ = {x, O.z} + type L^ <: {x, y, O.z} + type M^ >: {x, y, O.z} <: {C} + type N^ >: {x} <: {x} + type O^ >: {O.z} <: {O.z} + type P^ >: {B,D} // error + type Q^ >: {E} <: Int // error + type R^ >: {E} <: Int // error + type S^ >: Nothing <: Int // error + type T >: Nothing <: Int \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-variance.scala b/tests/neg-custom-args/captures/capset-variance.scala new file mode 100644 index 000000000000..2ad97a014648 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-variance.scala @@ -0,0 +1,32 @@ +import language.experimental.captureChecking + +trait Test: + type T[-C^] + type U[+C^] + type C^ + type D^ + + def foo[C^](x: T[C]): Unit = ??? + def bar(x: T[{}]): Unit = ??? + def baz(x: T[{caps.cap}]): Unit = ??? + def foo2[C^](x: U[C]): Unit = ??? + def bar2(x: U[{}]): Unit = ??? + def baz2(x: U[{caps.cap}]): Unit = ??? + + def test = + val t: T[{C}] = ??? + val t2: T[C] = ??? + val u: U[{D}] = ??? + val u2: U[D] = ??? + foo(t) + bar(t) + baz(t) // error + foo2(u) + bar2(u) // error + baz2(u) + foo(t2) + bar(t2) + baz(t2) // error + foo2(u2) + bar2(u2) // error + baz2(u2) \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capt-capability.scala b/tests/neg-custom-args/captures/capt-capability.scala new file mode 100644 index 000000000000..0f293872da25 --- /dev/null +++ b/tests/neg-custom-args/captures/capt-capability.scala @@ -0,0 +1,9 @@ +import caps.{Capability, Sharable} + +def foo() = + val x: Sharable = ??? + + val z3 = + if x == null then (y: Unit) => x else (y: Unit) => new Capability() {} // error + + diff --git a/tests/neg-custom-args/captures/capt-depfun.check b/tests/neg-custom-args/captures/capt-depfun.check index 7cd838d72dc0..369c9915bbe0 100644 --- a/tests/neg-custom-args/captures/capt-depfun.check +++ b/tests/neg-custom-args/captures/capt-depfun.check @@ -1,12 +1,9 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt-depfun.scala:11:43 ---------------------------------- -11 | val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error // error: separatioon +11 | val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error | ^^^^^^^ | Found: Str^{} ->{ac, y, z} Str^{y, z} - | Required: Str^{y, z} ->{fresh} Str^{y, z} + | Required: Str^{y, z} => Str^{y, z} + | + | where: => refers to a fresh root capability in the type of value dc | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/capt-depfun.scala:11:24 ------------------------------------------------------- -11 | val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error // error: separatioon - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Separation failure: value dc's type Str^{y, z} => Str^{y, z} hides parameters y and z. - | The parameters need to be annotated with @consume to allow this. diff --git a/tests/neg-custom-args/captures/capt-depfun.scala b/tests/neg-custom-args/captures/capt-depfun.scala index 384c403bdd27..e973bc0486ff 100644 --- a/tests/neg-custom-args/captures/capt-depfun.scala +++ b/tests/neg-custom-args/captures/capt-depfun.scala @@ -2,10 +2,10 @@ import annotation.retains class C -type Cap = C @retains(caps.cap) +type Cap = C @retains[caps.cap.type] class Str def f(y: Cap, z: Cap) = - def g(): C @retains(y, z) = ??? - val ac: ((x: Cap) => Str @retains(x) => Str @retains(x)) = ??? - val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error // error: separatioon + def g(): C @retains[y.type | z.type] = ??? + val ac: ((x: Cap) => Str @retains[x.type] => Str @retains[x.type]) = ??? + val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error diff --git a/tests/neg-custom-args/captures/capt-depfun2.scala b/tests/neg-custom-args/captures/capt-depfun2.scala index cb4bc5f9634d..d5ea3b05f168 100644 --- a/tests/neg-custom-args/captures/capt-depfun2.scala +++ b/tests/neg-custom-args/captures/capt-depfun2.scala @@ -1,11 +1,11 @@ import annotation.retains class C -type Cap = C @retains(caps.cap) +type Cap = C @retains[caps.cap.type] class Str def f(y: Cap, z: Cap) = - def g(): C @retains(y, z) = ??? - val ac: ((x: Cap) => Array[Str @retains(x)]) = ??? + def g(): C @retains[y.type | z.type] = ??? + val ac: ((x: Cap) => Array[Str @retains[x.type]]) = ??? val dc = ac(g()) // error: Needs explicit type Array[? >: Str <: {y, z} Str] // This is a shortcoming of rechecking since the originally inferred // type is `Array[Str]` and the actual type after rechecking diff --git a/tests/neg-custom-args/captures/capt-test.scala b/tests/neg-custom-args/captures/capt-test.scala index f200bb6083b5..3d5ebd3ee6ff 100644 --- a/tests/neg-custom-args/captures/capt-test.scala +++ b/tests/neg-custom-args/captures/capt-test.scala @@ -2,8 +2,8 @@ import annotation.retains import language.experimental.erasedDefinitions class CT[E <: Exception] -type CanThrow[E <: Exception] = CT[E] @retains(caps.cap) -type Top = Any @retains(caps.cap) +type CanThrow[E <: Exception] = CT[E] @retains[caps.cap.type] +type Top = Any @retains[caps.cap.type] infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?=> R @@ -14,7 +14,7 @@ def raise[E <: Exception](e: E): Nothing throws E = throw e def foo(x: Boolean): Int throws Fail = if x then 1 else raise(Fail()) -def handle[E <: Exception, R <: Top](op: (CT[E] @retains(caps.cap)) => R)(handler: E => R): R = +def handle[E <: Exception, R <: Top](op: (CT[E] @retains[caps.cap.type]) => R)(handler: E => R): R = val x: CT[E] = ??? try op(x) catch case ex: E => handler(ex) diff --git a/tests/neg-custom-args/captures/capt-wf-typer.scala b/tests/neg-custom-args/captures/capt-wf-typer.scala index 09b2841d3c77..e01cc5f0e9b4 100644 --- a/tests/neg-custom-args/captures/capt-wf-typer.scala +++ b/tests/neg-custom-args/captures/capt-wf-typer.scala @@ -6,6 +6,6 @@ object foo def test(c: Cap, other: String): Unit = val x7: String^{c} = ??? // OK - val x8: String @retains(x7 + x7) = ??? // error - val x9: String @retains(foo) = ??? // error + val x8: String @retains[""] = ??? // error + val x9: String @retains[foo] = ??? // error () \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check index e0eb8731b3de..5c1008190886 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -40,35 +40,50 @@ -- Error: tests/neg-custom-args/captures/capt1.scala:36:16 ------------------------------------------------------------- 36 | 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 () -> C^ since + | the part C^ of that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:36:24 ---------------------------------------- 36 | val z2 = h[() -> Cap](() => x) // error // error | ^^^^^^^ - | Found: () ->{x} box C^{x} - | Required: () -> box C^ + |Found: () ->{x} C^{x} + |Required: () -> C^ + | + |where: ^ refers to a fresh root capability created in value z2 when checking argument to parameter a of method h | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/capt1.scala:38:13 ------------------------------------------------------------- -38 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error - | ^^^^^^^^^^^^^^^^^^^^^^^ - | Type variable X of method h cannot be instantiated to box () ->{x} C^ since +38 | val z3 = h[(() -> Cap) @retains[x.type]](() => x)(() => C()) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Type variable X of method h cannot be instantiated to () ->{x} C^ since | the part C^ of that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- Error: tests/neg-custom-args/captures/capt1.scala:43:7 -------------------------------------------------------------- 43 | if x == null then // error: separation | ^ | Separation failure: Illegal access to {x} which is hidden by the previous definition | of value z1 with type () => C^. | This type hides capabilities {x} + | + | where: => refers to a fresh root capability in the type of value z1 + | ^ refers to a fresh root capability in the type of value z1 -- Error: tests/neg-custom-args/captures/capt1.scala:44:12 ------------------------------------------------------------- 44 | () => x // error: separation | ^ | Separation failure: Illegal access to {x} which is hidden by the previous definition | of value z1 with type () => C^. | This type hides capabilities {x} + | + | where: => refers to a fresh root capability in the type of value z1 + | ^ refers to a fresh root capability in the type of value z1 -- Error: tests/neg-custom-args/captures/capt1.scala:47:2 -------------------------------------------------------------- 47 | x // error: separation | ^ | Separation failure: Illegal access to {x} which is hidden by the previous definition | of value z1 with type () => C^. | This type hides capabilities {x} + | + | where: => refers to a fresh root capability in the type of value z1 + | ^ refers to a fresh root capability in the type of value z1 diff --git a/tests/neg-custom-args/captures/capt1.scala b/tests/neg-custom-args/captures/capt1.scala index 48778dbd6716..7b612363958e 100644 --- a/tests/neg-custom-args/captures/capt1.scala +++ b/tests/neg-custom-args/captures/capt1.scala @@ -1,22 +1,22 @@ import annotation.retains class C -def f(x: C @retains(caps.cap), y: C): () -> C = +def f(x: C @retains[caps.cap.type], y: C): () -> C = () => if x == null then y else y // error -def g(x: C @retains(caps.cap), y: C): Matchable = +def g(x: C @retains[caps.cap.type], y: C): Matchable = () => if x == null then y else y // error -def h1(x: C @retains(caps.cap), y: C): Any = +def h1(x: C @retains[caps.cap.type], y: C): Any = def f() = if x == null then y else y () => f() // ok -def h2(x: C @retains(caps.cap)): Matchable = +def h2(x: C @retains[caps.cap.type]): Matchable = def f(y: Int) = if x == null then y else y // error f class A -type Cap = C @retains(caps.cap) +type Cap = C @retains[caps.cap.type] def h3(x: Cap): A = class F(y: Int) extends A: // error @@ -30,12 +30,12 @@ def h4(x: Cap, y: Int): A = def f1(c: Cap): () ->{c} c.type = () => c // ok def foo() = - val x: C @retains(caps.cap) = ??? + val x: C @retains[caps.cap.type] = ??? def h[X](a: X)(b: X) = a val z2 = h[() -> Cap](() => x) // error // error (() => C()) - val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error + val z3 = h[(() -> Cap) @retains[x.type]](() => x)(() => C()) // error val z1: () => Cap = f1(x) diff --git a/tests/neg-custom-args/captures/capt3.scala b/tests/neg-custom-args/captures/capt3.scala index 44a7ffdc6c4a..a9329a9e4290 100644 --- a/tests/neg-custom-args/captures/capt3.scala +++ b/tests/neg-custom-args/captures/capt3.scala @@ -1,6 +1,6 @@ import annotation.retains class C -type Cap = C @retains(caps.cap) +type Cap = C @retains[caps.cap.type] def test1() = val x: Cap = C() diff --git a/tests/neg-custom-args/captures/capture-parameters.scala b/tests/neg-custom-args/captures/capture-parameters.scala index d59305ae0cb8..8f5de6346268 100644 --- a/tests/neg-custom-args/captures/capture-parameters.scala +++ b/tests/neg-custom-args/captures/capture-parameters.scala @@ -2,8 +2,7 @@ import caps.* class C -def test[X^, Y^, Z >: X <: Y](x: C^{X^}, y: C^{Y^}, z: C^{Z^}) = - val x2z: C^{Z^} = x - val z2y: C^{Y^} = z - val x2y: C^{Y^} = x // error - \ No newline at end of file +def test[X^, Y^, Z^ >: X <: Y](x: C^{X}, y: C^{Y}, z: C^{Z}) = + val x2z: C^{Z} = x + val z2y: C^{Y} = z + val x2y: C^{Y} = x // error diff --git a/tests/neg-custom-args/captures/capture-poly.scala b/tests/neg-custom-args/captures/capture-poly.scala index 88989b418726..c0608ad812de 100644 --- a/tests/neg-custom-args/captures/capture-poly.scala +++ b/tests/neg-custom-args/captures/capture-poly.scala @@ -3,20 +3,20 @@ import caps.* trait Foo extends Capability trait CaptureSet: - type C >: CapSet <: CapSet^ + type C^ -def capturePoly[C^](a: Foo^{C^}): Foo^{C^} = a -def capturePoly2(c: CaptureSet)(a: Foo^{c.C^}): Foo^{c.C^} = a +def capturePoly[C^](a: Foo^{C}): Foo^{C} = a +def capturePoly2(c: CaptureSet)(a: Foo^{c.C}): Foo^{c.C} = a def test = val x: Foo^ = ??? val y: Foo^ = ??? object X extends CaptureSet: - type C = CapSet^{x} + type C = {x} - val z1: Foo^{X.C^} = x - val z2: Foo^{X.C^} = y // error + val z1: Foo^{X.C} = x + val z2: Foo^{X.C} = y // error val z3: Foo^{x} = capturePoly(x) val z4: Foo^{x} = capturePoly(y) // error diff --git a/tests/neg-custom-args/captures/capture-vars-subtyping2.scala b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala index 205451ee41ed..235bfb5e1cf0 100644 --- a/tests/neg-custom-args/captures/capture-vars-subtyping2.scala +++ b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala @@ -9,36 +9,37 @@ trait BoundsTest: 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^}] = + B^ <: {A}, + C^ <: {B}, + D^ <: C, + E^ <: {D}, + F^ <: {A,b}, + X^ <: {F,D}, + Y^ >: {F} <: {F,A,b}, + Z^ >: {b} <: {b,Y}, T <: List[Bar^{Z}]] = val e: E = ??? - val e2: CapSet^{E^} = e + val e2: CapSet^{E} = e val ed: D = e - val ed2: CapSet^{D^} = e + val ed2: CapSet^{D} = e val ec: C = e - val ec2: CapSet^{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 eb2: CapSet^{B} = e + locally: + val ea: A = e + val ea2: CapSet^{A} = e val ex: X = e // error - val ex2: CapSet^{X^} = e // error + val ex2: CapSet^{X} = e // error val f: F = ??? - val f2: CapSet^{F^} = f + val f2: CapSet^{F} = f val y: Y = f - val y2: CapSet^{Y^} = f + val y2: CapSet^{Y} = f val cb: CapSet^{b} = ??? val z: Z = cb - val z2: CapSet^{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 + testTransMixed[{x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {b,x,y,z}, List[Bar^{b}]] + testTransMixed[{x,y,z}, {x,y}, {x,y}, {x}, {}, {b,x}, {b}, {b,x}, {b}, List[Bar^{b}]] + testTransMixed[{x,y,z}, {x,y}, {x,y}, {x}, {}, {b,x}, {b}, {b,x}, {b,x,y,z}, List[Bar^{}]] // error diff --git a/tests/neg-custom-args/captures/cc-annot-value-classes.scala b/tests/neg-custom-args/captures/cc-annot-value-classes.scala new file mode 100644 index 000000000000..745b1c85b8b1 --- /dev/null +++ b/tests/neg-custom-args/captures/cc-annot-value-classes.scala @@ -0,0 +1,18 @@ +import language.experimental.captureChecking +import caps.* + +class Runner(val x: Int) extends AnyVal: + def runOps(@use ops: List[() => Unit]): Unit = + ops.foreach(_()) // ok + +class RunnerAlt(val x: Int): + def runOps(@use ops: List[() => Unit]): Unit = + ops.foreach(_()) // ok, of course + +class RunnerAltAlt(val x: Int) extends AnyVal: + def runOps(ops: List[() => Unit]): Unit = + ops.foreach(_()) // error, as expected + +class RunnerAltAltAlt(val x: Int): + def runOps(ops: List[() => Unit]): Unit = + ops.foreach(_()) // error, as expected diff --git a/tests/neg-custom-args/captures/cc-annot-value-classes2.scala b/tests/neg-custom-args/captures/cc-annot-value-classes2.scala new file mode 100644 index 000000000000..5821f9664f6b --- /dev/null +++ b/tests/neg-custom-args/captures/cc-annot-value-classes2.scala @@ -0,0 +1,16 @@ +import language.experimental.captureChecking +import caps.* +trait Ref extends Mutable +def kill(@consume x: Ref^): Unit = () + +class C1: + def myKill(@consume x: Ref^): Unit = kill(x) // ok + +class C2(val dummy: Int) extends AnyVal: + def myKill(@consume x: Ref^): Unit = kill(x) // ok, too + +class C3: + def myKill(x: Ref^): Unit = kill(x) // error + +class C4(val dummy: Int) extends AnyVal: + def myKill(x: Ref^): Unit = kill(x) // error, too diff --git a/tests/neg-custom-args/captures/cc-existential-conformance.check b/tests/neg-custom-args/captures/cc-existential-conformance.check index bc94b97fc327..549e1c0543b5 100644 --- a/tests/neg-custom-args/captures/cc-existential-conformance.check +++ b/tests/neg-custom-args/captures/cc-existential-conformance.check @@ -1,40 +1,48 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-existential-conformance.scala:8:24 -------------------- 8 | val y: A -> Fun[B^] = x // error | ^ - | Found: (x : A -> (x²: A) -> B^{localcap}) - | Required: A -> A -> B^{fresh} + | Found: (x : A -> (x²: A) -> B^) + | Required: A -> A -> B^² | - | where: x is a value in method test + | where: ^ refers to a root capability associated with the result type of (x²: A): B^ + | ^² refers to a fresh root capability in the type of value y + | x is a value in method test | x² is a reference to a value parameter | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-existential-conformance.scala:9:29 -------------------- 9 | val z: A -> (x: A) -> B^ = y // error | ^ - | Found: (y : A -> A -> B^) + | Found: A -> A -> B^{y*} | Required: A -> (x: A) -> B^ | + | where: ^ refers to a root capability associated with the result type of (x: A): B^ + | | Note that the existential capture root in B^ - | cannot subsume the capability cap + | cannot subsume the capability y* since that capability is not a `Sharable` capability | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-existential-conformance.scala:13:19 ------------------- 13 | val y: Fun[B^] = x // error | ^ - | Found: (x : (x²: A) -> B^{localcap}) - | Required: A -> B^{fresh} + | Found: (x : (x²: A) -> B^) + | Required: A -> B^² | - | where: x is a value in method test2 + | where: ^ refers to a root capability associated with the result type of (x²: A): B^ + | ^² refers to a fresh root capability in the type of value y + | x is a value in method test2 | x² is a reference to a value parameter | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-existential-conformance.scala:14:24 ------------------- 14 | val z: (x: A) -> B^ = y // error | ^ - | Found: (y : A -> B^) + | Found: A -> B^{y*} | Required: (x: A) -> B^ | + | where: ^ refers to a root capability associated with the result type of (x: A): B^ + | | Note that the existential capture root in B^ - | cannot subsume the capability cap + | cannot subsume the capability y* since that capability is not a `Sharable` capability | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/cc-fresh-levels.check b/tests/neg-custom-args/captures/cc-fresh-levels.check new file mode 100644 index 000000000000..029ee9280993 --- /dev/null +++ b/tests/neg-custom-args/captures/cc-fresh-levels.check @@ -0,0 +1,30 @@ +Flag -source set repeatedly +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-fresh-levels.scala:14:10 ------------------------------ +14 | r.put(x) // error + | ^ + | Found: IO^{x} + | Required: IO^ + | + | where: ^ refers to a fresh root capability in the type of value r + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-fresh-levels.scala:17:10 ------------------------------ +17 | r.put(innerIO) // error + | ^^^^^^^ + | Found: IO^{innerIO} + | Required: IO^ + | + | where: ^ refers to a fresh root capability in the type of value r + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-fresh-levels.scala:18:9 ------------------------------- +18 | runIO: innerIO => // error + | ^ + |Found: (innerIO: IO^?) ->? Unit + |Required: IO^ => Unit + | + |where: => refers to a fresh root capability created in method test1 when checking argument to parameter op of method runIO + | ^ refers to the universal root capability +19 | r.put(innerIO) + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/cc-poly-1.scala b/tests/neg-custom-args/captures/cc-poly-1.scala index 580b124bc8f3..b205b9b25246 100644 --- a/tests/neg-custom-args/captures/cc-poly-1.scala +++ b/tests/neg-custom-args/captures/cc-poly-1.scala @@ -6,7 +6,7 @@ object Test: class C extends Capability class D - def f[X^](x: D^{X^}): D^{X^} = x + def f[X^](x: D^{X}): D^{X} = x def test(c1: C, c2: C) = f[Any](D()) // error diff --git a/tests/neg-custom-args/captures/cc-poly-2.check b/tests/neg-custom-args/captures/cc-poly-2.check index 90568de385b3..28e0e1863760 100644 --- a/tests/neg-custom-args/captures/cc-poly-2.check +++ b/tests/neg-custom-args/captures/cc-poly-2.check @@ -4,6 +4,8 @@ | Found: (d : Test.D^) | Required: Test.D^{c1} | + | where: ^ refers to a fresh root capability in the type of value d + | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-2.scala:16:20 ------------------------------------ 16 | val _: D^{c1} = x // error diff --git a/tests/neg-custom-args/captures/cc-poly-2.scala b/tests/neg-custom-args/captures/cc-poly-2.scala index c9249ba59437..8fb590f4f769 100644 --- a/tests/neg-custom-args/captures/cc-poly-2.scala +++ b/tests/neg-custom-args/captures/cc-poly-2.scala @@ -6,7 +6,7 @@ object Test: class C extends Capability class D - def f[X^](x: D^{X^}): D^{X^} = x + def f[X^](x: D^{X}): D^{X} = x def test(c1: C, c2: C) = val d: D^ = D() diff --git a/tests/neg-custom-args/captures/cc-poly-source.scala b/tests/neg-custom-args/captures/cc-poly-source.scala index e08ea36a6fc9..915903d670e8 100644 --- a/tests/neg-custom-args/captures/cc-poly-source.scala +++ b/tests/neg-custom-args/captures/cc-poly-source.scala @@ -10,14 +10,14 @@ import caps.use class Listener class Source[X^]: - private var listeners: Set[Listener^{X^}] = Set.empty - def register(x: Listener^{X^}): Unit = + private var listeners: Set[Listener^{X}] = Set.empty + def register(x: Listener^{X}): Unit = listeners += x - def allListeners: Set[Listener^{X^}] = listeners + def allListeners: Set[Listener^{X}] = listeners def test1(lbl1: Label^, lbl2: Label^) = - val src = Source[CapSet^{lbl1, lbl2}] + val src = Source[{lbl1, lbl2}] def l1: Listener^{lbl1} = ??? val l2: Listener^{lbl2} = ??? src.register{l1} @@ -30,8 +30,8 @@ import caps.use val listeners = lbls.map(makeListener) // error // we get an error here because we no longer allow contravariant cap // to subsume other capabilities. The problem can be solved by declaring - // Label a SharedCapability, see cc-poly-source-capability.scala - val src = Source[CapSet^{lbls*}] + // Label a Sharable, see cc-poly-source-capability.scala + val src = Source[{lbls*}] for l <- listeners do src.register(l) val ls = src.allListeners diff --git a/tests/neg-custom-args/captures/cc-this2.check b/tests/neg-custom-args/captures/cc-this2.check index dc61fe2e0396..932e7c336ef0 100644 --- a/tests/neg-custom-args/captures/cc-this2.check +++ b/tests/neg-custom-args/captures/cc-this2.check @@ -9,4 +9,6 @@ | illegal inheritance: self type D^ of class D does not conform to self type C | of parent class C | + | where: ^ refers to the universal root capability + | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/cc-this3.check b/tests/neg-custom-args/captures/cc-this3.check index d57471c6872e..6199a39bed17 100644 --- a/tests/neg-custom-args/captures/cc-this3.check +++ b/tests/neg-custom-args/captures/cc-this3.check @@ -1,12 +1,14 @@ --- [E058] Type Mismatch Error: tests/neg-custom-args/captures/cc-this3.scala:8:6 --------------------------------------- -8 |class B extends A: // error +-- [E058] Type Mismatch Error: tests/neg-custom-args/captures/cc-this3.scala:9:6 --------------------------------------- +9 |class B extends A: // error | ^ | illegal inheritance: self type B^ of class B does not conform to self type A^{} | of parent class A | + | where: ^ refers to the universal root capability + | | longer explanation available when compiling with `-explain` --- [E058] Type Mismatch Error: tests/neg-custom-args/captures/cc-this3.scala:11:6 -------------------------------------- -11 |class C(val f: () => Int) extends A // error +-- [E058] Type Mismatch Error: tests/neg-custom-args/captures/cc-this3.scala:12:6 -------------------------------------- +12 |class C(val f: () => Int) extends A // error | ^ | illegal inheritance: self type C^{C.this.f} of class C does not conform to self type A^{} | of parent class A diff --git a/tests/neg-custom-args/captures/cc-this3.scala b/tests/neg-custom-args/captures/cc-this3.scala index 0a36cde8173b..c3d751b12f67 100644 --- a/tests/neg-custom-args/captures/cc-this3.scala +++ b/tests/neg-custom-args/captures/cc-this3.scala @@ -1,3 +1,4 @@ +import language.experimental.captureChecking class Cap extends caps.Capability def eff(using Cap): Unit = () diff --git a/tests/neg-custom-args/captures/cc-this4.check b/tests/neg-custom-args/captures/cc-this4.check index d650d905f9b5..d97c304a9cd3 100644 --- a/tests/neg-custom-args/captures/cc-this4.check +++ b/tests/neg-custom-args/captures/cc-this4.check @@ -4,4 +4,6 @@ | Found: (C.this : C^) | Required: C | + | where: ^ refers to the universal root capability + | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/cc1.scala b/tests/neg-custom-args/captures/cc1.scala index 6787b417a3b2..cc2c5bde3220 100644 --- a/tests/neg-custom-args/captures/cc1.scala +++ b/tests/neg-custom-args/captures/cc1.scala @@ -1,5 +1,10 @@ +import language.experimental.captureChecking import annotation.retains +import annotation.retainsCap + object Test: - def f[A <: Matchable @retains(caps.cap)](x: A): Matchable = x // error + def f[A <: Matchable @retains[caps.cap.type]](x: A): Matchable = x // error + + def g[A <: Matchable @retainsCap](x: A): Matchable = x // error diff --git a/tests/neg-custom-args/captures/check-dummy-symbol.check b/tests/neg-custom-args/captures/check-dummy-symbol.check new file mode 100644 index 000000000000..345934089910 --- /dev/null +++ b/tests/neg-custom-args/captures/check-dummy-symbol.check @@ -0,0 +1,12 @@ +-- [E195] Type Error: tests/neg-custom-args/captures/check-dummy-symbol.scala:6:21 ------------------------------------- +6 |def f(a: A): a.C = a.C // error + | ^^^ + | dummy term capture parameter value C cannot be used as a value + | + | longer explanation available when compiling with `-explain` +-- [E195] Type Error: tests/neg-custom-args/captures/check-dummy-symbol.scala:7:15 ------------------------------------- +7 |def g[C^]: C = C // error + | ^ + | dummy term capture parameter value C cannot be used as a value + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/check-dummy-symbol.scala b/tests/neg-custom-args/captures/check-dummy-symbol.scala new file mode 100644 index 000000000000..c2cafcc14cf1 --- /dev/null +++ b/tests/neg-custom-args/captures/check-dummy-symbol.scala @@ -0,0 +1,7 @@ +import language.experimental.captureChecking + +class A: + type C^ + +def f(a: A): a.C = a.C // error +def g[C^]: C = C // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/class-level-attack.check b/tests/neg-custom-args/captures/class-level-attack.check new file mode 100644 index 000000000000..a2f60535d86f --- /dev/null +++ b/tests/neg-custom-args/captures/class-level-attack.check @@ -0,0 +1,16 @@ +-- Error: tests/neg-custom-args/captures/class-level-attack.scala:12:24 ------------------------------------------------ +12 | val r: Ref[IO^] = Ref[IO^](io) // error: + | ^^^ + | Type variable X of constructor Ref cannot be instantiated to IO^ since + | that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/class-level-attack.scala:17:26 --------------------------- +17 | def set(x: IO^) = r.put(x) // error + | ^ + | Found: IO^{x} + | Required: IO^ + | + | where: ^ refers to a fresh root capability in the type of value r + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/class-level-attack.scala b/tests/neg-custom-args/captures/class-level-attack.scala new file mode 100644 index 000000000000..b9847ea4045d --- /dev/null +++ b/tests/neg-custom-args/captures/class-level-attack.scala @@ -0,0 +1,24 @@ +import language.experimental.captureChecking +import caps.* + +class IO + +class Ref[X](init: X): + var x = init + def get: X = x + def put(y: X): Unit = x = y + +class C(io: IO^): + val r: Ref[IO^] = Ref[IO^](io) // error: + //Type variable X of constructor Ref cannot be instantiated to box IO^ since + //that type captures the root capability `cap`. + // where: ^ refers to the universal root capability + val r2: Ref[IO^] = Ref(io) + def set(x: IO^) = r.put(x) // error + +def outer(outerio: IO^) = + val c = C(outerio) + def test(innerio: IO^) = + c.set(innerio) + + diff --git a/tests/neg-custom-args/captures/classified-inheritance.check b/tests/neg-custom-args/captures/classified-inheritance.check new file mode 100644 index 000000000000..629f815c4b06 --- /dev/null +++ b/tests/neg-custom-args/captures/classified-inheritance.check @@ -0,0 +1,8 @@ +-- Error: tests/neg-custom-args/captures/classified-inheritance.scala:5:6 ---------------------------------------------- +5 |class C2 extends caps.Control, caps.Mutable // error + | ^ + | class C2 inherits two unrelated classifier traits: trait Mutable and trait Control +-- Error: tests/neg-custom-args/captures/classified-inheritance.scala:10:6 --------------------------------------------- +10 |class C3 extends Matrix, Async // error + | ^ + | class C3 inherits two unrelated classifier traits: trait Control and trait Mutable diff --git a/tests/neg-custom-args/captures/classified-inheritance.scala b/tests/neg-custom-args/captures/classified-inheritance.scala new file mode 100644 index 000000000000..11f342d314a7 --- /dev/null +++ b/tests/neg-custom-args/captures/classified-inheritance.scala @@ -0,0 +1,10 @@ +import language.experimental.captureChecking + +class C1 extends caps.Control, caps.Sharable // OK + +class C2 extends caps.Control, caps.Mutable // error + +trait Async extends caps.Control +class Matrix extends caps.Mutable + +class C3 extends Matrix, Async // error diff --git a/tests/neg-custom-args/captures/classified-wf.check b/tests/neg-custom-args/captures/classified-wf.check new file mode 100644 index 000000000000..9552653d84df --- /dev/null +++ b/tests/neg-custom-args/captures/classified-wf.check @@ -0,0 +1,5 @@ +-- Error: tests/neg-custom-args/captures/classified-wf.scala:7:19 ------------------------------------------------------ +7 |def foo(x: Object^{cap.only[Async]}) = ??? // error + | ^^^^^^^^^^^^^^^ + | scala.caps.cap.only[Async] is not well-formed since class Async is not a classifier class. + | A classifier class is a class extending `caps.Capability` and directly extending `caps.Classifier`. diff --git a/tests/neg-custom-args/captures/classified-wf.scala b/tests/neg-custom-args/captures/classified-wf.scala new file mode 100644 index 000000000000..98a61b2d7b7d --- /dev/null +++ b/tests/neg-custom-args/captures/classified-wf.scala @@ -0,0 +1,8 @@ +import caps.* + +class Async extends Capability + +class IO extends Capability, Classifier + +def foo(x: Object^{cap.only[Async]}) = ??? // error +def bar(x: Object^{cap.only[IO]}) = ??? // ok diff --git a/tests/neg-custom-args/captures/classifiers-1.scala b/tests/neg-custom-args/captures/classifiers-1.scala new file mode 100644 index 000000000000..ee49330ec801 --- /dev/null +++ b/tests/neg-custom-args/captures/classifiers-1.scala @@ -0,0 +1,9 @@ +class M extends caps.Mutable + +class M1(x: Int => Int) extends M // error + +def f(x: M^) = ??? + +def test(g: Int => Int) = f(new M1(g)) // error + + diff --git a/tests/neg-custom-args/captures/classifiers-secondclass.scala b/tests/neg-custom-args/captures/classifiers-secondclass.scala new file mode 100644 index 000000000000..c4378df5f9a5 --- /dev/null +++ b/tests/neg-custom-args/captures/classifiers-secondclass.scala @@ -0,0 +1,67 @@ +import language.experimental.captureChecking +import language.experimental.separationChecking +import caps.* + +// Test inspired by the "Gentrification Gone too Far?" paper +object Levels: + + trait Read extends Classifier, Capability + trait ReadWrite extends Classifier, Capability + + trait File(val name: String): + val r: Read^ + val rw: ReadWrite^ + // operations guarded by boxed capability members + val read: () ->{r} Int + val write: Int ->{rw} Unit + + object File: + def apply(s: String): File^ = new File(s) { + val r = new Read {} + val rw = new ReadWrite {} + val read = () => + println(s"Reading from $name with capability $r") + 42 + val write = (i: Int) => + println(s"Writing $i to $name with capability $rw") + } + + // Unfortunately, we do not have @use lambdas yet + trait UseFunction[U]: + def apply(@use f: File^): U + + def withFile[U](name: String)(block: UseFunction[U]): U = block(File(name)) // unrestricted use of files & other capabilities + def parReduce[U](xs: Seq[U])(op: (U, U) ->{cap.only[Read]} U): U = xs.reduce(op) // only Read-classified allowed + + @main def test = + withFile("foo.txt"): + new UseFunction[Unit]: + def apply(@use f: File^): Unit = + f.read() // ok + parReduce(1 to 1000): (a, b) => + a * b * f.read() // ok + parReduce(1 to 1000): (a, b) => // error + f.write(42) // the error stems from here + a + b + f.read() // ok + f.write(42) // ok, unrestricted access to file + + def testMulti = + withFile("foo.txt"): + new UseFunction[Unit]: + def apply(@use f: File^): Unit = + withFile("bar.txt"): + new UseFunction[Unit]: + def apply(@use g: File^): Unit = + f.read() // ok + g.read() // ok + parReduce(1 to 1000): (a, b) => + a * b * f.read() + g.read() // ok + parReduce(1 to 1000): (a, b) => // error + f.write(42) // the error stems from here + a + b + f.read() + g.read() // ok + parReduce(1 to 1000): (a, b) => // error + g.write(42) // the error stems from here + 0 + f.write(42) // ok, unrestricted access to file + g.write(42) // ok, unrestricted access to file + diff --git a/tests/neg-custom-args/captures/contracap.check b/tests/neg-custom-args/captures/contracap.check index 31b58acb80d5..e2a3e0958674 100644 --- a/tests/neg-custom-args/captures/contracap.check +++ b/tests/neg-custom-args/captures/contracap.check @@ -4,4 +4,6 @@ | Found: (f : (Ref[Int]^, Ref[Int]^) -> Unit) | Required: (Ref[Int]^{a}, Ref[Int]^{a}) -> Unit | + | where: ^ refers to the universal root capability + | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/contracap.scala b/tests/neg-custom-args/captures/contracap.scala index 0ba1507eb11f..085fff6f4903 100644 --- a/tests/neg-custom-args/captures/contracap.scala +++ b/tests/neg-custom-args/captures/contracap.scala @@ -4,7 +4,7 @@ import caps.* class Ref[T](init: T) extends Mutable: private var value: T = init def get: T = value - mut def set(newValue: T): Unit = value = newValue + update def set(newValue: T): Unit = value = newValue // a library function that assumes that a and b MUST BE separate def swap[T](a: Ref[Int]^, b: Ref[Int]^): Unit = ??? diff --git a/tests/neg-custom-args/captures/dcs-tvar.check b/tests/neg-custom-args/captures/dcs-tvar.check index 76b4036a8821..65fc12f82ba0 100644 --- a/tests/neg-custom-args/captures/dcs-tvar.check +++ b/tests/neg-custom-args/captures/dcs-tvar.check @@ -1,10 +1,14 @@ --- Error: tests/neg-custom-args/captures/dcs-tvar.scala:6:15 ----------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/dcs-tvar.scala:6:2 --------------------------------------- 6 | () => runOps(xs) // error - | ^^ - | Local reach capability xs* leaks into capture scope of method f. - | To allow this, the parameter xs should be declared with a @use annotation --- Error: tests/neg-custom-args/captures/dcs-tvar.scala:9:15 ----------------------------------------------------------- + | ^^^^^^^^^^^^^^^^ + | Found: () ->{xs*} Unit + | Required: () -> Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/dcs-tvar.scala:9:2 --------------------------------------- 9 | () => runOps(xs) // error - | ^^ - | Local reach capability xs* leaks into capture scope of method g. - | To allow this, the parameter xs should be declared with a @use annotation + | ^^^^^^^^^^^^^^^^ + | Found: () ->{xs*} Unit + | Required: () -> Unit + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/delayedRunops.check b/tests/neg-custom-args/captures/delayedRunops.check index 4e2fb9525e8f..5554a32bc10e 100644 --- a/tests/neg-custom-args/captures/delayedRunops.check +++ b/tests/neg-custom-args/captures/delayedRunops.check @@ -1,14 +1,27 @@ --- Error: tests/neg-custom-args/captures/delayedRunops.scala:17:13 ----------------------------------------------------- -17 | runOps(ops1) // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops.scala:15:4 --------------------------------- +15 | () => // error + | ^ + | Found: () ->{ops*} Unit + | Required: () -> Unit +16 | val ops1 = ops +17 | runOps(ops1) + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops.scala:27:4 --------------------------------- +27 | () => // error + | ^ + | Found: () ->{ops*} Unit + | Required: () -> Unit +28 | val ops1: List[() ->{ops*} Unit] = ops +29 | runOps(ops1) + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/delayedRunops.scala:23:13 ----------------------------------------------------- +23 | runOps(ops1) // error | ^^^^ - | Local reach capability ops* leaks into capture scope of method delayedRunOps1. - | To allow this, the parameter ops should be declared with a @use annotation --- Error: tests/neg-custom-args/captures/delayedRunops.scala:29:13 ----------------------------------------------------- -29 | runOps(ops1) // error - | ^^^^ - | Local reach capability ops* leaks into capture scope of method delayedRunOps3. + | Local reach capability ops* leaks into capture scope of method delayedRunOps2. | To allow this, the parameter ops should be declared with a @use annotation -- Error: tests/neg-custom-args/captures/delayedRunops.scala:22:16 ----------------------------------------------------- 22 | val ops1: List[() => Unit] = ops // error | ^^^^^^^^^^^^^^^^ - | Separation failure: value ops1's type List[box () => Unit] hides non-local parameter ops + | Separation failure: value ops1's type List[() => Unit] hides non-local parameter ops diff --git a/tests/neg-custom-args/captures/delayedRunops.scala b/tests/neg-custom-args/captures/delayedRunops.scala index 946d4324ddeb..a4cb0129d912 100644 --- a/tests/neg-custom-args/captures/delayedRunops.scala +++ b/tests/neg-custom-args/captures/delayedRunops.scala @@ -12,18 +12,18 @@ import caps.{use, consume} // unsound: impure operation pretended pure def delayedRunOps1(ops: List[() => Unit]): () ->{} Unit = - () => + () => // error val ops1 = ops - runOps(ops1) // error + runOps(ops1) // unsound: impure operation pretended pure def delayedRunOps2(@consume ops: List[() => Unit]): () ->{} Unit = () => val ops1: List[() => Unit] = ops // error - runOps(ops1) // was error + runOps(ops1) // error // unsound: impure operation pretended pure def delayedRunOps3(ops: List[() => Unit]): () ->{} Unit = - () => + () => // error val ops1: List[() ->{ops*} Unit] = ops - runOps(ops1) // error + runOps(ops1) diff --git a/tests/neg-custom-args/captures/depfun-reach.check b/tests/neg-custom-args/captures/depfun-reach.check index c4b74c5123c6..b6ab88b020ed 100644 --- a/tests/neg-custom-args/captures/depfun-reach.check +++ b/tests/neg-custom-args/captures/depfun-reach.check @@ -1,26 +1,29 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/depfun-reach.scala:12:27 --------------------------------- 12 | List(() => op.foreach((f,g) => { f(); g() })) // error (???) | ^^^^^^^^^^^^^^^^^^^^ - | Found: (x$1: (box () ->? Unit, box () ->? Unit)^?) ->? Unit - | Required: (x$1: (box () ->{op*} Unit, box () ->{op*} Unit)) => Unit + |Found: (x$1: (() ->? Unit, () ->? Unit)^?) ->? Unit + |Required: ((() ->{op*} Unit, () ->{op*} Unit)) => Unit + | + |where: => refers to a fresh root capability created in anonymous function of type (): Unit when checking argument to parameter op of method foreach | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/depfun-reach.scala:19:4 ---------------------------------- 19 | op // error | ^^ - | Found: (op : (xs: List[(X, box () ->{io} Unit)]) => List[box () ->{xs*} Unit]) - | Required: (xs: List[(X, box () ->{io} Unit)]) => List[() -> Unit] + | Found: (op : (xs: List[(X, () ->{io} Unit)]) => List[() ->{xs*} Unit]) + | Required: (xs: List[(X, () ->{io} Unit)]) =>² List[() -> Unit] + | + | where: => refers to a fresh root capability in the type of parameter op + | =>² refers to a fresh root capability in the result type of method foo | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/depfun-reach.scala:26:60 --------------------------------- 26 | val b: (xs: List[() ->{io} Unit]) => List[() ->{} Unit] = a // error | ^ - | Found: (a : (xs: List[box () ->{io} Unit]) => List[box () ->{xs*} Unit]) - | Required: (xs: List[box () ->{io} Unit]) => List[() -> Unit] + | Found: (a : (xs: List[() ->{io} Unit]) => List[() ->{xs*} Unit]) + | Required: (xs: List[() ->{io} Unit]) =>² List[() -> Unit] + | + | where: => refers to a fresh root capability in the type of value a + | =>² refers to a fresh root capability in the type of value b | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/depfun-reach.scala:18:17 ------------------------------------------------------ -18 | : (xs: List[(X, () ->{io} Unit)]) => List[() ->{} Unit] = // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |Separation failure: method foo's result type (xs: List[(X, box () ->{io} Unit)]) => List[() -> Unit] hides parameter op. - |The parameter needs to be annotated with @consume to allow this. diff --git a/tests/neg-custom-args/captures/depfun-reach.scala b/tests/neg-custom-args/captures/depfun-reach.scala index 074a9429bec4..f9a4589ab5c4 100644 --- a/tests/neg-custom-args/captures/depfun-reach.scala +++ b/tests/neg-custom-args/captures/depfun-reach.scala @@ -15,7 +15,7 @@ def test(io: Object^, async: Object^) = compose(op) def foo[X](op: (xs: List[(X, () ->{io} Unit)]) => List[() ->{xs*} Unit]) - : (xs: List[(X, () ->{io} Unit)]) => List[() ->{} Unit] = // error + : (xs: List[(X, () ->{io} Unit)]) => List[() ->{} Unit] = op // error def boom(op: List[(() ->{async} Unit, () ->{io} Unit)]): List[() ->{} Unit] = diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.check b/tests/neg-custom-args/captures/effect-swaps-explicit.check index 9fc99f1a10d2..04aebe522a1e 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.check +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.check @@ -2,7 +2,7 @@ 63 | Result: 64 | Future: // error, type mismatch | ^ - | Found: Result.Ok[box Future[box T^?]^{fr, contextual$1}] + | Found: Result.Ok[Future[T^?]^{fr, contextual$1}] | Required: Result[Future[T], Nothing] 65 | fr.await.ok |-------------------------------------------------------------------------------------------------------------------- @@ -17,9 +17,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:69:10 ------------------------ 69 | Future: fut ?=> // error, type mismatch | ^ - |Found: (contextual$9: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]^) ?->{fr, async} - | box Future[box T^?]^{fr, contextual$9} - |Required: (contextual$9: boundary.Label[Result[box Future[box T^?]^?, box E^?]]^) ?->{fresh} box Future[box T^?]^? + |Found: (contextual$9: boundary.Label[Result[Future[T^?]^?, E^?]^?]^?) ?->{fr, async} Future[T^?]^{fr, contextual$9} + |Required: (boundary.Label[Result[Future[T^?]^?, E^?]]^) ?=> Future[T^?]^? + | + |where: ?=> refers to a fresh root capability created in method fail4 when checking argument to parameter body of method make + | ^ refers to the universal root capability | |Note that reference contextual$9.type |cannot be included in outer capture set ? @@ -29,8 +31,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:73:35 ------------------------ 73 | Result.make[Future[T], E]: lbl ?=> // error: type mismatch | ^ - |Found: (lbl: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]^) ?->{fr, async} Future[box T^?]^{fr, lbl} - |Required: (lbl: boundary.Label[Result[Future[T], E]]^) ?->{fresh} Future[T] + |Found: (lbl: boundary.Label[Result[Future[T^?]^?, E^?]^?]^?) ?->{fr, async} Future[T^?]^{fr, lbl} + |Required: (boundary.Label[Result[Future[T], E]]^) ?=> Future[T] + | + |where: ?=> refers to a fresh root capability created in method fail5 when checking argument to parameter body of method make + | ^ refers to the universal root capability 74 | Future: fut ?=> 75 | fr.await.ok | diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.scala b/tests/neg-custom-args/captures/effect-swaps-explicit.scala index 33596772b9a0..56ab856a7782 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.scala +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.scala @@ -14,7 +14,7 @@ end boundary import boundary.{Label, break} -trait Async extends caps.SharedCapability +trait Async extends caps.Sharable object Async: def blocking[T](body: Async ?=> T): T = ??? diff --git a/tests/neg-custom-args/captures/effect-swaps.check b/tests/neg-custom-args/captures/effect-swaps.check index dc832fca0ee9..1efe26c5182f 100644 --- a/tests/neg-custom-args/captures/effect-swaps.check +++ b/tests/neg-custom-args/captures/effect-swaps.check @@ -2,7 +2,7 @@ 63 | Result: 64 | Future: // error, type mismatch | ^ - | Found: Result.Ok[box Future[box T^?]^{fr, contextual$1}] + | Found: Result.Ok[Future[T^?]^{fr, contextual$1}] | Required: Result[Future[T], Nothing] 65 | fr.await.ok |-------------------------------------------------------------------------------------------------------------------- @@ -17,9 +17,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps.scala:69:10 --------------------------------- 69 | Future: fut ?=> // error, type mismatch | ^ - |Found: (contextual$9: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]) ?->{fr, async} - | box Future[box T^?]^{fr, contextual$9} - |Required: (contextual$9: boundary.Label[Result[box Future[box T^?]^?, box E^?]]) ?->{fresh} box Future[box T^?]^? + |Found: (contextual$9: boundary.Label[Result[Future[T^?]^?, E^?]^?]^?) ?->{fr, async} Future[T^?]^{fr, contextual$9} + |Required: (boundary.Label[Result[Future[T^?]^?, E^?]]^{cap.rd}) ?=> Future[T^?]^? + | + |where: ?=> refers to a fresh root capability created in method fail4 when checking argument to parameter body of method make + | cap is the universal root capability | |Note that reference contextual$9.type |cannot be included in outer capture set ? @@ -29,8 +31,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps.scala:73:35 --------------------------------- 73 | Result.make[Future[T], E]: lbl ?=> // error: type mismatch | ^ - |Found: (lbl: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]) ?->{fr, async} Future[box T^?]^{fr, lbl} - |Required: (lbl: boundary.Label[Result[Future[T], E]]) ?->{fresh} Future[T] + |Found: (lbl: boundary.Label[Result[Future[T^?]^?, E^?]^?]^?) ?->{fr, async} Future[T^?]^{fr, lbl} + |Required: (boundary.Label[Result[Future[T], E]]^{cap.rd}) ?=> Future[T] + | + |where: ?=> refers to a fresh root capability created in method fail5 when checking argument to parameter body of method make + | cap is the universal root capability 74 | Future: fut ?=> 75 | fr.await.ok | diff --git a/tests/neg-custom-args/captures/effect-swaps.scala b/tests/neg-custom-args/captures/effect-swaps.scala index 06dca2bfa004..27d84d27e556 100644 --- a/tests/neg-custom-args/captures/effect-swaps.scala +++ b/tests/neg-custom-args/captures/effect-swaps.scala @@ -14,7 +14,7 @@ end boundary import boundary.{Label, break} -trait Async extends caps.SharedCapability +trait Async extends caps.Sharable object Async: def blocking[T](body: Async ?=> T): T = ??? diff --git a/tests/neg-custom-args/captures/erased-methods2.check b/tests/neg-custom-args/captures/erased-methods2.check index 876ff36b3696..8e163795f94b 100644 --- a/tests/neg-custom-args/captures/erased-methods2.check +++ b/tests/neg-custom-args/captures/erased-methods2.check @@ -1,11 +1,15 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/erased-methods2.scala:20:4 ------------------------------- 20 | = (x$1: CT[Ex3]^) // error | ^ - | Found: (erased x$1: CT[Ex3]^) ?->? (erased x$2: CT[Ex2]^?) ?->{x$1} Unit - | Required: (erased x$1: CT[Ex3]^) ?->{fresh} (erased x$2: CT[Ex2]^) ?->{localcap} Unit + |Found: (erased x$1: CT[Ex3]^) ?->? (erased x$2: CT[Ex2]^?) ?->{x$1} Unit + |Required: (erased x$1: CT[Ex3]^) ?=> (erased x$2: CT[Ex2]^) ?=>² Unit | - | Note that the existential capture root in (erased x$2: CT[Ex2]^) ?=> Unit - | cannot subsume the capability x$1.type since that capability is not a SharedCapability + |where: ?=> refers to a fresh root capability in the result type of method foo9a + | ?=>² refers to a root capability associated with the result type of (using erased x$1: CT[Ex3]^): (erased x$2: CT[Ex2]^) ?=>² Unit + | ^ refers to the universal root capability + | + |Note that the existential capture root in (erased x$2: CT[Ex2]^) ?=> Unit + |cannot subsume the capability x$1.type since that capability is not a `Sharable` capability 21 | ?=> (x$2: CT[Ex2]^) 22 | ?=> 23 | //given (CT[Ex3]^) = x$1 @@ -16,10 +20,15 @@ 31 | = (erased x$1: CT[Ex3]^) // error | ^ |Found: (erased x$1: CT[Ex3]^) ?->? (erased x$1: CT[Ex2]^?) ?->{x$1} (erased x$2: CT[Ex1]^?) ?->{x$1} Unit - |Required: (erased x$1: CT[Ex3]^) ?->{fresh} (erased x$1: CT[Ex2]^) ?->{localcap} (erased x$2: CT[Ex1]^) ?->{localcap} Unit + |Required: (erased x$1: CT[Ex3]^) ?=> (erased x$1: CT[Ex2]^) ?=>² (erased x$2: CT[Ex1]^) ?=>³ Unit + | + |where: ?=> refers to a fresh root capability in the result type of method foo10a + | ?=>² refers to a root capability associated with the result type of (using erased x$1: CT[Ex3]^): (erased x$1: CT[Ex2]^) ?=>² (erased x$2: CT[Ex1]^) ?=>³ Unit + | ?=>³ refers to a root capability associated with the result type of (using erased x$1: CT[Ex2]^): (erased x$2: CT[Ex1]^) ?=>³ Unit + | ^ refers to the universal root capability | - |Note that the existential capture root in (erased x$1: CT[Ex2]^) ?=> (erased x$2: CT[Ex1]^) ?->{localcap} Unit - |cannot subsume the capability x$1.type since that capability is not a SharedCapability + |Note that the existential capture root in (erased x$1: CT[Ex2]^) ?=> (erased x$2: CT[Ex1]^) ?=> Unit + |cannot subsume the capability x$1.type since that capability is not a `Sharable` capability 32 | ?=> (erased x$2: CT[Ex2]^) 33 | ?=> (erased x$3: CT[Ex1]^) 34 | ?=> Throw(new Ex3) diff --git a/tests/neg-custom-args/captures/erased-methods2.scala b/tests/neg-custom-args/captures/erased-methods2.scala index 6e111f1702da..4eda00d1b4ac 100644 --- a/tests/neg-custom-args/captures/erased-methods2.scala +++ b/tests/neg-custom-args/captures/erased-methods2.scala @@ -6,7 +6,7 @@ class Ex1 extends Exception("Ex1") class Ex2 extends Exception("Ex2") class Ex3 extends Exception("Ex3") -erased class CT[-E <: Exception] extends caps.Capability +class CT[-E <: Exception] extends caps.Capability, compiletime.Erased def Throw[Ex <: Exception](ex: Ex)(using CT[Ex]^): Nothing = ??? diff --git a/tests/neg-custom-args/captures/eta.check b/tests/neg-custom-args/captures/eta.check index d658adcad17b..9b74a86d3b49 100644 --- a/tests/neg-custom-args/captures/eta.check +++ b/tests/neg-custom-args/captures/eta.check @@ -8,7 +8,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/eta.scala:6:14 ------------------------------------------- 6 | bar( () => f ) // error | ^^^^^^^ - | Found: () ->{f} box () ->{f} Unit - | Required: () -> box () ->? Unit + | Found: () ->{f} () ->{f} Unit + | Required: () -> () ->? Unit | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/exception-definitions.check b/tests/neg-custom-args/captures/exception-definitions.check index 67f0f3b72cbb..be5ea4304bf5 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 cap captured by this self type is not included in the allowed capture set {} of pure base class class Throwable + | Err is a pure type, it makes no sense to add a capture set to it -- 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 974c5c38f74c..a867be0d8f7a 100644 --- a/tests/neg-custom-args/captures/existential-mapping.check +++ b/tests/neg-custom-args/captures/existential-mapping.check @@ -1,89 +1,135 @@ -- Error: tests/neg-custom-args/captures/existential-mapping.scala:46:10 ----------------------------------------------- 46 | val z2: (x: A^) => Array[C^] = ??? // error | ^^^^^^^^^^^^^^^^^^^^ - | Array[box C^] captures the root capability `cap` in invariant position. + | Array[C^] captures the root capability `cap` in invariant position. | This capability cannot be converted to an existential in the result type of a function. + | + | where: ^ refers to the universal root capability -- [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^) -> C^{localcap}) + | Found: (x1 : (x: C^) -> C^²) | Required: (x: C^) -> C | + | where: ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (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^ -> C^{fresh}) + | Found: C^ -> C^{x2*} | Required: C^ -> C | + | where: ^ refers to the universal root capability + | | 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^) -> C^{localcap}) - | Required: A^ -> (x: C^) -> C + | Found: (x3 : A^ -> (x: C^) -> C^²) + | Required: A^ -> (x: C^) -> C + | + | where: ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (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^ -> C^{fresh}) + | Found: A^ -> C^ -> C^{x4*} | Required: A^ -> C^ -> C | + | where: ^ refers to the universal root capability + | | 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^ -> (x: C^) -> C^{localcap}) - | Required: A^ -> (x: C^) -> C + | Found: (x5 : A^ -> (x: C^) -> C^²) + | Required: A^ -> (x: C^) -> C + | + | where: ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (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^ -> (x: C^) ->{fresh} C^{localcap}) - | Required: A^ -> (x: C^) ->{fresh} C + | Found: A^ -> (x: C^) ->{x6*} C^² + | Required: A^ -> (x: C^) => C + | + | where: => refers to a fresh root capability in the type of value _$6 + | ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: C^): 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^) ->{fresh} C^{localcap}) - | Required: (x: C^) ->{fresh} C + | Found: (y1 : (x: C^) => C^²) + | Required: (x: C^) =>² C + | + | where: => refers to a fresh root capability in the type of value y1 + | =>² refers to a fresh root capability in the type of value _$7 + | ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (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^ ->{fresh} C^{fresh}) - | Required: C^ ->{fresh} C + | Found: C^ ->{y2} C^{y2*} + | Required: C^ => C + | + | where: => refers to a fresh root capability in the type of value _$8 + | ^ refers to the universal root capability | | 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^ ->{fresh} (x: C^) ->{fresh} C^{localcap}) - | Required: A^ ->{fresh} (x: C^) ->{fresh} C + | Found: A^ ->{y3} (x: C^) ->{y3*} C^² + | Required: A^ => (x: C^) =>² C + | + | where: => refers to a fresh root capability in the type of value _$9 + | =>² refers to a fresh root capability in the type of value _$9 + | ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: C^): 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^ ->{fresh} C^ ->{fresh} C^{fresh}) - | Required: A^ ->{fresh} C^ ->{fresh} C + | Found: A^ ->{y4} C^ ->{y4*} C^{y4*} + | Required: A^ => C^ =>² C + | + | where: => refers to a fresh root capability in the type of value _$10 + | =>² refers to a fresh root capability in the type of value _$10 + | ^ refers to the universal root capability | | 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^ ->{fresh} (x: C^) -> C^{localcap}) - | Required: A^ ->{fresh} (x: C^) -> C + | Found: (y5 : A^ => (x: C^) -> C^²) + | Required: A^ =>² (x: C^) -> C + | + | where: => refers to a fresh root capability in the type of value y5 + | =>² refers to a fresh root capability in the type of value _$11 + | ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (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^ ->{fresh} (x: C^) ->{fresh} C^{localcap}) - | Required: A^ ->{fresh} (x: C^) ->{fresh} C + | Found: A^ ->{y6} (x: C^) ->{y6*} C^² + | Required: A^ => (x: C^) =>² C + | + | where: => refers to a fresh root capability in the type of value _$12 + | =>² refers to a fresh root capability in the type of value _$12 + | ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: C^): C^² | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/extending-cap-classes.check b/tests/neg-custom-args/captures/extending-cap-classes.check index 4a77a638a4d8..fb77546dd8e5 100644 --- a/tests/neg-custom-args/captures/extending-cap-classes.check +++ b/tests/neg-custom-args/captures/extending-cap-classes.check @@ -1,15 +1,19 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/extending-cap-classes.scala:7:15 ------------------------- 7 | val x2: C1 = new C2 // error | ^^^^^^ - | Found: C2 - | Required: C1 + | Found: C2^{cap.rd} + | Required: C1 + | + | where: cap is a fresh root capability created in value x2 when constructing Capability instance C2 | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/extending-cap-classes.scala:8:15 ------------------------- 8 | val x3: C1 = new C3 // error | ^^^^^^ - | Found: C3 - | Required: C1 + | Found: C3^{cap.rd} + | Required: C1 + | + | where: cap is a fresh root capability created in value x3 when constructing Capability instance C3 | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/extending-cap-classes.scala:13:15 ------------------------ diff --git a/tests/neg-custom-args/captures/filevar-expanded.check b/tests/neg-custom-args/captures/filevar-expanded.check index f48736984a36..516d03b5ed7c 100644 --- a/tests/neg-custom-args/captures/filevar-expanded.check +++ b/tests/neg-custom-args/captures/filevar-expanded.check @@ -1,14 +1,17 @@ -- Error: tests/neg-custom-args/captures/filevar-expanded.scala:34:13 -------------------------------------------------- 34 | withFile(io3): f => // error: separation failure | ^^^ - | Separation failure: argument of type (io3 : test2.IO^) - | to method withFile: [T](io2: test2.IO^)(op: (f: test2.File^{io2}) => T): T - | corresponds to capture-polymorphic formal parameter io2 of type test2.IO^ - | and hides capabilities {io3}. - | Some of these overlap with the captures of the second argument with type (f: test2.File^{io3}) ->{io3} Unit. + |Separation failure: argument of type (io3 : test2.IO^) + |to method withFile: [T](io2: test2.IO^)(op: (f: test2.File^{io2}) => T): T + |corresponds to capture-polymorphic formal parameter io2 of type test2.IO^² + |and hides capabilities {io3}. + |Some of these overlap with the captures of the second argument with type (f: test2.File^{io3}) ->{io3} Unit. | - | Hidden set of current argument : {io3} - | Hidden footprint of current argument : {io3} - | Capture set of second argument : {io3} - | Footprint set of second argument : {io3} - | The two sets overlap at : {io3} + | Hidden set of current argument : {io3} + | Hidden footprint of current argument : {io3} + | Capture set of second argument : {io3} + | Footprint set of second argument : {io3} + | The two sets overlap at : {io3} + | + |where: ^ refers to a fresh root capability in the type of parameter io3 + | ^² refers to a fresh root capability created in method test when checking argument to parameter io2 of method withFile diff --git a/tests/neg-custom-args/captures/filevar.check b/tests/neg-custom-args/captures/filevar.check index 22efd36053b4..54f67bce972a 100644 --- a/tests/neg-custom-args/captures/filevar.check +++ b/tests/neg-custom-args/captures/filevar.check @@ -1,8 +1,19 @@ --- Error: tests/neg-custom-args/captures/filevar.scala:8:6 ------------------------------------------------------------- -8 | var file: File^ = uninitialized // error, was OK under unsealed - | ^ - | Mutable variable file cannot have type File^ since - | that type captures the root capability `cap`. +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/filevar.scala:15:12 -------------------------------------- +15 | withFile: f => // error with level checking, was OK under both schemes before + | ^ + |Found: (l: scala.caps.Capability^{cap.rd}) ?->? File^? ->? Unit + |Required: (l: scala.caps.Capability^{cap.rd}) ?-> (f: File^{l}) => Unit + | + |where: => refers to a root capability associated with the result type of (using l: scala.caps.Capability^{cap.rd}): (f: File^{l}) => Unit + | cap is the universal root capability + | + |Note that reference l.type + |cannot be included in outer capture set ? of parameter f +16 | val o = Service() +17 | o.file = f +18 | o.log + | + | longer explanation available when compiling with `-explain` -- Warning: tests/neg-custom-args/captures/filevar.scala:11:55 --------------------------------------------------------- 11 |def withFile[T](op: (l: caps.Capability) ?-> (f: File^{l}) => T): T = | ^ diff --git a/tests/neg-custom-args/captures/filevar.scala b/tests/neg-custom-args/captures/filevar.scala index 5b83f1d29380..047c41c6af05 100644 --- a/tests/neg-custom-args/captures/filevar.scala +++ b/tests/neg-custom-args/captures/filevar.scala @@ -5,14 +5,14 @@ class File: def write(x: String): Unit = ??? class Service: - var file: File^ = uninitialized // error, was OK under unsealed + var file: File^ = uninitialized //OK, was error under sealed def log = file.write("log") // OK, was error under unsealed def withFile[T](op: (l: caps.Capability) ?-> (f: File^{l}) => T): T = op(using caps.cap)(new File) def test = - withFile: f => + withFile: f => // error with level checking, was OK under both schemes before val o = Service() o.file = f o.log diff --git a/tests/neg-custom-args/captures/gears-problem.check b/tests/neg-custom-args/captures/gears-problem.check index 1c3e648a6264..0fc5f8c3f163 100644 --- a/tests/neg-custom-args/captures/gears-problem.check +++ b/tests/neg-custom-args/captures/gears-problem.check @@ -8,7 +8,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/gears-problem.scala:24:34 -------------------------------- 24 | val fut2: Future[T]^{fs*} = r.get // error | ^^^^^ - | Found: Future[box T^{}]^{collector.futures*} + | Found: Future[T^{}]^{collector.futures*} | Required: Future[T]^{fs*} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/heal-tparam-cs.check b/tests/neg-custom-args/captures/heal-tparam-cs.check index 4cd30d74f4dc..cfda44733b6e 100644 --- a/tests/neg-custom-args/captures/heal-tparam-cs.check +++ b/tests/neg-custom-args/captures/heal-tparam-cs.check @@ -1,11 +1,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:10:23 ------------------------------- 10 | val test1 = localCap { c => // error | ^ - | Found: (c: Capp^) ->? box () ->{c} Unit - | Required: (c: Capp^) ->{fresh} box () ->? Unit + |Found: (c: Capp^?) ->? () ->{c} Unit + |Required: (c: Capp^) => () ->? Unit | - | Note that reference c.type - | cannot be included in outer capture set ? + |where: => refers to a fresh root capability created in value test1 when checking argument to parameter op of method localCap + | ^ refers to the universal root capability + | + |Note that reference c.type + |cannot be included in outer capture set ? 11 | () => { c.use() } 12 | } | @@ -13,11 +16,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:15:13 ------------------------------- 15 | localCap { c => // error | ^ - | Found: (x$0: Capp^) ->? () ->{x$0} Unit - | Required: (c: Capp^) -> () ->{localcap} Unit + | Found: (x$0: Capp^?) ->? () ->{x$0} Unit + | Required: (c: Capp^) -> () => Unit + | + | where: => refers to a root capability associated with the result type of (c: Capp^): () => Unit + | ^ refers to the universal root capability | | Note that the existential capture root in () => Unit - | cannot subsume the capability x$0.type since that capability is not a SharedCapability + | cannot subsume the capability x$0.type since that capability is not a `Sharable` capability 16 | (c1: Capp^) => () => { c1.use() } 17 | } | @@ -25,7 +31,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:25:13 ------------------------------- 25 | localCap { c => // error | ^ - | Found: (x$0: Capp^{io}) ->? () ->{x$0} Unit + | Found: (x$0: Capp^?) ->? () ->{x$0} Unit | Required: (c: Capp^{io}) -> () ->{net} Unit 26 | (c1: Capp^{io}) => () => { c1.use() } 27 | } @@ -37,11 +43,15 @@ | Found: (io: Capp^) ->? () ->{io} Unit | Required: (io: Capp^) -> () -> Unit | + | where: ^ refers to the universal root capability + | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:44:4 -------------------------------- 44 | io => () => io.use() // error | ^^^^^^^^^^^^^^^^^^^^ | Found: (io: Capp^) ->? () ->{io} Unit - | Required: (io: Capp^) -> () -> Unit + | Required: Capp^ -> () -> Unit + | + | where: ^ refers to the universal root capability | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/ho-ref.scala b/tests/neg-custom-args/captures/ho-ref.scala new file mode 100644 index 000000000000..f465cb4439a8 --- /dev/null +++ b/tests/neg-custom-args/captures/ho-ref.scala @@ -0,0 +1,7 @@ +class Ref + +def test(a: Object^) = + val r: Ref^{a} = ??? + def mk1(op: (z: Ref^) -> Ref^{a} ->{z} Unit) = op(r) // error + def bad(x: Ref^)(y: Ref^{a}) = ??? + mk1(bad) diff --git a/tests/neg-custom-args/captures/i15116.check b/tests/neg-custom-args/captures/i15116.check index 0a16af9f6704..462c1b6ed89b 100644 --- a/tests/neg-custom-args/captures/i15116.check +++ b/tests/neg-custom-args/captures/i15116.check @@ -1,68 +1,48 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15116.scala:3:13 ---------------------------------------- -3 | val x = Foo(m) // error - | ^^^^^^ - | Found: Foo{val m²: (Bar.this.m : String^)}^{Bar.this.m} - | Required: Foo - | - | where: m is a value in class Bar - | m² is a value in class Foo - | - | - | Note that the expected type Foo - | is the previously inferred type of value x - | which is also the type seen in separately compiled sources. - | The new inferred type Foo{val m: (Bar.this.m : String^)}^{Bar.this.m} - | must conform to this type. - | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15116.scala:5:13 ---------------------------------------- +-- Error: tests/neg-custom-args/captures/i15116.scala:5:13 ------------------------------------------------------------- 5 | val x = Foo(m) // error | ^^^^^^ - | Found: Foo{val m²: (Baz.this.m : String^)}^{Baz.this.m} - | Required: Foo - | - | where: m is a value in trait Baz - | m² is a value in class Foo - | + | value x needs an explicit type because the inferred type does not conform to + | the type that is externally visible in other compilation units. | - | Note that the expected type Foo - | is the previously inferred type of value x - | which is also the type seen in separately compiled sources. - | The new inferred type Foo{val m: (Baz.this.m : String^)}^{Baz.this.m} - | must conform to this type. + | Inferred type : test.Foo{val m: (Bar.this.m² : test.STR^)}^{Bar.this.m²} + | Externally visible type: test.Foo | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15116.scala:7:13 ---------------------------------------- + | where: ^ refers to a fresh root capability in the type of value m² + | m is a value in class Foo + | m² is a value in class Bar +-- Error: tests/neg-custom-args/captures/i15116.scala:7:13 ------------------------------------------------------------- 7 | val x = Foo(m) // error | ^^^^^^ - | Found: Foo{val m²: (Bar1.this.m : String^)}^{Bar1.this.m} - | Required: Foo + | value x needs an explicit type because the inferred type does not conform to + | the type that is externally visible in other compilation units. | - | where: m is a value in class Bar1 - | m² is a value in class Foo + | Inferred type : test.Foo{val m: (Baz.this.m² : test.STR^)}^{Baz.this.m²} + | Externally visible type: test.Foo | - | - | Note that the expected type Foo - | is the previously inferred type of value x - | which is also the type seen in separately compiled sources. - | The new inferred type Foo{val m: (Bar1.this.m : String^)}^{Bar1.this.m} - | must conform to this type. - | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15116.scala:9:13 ---------------------------------------- + | where: ^ refers to a fresh root capability in the type of value m² + | m is a value in class Foo + | m² is a value in trait Baz +-- Error: tests/neg-custom-args/captures/i15116.scala:9:13 ------------------------------------------------------------- 9 | val x = Foo(m) // error | ^^^^^^ - | Found: Foo{val m²: (Baz2.this.m : String^)}^{Baz2.this.m} - | Required: Foo - | - | where: m is a value in trait Baz2 - | m² is a value in class Foo - | - | - | Note that the expected type Foo - | is the previously inferred type of value x - | which is also the type seen in separately compiled sources. - | The new inferred type Foo{val m: (Baz2.this.m : String^)}^{Baz2.this.m} - | must conform to this type. - | - | longer explanation available when compiling with `-explain` + | value x needs an explicit type because the inferred type does not conform to + | the type that is externally visible in other compilation units. + | + | Inferred type : test.Foo{val m: (Bar1.this.m² : test.STR^)}^{Bar1.this.m²} + | Externally visible type: test.Foo + | + | where: ^ refers to a fresh root capability in the type of value m² + | m is a value in class Foo + | m² is a value in class Bar1 +-- Error: tests/neg-custom-args/captures/i15116.scala:11:13 ------------------------------------------------------------ +11 | val x = Foo(m) // error + | ^^^^^^ + | value x needs an explicit type because the inferred type does not conform to + | the type that is externally visible in other compilation units. + | + | Inferred type : test.Foo{val m: (Baz2.this.m² : test.STR^)}^{Baz2.this.m²} + | Externally visible type: test.Foo + | + | where: ^ refers to a fresh root capability in the type of value m² + | m is a value in class Foo + | m² is a value in trait Baz2 diff --git a/tests/neg-custom-args/captures/i15116.scala b/tests/neg-custom-args/captures/i15116.scala index c4dc6c88d56c..d7265646782b 100644 --- a/tests/neg-custom-args/captures/i15116.scala +++ b/tests/neg-custom-args/captures/i15116.scala @@ -1,9 +1,11 @@ -class Foo(m: String^) -class Bar(val m: String^): +package test +class STR +class Foo(m: STR^) +class Bar(val m: STR^): val x = Foo(m) // error -trait Baz(val m: String^): +trait Baz(val m: STR^): val x = Foo(m) // error -class Bar1(m: String^): +class Bar1(m: STR^): val x = Foo(m) // error -trait Baz2(m: String^): +trait Baz2(m: STR^): val x = Foo(m) // error diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index 98791d729c16..8c71937090ed 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -1,21 +1,33 @@ -- Error: tests/neg-custom-args/captures/i15772.scala:22:46 ------------------------------------------------------------ 22 | val boxed1 : ((C^) => Unit) -> Unit = box1(c) // error | ^^^^^^^ - |C^ => Unit cannot be box-converted to box C{val arg: C^}^{c} ->{cap, c} Unit - |since the additional capture set {c} resulting from box conversion is not allowed in box C{val arg: C^}^{c} => Unit + |C^ => Unit cannot be box-converted to C{val arg: C^²}^{c} ->{cap, c} Unit + |since the additional capture set {c} resulting from box conversion is not allowed in C{val arg: C^²}^{c} => Unit + | + |where: => refers to the universal root capability + | ^ refers to the universal root capability + | ^² refers to a fresh root capability in the type of value arg + | cap is the universal root capability -- Error: tests/neg-custom-args/captures/i15772.scala:29:35 ------------------------------------------------------------ 29 | val boxed2 : Observe[C^] = box2(c) // error | ^^^^^^^ - |C^ => Unit cannot be box-converted to box C{val arg: C^}^{c} ->{cap, c} Unit - |since the additional capture set {c} resulting from box conversion is not allowed in box C{val arg: C^}^{c} => Unit --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:35:34 --------------------------------------- + |C^ => Unit cannot be box-converted to C{val arg: C^²}^{c} ->{cap, c} Unit + |since the additional capture set {c} resulting from box conversion is not allowed in C{val arg: C^²}^{c} => Unit + | + |where: => refers to the universal root capability + | ^ refers to the universal root capability + | ^² refers to a fresh root capability in the type of value arg + | cap is the universal root capability +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:35:33 --------------------------------------- 35 | val boxed2 : Observe[C]^ = box2(c) // error - | ^ - | Found: box C^ - | Required: box C{val arg: C^?}^? + | ^^^^^^^ + |Found: (C{val arg: C^}^² => Unit) -> Unit + |Required: (C => Unit) =>² Unit | - | Note that the universal capability `cap` - | cannot be included in capture set ? + |where: => refers to the universal root capability + | =>² refers to a fresh root capability in the type of value boxed2 + | ^ refers to a fresh root capability in the type of value arg + | ^² refers to a fresh root capability created in value boxed2 when instantiating method c's type -> C^{cap} | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:46:2 ---------------------------------------- diff --git a/tests/neg-custom-args/captures/i15923.check b/tests/neg-custom-args/captures/i15923.check new file mode 100644 index 000000000000..9eb930e0f405 --- /dev/null +++ b/tests/neg-custom-args/captures/i15923.check @@ -0,0 +1,21 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15923.scala:12:21 --------------------------------------- +12 | val leak = withCap(cap => mkId(cap)) // error + | ^^^^^^^^^^^^^^^^ + |Found: (lcap: scala.caps.Capability^{cap.rd}) ?->? Cap^? ->? Id[Cap^?]^? + |Required: (lcap: scala.caps.Capability^{cap.rd}) ?-> Cap^{lcap} => Id[Cap^?]^? + | + |where: => refers to a root capability associated with the result type of (using lcap: scala.caps.Capability^{cap.rd}): Cap^{lcap} => Id[Cap^?]^? + | cap is the universal root capability + | + |Note that reference .rd + |cannot be included in outer capture set ? + | + | longer explanation available when compiling with `-explain` +-- Warning: tests/neg-custom-args/captures/i15923.scala:21:56 ---------------------------------------------------------- +21 | def withCap[X](op: (lcap: caps.Capability) ?-> Cap^{lcap} => X): X = { + | ^^^^ + | redundant capture: test2.Cap already accounts for lcap.type +-- Warning: tests/neg-custom-args/captures/i15923.scala:6:54 ----------------------------------------------------------- +6 | def withCap[X](op: (lcap: caps.Capability) ?-> Cap^{lcap} => X): X = { + | ^^^^ + | redundant capture: Cap already accounts for lcap.type diff --git a/tests/neg-custom-args/captures/i15923.scala b/tests/neg-custom-args/captures/i15923.scala index f7de2d8b4d2a..41bfd49e0250 100644 --- a/tests/neg-custom-args/captures/i15923.scala +++ b/tests/neg-custom-args/captures/i15923.scala @@ -1,6 +1,6 @@ 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 bar() = { def withCap[X](op: (lcap: caps.Capability) ?-> Cap^{lcap} => X): X = { @@ -9,6 +9,21 @@ def bar() = { result } - val leak = withCap(cap => mkId(cap)) // error - leak { cap => cap.use() } -} \ No newline at end of file + val leak = withCap(cap => mkId(cap)) // error +} + +package test2: + 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) + + def bar() = { + def withCap[X](op: (lcap: caps.Capability) ?-> Cap^{lcap} => X): X = { + val cap: Cap = new Cap { def use() = { println("cap is used"); 0 } } + val result = op(using caps.cap)(cap) + result + } + + val leak = withCap(cap => mkId(cap)) // no error here since type aliases don't box + leak { cap => cap.use() } + } \ No newline at end of file diff --git a/tests/neg-custom-args/captures/i15923a.check b/tests/neg-custom-args/captures/i15923a.check new file mode 100644 index 000000000000..bc506822275e --- /dev/null +++ b/tests/neg-custom-args/captures/i15923a.check @@ -0,0 +1,14 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15923a.scala:7:21 --------------------------------------- +7 | val leak = withCap(lcap => () => mkId(lcap)) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^ + |Found: (lcap: Cap^?) ->? () ->? Id[Cap^?]^? + |Required: (lcap: Cap^) => () =>² Id[Cap^?]^? + | + |where: => refers to a fresh root capability created in value leak when checking argument to parameter op of method withCap + | =>² refers to a root capability associated with the result type of (lcap: Cap^): () =>² Id[Cap^?]^? + | ^ refers to the universal root capability + | + |Note that reference + |cannot be included in outer capture set ? + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i15923a.scala b/tests/neg-custom-args/captures/i15923a.scala new file mode 100644 index 000000000000..e084fddebd86 --- /dev/null +++ b/tests/neg-custom-args/captures/i15923a.scala @@ -0,0 +1,7 @@ +import language.experimental.captureChecking +trait Cap +case class Id[X](x: X) +def mkId[X](x: X): Id[X] = Id(x) +def withCap[X](op: (lcap: Cap^) => () => X): X = ??? +def bar() = + val leak = withCap(lcap => () => mkId(lcap)) // error diff --git a/tests/neg-custom-args/captures/i15923b.check b/tests/neg-custom-args/captures/i15923b.check new file mode 100644 index 000000000000..c7825851a7ef --- /dev/null +++ b/tests/neg-custom-args/captures/i15923b.check @@ -0,0 +1,13 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15923b.scala:8:21 --------------------------------------- +8 | val leak = withCap(f) // error + | ^ + |Found: (x$0: Cap^) -> Id[Cap^{x$0}] + |Required: (lcap: Cap^) => Id[Cap^?]^? + | + |where: => refers to a fresh root capability created in value leak when checking argument to parameter op of method withCap + | ^ refers to the universal root capability + | + |Note that reference lcap.type + |cannot be included in outer capture set ? + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i15923b.scala b/tests/neg-custom-args/captures/i15923b.scala new file mode 100644 index 000000000000..cd432359ed43 --- /dev/null +++ b/tests/neg-custom-args/captures/i15923b.scala @@ -0,0 +1,9 @@ +import language.experimental.captureChecking +trait Cap +case class Id[X](x: X) +def mkId[X](x: X): Id[X] = Id(x) +def withCap[X](op: (lcap: Cap^) => X): X = ??? +def bar() = + val f = (lcap: Cap^) => mkId(lcap) + val leak = withCap(f) // error + val leaked = leak.x \ No newline at end of file diff --git a/tests/neg-custom-args/captures/i16114.check b/tests/neg-custom-args/captures/i16114.check index e7ae191dbf14..5e755d6770f1 100644 --- a/tests/neg-custom-args/captures/i16114.check +++ b/tests/neg-custom-args/captures/i16114.check @@ -1,25 +1,35 @@ -- Error: tests/neg-custom-args/captures/i16114.scala:18:13 ------------------------------------------------------------ 18 | expect[Cap^] { // error | ^^^^ - | Type variable T of method expect cannot be instantiated to box Cap^ since + | Type variable T of method expect cannot be instantiated to Cap^ since | that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- Error: tests/neg-custom-args/captures/i16114.scala:24:13 ------------------------------------------------------------ 24 | expect[Cap^] { // error | ^^^^ - | Type variable T of method expect cannot be instantiated to box Cap^ since + | Type variable T of method expect cannot be instantiated to Cap^ since | that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- Error: tests/neg-custom-args/captures/i16114.scala:30:13 ------------------------------------------------------------ 30 | expect[Cap^] { // error | ^^^^ - | Type variable T of method expect cannot be instantiated to box Cap^ since + | Type variable T of method expect cannot be instantiated to Cap^ since | that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- Error: tests/neg-custom-args/captures/i16114.scala:36:13 ------------------------------------------------------------ 36 | expect[Cap^](io) // error | ^^^^ - | Type variable T of method expect cannot be instantiated to box Cap^ since + | Type variable T of method expect cannot be instantiated to Cap^ since | that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- Error: tests/neg-custom-args/captures/i16114.scala:39:13 ------------------------------------------------------------ 39 | expect[Cap^] { // error | ^^^^ - | Type variable T of method expect cannot be instantiated to box Cap^ since + | Type variable T of method expect cannot be instantiated to Cap^ since | that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/i16226.check b/tests/neg-custom-args/captures/i16226.check index 5a3c3b690f6d..1d79d29165dc 100644 --- a/tests/neg-custom-args/captures/i16226.check +++ b/tests/neg-custom-args/captures/i16226.check @@ -1,19 +1,27 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i16226.scala:13:4 ---------------------------------------- 13 | (ref1, f1) => map[A, B](ref1, f1) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (ref1: LazyRef[box A^?]{val elem: () ->{cap, fresh} A^?}^{io}, f1: (x$0: A^?) => B^?) ->? - | LazyRef[box B^?]{val elem: () ->{localcap} B^?}^{f1, ref1} - | Required: (ref1: LazyRef[A]^{io}, f1: A => B) ->{fresh} LazyRef[B]^{fresh} + |Found: (ref1: LazyRef[A^?]{val elem: () ->? A^?}^?, f1: A^? ->? B^?) ->? LazyRef[B^?]{val elem: () => B^?}^{f1, ref1} + |Required: (LazyRef[A]^{io}, A =>² B) =>³ LazyRef[B]^ + | + |where: => refers to a root capability associated with the result type of (ref1: LazyRef[A^?]{val elem: () ->? A^?}^?, f1: A^? ->? B^?): LazyRef[B^?]{val elem: () => B^?}^{f1, ref1} + | =>² refers to the universal root capability + | =>³ refers to a fresh root capability in the result type of method mapc + | ^ refers to a fresh root capability in the result type of method mapc | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i16226.scala:15:4 ---------------------------------------- 15 | (ref1, f1) => map[A, B](ref1, f1) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (ref1: LazyRef[box A^?]{val elem: () ->{cap, fresh} A^?}^{io}, f1: (x$0: A^?) => B^?) ->? - | LazyRef[box B^?]{val elem: () ->{localcap} B^?}^{f1, ref1} - | Required: (ref: LazyRef[A]^{io}, f: A => B) ->{fresh} LazyRef[B]^{localcap} + |Found: (ref1: LazyRef[A^?]{val elem: () ->? A^?}^?, f1: A^? ->? B^?) ->? LazyRef[B^?]{val elem: () => B^?}^{f1, ref1} + |Required: (ref: LazyRef[A]^{io}, f: A =>² B) =>³ LazyRef[B]^ + | + |where: => refers to a root capability associated with the result type of (ref1: LazyRef[A^?]{val elem: () ->? A^?}^?, f1: A^? ->? B^?): LazyRef[B^?]{val elem: () => B^?}^{f1, ref1} + | =>² refers to the universal root capability + | =>³ refers to a fresh root capability in the result type of method mapd + | ^ refers to a root capability associated with the result type of (ref: LazyRef[A]^{io}, f: A =>² B): LazyRef[B]^ | - | Note that the existential capture root in LazyRef[B]^ - | cannot subsume the capability f1.type since that capability is not a SharedCapability + |Note that the existential capture root in LazyRef[B]^ + |cannot subsume the capability f1.type since that capability is not a `Sharable` capability | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i16725.scala b/tests/neg-custom-args/captures/i16725.scala index 96cf44e72f3c..3422ae537c92 100644 --- a/tests/neg-custom-args/captures/i16725.scala +++ b/tests/neg-custom-args/captures/i16725.scala @@ -6,9 +6,9 @@ def usingIO[T](op: IO => T): T = ??? 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.value: io => // error + () => // error + wrapper.value: io => io.brewCoffee() def main(): Unit = - val escaped = usingIO(io => useWrappedIO(mk(io))) // error + val escaped = usingIO(io => useWrappedIO(mk(io))) escaped() // boom diff --git a/tests/neg-custom-args/captures/i19330.check b/tests/neg-custom-args/captures/i19330.check index 06a074c40a10..5a7dc525af4d 100644 --- a/tests/neg-custom-args/captures/i19330.check +++ b/tests/neg-custom-args/captures/i19330.check @@ -16,6 +16,9 @@ | Found: bar.T | Required: () => Logger^ | + | where: => refers to a fresh root capability in the type of value bad + | ^ refers to a fresh root capability in the type of value bad + | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/i19330.scala:16:14 ------------------------------------------------------------ 16 | val t: () => Logger^ = () => l // error diff --git a/tests/neg-custom-args/captures/i19470.check b/tests/neg-custom-args/captures/i19470.check index 68eba72c92bd..745018c54c88 100644 --- a/tests/neg-custom-args/captures/i19470.check +++ b/tests/neg-custom-args/captures/i19470.check @@ -1,7 +1,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i19470.scala:9:12 ---------------------------------------- 9 | List(foo(f())) // error | ^^^^^^^^ - | Found: Inv[box IO^{f?}] - | Required: box Inv[box IO^?]^? + | Found: Inv[IO^{f?}] + | Required: Inv[IO^?]^? | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21313.check b/tests/neg-custom-args/captures/i21313.check index f087c1f86d63..2a90a1f7aa47 100644 --- a/tests/neg-custom-args/captures/i21313.check +++ b/tests/neg-custom-args/captures/i21313.check @@ -5,7 +5,9 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21313.scala:15:12 --------------------------------------- 15 | ac1.await(src2) // error | ^^^^ - | Found: (src2 : Source[Int, scala.caps.CapSet^{ac2}]^{}) - | Required: Source[Int, scala.caps.CapSet^{ac1}]^ + |Found: (src2 : Source[Int, scala.caps.CapSet^{ac2}]^{}) + |Required: Source[Int, scala.caps.CapSet^{ac1}]^ + | + |where: ^ refers to a fresh root capability created in method test when checking argument to parameter src of method await | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21313.scala b/tests/neg-custom-args/captures/i21313.scala index 01bedb10aefd..9ee31aa7b196 100644 --- a/tests/neg-custom-args/captures/i21313.scala +++ b/tests/neg-custom-args/captures/i21313.scala @@ -1,15 +1,15 @@ -import caps.CapSet +import language.experimental.captureChecking trait Async: - def await[T, Cap^](using caps.Contains[Cap, this.type])(src: Source[T, Cap]^): T + def await[T, Cap^](using caps.Contains[Cap, this.type])(src: Source[T, {Cap}]^): T def foo(x: Async) = x.await(???) // error trait Source[+T, Cap^]: - final def await(using ac: Async^{Cap^}) = ac.await[T, Cap](this) // Contains[Cap, ac] is assured because {ac} <: Cap. + final def await(using ac: Async^{Cap}) = ac.await[T, {Cap}](this) // Contains[Cap, ac] is assured because {ac} <: Cap. def test(using ac1: Async^, ac2: Async^, x: String) = - val src1 = new Source[Int, CapSet^{ac1}] {} + val src1 = new Source[Int, {ac1}] {} ac1.await(src1) // ok - val src2 = new Source[Int, CapSet^{ac2}] {} + val src2 = new Source[Int, {ac2}] {} ac1.await(src2) // error diff --git a/tests/neg-custom-args/captures/i21347.scala b/tests/neg-custom-args/captures/i21347.scala index 54fe859caedd..2e37511f053c 100644 --- a/tests/neg-custom-args/captures/i21347.scala +++ b/tests/neg-custom-args/captures/i21347.scala @@ -1,6 +1,6 @@ import language.experimental.captureChecking -def runOps[C^](ops: List[() ->{C^} Unit]): Unit = +def runOps[C^](ops: List[() ->{C} Unit]): Unit = ops.foreach: op => // error op() diff --git a/tests/neg-custom-args/captures/i21401.check b/tests/neg-custom-args/captures/i21401.check index 8eade64e372f..91444d0957e9 100644 --- a/tests/neg-custom-args/captures/i21401.check +++ b/tests/neg-custom-args/captures/i21401.check @@ -1,32 +1,56 @@ -- Error: tests/neg-custom-args/captures/i21401.scala:13:14 ------------------------------------------------------------ 13 | op1(Boxed[IO^](x)) // error | ^^^ - | Type variable T of object Boxed cannot be instantiated to box IO^ since + | Type variable T of object Boxed cannot be instantiated to IO^ since | that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- Error: tests/neg-custom-args/captures/i21401.scala:15:18 ------------------------------------------------------------ 15 | val a = usingIO[IO^](x => x) // error // error | ^^^ - | Type variable R of method usingIO cannot be instantiated to box IO^ since + | Type variable R of method usingIO cannot be instantiated to IO^ since | that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21401.scala:15:23 --------------------------------------- 15 | val a = usingIO[IO^](x => x) // error // error | ^^^^^^ - | Found: (x: IO^) ->? box IO^{x} - | Required: (x: IO^) ->{fresh} box IO^{fresh} + |Found: (x: IO^?) ->? IO^{x} + |Required: IO^ => IO^² + | + |where: => refers to a fresh root capability created in value a when checking argument to parameter op of method usingIO + | ^ refers to the universal root capability + | ^² refers to a fresh root capability created in value a when checking argument to parameter op of method usingIO | | longer explanation available when compiling with `-explain` -- 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 [R, X <: Boxed[box IO^] -> R] => (op: X) -> R 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[IO^] -> R] => (op: X) -> R since + | the part IO^ of that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- 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 +17 | val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error // error | ^^^^^^^^^^ - | Type variable R of value leaked cannot be instantiated to Boxed[box IO^] since - | the part box IO^ of that type captures the root capability `cap`. + | Type variable R of value leaked cannot be instantiated to Boxed[IO^] since + | the part IO^ of that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- 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 +17 | val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error // error | ^^^^^^^^^^^^^^^^^^^^^^^^ - | Type variable X of value leaked cannot be instantiated to Boxed[box IO^] -> Boxed[box IO^] since - | the part box IO^ of that type captures the root capability `cap`. + | Type variable X of value leaked cannot be instantiated to Boxed[IO^] -> Boxed[IO^] since + | the part IO^ of that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21401.scala:17:67 --------------------------------------- +17 | val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error // error + | ^^^^^^ + | Found: (x: Boxed[IO^]^?) ->? Boxed[IO^{x*}]^? + | Required: Boxed[IO^] -> Boxed[IO^²] + | + | where: ^ refers to the universal root capability + | ^² refers to a fresh root capability created in value x² + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21401.scala b/tests/neg-custom-args/captures/i21401.scala index bd10a97952a2..af82fb88002d 100644 --- a/tests/neg-custom-args/captures/i21401.scala +++ b/tests/neg-custom-args/captures/i21401.scala @@ -14,6 +14,6 @@ def mkRes(x: IO^): Res = def test2() = val a = usingIO[IO^](x => x) // error // error val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error - val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error + val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error // error val y: IO^{x*} = x.unbox // was error y.println("boom") diff --git a/tests/neg-custom-args/captures/i21442.check b/tests/neg-custom-args/captures/i21442.check index 66bc0727f412..330c88d393dd 100644 --- a/tests/neg-custom-args/captures/i21442.check +++ b/tests/neg-custom-args/captures/i21442.check @@ -3,8 +3,13 @@ | ^^^^^^^ | Local reach capability x.unbox* leaks into capture scope of method foo. | To allow this, the parameter x should be declared with a @use annotation +-- Error: tests/neg-custom-args/captures/i21442.scala:18:14 ------------------------------------------------------------ +18 | val io = x1.unbox // error + | ^^^^^^^^ + | Local reach capability x* leaks into capture scope of method bar. + | To allow this, the parameter x should be declared with a @use annotation -- Error: tests/neg-custom-args/captures/i21442.scala:17:10 ------------------------------------------------------------ 17 | val x1: Boxed[IO^] = x // error | ^^^^^^^^^^ - | Separation failure: value x1's type Boxed[box IO^] hides parameter x. + | Separation failure: value x1's type Boxed[IO^] hides parameter x. | The parameter needs to be annotated with @consume to allow this. diff --git a/tests/neg-custom-args/captures/i21442.scala b/tests/neg-custom-args/captures/i21442.scala index 16d32c5218cb..3541bd89789a 100644 --- a/tests/neg-custom-args/captures/i21442.scala +++ b/tests/neg-custom-args/captures/i21442.scala @@ -15,5 +15,5 @@ def foo(x: Boxed[IO^]): Unit = // But, no type error reported. def bar(x: Boxed[IO^]): Unit = val x1: Boxed[IO^] = x // error - val io = x1.unbox // was error + val io = x1.unbox // error io.use() diff --git a/tests/neg-custom-args/captures/i21614.check b/tests/neg-custom-args/captures/i21614.check index 6f1a2a4e23fa..9535ce2e5b96 100644 --- a/tests/neg-custom-args/captures/i21614.check +++ b/tests/neg-custom-args/captures/i21614.check @@ -1,17 +1,22 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:12:33 --------------------------------------- 12 | files.map((f: F) => new Logger(f)) // error, Q: can we make this pass (see #19076)? | ^ - | Found: (f : F) - | Required: File^ + |Found: (f : F) + |Required: File^ + | + |where: ^ refers to a fresh root capability created in anonymous function of type (f²: F): Logger when checking argument to parameter f of constructor Logger | | longer explanation available when compiling with `-explain` -- [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*} box Logger{val f: File^{_$1}}^{localcap.rd, _$1} - | Required: (_$1: box File^{files*}) ->{fresh} box Logger{val f: File^?}^? + |Found: (_$1: File^?) ->{files*} Logger{val f: File^{_$1}}^{cap.rd, _$1} + |Required: File^{files*} => Logger{val f: File^?}^? + | + |where: => refers to a fresh root capability created in method mkLoggers2 when checking argument to parameter f of method map + | cap is a root capability associated with the result type of (_$1: File^?): Logger{val f: File^{_$1}}^{cap.rd, _$1} | - | Note that reference .rd - | cannot be included in outer capture set ? + |Note that reference .rd + |cannot be included in outer capture set ? | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21868.scala b/tests/neg-custom-args/captures/i21868.scala deleted file mode 100644 index 876b68ac90a4..000000000000 --- a/tests/neg-custom-args/captures/i21868.scala +++ /dev/null @@ -1,13 +0,0 @@ -import caps.* - -trait AbstractWrong: - type C <: CapSet - def f(): Unit^{C^} // error - -trait Abstract1: - type C >: CapSet <: CapSet^ - def f(): Unit^{C^} - -// class Abstract2: -// type C^ -// def f(): Unit^{C^} \ No newline at end of file diff --git a/tests/neg-custom-args/captures/i21868b.scala b/tests/neg-custom-args/captures/i21868b.scala index 70f4e9c9d59c..3c15f5c1cdf7 100644 --- a/tests/neg-custom-args/captures/i21868b.scala +++ b/tests/neg-custom-args/captures/i21868b.scala @@ -1,3 +1,4 @@ +import language.experimental.captureChecking import language.experimental.modularity import caps.* @@ -6,44 +7,44 @@ class IO class File trait Abstract: - type C >: CapSet <: CapSet^ - def f(file: File^{C^}): Unit + type C^ + def f(file: File^{C}): Unit class Concrete1 extends Abstract: - type C = CapSet + type C = {} def f(file: File) = () class Concrete2(io: IO^) extends Abstract: - type C = CapSet^{io} + type C = {io} def f(file: File^{io}) = () class Concrete3(io: IO^) extends Abstract: - type C = CapSet^{io} + type C = {io} def f(file: File) = () // error trait Abstract2(tracked val io: IO^): - type C >: CapSet <: CapSet^{io} - def f(file: File^{C^}): Unit + type C^ <: {io} + def f(file: File^{C}): Unit class Concrete4(io: IO^) extends Abstract2(io): - type C = CapSet + type C = {} def f(file: File) = () class Concrete5(io1: IO^, io2: IO^) extends Abstract2(io1): - type C = CapSet^{io2} // error + type C = {io2} // error def f(file: File^{io2}) = () trait Abstract3[X^]: - type C >: CapSet <: X - def f(file: File^{C^}): Unit + type C^ <: X + def f(file: File^{C}): Unit -class Concrete6(io: IO^) extends Abstract3[CapSet^{io}]: - type C = CapSet +class Concrete6(io: IO^) extends Abstract3[{io}]: + type C = {} def f(file: File) = () -class Concrete7(io1: IO^, io2: IO^) extends Abstract3[CapSet^{io1}]: - type C = CapSet^{io2} // error +class Concrete7(io1: IO^, io2: IO^) extends Abstract3[{io1}]: + type C = {io2} // error def f(file: File^{io2}) = () -class Concrete8(io1: IO^, io2: IO^) extends Abstract3[CapSet^{io1}]: +class Concrete8(io1: IO^, io2: IO^) extends Abstract3[{io1}]: def f(file: File^{io2}) = () // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/i21920.check b/tests/neg-custom-args/captures/i21920.check index 70efe6990d4c..9b3de5548f5b 100644 --- a/tests/neg-custom-args/captures/i21920.check +++ b/tests/neg-custom-args/captures/i21920.check @@ -1,10 +1,13 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21920.scala:34:35 --------------------------------------- 34 | val cell: Cell[File] = File.open(f => Cell(() => Seq(f))) // error | ^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (f: File^?) ->? box Cell[box File^?]{val head: () ->? IterableOnce[box File^?]^?}^? - | Required: (f: File^) ->{fresh} box Cell[box File^?]{val head: () ->? IterableOnce[box File^?]^?}^? + |Found: (f: File^?) ->? Cell[File^?]{val head: () ->? IterableOnce[File^?]^?}^? + |Required: File^ => Cell[File^?]{val head: () ->? IterableOnce[File^?]^?}^? | - | Note that the universal capability `cap` - | cannot be included in capture set ? + |where: => refers to a fresh root capability created in value cell when checking argument to parameter f of method open + | ^ refers to the universal root capability + | + |Note that reference + |cannot be included in outer capture set ? | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i22005.scala b/tests/neg-custom-args/captures/i22005.scala index 689246d6f835..fc7d4183f8c3 100644 --- a/tests/neg-custom-args/captures/i22005.scala +++ b/tests/neg-custom-args/captures/i22005.scala @@ -1,9 +1,10 @@ +import language.experimental.captureChecking import caps.* class IO class File(io: IO^) class Handler[C^]: - def f(file: File^): File^{C^} = file // error - def g(@consume file: File^{C^}): File^ = file // ok + def f(file: File^): File^{C} = file // error + def g(@consume file: File^{C}): File^ = file // ok diff --git a/tests/neg-custom-args/captures/i23207.check b/tests/neg-custom-args/captures/i23207.check new file mode 100644 index 000000000000..78a5406ce03d --- /dev/null +++ b/tests/neg-custom-args/captures/i23207.check @@ -0,0 +1,25 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i23207.scala:15:17 --------------------------------------- +15 | val a: A = box.x // error + | ^^^^^ + | Found: (box.x : (b : B^{io})^{b}) + | Required: A + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i23207.scala:18:13 --------------------------------------- +18 | val _: A = c // error + | ^ + | Found: (c : B^{b}) + | Required: A + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i23207.scala:23:2 ---------------------------------------- +23 | class B extends A: // error, now we see the error for the whole block since there are no nested errors + | ^ + | Found: A^{io} + | Required: A +24 | val hide: AnyRef^{io} = io +25 | val b = new B +26 | val c = b.getBox.x +27 | c + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i23207.scala b/tests/neg-custom-args/captures/i23207.scala new file mode 100644 index 000000000000..36402944d393 --- /dev/null +++ b/tests/neg-custom-args/captures/i23207.scala @@ -0,0 +1,28 @@ +import language.experimental.captureChecking +import caps.* + +case class Box[T](x: T) + +class A: + def getBox: Box[this.type] = Box(this) + +def leak(io: AnyRef^): A = + class B extends A: + val hide: AnyRef^{io} = io + + val b = new B + val box = b.getBox + val a: A = box.x // error + val c = b.getBox.x + val _: B^{b} = c // ok + val _: A = c // error + c // no error here since we don't propagate expected type into the last expression of a block + // and the whole block's span overlaps with previous errors + +def leak2(io: AnyRef^): A = + class B extends A: // error, now we see the error for the whole block since there are no nested errors + val hide: AnyRef^{io} = io + + val b = new B + val c = b.getBox.x + c diff --git a/tests/neg-custom-args/captures/i23223.scala b/tests/neg-custom-args/captures/i23223.scala new file mode 100644 index 000000000000..e606875b840a --- /dev/null +++ b/tests/neg-custom-args/captures/i23223.scala @@ -0,0 +1,12 @@ +import language.experimental.captureChecking +import caps.* + +class A: + def a: A = this + +class B extends A, Capability // error + +def leak(b: B): A = b.a + +class C extends Capability: + def c: C^{} = this // error diff --git a/tests/neg-custom-args/captures/i23234.scala b/tests/neg-custom-args/captures/i23234.scala new file mode 100644 index 000000000000..61d7123c8b78 --- /dev/null +++ b/tests/neg-custom-args/captures/i23234.scala @@ -0,0 +1,5 @@ +abstract class MyFun: + def apply(x: Int): Int + +object Test: + val myFun: MyFun = (x: Int) ?=> x + 10 // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/i23303.check b/tests/neg-custom-args/captures/i23303.check new file mode 100644 index 000000000000..437cd471a42c --- /dev/null +++ b/tests/neg-custom-args/captures/i23303.check @@ -0,0 +1,10 @@ +-- Error: tests/neg-custom-args/captures/i23303.scala:6:36 ------------------------------------------------------------- +6 | def execute: Unit = ops.foreach(f => f()) // error + | ^^^^^^^^ + | Local reach capability Runner.this.ops* leaks into capture scope of class Runner. + | To allow this, the value ops should be declared with a @use annotation +-- Error: tests/neg-custom-args/captures/i23303.scala:9:22 ------------------------------------------------------------- +9 | () => ops.foreach(f => f()) // error + | ^^^^^^^^ + | Local reach capability ops* leaks into capture scope of method Runner2. + | To allow this, the parameter ops should be declared with a @use annotation diff --git a/tests/neg-custom-args/captures/i23303.scala b/tests/neg-custom-args/captures/i23303.scala new file mode 100644 index 000000000000..81bb45af315c --- /dev/null +++ b/tests/neg-custom-args/captures/i23303.scala @@ -0,0 +1,17 @@ +import language.experimental.captureChecking +import caps.use + +class Test: + class Runner(ops: List[() => Unit]): + def execute: Unit = ops.foreach(f => f()) // error + + def Runner2(ops: List[() => Unit]) = + () => ops.foreach(f => f()) // error + + +class Test2: + class Runner(@use ops: List[() => Unit]): + def execute: Unit = ops.foreach(f => f()) //ok + + private def Runner2(@use ops: List[() => Unit]) = + () => ops.foreach(f => f()) // ok diff --git a/tests/neg-custom-args/captures/i23389-1.scala b/tests/neg-custom-args/captures/i23389-1.scala new file mode 100644 index 000000000000..568c9da52aa7 --- /dev/null +++ b/tests/neg-custom-args/captures/i23389-1.scala @@ -0,0 +1,8 @@ +import language.experimental.captureChecking +import caps.* + +trait Collection[+T] extends Mutable: + update def add(elem: T): Unit // error + update def remove(elem: T): Unit // error + def get(index: Int): Option[T] + diff --git a/tests/neg-custom-args/captures/i23389-2.scala b/tests/neg-custom-args/captures/i23389-2.scala new file mode 100644 index 000000000000..a7612891d3a9 --- /dev/null +++ b/tests/neg-custom-args/captures/i23389-2.scala @@ -0,0 +1,8 @@ +import language.experimental.captureChecking +import caps.* + +trait Collection[T] extends Mutable // <- note the forgotten : + update def add(elem: T): Unit // error // error + update def remove(elem: T): Unit // error // error + def get(index: Int): Option[T] // error // error + diff --git a/tests/neg-custom-args/captures/i23389.scala b/tests/neg-custom-args/captures/i23389.scala new file mode 100644 index 000000000000..3962949e2088 --- /dev/null +++ b/tests/neg-custom-args/captures/i23389.scala @@ -0,0 +1,36 @@ +import language.experimental.captureChecking +import caps.* + +package test1: + + trait Collection[T] extends Mutable: + update def add(elem: T): Unit + update def remove(elem: T): Unit + def get(index: Int): Option[T] + + object Collection: + def empty[T]: Collection[T] = ??? + + trait Foo: + val thunks: Collection[() => Unit] // that's fine + + object FooImpl1 extends Foo: + val thunks: Collection[() => Unit] = Collection.empty // was error, now ok + val thunks2: Collection[() => Unit] = Collection.empty[() => Unit] // error + val thunks3: Collection[() => Unit] = Collection.empty[() => Unit] // error + +package test2: + + trait Collection[+T] extends Mutable: + def get(index: Int): Option[T] + + object Collection: + def empty[T]: Collection[T] = ??? + + trait Foo: + val thunks: Collection[() => Unit] // that's fine + + object FooImpl1 extends Foo: + val thunks: Collection[() => Unit] = Collection.empty // was error, now ok + val thunks2: Collection[() => Unit] = Collection.empty[() => Unit] // error + val thunks3: Collection[() => Unit] = Collection.empty[() => Unit] // error diff --git a/tests/neg-custom-args/captures/i23431.check b/tests/neg-custom-args/captures/i23431.check new file mode 100644 index 000000000000..1332c2cf2c79 --- /dev/null +++ b/tests/neg-custom-args/captures/i23431.check @@ -0,0 +1,36 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i23431.scala:8:13 ---------------------------------------- +8 | myIO = io // error, level mismatch + | ^^ + | Found: (io : IO^) + | Required: IO^² + | + | where: ^ refers to a fresh root capability in the type of parameter io + | ^² refers to a fresh root capability in the type of variable myIO + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i23431.scala:11:13 --------------------------------------- +11 | myIO = io2 // error, level mismatch + | ^^^ + | Found: (io2 : IO^) + | Required: IO^² + | + | where: ^ refers to a fresh root capability in the type of parameter io2 + | ^² refers to a fresh root capability in the type of variable myIO + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i23431.scala:12:12 --------------------------------------- +12 | withIO: io3 => // error + | ^ + |Found: (io3: IO^?) ->? Unit + |Required: IO^ => Unit + | + |where: => refers to a fresh root capability created in anonymous function of type (io1: IO^): Unit when checking argument to parameter op of method withIO + | ^ refers to the universal root capability +13 | myIO = io3 + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/i23431.scala:6:14 ------------------------------------------------------------- +6 | var myIO: IO^ = io1 // error: separation + | ^^^ + | Separation failure: variable myIO's type IO^ hides parameter io1. + | The parameter needs to be annotated with @consume to allow this. diff --git a/tests/neg-custom-args/captures/i23431.scala b/tests/neg-custom-args/captures/i23431.scala new file mode 100644 index 000000000000..df2edf8f6344 --- /dev/null +++ b/tests/neg-custom-args/captures/i23431.scala @@ -0,0 +1,13 @@ +import language.experimental.captureChecking +trait IO +def withIO(op: IO^ => Unit): Unit = ??? +def test(): Unit = + withIO: io1 => + var myIO: IO^ = io1 // error: separation + def setIO(io: IO^): Unit = + myIO = io // error, level mismatch + withIO(setIO) + withIO: (io2: IO^) => + myIO = io2 // error, level mismatch + withIO: io3 => // error + myIO = io3 diff --git a/tests/neg-custom-args/captures/i23726.check b/tests/neg-custom-args/captures/i23726.check new file mode 100644 index 000000000000..8c8ac94a61e0 --- /dev/null +++ b/tests/neg-custom-args/captures/i23726.check @@ -0,0 +1,51 @@ +-- Error: tests/neg-custom-args/captures/i23726.scala:10:5 ------------------------------------------------------------- +10 | f1(a) // error, as expected + | ^ + |Separation failure: argument of type (a : Ref^) + |to a function of type (x: Ref^) -> List[() ->{a, x} Unit] + |corresponds to capture-polymorphic formal parameter x of type Ref^² + |and hides capabilities {a}. + |Some of these overlap with the captures of the function result with type List[() ->{a} Unit]. + | + | Hidden set of current argument : {a} + | Hidden footprint of current argument : {a} + | Capture set of function result : {a} + | Footprint set of function result : {a} + | The two sets overlap at : {a} + | + |where: ^ refers to a fresh root capability classified as Mutable created in value a when constructing mutable Ref + | ^² refers to a fresh root capability classified as Mutable created in method test1 when checking argument to parameter x of method apply +-- Error: tests/neg-custom-args/captures/i23726.scala:15:5 ------------------------------------------------------------- +15 | f3(b) // error + | ^ + |Separation failure: argument of type (b : Ref^) + |to a function of type (x: Ref^) -> (op: () ->{b} Unit) -> List[() ->{op} Unit] + |corresponds to capture-polymorphic formal parameter x of type Ref^² + |and hides capabilities {b}. + |Some of these overlap with the captures of the function result with type (op: () ->{b} Unit) -> List[() ->{op} Unit]. + | + | Hidden set of current argument : {b} + | Hidden footprint of current argument : {b} + | Capture set of function result : {op} + | Footprint set of function result : {op, b} + | The two sets overlap at : {b} + | + |where: ^ refers to a fresh root capability classified as Mutable created in value b when constructing mutable Ref + | ^² refers to a fresh root capability classified as Mutable created in method test1 when checking argument to parameter x of method apply +-- Error: tests/neg-custom-args/captures/i23726.scala:23:5 ------------------------------------------------------------- +23 | f7(a) // error + | ^ + |Separation failure: argument of type (a : Ref^) + |to a function of type (x: Ref^) ->{a, b} (y: List[Ref^{a, b}]) ->{a, b} Unit + |corresponds to capture-polymorphic formal parameter x of type Ref^² + |and hides capabilities {a}. + |Some of these overlap with the captures of the function prefix. + | + | Hidden set of current argument : {a} + | Hidden footprint of current argument : {a} + | Capture set of function prefix : {f7*} + | Footprint set of function prefix : {f7*, a, b} + | The two sets overlap at : {a} + | + |where: ^ refers to a fresh root capability classified as Mutable created in value a when constructing mutable Ref + | ^² refers to a fresh root capability classified as Mutable created in method test1 when checking argument to parameter x of method apply diff --git a/tests/neg-custom-args/captures/i23726.scala b/tests/neg-custom-args/captures/i23726.scala new file mode 100644 index 000000000000..fc833ef29583 --- /dev/null +++ b/tests/neg-custom-args/captures/i23726.scala @@ -0,0 +1,23 @@ +import language.experimental.captureChecking +import language.experimental.separationChecking +import caps.* +class Ref extends Mutable +def swap(a: Ref^, b: Ref^): Unit = () +def test1(): Unit = + val a = Ref() + val b = Ref() + val f1: (x: Ref^) -> List[() ->{a,x} Unit] = ??? + f1(a) // error, as expected + val f2: (x: Ref^) -> List[() ->{x} Unit] = ??? + f2(a) // ok, as expected + val f3: (x: Ref^) -> (op: () ->{b} Unit) -> List[() ->{op} Unit] = ??? + f3(a) // ok + f3(b) // error + val f4: (x: Ref^) -> (y: Ref^{x}) ->{x} Unit = ??? + f4(a) // ok + val f5: (x: Ref^) -> (y: List[Ref^{a}]) ->{} Unit = ??? + f5(a) // ok + val f6: (x: Ref^) -> (y: List[Ref^{a, b}]) ->{} Unit = ??? + f6(b) // ok + val f7: (x: Ref^) ->{a, b} (y: List[Ref^{a, b}]) ->{a, b} Unit = ??? + f7(a) // error diff --git a/tests/neg-custom-args/captures/io.scala b/tests/neg-custom-args/captures/io.scala index f481bf357fc8..4869a2c89a89 100644 --- a/tests/neg-custom-args/captures/io.scala +++ b/tests/neg-custom-args/captures/io.scala @@ -3,17 +3,17 @@ sealed trait IO: def puts(msg: Any): Unit = println(msg) def test1 = - val IO : IO @retains(caps.cap) = new IO {} + val IO : IO @retains[caps.cap.type] = new IO {} def foo = {IO; IO.puts("hello") } val x : () -> Unit = () => foo // error: Found: (() -> Unit) retains IO; Required: () -> Unit def test2 = - val IO : IO @retains(caps.cap) = new IO {} - def puts(msg: Any, io: IO @retains(caps.cap)) = println(msg) + val IO : IO @retains[caps.cap.type] = new IO {} + def puts(msg: Any, io: IO @retains[caps.cap.type]) = println(msg) def foo() = puts("hello", IO) val x : () -> Unit = () => foo() // error: Found: (() -> Unit) retains IO; Required: () -> Unit -type Capability[T] = T @retains(caps.cap) +type Capability[T] = T @retains[caps.cap.type] def test3 = val IO : Capability[IO] = new IO {} diff --git a/tests/neg-custom-args/captures/lazyListState.check b/tests/neg-custom-args/captures/lazyListState.check new file mode 100644 index 000000000000..bb84f3d7f006 --- /dev/null +++ b/tests/neg-custom-args/captures/lazyListState.check @@ -0,0 +1,10 @@ +-- [E164] Declaration Error: tests/neg-custom-args/captures/lazyListState.scala:12:39 ---------------------------------- +12 | final class Cons[A](val head: A, val tail: LazyListIterable[A]^) extends State[A] // error + | ^ + | error overriding method tail in trait State of type -> LazyListIterable[A]^{cap}; + | value tail of type LazyListIterable[A]^ has incompatible type + | + | where: ^ refers to a fresh root capability in the type of value tail + | cap is a root capability associated with the result type of -> LazyListIterable[A²]^² + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/lazyListState.scala b/tests/neg-custom-args/captures/lazyListState.scala new file mode 100644 index 000000000000..43e8ba19bb2f --- /dev/null +++ b/tests/neg-custom-args/captures/lazyListState.scala @@ -0,0 +1,13 @@ +class LazyListIterable[+A] + +private sealed trait State[+A]: + def head: A + def tail: LazyListIterable[A]^ + +private object State: + object Empty extends State[Nothing]: + def head: Nothing = throw new NoSuchElementException("head of empty lazy list") + def tail: LazyListIterable[Nothing] = throw new UnsupportedOperationException("tail of empty lazy list") + + final class Cons[A](val head: A, val tail: LazyListIterable[A]^) extends State[A] // error + diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index acdbfaaca3e0..0959f1a082fb 100644 --- a/tests/neg-custom-args/captures/lazylist.check +++ b/tests/neg-custom-args/captures/lazylist.check @@ -29,7 +29,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:41:42 ------------------------------------- 41 | val ref4c: LazyList[Int]^{cap1, ref3} = ref4 // error | ^^^^ - | Found: (ref4 : lazylists.LazyList[Int]^{cap3, ref1, ref2}) + | Found: (ref4 : lazylists.LazyList[Int]^{cap3, cap1, cap2}) | Required: lazylists.LazyList[Int]^{cap1, ref3} | | longer explanation available when compiling with `-explain` @@ -37,6 +37,8 @@ 22 | def tail: LazyList[Nothing]^ = ??? // error overriding | ^ | error overriding method tail in class LazyList of type -> lazylists.LazyList[Nothing]; - | method tail of type -> lazylists.LazyList[Nothing]^ has incompatible type + | method tail of type -> lazylists.LazyList[Nothing]^{cap} has incompatible type + | + | where: cap is a root capability associated with the result type of -> lazylists.LazyList[Nothing]^ | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/lazylists-exceptions.check b/tests/neg-custom-args/captures/lazylists-exceptions.check index bdd053910ac8..24ba72b1cdad 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.check +++ b/tests/neg-custom-args/captures/lazylists-exceptions.check @@ -4,6 +4,8 @@ | The result of `try` cannot have type LazyList[Int]^{cap.rd} since | that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. + | + | where: cap is a fresh root capability classified as Control in the type of given instance canThrow$1 37 | tabulate(10) { i => 38 | if i > 9 then throw Ex1() 39 | i * i diff --git a/tests/neg-custom-args/captures/lazylists2.check b/tests/neg-custom-args/captures/lazylists2.check index d1883cc54718..720434cb2a2d 100644 --- a/tests/neg-custom-args/captures/lazylists2.check +++ b/tests/neg-custom-args/captures/lazylists2.check @@ -1,7 +1,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:18:4 ------------------------------------ 18 | final class Mapped extends LazyList[B]: // error | ^ - | Found: LazyList[box B^?]^{f, xs} + | Found: LazyList[B^?]^{f, xs} | Required: LazyList[B]^{f} 19 | this: (Mapped^{xs, f}) => 20 | def isEmpty = false @@ -13,7 +13,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:27:4 ------------------------------------ 27 | final class Mapped extends LazyList[B]: // error | ^ - | Found: LazyList[box B^?]^{f, xs} + | Found: LazyList[B^?]^{f, xs} | Required: LazyList[B]^{xs} 28 | this: Mapped^{xs, f} => 29 | def isEmpty = false @@ -33,7 +33,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:45:4 ------------------------------------ 45 | final class Mapped extends LazyList[B]: // error | ^ - | Found: LazyList[box B^?]^{f, xs} + | Found: LazyList[B^?]^{f, xs} | Required: LazyList[B]^{xs} 46 | this: (Mapped^{xs, f}) => 47 | def isEmpty = false diff --git a/tests/neg-custom-args/captures/lazyref.check b/tests/neg-custom-args/captures/lazyref.check index 8db8791d1123..a8419cb30b12 100644 --- a/tests/neg-custom-args/captures/lazyref.check +++ b/tests/neg-custom-args/captures/lazyref.check @@ -32,39 +32,21 @@ | Separation failure: Illegal access to {LazyRef.this.elem} which is hidden by the previous definition | of value get with type () => T. | This type hides capabilities {LazyRef.this.elem} --- Error: tests/neg-custom-args/captures/lazyref.scala:23:13 ----------------------------------------------------------- -23 | val ref3 = ref1.map(g) // error: separation failure - | ^^^^ - | Separation failure: Illegal access to {cap1} which is hidden by the previous definition - | of value ref2 with type LazyRef[Int]{val elem: () => Int}^{cap2, ref1}. - | This type hides capabilities {cap1} --- Error: tests/neg-custom-args/captures/lazyref.scala:26:9 ------------------------------------------------------------ -26 | if cap1 == cap2 // error: separation failure // error: separation failure - | ^^^^ - | Separation failure: Illegal access to {cap1} which is hidden by the previous definition - | of value ref3 with type LazyRef[Int]{val elem: () => Int}^{cap2, ref1}. - | This type hides capabilities {ref2*, cap1} --- Error: tests/neg-custom-args/captures/lazyref.scala:26:17 ----------------------------------------------------------- -26 | if cap1 == cap2 // error: separation failure // error: separation failure - | ^^^^ - | Separation failure: Illegal access to {cap2} which is hidden by the previous definition - | of value ref3 with type LazyRef[Int]{val elem: () => Int}^{cap2, ref1}. - | This type hides capabilities {ref2*, cap1} --- Error: tests/neg-custom-args/captures/lazyref.scala:27:11 ----------------------------------------------------------- -27 | then ref1 // error: separation failure - | ^^^^ - | Separation failure: Illegal access to {cap1, ref1} which is hidden by the previous definition - | of value ref3 with type LazyRef[Int]{val elem: () => Int}^{cap2, ref1}. - | This type hides capabilities {ref2*, cap1} --- Error: tests/neg-custom-args/captures/lazyref.scala:28:11 ----------------------------------------------------------- -28 | else ref2) // error: separation failure - | ^^^^ - | Separation failure: Illegal access to {cap1, cap2, ref1} which is hidden by the previous definition - | of value ref3 with type LazyRef[Int]{val elem: () => Int}^{cap2, ref1}. - | This type hides capabilities {ref2*, cap1} + | + | where: => refers to a fresh root capability in the type of value get -- Error: tests/neg-custom-args/captures/lazyref.scala:29:9 ------------------------------------------------------------ 29 | .map(g) // error: separation failure | ^ - | Separation failure: Illegal access to {cap2} which is hidden by the previous definition - | of value ref3 with type LazyRef[Int]{val elem: () => Int}^{cap2, ref1}. - | This type hides capabilities {ref2*, cap1} + |Separation failure: argument of type (x: Int) ->{cap2} Int + |to method map: [U](f: T => U): LazyRef[U]^{f, LazyRef.this} + |corresponds to capture-polymorphic formal parameter f of type Int => Int + |and hides capabilities {cap2}. + |Some of these overlap with the captures of the function prefix with type (LazyRef[Int]{val elem: () ->{ref2*} Int} | (ref1 : LazyRef[Int]{val elem: () ->{cap1} Int}^{cap1}))^{ref2}. + | + | Hidden set of current argument : {cap2} + | Hidden footprint of current argument : {cap2} + | Capture set of function prefix : {ref1, ref2, ref2*} + | Footprint set of function prefix : {ref1, ref2, ref2*, cap1, cap2} + | The two sets overlap at : {cap2} + | + |where: => refers to a fresh root capability created in value ref4 when checking argument to parameter f of method map diff --git a/tests/neg-custom-args/captures/lazyref.scala b/tests/neg-custom-args/captures/lazyref.scala index 396d9470ea17..ea3897484857 100644 --- a/tests/neg-custom-args/captures/lazyref.scala +++ b/tests/neg-custom-args/captures/lazyref.scala @@ -20,11 +20,11 @@ def test(cap1: Cap, cap2: Cap) = val ref1c: LazyRef[Int] = ref1 // error val ref2 = map(ref1, g) val ref2c: LazyRef[Int]^{cap2} = ref2 // error - val ref3 = ref1.map(g) // error: separation failure + val ref3 = ref1.map(g) val ref3c: LazyRef[Int]^{ref1} = ref3 // error val ref4 = ( - if cap1 == cap2 // error: separation failure // error: separation failure - then ref1 // error: separation failure - else ref2) // error: separation failure + if cap1 == cap2 + then ref1 + else ref2) .map(g) // error: separation failure val ref4c: LazyRef[Int]^{cap1} = ref4 // error diff --git a/tests/neg-custom-args/captures/leak-problem-2.check b/tests/neg-custom-args/captures/leak-problem-2.check index 42282ff7f9f4..80add5eddd22 100644 --- a/tests/neg-custom-args/captures/leak-problem-2.check +++ b/tests/neg-custom-args/captures/leak-problem-2.check @@ -1,7 +1,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/leak-problem-2.scala:8:8 --------------------------------- 8 | = race(Seq(src1, src2)) // error | ^^^^^^^^^^^^^^^^^^^^^ - | Found: Source[box T^?]^{src1, src2} + | Found: Source[T^?]^{src1, src2} | Required: Source[T] | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/leak-problem-unboxed.scala b/tests/neg-custom-args/captures/leak-problem-unboxed.scala index aedd7c889112..6d4c4b4c94aa 100644 --- a/tests/neg-custom-args/captures/leak-problem-unboxed.scala +++ b/tests/neg-custom-args/captures/leak-problem-unboxed.scala @@ -19,14 +19,14 @@ def useBoxedAsync1(@use x: Box[Async^]): Unit = x.get.read() // ok def test(): Unit = val f: Box[Async^] => Unit = (x: Box[Async^]) => useBoxedAsync(x) // error - val _: Box[Async^] => Unit = useBoxedAsync(_) // error - val _: Box[Async^] => Unit = useBoxedAsync // error - val _ = useBoxedAsync(_) // error - val _ = useBoxedAsync // error + val t1: Box[Async^] => Unit = useBoxedAsync(_) // error + val t2: Box[Async^] => Unit = useBoxedAsync // error + val t3 = useBoxedAsync(_) // was error, now ok + val t4 = useBoxedAsync // was error, now ok def boom(x: Async^): () ->{f} Unit = () => f(Box(x)) val leaked = usingAsync[() ->{f} Unit](boom) - leaked() // scope violation \ No newline at end of file + leaked() // scope violation diff --git a/tests/neg-custom-args/captures/leaking-iterators.check b/tests/neg-custom-args/captures/leaking-iterators.check index 54a8d6ccea9e..f4dde1d7f2fd 100644 --- a/tests/neg-custom-args/captures/leaking-iterators.check +++ b/tests/neg-custom-args/captures/leaking-iterators.check @@ -1,11 +1,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/leaking-iterators.scala:56:16 ---------------------------- 56 | usingLogFile: log => // error | ^ - | Found: (log: java.io.FileOutputStream^) ->? box cctest.Iterator[Int]^{log} - | Required: (log: java.io.FileOutputStream^) ->{fresh} box cctest.Iterator[Int]^? + |Found: (log: java.io.FileOutputStream^?) ->? cctest.Iterator[Int]^{log} + |Required: java.io.FileOutputStream^ => cctest.Iterator[Int]^? | - | Note that reference log.type - | cannot be included in outer capture set ? + |where: => refers to a fresh root capability created in method test when checking argument to parameter op of method usingLogFile + | ^ refers to the universal root capability + | + |Note that reference log.type + |cannot be included in outer capture set ? 57 | xs.iterator.map: x => 58 | log.write(x) 59 | x * x diff --git a/tests/neg-custom-args/captures/levels.check b/tests/neg-custom-args/captures/levels.check index 7ef29c36efc3..dc8994907634 100644 --- a/tests/neg-custom-args/captures/levels.check +++ b/tests/neg-custom-args/captures/levels.check @@ -1,12 +1,14 @@ -- Error: tests/neg-custom-args/captures/levels.scala:17:21 ------------------------------------------------------------ 17 | val _ = Ref[String => String]((x: String) => x) // error | ^^^^^^^^^^^^^^^^ - | Type variable T of constructor Ref cannot be instantiated to box String => String since + | Type variable T of constructor Ref cannot be instantiated to String => String since | that type captures the root capability `cap`. + | + | where: => refers to the universal root capability -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/levels.scala:22:11 --------------------------------------- 22 | r.setV(g) // error | ^ | Found: (x: String) ->{cap3} String - | Required: (x: String) -> String + | Required: String -> String | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/lexical-control.scala b/tests/neg-custom-args/captures/lexical-control.scala new file mode 100644 index 000000000000..7a38a6a94600 --- /dev/null +++ b/tests/neg-custom-args/captures/lexical-control.scala @@ -0,0 +1,27 @@ +import language.experimental.captureChecking +import caps.* + +trait Label extends Capability: + type Fv^ // the capability set occurring freely in the `block` passed to `boundary` below. + +def boundary[T, C^](block: Label{type Fv = {C} } ->{C} T): T = ??? // link label and block capture set +def suspend[U](label: Label)[D^ <: {label.Fv}](handler: () ->{D} U): U = ??? // note the path + +def test = + val x = 1 + boundary: outer => + val y = 2 + boundary: inner => + val z = 3 + val w = suspend(outer) {() => z} // ok + val v = suspend(inner) {() => y} // ok + val u = suspend(inner): () => + suspend(outer) {() => y} // ok + suspend(outer) {() => y} // ok + y + suspend(outer) { () => // error + suspend(outer) {() => y } + } + suspend(outer): () => // error (leaks the inner label) + println(inner) + x + y + z \ No newline at end of file diff --git a/tests/neg-custom-args/captures/linear-buffer-2.check b/tests/neg-custom-args/captures/linear-buffer-2.check index 3d64c432d116..be8f0623418e 100644 --- a/tests/neg-custom-args/captures/linear-buffer-2.check +++ b/tests/neg-custom-args/captures/linear-buffer-2.check @@ -4,26 +4,36 @@ | Separation failure: Illegal access to {buf} which is hidden by the previous definition | of value buf1 with type Buffer[Int]^. | This type hides capabilities {buf} + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of value buf1 -- Error: tests/neg-custom-args/captures/linear-buffer-2.scala:20:13 --------------------------------------------------- 20 | val buf3 = buf1.append(4) // error | ^^^^ | Separation failure: Illegal access to (buf1 : Buffer[Int]^), which was passed to a | @consume parameter or was used as a prefix to a @consume method on line 18 | and therefore is no longer available. + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of value buf1 -- Error: tests/neg-custom-args/captures/linear-buffer-2.scala:28:13 --------------------------------------------------- 28 | val buf3 = buf1.append(4) // error | ^^^^ | Separation failure: Illegal access to (buf1 : Buffer[Int]^), which was passed to a | @consume parameter or was used as a prefix to a @consume method on line 25 | and therefore is no longer available. + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of value buf1 -- Error: tests/neg-custom-args/captures/linear-buffer-2.scala:38:13 --------------------------------------------------- 38 | val buf3 = buf1.append(4) // error | ^^^^ | Separation failure: Illegal access to (buf1 : Buffer[Int]^), which was passed to a | @consume parameter or was used as a prefix to a @consume method on line 33 | and therefore is no longer available. + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of value buf1 -- Error: tests/neg-custom-args/captures/linear-buffer-2.scala:42:4 ---------------------------------------------------- 42 | buf.append(1) // error | ^^^ | Separation failure: (buf : Buffer[Int]^) appears in a loop, therefore it cannot | be passed to a @consume parameter or be used as a prefix of a @consume method call. + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of parameter buf diff --git a/tests/neg-custom-args/captures/linear-buffer-2.scala b/tests/neg-custom-args/captures/linear-buffer-2.scala index 428171c3fab8..78561351838b 100644 --- a/tests/neg-custom-args/captures/linear-buffer-2.scala +++ b/tests/neg-custom-args/captures/linear-buffer-2.scala @@ -2,7 +2,7 @@ import caps.{cap, consume, Mutable} import language.experimental.captureChecking class Buffer[T] extends Mutable: - @consume mut def append(x: T): Buffer[T]^ = this // ok + @consume update def append(x: T): Buffer[T]^ = this // ok def app[T](@consume buf: Buffer[T]^, elem: T): Buffer[T]^ = buf.append(elem) diff --git a/tests/neg-custom-args/captures/linear-buffer.check b/tests/neg-custom-args/captures/linear-buffer.check index 55af88406356..4aaf1794aac1 100644 --- a/tests/neg-custom-args/captures/linear-buffer.check +++ b/tests/neg-custom-args/captures/linear-buffer.check @@ -1,6 +1,6 @@ --- Error: tests/neg-custom-args/captures/linear-buffer.scala:5:24 ------------------------------------------------------ -5 | mut def append(x: T): BadBuffer[T]^ = this // error - | ^^^^^^^^^^^^^ +-- Error: tests/neg-custom-args/captures/linear-buffer.scala:5:27 ------------------------------------------------------ +5 | update def append(x: T): BadBuffer[T]^ = this // error + | ^^^^^^^^^^^^^ | Separation failure: method append's result type BadBuffer[T]^ hides non-local this of class class BadBuffer. | The access must be in a @consume method to allow this. -- Error: tests/neg-custom-args/captures/linear-buffer.scala:7:13 ------------------------------------------------------ @@ -8,37 +8,42 @@ | ^^^^^^^^^^^^^ | Separation failure: method bar's result type BadBuffer[T]^ hides non-local this of class class BadBuffer. | The access must be in a @consume method to allow this. --- Error: tests/neg-custom-args/captures/linear-buffer.scala:6:9 ------------------------------------------------------- -6 | def foo = // error - | ^ - |Separation failure: method foo's inferred result type BadBuffer[box T^{}]^ hides non-local this of class class BadBuffer. - |The access must be in a @consume method to allow this. -- Error: tests/neg-custom-args/captures/linear-buffer.scala:19:17 ----------------------------------------------------- 19 | val buf3 = app(buf, 3) // error | ^^^ - | Separation failure: Illegal access to (buf : Buffer[Int]^), which was passed to a - | @consume parameter or was used as a prefix to a @consume method on line 17 - | and therefore is no longer available. + | Separation failure: Illegal access to (buf : Buffer[Int]^), which was passed to a + | @consume parameter or was used as a prefix to a @consume method on line 17 + | and therefore is no longer available. + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of parameter buf -- Error: tests/neg-custom-args/captures/linear-buffer.scala:26:17 ----------------------------------------------------- 26 | val buf3 = app(buf1, 4) // error | ^^^^ | Separation failure: Illegal access to (buf1 : Buffer[Int]^), which was passed to a | @consume parameter or was used as a prefix to a @consume method on line 24 | and therefore is no longer available. + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of value buf1 -- Error: tests/neg-custom-args/captures/linear-buffer.scala:34:17 ----------------------------------------------------- 34 | val buf3 = app(buf1, 4) // error | ^^^^ | Separation failure: Illegal access to (buf1 : Buffer[Int]^), which was passed to a | @consume parameter or was used as a prefix to a @consume method on line 31 | and therefore is no longer available. + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of value buf1 -- Error: tests/neg-custom-args/captures/linear-buffer.scala:44:17 ----------------------------------------------------- 44 | val buf3 = app(buf1, 4) // error | ^^^^ | Separation failure: Illegal access to (buf1 : Buffer[Int]^), which was passed to a | @consume parameter or was used as a prefix to a @consume method on line 39 | and therefore is no longer available. + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of value buf1 -- Error: tests/neg-custom-args/captures/linear-buffer.scala:48:8 ------------------------------------------------------ 48 | app(buf, 1) // error | ^^^ | Separation failure: (buf : Buffer[Int]^) appears in a loop, therefore it cannot | be passed to a @consume parameter or be used as a prefix of a @consume method call. + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of parameter buf diff --git a/tests/neg-custom-args/captures/linear-buffer.scala b/tests/neg-custom-args/captures/linear-buffer.scala index 97315c1aa0fb..5c6d991579aa 100644 --- a/tests/neg-custom-args/captures/linear-buffer.scala +++ b/tests/neg-custom-args/captures/linear-buffer.scala @@ -2,13 +2,13 @@ import caps.{cap, consume, Mutable} import language.experimental.captureChecking class BadBuffer[T] extends Mutable: - mut def append(x: T): BadBuffer[T]^ = this // error - def foo = // error + update def append(x: T): BadBuffer[T]^ = this // error + def foo = def bar: BadBuffer[T]^ = this // error bar class Buffer[T] extends Mutable: - @consume mut def append(x: T): Buffer[T]^ = this // ok + @consume update def append(x: T): Buffer[T]^ = this // ok def app[T](@consume buf: Buffer[T]^, elem: T): Buffer[T]^ = buf.append(elem) diff --git a/tests/neg-custom-args/captures/localReaches.check b/tests/neg-custom-args/captures/localReaches.check new file mode 100644 index 000000000000..886fbbf7c3e9 --- /dev/null +++ b/tests/neg-custom-args/captures/localReaches.check @@ -0,0 +1,20 @@ +-- Error: tests/neg-custom-args/captures/localReaches.scala:24:30 ------------------------------------------------------ +24 | var x: () ->{xs*} Unit = ys.head // error + | ^^^^^^^ + | Local reach capability ops* leaks into capture scope of method localReach3. + | To allow this, the parameter ops should be declared with a @use annotation +-- Error: tests/neg-custom-args/captures/localReaches.scala:27:11 ------------------------------------------------------ +27 | x = ys.head // error + | ^^^^^^^ + | Local reach capability ops* leaks into capture scope of method localReach3. + | To allow this, the parameter ops should be declared with a @use annotation +-- Error: tests/neg-custom-args/captures/localReaches.scala:14:10 ------------------------------------------------------ +14 | val xs: List[() => Unit] = op :: Nil // error + | ^^^^^^^^^^^^^^^^ + | Separation failure: value xs's type List[() => Unit] hides parameter op. + | The parameter needs to be annotated with @consume to allow this. +-- Error: tests/neg-custom-args/captures/localReaches.scala:22:10 ------------------------------------------------------ +22 | val xs: List[() => Unit] = ops // error + | ^^^^^^^^^^^^^^^^ + | Separation failure: value xs's type List[() => Unit] hides parameter ops. + | The parameter needs to be annotated with @consume to allow this. diff --git a/tests/neg-custom-args/captures/localReaches.scala b/tests/neg-custom-args/captures/localReaches.scala new file mode 100644 index 000000000000..9795a6dd099c --- /dev/null +++ b/tests/neg-custom-args/captures/localReaches.scala @@ -0,0 +1,28 @@ +import language.experimental.captureChecking +// no separation checking +import caps.consume + +def localReach() = + val xs: List[() => Unit] = ??? + var ys: List[() ->{xs*} Unit] = xs + var x: () ->{xs*} Unit = ys.head + while ys.nonEmpty do + ys = ys.tail + x = ys.head + +def localReach2(op: () => Unit) = + val xs: List[() => Unit] = op :: Nil // error + var ys: List[() ->{xs*} Unit] = xs + var x: () ->{xs*} Unit = ys.head + while ys.nonEmpty do + ys = ys.tail + x = ys.head + +def localReach3(ops: List[() => Unit]) = + val xs: List[() => Unit] = ops // error + var ys: List[() ->{xs*} Unit] = xs + var x: () ->{xs*} Unit = ys.head // error + while ys.nonEmpty do + ys = ys.tail + x = ys.head // error + diff --git a/tests/neg-custom-args/captures/match.scala b/tests/neg-custom-args/captures/match.scala new file mode 100644 index 000000000000..cf0c0f1a3377 --- /dev/null +++ b/tests/neg-custom-args/captures/match.scala @@ -0,0 +1,24 @@ +import language.experimental.captureChecking + +trait A + +case class B(x: AnyRef^) extends A + +def test = + val x: AnyRef^ = new AnyRef + val a: A^{x} = B(x) + + val y1: A = a match + case b: B => b // error: (b: B) becomes B^{x} implicitly + + val y2: A^{x} = a match + case b: B => + val bb: B^{b} = b + val aa: A^{a} = bb + b // ok + + val x3: AnyRef = a match + case B(x2: AnyRef) => x2 // error: we lose some information about field x, but it still cannot be pure + + val x4: AnyRef = a match + case b: B => b.x // error diff --git a/tests/neg-custom-args/captures/matrix.check b/tests/neg-custom-args/captures/matrix.check new file mode 100644 index 000000000000..f6d1cf6634bc --- /dev/null +++ b/tests/neg-custom-args/captures/matrix.check @@ -0,0 +1,32 @@ +-- Error: tests/neg-custom-args/captures/matrix.scala:27:10 ------------------------------------------------------------ +27 | mul(m1, m2, m2) // error: will fail separation checking + | ^^ + |Separation failure: argument of type Matrix^{m2.rd} + |to method mul: (x: Matrix^{cap.rd}, y: Matrix^{cap.rd}, z: Matrix^): Unit + |corresponds to capture-polymorphic formal parameter y of type Matrix^{cap.rd} + |and hides capabilities {m2.rd}. + |Some of these overlap with the captures of the third argument with type (m2 : Matrix^). + | + | Hidden set of current argument : {m2.rd} + | Hidden footprint of current argument : {m2.rd} + | Capture set of third argument : {m2} + | Footprint set of third argument : {m2} + | The two sets overlap at : {m2} + | + |where: cap is a fresh root capability classified as Mutable created in method Test when checking argument to parameter y of method mul +-- Error: tests/neg-custom-args/captures/matrix.scala:30:11 ------------------------------------------------------------ +30 | mul1(m1, m2, m2) // error: will fail separation checking + | ^^ + |Separation failure: argument of type Matrix^{m2.rd} + |to method mul1: (x: Matrix^{cap.rd}, y: Matrix^{cap.rd}, z: Matrix^): Unit + |corresponds to capture-polymorphic formal parameter y of type Matrix^{cap.rd} + |and hides capabilities {m2.rd}. + |Some of these overlap with the captures of the third argument with type (m2 : Matrix^). + | + | Hidden set of current argument : {m2.rd} + | Hidden footprint of current argument : {m2.rd} + | Capture set of third argument : {m2} + | Footprint set of third argument : {m2} + | The two sets overlap at : {m2} + | + |where: cap is a fresh root capability classified as Mutable created in method Test when checking argument to parameter y of method mul1 diff --git a/tests/neg-custom-args/captures/matrix.scala b/tests/neg-custom-args/captures/matrix.scala new file mode 100644 index 000000000000..e473da7edc70 --- /dev/null +++ b/tests/neg-custom-args/captures/matrix.scala @@ -0,0 +1,36 @@ +import caps.Mutable +import caps.cap + +trait Rdr[T]: + def get: T + +class Ref[T](init: T) extends Rdr[T], Mutable: + private var current = init + def get: T = current + update def put(x: T): Unit = current = x + +abstract class IMatrix: + def apply(i: Int, j: Int): Double + +class Matrix(nrows: Int, ncols: Int) extends IMatrix, Mutable: + val arr = Array.fill(nrows, ncols)(0.0) + def apply(i: Int, j: Int): Double = arr(i)(j) + update def update(i: Int, j: Int, x: Double): Unit = arr(i)(j) = x + + +def mul(x: Matrix, y: Matrix, z: Matrix^): Unit = ??? +def mul1(x: Matrix^{cap.rd}, y: Matrix^{cap.rd}, z: Matrix^): Unit = ??? + +def Test(c: Object^): Unit = + val m1 = Matrix(10, 10) + val m2 = Matrix(10, 10) + mul(m1, m2, m2) // error: will fail separation checking + mul(m1, m1, m2) // should be ok + + mul1(m1, m2, m2) // error: will fail separation checking + mul(m1, m1, m2) // should be ok + + def f2(): Matrix^ = Matrix(10, 10) + + val i1: IMatrix^{cap.rd} = m1 + val i2: IMatrix^{cap.rd} = f2() diff --git a/tests/neg-custom-args/captures/method-uses.scala b/tests/neg-custom-args/captures/method-uses.scala index 69acef6a99a8..da8f226685c0 100644 --- a/tests/neg-custom-args/captures/method-uses.scala +++ b/tests/neg-custom-args/captures/method-uses.scala @@ -2,9 +2,9 @@ def test(xs: List[() => Unit]) = xs.head // error def foo = - xs.head // ok + xs.head // error, ok under deferredReaches def bar() = - xs.head // ok + xs.head // error, ok under deferredReaches class Foo: println(xs.head) // error, but could be OK @@ -14,7 +14,7 @@ def test(xs: List[() => Unit]) = Foo() // OK, but could be error def test2(xs: List[() => Unit]) = - def foo = xs.head // ok + def foo = xs.head // error, ok under deferredReaches () def test3(xs: List[() => Unit]): () ->{xs*} Unit = () => diff --git a/tests/neg-custom-args/captures/mut-outside-mutable.check b/tests/neg-custom-args/captures/mut-outside-mutable.check index bfc1b5161f0a..d0f61f150565 100644 --- a/tests/neg-custom-args/captures/mut-outside-mutable.check +++ b/tests/neg-custom-args/captures/mut-outside-mutable.check @@ -1,8 +1,8 @@ --- Error: tests/neg-custom-args/captures/mut-outside-mutable.scala:5:10 ------------------------------------------------ -5 | mut def foreach(op: T => Unit): Unit // error - | ^ - | Update methods can only be used as members of classes extending the `Mutable` trait --- Error: tests/neg-custom-args/captures/mut-outside-mutable.scala:9:12 ------------------------------------------------ -9 | mut def baz() = 1 // error - | ^ - | Update methods can only be used as members of classes extending the `Mutable` trait +-- Error: tests/neg-custom-args/captures/mut-outside-mutable.scala:5:13 ------------------------------------------------ +5 | update def foreach(op: T => Unit): Unit // error + | ^ + | Update methods can only be used as members of classes extending the `Mutable` trait +-- Error: tests/neg-custom-args/captures/mut-outside-mutable.scala:9:15 ------------------------------------------------ +9 | update def baz() = 1 // error + | ^ + | Update methods can only be used as members of classes extending the `Mutable` trait diff --git a/tests/neg-custom-args/captures/mut-outside-mutable.scala b/tests/neg-custom-args/captures/mut-outside-mutable.scala index 18c0e59c5bd8..12c47334a422 100644 --- a/tests/neg-custom-args/captures/mut-outside-mutable.scala +++ b/tests/neg-custom-args/captures/mut-outside-mutable.scala @@ -2,9 +2,9 @@ import caps.Mutable trait IterableOnce[T]: def iterator: Iterator[T]^{this} - mut def foreach(op: T => Unit): Unit // error + update def foreach(op: T => Unit): Unit // error trait Foo extends Mutable: def bar = - mut def baz() = 1 // error + update def baz() = 1 // error baz() diff --git a/tests/neg-custom-args/captures/mut-override.scala b/tests/neg-custom-args/captures/mut-override.scala index 848e4d880223..aa2913645e43 100644 --- a/tests/neg-custom-args/captures/mut-override.scala +++ b/tests/neg-custom-args/captures/mut-override.scala @@ -2,18 +2,18 @@ import caps.Mutable trait IterableOnce[T] extends Mutable: def iterator: Iterator[T]^{this} - mut def foreach(op: T => Unit): Unit + update def foreach(op: T => Unit): Unit trait Iterator[T] extends IterableOnce[T]: def iterator = this def hasNext: Boolean - mut def next(): T - mut def foreach(op: T => Unit): Unit = ??? - override mut def toString = ??? // error + update def next(): T + update def foreach(op: T => Unit): Unit = ??? + override update def toString = ??? // error trait Iterable[T] extends IterableOnce[T]: def iterator: Iterator[T] = ??? def foreach(op: T => Unit) = iterator.foreach(op) trait BadIterator[T] extends Iterator[T]: - override mut def hasNext: Boolean // error + override update def hasNext: Boolean // error diff --git a/tests/neg-custom-args/captures/nonsensical-for-pure.check b/tests/neg-custom-args/captures/nonsensical-for-pure.check new file mode 100644 index 000000000000..6860446c4177 --- /dev/null +++ b/tests/neg-custom-args/captures/nonsensical-for-pure.check @@ -0,0 +1,4 @@ +-- Error: tests/neg-custom-args/captures/nonsensical-for-pure.scala:1:7 ------------------------------------------------ +1 |val x: Int^ = 2 // error + | ^^^^ + | Int is a pure type, it makes no sense to add a capture set to it diff --git a/tests/neg-custom-args/captures/nonsensical-for-pure.scala b/tests/neg-custom-args/captures/nonsensical-for-pure.scala new file mode 100644 index 000000000000..ffdd9c28e7bf --- /dev/null +++ b/tests/neg-custom-args/captures/nonsensical-for-pure.scala @@ -0,0 +1 @@ +val x: Int^ = 2 // error diff --git a/tests/neg-custom-args/captures/outer-var.check b/tests/neg-custom-args/captures/outer-var.check index a49722eb4bb9..d6d9067d9d2f 100644 --- a/tests/neg-custom-args/captures/outer-var.check +++ b/tests/neg-custom-args/captures/outer-var.check @@ -4,7 +4,8 @@ | Found: (q : () => Unit) | Required: () ->{p, q²} Unit | - | where: q is a parameter in method inner + | where: => refers to a fresh root capability in the type of parameter q + | q is a parameter in method inner | q² is a parameter in method test | | longer explanation available when compiling with `-explain` @@ -14,6 +15,8 @@ | Found: () => Unit | Required: () ->{p, q} Unit | + | where: => refers to a fresh root capability created in method inner + | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:14:9 ------------------------------------- 14 | y = (q: Proc) // error @@ -21,6 +24,8 @@ | Found: () => Unit | Required: () ->{p} Unit | + | where: => refers to a fresh root capability created in method inner + | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:15:8 ------------------------------------- 15 | y = q // error, was OK under unsealed @@ -28,9 +33,13 @@ | Found: (q : () => Unit) | Required: () ->{p} Unit | + | where: => refers to a fresh root capability in the type of parameter q + | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/outer-var.scala:17:57 --------------------------------------------------------- 17 | var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error, was OK under unsealed | ^^^^^^^^^^ - | Type variable A of object ListBuffer cannot be instantiated to box () => Unit since - | that type captures the root capability `cap`. + | Type variable A of object ListBuffer cannot be instantiated to () => Unit since + | that type captures the root capability `cap`. + | + | where: => refers to the universal root capability diff --git a/tests/neg-custom-args/captures/polyCaptures.check b/tests/neg-custom-args/captures/polyCaptures.check index 8173828b7bc8..08ebaf1c63ee 100644 --- a/tests/neg-custom-args/captures/polyCaptures.check +++ b/tests/neg-custom-args/captures/polyCaptures.check @@ -1,8 +1,8 @@ -- Error: tests/neg-custom-args/captures/polyCaptures.scala:4:22 ------------------------------------------------------- -4 |val runOpsCheck: [C^] -> (ops: List[() ->{C^} Unit]) ->{C^} Unit = runOps // error +4 |val runOpsCheck: [C^] -> (ops: List[() ->{C} Unit]) ->{C} Unit = runOps // error | ^ | Implementation restriction: polymorphic function types cannot wrap function types that have capture sets -- Error: tests/neg-custom-args/captures/polyCaptures.scala:5:23 ------------------------------------------------------- -5 |val runOpsCheck2: [C^] => (ops: List[() ->{C^} Unit]) ->{C^} Unit = runOps // error +5 |val runOpsCheck2: [C^] => (ops: List[() ->{C} Unit]) ->{C} Unit = runOps // error | ^ | Implementation restriction: polymorphic function types cannot wrap function types that have capture sets diff --git a/tests/neg-custom-args/captures/polyCaptures.scala b/tests/neg-custom-args/captures/polyCaptures.scala index 776af95e5dcf..eb8e50f0b997 100644 --- a/tests/neg-custom-args/captures/polyCaptures.scala +++ b/tests/neg-custom-args/captures/polyCaptures.scala @@ -1,7 +1,7 @@ class Box[X](val elem: X) -val runOps = [C^] => (b: Box[() ->{C^} Unit]) => b.elem() -val runOpsCheck: [C^] -> (ops: List[() ->{C^} Unit]) ->{C^} Unit = runOps // error -val runOpsCheck2: [C^] => (ops: List[() ->{C^} Unit]) ->{C^} Unit = runOps // error +val runOps = [C^] => (b: Box[() ->{C} Unit]) => b.elem() +val runOpsCheck: [C^] -> (ops: List[() ->{C} Unit]) ->{C} Unit = runOps // error +val runOpsCheck2: [C^] => (ops: List[() ->{C} Unit]) ->{C} Unit = runOps // error diff --git a/tests/neg-custom-args/captures/reach-in-results.scala b/tests/neg-custom-args/captures/reach-in-results.scala new file mode 100644 index 000000000000..57339bc81e04 --- /dev/null +++ b/tests/neg-custom-args/captures/reach-in-results.scala @@ -0,0 +1,7 @@ +import language.experimental.captureChecking +trait File +def usingFile[R](op: File^ => R): R = op(new File {}) +def id(x: File^): File^{x} = x +def test1(): Unit = + val op: File^ -> File^ = id // error, this should be disallowed + val g: File^ -> File^{op*} = op // otherwise we can brand arbitrary File as simply capturing op* \ No newline at end of file diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index 8dc433afc979..70c2b8f2a82b 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -1,101 +1,117 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:22:13 -------------------------------------- 22 | usingFile: f => // error | ^ - | Found: (f: File^?) ->? Unit - | Required: (f: File^) ->{fresh} Unit + |Found: (f: File^?) ->? Unit + |Required: File^ => Unit + | + |where: => refers to a fresh root capability created in method runAll0 when checking argument to parameter f of method usingFile + | ^ refers to the universal root capability 23 | cur = (() => f.write()) :: Nil | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:32:13 -------------------------------------- 32 | usingFile: f => // error | ^ - | Found: (f: File^?) ->? Unit - | Required: (f: File^) ->{fresh} Unit + |Found: (f: File^?) ->? Unit + |Required: File^ => Unit + | + |where: => refers to a fresh root capability created in method runAll1 when checking argument to parameter f of method usingFile + | ^ refers to the universal root capability 33 | cur.set: 34 | (() => f.write()) :: Nil | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/reaches.scala:37:6 ------------------------------------------------------------ -37 | var cur: List[Proc] = xs // error - | ^ - | Mutable variable cur cannot have type List[box () => Unit] since - | the part box () => Unit of that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/reaches.scala:44:16 ----------------------------------------------------------- 44 | val cur = Ref[List[Proc]](xs) // error | ^^^^^^^^^^ - | Type variable T of constructor Ref cannot be instantiated to List[box () => Unit] since - | the part box () => Unit of that type captures the root capability `cap`. + | Type variable T of constructor Ref cannot be instantiated to List[() => Unit] since + | the part () => Unit of that type captures the root capability `cap`. + | + | where: => refers to the universal root capability -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:46:35 -------------------------------------- 46 | val next: () => Unit = cur.get.head // error | ^^^^^^^^^^^^ | Found: () => Unit - | Required: () ->{fresh} Unit + | Required: () =>² Unit + | + | where: => refers to the universal root capability + | =>² refers to a fresh root capability in the type of value next | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:48:20 -------------------------------------- 48 | cur.set(cur.get.tail: List[Proc]) // error | ^^^^^^^^^^^^ - | Found: List[box () => Unit] - | Required: List[box () ->{fresh} Unit] + | Found: List[() => Unit] + | Required: List[() =>² Unit] + | + | where: => refers to the universal root capability + | =>² refers to a fresh root capability created in method runAll3 | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/reaches.scala:54:51 ----------------------------------------------------------- 54 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error | ^ - | Type variable A of constructor Id cannot be instantiated to box () => Unit since - | that type captures the root capability `cap`. + | Type variable A of constructor Id cannot be instantiated to () => Unit since + | that type captures the root capability `cap`. + | + | where: => refers to the universal root capability -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:59:27 -------------------------------------- 59 | val id: File^ -> File^ = x => x // error | ^^^^^^ | Found: (x: File^) ->? File^{x} - | Required: (x: File^) -> File^{fresh} + | Required: File^ -> File^² | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:62:38 -------------------------------------- -62 | val leaked = usingFile[File^{id*}]: f => // error // error - | ^ - | Found: (f: File^?) ->? box File^? - | Required: (f: File^) ->{fresh} box File^{id*} -63 | val f1: File^{id*} = id(f) -64 | f1 + | where: ^ refers to the universal root capability + | ^² refers to a fresh root capability in the type of value id | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:67:32 -------------------------------------- 67 | val id: (x: File^) -> File^ = x => x // error | ^^^^^^ - | Found: (x: File^) ->? File^{x} - | Required: (x: File^) -> File^{localcap} + | Found: (x: File^) ->? File^{x} + | Required: (x: File^) -> File^² | - | Note that the existential capture root in File^ - | cannot subsume the capability x.type since that capability is not a SharedCapability + | where: ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: File^): File^² + | + | Note that the existential capture root in File^ + | cannot subsume the capability x.type since that capability is not a `Sharable` capability | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:70:38 -------------------------------------- -70 | val leaked = usingFile[File^{id*}]: f => // error // error +70 | val leaked = usingFile[File^{id*}]: f => // error | ^ - | Found: (f: File^?) ->? box File^? - | Required: (f: File^) ->{fresh} box File^{id*} + |Found: (f: File^?) ->? File^{id*} + |Required: File^ => File^{id*} + | + |where: => refers to a fresh root capability created in value leaked when checking argument to parameter f of method usingFile + | ^ refers to the universal root capability 71 | val f1: File^{id*} = id(f) 72 | f1 | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/reaches.scala:88:10 ----------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:88:10 -------------------------------------- 88 | ps.map((x, y) => compose1(x, y)) // error - | ^ - | Local reach capability ps* leaks into capture scope of method mapCompose. - | To allow this, the parameter ps should be declared with a @use annotation + | ^^^^^^^^^^^^^^^^^^^^^^^ + |Found: (x$1: (A^ ->? A^?, A^ ->? A^?)^?) ->? A^? ->? A^? + |Required: ((A ->{ps*} A, A ->{ps*} A)) => A^? ->? A^? + | + |where: => refers to a fresh root capability created in method mapCompose when checking argument to parameter f of method map + | ^ refers to the universal root capability + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:91:10 -------------------------------------- 91 | ps.map((x, y) => compose1(x, y)) // error | ^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (x$1: (box (x$0: A^) ->? A^?, box (x$0: A^) ->? A^?)^?) ->? box (x$0: A^?) ->? A^? - | Required: (x$1: (box A ->{ps*} A, box A ->{ps*} A)) ->{fresh} box (x$0: A^?) ->? A^? + |Found: (x$1: (A^ ->? A^?, A^ ->? A^?)^?) ->? A^? ->? A^? + |Required: ((A ->{ps*} A, A ->{ps*} A)) => A^? ->? A^? + | + |where: => refers to a fresh root capability created in method mapCompose2 when checking argument to parameter f of method map + | ^ refers to the universal root capability | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/reaches.scala:62:31 ----------------------------------------------------------- -62 | val leaked = usingFile[File^{id*}]: f => // error // error - | ^^^ - | id* cannot be tracked since its deep capture set is empty --- Error: tests/neg-custom-args/captures/reaches.scala:70:31 ----------------------------------------------------------- -70 | val leaked = usingFile[File^{id*}]: f => // error // error - | ^^^ - | id* cannot be tracked since its deep capture set is empty +-- Error: tests/neg-custom-args/captures/reaches.scala:39:31 ----------------------------------------------------------- +39 | val next: () => Unit = cur.head // error + | ^^^^^^^^ + | Local reach capability xs* leaks into capture scope of method runAll2. + | To allow this, the parameter xs should be declared with a @use annotation diff --git a/tests/neg-custom-args/captures/reaches.scala b/tests/neg-custom-args/captures/reaches.scala index 709f818a02a8..b811b1405590 100644 --- a/tests/neg-custom-args/captures/reaches.scala +++ b/tests/neg-custom-args/captures/reaches.scala @@ -34,9 +34,9 @@ def runAll1(@use xs: List[Proc]): Unit = (() => f.write()) :: Nil def runAll2(@consume xs: List[Proc]): Unit = - var cur: List[Proc] = xs // error + var cur: List[Proc] = xs while cur.nonEmpty do - val next: () => Unit = cur.head + val next: () => Unit = cur.head // error next() cur = cur.tail @@ -59,7 +59,7 @@ def attack2 = val id: File^ -> File^ = x => x // error // val id: File^ -> File^{fresh} - val leaked = usingFile[File^{id*}]: f => // error // error + val leaked = usingFile[File^{id*}]: f => // now ok val f1: File^{id*} = id(f) f1 @@ -67,7 +67,7 @@ def attack3 = val id: (x: File^) -> File^ = x => x // error // val id: File^ -> EX C.File^C - val leaked = usingFile[File^{id*}]: f => // error // error + val leaked = usingFile[File^{id*}]: f => // error val f1: File^{id*} = id(f) f1 diff --git a/tests/neg-custom-args/captures/reaches2.check b/tests/neg-custom-args/captures/reaches2.check index e9238365ec18..3ad78fd49eed 100644 --- a/tests/neg-custom-args/captures/reaches2.check +++ b/tests/neg-custom-args/captures/reaches2.check @@ -1,24 +1,7 @@ --- Error: tests/neg-custom-args/captures/reaches2.scala:10:10 ---------------------------------------------------------- -10 | ps.map((x, y) => compose1(x, y)) // error // error // error - | ^ - | Local reach capability ps* leaks into capture scope of method mapCompose. - | To allow this, the parameter ps should be declared with a @use annotation --- Error: tests/neg-custom-args/captures/reaches2.scala:10:13 ---------------------------------------------------------- -10 | ps.map((x, y) => compose1(x, y)) // error // error // error - | ^ - | Local reach capability ps* leaks into capture scope of method mapCompose. - | To allow this, the parameter ps should be declared with a @use annotation --- Error: tests/neg-custom-args/captures/reaches2.scala:10:28 ---------------------------------------------------------- -10 | ps.map((x, y) => compose1(x, y)) // error // error // error - | ^ - | Separation failure: argument of type A ->{x} A - | to method compose1: [A, B, C](f: A => B, g: B => C): A ->{f, g} C - | corresponds to capture-polymorphic formal parameter f of type box A^? => box A^? - | and hides capabilities {x}. - | Some of these overlap with the captures of the second argument with type A ->{y} A. +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches2.scala:10:10 ------------------------------------- +10 | ps.map((x, y) => compose1(x, y)) // error + | ^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (x$1: (A^? ->{ps*} A^?, A^? ->{ps*} A^?)^?) ->{ps*} A^? ->{ps*} A^? + | Required: ((A ->{ps*} A, A ->{ps*} A)) -> A^? ->? A^? | - | Hidden set of current argument : {x} - | Hidden footprint of current argument : {x, ps*} - | Capture set of second argument : {y} - | Footprint set of second argument : {y, ps*} - | The two sets overlap at : {ps*} + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/reaches2.scala b/tests/neg-custom-args/captures/reaches2.scala index 69ee3472cf86..1ba3dff04ba1 100644 --- a/tests/neg-custom-args/captures/reaches2.scala +++ b/tests/neg-custom-args/captures/reaches2.scala @@ -7,5 +7,5 @@ def compose1[A, B, C](f: A => B, g: B => C): A ->{f, g} C = z => g(f(z)) def mapCompose[A](ps: List[(A => A, A => A)]): List[A ->{ps*} A] = - ps.map((x, y) => compose1(x, y)) // error // error // error + ps.map((x, y) => compose1(x, y)) // error diff --git a/tests/neg-custom-args/captures/readOnly.scala b/tests/neg-custom-args/captures/readOnly.scala index 4edea6638980..1c8a2bbc351e 100644 --- a/tests/neg-custom-args/captures/readOnly.scala +++ b/tests/neg-custom-args/captures/readOnly.scala @@ -4,7 +4,7 @@ import caps.cap class Ref(init: Int) extends Mutable: private var current = init def get: Int = current - mut def put(x: Int): Unit = current = x + update def put(x: Int): Unit = current = x def Test(c: Object^) = val a: Ref^ = Ref(1) diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index 6b478b48515a..c68e82514f24 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -10,6 +10,8 @@ | The result of `try` cannot have type () ->{cap.rd} Unit since | that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. + | + | where: cap is a fresh root capability classified as Control in the type of given instance canThrow$1 15 | () => foo(1) 16 | catch 17 | case _: Ex1 => ??? @@ -17,9 +19,11 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:20:10 ---------------------------------------------------------- 20 | val x = try // error | ^ - | The result of `try` cannot have type () ->{cap.rd} Unit since - | that type captures the root capability `cap`. - | This is often caused by a locally generated exception capability leaking as part of its result. + | The result of `try` cannot have type () ->{cap.rd} Unit since + | that type captures the root capability `cap`. + | This is often caused by a locally generated exception capability leaking as part of its result. + | + | where: cap is a fresh root capability classified as Control in the type of given instance canThrow$2 21 | () => foo(1) 22 | catch 23 | case _: Ex1 => ??? @@ -27,9 +31,11 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:26:10 ---------------------------------------------------------- 26 | val y = try // error | ^ - | The result of `try` cannot have type () ->{cap.rd} Cell[Unit]^? since - | that type captures the root capability `cap`. - | This is often caused by a locally generated exception capability leaking as part of its result. + | The result of `try` cannot have type () ->{cap.rd} Cell[Unit]^? since + | that type captures the root capability `cap`. + | This is often caused by a locally generated exception capability leaking as part of its result. + | + | where: cap is a fresh root capability classified as Control in the type of given instance canThrow$3 27 | () => Cell(foo(1)) 28 | catch 29 | case _: Ex1 => ??? @@ -37,9 +43,11 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:32:10 ---------------------------------------------------------- 32 | val b = try // error | ^ - | The result of `try` cannot have type Cell[box () ->{cap.rd} Unit]^? since - | the part box () ->{cap.rd} Unit of that type captures the root capability `cap`. - | This is often caused by a locally generated exception capability leaking as part of its result. + | The result of `try` cannot have type Cell[() ->{cap.rd} Unit]^? since + | the part () ->{cap.rd} Unit of that type captures the root capability `cap`. + | This is often caused by a locally generated exception capability leaking as part of its result. + | + | where: cap is a fresh root capability classified as Control in the type of given instance canThrow$4 33 | Cell(() => foo(1)) 34 | catch 35 | case _: Ex1 => ??? diff --git a/tests/neg-custom-args/captures/refine-reach-shallow.scala b/tests/neg-custom-args/captures/refine-reach-shallow.scala index e92e2fd960a0..dda865a85891 100644 --- a/tests/neg-custom-args/captures/refine-reach-shallow.scala +++ b/tests/neg-custom-args/captures/refine-reach-shallow.scala @@ -2,7 +2,7 @@ import language.experimental.captureChecking trait IO def test1(): Unit = val f: IO^ => IO^ = x => x // error - val g: IO^ => IO^{f*} = f // error + val g: IO^ => IO^{f*} = f // now OK def test2(): Unit = val f: [R] -> (IO^ => R) -> R = ??? val ff = f @@ -15,5 +15,5 @@ def test4(): Unit = val ys: List[IO^{xs*}] = xs // ok def test5(): Unit = val f: [R] -> (IO^ -> R) -> IO^ = ??? - val g: [R] -> (IO^ -> R) -> IO^{f*} = f // error // error - val h: [R] -> (IO^{f*} -> R) -> IO^ = f // error // error + val g: [R] -> (IO^ -> R) -> IO^{f*} = f // error + val h: [R] -> (IO^{f*} -> R) -> IO^ = f // error diff --git a/tests/neg-custom-args/captures/restrict-subsumes.scala b/tests/neg-custom-args/captures/restrict-subsumes.scala new file mode 100644 index 000000000000..8bd17f7330a6 --- /dev/null +++ b/tests/neg-custom-args/captures/restrict-subsumes.scala @@ -0,0 +1,17 @@ +import caps.{cap, Classifier, Capability} + +trait Read extends Capability, Classifier +trait Write extends Capability, Classifier + +trait A extends Read +trait B extends Write + +def weird(f: () ->{cap.only[Read]} Unit) = ??? + +def test(x: A^, y: B^) = + val g = () => println(x) + weird(g) // ok + val h = () => println(y) + weird(h) // error + val k = () => { println(x); println(y) } + weird(k) // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/ro-mut-conformance.check b/tests/neg-custom-args/captures/ro-mut-conformance.check new file mode 100644 index 000000000000..9ac0fe2be51e --- /dev/null +++ b/tests/neg-custom-args/captures/ro-mut-conformance.check @@ -0,0 +1,17 @@ +-- Error: tests/neg-custom-args/captures/ro-mut-conformance.scala:10:4 ------------------------------------------------- +10 | a.set(42) // error + | ^^^^^ + | cannot call update method set from (a : Ref), + | since its capture set {a} is read-only +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/ro-mut-conformance.scala:12:21 --------------------------- +12 | val t: Ref^{cap} = a // error + | ^ + | Found: (a : Ref) + | Required: Ref^ + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of value t + | + | Note that {cap} is an exclusive capture set of the mutable type Ref^, + | it cannot subsume a read-only capture set of the mutable type (a : Ref). + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/ro-mut-conformance.scala b/tests/neg-custom-args/captures/ro-mut-conformance.scala new file mode 100644 index 000000000000..4b3f34af47f3 --- /dev/null +++ b/tests/neg-custom-args/captures/ro-mut-conformance.scala @@ -0,0 +1,15 @@ +import language.experimental.captureChecking +import caps.* +trait IRef: + def get: Int +class Ref extends IRef, Mutable: + private var _data: Int = 0 + def get: Int = _data + update def set(x: Int): Unit = _data = x +def test1(a: Ref^{cap.rd}): Unit = + a.set(42) // error +def test2(a: Ref^{cap.rd}): Unit = + val t: Ref^{cap} = a // error + def b: Ref^{cap.rd} = ??? + val i: IRef^{cap} = b // ok, no added privilege from `cap` on an IRef + t.set(42) \ No newline at end of file diff --git a/tests/neg-custom-args/captures/scope-extrude-mut.check b/tests/neg-custom-args/captures/scope-extrude-mut.check new file mode 100644 index 000000000000..fb917d7ceeae --- /dev/null +++ b/tests/neg-custom-args/captures/scope-extrude-mut.check @@ -0,0 +1,10 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrude-mut.scala:9:8 ------------------------------ +9 | a = a1 // error + | ^^ + |Found: (a1 : A^) + |Required: A^² + | + |where: ^ refers to a fresh root capability classified as Mutable created in value a1 when constructing mutable A + | ^² refers to a fresh root capability classified as Mutable in the type of variable a + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/scope-extrude-mut.scala b/tests/neg-custom-args/captures/scope-extrude-mut.scala new file mode 100644 index 000000000000..75ff0e11abcb --- /dev/null +++ b/tests/neg-custom-args/captures/scope-extrude-mut.scala @@ -0,0 +1,9 @@ +import language.experimental.captureChecking + +class A extends caps.Mutable + +class B: + private var a: A^ = A() + def b() = + val a1 = A() + a = a1 // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/scoped-caps.check b/tests/neg-custom-args/captures/scoped-caps.check new file mode 100644 index 000000000000..65d9865a393c --- /dev/null +++ b/tests/neg-custom-args/captures/scoped-caps.check @@ -0,0 +1,95 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps.scala:7:20 ----------------------------------- +7 | val g: A^ -> B^ = f // error + | ^ + | Found: (f : (x: A^) -> B^²) + | Required: A^ -> B^³ + | + | where: ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: A^): B^² + | ^³ refers to a fresh root capability in the type of value g + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps.scala:9:25 ----------------------------------- +9 | val _: (x: A^) -> B^ = g // error + | ^ + | Found: A^ -> B^{g*} + | Required: (x: A^) -> B^² + | + | where: ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: A^): B^² + | + | Note that the existential capture root in B^ + | cannot subsume the capability g* since that capability is not a `Sharable` capability + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps.scala:10:20 ---------------------------------- +10 | val _: A^ -> B^ = f // error + | ^ + | Found: (f : (x: A^) -> B^²) + | Required: A^ -> B^³ + | + | where: ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: A^): B^² + | ^³ refers to a fresh root capability in the type of value _$3 + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps.scala:13:25 ---------------------------------- +13 | val _: (x: A^) -> B^ = x => f(x) // error: existential in B cannot subsume `x` since `x` is not shared + | ^^^^^^^^^ + | Found: (x: A^) ->? B^{x} + | Required: (x: A^) -> B^² + | + | where: ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: A^): B^² + | + | Note that the existential capture root in B^ + | cannot subsume the capability x.type since that capability is not a `Sharable` capability + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps.scala:16:24 ---------------------------------- +16 | val _: (x: S) -> B^ = h // error: direct conversion fails + | ^ + | Found: S^{cap.rd} -> B^{h*} + | Required: (x: S^{cap.rd}) -> B^ + | + | where: ^ refers to a root capability associated with the result type of (x: S^{cap.rd}): B^ + | cap is the universal root capability + | + | Note that the existential capture root in B^ + | cannot subsume the capability h* since that capability is not a `Sharable` capability + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps.scala:17:24 ---------------------------------- +17 | val _: (x: S) -> B^ = (x: S) => h(x) // error: eta expansion fails + | ^^^^^^^^^^^^^^ + | Found: (x: S^{cap.rd}) ->? B^{h*} + | Required: (x: S^{cap.rd}) -> B^ + | + | where: ^ refers to a root capability associated with the result type of (x: S^{cap.rd}): B^ + | cap is the universal root capability + | + | Note that the existential capture root in B^ + | cannot subsume the capability h* since that capability is not a `Sharable` capability + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps.scala:26:19 ---------------------------------- +26 | val _: S -> B^ = j // error + | ^ + | Found: (j : (x: S) -> B^) + | Required: S^{cap.rd} -> B^² + | + | where: ^ refers to a root capability associated with the result type of (x: S^{cap.rd}): B^ + | ^² refers to a fresh root capability in the type of value _$13 + | cap is the universal root capability + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps.scala:27:19 ---------------------------------- +27 | val _: S -> B^ = x => j(x) // error + | ^^^^^^^^^ + | Found: (x: S^{cap.rd}) ->? B^{x} + | Required: S^{cap.rd} -> B^ + | + | where: ^ refers to a fresh root capability in the type of value _$14 + | cap is the universal root capability + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/scoped-caps.scala b/tests/neg-custom-args/captures/scoped-caps.scala index b4705b3d6a62..9d11f9f00b7e 100644 --- a/tests/neg-custom-args/captures/scoped-caps.scala +++ b/tests/neg-custom-args/captures/scoped-caps.scala @@ -1,6 +1,6 @@ class A class B -class S extends caps.SharedCapability +class S extends caps.Sharable def test(io: Object^): Unit = val f: (x: A^) -> B^ = ??? @@ -9,12 +9,16 @@ def test(io: Object^): Unit = val _: (x: A^) -> B^ = g // error val _: A^ -> B^ = f // error val _: A^ -> B^ = g - val _: A^ -> B^ = x => g(x) // error, since g is pure, g(x): B^{x} , which does not match B^{fresh} + val _: A^ -> B^ = x => g(x) // now ok, was error: since g is pure, g(x): B^{x} , which does not match B^{fresh} val _: (x: A^) -> B^ = x => f(x) // error: existential in B cannot subsume `x` since `x` is not shared val h: S -> B^ = ??? val _: (x: S) -> B^ = h // error: direct conversion fails - val _: (x: S) -> B^ = x => h(x) // but eta expansion succeeds (for SharedCapabilities) + val _: (x: S) -> B^ = (x: S) => h(x) // error: eta expansion fails + + val h2: S -> S = ??? + val _: (x: S) -> S = h2 // direct conversion OK for shared S + val _: (x: S) -> S = (x: S) => h2(x) // eta conversion is also OK val j: (x: S) -> B^ = ??? val _: (x: S) -> B^ = j @@ -23,6 +27,9 @@ def test(io: Object^): Unit = val _: S -> B^ = x => j(x) // error val g2: A^ => B^ = ??? - val _: A^ => B^ = x => g2(x) // error: g2(x): B^{g2, x}, and the `x` cannot be subsumed by fresh + val _: A^ => B^ = x => + val y = g2(x) // now ok since g2(x): B^{g2*} + val _: B^{g2*} = y + y val g3: A^ => B^ = ??? - val _: A^{io} => B^ = x => g3(x) // ok, now g3(x): B^{g3, x}, which is widened to B^{g3, io} + val _: A^{io} => B^ = x => g3(x) // now g3(x): B^{g3*} diff --git a/tests/neg-custom-args/captures/sep-box.check b/tests/neg-custom-args/captures/sep-box.check index f60f09d906a8..a0fe7340b93b 100644 --- a/tests/neg-custom-args/captures/sep-box.check +++ b/tests/neg-custom-args/captures/sep-box.check @@ -1,14 +1,16 @@ -- Error: tests/neg-custom-args/captures/sep-box.scala:41:9 ------------------------------------------------------------ 41 | par(h1.value, h2.value) // error | ^^^^^^^^ - | Separation failure: argument of type Ref^{h1.value*} - | to method par: (x: Ref^, y: Ref^): Unit - | corresponds to capture-polymorphic formal parameter x of type Ref^ - | and hides capabilities {h1.value*}. - | Some of these overlap with the captures of the second argument with type Ref^{h2.value*}. + |Separation failure: argument of type Ref^{h1.value*} + |to method par: (x: Ref^, y: Ref^): Unit + |corresponds to capture-polymorphic formal parameter x of type Ref^ + |and hides capabilities {h1.value*}. + |Some of these overlap with the captures of the second argument with type Ref^{h2.value*}. | - | Hidden set of current argument : {h1.value*} - | Hidden footprint of current argument : {h1.value*, xs*} - | Capture set of second argument : {h2.value*} - | Footprint set of second argument : {h2.value*, xs*} - | The two sets overlap at : {xs*} + | Hidden set of current argument : {h1.value*} + | Hidden footprint of current argument : {h1.value*, xs*} + | Capture set of second argument : {h2.value*} + | Footprint set of second argument : {h2.value*, xs*} + | The two sets overlap at : {xs*} + | + |where: ^ refers to a fresh root capability classified as Mutable created in method test when checking argument to parameter x of method par diff --git a/tests/neg-custom-args/captures/sep-box.scala b/tests/neg-custom-args/captures/sep-box.scala index fd9348acc893..218505b8c2ca 100644 --- a/tests/neg-custom-args/captures/sep-box.scala +++ b/tests/neg-custom-args/captures/sep-box.scala @@ -21,7 +21,7 @@ object NIL extends LIST[Nothing]: class Ref extends Mutable: var x = 0 def get: Int = x - mut def put(y: Int): Unit = x = y + update def put(y: Int): Unit = x = y class Box[+X](val value: X) diff --git a/tests/neg-custom-args/captures/sep-compose.check b/tests/neg-custom-args/captures/sep-compose.check index 3bd1e5b08eeb..b53bc3036d4e 100644 --- a/tests/neg-custom-args/captures/sep-compose.check +++ b/tests/neg-custom-args/captures/sep-compose.check @@ -1,112 +1,128 @@ -- Error: tests/neg-custom-args/captures/sep-compose.scala:32:7 -------------------------------------------------------- 32 | seq3(f)(f) // error | ^ - | Separation failure: argument of type (f : () ->{a} Unit) - | to method seq3: (x: () => Unit)(y: () ->{a, cap} Unit): Unit - | corresponds to capture-polymorphic formal parameter x of type () => Unit - | and hides capabilities {f}. - | Some of these overlap with the captures of the second argument with type (f : () ->{a} Unit). - | - | Hidden set of current argument : {f} - | Hidden footprint of current argument : {f, a, io} - | Capture set of second argument : {f} - | Footprint set of second argument : {f, a, io} - | The two sets overlap at : {f, a, io} + |Separation failure: argument of type (f : () ->{a} Unit) + |to method seq3: (x: () => Unit)(y: () ->{a, cap} Unit): Unit + |corresponds to capture-polymorphic formal parameter x of type () => Unit + |and hides capabilities {f}. + |Some of these overlap with the captures of the second argument with type (f : () ->{a} Unit). + | + | Hidden set of current argument : {f} + | Hidden footprint of current argument : {f, a, io} + | Capture set of second argument : {f} + | Footprint set of second argument : {f, a, io} + | The two sets overlap at : {f, a, io} + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter x of method seq3 -- Error: tests/neg-custom-args/captures/sep-compose.scala:33:10 ------------------------------------------------------- 33 | seq4(f)(f) // error | ^ - | Separation failure: argument of type (f : () ->{a} Unit) - | to method seq4: (x: () ->{a, cap} Unit)(y: () => Unit): Unit - | corresponds to capture-polymorphic formal parameter y of type () => Unit - | and hides capabilities {f}. - | Some of these overlap with the captures of the first argument with type (f : () ->{a} Unit). - | - | Hidden set of current argument : {f} - | Hidden footprint of current argument : {f, a, io} - | Capture set of first argument : {f} - | Footprint set of first argument : {f, a, io} - | The two sets overlap at : {f, a, io} + |Separation failure: argument of type (f : () ->{a} Unit) + |to method seq4: (x: () ->{a, cap} Unit)(y: () => Unit): Unit + |corresponds to capture-polymorphic formal parameter y of type () => Unit + |and hides capabilities {f}. + |Some of these overlap with the captures of the first argument with type (f : () ->{a} Unit). + | + | Hidden set of current argument : {f} + | Hidden footprint of current argument : {f, a, io} + | Capture set of first argument : {f} + | Footprint set of first argument : {f, a, io} + | The two sets overlap at : {f, a, io} + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter y of method seq4 -- Error: tests/neg-custom-args/captures/sep-compose.scala:34:7 -------------------------------------------------------- 34 | seq5(f)(f) // error | ^ - | Separation failure: argument of type (f : () ->{a} Unit) - | to method seq5: (x: () => Unit)(y: () => Unit): Unit - | corresponds to capture-polymorphic formal parameter x of type () => Unit - | and hides capabilities {f}. - | Some of these overlap with the captures of the second argument with type (f : () ->{a} Unit). - | - | Hidden set of current argument : {f} - | Hidden footprint of current argument : {f, a, io} - | Capture set of second argument : {f} - | Footprint set of second argument : {f, a, io} - | The two sets overlap at : {f, a, io} + |Separation failure: argument of type (f : () ->{a} Unit) + |to method seq5: (x: () => Unit)(y: () => Unit): Unit + |corresponds to capture-polymorphic formal parameter x of type () => Unit + |and hides capabilities {f}. + |Some of these overlap with the captures of the second argument with type (f : () ->{a} Unit). + | + | Hidden set of current argument : {f} + | Hidden footprint of current argument : {f, a, io} + | Capture set of second argument : {f} + | Footprint set of second argument : {f, a, io} + | The two sets overlap at : {f, a, io} + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter x of method seq5 -- Error: tests/neg-custom-args/captures/sep-compose.scala:35:7 -------------------------------------------------------- 35 | seq6(f, f) // error | ^ - | Separation failure: argument of type (f : () ->{a} Unit) - | to method seq6: (x: () => Unit, y: () ->{a, cap} Unit): Unit - | corresponds to capture-polymorphic formal parameter x of type () => Unit - | and hides capabilities {f}. - | Some of these overlap with the captures of the second argument with type (f : () ->{a} Unit). - | - | Hidden set of current argument : {f} - | Hidden footprint of current argument : {f, a, io} - | Capture set of second argument : {f} - | Footprint set of second argument : {f, a, io} - | The two sets overlap at : {f, a, io} + |Separation failure: argument of type (f : () ->{a} Unit) + |to method seq6: (x: () => Unit, y: () ->{a, cap} Unit): Unit + |corresponds to capture-polymorphic formal parameter x of type () => Unit + |and hides capabilities {f}. + |Some of these overlap with the captures of the second argument with type (f : () ->{a} Unit). + | + | Hidden set of current argument : {f} + | Hidden footprint of current argument : {f, a, io} + | Capture set of second argument : {f} + | Footprint set of second argument : {f, a, io} + | The two sets overlap at : {f, a, io} + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter x of method seq6 -- Error: tests/neg-custom-args/captures/sep-compose.scala:36:10 ------------------------------------------------------- 36 | seq7(f, f) // error | ^ - | Separation failure: argument of type (f : () ->{a} Unit) - | to method seq7: (x: () ->{a, cap} Unit, y: () => Unit): Unit - | corresponds to capture-polymorphic formal parameter y of type () => Unit - | and hides capabilities {f}. - | Some of these overlap with the captures of the first argument with type (f : () ->{a} Unit). - | - | Hidden set of current argument : {f} - | Hidden footprint of current argument : {f, a, io} - | Capture set of first argument : {f} - | Footprint set of first argument : {f, a, io} - | The two sets overlap at : {f, a, io} + |Separation failure: argument of type (f : () ->{a} Unit) + |to method seq7: (x: () ->{a, cap} Unit, y: () => Unit): Unit + |corresponds to capture-polymorphic formal parameter y of type () => Unit + |and hides capabilities {f}. + |Some of these overlap with the captures of the first argument with type (f : () ->{a} Unit). + | + | Hidden set of current argument : {f} + | Hidden footprint of current argument : {f, a, io} + | Capture set of first argument : {f} + | Footprint set of first argument : {f, a, io} + | The two sets overlap at : {f, a, io} + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter y of method seq7 -- Error: tests/neg-custom-args/captures/sep-compose.scala:37:7 -------------------------------------------------------- 37 | seq8(f)(f) // error | ^ - | Separation failure: argument of type (f : () ->{a} Unit) - | to method seq8: (x: () => Unit)(y: () ->{a} Unit): Unit - | corresponds to capture-polymorphic formal parameter x of type () => Unit - | and hides capabilities {f}. - | Some of these overlap with the captures of the second argument with type (f : () ->{a} Unit). - | - | Hidden set of current argument : {f} - | Hidden footprint of current argument : {f, a, io} - | Capture set of second argument : {f} - | Footprint set of second argument : {f, a, io} - | The two sets overlap at : {f, a, io} + |Separation failure: argument of type (f : () ->{a} Unit) + |to method seq8: (x: () => Unit)(y: () ->{a} Unit): Unit + |corresponds to capture-polymorphic formal parameter x of type () => Unit + |and hides capabilities {f}. + |Some of these overlap with the captures of the second argument with type (f : () ->{a} Unit). + | + | Hidden set of current argument : {f} + | Hidden footprint of current argument : {f, a, io} + | Capture set of second argument : {f} + | Footprint set of second argument : {f, a, io} + | The two sets overlap at : {f, a, io} + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter x of method seq8 -- Error: tests/neg-custom-args/captures/sep-compose.scala:40:5 -------------------------------------------------------- 40 | p1(f) // error | ^ - | Separation failure: argument of type (f : () ->{a} Unit) - | to a function of type (() => Unit) ->{f} Unit - | corresponds to capture-polymorphic formal parameter v1 of type () => Unit - | and hides capabilities {f}. - | Some of these overlap with the captures of the function prefix. - | - | Hidden set of current argument : {f} - | Hidden footprint of current argument : {f, a, io} - | Capture set of function prefix : {p1} - | Footprint set of function prefix : {p1, f, a, io} - | The two sets overlap at : {f, a, io} + |Separation failure: argument of type (f : () ->{a} Unit) + |to a function of type (() => Unit) ->{f} Unit + |corresponds to capture-polymorphic formal parameter v1 of type () => Unit + |and hides capabilities {f}. + |Some of these overlap with the captures of the function prefix. + | + | Hidden set of current argument : {f} + | Hidden footprint of current argument : {f, a, io} + | Capture set of function prefix : {p1} + | Footprint set of function prefix : {p1, f, a, io} + | The two sets overlap at : {f, a, io} + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter v1 of method apply -- Error: tests/neg-custom-args/captures/sep-compose.scala:41:38 ------------------------------------------------------- 41 | val p8 = (x: () ->{a} Unit) => seq8(f)(x) // error | ^ - | Separation failure: argument of type (f : () ->{a} Unit) - | to method seq8: (x: () => Unit)(y: () ->{a} Unit): Unit - | corresponds to capture-polymorphic formal parameter x of type () => Unit - | and hides capabilities {f}. - | Some of these overlap with the captures of the second argument with type (x : () ->{a} Unit). - | - | Hidden set of current argument : {f} - | Hidden footprint of current argument : {f, a, io} - | Capture set of second argument : {x} - | Footprint set of second argument : {x, a, io} - | The two sets overlap at : {a, io} + |Separation failure: argument of type (f : () ->{a} Unit) + |to method seq8: (x: () => Unit)(y: () ->{a} Unit): Unit + |corresponds to capture-polymorphic formal parameter x of type () => Unit + |and hides capabilities {f}. + |Some of these overlap with the captures of the second argument with type (x : () ->{a} Unit). + | + | Hidden set of current argument : {f} + | Hidden footprint of current argument : {f, a, io} + | Capture set of second argument : {x} + | Footprint set of second argument : {x, a, io} + | The two sets overlap at : {a, io} + | + |where: => refers to a fresh root capability created in anonymous function of type (x²: () ->{a} Unit): Unit when checking argument to parameter x of method seq8 diff --git a/tests/neg-custom-args/captures/sep-consume.check b/tests/neg-custom-args/captures/sep-consume.check index d8ee2c5ed43e..9aeaf0a59cc5 100644 --- a/tests/neg-custom-args/captures/sep-consume.check +++ b/tests/neg-custom-args/captures/sep-consume.check @@ -4,12 +4,16 @@ | Separation failure: Illegal access to (x : Ref^), which was passed to a | @consume parameter or was used as a prefix to a @consume method on line 18 | and therefore is no longer available. + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of parameter x -- Error: tests/neg-custom-args/captures/sep-consume.scala:21:16 ------------------------------------------------------- 21 | par(rx, () => x.put(42)) // error | ^ | Separation failure: Illegal access to (x : Ref^), which was passed to a | @consume parameter or was used as a prefix to a @consume method on line 18 | and therefore is no longer available. + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of parameter x -- Error: tests/neg-custom-args/captures/sep-consume.scala:26:16 ------------------------------------------------------- 26 | def foo = bad(f) // error | ^ diff --git a/tests/neg-custom-args/captures/sep-consume.scala b/tests/neg-custom-args/captures/sep-consume.scala index 46c45e8cc6f5..3d167e0674d9 100644 --- a/tests/neg-custom-args/captures/sep-consume.scala +++ b/tests/neg-custom-args/captures/sep-consume.scala @@ -4,7 +4,7 @@ import caps.* class Ref extends Mutable: private var _data = 0 def get: Int = _data - mut def put(x: Int): Unit = _data = x + update def put(x: Int): Unit = _data = x case class Pair[+A, +B](fst: A, snd: B) diff --git a/tests/neg-custom-args/captures/sep-counter.check b/tests/neg-custom-args/captures/sep-counter.check index 230c1e6f6e53..f46d3fa03dfc 100644 --- a/tests/neg-custom-args/captures/sep-counter.check +++ b/tests/neg-custom-args/captures/sep-counter.check @@ -1,7 +1,11 @@ -- Error: tests/neg-custom-args/captures/sep-counter.scala:12:19 ------------------------------------------------------- 12 | def mkCounter(): Pair[Ref^, Ref^] = // error | ^^^^^^^^^^^^^^^^ - | Separation failure in method mkCounter's result type Pair[box Ref^, box Ref^]. - | One part, box Ref^, hides capabilities {cap}. - | Another part, box Ref^, captures capabilities {cap}. - | The two sets overlap at cap of value c. + | Separation failure in method mkCounter's result type Pair[Ref^, Ref^²]. + | One part, Ref^, hides capabilities {cap}. + | Another part, Ref^², captures capabilities {cap}. + | The two sets overlap at cap of method mkCounter. + | + | where: ^ refers to a fresh root capability classified as Mutable in the result type of method mkCounter + | ^² refers to a fresh root capability classified as Mutable in the result type of method mkCounter + | cap is a fresh root capability classified as Mutable created in value c when constructing mutable Ref diff --git a/tests/neg-custom-args/captures/sep-counter.scala b/tests/neg-custom-args/captures/sep-counter.scala index 42bf5afc172a..a2df6cd0e62d 100644 --- a/tests/neg-custom-args/captures/sep-counter.scala +++ b/tests/neg-custom-args/captures/sep-counter.scala @@ -4,7 +4,7 @@ import caps.cap class Ref extends Mutable: var x = 0 def get: Int = x - mut def put(y: Int): Unit = x = y + update def put(y: Int): Unit = x = y class Pair[+X, +Y](val fst: X, val snd: Y) diff --git a/tests/neg-custom-args/captures/sep-curried-redux.scala b/tests/neg-custom-args/captures/sep-curried-redux.scala new file mode 100644 index 000000000000..a34164b106f1 --- /dev/null +++ b/tests/neg-custom-args/captures/sep-curried-redux.scala @@ -0,0 +1,18 @@ +import language.experimental.captureChecking +import caps.* + +class Ref[T](init: T) extends Mutable: + private var value: T = init + def get: T = value + update def set(newValue: T): Unit = value = newValue + +// a library function that assumes that a and b MUST BE separate +def swap[T](a: Ref[Int]^, b: Ref[Int]^): Unit = ??? + +def test4(): Unit = + val a: Ref[Int]^ = Ref(0) + val foo: (x: Ref[Int]^) -> (y: Ref[Int]^) ->{x} Unit = + x => y => swap(x, y) + val f = foo(a) + f(a) // error + diff --git a/tests/neg-custom-args/captures/sep-curried.check b/tests/neg-custom-args/captures/sep-curried.check index edf359c4be14..04ca5644bc33 100644 --- a/tests/neg-custom-args/captures/sep-curried.check +++ b/tests/neg-custom-args/captures/sep-curried.check @@ -4,74 +4,91 @@ | Found: (y: Ref[Int]^) ->{a} Unit | Required: (y: Ref[Int]^{a}) ->{a} Unit | + | where: ^ refers to the universal root capability + | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/sep-curried.scala:16:6 -------------------------------------------------------- 16 | foo(a)(a) // error | ^ - | Separation failure: argument of type (a : Ref[Int]^) - | to method foo: (x: Ref[Int]^)(y: Ref[Int]^{a}): Unit - | corresponds to capture-polymorphic formal parameter x of type Ref[Int]^ - | and hides capabilities {a}. - | Some of these overlap with the captures of the second argument with type (a : Ref[Int]^). + |Separation failure: argument of type (a : Ref[Int]^) + |to method foo: (x: Ref[Int]^)(y: Ref[Int]^{a}): Unit + |corresponds to capture-polymorphic formal parameter x of type Ref[Int]^² + |and hides capabilities {a}. + |Some of these overlap with the captures of the second argument with type (a : Ref[Int]^). + | + | Hidden set of current argument : {a} + | Hidden footprint of current argument : {a} + | Capture set of second argument : {a} + | Footprint set of second argument : {a} + | The two sets overlap at : {a} | - | Hidden set of current argument : {a} - | Hidden footprint of current argument : {a} - | Capture set of second argument : {a} - | Footprint set of second argument : {a} - | The two sets overlap at : {a} + |where: ^ refers to a fresh root capability classified as Mutable in the type of value a + | ^² refers to a fresh root capability classified as Mutable created in method test0 when checking argument to parameter x of method foo -- Error: tests/neg-custom-args/captures/sep-curried.scala:22:44 ------------------------------------------------------- 22 | val f: (y: Ref[Int]^{a}) ->{a} Unit = foo(a) // error | ^ - | Separation failure: argument of type (a : Ref[Int]^) - | to a function of type (x: Ref[Int]^) -> (y: Ref[Int]^{a}) ->{x} Unit - | corresponds to capture-polymorphic formal parameter x of type Ref[Int]^ - | and hides capabilities {a}. - | Some of these overlap with the captures of the function result with type (y: Ref[Int]^{a}) ->{a} Unit. + |Separation failure: argument of type (a : Ref[Int]^) + |to a function of type (x: Ref[Int]^) -> (y: Ref[Int]^{a}) ->{x} Unit + |corresponds to capture-polymorphic formal parameter x of type Ref[Int]^² + |and hides capabilities {a}. + |Some of these overlap with the captures of the function result with type (y: Ref[Int]^{a}) ->{a} Unit. + | + | Hidden set of current argument : {a} + | Hidden footprint of current argument : {a} + | Capture set of function result : {a} + | Footprint set of function result : {a} + | The two sets overlap at : {a} | - | Hidden set of current argument : {a} - | Hidden footprint of current argument : {a} - | Capture set of function result : {a} - | Footprint set of function result : {a} - | The two sets overlap at : {a} + |where: ^ refers to a fresh root capability classified as Mutable in the type of value a + | ^² refers to a fresh root capability classified as Mutable created in value f when checking argument to parameter x of method apply -- Error: tests/neg-custom-args/captures/sep-curried.scala:29:6 -------------------------------------------------------- 29 | foo(a)(a) // error | ^ - | Separation failure: argument of type (a : Ref[Int]^) - | to a function of type (x: Ref[Int]^) -> (y: Ref[Int]^{a}) ->{x} Unit - | corresponds to capture-polymorphic formal parameter x of type Ref[Int]^ - | and hides capabilities {a}. - | Some of these overlap with the captures of the function result with type (y: Ref[Int]^{a}) ->{a} Unit. + |Separation failure: argument of type (a : Ref[Int]^) + |to a function of type (x: Ref[Int]^) -> (y: Ref[Int]^{a}) ->{x} Unit + |corresponds to capture-polymorphic formal parameter x of type Ref[Int]^² + |and hides capabilities {a}. + |Some of these overlap with the captures of the function result with type (y: Ref[Int]^{a}) ->{a} Unit. | - | Hidden set of current argument : {a} - | Hidden footprint of current argument : {a} - | Capture set of function result : {a} - | Footprint set of function result : {a} - | The two sets overlap at : {a} + | Hidden set of current argument : {a} + | Hidden footprint of current argument : {a} + | Capture set of function result : {a} + | Footprint set of function result : {a} + | The two sets overlap at : {a} + | + |where: ^ refers to a fresh root capability classified as Mutable in the type of value a + | ^² refers to a fresh root capability classified as Mutable created in method test2 when checking argument to parameter x of method apply -- Error: tests/neg-custom-args/captures/sep-curried.scala:35:9 -------------------------------------------------------- 35 | foo(a)(a) // error | ^ - | Separation failure: argument of type (a : Ref[Int]^) - | to a function of type (y: Ref[Int]^) ->{a} Unit - | corresponds to capture-polymorphic formal parameter y of type Ref[Int]^ - | and hides capabilities {a}. - | Some of these overlap with the captures of the function prefix. + |Separation failure: argument of type (a : Ref[Int]^) + |to a function of type (y: Ref[Int]^) ->{a} Unit + |corresponds to capture-polymorphic formal parameter y of type Ref[Int]^² + |and hides capabilities {a}. + |Some of these overlap with the captures of the function prefix. + | + | Hidden set of current argument : {a} + | Hidden footprint of current argument : {a} + | Capture set of function prefix : {a} + | Footprint set of function prefix : {a} + | The two sets overlap at : {a} | - | Hidden set of current argument : {a} - | Hidden footprint of current argument : {a} - | Capture set of function prefix : {a} - | Footprint set of function prefix : {a} - | The two sets overlap at : {a} + |where: ^ refers to a fresh root capability classified as Mutable in the type of value a + | ^² refers to a fresh root capability classified as Mutable created in method test3 when checking argument to parameter y of method apply -- Error: tests/neg-custom-args/captures/sep-curried.scala:42:4 -------------------------------------------------------- 42 | f(a) // error | ^ - | Separation failure: argument of type (a : Ref[Int]^) - | to a function of type (y: Ref[Int]^) ->{a} Unit - | corresponds to capture-polymorphic formal parameter y of type Ref[Int]^ - | and hides capabilities {a}. - | Some of these overlap with the captures of the function prefix. + |Separation failure: argument of type (a : Ref[Int]^) + |to a function of type (y: Ref[Int]^) ->{a} Unit + |corresponds to capture-polymorphic formal parameter y of type Ref[Int]^² + |and hides capabilities {a}. + |Some of these overlap with the captures of the function prefix. + | + | Hidden set of current argument : {a} + | Hidden footprint of current argument : {a} + | Capture set of function prefix : {f} + | Footprint set of function prefix : {f, a} + | The two sets overlap at : {a} | - | Hidden set of current argument : {a} - | Hidden footprint of current argument : {a} - | Capture set of function prefix : {f} - | Footprint set of function prefix : {f, a} - | The two sets overlap at : {a} + |where: ^ refers to a fresh root capability classified as Mutable in the type of value a + | ^² refers to a fresh root capability classified as Mutable created in method test4 when checking argument to parameter y of method apply diff --git a/tests/neg-custom-args/captures/sep-curried.scala b/tests/neg-custom-args/captures/sep-curried.scala index 13a07ad6cca7..2ae424aed9e3 100644 --- a/tests/neg-custom-args/captures/sep-curried.scala +++ b/tests/neg-custom-args/captures/sep-curried.scala @@ -4,7 +4,7 @@ import caps.* class Ref[T](init: T) extends Mutable: private var value: T = init def get: T = value - mut def set(newValue: T): Unit = value = newValue + update def set(newValue: T): Unit = value = newValue // a library function that assumes that a and b MUST BE separate def swap[T](a: Ref[Int]^, b: Ref[Int]^): Unit = ??? diff --git a/tests/neg-custom-args/captures/sep-lazyListState.check b/tests/neg-custom-args/captures/sep-lazyListState.check new file mode 100644 index 000000000000..519d143983bf --- /dev/null +++ b/tests/neg-custom-args/captures/sep-lazyListState.check @@ -0,0 +1,5 @@ +-- Error: tests/neg-custom-args/captures/sep-lazyListState.scala:13:14 ------------------------------------------------- +13 | def tail: LazyListIterable[A]^ = tl // error + | ^^^^^^^^^^^^^^^^^^^^ + | Separation failure: method tail's result type LazyListIterable[A]^ hides non-local this of class class Cons. + | The access must be in a @consume method to allow this. diff --git a/tests/neg-custom-args/captures/sep-lazyListState.scala b/tests/neg-custom-args/captures/sep-lazyListState.scala new file mode 100644 index 000000000000..4ef39eb4912d --- /dev/null +++ b/tests/neg-custom-args/captures/sep-lazyListState.scala @@ -0,0 +1,14 @@ +class LazyListIterable[+A] + +private sealed trait State[+A]: + def head: A + def tail: LazyListIterable[A]^ + +private object State: + object Empty extends State[Nothing]: + def head: Nothing = throw new NoSuchElementException("head of empty lazy list") + def tail: LazyListIterable[Nothing] = throw new UnsupportedOperationException("tail of empty lazy list") + + final class Cons[A](val head: A, tl: LazyListIterable[A]^) extends State[A]: + def tail: LazyListIterable[A]^ = tl // error + diff --git a/tests/neg-custom-args/captures/sep-list.check b/tests/neg-custom-args/captures/sep-list.check index 86d4937677e8..43ac04df02b0 100644 --- a/tests/neg-custom-args/captures/sep-list.check +++ b/tests/neg-custom-args/captures/sep-list.check @@ -1,14 +1,16 @@ -- Error: tests/neg-custom-args/captures/sep-list.scala:39:6 ----------------------------------------------------------- 39 | par(h1, h2) // error | ^^ - | Separation failure: argument of type (h1 : Ref^{xs*}) - | to method par: (x: Ref^, y: Ref^): Unit - | corresponds to capture-polymorphic formal parameter x of type Ref^ - | and hides capabilities {h1}. - | Some of these overlap with the captures of the second argument with type (h2 : Ref^{xs*}). + |Separation failure: argument of type (h1 : Ref^{xs*}) + |to method par: (x: Ref^, y: Ref^): Unit + |corresponds to capture-polymorphic formal parameter x of type Ref^ + |and hides capabilities {h1}. + |Some of these overlap with the captures of the second argument with type (h2 : Ref^{xs*}). | - | Hidden set of current argument : {h1} - | Hidden footprint of current argument : {h1, xs*} - | Capture set of second argument : {h2} - | Footprint set of second argument : {h2, xs*} - | The two sets overlap at : {xs*} + | Hidden set of current argument : {h1} + | Hidden footprint of current argument : {h1, xs*} + | Capture set of second argument : {h2} + | Footprint set of second argument : {h2, xs*} + | The two sets overlap at : {xs*} + | + |where: ^ refers to a fresh root capability classified as Mutable created in method test when checking argument to parameter x of method par diff --git a/tests/neg-custom-args/captures/sep-list.scala b/tests/neg-custom-args/captures/sep-list.scala index 46e9b6988318..b5c379a1e3c7 100644 --- a/tests/neg-custom-args/captures/sep-list.scala +++ b/tests/neg-custom-args/captures/sep-list.scala @@ -21,7 +21,7 @@ object NIL extends LIST[Nothing]: class Ref extends Mutable: var x = 0 def get: Int = x - mut def put(y: Int): Unit = x = y + update def put(y: Int): Unit = x = y def listFresh(n: Int): LIST[Ref^] = if n == 0 then NIL diff --git a/tests/neg-custom-args/captures/sep-pairs-2.scala b/tests/neg-custom-args/captures/sep-pairs-2.scala new file mode 100644 index 000000000000..ca166c95bb76 --- /dev/null +++ b/tests/neg-custom-args/captures/sep-pairs-2.scala @@ -0,0 +1,21 @@ +import caps.Mutable +import caps.cap + +class Ref extends Mutable: + var x = 0 + def get: Int = x + update def put(y: Int): Unit = x = y + +class Pair[+X, +Y](val fst: X, val snd: Y) + +def twoRefs1(): Pair[Ref^, Ref^] = + Pair(Ref(), Ref()) + +def twoRefs2(): Pair[Ref^, Ref^] = + val p = Pair(Ref(), Ref()) + Pair(p.fst, p.snd) + +def twoRefs3(): Pair[Ref^, Ref^] = // error but should work + val p = Pair(Ref(), Ref()) + p + diff --git a/tests/neg-custom-args/captures/sep-pairs.check b/tests/neg-custom-args/captures/sep-pairs.check index fd3e00c138bf..3835730bea45 100644 --- a/tests/neg-custom-args/captures/sep-pairs.check +++ b/tests/neg-custom-args/captures/sep-pairs.check @@ -1,41 +1,32 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/sep-pairs.scala:32:10 ------------------------------------ -32 | Pair(Ref(), Ref()) // error // error: universal capability cannot be included in capture set - | ^^^^^ - | Found: box Ref^ - | Required: box Ref^? - | - | Note that the universal capability `cap` - | cannot be included in capture set ? - | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/sep-pairs.scala:32:17 ------------------------------------ -32 | Pair(Ref(), Ref()) // error // error: universal capability cannot be included in capture set - | ^^^^^ - | Found: box Ref^ - | Required: box Ref^? - | - | Note that the universal capability `cap` - | cannot be included in capture set ? - | - | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/sep-pairs.scala:15:10 --------------------------------------------------------- 15 | val r1: Pair[Ref^, Ref^] = mkPair(r0) // error: overlap at r0 | ^^^^^^^^^^^^^^^^ - | Separation failure in value r1's type Pair[box Ref^, box Ref^]. - | One part, box Ref^, hides capabilities {r0}. - | Another part, box Ref^, captures capabilities {r0}. + | Separation failure in value r1's type Pair[Ref^, Ref^²]. + | One part, Ref^, hides capabilities {r0}. + | Another part, Ref^², captures capabilities {r0}. | The two sets overlap at {r0}. + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of value r1 + | ^² refers to a fresh root capability classified as Mutable in the type of value r1 -- Error: tests/neg-custom-args/captures/sep-pairs.scala:13:9 ---------------------------------------------------------- 13 |def bad: Pair[Ref^, Ref^] = // error: overlap at r1*, r0 | ^^^^^^^^^^^^^^^^ - | Separation failure in method bad's result type Pair[box Ref^, box Ref^]. - | One part, box Ref^, hides capabilities {cap, cap, r1*, r0}. - | Another part, box Ref^, captures capabilities {cap, cap, r1*, r0}. + | Separation failure in method bad's result type Pair[Ref^, Ref^²]. + | One part, Ref^, hides capabilities {cap, cap², r1*, r0}. + | Another part, Ref^², captures capabilities {cap, cap², r1*, r0}. | The two sets overlap at {r1*, r0}. --- Error: tests/neg-custom-args/captures/sep-pairs.scala:44:18 --------------------------------------------------------- -44 | val sameToPair: Pair[Ref^, Ref^] = Pair(fstSame, sndSame) // error + | + | where: ^ refers to a fresh root capability classified as Mutable in the result type of method bad + | ^² refers to a fresh root capability classified as Mutable in the result type of method bad + | cap is a fresh root capability classified as Mutable in the type of value r1 + | cap² is a fresh root capability classified as Mutable in the type of value r1 +-- Error: tests/neg-custom-args/captures/sep-pairs.scala:43:18 --------------------------------------------------------- +43 | val sameToPair: Pair[Ref^, Ref^] = Pair(fstSame, sndSame) // error | ^^^^^^^^^^^^^^^^ - | Separation failure in value sameToPair's type Pair[box Ref^, box Ref^]. - | One part, box Ref^, hides capabilities {fstSame}. - | Another part, box Ref^, captures capabilities {sndSame}. - | The two sets overlap at cap of value same. + | Separation failure in value sameToPair's type Pair[Ref^, Ref^²]. + | One part, Ref^, hides capabilities {fstSame}. + | Another part, Ref^², captures capabilities {sndSame}. + | The two sets overlap at cap of value same. + | + | where: ^ refers to a fresh root capability classified as Mutable in the type of value sameToPair + | ^² refers to a fresh root capability classified as Mutable in the type of value sameToPair diff --git a/tests/neg-custom-args/captures/sep-pairs.scala b/tests/neg-custom-args/captures/sep-pairs.scala index 582c635ce27d..0a8635f3ea63 100644 --- a/tests/neg-custom-args/captures/sep-pairs.scala +++ b/tests/neg-custom-args/captures/sep-pairs.scala @@ -4,7 +4,7 @@ import caps.cap class Ref extends Mutable: var x = 0 def get: Int = x - mut def put(y: Int): Unit = x = y + update def put(y: Int): Unit = x = y class Pair[+X, +Y](val fst: X, val snd: Y) @@ -29,8 +29,7 @@ def twoRefs2(): SamePair[Ref^] = r3 def twoRefsBad(): Pair[Ref^, Ref^] = - Pair(Ref(), Ref()) // error // error: universal capability cannot be included in capture set - // but should work since this is morally equivalent to `twoRefs` + Pair(Ref(), Ref()) // ok now def test(io: Object^): Unit = val two = twoRefs() diff --git a/tests/neg-custom-args/captures/sep-use.check b/tests/neg-custom-args/captures/sep-use.check index 64e2bd7800bc..5d8636007fdc 100644 --- a/tests/neg-custom-args/captures/sep-use.check +++ b/tests/neg-custom-args/captures/sep-use.check @@ -4,6 +4,8 @@ | Separation failure: Illegal access to {io} which is hidden by the previous definition | of value x with type () => Unit. | This type hides capabilities {io} + | + | where: => refers to a fresh root capability in the type of value x -- Error: tests/neg-custom-args/captures/sep-use.scala:12:12 ----------------------------------------------------------- 12 | def x: () => Unit = () => println(io) // error | ^^^^^^^^^^ @@ -14,6 +16,8 @@ | Separation failure: Illegal access to {io} which is hidden by the previous definition | of method x with result type () => Unit. | This type hides capabilities {io} + | + | where: => refers to a fresh root capability in the result type of method x -- Error: tests/neg-custom-args/captures/sep-use.scala:18:10 ----------------------------------------------------------- 18 | def xx: (y: Int) => Unit = _ => println(io) // error | ^^^^^^^^^^^^^^^^ @@ -24,6 +28,8 @@ | Separation failure: Illegal access to {io} which is hidden by the previous definition | of method xx with result type (y: Int) => Unit. | This type hides capabilities {io} + | + | where: => refers to a fresh root capability in the result type of method xx -- Error: tests/neg-custom-args/captures/sep-use.scala:24:19 ----------------------------------------------------------- 24 | def xxx(y: Int): Object^ = io // error | ^^^^^^^ @@ -34,3 +40,5 @@ | Separation failure: Illegal access to {io} which is hidden by the previous definition | of method xxx with result type Object^. | This type hides capabilities {io} + | + | where: ^ refers to a fresh root capability in the result type of method xxx diff --git a/tests/neg-custom-args/captures/sep-use2.check b/tests/neg-custom-args/captures/sep-use2.check index a8e3aa884fd5..21980d592ef1 100644 --- a/tests/neg-custom-args/captures/sep-use2.check +++ b/tests/neg-custom-args/captures/sep-use2.check @@ -8,6 +8,8 @@ | Separation failure: Illegal access to {c} which is hidden by the previous definition | of method cc with result type Object^. | This type hides capabilities {c} + | + | where: ^ refers to a fresh root capability in the result type of method cc -- Error: tests/neg-custom-args/captures/sep-use2.scala:12:10 ---------------------------------------------------------- 12 | val x4: Object^ = // error | ^^^^^^^ @@ -18,48 +20,70 @@ | ^^^^^^^ | Separation failure: method cc's result type Object^ hides non-local parameter c -- Error: tests/neg-custom-args/captures/sep-use2.scala:18:6 ----------------------------------------------------------- -18 | { f(cc) } // error // error +18 | { f(cc) } // error | ^ | Separation failure: Illegal access to {c} which is hidden by the previous definition | of method cc with result type Object^. | This type hides capabilities {c} --- Error: tests/neg-custom-args/captures/sep-use2.scala:18:8 ----------------------------------------------------------- -18 | { f(cc) } // error // error - | ^^ - | Separation failure: argument of type (cc : -> Object^) - | to a function of type (x: Object^) ->{c} Object^ - | corresponds to capture-polymorphic formal parameter x of type Object^ - | and hides capabilities {cap, c}. - | Some of these overlap with the captures of the function prefix. | - | Hidden set of current argument : {cap, c} - | Hidden footprint of current argument : {c} - | Capture set of function prefix : {f} - | Footprint set of function prefix : {f, c} - | The two sets overlap at : {c} + | where: ^ refers to a fresh root capability in the result type of method cc -- Error: tests/neg-custom-args/captures/sep-use2.scala:20:6 ----------------------------------------------------------- 20 | { f(c) } // error // error | ^ | Separation failure: Illegal access to {c} which is hidden by the previous definition | of method cc with result type Object^. | This type hides capabilities {c} + | + | where: ^ refers to a fresh root capability in the result type of method cc -- Error: tests/neg-custom-args/captures/sep-use2.scala:20:8 ----------------------------------------------------------- 20 | { f(c) } // error // error | ^ | Separation failure: Illegal access to {c} which is hidden by the previous definition | of method cc with result type Object^. | This type hides capabilities {c} + | + | where: ^ refers to a fresh root capability in the result type of method cc -- Error: tests/neg-custom-args/captures/sep-use2.scala:24:8 ----------------------------------------------------------- 24 | { f(c) } // error | ^ - | Separation failure: argument of type (c : Object^) - | to a function of type Object^ ->{c} Object^ - | corresponds to capture-polymorphic formal parameter v1 of type Object^ - | and hides capabilities {c}. - | Some of these overlap with the captures of the function prefix. + |Separation failure: argument of type (c : Object^) + |to a function of type Object^ ->{f} Object^{f*} + |corresponds to capture-polymorphic formal parameter v1 of type Object^² + |and hides capabilities {c}. + |Some of these overlap with the captures of the function prefix. + | + | Hidden set of current argument : {c} + | Hidden footprint of current argument : {c} + | Capture set of function prefix : {f*} + | Footprint set of function prefix : {f*, c} + | The two sets overlap at : {c} + | + |where: ^ refers to a fresh root capability in the type of parameter c + | ^² refers to a fresh root capability created in value x4 when checking argument to parameter v1 of method apply +-- Error: tests/neg-custom-args/captures/sep-use2.scala:23:10 ---------------------------------------------------------- +23 | val x4: Object^ = // error: ^ hides f*, needs @consume + | ^^^^^^^ + | Separation failure: value x4's type Object^ hides parameter f. + | The parameter needs to be annotated with @consume to allow this. +-- Error: tests/neg-custom-args/captures/sep-use2.scala:28:8 ----------------------------------------------------------- +28 | { f(c) } // error + | ^ + |Separation failure: argument of type (c : Object^) + |to a function of type Object^ ->{f} Object^{f*} + |corresponds to capture-polymorphic formal parameter v1 of type Object^² + |and hides capabilities {c}. + |Some of these overlap with the captures of the function prefix. | - | Hidden set of current argument : {c} - | Hidden footprint of current argument : {c} - | Capture set of function prefix : {f} - | Footprint set of function prefix : {f, c} - | The two sets overlap at : {c} + | Hidden set of current argument : {c} + | Hidden footprint of current argument : {c} + | Capture set of function prefix : {f*} + | Footprint set of function prefix : {f*, c} + | The two sets overlap at : {c} + | + |where: ^ refers to a fresh root capability in the type of parameter c + | ^² refers to a fresh root capability created in value x4 when checking argument to parameter v1 of method apply +-- Error: tests/neg-custom-args/captures/sep-use2.scala:27:10 ---------------------------------------------------------- +27 | val x4: Object^ = // error: ^ hides f* which refers to c, so c needs @consume + | ^^^^^^^ + | Separation failure: value x4's type Object^ hides parameter c. + | The parameter needs to be annotated with @consume to allow this. diff --git a/tests/neg-custom-args/captures/sep-use2.scala b/tests/neg-custom-args/captures/sep-use2.scala index a31fb098a719..088984fab0c6 100644 --- a/tests/neg-custom-args/captures/sep-use2.scala +++ b/tests/neg-custom-args/captures/sep-use2.scala @@ -15,14 +15,19 @@ def test1(@consume c: Object^, f: (x: Object^) => Object^) = def test2(@consume c: Object^, f: (x: Object^) ->{c} Object^) = def cc: Object^ = c // error val x1 = - { f(cc) } // error // error + { f(cc) } // error val x4: Object^ = // ^ hides just c, since the Object^ in the result of `f` is existential { f(c) } // error // error def test3(@consume c: Object^, f: Object^ ->{c} Object^) = - val x4: Object^ = // ^ hides c and f* + val x4: Object^ = // error: ^ hides f*, needs @consume { f(c) } // error +def test4(c: Object^, @consume f: Object^ ->{c} Object^) = + val x4: Object^ = // error: ^ hides f* which refers to c, so c needs @consume + { f(c) } // error + + diff --git a/tests/neg-custom-args/captures/sepchecks.scala b/tests/neg-custom-args/captures/sepchecks.scala index 4508b6839781..f84c80da9c54 100644 --- a/tests/neg-custom-args/captures/sepchecks.scala +++ b/tests/neg-custom-args/captures/sepchecks.scala @@ -8,7 +8,7 @@ trait Rdr[T]: class Ref[T](init: T) extends Rdr[T], Mutable: private var current = init def get: T = current - mut def put(x: T): Unit = current = x + update def put(x: T): Unit = current = x def Test(c: Object^): Unit = val a: Ref[Int]^ = Ref(1) @@ -39,7 +39,7 @@ def Test(c: Object^): Unit = class Matrix(nrows: Int, ncols: Int) extends IMatrix, Mutable: val arr = Array.fill(nrows, ncols)(0.0) def apply(i: Int, j: Int): Double = arr(i)(j) - mut def update(i: Int, j: Int, x: Double): Unit = arr(i)(j) = x + update def update(i: Int, j: Int, x: Double): Unit = arr(i)(j) = x def mul(x: IMatrix^{cap.rd}, y: IMatrix^{cap.rd}, z: Matrix^): Matrix^ = ??? diff --git a/tests/neg-custom-args/captures/sepchecks2.check b/tests/neg-custom-args/captures/sepchecks2.check index 47a80306f3b7..e7b8dcbf038f 100644 --- a/tests/neg-custom-args/captures/sepchecks2.check +++ b/tests/neg-custom-args/captures/sepchecks2.check @@ -2,50 +2,62 @@ 10 | println(c) // error | ^ | Separation failure: Illegal access to {c} which is hidden by the previous definition - | of value xs with type List[box () => Unit]. + | of value xs with type List[() => Unit]. | This type hides capabilities {c} + | + | where: => refers to a fresh root capability in the type of value xs -- Error: tests/neg-custom-args/captures/sepchecks2.scala:13:7 --------------------------------------------------------- 13 | foo((() => println(c)) :: Nil, c) // error | ^^^^^^^^^^^^^^^^^^^^^^^^ - | Separation failure: argument of type List[box () ->{c} Unit] - | to method foo: (xs: List[box () => Unit], y: Object^): Nothing - | corresponds to capture-polymorphic formal parameter xs of type List[box () => Unit] - | and hides capabilities {c}. - | Some of these overlap with the captures of the second argument with type (c : Object^). + |Separation failure: argument of type List[() ->{c} Unit] + |to method foo: (xs: List[() => Unit], y: Object^): Nothing + |corresponds to capture-polymorphic formal parameter xs of type List[() => Unit] + |and hides capabilities {c}. + |Some of these overlap with the captures of the second argument with type (c : Object^). + | + | Hidden set of current argument : {c} + | Hidden footprint of current argument : {c} + | Capture set of second argument : {c} + | Footprint set of second argument : {c} + | The two sets overlap at : {c} | - | Hidden set of current argument : {c} - | Hidden footprint of current argument : {c} - | Capture set of second argument : {c} - | Footprint set of second argument : {c} - | The two sets overlap at : {c} + |where: => refers to a fresh root capability created in method Test2 when checking argument to parameter xs of method foo -- Error: tests/neg-custom-args/captures/sepchecks2.scala:14:10 -------------------------------------------------------- 14 | val x1: (Object^, Object^) = (c, c) // error | ^^^^^^^^^^^^^^^^^^ - | Separation failure in value x1's type (box Object^, box Object^). - | One part, box Object^, hides capabilities {c}. - | Another part, box Object^, captures capabilities {c}. + | Separation failure in value x1's type (Object^, Object^²). + | One part, Object^, hides capabilities {c}. + | Another part, Object^², captures capabilities {c}. | The two sets overlap at {c}. + | + | where: ^ refers to a fresh root capability in the type of value x1 + | ^² refers to a fresh root capability in the type of value x1 -- Error: tests/neg-custom-args/captures/sepchecks2.scala:15:10 -------------------------------------------------------- 15 | val x2: (Object^, Object^{d}) = (d, d) // error | ^^^^^^^^^^^^^^^^^^^^^ - | Separation failure in value x2's type (box Object^, box Object^{d}). - | One part, box Object^, hides capabilities {d}. - | Another part, box Object^{d}, captures capabilities {d}. + | Separation failure in value x2's type (Object^, Object^{d}). + | One part, Object^, hides capabilities {d}. + | Another part, Object^{d}, captures capabilities {d}. | The two sets overlap at {d}. + | + | where: ^ refers to a fresh root capability in the type of value x2 -- Error: tests/neg-custom-args/captures/sepchecks2.scala:27:6 --------------------------------------------------------- 27 | bar((c, c)) // error | ^^^^^^ - | Separation failure in the argument's adapted type (box Object^, box Object^). - | One part, box Object^, hides capabilities {c}. - | Another part, box Object^, captures capabilities {c}. - | The two sets overlap at {c}. + |Separation failure in the argument's adapted type (Object^, Object^²). + |One part, Object^, hides capabilities {c}. + |Another part, Object^², captures capabilities {c}. + |The two sets overlap at {c}. + | + |where: ^ refers to a fresh root capability created in method Test6 when checking argument to parameter x of method bar + | ^² refers to a fresh root capability created in method Test6 when checking argument to parameter x of method bar -- Error: tests/neg-custom-args/captures/sepchecks2.scala:30:9 --------------------------------------------------------- 30 | val x: (Object^, Object^{c}) = (d, c) // error | ^^^^^^^^^^^^^^^^^^^^^ - | Separation failure: value x's type (box Object^, box Object^{c}) hides parameter d. + | Separation failure: value x's type (Object^, Object^{c}) hides parameter d. | The parameter needs to be annotated with @consume to allow this. -- Error: tests/neg-custom-args/captures/sepchecks2.scala:33:9 --------------------------------------------------------- 33 | val x: (Object^, Object^) = (c, d) // error | ^^^^^^^^^^^^^^^^^^ - | Separation failure: value x's type (box Object^, box Object^) hides parameters c and d. + | Separation failure: value x's type (Object^, Object^) hides parameters c and d. | The parameters need to be annotated with @consume to allow this. diff --git a/tests/neg-custom-args/captures/sepchecks4.check b/tests/neg-custom-args/captures/sepchecks4.check index 5934be1ff637..7b587363d1cd 100644 --- a/tests/neg-custom-args/captures/sepchecks4.check +++ b/tests/neg-custom-args/captures/sepchecks4.check @@ -11,14 +11,16 @@ -- Error: tests/neg-custom-args/captures/sepchecks4.scala:12:6 --------------------------------------------------------- 12 | par(() => println(io))(() => println(io)) // error // (1) | ^^^^^^^^^^^^^^^^^ - | Separation failure: argument of type () ->{io} Unit - | to method par: (op1: () => Unit)(op2: () => Unit): Unit - | corresponds to capture-polymorphic formal parameter op1 of type () => Unit - | and hides capabilities {io}. - | Some of these overlap with the captures of the second argument with type () ->{io} Unit. + |Separation failure: argument of type () ->{io} Unit + |to method par: (op1: () => Unit)(op2: () => Unit): Unit + |corresponds to capture-polymorphic formal parameter op1 of type () => Unit + |and hides capabilities {io}. + |Some of these overlap with the captures of the second argument with type () ->{io} Unit. | - | Hidden set of current argument : {io} - | Hidden footprint of current argument : {io} - | Capture set of second argument : {io} - | Footprint set of second argument : {io} - | The two sets overlap at : {io} + | Hidden set of current argument : {io} + | Hidden footprint of current argument : {io} + | Capture set of second argument : {io} + | Footprint set of second argument : {io} + | The two sets overlap at : {io} + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter op1 of method par diff --git a/tests/neg-custom-args/captures/sepchecks5.check b/tests/neg-custom-args/captures/sepchecks5.check index aae5cb89da53..63ec3bc73431 100644 --- a/tests/neg-custom-args/captures/sepchecks5.check +++ b/tests/neg-custom-args/captures/sepchecks5.check @@ -14,3 +14,5 @@ | Separation failure: Illegal access to (io : Object^), which was passed to a | @consume parameter or was used as a prefix to a @consume method on line 19 | and therefore is no longer available. + | + | where: ^ refers to a fresh root capability in the type of parameter io diff --git a/tests/neg-custom-args/captures/shared-capability.check b/tests/neg-custom-args/captures/shared-capability.check index 64fb3eb39d44..0c575cd69b78 100644 --- a/tests/neg-custom-args/captures/shared-capability.check +++ b/tests/neg-custom-args/captures/shared-capability.check @@ -1,4 +1,6 @@ -- Error: tests/neg-custom-args/captures/shared-capability.scala:9:13 -------------------------------------------------- 9 |def test2(a: Async^): Object^ = a // error | ^^^^^^ - | Async^ extends SharedCapability, so it cannot capture `cap` + | Async^ extends Sharable, so it cannot capture `cap` + | + | where: ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/shared-capability.scala b/tests/neg-custom-args/captures/shared-capability.scala index 262a6db386ba..f10bc6f53444 100644 --- a/tests/neg-custom-args/captures/shared-capability.scala +++ b/tests/neg-custom-args/captures/shared-capability.scala @@ -1,8 +1,8 @@ -import caps.SharedCapability +import caps.Sharable -class Async extends SharedCapability +class Async extends Sharable def test1(a: Async): Object^ = a // OK diff --git a/tests/neg-custom-args/captures/simple-using.check b/tests/neg-custom-args/captures/simple-using.check index e46b9e50b58d..16ee43bbbe6b 100644 --- a/tests/neg-custom-args/captures/simple-using.check +++ b/tests/neg-custom-args/captures/simple-using.check @@ -1,10 +1,13 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/simple-using.scala:8:15 ---------------------------------- 8 | usingLogFile { f => () => f.write(2) } // error | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (f: java.io.FileOutputStream^) ->? box () ->{f} Unit - | Required: (f: java.io.FileOutputStream^) ->{fresh} box () ->? Unit + |Found: (f: java.io.FileOutputStream^?) ->? () ->{f} Unit + |Required: java.io.FileOutputStream^ => () ->? Unit | - | Note that reference f.type - | cannot be included in outer capture set ? + |where: => refers to a fresh root capability created in method test when checking argument to parameter op of method usingLogFile + | ^ refers to the universal root capability + | + |Note that reference f.type + |cannot be included in outer capture set ? | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/spread-problem.check b/tests/neg-custom-args/captures/spread-problem.check index 31cf38a51727..91022f920486 100644 --- a/tests/neg-custom-args/captures/spread-problem.check +++ b/tests/neg-custom-args/captures/spread-problem.check @@ -1,14 +1,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/spread-problem.scala:8:6 --------------------------------- 8 | race(Seq(src1, src2)*) // error | ^^^^^^^^^^^^^^^^^^^^^^ - | Found: Source[box T^?]^{src1, src2} + | Found: Source[T^?]^{src1, src2} | Required: Source[T] | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/spread-problem.scala:11:6 -------------------------------- 11 | race(src1, src2) // error | ^^^^^^^^^^^^^^^^ - | Found: Source[box T^?]^{src1, src2} + | Found: Source[T^?]^{src1, src2} | Required: Source[T] | | 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 9cd40a8bc8c4..c470ee13e9ca 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -1,13 +1,19 @@ -- Error: tests/neg-custom-args/captures/try.scala:23:28 --------------------------------------------------------------- 23 | val a = handle[Exception, CanThrow[Exception]] { // error // error | ^^^^^^^^^^^^^^^^^^^ - | Type variable R of method handle cannot be instantiated to box CT[Exception]^ since + | Type variable R of method handle cannot be instantiated to CT[Exception]^ since | that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:23:49 ------------------------------------------ 23 | val a = handle[Exception, CanThrow[Exception]] { // error // error | ^ - | Found: (x: CT[Exception]^) ->? box CT[Exception]^{x} - | Required: (x: CT[Exception]^) ->{fresh} box CT[Exception]^{fresh} + |Found: (x: CT[Exception]^) ->? CT[Exception]^{x} + |Required: CT[Exception]^ => CT[Exception]^² + | + |where: => refers to a fresh root capability created in value a when checking argument to parameter op of method handle + | ^ refers to the universal root capability + | ^² refers to a fresh root capability created in value a when checking argument to parameter op of method handle 24 | (x: CanThrow[Exception]) => x 25 | }{ | @@ -15,8 +21,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:29:43 ------------------------------------------ 29 | val b = handle[Exception, () -> Nothing] { // error | ^ - | Found: (x: CT[Exception]^) ->? () ->{x} Nothing - | Required: (x: CT[Exception]^) ->{fresh} () -> Nothing + |Found: (x: CT[Exception]^) ->? () ->{x} Nothing + |Required: CT[Exception]^ => () -> Nothing + | + |where: => refers to a fresh root capability created in value b when checking argument to parameter op of method handle + | ^ refers to the universal root capability 30 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) 31 | } { | @@ -24,11 +33,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:35:18 ------------------------------------------ 35 | val xx = handle { // error | ^ - | Found: (x: CT[Exception]^) ->? box () ->{x} Int - | Required: (x: CT[Exception]^) ->{fresh} box () ->? Int + |Found: (x: CT[Exception]^) ->? () ->{x} Int + |Required: CT[Exception]^ => () ->? Int | - | Note that reference x.type - | cannot be included in outer capture set ? + |where: => refers to a fresh root capability created in value xx when checking argument to parameter op of method handle + | ^ refers to the universal root capability + | + |Note that reference x.type + |cannot be included in outer capture set ? 36 | (x: CanThrow[Exception]) => 37 | () => 38 | raise(new Exception)(using x) @@ -39,11 +51,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:47:31 ------------------------------------------ 47 |val global: () -> Int = handle { // error | ^ - | Found: (x: CT[Exception]^) ->? box () ->{x} Int - | Required: (x: CT[Exception]^) ->{fresh} box () ->? Int + |Found: (x: CT[Exception]^) ->? () ->{x} Int + |Required: CT[Exception]^ => () ->? Int + | + |where: => refers to a fresh root capability created in value global when checking argument to parameter op of method handle + | ^ refers to the universal root capability | - | Note that reference x.type - | cannot be included in outer capture set ? + |Note that reference x.type + |cannot be included in outer capture set ? 48 | (x: CanThrow[Exception]) => 49 | () => 50 | raise(new Exception)(using x) diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala index 753958b234c5..8168504617ac 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -2,8 +2,8 @@ import annotation.retains import language.experimental.erasedDefinitions class CT[E <: Exception] -type CanThrow[E <: Exception] = CT[E] @retains(caps.cap) -type Top = Any @retains(caps.cap) +type CanThrow[E <: Exception] = CT[E] @retains[caps.cap.type] +type Top = Any @retains[caps.cap.type] infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?=> R diff --git a/tests/neg-custom-args/captures/unbox-overrides.check b/tests/neg-custom-args/captures/unbox-overrides.check index dbffc164b5c5..a531b546a62a 100644 --- a/tests/neg-custom-args/captures/unbox-overrides.check +++ b/tests/neg-custom-args/captures/unbox-overrides.check @@ -1,7 +1,7 @@ -- [E164] Declaration Error: tests/neg-custom-args/captures/unbox-overrides.scala:8:6 ---------------------------------- 8 | def foo(x: C): C // error | ^ - |error overriding method foo in trait A of type (x: C): C; + |error overriding method foo in trait A of type (@use x: C): C; | method foo of type (x: C): C has a parameter x with different @use status than the corresponding parameter in the overridden definition | | longer explanation available when compiling with `-explain` @@ -9,13 +9,13 @@ 9 | def bar(@use x: C): C // error | ^ |error overriding method bar in trait A of type (x: C): C; - | method bar of type (x: C): C has a parameter x with different @use status than the corresponding parameter in the overridden definition + | method bar of type (@use x: C): C has a parameter x with different @use status than the corresponding parameter in the overridden definition | | longer explanation available when compiling with `-explain` -- [E164] Declaration Error: tests/neg-custom-args/captures/unbox-overrides.scala:15:15 -------------------------------- 15 |abstract class C extends A[C], B2 // error | ^ - |error overriding method foo in trait A of type (x: C): C; + |error overriding method foo in trait A of type (@use x: C): C; | method foo in trait B2 of type (x: C): C has a parameter x with different @use status than the corresponding parameter in the overridden definition | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/unsound-reach-4.check b/tests/neg-custom-args/captures/unsound-reach-4.check index f21d1403eb3d..576e3c46186b 100644 --- a/tests/neg-custom-args/captures/unsound-reach-4.check +++ b/tests/neg-custom-args/captures/unsound-reach-4.check @@ -3,17 +3,24 @@ | ^^^^^^^^^^ | Type variable X of trait Foo cannot be instantiated to File^ since | that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/unsound-reach-4.scala:20:29 ------------------------------ 20 | val backdoor: Foo[File^] = new Bar // error (follow-on, since the parent Foo[File^] of bar is illegal). | ^^^^^^^ | Found: Bar^? - | Required: Foo[box File^] + | Required: Foo[File^] + | + | where: ^ refers to a fresh root capability in the type of value backdoor | | longer explanation available when compiling with `-explain` -- [E164] Declaration Error: tests/neg-custom-args/captures/unsound-reach-4.scala:17:6 --------------------------------- 17 | def use(@consume x: F): File^ = x // error @consume override | ^ - | error overriding method use in trait Foo of type (x: File^): box File^; - | method use of type (x: File^): File^ has incompatible type + | error overriding method use in trait Foo of type (x: File^): File^; + | method use of type (@consume x: File^): File^² has incompatible type + | + | where: ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (@consume x: File^): File^² | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/unsound-reach-6.check b/tests/neg-custom-args/captures/unsound-reach-6.check index ed81271efa90..8308e2336d7c 100644 --- a/tests/neg-custom-args/captures/unsound-reach-6.check +++ b/tests/neg-custom-args/captures/unsound-reach-6.check @@ -1,13 +1,3 @@ --- Error: tests/neg-custom-args/captures/unsound-reach-6.scala:7:13 ---------------------------------------------------- -7 | println(xs.head) // error - | ^^^^^^^ - | Local reach capability xs* leaks into capture scope of method f. - | To allow this, the parameter xs should be declared with a @use annotation --- Error: tests/neg-custom-args/captures/unsound-reach-6.scala:11:14 --------------------------------------------------- -11 | val z = f(ys) // error @consume failure - | ^^ - | Local reach capability ys* leaks into capture scope of method test. - | To allow this, the parameter ys should be declared with a @use annotation -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/unsound-reach-6.scala:13:22 ------------------------------ 13 | val _: () -> Unit = x // error | ^ @@ -22,7 +12,17 @@ | Required: () -> Unit | | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/unsound-reach-6.scala:7:13 ---------------------------------------------------- +7 | println(xs.head) // error + | ^^^^^^^ + | Local reach capability xs* leaks into capture scope of method f. + | To allow this, the parameter xs should be declared with a @use annotation +-- Error: tests/neg-custom-args/captures/unsound-reach-6.scala:11:14 --------------------------------------------------- +11 | val z = f(ys) // error @consume failure + | ^^ + | Local reach capability ys* leaks into capture scope of method test. + | To allow this, the parameter ys should be declared with a @use annotation -- Error: tests/neg-custom-args/captures/unsound-reach-6.scala:19:14 --------------------------------------------------- 19 | val z = f(ys) // error @consume failure | ^^ - |Separation failure: argument to @consume parameter with type (ys : -> List[box () ->{io} Unit]) refers to non-local parameter io + |Separation failure: argument to @consume parameter with type List[() ->{io} Unit] refers to non-local parameter io diff --git a/tests/neg-custom-args/captures/unsound-reach.check b/tests/neg-custom-args/captures/unsound-reach.check index a1ebd30e4915..23af7d907362 100644 --- a/tests/neg-custom-args/captures/unsound-reach.check +++ b/tests/neg-custom-args/captures/unsound-reach.check @@ -3,15 +3,21 @@ | ^^^^^^^^^^ | Type variable X of trait Foo cannot be instantiated to File^ since | that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- Error: tests/neg-custom-args/captures/unsound-reach.scala:14:19 ----------------------------------------------------- 14 |class Bar2 extends Foo2[File^]: // error | ^ - | Type variable X of constructor Foo2 cannot be instantiated to box File^ since + | Type variable X of constructor Foo2 cannot be instantiated to File^ since | that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/unsound-reach.scala:18:31 -------------------------------- 18 | val backdoor: Foo[File^] = new Bar // error (follow-on, since the parent Foo[File^] of bar is illegal). | ^^^^^^^ | Found: Bar^? - | Required: Foo[box File^] + | Required: Foo[File^] + | + | where: ^ refers to a fresh root capability in the type of value backdoor | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/update-call.scala b/tests/neg-custom-args/captures/update-call.scala index 848e4d880223..aa2913645e43 100644 --- a/tests/neg-custom-args/captures/update-call.scala +++ b/tests/neg-custom-args/captures/update-call.scala @@ -2,18 +2,18 @@ import caps.Mutable trait IterableOnce[T] extends Mutable: def iterator: Iterator[T]^{this} - mut def foreach(op: T => Unit): Unit + update def foreach(op: T => Unit): Unit trait Iterator[T] extends IterableOnce[T]: def iterator = this def hasNext: Boolean - mut def next(): T - mut def foreach(op: T => Unit): Unit = ??? - override mut def toString = ??? // error + update def next(): T + update def foreach(op: T => Unit): Unit = ??? + override update def toString = ??? // error trait Iterable[T] extends IterableOnce[T]: def iterator: Iterator[T] = ??? def foreach(op: T => Unit) = iterator.foreach(op) trait BadIterator[T] extends Iterator[T]: - override mut def hasNext: Boolean // error + override update def hasNext: Boolean // error diff --git a/tests/neg-custom-args/captures/use-capset.check b/tests/neg-custom-args/captures/use-capset.check index 6789ec306464..fc65636b1302 100644 --- a/tests/neg-custom-args/captures/use-capset.check +++ b/tests/neg-custom-args/captures/use-capset.check @@ -1,8 +1,3 @@ --- Error: tests/neg-custom-args/captures/use-capset.scala:5:50 --------------------------------------------------------- -5 |private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error - | ^^^^^^^ - | Capture set parameter C leaks into capture scope of method g. - | To allow this, the type C should be declared with a @use annotation -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:11:22 ----------------------------------- 11 | val _: () -> Unit = h // error: should be ->{io} | ^ @@ -13,7 +8,12 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:13:50 ----------------------------------- 13 | val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} | ^^ - | Found: (h2 : () ->{} List[box Object^{io}]^{} ->{io} Object^{io}) - | Required: () -> List[box Object^{io}] -> Object^{io} + | Found: (h2 : () ->{} List[Object^{io}]^{} ->{io} Object^{io}) + | Required: () -> List[Object^{io}] -> Object^{io} | | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/use-capset.scala:5:49 --------------------------------------------------------- +5 |private def g[C^] = (xs: List[Object^{C}]) => xs.head // error + | ^^^^^^^ + | Capture set parameter C leaks into capture scope of method g. + | To allow this, the type C should be declared with a @use annotation diff --git a/tests/neg-custom-args/captures/use-capset.scala b/tests/neg-custom-args/captures/use-capset.scala index 74288d616396..2d489a5061d8 100644 --- a/tests/neg-custom-args/captures/use-capset.scala +++ b/tests/neg-custom-args/captures/use-capset.scala @@ -1,14 +1,14 @@ import caps.{use, CapSet} -def f[C^](@use xs: List[Object^{C^}]): Unit = ??? +def f[C^](@use xs: List[Object^{C}]): Unit = ??? -private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error +private def g[C^] = (xs: List[Object^{C}]) => xs.head // error -private def g2[@use C^] = (xs: List[Object^{C^}]) => xs.head // ok +private def g2[@use C^] = (xs: List[Object^{C}]) => xs.head // ok def test(io: Object^)(@use xs: List[Object^{io}]): Unit = val h = () => f(xs) val _: () -> Unit = h // error: should be ->{io} - val h2 = () => g[CapSet^{io}] + val h2 = () => g[{io}] val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} diff --git a/tests/neg-custom-args/captures/usingLogFile.check b/tests/neg-custom-args/captures/usingLogFile.check index 1c77435685c7..81f57e8ba655 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -1,40 +1,52 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:22:27 --------------------------------- 22 | val later = usingLogFile { f => () => f.write(0) } // error | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (f: java.io.FileOutputStream^) ->? box () ->{f} Unit - | Required: (f: java.io.FileOutputStream^) ->{fresh} box () ->? Unit + |Found: (f: java.io.FileOutputStream^?) ->? () ->{f} Unit + |Required: java.io.FileOutputStream^ => () ->? Unit | - | Note that reference f.type - | cannot be included in outer capture set ? + |where: => refers to a fresh root capability created in value later when checking argument to parameter op of method usingLogFile + | ^ refers to the universal root capability + | + |Note that reference f.type + |cannot be included in outer capture set ? | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:27:36 --------------------------------- 27 | private val later2 = usingLogFile { f => Cell(() => f.write(0)) } // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (f: java.io.FileOutputStream^) ->? box Test2.Cell[box () ->{f} Unit]^? - | Required: (f: java.io.FileOutputStream^) ->{fresh} box Test2.Cell[box () ->? Unit]^? + |Found: (f: java.io.FileOutputStream^?) ->? Test2.Cell[() ->{f} Unit]^? + |Required: java.io.FileOutputStream^ => Test2.Cell[() ->? Unit]^? + | + |where: => refers to a fresh root capability created in value later2 when checking argument to parameter op of method usingLogFile + | ^ refers to the universal root capability | - | Note that reference f.type - | cannot be included in outer capture set ? + |Note that reference f.type + |cannot be included in outer capture set ? | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:43:33 --------------------------------- 43 | val later = usingFile("out", f => (y: Int) => xs.foreach(x => f.write(x + y))) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (f: java.io.OutputStream^) ->? box Int ->{f} Unit - | Required: (f: java.io.OutputStream^) ->{fresh} box Int ->? Unit + |Found: (f: java.io.OutputStream^?) ->? Int ->{f} Unit + |Required: java.io.OutputStream^ => Int ->? Unit | - | Note that reference f.type - | cannot be included in outer capture set ? + |where: => refers to a fresh root capability created in value later when checking argument to parameter op of method usingFile + | ^ refers to the universal root capability + | + |Note that reference f.type + |cannot be included in outer capture set ? | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:52:6 ---------------------------------- 52 | usingLogger(_, l => () => l.log("test"))) // error after checking mapping scheme | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (_$1: java.io.OutputStream^) ->? box () ->{_$1} Unit - | Required: (_$1: java.io.OutputStream^) ->{fresh} box () ->? Unit + |Found: (_$1: java.io.OutputStream^?) ->? () ->{_$1} Unit + |Required: java.io.OutputStream^ => () ->? Unit + | + |where: => refers to a fresh root capability created in value later when checking argument to parameter op of method usingFile + | ^ refers to the universal root capability | - | Note that reference _$1.type - | cannot be included in outer capture set ? + |Note that reference _$1.type + |cannot be included in outer capture set ? | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/vars-simple.check b/tests/neg-custom-args/captures/vars-simple.check index 71fab0dcf7d2..7454ba0ecfbd 100644 --- a/tests/neg-custom-args/captures/vars-simple.check +++ b/tests/neg-custom-args/captures/vars-simple.check @@ -4,18 +4,20 @@ | Found: String => String | Required: String ->{cap1, cap2} String | + | where: => refers to a fresh root capability created in method scope + | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars-simple.scala:16:8 ----------------------------------- 16 | a = g // error | ^ | Found: (x: String) ->{cap3} String - | Required: (x: String) ->{cap1, cap2} String + | Required: String ->{cap1, cap2} String | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars-simple.scala:17:12 ---------------------------------- 17 | b = List(g) // error | ^^^^^^^ - | Found: List[box String ->{cap3} String] - | Required: List[box String ->{cap1, cap2} String] + | Found: List[String ->{cap3} String] + | Required: List[String ->{cap1, cap2} String] | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index fae2645fcb8b..484f9270e71f 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -2,28 +2,30 @@ 24 | a = x => g(x) // error | ^^^^^^^^^ | Found: (x: String) ->{cap3} String - | Required: (x: String) ->{cap1} String + | Required: String ->{cap1} String | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:25:8 ------------------------------------------ 25 | a = g // error | ^ | Found: (x: String) ->{cap3} String - | Required: (x: String) ->{cap1} String + | Required: String ->{cap1} String | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:27:12 ----------------------------------------- 27 | b = List(g) // error | ^^^^^^^ - | Found: List[box String ->{cap3} String] - | Required: List[box String ->{cap1, cap2} String] + | Found: List[String ->{cap3} String] + | Required: List[String ->{cap1, cap2} String] | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:36:8 ------------------------------------------ 36 | local { cap3 => // error | ^ - | Found: (cap3: CC^) ->? box String ->{cap3} String - | Required: (cap3: CC^) -> box String ->? String + | Found: (cap3: CC^) ->? String ->{cap3} String + | Required: CC^ -> String ->? String + | + | where: ^ refers to the universal root capability | | Note that reference cap3.type | cannot be included in outer capture set ? diff --git a/tests/neg-custom-args/captures/widen-reach.check b/tests/neg-custom-args/captures/widen-reach.check index e2028941a009..9d0ecceb1808 100644 --- a/tests/neg-custom-args/captures/widen-reach.check +++ b/tests/neg-custom-args/captures/widen-reach.check @@ -3,27 +3,34 @@ | ^^^^^^^^ | Type variable T of trait Foo cannot be instantiated to IO^ since | that type captures the root capability `cap`. + | + | where: ^ refers to the universal root capability -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/widen-reach.scala:9:24 ----------------------------------- -9 | val foo: IO^ -> IO^ = x => x // error // error +9 | val foo: IO^ -> IO^ = x => x // error | ^^^^^^ | Found: (x: IO^) ->? IO^{x} - | Required: (x: IO^) -> IO^{fresh} + | Required: IO^ -> IO^² + | + | where: ^ refers to the universal root capability + | ^² refers to a fresh root capability in the type of value foo | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/widen-reach.scala:13:26 ------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/widen-reach.scala:13:26 ---------------------------------- 13 | val y2: IO^ -> IO^ = y1.foo // error | ^^^^^^ - | Local reach capability x* leaks into capture scope of method test. - | To allow this, the parameter x should be declared with a @use annotation --- Error: tests/neg-custom-args/captures/widen-reach.scala:14:30 ------------------------------------------------------- + | Found: IO^ ->{x*} IO^{x*} + | Required: IO^ -> IO^² + | + | where: ^ refers to the universal root capability + | ^² refers to a fresh root capability in the type of value y2 + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/widen-reach.scala:14:30 ---------------------------------- 14 | val y3: IO^ -> IO^{x*} = y1.foo // error | ^^^^^^ - | Local reach capability x* leaks into capture scope of method test. - | To allow this, the parameter x should be declared with a @use annotation --- [E164] Declaration Error: tests/neg-custom-args/captures/widen-reach.scala:9:6 -------------------------------------- -9 | val foo: IO^ -> IO^ = x => x // error // error - | ^ - | error overriding value foo in trait Foo of type IO^ -> box IO^; - | value foo of type IO^ -> IO^ has incompatible type - | - | longer explanation available when compiling with `-explain` + | Found: IO^ ->{x*} IO^{x*} + | Required: IO^ -> IO^{x*} + | + | where: ^ refers to the universal root capability + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/widen-reach.scala b/tests/neg-custom-args/captures/widen-reach.scala index ae64a3d61fc1..6d3a57d6a669 100644 --- a/tests/neg-custom-args/captures/widen-reach.scala +++ b/tests/neg-custom-args/captures/widen-reach.scala @@ -6,7 +6,7 @@ trait Foo[+T]: val foo: IO^ -> T trait Bar extends Foo[IO^]: // error - val foo: IO^ -> IO^ = x => x // error // error + val foo: IO^ -> IO^ = x => x // error def test(x: Foo[IO^]): Unit = val y1: Foo[IO^{x*}] = x diff --git a/tests/neg-custom-args/captures/withFile.scala b/tests/neg-custom-args/captures/withFile.scala index b8cdf96f9143..8e287d8631ba 100644 --- a/tests/neg-custom-args/captures/withFile.scala +++ b/tests/neg-custom-args/captures/withFile.scala @@ -1,10 +1,19 @@ -import java.io.* object Test2: + class File: + def write(): Unit = () + def close(): Unit = () + class Box[+T](x: T) - def usingLogFile[T](op: FileOutputStream^ => T): T = - val logFile = FileOutputStream("log") + def usingLogFile[T](op: File^ => T): T = + val logFile = File() val result = op(logFile) logFile.close() result - private val later = usingLogFile { f => () => f.write(0) } // error + + private val later1 = usingLogFile { f => () => f.write() } // error + private val later2 = usingLogFile { f => Box(f) } // error + private val later3 = usingLogFile[() => Unit]: // error + f => () => f.write() // error + private val later4 = usingLogFile[Box[File^]]: // error + f => Box(f) // error diff --git a/tests/neg-macros/i18174.check b/tests/neg-macros/i18174.check deleted file mode 100644 index 1f4396d6b5ca..000000000000 --- a/tests/neg-macros/i18174.check +++ /dev/null @@ -1,12 +0,0 @@ --- [E172] Type Error: tests/neg-macros/i18174.scala:27:33 -------------------------------------------------------------- -27 | (charClassIntersection.rep() | classItem.rep()) // error - | ^^^^^^^^^^^^^^^ - |No given instance of type pkg.Implicits.Repeater[pkg.RegexTree, V] was found. - |I found: - | - | pkg.Implicits.Repeater.GenericRepeaterImplicit[T] - | - |But method GenericRepeaterImplicit in object Repeater does not match type pkg.Implicits.Repeater[pkg.RegexTree, V] - | - |where: V is a type variable with constraint <: Seq[pkg.CharClassIntersection] - |. diff --git a/tests/neg-macros/i19842-a.check b/tests/neg-macros/i19842-a.check index af628c566c15..4d92739e00ff 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:286) | 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-b.check b/tests/neg-macros/i19842-b.check index b402006c2d4b..5a2fe6f39d48 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:286) | 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/i22616.check b/tests/neg-macros/i22616.check new file mode 100644 index 000000000000..d830d0c3fe00 --- /dev/null +++ b/tests/neg-macros/i22616.check @@ -0,0 +1,16 @@ +-- [E219] Staging Issue Error: tests/neg-macros/i22616.scala:13:22 ----------------------------------------------------- +13 | case '{ new caseName(${ Expr(name) }) } => Some(caseName(name)) // error // error + | ^^^^^^^^ + | Quoted pattern type variable `caseName` cannot be instantiated. + | If you meant to refer to a class named `caseName`, wrap it in backticks. + | If you meant to introduce a binding, this is not allowed after `new`. You might + | want to use the lower-level `quotes.reflect` API instead. + | Read more about type variables in quoted pattern in the Scala documentation: + | https://docs.scala-lang.org/scala3/guides/macros/quotes.html#type-variables-in-quoted-patterns + | +-- [E006] Not Found Error: tests/neg-macros/i22616.scala:13:67 --------------------------------------------------------- +13 | case '{ new caseName(${ Expr(name) }) } => Some(caseName(name)) // error // error + | ^^^^ + | Not found: name + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-macros/i22616.scala b/tests/neg-macros/i22616.scala new file mode 100644 index 000000000000..bd86716bc30c --- /dev/null +++ b/tests/neg-macros/i22616.scala @@ -0,0 +1,18 @@ +import scala.quoted.* + +final case class caseName(name: String) extends scala.annotation.Annotation +object caseName { + + given FromExpr[caseName] = + new FromExpr[caseName] { + override def unapply(x: Expr[caseName])(using Quotes): Option[caseName] = + val y: Int = 42 + x match { + case '{ caseName(${ Expr(name) }) } => Some(caseName(name)) + // with/without the following line... + case '{ new caseName(${ Expr(name) }) } => Some(caseName(name)) // error // error + case _ => println(x.show); None + } + } + +} diff --git a/tests/neg-macros/i22616b.check b/tests/neg-macros/i22616b.check new file mode 100644 index 000000000000..3c6007276cef --- /dev/null +++ b/tests/neg-macros/i22616b.check @@ -0,0 +1,13 @@ +-- [E007] Type Mismatch Error: tests/neg-macros/i22616b.scala:17:18 ---------------------------------------------------- +17 | case '{ Foo($y: t) } => // error + | ^^^^^ + | Found: t + | Required: String + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg-macros/i22616b.scala:18:19 -------------------------------------------------------- +18 | '{type S = t; ()} // error + | ^ + | Not found: type t + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-macros/i22616b.scala b/tests/neg-macros/i22616b.scala new file mode 100644 index 000000000000..45926f4e0d37 --- /dev/null +++ b/tests/neg-macros/i22616b.scala @@ -0,0 +1,22 @@ +// This test illustrates a current limitation of quoted pattern type variables, +// which has been discussed in https://github.com/scala/scala3/issues/22616#issuecomment-3012534064: +// These type variables do not have bound in general (see `typedQuotedTypeVar`), +// so they might not conform to the expected type. Here, `t` does not conform +// to `String`. + +import scala.quoted.{FromExpr, Expr, Quotes} + +case class Foo(x: String) + +object Macro: + inline def myMacro(): Unit = + ${ myMacroImpl('{Foo("hello")}) } + + def myMacroImpl(x: Expr[Foo])(using Quotes): Expr[Unit] = + x match + case '{ Foo($y: t) } => // error + '{type S = t; ()} // error + case _ => + println("not a foo") + + '{()} diff --git a/tests/neg-macros/i23008.check b/tests/neg-macros/i23008.check new file mode 100644 index 000000000000..b26ef11d201f --- /dev/null +++ b/tests/neg-macros/i23008.check @@ -0,0 +1,21 @@ + +-- Error: tests/neg-macros/i23008/Test_2.scala:1:24 -------------------------------------------------------------------- +1 |@main def Test = Macros.buildString // error + | ^^^^^^^^^^^^^^^^^^ + | Exception occurred while executing macro expansion. + | java.lang.IllegalArgumentException: requirement failed: value of StringConstant cannot be `null` + | at scala.Predef$.require(Predef.scala:337) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2534) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2533) + | at scala.quoted.ToExpr$StringToExpr.apply(ToExpr.scala:80) + | at scala.quoted.ToExpr$StringToExpr.apply(ToExpr.scala:78) + | at scala.quoted.Expr$.apply(Expr.scala:70) + | at Macros$.buildStringCode(Macro_1.scala:9) + | + |--------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from Macro_1.scala:4 +4 | inline def buildString = ${buildStringCode} + | ^^^^^^^^^^^^^^^^^^ + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i23008/Macro_1.scala b/tests/neg-macros/i23008/Macro_1.scala new file mode 100644 index 000000000000..67bd091cd9f3 --- /dev/null +++ b/tests/neg-macros/i23008/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.quoted.* + +object Macros { + inline def buildString = ${buildStringCode} + + def buildStringCode(using Quotes): Expr[String] = { + import quotes.reflect.* + val str: String = null + Expr(str) + } +} diff --git a/tests/neg-macros/i23008/Test_2.scala b/tests/neg-macros/i23008/Test_2.scala new file mode 100644 index 000000000000..5ebf2715cf14 --- /dev/null +++ b/tests/neg-macros/i23008/Test_2.scala @@ -0,0 +1 @@ +@main def Test = Macros.buildString // error diff --git a/tests/neg/19680.check b/tests/neg/19680.check index 5bdaaad99c2a..f7547d6a4eee 100644 --- a/tests/neg/19680.check +++ b/tests/neg/19680.check @@ -7,7 +7,10 @@ | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | - | Tree: new Config() + | Tree: + | + | new Config() + | | I tried to show that | Config | conforms to diff --git a/tests/neg/19680b.check b/tests/neg/19680b.check index 06ff26ee3289..6c5c60b29dee 100644 --- a/tests/neg/19680b.check +++ b/tests/neg/19680b.check @@ -7,7 +7,10 @@ | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | - | Tree: "hello" + | Tree: + | + | "hello" + | | I tried to show that | ("hello" : String) | conforms to diff --git a/tests/neg/6314-6.check b/tests/neg/6314-6.check index df988f1db9dd..7d6bd182173d 100644 --- a/tests/neg/6314-6.check +++ b/tests/neg/6314-6.check @@ -4,7 +4,7 @@ |object creation impossible, since def apply(fa: String): Int in trait XX in object Test3 is not defined |(Note that | parameter String in def apply(fa: String): Int in trait XX in object Test3 does not match - | parameter Test3.Bar[X & (X & Y)] in def apply(fa: Test3.Bar[X & YY.this.Foo]): Test3.Bar[Y & YY.this.Foo] in trait YY in object Test3 + | parameter Test3.Bar[X & Object with Test3.YY {...}#Foo] in def apply(fa: Test3.Bar[X & YY.this.Foo]): Test3.Bar[Y & YY.this.Foo] in trait YY in object Test3 | ) -- Error: tests/neg/6314-6.scala:52:3 ---------------------------------------------------------------------------------- 52 | (new YY {}).boom // error: object creation impossible @@ -12,5 +12,5 @@ |object creation impossible, since def apply(fa: String): Int in trait XX in object Test4 is not defined |(Note that | parameter String in def apply(fa: String): Int in trait XX in object Test4 does not match - | parameter Test4.Bar[X & (X & Y)] in def apply(fa: Test4.Bar[X & YY.this.FooAlias]): Test4.Bar[Y & YY.this.FooAlias] in trait YY in object Test4 + | parameter Test4.Bar[X & Object with Test4.YY {...}#FooAlias] in def apply(fa: Test4.Bar[X & YY.this.FooAlias]): Test4.Bar[Y & YY.this.FooAlias] in trait YY in object Test4 | ) diff --git a/tests/warn/abstract-givens-new.scala b/tests/neg/abstract-givens-new.scala similarity index 85% rename from tests/warn/abstract-givens-new.scala rename to tests/neg/abstract-givens-new.scala index c18ebe46acf7..1f0e641f9017 100644 --- a/tests/warn/abstract-givens-new.scala +++ b/tests/neg/abstract-givens-new.scala @@ -4,7 +4,7 @@ class C: trait T: given Int is C // ok - given intC: Int is C // warn + given intC: Int is C // error given intC2: (Int is C)() // ok given intC3: Int is C {} // also ok diff --git a/tests/neg/abstract-givens.check b/tests/neg/abstract-givens.check index 1430c5b6e950..51f50db266c2 100644 --- a/tests/neg/abstract-givens.check +++ b/tests/neg/abstract-givens.check @@ -10,7 +10,10 @@ -- [E164] Declaration Error: tests/neg/abstract-givens.scala:9:8 ------------------------------------------------------- 9 | given z[T](using T): Seq[T] = List(summon[T]) // error | ^ - | error overriding given instance z in trait T of type [T](using x$1: T): List[T]; + | error overriding given instance z in trait T² of type [T](using x$1: T): List[T]; | given instance z of type [T](using x$1: T): Seq[T] has incompatible type | + | where: T is a type variable + | T² is a trait in the empty package + | | longer explanation available when compiling with `-explain` diff --git a/tests/neg/applied_constructor_types.check b/tests/neg/applied_constructor_types.check new file mode 100644 index 000000000000..e966c5d15f27 --- /dev/null +++ b/tests/neg/applied_constructor_types.check @@ -0,0 +1,18 @@ +-- [E006] Not Found Error: tests/neg/applied_constructor_types.scala:8:10 ---------------------------------------------- +8 | val v1: f(1) = f(1) // error + | ^ + | Not found: type f + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/applied_constructor_types.scala:9:10 ---------------------------------------------- +9 | val v2: id(1) = f(1) // error + | ^^ + | Not found: type id - did you mean is? + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/applied_constructor_types.scala:10:10 --------------------------------------------- +10 | val v3: idDependent(1) = f(1) // error + | ^^^^^^^^^^^ + | Not found: type idDependent + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/applied_constructor_types.scala b/tests/neg/applied_constructor_types.scala new file mode 100644 index 000000000000..8207b1213851 --- /dev/null +++ b/tests/neg/applied_constructor_types.scala @@ -0,0 +1,10 @@ +import scala.language.experimental.modularity + +def f(x: Int): Int = x +def id[T](x: T): T = x +def idDependent(x: Any): x.type = x + +def test = + val v1: f(1) = f(1) // error + val v2: id(1) = f(1) // error + val v3: idDependent(1) = f(1) // error diff --git a/tests/neg-custom-args/captures/capture-vars-subtyping.scala b/tests/neg/capture-vars-subtyping.scala similarity index 57% rename from tests/neg-custom-args/captures/capture-vars-subtyping.scala rename to tests/neg/capture-vars-subtyping.scala index 1986a0aa33fc..5552448194f1 100644 --- a/tests/neg-custom-args/captures/capture-vars-subtyping.scala +++ b/tests/neg/capture-vars-subtyping.scala @@ -1,34 +1,35 @@ import language.experimental.captureChecking +// no separation checking, TODO enable and move to neg-customargs import caps.* def test[C^] = val a: C = ??? - val b: CapSet^{C^} = a + val b: CapSet^{C} = a val c: C = b - val d: CapSet^{C^, c} = a + 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^] = +def testTrans[C^, D^ <: C, E^ <: {D}, F^ >: C] = val d1: D = ??? - val d2: CapSet^{D^} = d1 + val d2: CapSet^{D} = d1 val d3: D = d2 val e1: E = ??? - val e2: CapSet^{E^} = e1 + 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 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 + val c5: CapSet^{C} = e1 trait A[+T] @@ -37,12 +38,12 @@ 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 b: A[{C}] = a + val c: A[{D}] = a // error + val d: A[{C,D}] = a val e: A[C] = d // error val f: B[C] = ??? - val g: B[CapSet^{C^}] = f + val g: B[{C}] = f val h: B[C] = g - val i: B[CapSet^{C^,D^}] = h // error + val i: B[{C,D}] = h // error val j: B[C] = i diff --git a/tests/neg/cc-fresh-levels.scala b/tests/neg/cc-fresh-levels.scala new file mode 100644 index 000000000000..b3415dd9539c --- /dev/null +++ b/tests/neg/cc-fresh-levels.scala @@ -0,0 +1,19 @@ +import language.experimental.captureChecking +// no separation checking +import caps.* +class IO +class Ref[X](init: X): + private var _data = init + def get: X = _data + def put(y: X): Unit = _data = y +def runIO(op: IO^ => Unit): Unit = () +def test1(a: IO^, b: IO^, c: IO^): Unit = + val r: Ref[IO^] = Ref(a) + r.put(b) // ok + def outer(x: IO^): Unit = + r.put(x) // error + r.put(c) // ok + runIO: (innerIO: IO^) => + r.put(innerIO) // error + runIO: innerIO => // error + r.put(innerIO) diff --git a/tests/neg/context-bounds-migration-future.check b/tests/neg/context-bounds-migration-future.check index f517a1e335c9..f56da5d6b28d 100644 --- a/tests/neg/context-bounds-migration-future.check +++ b/tests/neg/context-bounds-migration-future.check @@ -4,7 +4,3 @@ | method foo does not take more parameters | | longer explanation available when compiling with `-explain` --- Warning: tests/neg/context-bounds-migration-future.scala:6:6 -------------------------------------------------------- -6 |given [T]: C[T] = C[T]() - | ^ - | This old given syntax is no longer supported; use `=>` instead of `:` diff --git a/tests/neg/context-bounds-migration-future.scala b/tests/neg/context-bounds-migration-future.scala index 6d0e94c0b434..0816518d48a9 100644 --- a/tests/neg/context-bounds-migration-future.scala +++ b/tests/neg/context-bounds-migration-future.scala @@ -3,7 +3,7 @@ class C[T] def foo[X: C] = () -given [T]: C[T] = C[T]() +given [T] => C[T] = C[T]() def Test = foo(C[Int]()) // error diff --git a/tests/neg/context-function-syntax.scala b/tests/neg/context-function-syntax.scala index 1f9b74cc69a6..5df5fd41aeb6 100644 --- a/tests/neg/context-function-syntax.scala +++ b/tests/neg/context-function-syntax.scala @@ -1,8 +1,8 @@ val test = - (using x: Int) => x // error // error + (using x: Int) => x // error // error // error val f = () ?=> 23 // error -val g: ContextFunction0[Int] = ??? // ok +val g: ContextFunction0[Int] = ??? // error at typer for RHS not expanded val h: () ?=> Int = ??? // error object Example3 extends App { diff --git a/tests/neg/deptypes.scala b/tests/neg/deptypes.scala index 39b1e42ccbca..a64143a05185 100644 --- a/tests/neg/deptypes.scala +++ b/tests/neg/deptypes.scala @@ -2,10 +2,10 @@ type Vec[T] = (n: Int) =>> Array[T] // error: not yet implemented -type Matrix[T](m: Int, n: Int) = Vec[Vec[T](n)](m) // error: not yet implemented +type Matrix[T](m: Int, n: Int) = Vec[Vec[T](n)](m) // error // error: not yet implemented -type Tensor2[T](m: Int)(n: Int) = Matrix[T](m, n) // error: not yet implemented +type Tensor2[T](m: Int)(n: Int) = Matrix[T](m, n) -val x: Vec[Int](10) = ??? // error: not yet implemented +val x: Vec[Int](10) = ??? val n = 10 -type T = Vec[String](n) // error: not yet implemented \ No newline at end of file +type T = Vec[String](n) diff --git a/tests/neg/doubleDefinition.check b/tests/neg/doubleDefinition.check index 953a0ba8c128..3c164fcf878b 100644 --- a/tests/neg/doubleDefinition.check +++ b/tests/neg/doubleDefinition.check @@ -1,130 +1,166 @@ -- [E120] Naming Error: tests/neg/doubleDefinition.scala:14:5 ---------------------------------------------------------- 14 | def foo(x: List[B]): Function1[B, B] = ??? // error: same jvm signature | ^ - | Double definition: + | Conflicting definitions: | def foo(x: List[A]): A => A in class Test2 at line 13 and | def foo(x: List[B]): B => B in class Test2 at line 14 - | have the same type after erasure. + | have the same type (x: List): Function1 after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions | for disambiguation. + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:21:5 ---------------------------------------------------------- 21 | def foo(x: List[A]): Function2[B, B, B] = ??? // error | ^ - | Double definition: + | Conflicting definitions: | def foo(x: List[A]): A => A in class Test3 at line 20 and | def foo(x: List[A]): (B, B) => B in class Test3 at line 21 | have matching parameter types. -- [E120] Naming Error: tests/neg/doubleDefinition.scala:26:5 ---------------------------------------------------------- 26 | def foo = 2 // error | ^ - | Double definition: + | Conflicting definitions: | val foo: Int in class Test4 at line 25 and | def foo: Int in class Test4 at line 26 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:31:5 ---------------------------------------------------------- 31 | val foo = 1 // error | ^ - | Double definition: + | Conflicting definitions: | def foo: Int in class Test4b at line 30 and | val foo: Int in class Test4b at line 31 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:36:5 ---------------------------------------------------------- 36 | var foo = 1 // error | ^ - | Double definition: + | Conflicting definitions: | def foo: Int in class Test4c at line 35 and | var foo: Int in class Test4c at line 36 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:41:5 ---------------------------------------------------------- 41 | def foo = 2 // error | ^ - | Double definition: + | Conflicting definitions: | var foo: Int in class Test4d at line 40 and | def foo: Int in class Test4d at line 41 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:55:5 ---------------------------------------------------------- 55 | def foo(x: List[B]): Function1[B, B] = ??? // error: same jvm signature | ^ - | Double definition: + | Conflicting definitions: | def foo(x: List[A]): A => A in trait Test6 at line 54 and | def foo(x: List[B]): B => B in trait Test6 at line 55 - | have the same type after erasure. + | have the same type (x: List): Function1 after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions | for disambiguation. + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:62:5 ---------------------------------------------------------- 62 | def foo(x: List[A]): Function2[B, B, B] = ??? // error | ^ - | Double definition: + | Conflicting definitions: | def foo(x: List[A]): A => A in trait Test7 at line 61 and | def foo(x: List[A]): (B, B) => B in trait Test7 at line 62 | have matching parameter types. -- [E120] Naming Error: tests/neg/doubleDefinition.scala:67:5 ---------------------------------------------------------- 67 | def foo = 2 // error | ^ - | Double definition: + | Conflicting definitions: | val foo: Int in class Test8 at line 66 and | def foo: Int in class Test8 at line 67 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:72:5 ---------------------------------------------------------- 72 | val foo = 1 // error | ^ - | Double definition: + | Conflicting definitions: | def foo: Int in class Test8b at line 71 and | val foo: Int in class Test8b at line 72 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:77:5 ---------------------------------------------------------- 77 | var foo = 1 // error | ^ - | Double definition: + | Conflicting definitions: | def foo: Int in class Test8c at line 76 and | var foo: Int in class Test8c at line 77 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:82:5 ---------------------------------------------------------- 82 | def foo = 2 // error | ^ - | Double definition: + | Conflicting definitions: | var foo: Int in class Test8d at line 81 and | def foo: Int in class Test8d at line 82 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:88:5 ---------------------------------------------------------- 88 | def foo: String // error | ^ - | Double definition: + | Conflicting definitions: | val foo: Int in class Test9 at line 87 and | def foo: String in class Test9 at line 88 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:92:5 ---------------------------------------------------------- 92 | def foo: Int // error | ^ - | Double definition: + | Conflicting definitions: | val foo: Int in class Test10 at line 91 and | def foo: Int in class Test10 at line 92 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:96:5 ---------------------------------------------------------- 96 | def foo: String // error | ^ - | Double definition: + | Conflicting definitions: | val foo: Int in class Test11 at line 95 and | def foo: String in class Test11 at line 96 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:100:5 --------------------------------------------------------- 100 | def foo: Int // error | ^ - | Double definition: + | Conflicting definitions: | val foo: Int in class Test12 at line 99 and | def foo: Int in class Test12 at line 100 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:104:5 --------------------------------------------------------- 104 | def foo: String // error | ^ - | Double definition: + | Conflicting definitions: | var foo: Int in class Test13 at line 103 and | def foo: String in class Test13 at line 104 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:108:5 --------------------------------------------------------- 108 | def foo: Int // error | ^ - | Double definition: + | Conflicting definitions: | var foo: Int in class Test14 at line 107 and | def foo: Int in class Test14 at line 108 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:112:5 --------------------------------------------------------- 112 | def foo: String // error | ^ - | Double definition: + | Conflicting definitions: | var foo: Int in class Test15 at line 111 and | def foo: String in class Test15 at line 112 + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/doubleDefinition.scala:116:5 --------------------------------------------------------- 116 | def foo: Int // error | ^ - | Double definition: + | Conflicting definitions: | var foo: Int in class Test16 at line 115 and | def foo: Int in class Test16 at line 116 + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/erased-6.scala b/tests/neg/erased-6.scala index 4585ab876b3d..76fa1b937f00 100644 --- a/tests/neg/erased-6.scala +++ b/tests/neg/erased-6.scala @@ -1,7 +1,7 @@ //> using options -language:experimental.erasedDefinitions object Test { - erased def foo: Foo = new Foo + erased val foo: Foo = new Foo // error, Foo is not noInits foo.x() // error foo.y // error foo.z // error diff --git a/tests/neg/erased-assign.scala b/tests/neg/erased-assign.scala index 5026ca3f1856..61c8802e576e 100644 --- a/tests/neg/erased-assign.scala +++ b/tests/neg/erased-assign.scala @@ -4,7 +4,7 @@ object Test { var i: Int = 1 def foo(erased a: Int): Int = { i = a // error - erased def r = { + inline def r = { i = a () } diff --git a/tests/neg/erased-can-serialize.scala b/tests/neg/erased-can-serialize.scala new file mode 100644 index 000000000000..13d9ad072f01 --- /dev/null +++ b/tests/neg/erased-can-serialize.scala @@ -0,0 +1,17 @@ +import language.experimental.erasedDefinitions + +class CanSerialize[T] + +inline given CanSerialize[String] = CanSerialize() +inline given [T: CanSerialize] => CanSerialize[List[T]] = CanSerialize() + +def safeWriteObject[T <: java.io.Serializable](out: java.io.ObjectOutputStream, x: T)(using erased CanSerialize[T]) = + out.writeObject(x) + +def writeList[T](out: java.io.ObjectOutputStream, xs: List[T])(using erased CanSerialize[T]) = + safeWriteObject(out, xs) + +@main def Test(out: java.io.ObjectOutputStream) = + writeList(out, List(List("a", "b"))) + writeList(out, List[Int => Int](x => x + 1, y => y * 2)) // error + diff --git a/tests/neg/erased-class.scala b/tests/neg/erased-class.scala index 96a1c8769bb1..53dc08a38ccd 100644 --- a/tests/neg/erased-class.scala +++ b/tests/neg/erased-class.scala @@ -1,10 +1,10 @@ import language.experimental.erasedDefinitions import scala.annotation.compileTimeOnly -erased class AA -erased class BB extends AA // ok +class AA extends compiletime.Erased +class BB extends AA // ok @main def Test = - val f1: Array[AA] = compiletime.erasedValue // error // error - def f2(x: Int): Array[AA] = compiletime.erasedValue // error // error - def bar: AA = compiletime.erasedValue // ok - val baz: AA = compiletime.erasedValue // ok + val f1: Array[AA] = caps.unsafe.unsafeErasedValue // error + def f2(x: Int): Array[AA] = caps.unsafe.unsafeErasedValue // error + def bar: AA = caps.unsafe.unsafeErasedValue // error + val baz: AA = caps.unsafe.unsafeErasedValue // ok diff --git a/tests/neg/erased-lazy-given.check b/tests/neg/erased-lazy-given.check new file mode 100644 index 000000000000..2c6ac62026d8 --- /dev/null +++ b/tests/neg/erased-lazy-given.check @@ -0,0 +1,7 @@ +-- [E218] Type Error: tests/neg/erased-lazy-given.scala:8:13 ----------------------------------------------------------- +8 | lazy given E = E() // error + | ^ + | given instance given_E is implicitly `erased` since its type extends trait `compiletime.Erased`. + | But `erased` is not allowed for this kind of definition. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/erased-lazy-given.scala b/tests/neg/erased-lazy-given.scala new file mode 100644 index 000000000000..5b0ab5721fad --- /dev/null +++ b/tests/neg/erased-lazy-given.scala @@ -0,0 +1,9 @@ +import language.experimental.erasedDefinitions + +class E extends compiletime.Erased + +object test1: + given E = E() // OK +object test2: + lazy given E = E() // error + diff --git a/tests/neg/erased-lazy-val.check b/tests/neg/erased-lazy-val.check new file mode 100644 index 000000000000..88a29299fdf1 --- /dev/null +++ b/tests/neg/erased-lazy-val.check @@ -0,0 +1,19 @@ +-- [E218] Type Error: tests/neg/erased-lazy-val.scala:6:18 ------------------------------------------------------------- +6 | erased lazy val i: Int = 1 // error + | ^ + | `erased` is not allowed for this kind of definition. + | + | longer explanation available when compiling with `-explain` +-- [E218] Type Error: tests/neg/erased-lazy-val.scala:7:11 ------------------------------------------------------------- +7 | lazy val e: E = E() // error + | ^ + | lazy value e is implicitly `erased` since its type extends trait `compiletime.Erased`. + | But `erased` is not allowed for this kind of definition. + | + | longer explanation available when compiling with `-explain` +-- [E218] Type Error: tests/neg/erased-lazy-val.scala:8:2 -------------------------------------------------------------- +8 | erased object obj1 // error + | ^ + | `erased` is not allowed for this kind of definition. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/erased-lazy-val.scala b/tests/neg/erased-lazy-val.scala index 271f87cc2cf0..cd3fa34b2a3c 100644 --- a/tests/neg/erased-lazy-val.scala +++ b/tests/neg/erased-lazy-val.scala @@ -1,5 +1,10 @@ -//> using options -language:experimental.erasedDefinitions +import language.experimental.erasedDefinitions + +class E extends compiletime.Erased object Test { erased lazy val i: Int = 1 // error + lazy val e: E = E() // error + erased object obj1 // error + object obj2 extends E // ok, obj2 is not erased } diff --git a/tests/neg/erased-path.scala b/tests/neg/erased-path.scala index ece90e563483..6666165d5cc6 100644 --- a/tests/neg/erased-path.scala +++ b/tests/neg/erased-path.scala @@ -6,6 +6,6 @@ trait Obj { erased val s: Sys lazy val t: Sys - type S = s.X // error: not a legal path, since nonfinal + type S = s.X // now OK, was error: not a legal path, since nonfinal type T = t.X // error: not a legal path, since nonfinal } \ No newline at end of file diff --git a/tests/neg/erasedValueb.check b/tests/neg/erasedValueb.check new file mode 100644 index 000000000000..22d13a25ebe0 --- /dev/null +++ b/tests/neg/erasedValueb.check @@ -0,0 +1,10 @@ +-- Error: tests/neg/erasedValueb.scala:7:7 ----------------------------------------------------------------------------- +7 | foo0(erasedValue[Int]) // error + | ^^^^^^^^^^^ + | method erasedValue is declared as `erased`, but is in fact used +-- [E217] Type Error: tests/neg/erasedValueb.scala:8:18 ---------------------------------------------------------------- +8 | foo1(erasedValue[Int]) // error + | ^^^^^^^^^^^^^^^^ + | argument to an erased parameter fails to be a pure expression + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/erasedValueb.scala b/tests/neg/erasedValueb.scala index 5c1f1d359e93..a25cf66ec3cb 100644 --- a/tests/neg/erasedValueb.scala +++ b/tests/neg/erasedValueb.scala @@ -5,5 +5,5 @@ object Test { def foo0(a: Int): Int = 3 def foo1(erased a: Int): Int = 3 foo0(erasedValue[Int]) // error - foo1(erasedValue[Int]) + foo1(erasedValue[Int]) // error } diff --git a/tests/neg/experimental-imports.scala b/tests/neg/experimental-imports.scala index e3a91be45f08..10e655ddf3b2 100644 --- a/tests/neg/experimental-imports.scala +++ b/tests/neg/experimental-imports.scala @@ -8,14 +8,14 @@ object Object1: import language.experimental.namedTypeArguments import language.experimental.genericNumberLiterals import language.experimental.erasedDefinitions - erased def f = 1 + erased val f = 1 object Object2: import language.experimental.fewerBraces // error import language.experimental.namedTypeArguments // error import language.experimental.genericNumberLiterals // error import language.experimental.erasedDefinitions // error - erased def f = 1 + erased val f = 1 @experimental object Class1: @@ -23,14 +23,14 @@ object Class1: import language.experimental.namedTypeArguments import language.experimental.genericNumberLiterals import language.experimental.erasedDefinitions - erased def f = 1 + erased val f = 1 object Class2: import language.experimental.fewerBraces // error import language.experimental.namedTypeArguments // error import language.experimental.genericNumberLiterals // error import language.experimental.erasedDefinitions // error - erased def f = 1 + erased val f = 1 @experimental def fun1 = @@ -38,11 +38,11 @@ def fun1 = import language.experimental.namedTypeArguments import language.experimental.genericNumberLiterals import language.experimental.erasedDefinitions - erased def f = 1 + erased val f = 1 def fun2 = import language.experimental.fewerBraces // error import language.experimental.namedTypeArguments // error import language.experimental.genericNumberLiterals // error import language.experimental.erasedDefinitions // error - erased def f = 1 + erased val f = 1 diff --git a/tests/neg/experimental.scala b/tests/neg/experimental.scala index f35a7ca19d7f..583a8c5aa183 100644 --- a/tests/neg/experimental.scala +++ b/tests/neg/experimental.scala @@ -13,7 +13,7 @@ class Test1 { import scala.compiletime.erasedValue type UnivEq[A] object UnivEq: - erased def force[A]: UnivEq[A] = erasedValue + inline def force[A]: UnivEq[A] = erasedValue extension [A](erased proof: UnivEq[A]) inline def univEq(a: A, b: A): Boolean = a == b diff --git a/tests/neg/exports.check b/tests/neg/exports.check index 79951cebfc39..bb4d7ab59421 100644 --- a/tests/neg/exports.check +++ b/tests/neg/exports.check @@ -15,33 +15,39 @@ -- [E120] Naming Error: tests/neg/exports.scala:23:33 ------------------------------------------------------------------ 23 | export printUnit.{stat => _, _} // error: double definition | ^ - | Double definition: + | Conflicting definitions: | def status: List[String] in class Copier at line 28 and | final def status: List[String] in class Copier at line 23 - | have the same type after erasure. + | have the same type (): List after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions | for disambiguation. + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/exports.scala:24:20 ------------------------------------------------------------------ 24 | export scanUnit._ // error: double definition | ^ - | Double definition: + | Conflicting definitions: | final def status: List[String] in class Copier at line 23 and | final def status: List[String] in class Copier at line 24 - | have the same type after erasure. + | have the same type (): List after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions | for disambiguation. + | + | longer explanation available when compiling with `-explain` -- [E120] Naming Error: tests/neg/exports.scala:26:21 ------------------------------------------------------------------ 26 | export printUnit.status // error: double definition | ^ - | Double definition: + | Conflicting definitions: | final def status: List[String] in class Copier at line 24 and | final def status: List[String] in class Copier at line 26 - | have the same type after erasure. + | have the same type (): List after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions | for disambiguation. + | + | longer explanation available when compiling with `-explain` -- Error: tests/neg/exports.scala:35:24 -------------------------------------------------------------------------------- 35 | export this.{concat => ++} // error: no eligible member | ^^^^^^^^^^^^ @@ -55,9 +61,11 @@ -- [E120] Naming Error: tests/neg/exports.scala:46:15 ------------------------------------------------------------------ 46 | export bar._ // error: double definition | ^ - | Double definition: + | Conflicting definitions: | val bar: Bar in class Baz at line 45 and | final def bar: (Baz.this.bar.bar : => (Baz.this.bar.baz.bar : Bar)) in class Baz at line 46 + | + | longer explanation available when compiling with `-explain` -- [E083] Type Error: tests/neg/exports.scala:57:11 -------------------------------------------------------------------- 57 | export printer.* // error: not stable | ^^^^^^^ diff --git a/tests/neg/extensions-can-only-have-using.scala b/tests/neg/extensions-can-only-have-using.scala new file mode 100644 index 000000000000..fcf451fc7258 --- /dev/null +++ b/tests/neg/extensions-can-only-have-using.scala @@ -0,0 +1,3 @@ +extension(x: Any)() // error + def f = 42 +val x = Nil.f diff --git a/tests/neg/gadt-alt-expr1.scala b/tests/neg/gadt-alt-expr1.scala new file mode 100644 index 000000000000..dc3296e781c8 --- /dev/null +++ b/tests/neg/gadt-alt-expr1.scala @@ -0,0 +1,13 @@ +enum Expr[+T]: + case I1() extends Expr[Int] + case I2() extends Expr[Int] + case B() extends Expr[Boolean] +import Expr.* +def foo[T](e: Expr[T]): T = + e match + case I1() | I2() => 42 // ok + case B() => true +def bar[T](e: Expr[T]): T = + e match + case I1() | B() => 42 // error + case I2() => 0 diff --git a/tests/neg/gadt-alt-expr2.scala b/tests/neg/gadt-alt-expr2.scala new file mode 100644 index 000000000000..a7e05cbe0870 --- /dev/null +++ b/tests/neg/gadt-alt-expr2.scala @@ -0,0 +1,15 @@ +enum Expr[+T]: + case I1() extends Expr[Int] + case I2() extends Expr[Int] + case I3() extends Expr[Int] + case I4() extends Expr[Int] + case I5() extends Expr[Int] + case B() extends Expr[Boolean] +import Expr.* +def test1[T](e: Expr[T]): T = + e match + case I1() | I2() | I3() | I4() | I5() => 42 // ok + case B() => true +def test2[T](e: Expr[T]): T = + e match + case I1() | I2() | I3() | I4() | I5() | B() => 42 // error diff --git a/tests/neg/gadt-alt-expr3.scala b/tests/neg/gadt-alt-expr3.scala new file mode 100644 index 000000000000..a6ebf69ba8d1 --- /dev/null +++ b/tests/neg/gadt-alt-expr3.scala @@ -0,0 +1,34 @@ +trait A +trait B extends A +trait C extends B +trait D +enum Expr[+T]: + case IsA() extends Expr[A] + case IsB() extends Expr[B] + case IsC() extends Expr[C] + case IsD() extends Expr[D] +import Expr.* +def test1[T](e: Expr[T]): T = e match + case IsA() => new A {} + case IsB() => new B {} + case IsC() => new C {} +def test2[T](e: Expr[T]): T = e match + case IsA() | IsB() => + // IsA() implies T >: A + // IsB() implies T >: B + // So T >: B is chosen + new B {} + case IsC() => new C {} +def test3[T](e: Expr[T]): T = e match + case IsA() | IsB() | IsC() => + // T >: C is chosen + new C {} +def test4[T](e: Expr[T]): T = e match + case IsA() | IsB() | IsC() => + new B {} // error +def test5[T](e: Expr[T]): T = e match + case IsA() | IsB() => + new A {} // error +def test6[T](e: Expr[T]): T = e match + case IsA() | IsC() | IsD() => + new C {} // error diff --git a/tests/neg/gadt-alt-expr4.scala b/tests/neg/gadt-alt-expr4.scala new file mode 100644 index 000000000000..6b85655c40c1 --- /dev/null +++ b/tests/neg/gadt-alt-expr4.scala @@ -0,0 +1,19 @@ +trait A +trait B extends A +trait C extends B +enum Expr[T]: + case IsA() extends Expr[A] + case IsB() extends Expr[B] + case IsC() extends Expr[C] +import Expr.* +def test1[T](e: Expr[T]): T = e match + case IsA() => new A {} + case IsB() => new B {} + case IsC() => new C {} +def test2[T](e: Expr[T]): T = e match + case IsA() | IsB() => + // IsA() implies T =:= A + // IsB() implies T =:= B + // No necessary constraint can be found + new B {} // error + case IsC() => new C {} diff --git a/tests/neg/gadt-alt-expr5.scala b/tests/neg/gadt-alt-expr5.scala new file mode 100644 index 000000000000..19993a6f34ab --- /dev/null +++ b/tests/neg/gadt-alt-expr5.scala @@ -0,0 +1,14 @@ +trait A +trait B extends A +trait C extends B +enum Expr[-T]: + case IsA() extends Expr[A] + case IsB() extends Expr[B] + case IsC() extends Expr[C] +import Expr.* +def test1[T](e: Expr[T]): Unit = e match + case IsA() | IsB() => + val t1: T = ??? + val t2: A = t1 + val t3: B = t1 // error + case IsC() => diff --git a/tests/neg/gadt-alternatives.scala b/tests/neg/gadt-alternatives.scala index 034f88ea5f24..e105e1f1d0fd 100644 --- a/tests/neg/gadt-alternatives.scala +++ b/tests/neg/gadt-alternatives.scala @@ -6,4 +6,4 @@ import Expr.* def eval[T](e: Expr[T]): T = e match case StringVal(_) | IntVal(_) => "42" // error def eval1[T](e: Expr[T]): T = e match - case IntValAlt(_) | IntVal(_) => 42 // error // limitation + case IntValAlt(_) | IntVal(_) => 42 // previously error, now ok diff --git a/tests/neg/hidden-type-errors.check b/tests/neg/hidden-type-errors.check index 2f4a1748dc67..47f5d454c297 100644 --- a/tests/neg/hidden-type-errors.check +++ b/tests/neg/hidden-type-errors.check @@ -7,7 +7,10 @@ | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | - | Tree: t12717.A.bar("XXX") + | Tree: + | + | t12717.A.bar("XXX") + | | I tried to show that | String | conforms to diff --git a/tests/neg/i10666.check b/tests/neg/i10666.check index a70aa9815dc5..491b88f1ffa5 100644 --- a/tests/neg/i10666.check +++ b/tests/neg/i10666.check @@ -1,6 +1,6 @@ -- Error: tests/neg/i10666.scala:8:6 ----------------------------------------------------------------------------------- 8 |class Bar extends Foo { // error - | ^ + | ^^^ | class Bar needs to be abstract, since def foo[T <: B](tx: T): Unit in trait Foo is not defined | (Note that | parameter T in def foo[T <: B](tx: T): Unit in trait Foo does not match diff --git a/tests/neg/i123577.check b/tests/neg/i123577.check new file mode 100644 index 000000000000..6d8f1403b819 --- /dev/null +++ b/tests/neg/i123577.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/i123577.scala:11:4 ------------------------------------------------------------ +11 | (msg: String) => ??? // error + | ^^^^^^^^^^^^^^^^^^^^ + | Found: String => Nothing + | Required: MillRpcChannel + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i123577.scala b/tests/neg/i123577.scala new file mode 100644 index 000000000000..fe69d3477757 --- /dev/null +++ b/tests/neg/i123577.scala @@ -0,0 +1,13 @@ +trait MillRpcMessage { + type Response +} + +trait MillRpcChannel { + def apply(requestId: Long, input: MillRpcMessage): input.Response +} + +object MillRpcChannel { + def createChannel: MillRpcChannel = { + (msg: String) => ??? // error + } +} diff --git a/tests/neg/i12828.check b/tests/neg/i12828.check index 070633fc35b3..e2a1cdb92dcd 100644 --- a/tests/neg/i12828.check +++ b/tests/neg/i12828.check @@ -1,6 +1,6 @@ -- Error: tests/neg/i12828.scala:7:7 ----------------------------------------------------------------------------------- 7 |object Baz extends Bar[Int] // error: not implemented - | ^ + | ^^^ | object creation impossible, since def foo(x: A): Unit in trait Foo is not defined | (Note that | parameter A in def foo(x: A): Unit in trait Foo does not match diff --git a/tests/neg/i13044.check b/tests/neg/i13044.check index e504b14185da..71fef532ec43 100644 --- a/tests/neg/i13044.check +++ b/tests/neg/i13044.check @@ -1,63 +1,18 @@ --- Error: tests/neg/i13044.scala:61:40 --------------------------------------------------------------------------------- -61 | implicit def typeSchema: Schema[A] = Schema.gen // error // error +-- [E172] Type Error: tests/neg/i13044.scala:61:40 --------------------------------------------------------------------- +61 | implicit def typeSchema: Schema[A] = Schema.gen // error | ^^^^^^^^^^ - | given instance gen is declared as `inline`, but was not inlined + | No given instance of type Schema[B] was found. + | I found: | - | Try increasing `-Xmax-inlines` above 32 + | Schema.gen[B] + | + | But given instance gen in trait SchemaDerivation does not match type Schema[B]. |-------------------------------------------------------------------------------------------------------------------- |Inline stack trace |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:17 17 | val builder = summonInline[Schema[t]].asInstanceOf[Schema[Any]] - | ^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:17 -29 | lazy val fields = recurse[m.MirroredElemTypes] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:17 -33 | inline given gen[A]: Schema[A] = derived - | ^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:17 -17 | val builder = summonInline[Schema[t]].asInstanceOf[Schema[Any]] - | ^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:17 -29 | lazy val fields = recurse[m.MirroredElemTypes] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:17 -33 | inline given gen[A]: Schema[A] = derived - | ^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:17 -17 | val builder = summonInline[Schema[t]].asInstanceOf[Schema[Any]] - | ^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:17 -29 | lazy val fields = recurse[m.MirroredElemTypes] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:17 -33 | inline given gen[A]: Schema[A] = derived - | ^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:17 -17 | val builder = summonInline[Schema[t]].asInstanceOf[Schema[Any]] - | ^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:17 -29 | lazy val fields = recurse[m.MirroredElemTypes] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:17 -33 | inline given gen[A]: Schema[A] = derived - | ^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:17 -17 | val builder = summonInline[Schema[t]].asInstanceOf[Schema[Any]] - | ^ + | ^^^^^^^^^^^^^^^^^^^^^^^ |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:17 18 | builder :: recurse[ts] @@ -71,76 +26,3 @@ 33 | inline given gen[A]: Schema[A] = derived | ^^^^^^^ -------------------------------------------------------------------------------------------------------------------- --- Error: tests/neg/i13044.scala:61:40 --------------------------------------------------------------------------------- -61 | implicit def typeSchema: Schema[A] = Schema.gen // error // error - | ^^^^^^^^^^ - | method recurse is declared as `inline`, but was not inlined - | - | Try increasing `-Xmax-inlines` above 32 - |-------------------------------------------------------------------------------------------------------------------- - |Inline stack trace - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -18 | builder :: recurse[ts] - | ^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -29 | lazy val fields = recurse[m.MirroredElemTypes] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -33 | inline given gen[A]: Schema[A] = derived - | ^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -17 | val builder = summonInline[Schema[t]].asInstanceOf[Schema[Any]] - | ^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -29 | lazy val fields = recurse[m.MirroredElemTypes] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -33 | inline given gen[A]: Schema[A] = derived - | ^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -17 | val builder = summonInline[Schema[t]].asInstanceOf[Schema[Any]] - | ^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -29 | lazy val fields = recurse[m.MirroredElemTypes] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -33 | inline given gen[A]: Schema[A] = derived - | ^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -17 | val builder = summonInline[Schema[t]].asInstanceOf[Schema[Any]] - | ^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -29 | lazy val fields = recurse[m.MirroredElemTypes] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -33 | inline given gen[A]: Schema[A] = derived - | ^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -17 | val builder = summonInline[Schema[t]].asInstanceOf[Schema[Any]] - | ^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -18 | builder :: recurse[ts] - | ^^^^^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -29 | lazy val fields = recurse[m.MirroredElemTypes] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from i13044.scala:18 -33 | inline given gen[A]: Schema[A] = derived - | ^^^^^^^ - -------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i13044.scala b/tests/neg/i13044.scala index 2b00fc188f8c..2f9cae2f9039 100644 --- a/tests/neg/i13044.scala +++ b/tests/neg/i13044.scala @@ -58,5 +58,5 @@ case class B(c: C) case class A(a: A, b: B) object TestApp { - implicit def typeSchema: Schema[A] = Schema.gen // error // error + implicit def typeSchema: Schema[A] = Schema.gen // error } diff --git a/tests/neg/i13466.check b/tests/neg/i13466.check index a15ae059427f..ad097ddae96b 100644 --- a/tests/neg/i13466.check +++ b/tests/neg/i13466.check @@ -1,6 +1,6 @@ -- Error: tests/neg/i13466.scala:9:6 ----------------------------------------------------------------------------------- 9 |given none: SomeTrait[Finally] with {} // error - | ^ + | ^^^^ | object creation impossible, since: | it has 3 unimplemented members. | /** As seen from module class none$, the missing signatures are as follows. diff --git a/tests/neg/i14966a.check b/tests/neg/i14966a.check index 777d1ec74955..fd941509807a 100644 --- a/tests/neg/i14966a.check +++ b/tests/neg/i14966a.check @@ -1,10 +1,12 @@ -- [E120] Naming Error: tests/neg/i14966a.scala:3:6 -------------------------------------------------------------------- 3 | def f(x: List[Int]): String = ??? // error | ^ - | Double definition: + | Conflicting definitions: | def f[X <: String](x: List[X]): String in class Test at line 2 and | def f(x: List[Int]): String in class Test at line 3 | have the same type (x: scala.collection.immutable.List): String after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions | for disambiguation. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i15144.scala b/tests/neg/i15144.scala new file mode 100644 index 000000000000..af73a9c08e03 --- /dev/null +++ b/tests/neg/i15144.scala @@ -0,0 +1,16 @@ +//> using options --release 11 +// test: -jvm 17+ +// Must be tested where release target < current JVM. +// Maybe a bug that ct.sym is not used if release == JVM version. + +import java.time.Instant + +class C: + def f: Instant = new Instant // error + def g: Instant = Instant() // error + def p: P = new P // error + +class P private () + +@main def test() = println: + C().f diff --git a/tests/neg/i15784.check b/tests/neg/i15784.check index c1540b33d956..d8c9c193c7dc 100644 --- a/tests/neg/i15784.check +++ b/tests/neg/i15784.check @@ -10,3 +10,17 @@ | Not found: A | | longer explanation available when compiling with `-explain` +-- Warning: tests/neg/i15784.scala:7:7 --------------------------------------------------------------------------------- +7 | case X: Int => X // warn + | ^ + | Type ascriptions after patterns other than: + | * variable pattern, e.g. `case x: String =>` + | * number literal pattern, e.g. `case 10.5: Double =>` + | are no longer supported. Remove the type ascription or move it to a separate variable pattern. +-- Warning: tests/neg/i15784.scala:10:7 -------------------------------------------------------------------------------- +10 | case `Int`: Int => `Int` // warn + | ^^^^^ + | Type ascriptions after patterns other than: + | * variable pattern, e.g. `case x: String =>` + | * number literal pattern, e.g. `case 10.5: Double =>` + | are no longer supported. Remove the type ascription or move it to a separate variable pattern. diff --git a/tests/neg/i15784.scala b/tests/neg/i15784.scala index a58a27fb3cfa..4be909198eaa 100644 --- a/tests/neg/i15784.scala +++ b/tests/neg/i15784.scala @@ -1,4 +1,10 @@ def i15784 = List(42) match case List(_, Rest @ `a`) => Rest // error case List(_, Rest @ A) => Rest // error - case _ => ??? \ No newline at end of file + case _ => ??? + +def case2 = 42 match + case X: Int => X // warn + +def case3 = 42 match + case `Int`: Int => `Int` // warn diff --git a/tests/neg/i16842.check b/tests/neg/i16842.check index 8cad4bc7656f..936b08f95dbb 100644 --- a/tests/neg/i16842.check +++ b/tests/neg/i16842.check @@ -1,16 +1,4 @@ --- [E007] Type Mismatch Error: tests/neg/i16842.scala:24:8 ------------------------------------------------------------- -24 | Liter(SemanticArray[SemanticInt.type], x) // error // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: Int => SemanticArray[SemanticInt.type] - | Required: SemanticArray[SemanticType] - | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/i16842.scala:24:41 ------------------------------------------------------------ -24 | Liter(SemanticArray[SemanticInt.type], x) // error // error - | ^ - | Found: (x : List[Expr2[SemanticInt.type]]) - | Required: ty.T - | Note that implicit conversions were not tried because the result of an implicit conversion - | must be more specific than ty.T - | - | longer explanation available when compiling with `-explain` +-- Error: tests/neg/i16842.scala:24:7 ---------------------------------------------------------------------------------- +24 | Liter(SemanticArray[SemanticInt.type], x) // error + | ^ + | invalid new prefix (dim: Int): SemanticArray[SemanticInt.type] cannot replace ty.type in type ty.T diff --git a/tests/neg/i16842.scala b/tests/neg/i16842.scala index 1e7e5cc14339..e9935b46c01d 100644 --- a/tests/neg/i16842.scala +++ b/tests/neg/i16842.scala @@ -21,5 +21,5 @@ def typecheckArrayLiter( a: ArrayLiter ): Liter[SemanticArray[SemanticType]] = { val x: List[Expr2[SemanticInt.type]] = List() - Liter(SemanticArray[SemanticInt.type], x) // error // error + Liter(SemanticArray[SemanticInt.type], x) // error } diff --git a/tests/neg/i17132.min.check b/tests/neg/i17132.min.check new file mode 100644 index 000000000000..f23d3e91549a --- /dev/null +++ b/tests/neg/i17132.min.check @@ -0,0 +1,31 @@ +-- Error: tests/neg/i17132.min.scala:4:7 ------------------------------------------------------------------------------- +4 |class Q[T <: P[Any]] extends P[R[T]] // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Recursion limit exceeded. + | Maybe there is an illegal cyclic reference? + | If that's not the case, you could also try to increase the stacksize using the -Xss JVM option. + | For the unprocessed stack trace, compile with -Xno-enrich-error-messages. + | A recurring operation is (inner to outer): + | + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | ... + | + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type t match ... + | reduce type T match ... diff --git a/tests/neg/i17132.min.scala b/tests/neg/i17132.min.scala new file mode 100644 index 000000000000..903b19e5cfed --- /dev/null +++ b/tests/neg/i17132.min.scala @@ -0,0 +1,9 @@ + +class P[T] +//class Q[T] extends P[R[T]] // ok +class Q[T <: P[Any]] extends P[R[T]] // error +//type Q[T <: P[Any]] <: P[R[T]] // ok + +type R[U] = U match + case Q[t] => R[t] + case P[t] => t diff --git a/tests/neg/i17132.scala b/tests/neg/i17132.scala new file mode 100644 index 000000000000..d4b97e54293a --- /dev/null +++ b/tests/neg/i17132.scala @@ -0,0 +1,11 @@ + +sealed trait Transformation[T] + +case object Count extends Transformation[Int] +case class MultiTransformation[T1 <: Transformation[?], T2 <: Transformation[?]](t1: T1, t2: T2) // error cyclic + extends Transformation[MultiTransformationResult[T1, T2]] + +type MultiTransformationResult[T1 <: Transformation[?], T2 <: Transformation[?]] <: Tuple = (T1, T2) match { + case (Transformation[t], MultiTransformation[t1, t2]) => t *: MultiTransformationResult[t1, t2] + case (Transformation[t1], Transformation[t2]) => (t1, t2) +} diff --git a/tests/neg/i18737.check b/tests/neg/i18737.check index f2067ad017cd..4f9ac50a784b 100644 --- a/tests/neg/i18737.check +++ b/tests/neg/i18737.check @@ -7,7 +7,10 @@ | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | - | Tree: v + | Tree: + | + | v + | | I tried to show that | (v : String & Long) | conforms to @@ -32,7 +35,10 @@ | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | - | Tree: v + | Tree: + | + | v + | | I tried to show that | (v : String | Long) | conforms to diff --git a/tests/neg/i19351.check b/tests/neg/i19351.check deleted file mode 100644 index a9ee10510b32..000000000000 --- a/tests/neg/i19351.check +++ /dev/null @@ -1,4 +0,0 @@ --- Error: tests/neg/i19351/A.scala:3:35 -------------------------------------------------------------------------------- -3 | inline def myMacro(): x.type = ${myMacroExpr} // error - | ^ - |Cyclic macro dependency; macro refers to a toplevel symbol in tests/neg/i19351/A.scala from which the macro is called diff --git a/tests/neg/i19351/A.scala b/tests/neg/i19351/A.scala deleted file mode 100644 index f2258631a15a..000000000000 --- a/tests/neg/i19351/A.scala +++ /dev/null @@ -1,5 +0,0 @@ -//object A: - val x: Int = 1 - inline def myMacro(): x.type = ${myMacroExpr} // error - def test = myMacro() - diff --git a/tests/neg/i19351a.check b/tests/neg/i19351a.check deleted file mode 100644 index 10789c2db5aa..000000000000 --- a/tests/neg/i19351a.check +++ /dev/null @@ -1,4 +0,0 @@ --- Error: tests/neg/i19351a/Test.scala:8:34 ---------------------------------------------------------------------------- -8 |inline def not(b: Bool): Bool = ${notMacro('b)} // error - | ^ - |Cyclic macro dependency; macro refers to a toplevel symbol in tests/neg/i19351a/Test.scala from which the macro is called diff --git a/tests/neg/i19413.scala b/tests/neg/i19413.scala new file mode 100644 index 000000000000..92eb6ce611bd --- /dev/null +++ b/tests/neg/i19413.scala @@ -0,0 +1,14 @@ + +import hello.bla.* // error + +//transparent inline def hello: kla.type = kla // ok +def hello: kla.type = kla + +object kla: + def ra = ??? + object bla { val ra1 = 31 } + +//import kla.bla.ra1 + +@main def test = println: + ra1 diff --git a/tests/neg/19414-desugared.check b/tests/neg/i19414-desugared.check similarity index 91% rename from tests/neg/19414-desugared.check rename to tests/neg/i19414-desugared.check index cc51ee471553..72a3a5eabd37 100644 --- a/tests/neg/19414-desugared.check +++ b/tests/neg/i19414-desugared.check @@ -1,4 +1,4 @@ --- [E172] Type Error: tests/neg/19414-desugared.scala:22:34 ------------------------------------------------------------ +-- [E172] Type Error: tests/neg/i19414-desugared.scala:22:34 ----------------------------------------------------------- 22 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances | ^ |No best given instance of type BodySerializer[JsObject] was found for parameter x of method summon in object Predef. diff --git a/tests/neg/19414-desugared.scala b/tests/neg/i19414-desugared.scala similarity index 100% rename from tests/neg/19414-desugared.scala rename to tests/neg/i19414-desugared.scala diff --git a/tests/neg/19414.check b/tests/neg/i19414.check similarity index 91% rename from tests/neg/19414.check rename to tests/neg/i19414.check index 016e3942c825..10bc939494c6 100644 --- a/tests/neg/19414.check +++ b/tests/neg/i19414.check @@ -1,4 +1,4 @@ --- [E172] Type Error: tests/neg/19414.scala:15:34 ---------------------------------------------------------------------- +-- [E172] Type Error: tests/neg/i19414.scala:15:34 --------------------------------------------------------------------- 15 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances | ^ |No best given instance of type BodySerializer[JsObject] was found for parameter x of method summon in object Predef. diff --git a/tests/neg/19414.scala b/tests/neg/i19414.scala similarity index 100% rename from tests/neg/19414.scala rename to tests/neg/i19414.scala diff --git a/tests/neg/i19731.check b/tests/neg/i19731.check index eebfb924d199..5c6ef5246b1d 100644 --- a/tests/neg/i19731.check +++ b/tests/neg/i19731.check @@ -1,10 +1,10 @@ -- Error: tests/neg/i19731.scala:4:6 ----------------------------------------------------------------------------------- 4 |class F1 extends Foo: // error - | ^ + | ^^ | class F1 needs to be abstract, since def foo(): Unit in class F1 is not defined -- Error: tests/neg/i19731.scala:7:6 ----------------------------------------------------------------------------------- 7 |class F2 extends Foo: // error - | ^ + | ^^ | class F2 needs to be abstract, since: | it has 2 unimplemented members. | /** As seen from class F2, the missing signatures are as follows. @@ -14,7 +14,7 @@ | def foo(x: Int): Unit = ??? -- Error: tests/neg/i19731.scala:16:6 ---------------------------------------------------------------------------------- 16 |class B1 extends Bar: // error - | ^ + | ^^ | class B1 needs to be abstract, since: | it has 2 unimplemented members. | /** As seen from class B1, the missing signatures are as follows. diff --git a/tests/neg/i19809.check b/tests/neg/i19809.check index 269eacd18fd2..fc51067e339c 100644 --- a/tests/neg/i19809.check +++ b/tests/neg/i19809.check @@ -1,10 +1,12 @@ -- [E120] Naming Error: tests/neg/i19809.scala:3:6 --------------------------------------------------------------------- 3 | def x_=(x: Int): Unit // error | ^ - | Double definition: + | Conflicting definitions: | def x_=(x$1: Int): Unit in trait at line 2 and | def x_=(x: Int): Unit in trait at line 3 - | have the same type after erasure. + | have the same type (x: Int): Unit after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions | for disambiguation. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i20317a.scala b/tests/neg/i20317a.scala index d7b8b66eb80e..df0667d53ab7 100644 --- a/tests/neg/i20317a.scala +++ b/tests/neg/i20317a.scala @@ -1,5 +1,5 @@ type SemigroupStructural[A] = A & { def combine(a: A): A } def combineAll[A <: SemigroupStructural[A]]( - i: A, l: List[A] + i: A, l: List[A] // error ): A = l.foldLeft(i)(_.combine(_)) // error diff --git a/tests/neg/i21321.check b/tests/neg/i21321.check new file mode 100644 index 000000000000..88cb76154352 --- /dev/null +++ b/tests/neg/i21321.check @@ -0,0 +1,19 @@ +-- Error: tests/neg/i21321.scala:3:42 ---------------------------------------------------------------------------------- +3 |val v1b: scala.ContextFunction0[String] = () ?=> "x" // error + | ^^ + | context function literals require at least one formal parameter +-- Error: tests/neg/i21321.scala:4:8 ----------------------------------------------------------------------------------- +4 |val v2: () ?=> String = "y" // error // error in parser + | ^^ + | context function types require at least one parameter +-- Error: tests/neg/i21321.scala:2:41 ---------------------------------------------------------------------------------- +2 |val v1: scala.ContextFunction0[String] = "x" // error + | ^^^ + | context function types require at least one parameter +-- [E007] Type Mismatch Error: tests/neg/i21321.scala:4:24 ------------------------------------------------------------- +4 |val v2: () ?=> String = "y" // error // error in parser + | ^^^ + | Found: ("y" : String) + | Required: () => String + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i21321.scala b/tests/neg/i21321.scala new file mode 100644 index 000000000000..a7c60994d351 --- /dev/null +++ b/tests/neg/i21321.scala @@ -0,0 +1,4 @@ + +val v1: scala.ContextFunction0[String] = "x" // error +val v1b: scala.ContextFunction0[String] = () ?=> "x" // error +val v2: () ?=> String = "y" // error // error in parser diff --git a/tests/neg/i21335.check b/tests/neg/i21335.check index a7ee092eec0e..ae2e09df1f61 100644 --- a/tests/neg/i21335.check +++ b/tests/neg/i21335.check @@ -1,8 +1,8 @@ -- Error: tests/neg/i21335.scala:7:6 ----------------------------------------------------------------------------------- 7 |class Z1 extends Bar1 // error - | ^ + | ^^ | class Z1 needs to be abstract, since override def bar(): Bar1 in trait Bar1 is not defined -- Error: tests/neg/i21335.scala:12:6 ---------------------------------------------------------------------------------- 12 |class Z2 extends Bar2 // error - | ^ + | ^^ | class Z2 needs to be abstract, since def bar(): Bar2 in trait Bar2 is not defined diff --git a/tests/neg/i21786.scala b/tests/neg/i21786.scala deleted file mode 100644 index c5bb9c595d32..000000000000 --- a/tests/neg/i21786.scala +++ /dev/null @@ -1 +0,0 @@ -into class X // error diff --git a/tests/neg/i22423.check b/tests/neg/i22423.check new file mode 100644 index 000000000000..ec189d99524a --- /dev/null +++ b/tests/neg/i22423.check @@ -0,0 +1,24 @@ +-- Error: tests/neg/i22423.scala:35:14 --------------------------------------------------------------------------------- +35 | exportReader[Settings] // error + | ^^^^^^^^^^^^^^^^^^^^^^ + | cannot reduce summonFrom with + | patterns : case given reader @ _:ConfigReader[List[String]] + |-------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from i22423.scala:12 +12 | summonFrom { case reader: ConfigReader[A] => reader } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from i22423.scala:12 +15 | summonConfigReader[List[String]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from i22423.scala:12 + 8 | readCaseClass() + | ^^^^^^^^^^^^^^^ + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from i22423.scala:12 +30 |inline given exportReader[A]: Exported[ConfigReader[A]] = Exported(HintsAwareConfigReaderDerivation.deriveReader[A]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + -------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i22423.scala b/tests/neg/i22423.scala new file mode 100644 index 000000000000..4204f8f93216 --- /dev/null +++ b/tests/neg/i22423.scala @@ -0,0 +1,35 @@ +//> using options -Xmax-inlines:7 +import scala.deriving.Mirror +import scala.compiletime._ +import scala.compiletime.ops.int._ + +object HintsAwareConfigReaderDerivation { + inline def deriveReader[A]: ConfigReader[A] = + readCaseClass() + ??? + + private inline def summonConfigReader[A]: ConfigReader[A] = + summonFrom { case reader: ConfigReader[A] => reader } + + private inline def readCaseClass(): Unit = + summonConfigReader[List[String]] + val a1: Int = ??? + val a2: EmptyTuple = ??? + a1 *: a2 + ??? +} + +trait ConfigReader[A] +object ConfigReader { + implicit def traversableReader[A, F[A] <: TraversableOnce[A]](implicit configConvert: ConfigReader[A]): ConfigReader[F[A]] = ??? + implicit def exportedReader[A](implicit exported: Exported[ConfigReader[A]]): ConfigReader[A] = exported.instance + case class Exported[A](instance: A) +} + +import ConfigReader._ +inline given exportReader[A]: Exported[ConfigReader[A]] = Exported(HintsAwareConfigReaderDerivation.deriveReader[A]) + +case class Settings(rules: List[String]) + +val settings = + exportReader[Settings] // error diff --git a/tests/neg/i22439.check b/tests/neg/i22439.check index 471ed68d81d1..3b4dffd59aca 100644 --- a/tests/neg/i22439.check +++ b/tests/neg/i22439.check @@ -2,6 +2,9 @@ 7 | f() // error f() missing arg | ^^^ | missing argument for parameter i of method f: (implicit i: Int, j: Int): Int + | This code can be rewritten automatically under -rewrite -source 3.7-migration. + | + | longer explanation available when compiling with `-explain` -- [E050] Type Error: tests/neg/i22439.scala:8:2 ----------------------------------------------------------------------- 8 | g() // error g(given_Int, given_Int)() doesn't take more params | ^ @@ -24,3 +27,6 @@ 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]) + |This code can be rewritten automatically under -rewrite -source 3.7-migration. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i22440.check b/tests/neg/i22440.check index 699d70f343c3..5dece3235c5f 100644 --- a/tests/neg/i22440.check +++ b/tests/neg/i22440.check @@ -1,7 +1,12 @@ +-- Error: tests/neg/i22440.scala:3:8 ----------------------------------------------------------------------------------- +3 |def foo(implicit x: Int) = x // error + | ^ + | `implicit` parameters are no longer supported, use a `using` clause instead + | This construct can be rewritten automatically under -rewrite -source future-migration. -- 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: + | 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 index 79de3b71d2ec..d476cd922dba 100644 --- a/tests/neg/i22440.scala +++ b/tests/neg/i22440.scala @@ -1,4 +1,4 @@ //> using options -source future -def foo(implicit x: Int) = x +def foo(implicit x: Int) = x // error val _ = foo(1) // error diff --git a/tests/neg/i22552.check b/tests/neg/i22552.check index 7a9b6c9800c7..fe4f36aa2caf 100644 --- a/tests/neg/i22552.check +++ b/tests/neg/i22552.check @@ -1,7 +1,7 @@ --- [E040] Syntax Error: tests/neg/i22552.scala:3:10 -------------------------------------------------------------------- +-- [E214] Syntax Error: tests/neg/i22552.scala:3:14 -------------------------------------------------------------------- 3 | type A[X: TC] // error - | ^ - | ']' expected, but ':' found + | ^^ + | Context bounds are not allowed in this position -- Error: tests/neg/i22552.scala:4:15 ---------------------------------------------------------------------------------- 4 | type C = [X: TC] =>> List[X] // error | ^^ diff --git a/tests/neg/i22620.check b/tests/neg/i22620.check new file mode 100644 index 000000000000..51a79d7cabce --- /dev/null +++ b/tests/neg/i22620.check @@ -0,0 +1,21 @@ +-- Error: tests/neg/i22620.scala:4:34 ---------------------------------------------------------------------------------- +4 |class PrivateTest[-M](private val v: ArrayBuffer[M]) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | contravariant type M occurs in invariant position in type scala.collection.mutable.ArrayBuffer[M] of value v + | + | Implementation limitation: private val parameters cannot be inferred to be local + | and therefore are always variance-checked. + | + | Potential fix: remove the private val modifiers on the parameter v. + | +-- Error: tests/neg/i22620.scala:6:37 ---------------------------------------------------------------------------------- +6 |class PrivateTestMut[-M](private var v: ArrayBuffer[M]) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | contravariant type M occurs in invariant position in type scala.collection.mutable.ArrayBuffer[M] of variable v + | + | Implementation limitation: private var parameters cannot be inferred to be local + | and therefore are always variance-checked. + | + | Potential fix: remove the private var modifiers on the parameter v and add + | a field inside the class instead. + | diff --git a/tests/neg/i22620.scala b/tests/neg/i22620.scala index 97d1d55e3302..0f06d97f73e0 100644 --- a/tests/neg/i22620.scala +++ b/tests/neg/i22620.scala @@ -2,3 +2,7 @@ import scala.collection.mutable.ArrayBuffer class PrivateTest[-M](private val v: ArrayBuffer[M]) // error + +class PrivateTestMut[-M](private var v: ArrayBuffer[M]) // error + +class PrivateTestParamOnly[-M](v: ArrayBuffer[M]) // no error diff --git a/tests/neg/i22660.check b/tests/neg/i22660.check new file mode 100644 index 000000000000..309979bf1147 --- /dev/null +++ b/tests/neg/i22660.check @@ -0,0 +1,4 @@ +-- [E214] Syntax Error: tests/neg/i22660.scala:2:21 -------------------------------------------------------------------- +2 |type Foo[T: Equatable] // error + | ^^^^^^^^^ + | Context bounds are not allowed in this position diff --git a/tests/neg/i22660.scala b/tests/neg/i22660.scala new file mode 100644 index 000000000000..650ceac4f48c --- /dev/null +++ b/tests/neg/i22660.scala @@ -0,0 +1,2 @@ +trait Equatable[T] +type Foo[T: Equatable] // error diff --git a/tests/neg/i22792.check b/tests/neg/i22792.check new file mode 100644 index 000000000000..bde1a0e97f9c --- /dev/null +++ b/tests/neg/i22792.check @@ -0,0 +1,10 @@ +-- [E171] Type Error: tests/neg/i22792.scala:8:30 ---------------------------------------------------------------------- +8 |@main def Test = new Foo().run() // error + | ^^^^^^^^^^^^^^^ + | missing argument for parameter ev of method run in class Foo: (implicit ev: Permit): Unit + | This code can be rewritten automatically under -rewrite -source 3.7-migration. + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Old-style implicit argument lists may be omitted but not empty; this syntax was corrected in 3.7. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i22792.scala b/tests/neg/i22792.scala new file mode 100644 index 000000000000..10205573e1fa --- /dev/null +++ b/tests/neg/i22792.scala @@ -0,0 +1,8 @@ +//> using options -explain + +trait Permit +class Foo: + def run(implicit ev: Permit): Unit = ??? + +given Permit = ??? +@main def Test = new Foo().run() // error diff --git a/tests/neg/i22903.check b/tests/neg/i22903.check new file mode 100644 index 000000000000..7a60d43b5c1d --- /dev/null +++ b/tests/neg/i22903.check @@ -0,0 +1,4 @@ +-- [E215] Pattern Match Error: tests/neg/i22903.scala:18:21 ------------------------------------------------------------ +18 | case ProductMatch(someName = x) => println (x) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Named patterns cannot be used with CustomProduct, because it is not a named tuple or case class diff --git a/tests/neg/i22903.scala b/tests/neg/i22903.scala new file mode 100644 index 000000000000..255a6aeb6c07 --- /dev/null +++ b/tests/neg/i22903.scala @@ -0,0 +1,20 @@ +class CustomProduct(x: Int) extends Product { + def _1 = someName + def _2 = blub + + val someName = x + 5 + val blub = "blub" + + override def canEqual(that: Any): Boolean = ??? +} + +object ProductMatch { + def unapply(x: Int): CustomProduct = new CustomProduct(x) +} + +@main +def run = { + 3 match { + case ProductMatch(someName = x) => println (x) // error + } +} \ No newline at end of file diff --git a/tests/neg/i22941.check b/tests/neg/i22941.check new file mode 100644 index 000000000000..81edebd098d3 --- /dev/null +++ b/tests/neg/i22941.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/i22941.scala:4:6 ----------------------------------------------------------------------------------- +4 |class Baz extends Foo: // error + | ^^^ + | class Baz needs to be abstract, since def bar: String in trait Foo is not defined diff --git a/tests/neg/i22941.scala b/tests/neg/i22941.scala new file mode 100644 index 000000000000..3e8eb39777ab --- /dev/null +++ b/tests/neg/i22941.scala @@ -0,0 +1,5 @@ +trait Foo: + def bar: String + +class Baz extends Foo: // error + val a = "hello" diff --git a/tests/neg/i23111.check b/tests/neg/i23111.check new file mode 100644 index 000000000000..017657eac15c --- /dev/null +++ b/tests/neg/i23111.check @@ -0,0 +1,12 @@ +-- [E086] Syntax Error: tests/neg/i23111.scala:2:47 -------------------------------------------------------------------- +2 | def bar: (a: Int, b: Int) => A.this.type = x => ??? // error + | ^^^^^^^^ + | Wrong number of parameters, expected: 2 + | + | longer explanation available when compiling with `-explain` +-- [E086] Syntax Error: tests/neg/i23111.scala:3:45 -------------------------------------------------------------------- +3 | def baz: (a: Int, b: Int) => this.type = x => ??? // error + | ^^^^^^^^ + | Wrong number of parameters, expected: 2 + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i23111.scala b/tests/neg/i23111.scala new file mode 100644 index 000000000000..99dd3927e0ac --- /dev/null +++ b/tests/neg/i23111.scala @@ -0,0 +1,3 @@ +trait A: + def bar: (a: Int, b: Int) => A.this.type = x => ??? // error + def baz: (a: Int, b: Int) => this.type = x => ??? // error diff --git a/tests/neg/i23119.check b/tests/neg/i23119.check new file mode 100644 index 000000000000..34f9e3c564ae --- /dev/null +++ b/tests/neg/i23119.check @@ -0,0 +1,20 @@ +-- [E161] Naming Error: tests/neg/i23119.scala:8:4 --------------------------------------------------------------------- +8 | given Option[List[Int]] = Some(List(x)) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | given_Option_List is already defined as given instance given_Option_List + | + | Provide an explicit, unique name to given definitions, + | since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: Option[List[String]] // define an instance + | given myGiven @ Option[List[String]] // as a pattern variable +-- [E161] Naming Error: tests/neg/i23119.scala:18:8 -------------------------------------------------------------------- +18 | given [A] => List[A] = ??? // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | given_List_A is already defined as given instance given_List_A + | + | Provide an explicit, unique name to given definitions, + | since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: [A] => List[A] // define an instance + | given myGiven @ [A] => List[A] // as a pattern variable diff --git a/tests/neg/i23119.scala b/tests/neg/i23119.scala new file mode 100644 index 000000000000..0f882b66dc8d --- /dev/null +++ b/tests/neg/i23119.scala @@ -0,0 +1,19 @@ +//> using options -explain + +@main def test = println: + for x <- 1 to 2 + // works with explicit name + //ols @ given Option[List[String]] = Some(List(x.toString)) + given Option[List[String]] = Some(List(x.toString)) + given Option[List[Int]] = Some(List(x)) // error + yield summon[Option[List[String]]].map(ss => ss.corresponds(given_Option_List.get)((a, b) => a == b.toString)) + +// The naming clash is noticed when defining local values for "packaging": +// given_Option_List is already defined as given instance given_Option_List +// Previously the naming clash was noticed when extracting values in the map or do function: +// duplicate pattern variable: given_Option_List + +def also = + given [A] => List[A] = ??? + given [A] => List[A] = ??? // error + () diff --git a/tests/neg/i23155a.scala b/tests/neg/i23155a.scala new file mode 100644 index 000000000000..ac48bbf298e0 --- /dev/null +++ b/tests/neg/i23155a.scala @@ -0,0 +1,7 @@ +import scala.NamedTuple +object Unpack_NT { + (1, 2) match { + case Unpack_NT(first, _) => first // error + } + def unapply(e: (Int, Int)): Some[NamedTuple.NamedTuple["x" *: "y" *: EmptyTuple, Int *: Int *: EmptyTuple]] = ??? +} diff --git a/tests/neg/i23155b.scala b/tests/neg/i23155b.scala new file mode 100644 index 000000000000..ef35f4631983 --- /dev/null +++ b/tests/neg/i23155b.scala @@ -0,0 +1,6 @@ +object Unpack_T { + (1, 2) match { + case Unpack_T(first, _) => first // error + } + def unapply(e: (Int, Int)): Some[Int *: Int *: EmptyTuple] = ??? +} diff --git a/tests/neg/i23156.scala b/tests/neg/i23156.scala new file mode 100644 index 000000000000..c8f01ab16857 --- /dev/null +++ b/tests/neg/i23156.scala @@ -0,0 +1,6 @@ +object Unpack { + (1, 2) match { + case Unpack(first, _) => first + } + def unapply(e: (Int, Int)): Option[T] = ??? // error +} \ No newline at end of file diff --git a/tests/neg/i23261.scala b/tests/neg/i23261.scala new file mode 100644 index 000000000000..7186ebd9e615 --- /dev/null +++ b/tests/neg/i23261.scala @@ -0,0 +1,7 @@ +@main def main(): Unit = + summon[0.0 =:= -0.0] // error: Cannot prove that (0.0: Double) =:= (-0.0: Double). + val d: 0.0 = -0.0 // error: Cannot prove that (0.0: Double) =:= (-0.0: Double). + val d2: -0.0 = 0.0 // error: Cannot prove that (-0.0: Double) =:= (0.0: Double). + summon[0.0f =:= -0.0f] // error: Cannot prove that (0.0f: Float) =:= (-0.0f: Float). + val f: 0.0f = -0.0f // error: Cannot prove that (0.0f: Float) =:= (-0.0f: Float). + val f2: -0.0f = 0.0f // error: Cannot prove that (-0.0f: Float) =:= (0.0f: Float). diff --git a/tests/neg/i23350.check b/tests/neg/i23350.check new file mode 100644 index 000000000000..ac64b3d22c1e --- /dev/null +++ b/tests/neg/i23350.check @@ -0,0 +1,45 @@ +-- [E120] Naming Error: tests/neg/i23350.scala:8:7 --------------------------------------------------------------------- +8 |object D extends A: // error + | ^ + | Name clash between defined and inherited member: + | def apply(p: A.this.Props): Unit in class A at line 5 and + | def apply(a: UndefOr2[String]): Unit in object D at line 10 + | have the same type (a: Object): Unit after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | + | As part of the Scala compilation pipeline every type is reduced to its erased + | (runtime) form. In this phase, among other transformations, generic parameters + | disappear and separate parameter-list boundaries are flattened. + | + | For example, both `f[T](x: T)(y: String): Unit` and `f(x: Any, z: String): Unit` + | erase to the same runtime signature `f(x: Object, y: String): Unit`. Note that + | parameter names are irrelevant. + | + | In your code the two declarations + | + | def apply(p: A.this.Props): Unit + | def apply(a: UndefOr2[String]): Unit + | + | erase to the identical signature + | + | (a: Object): Unit + | + | so the compiler cannot keep both: the generated bytecode symbols would collide. + | + | To fix this error, you must disambiguate the two definitions by doing one of the following: + | + | 1. Rename one of the definitions. + | 2. Keep the same names in source but give one definition a distinct + | bytecode-level name via `@targetName`; for example: + | + | @targetName("apply_2") + | def apply(a: UndefOr2[String]): Unit + | + | Choose the `@targetName` argument carefully: it is the name that will be used + | when calling the method externally, so it should be unique and descriptive. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23350.scala b/tests/neg/i23350.scala new file mode 100644 index 000000000000..fa85805f8ae3 --- /dev/null +++ b/tests/neg/i23350.scala @@ -0,0 +1,10 @@ +//> using options -explain + +abstract class A: + type Props + def apply(p: Props) = () + +type UndefOr2[A] = A | Unit +object D extends A: // error + case class Props() + def apply(a: UndefOr2[String]) = () diff --git a/tests/neg/i23400.scala b/tests/neg/i23400.scala new file mode 100644 index 000000000000..08c75d279b87 --- /dev/null +++ b/tests/neg/i23400.scala @@ -0,0 +1,27 @@ +import scala.language.experimental.into +import Conversion.into + +import scala.deriving.Mirror + +object Opaques: + opaque into type MyInto[+A] >: A = A + +import Opaques.MyInto + +case class Foo(x: Int) +case class Bar(foo: into[Foo]) +case class Baz(foo: MyInto[Foo]) + +given Conversion[Int, Foo] = Foo(_) + +into enum Color: // error + case Red, Green + +def test = + val barMirror = summon[Mirror.Of[Bar]] + summon[barMirror.MirroredElemTypes =:= (into[Foo] *: EmptyTuple.type)] // error + summon[barMirror.MirroredElemTypes =:= (Foo *: EmptyTuple.type)] // ok + + val bazMirror = summon[Mirror.Of[Baz]] + summon[bazMirror.MirroredElemTypes =:= (MyInto[Foo] *: EmptyTuple.type)] // ok + summon[bazMirror.MirroredElemTypes =:= (Foo *: EmptyTuple.type)] // error \ No newline at end of file diff --git a/tests/neg/i23402.check b/tests/neg/i23402.check new file mode 100644 index 000000000000..b258ab79e75c --- /dev/null +++ b/tests/neg/i23402.check @@ -0,0 +1,45 @@ +-- [E120] Naming Error: tests/neg/i23402.scala:6:5 --------------------------------------------------------------------- +6 | def apply(p1: String)(p2: Int): A = A(p1, p2) // error + | ^ + | Conflicting definitions: + | def apply(p1: String, p2: Int): A in object A at line 5 and + | def apply(p1: String)(p2: Int): A in object A at line 6 + | have the same type (p1: String, p2: Int): A after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | + | As part of the Scala compilation pipeline every type is reduced to its erased + | (runtime) form. In this phase, among other transformations, generic parameters + | disappear and separate parameter-list boundaries are flattened. + | + | For example, both `f[T](x: T)(y: String): Unit` and `f(x: Any, z: String): Unit` + | erase to the same runtime signature `f(x: Object, y: String): Unit`. Note that + | parameter names are irrelevant. + | + | In your code the two declarations + | + | def apply(p1: String, p2: Int): A + | def apply(p1: String)(p2: Int): A + | + | erase to the identical signature + | + | (p1: String, p2: Int): A + | + | so the compiler cannot keep both: the generated bytecode symbols would collide. + | + | To fix this error, you must disambiguate the two definitions by doing one of the following: + | + | 1. Rename one of the definitions. + | 2. Keep the same names in source but give one definition a distinct + | bytecode-level name via `@targetName`; for example: + | + | @targetName("apply_2") + | def apply(p1: String)(p2: Int): A + | + | Choose the `@targetName` argument carefully: it is the name that will be used + | when calling the method externally, so it should be unique and descriptive. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23402.scala b/tests/neg/i23402.scala new file mode 100644 index 000000000000..9f6fcdf032aa --- /dev/null +++ b/tests/neg/i23402.scala @@ -0,0 +1,7 @@ +//> using options -explain + +class A(p1: String, p2: Int) +object A { + def apply(p1: String, p2: Int): A = A(p1, p2) + def apply(p1: String)(p2: Int): A = A(p1, p2) // error +} diff --git a/tests/neg/i23402b.check b/tests/neg/i23402b.check new file mode 100644 index 000000000000..5036cf20097f --- /dev/null +++ b/tests/neg/i23402b.check @@ -0,0 +1,12 @@ +-- [E120] Naming Error: tests/neg/i23402b.scala:4:5 -------------------------------------------------------------------- +4 | def apply[T](p1: String)(p2: Int): A = A(p1, p2) // error + | ^ + | Conflicting definitions: + | def apply[T](p1: String, p2: Int): A in object A at line 3 and + | def apply[T](p1: String)(p2: Int): A in object A at line 4 + | have the same type (p1: String, p2: Int): A after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i23402b.scala b/tests/neg/i23402b.scala new file mode 100644 index 000000000000..1b486f21b583 --- /dev/null +++ b/tests/neg/i23402b.scala @@ -0,0 +1,5 @@ +class A(p1: String, p2: Int) +object A { + def apply[T](p1: String, p2: Int): A = A(p1, p2) + def apply[T](p1: String)(p2: Int): A = A(p1, p2) // error +} diff --git a/tests/neg/i23402c.check b/tests/neg/i23402c.check new file mode 100644 index 000000000000..be73dd9c361d --- /dev/null +++ b/tests/neg/i23402c.check @@ -0,0 +1,12 @@ +-- [E120] Naming Error: tests/neg/i23402c.scala:4:5 -------------------------------------------------------------------- +4 | def apply[T](p1: String)(p2: Int): A = A(p1, p2) // error + | ^ + | Conflicting definitions: + | def apply(p1: String, p2: Int): A in object A at line 3 and + | def apply[T](p1: String)(p2: Int): A in object A at line 4 + | have the same type (p1: String, p2: Int): A after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i23402c.scala b/tests/neg/i23402c.scala new file mode 100644 index 000000000000..c28c0c512459 --- /dev/null +++ b/tests/neg/i23402c.scala @@ -0,0 +1,5 @@ +class A(p1: String, p2: Int) +object A { + def apply(p1: String, p2: Int): A = A(p1, p2) + def apply[T](p1: String)(p2: Int): A = A(p1, p2) // error +} diff --git a/tests/neg/i23402d.check b/tests/neg/i23402d.check new file mode 100644 index 000000000000..38806f9f122e --- /dev/null +++ b/tests/neg/i23402d.check @@ -0,0 +1,12 @@ +-- [E120] Naming Error: tests/neg/i23402d.scala:5:4 -------------------------------------------------------------------- +5 |def f(x: Any): Unit = ??? // error + | ^ + | Conflicting definitions: + | def f[T](x: T): Unit in the top-level definitions in package at line 4 and + | def f(x: Any): Unit in the top-level definitions in package at line 5 + | have the same type (x: Object): Unit after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i23402d.scala b/tests/neg/i23402d.scala new file mode 100644 index 000000000000..674515fefd14 --- /dev/null +++ b/tests/neg/i23402d.scala @@ -0,0 +1,5 @@ +// This test checks that the example given in the `-explain` of the +// `DoubleDefinition` message is correct. + +def f[T](x: T): Unit = ??? +def f(x: Any): Unit = ??? // error diff --git a/tests/neg/i23406.check b/tests/neg/i23406.check new file mode 100644 index 000000000000..d9d417e1ac13 --- /dev/null +++ b/tests/neg/i23406.check @@ -0,0 +1,40 @@ +-- Error: tests/neg/i23406.scala:21:7 ---------------------------------------------------------------------------------- +21 | funny[String] // error + | ^^^^^^^^^^^^^ + | value x is unusable in method Test because it refers to an erased expression + | in the selector of an inline match that reduces to + | + | { + | erased val $scrutinee1: String = compiletime.package$package.erasedValue[String] + | erased val x: String = $scrutinee1 + | { + | x:String + | } + | } + |-------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from i23406.scala:7 + 7 | case x: String => x + | ^ + -------------------------------------------------------------------------------------------------------------------- +-- Error: tests/neg/i23406.scala:22:9 ---------------------------------------------------------------------------------- +22 | problem[String] // error + | ^^^^^^^^^^^^^^^ + | value x is unusable in method Test because it refers to an erased expression + | in the selector of an inline match that reduces to + | + | { + | erased val $scrutinee2: String = compiletime.package$package.erasedValue[String] + | erased val x: String = $scrutinee2 + | { + | foo(x) + | } + | } + |-------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from i23406.scala:11 +11 | case x: String => foo(x) + | ^ + -------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23406.scala b/tests/neg/i23406.scala new file mode 100644 index 000000000000..a17a2c5272df --- /dev/null +++ b/tests/neg/i23406.scala @@ -0,0 +1,24 @@ +import language.experimental.erasedDefinitions + +def foo(erased x: String): String = "" + +inline def funny[T]: String = + inline compiletime.erasedValue[T] match + case x: String => x + +inline def problem[T]: String = + inline compiletime.erasedValue[T] match + case x: String => foo(x) + +inline def ok[T]: String = + inline compiletime.erasedValue[T] match + case x: String => "hi" +inline def alsoOk[T](erased x: T): String = + inline x match + case x: String => "hi again" + +def Test = + funny[String] // error + problem[String] // error + ok[String] + alsoOk[String](compiletime.erasedValue) diff --git a/tests/neg/i23474.check b/tests/neg/i23474.check new file mode 100644 index 000000000000..441978be6809 --- /dev/null +++ b/tests/neg/i23474.check @@ -0,0 +1,25 @@ +-- Error: tests/neg/i23474.scala:5:11 ---------------------------------------------------------------------------------- +5 |case class Y(val comment: String) extends Comment // error + | ^ + | class Y needs to be abstract, since var comment_=(x$1: String): Unit in trait Comment is not defined + | (Note that an abstract var requires a setter in addition to the getter) +-- Error: tests/neg/i23474.scala:7:6 ----------------------------------------------------------------------------------- +7 |class Z extends Comment: // error + | ^ + | class Z needs to be abstract, since var comment_=(x$1: String): Unit in trait Comment is not defined + | (Note that an abstract var requires a setter in addition to the getter) +-- [E164] Declaration Error: tests/neg/i23474.scala:11:15 -------------------------------------------------------------- +11 | override def comment: String = "" // error + | ^ + | error overriding variable comment in trait Comment of type String; + | method comment of type => String cannot override a mutable variable +-- Error: tests/neg/i23474.scala:10:6 ---------------------------------------------------------------------------------- +10 |class X extends Comment: // error + | ^ + | class X needs to be abstract, since var comment_=(x$1: String): Unit in trait Comment is not defined + | (Note that an abstract var requires a setter in addition to the getter) +-- Error: tests/neg/i23474.scala:13:6 ---------------------------------------------------------------------------------- +13 |class W extends Comment // error + | ^ + | class W needs to be abstract, since var comment: String in trait Comment is not defined + | (Note that variables need to be initialized to be defined) diff --git a/tests/neg/i23474.scala b/tests/neg/i23474.scala new file mode 100644 index 000000000000..3e34c2f457c0 --- /dev/null +++ b/tests/neg/i23474.scala @@ -0,0 +1,19 @@ +trait Comment { + var comment: String +} + +case class Y(val comment: String) extends Comment // error + +class Z extends Comment: // error + val comment: String = "" + +class X extends Comment: // error + override def comment: String = "" // error + +class W extends Comment // error + + +class OK: + val comment: String = "" + def comment_=(x: String): Unit = () + diff --git a/tests/neg/i23504.scala b/tests/neg/i23504.scala new file mode 100644 index 000000000000..e53337eaa105 --- /dev/null +++ b/tests/neg/i23504.scala @@ -0,0 +1,3 @@ +def test = + Seq.empty[[T] =>> () => ?].head() // error + Seq.empty[[T] =>> Int => Int].head(1) // error \ No newline at end of file diff --git a/tests/neg/i23552.scala b/tests/neg/i23552.scala new file mode 100644 index 000000000000..ea3d0ff7f874 --- /dev/null +++ b/tests/neg/i23552.scala @@ -0,0 +1,32 @@ +import NamedTuple.AnyNamedTuple + +sealed trait Z +sealed trait S[n] + +type TupleList[+A, N] <: AnyNamedTuple = + N match + case Z => NamedTuple.Empty + case S[n] => (head: A, tail: TupleList[A, n]) + +sealed trait Vect[+A, N]: + def ::[A1 >: A](a: A1): Vect[A1, S[N]] = + Vect.Cons(a, this) + + def toTupleList: TupleList[A, N] + +object Vect: + case object Empty extends Vect[Nothing, Z]: + override def toTupleList: TupleList[Nothing, Z] = NamedTuple.Empty + + case class Cons[A, N](head: A, tail: Vect[A, N]) extends Vect[A, S[N]]: + override def toTupleList: TupleList[A, S[N]] = (head, tail.toTupleList) + +object Foo: + def unapply[A, N](as: Vect[A, N]): Some[TupleList[A, N]] = + Some(as.toTupleList) + +@main +def test: Unit = + (1 :: 2 :: 3 :: Vect.Empty) match + // missing parens around named tuple inside Foo causes compiler crash + case Foo(head = h, tail = t) => ??? // error \ No newline at end of file diff --git a/tests/neg/i23637.check b/tests/neg/i23637.check new file mode 100644 index 000000000000..d568c04a31b7 --- /dev/null +++ b/tests/neg/i23637.check @@ -0,0 +1,6 @@ +-- [E083] Type Error: tests/neg/i23637.scala:6:9 ----------------------------------------------------------------------- +6 | export foo.pin.* // error: (because we need reflection to get at foo.pin) + | ^^^^^^^ + | (Test.foo.pin : Object) is not a valid export prefix, since it is not an immutable path + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i23637.scala b/tests/neg/i23637.scala new file mode 100644 index 000000000000..aac728f4fd99 --- /dev/null +++ b/tests/neg/i23637.scala @@ -0,0 +1,12 @@ +trait Foo extends reflect.Selectable +object Test: + val foo = new Foo: + object pin: + val x = 1 + export foo.pin.* // error: (because we need reflection to get at foo.pin) + +object OK: + object Foo: + object pin: + val x = 1 + export Foo.pin.* diff --git a/tests/neg/i23832a.check b/tests/neg/i23832a.check new file mode 100644 index 000000000000..6886327484c3 --- /dev/null +++ b/tests/neg/i23832a.check @@ -0,0 +1,45 @@ +-- [E120] Naming Error: tests/neg/i23832a.scala:9:8 -------------------------------------------------------------------- +9 | given Special[Option[Int]] = ??? // error + | ^ + | Conflicting definitions: + | final lazy given val given_Special_Option: Special[Option[Long]] in object syntax at line 8 and + | final lazy given val given_Special_Option: Special[Option[Int]] in object syntax at line 9 + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | + | As part of the Scala compilation pipeline every type is reduced to its erased + | (runtime) form. In this phase, among other transformations, generic parameters + | disappear and separate parameter-list boundaries are flattened. + | + | For example, both `f[T](x: T)(y: String): Unit` and `f(x: Any, z: String): Unit` + | erase to the same runtime signature `f(x: Object, y: String): Unit`. Note that + | parameter names are irrelevant. + | + | In your code the two declarations + | + | final lazy given val given_Special_Option: Special[Option[Long]] + | final lazy given val given_Special_Option: Special[Option[Int]] + | + | erase to the identical signature + | + | Special + | + | so the compiler cannot keep both: the generated bytecode symbols would collide. + | + | To fix this error, you must disambiguate the two definitions by doing one of the following: + | + | 1. Rename one of the definitions. Provide an explicit, unique name to given definitions, + | since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: Special[Option[Int]] + | + | 2. Keep the same names in source but give one definition a distinct + | bytecode-level name via `@targetName`; for example: + | + | @targetName("given_Special_Option_2") + | final lazy given val given_Special_Option: Special[Option[Int]] + | + | Choose the `@targetName` argument carefully: it is the name that will be used + | when calling the method externally, so it should be unique and descriptive. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23832a.scala b/tests/neg/i23832a.scala new file mode 100644 index 000000000000..5020c998ee96 --- /dev/null +++ b/tests/neg/i23832a.scala @@ -0,0 +1,9 @@ +//> using options -explain + +// follow-up to neg/i23402*.scala + +trait Special[A] + +object syntax: + given Special[Option[Long]] = ??? + given Special[Option[Int]] = ??? // error diff --git a/tests/neg/i23832b.check b/tests/neg/i23832b.check new file mode 100644 index 000000000000..82cb54044449 --- /dev/null +++ b/tests/neg/i23832b.check @@ -0,0 +1,45 @@ +-- [E120] Naming Error: tests/neg/i23832b.scala:9:8 -------------------------------------------------------------------- +9 | given [A] => Special[Option[A]] = ??? // error + | ^ + | Conflicting definitions: + | final lazy given val given_Special_Option: Special[Option[Long]] in object syntax at line 8 and + | final given def given_Special_Option[A]: Special[Option[A]] in object syntax at line 9 + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | + | As part of the Scala compilation pipeline every type is reduced to its erased + | (runtime) form. In this phase, among other transformations, generic parameters + | disappear and separate parameter-list boundaries are flattened. + | + | For example, both `f[T](x: T)(y: String): Unit` and `f(x: Any, z: String): Unit` + | erase to the same runtime signature `f(x: Object, y: String): Unit`. Note that + | parameter names are irrelevant. + | + | In your code the two declarations + | + | final lazy given val given_Special_Option: Special[Option[Long]] + | final given def given_Special_Option[A]: Special[Option[A]] + | + | erase to the identical signature + | + | (): Special + | + | so the compiler cannot keep both: the generated bytecode symbols would collide. + | + | To fix this error, you must disambiguate the two definitions by doing one of the following: + | + | 1. Rename one of the definitions. Provide an explicit, unique name to given definitions, + | since the names assigned to anonymous givens may clash. For example: + | + | given myGiven: [A] => Special[Option[A]] + | + | 2. Keep the same names in source but give one definition a distinct + | bytecode-level name via `@targetName`; for example: + | + | @targetName("given_Special_Option_2") + | final given def given_Special_Option[A]: Special[Option[A]] + | + | Choose the `@targetName` argument carefully: it is the name that will be used + | when calling the method externally, so it should be unique and descriptive. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i23832b.scala b/tests/neg/i23832b.scala new file mode 100644 index 000000000000..6e43ed008047 --- /dev/null +++ b/tests/neg/i23832b.scala @@ -0,0 +1,9 @@ +//> using options -explain + +// follow-up to neg/i23402*.scala + +trait Special[A] + +object syntax: + given Special[Option[Long]] = ??? + given [A] => Special[Option[A]] = ??? // error diff --git a/tests/neg/i4060.scala b/tests/neg/i4060.scala index bd16ed867966..b85c1190cc3e 100644 --- a/tests/neg/i4060.scala +++ b/tests/neg/i4060.scala @@ -6,7 +6,7 @@ object App { trait A { type L >: Any} def upcast(erased a: A)(x: Any): a.L = x erased val p: A { type L <: Nothing } = p - def coerce(x: Any): Int = upcast(p)(x) // error + def coerce(x: Any): Int = upcast(p)(x) // ok? def coerceInline(x: Any): Int = upcast(compiletime.erasedValue[A {type L <: Nothing}])(x) // error @@ -14,7 +14,7 @@ object App { def upcast_dep_parameter(erased a: B)(x: a.L) : Int = x erased val q : B { type L >: Any } = compiletime.erasedValue - def coerceInlineWithB(x: Any): Int = upcast_dep_parameter(q)(x) // error + def coerceInlineWithB(x: Any): Int = upcast_dep_parameter(q)(x) // ok? def main(args: Array[String]): Unit = { println(coerce("Uh oh!")) diff --git a/tests/neg/i7294.check b/tests/neg/i7294.check index 30c076470899..1af233b1279f 100644 --- a/tests/neg/i7294.check +++ b/tests/neg/i7294.check @@ -1,9 +1,9 @@ --- [E007] Type Mismatch Error: tests/neg/i7294.scala:7:15 -------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg/i7294.scala:7:18 -------------------------------------------------------------- 7 | case x: T => x.g(10) // error - | ^ - | Found: (x : Nothing) - | Required: ?{ g: ? } - | Note that implicit conversions were not tried because the result of an implicit conversion - | must be more specific than ?{ g: [applied to (10) returning T] } + | ^^^^^^^ + | Found: Any + | Required: T + | + | where: T is a type in given instance f with bounds <: foo.Foo | | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i7359-f.check b/tests/neg/i7359-f.check index 5eabb4e1dd8c..9dca40aebb35 100644 --- a/tests/neg/i7359-f.check +++ b/tests/neg/i7359-f.check @@ -4,7 +4,7 @@ | Name clash between inherited members: | def equals[T >: Boolean <: Boolean](obj: Any): T in trait SAMTrait at line 3 and | def equals(x$0: Any): Boolean in class Any - | have the same type after erasure. + | have the same type (x$0: Object): Boolean after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions | for disambiguation. diff --git a/tests/neg/i7751.scala b/tests/neg/i7751.scala index fd66e7d451be..18070cfd0551 100644 --- a/tests/neg/i7751.scala +++ b/tests/neg/i7751.scala @@ -1,3 +1,3 @@ import language.`3.3` -val a = Some(a=a,)=> // error // error // error // error +val a = Some(a=a,)=> // error // error // error val a = Some(x=y,)=> diff --git a/tests/neg/i8896-b.scala b/tests/neg/i8896-b.scala index f562d2d3b719..6592dba8f4ad 100644 --- a/tests/neg/i8896-b.scala +++ b/tests/neg/i8896-b.scala @@ -7,7 +7,7 @@ object Example { given Foo[Int]() def foo0[A: Foo]: A => A = identity - def foo1[A](implicit foo: Foo[A]): A => A = identity + def foo1[A](implicit foo: Foo[A]): A => A = identity // error def foo2[A](using Foo[A]): A => A = identity def test(): Unit = { diff --git a/tests/neg/i9329.check b/tests/neg/i9329.check index 7e4968edf607..e604a1b22888 100644 --- a/tests/neg/i9329.check +++ b/tests/neg/i9329.check @@ -1,5 +1,5 @@ -- Error: tests/neg/i9329.scala:8:6 ------------------------------------------------------------------------------------ 8 |class GrandSon extends Son // error - | ^ + | ^^^^^^^^ |class GrandSon needs to be abstract, since def name: String in trait Parent is not defined |(The class implements abstract override def name: String in trait Son but that definition still needs an implementation) diff --git a/tests/neg/i9568.check b/tests/neg/i9568.check index 3f318d0b0111..744023714a69 100644 --- a/tests/neg/i9568.check +++ b/tests/neg/i9568.check @@ -4,13 +4,10 @@ | No given instance of type => Monad[F] was found for parameter ev of method blaMonad in object Test. | I found: | - | Test.blaMonad[F², S](Test.blaMonad[F³, S²]) + | Test.blaMonad[F², S] | - | But method blaMonad in object Test does not match type => Monad[F²] + | But method blaMonad in object Test does not match type => Monad[F] | | where: F is a type variable with constraint <: [_] =>> Any | F² is a type variable with constraint <: [_] =>> Any - | F³ is a type variable with constraint <: [_] =>> Any - | S is a type variable - | S² is a type variable | . diff --git a/tests/neg/implicit-migration.check b/tests/neg/implicit-migration.check new file mode 100644 index 000000000000..c7a8f681e1ea --- /dev/null +++ b/tests/neg/implicit-migration.check @@ -0,0 +1,64 @@ +-- Error: tests/neg/implicit-migration.scala:16:21 --------------------------------------------------------------------- +16 | implicit def ol[T](implicit x: Ord[T]): Ord[List[T]] = new Ord[List[T]]() // error // error + | ^ + | `implicit` parameters are no longer supported, use a `using` clause instead + | This construct can be rewritten automatically under -rewrite -source future-migration. +-- Error: tests/neg/implicit-migration.scala:9:15 ---------------------------------------------------------------------- +9 | implicit def convert(x: String): Int = x.length // error + | ^ + | `implicit` conversion methods are no longer supported. They can usually be + | replaced by given instances of class `Conversion`. Example: + | + | given Conversion[String, Int] = x => ... + | +-- Error: tests/neg/implicit-migration.scala:11:15 --------------------------------------------------------------------- +11 | implicit val ob: Ord[Boolean] = Ord[Boolean]() // error + | ^ + | `implicit` vals are no longer supported, use a `given` clause instead. Example: + | + | given ob: Ord[Boolean] = ... + | + | Note: given clauses are evaluated lazily unless the right hand side is + | a simple reference. If eager evaluation of the value's right hand side + | is important, you can define a regular val and a given instance like this: + | + | val ob = ... + | given Ord[Boolean] = ob + | +-- Error: tests/neg/implicit-migration.scala:12:20 --------------------------------------------------------------------- +12 | lazy implicit val oi: Ord[Int] = Ord[Int]() // error + | ^ + | `implicit` vals are no longer supported, use a `given` clause instead. Example: + | + | given oi: Ord[Int] = ... + | +-- Error: tests/neg/implicit-migration.scala:14:15 --------------------------------------------------------------------- +14 | implicit def of: Ord[Float] = Ord[Float]() // error + | ^ + | `implicit` defs are no longer supported, use a `given` clause instead. Example: + | + | given of: () => Ord[Float] = ... + | +-- Error: tests/neg/implicit-migration.scala:16:15 --------------------------------------------------------------------- +16 | implicit def ol[T](implicit x: Ord[T]): Ord[List[T]] = new Ord[List[T]]() // error // error + | ^ + | `implicit` defs are no longer supported, use a `given` clause instead. Example: + | + | given ol: [T] => (x: Ord[T]) => Ord[List[T]] = ... + | +-- Error: tests/neg/implicit-migration.scala:3:15 ---------------------------------------------------------------------- +3 |implicit class C(x: String): // error + | ^ + | `implicit` classes are no longer supported. They can usually be replaced + | by extension methods. Example: + | + | extension (x: String) + | // class methods go here, replace `this` by `x` + | + | Alternatively, convert to a regular class and define + | a given `Conversion` instance into that class. Example: + | + | class C ... + | given Conversion[String, C] = C(x) + | +there was 1 feature warning; re-run with -feature for details diff --git a/tests/neg/implicit-migration.scala b/tests/neg/implicit-migration.scala new file mode 100644 index 000000000000..f5e2e2802fbe --- /dev/null +++ b/tests/neg/implicit-migration.scala @@ -0,0 +1,17 @@ +import language.future + +implicit class C(x: String): // error + def l: Int = x.length + +class Ord[T] + +object Test: + implicit def convert(x: String): Int = x.length // error + + implicit val ob: Ord[Boolean] = Ord[Boolean]() // error + lazy implicit val oi: Ord[Int] = Ord[Int]() // error + + implicit def of: Ord[Float] = Ord[Float]() // error + + implicit def ol[T](implicit x: Ord[T]): Ord[List[T]] = new Ord[List[T]]() // error // error + diff --git a/tests/neg/into-inferred.check b/tests/neg/into-inferred.check new file mode 100644 index 000000000000..f96fe4bbafaf --- /dev/null +++ b/tests/neg/into-inferred.check @@ -0,0 +1,39 @@ +-- [E007] Type Mismatch Error: tests/neg/into-inferred.scala:34:32 ----------------------------------------------------- +34 | val l1: List[into[Keyword]] = l :+ "then" :+ "else" // error + | ^^^^^^^^^^^^^^^^^^^^^ + | Found: List[Conversion.into[Keyword] | String] + | Required: List[Conversion.into[Keyword]] + | + | longer explanation available when compiling with `-explain` +-- Feature Warning: tests/neg/into-inferred.scala:22:43 ---------------------------------------------------------------- +22 | val ys: List[into[Keyword]] = List(ifKW, "then", "else") // warn // warn + | ^^^^^^ + | Use of implicit conversion given instance given_Conversion_String_Keyword in object Test should be enabled + | by adding the import clause 'import scala.language.implicitConversions' + | or by setting the compiler option -language:implicitConversions. + | See the Scala docs for value scala.language.implicitConversions for a discussion + | why the feature should be explicitly enabled. +-- Feature Warning: tests/neg/into-inferred.scala:22:51 ---------------------------------------------------------------- +22 | val ys: List[into[Keyword]] = List(ifKW, "then", "else") // warn // warn + | ^^^^^^ + | Use of implicit conversion given instance given_Conversion_String_Keyword in object Test should be enabled + | by adding the import clause 'import scala.language.implicitConversions' + | or by setting the compiler option -language:implicitConversions. + | See the Scala docs for value scala.language.implicitConversions for a discussion + | why the feature should be explicitly enabled. +-- Feature Warning: tests/neg/into-inferred.scala:35:42 ---------------------------------------------------------------- +35 | val l2: List[into[Keyword]] = l ++ List("then", "else") // warn // warn + | ^^^^^^ + | Use of implicit conversion given instance given_Conversion_String_Keyword in object Test should be enabled + | by adding the import clause 'import scala.language.implicitConversions' + | or by setting the compiler option -language:implicitConversions. + | See the Scala docs for value scala.language.implicitConversions for a discussion + | why the feature should be explicitly enabled. +-- Feature Warning: tests/neg/into-inferred.scala:35:50 ---------------------------------------------------------------- +35 | val l2: List[into[Keyword]] = l ++ List("then", "else") // warn // warn + | ^^^^^^ + | Use of implicit conversion given instance given_Conversion_String_Keyword in object Test should be enabled + | by adding the import clause 'import scala.language.implicitConversions' + | or by setting the compiler option -language:implicitConversions. + | See the Scala docs for value scala.language.implicitConversions for a discussion + | why the feature should be explicitly enabled. diff --git a/tests/neg/into-inferred.scala b/tests/neg/into-inferred.scala new file mode 100644 index 000000000000..e2d459394ab8 --- /dev/null +++ b/tests/neg/into-inferred.scala @@ -0,0 +1,37 @@ +//> using options -feature +import language.experimental.into +import Conversion.{into, underlying} + +trait Token +class Keyword(str: String) +case class Phrase(words: into[Keyword]*) + +object Test: + given Conversion[String, Keyword] = Keyword(_) + + val xs = List[into[Keyword]]("if", "then", "else") // ok + val _: List[Keyword] = xs.map(_.underlying) + + val p = Phrase("if", "then", "else") // ok + val ws = p.words + val _: Seq[Keyword] = ws + + val p2 = Phrase(xs*) // ok + + val ifKW: into[Keyword] = "if" + val ys: List[into[Keyword]] = List(ifKW, "then", "else") // warn // warn + + val s = Set(ifKW) + val s1 = s + "then" + "else" + val _: Set[into[Keyword]] = s1 + val s2 = s ++ List("then", "else") + val s3: Set[into[Keyword] | String] = s2 + val s4 = s3.map(_.underlying) + val _: Set[Keyword | String] = s4 + + + val l = List(ifKW) + val l1: List[into[Keyword]] = l :+ "then" :+ "else" // error + val l2: List[into[Keyword]] = l ++ List("then", "else") // warn // warn + + diff --git a/tests/neg/into-mods.check b/tests/neg/into-mods.check new file mode 100644 index 000000000000..30ff99372b99 --- /dev/null +++ b/tests/neg/into-mods.check @@ -0,0 +1,16 @@ +-- Error: tests/neg/into-mods.scala:10:11 ------------------------------------------------------------------------------ +10 | into def foo = 22 // error + | ^ + | values cannot be into +-- [E156] Syntax Error: tests/neg/into-mods.scala:7:12 ----------------------------------------------------------------- +7 |into object M // error + |^^^^^^^^^^^^^ + |Modifier into is not allowed for this definition +-- [E156] Syntax Error: tests/neg/into-mods.scala:12:2 ----------------------------------------------------------------- +12 | into type T = Int // error + | ^^^^ + | Modifier into is not allowed for this definition +-- Error: tests/neg/into-mods.scala:11:11 ------------------------------------------------------------------------------ +11 | into val x = 33 // error + | ^^^^^^^^^^^^^^^ + | modifier(s) `into` incompatible with value definition diff --git a/tests/neg/into-mods.scala b/tests/neg/into-mods.scala new file mode 100644 index 000000000000..7b426471c0e2 --- /dev/null +++ b/tests/neg/into-mods.scala @@ -0,0 +1,14 @@ +import language.experimental.into + +into class Test + +into trait T + +into object M // error + +object Test: + into def foo = 22 // error + into val x = 33 // error + into type T = Int // error + into opaque type U = Int // ok + diff --git a/tests/neg/into-override.check b/tests/neg/into-override.check index 812470494a8b..c497986a9800 100644 --- a/tests/neg/into-override.check +++ b/tests/neg/into-override.check @@ -1,21 +1,36 @@ --- [E164] Declaration Error: tests/neg/into-override.scala:16:15 ------------------------------------------------------- -16 | override def f(x: into X) = super.f(x) // error - | ^ - | error overriding method f in trait A of type (x: X): Unit; - | method f of type (x: into X): Unit has different occurrences of `into` modifiers +-- [E120] Naming Error: tests/neg/into-override.scala:16:6 ------------------------------------------------------------- +16 |trait C[X] extends A[X]: // error + | ^ + | Name clash between defined and inherited member: + | def f(x: X): Unit in trait A at line 11 and + | override def f(x: Conversion.into[X]): Unit in trait C at line 17 + | have the same type (x: Object): Unit after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. | | longer explanation available when compiling with `-explain` --- [E164] Declaration Error: tests/neg/into-override.scala:18:6 -------------------------------------------------------- -18 |class D[X] extends B[X], C[X] // error +-- [E120] Naming Error: tests/neg/into-override.scala:19:6 ------------------------------------------------------------- +19 |class D[X] extends B[X], C[X] // error | ^ - | error overriding method f in trait B of type (x: X): Unit; - | method f in trait C of type (x: into X): Unit has different occurrences of `into` modifiers + | Name clash between inherited members: + | override def f(x: X): Unit in trait B at line 14 and + | override def f(x: Conversion.into[X]): Unit in trait C at line 17 + | have the same type (x: Object): Unit after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. | | longer explanation available when compiling with `-explain` --- [E164] Declaration Error: tests/neg/into-override.scala:21:15 ------------------------------------------------------- -21 | override def f(x: X) = super.f(x) // error - | ^ - | error overriding method f in trait C of type (x: into X): Unit; - | method f of type (x: X): Unit has different occurrences of `into` modifiers +-- [E120] Naming Error: tests/neg/into-override.scala:21:6 ------------------------------------------------------------- +21 |trait E[X] extends C[X]: // error + | ^ + | Name clash between defined and inherited member: + | override def f(x: Conversion.into[X]): Unit in trait C at line 17 and + | override def f(x: X): Unit in trait E at line 22 + | have the same type (x: Object): Unit after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. | | longer explanation available when compiling with `-explain` diff --git a/tests/neg/into-override.scala b/tests/neg/into-override.scala index 645ae8756003..6a25ddbeb7f0 100644 --- a/tests/neg/into-override.scala +++ b/tests/neg/into-override.scala @@ -1,6 +1,7 @@ //> using options -Xfatal-warnings import language.experimental.into +import Conversion.into class Text(val str: String) @@ -12,12 +13,12 @@ trait A[X]: trait B[X] extends A[X]: override def f(x: X) = super.f(x) -trait C[X] extends A[X]: - override def f(x: into X) = super.f(x) // error +trait C[X] extends A[X]: // error + override def f(x: into[X]) = super.f(x) class D[X] extends B[X], C[X] // error -trait E[X] extends C[X]: - override def f(x: X) = super.f(x) // error +trait E[X] extends C[X]: // error + override def f(x: X) = super.f(x) + -def f = new D[Text].f("abc") diff --git a/tests/neg/into-syntax.check b/tests/neg/into-syntax.check index ad1f95db93be..8c54ad75af17 100644 --- a/tests/neg/into-syntax.check +++ b/tests/neg/into-syntax.check @@ -41,21 +41,15 @@ | Missing return type | | longer explanation available when compiling with `-explain` --- [E006] Not Found Error: tests/neg/into-syntax.scala:7:17 ------------------------------------------------------------ +-- Error: tests/neg/into-syntax.scala:7:17 ----------------------------------------------------------------------------- 7 | def f1(x: List[into Int]) = () // error // error | ^^^^ - | Not found: type into - did you mean into.type? - | - | longer explanation available when compiling with `-explain` --- [E006] Not Found Error: tests/neg/into-syntax.scala:21:18 ----------------------------------------------------------- + | Type argument into does not have the same kind as its bound +-- [E056] Syntax Error: tests/neg/into-syntax.scala:21:18 -------------------------------------------------------------- 21 | def f11(x: ((y: into Int) => into Int => Int)*) = () // error // error | ^^^^ - | Not found: type into - did you mean into.type? - | - | longer explanation available when compiling with `-explain` --- [E006] Not Found Error: tests/neg/into-syntax.scala:27:22 ----------------------------------------------------------- + | Missing type parameter for into +-- Error: tests/neg/into-syntax.scala:27:22 ---------------------------------------------------------------------------- 27 | def f17(x: into (y: into Int, z: into Int) => into Int) = () // error // error // error | ^^^^ - | Not found: type into - did you mean into.type? - | - | longer explanation available when compiling with `-explain` + | Type argument into does not have the same kind as its bound diff --git a/tests/neg/into-syntax.scala b/tests/neg/into-syntax.scala deleted file mode 100644 index 8f48a603adf6..000000000000 --- a/tests/neg/into-syntax.scala +++ /dev/null @@ -1,27 +0,0 @@ -//> using options -feature - -import language.experimental.into - - -object x1: - def f1(x: List[into Int]) = () // error // error -object x3: - def f3(x: ((into Int))) = () // ok -object x4: - def f4(x: into Int*) = () // error -object x5: - def f5(x: ((into Int))*) = () // ok - -object x6: - def f6(x: (into Int)*) = () // ok - def f7(x: (Int => into Int)*) = () // ok - def f8(x: (Int => (into Int))*) = () // ok - def f9(x: (y: Int) => into Int) = () // ok - def f10(x: ((y: Int) => into Int)*) = () // ok - def f11(x: ((y: into Int) => into Int => Int)*) = () // error // error - -object x7: - def f14(x: (into Int) => Int) = () // error - def f15(x: (into Int, into Int)) = () // error // error - def f16(x: (into Int, into Int) => Int) = () // error // error - def f17(x: into (y: into Int, z: into Int) => into Int) = () // error // error // error diff --git a/tests/neg/irrefutable-genfrom.scala b/tests/neg/irrefutable-genfrom.scala new file mode 100644 index 000000000000..fb70f10283e3 --- /dev/null +++ b/tests/neg/irrefutable-genfrom.scala @@ -0,0 +1,12 @@ +object Test: + def one: Unit = + for + // Was already an error as expected + (i, Some(_)) <- List.empty[Int] zip List.empty[Option[String]] // error + do () + + def two: Unit = + for + // Used to be a warning + (i, Some(_)) <- List.empty[Int] lazyZip List.empty[Option[String]] // error + do () diff --git a/tests/neg/lambda-infer.scala b/tests/neg/lambda-infer.scala index 6c3db90cb893..ed2737a6f7ad 100644 --- a/tests/neg/lambda-infer.scala +++ b/tests/neg/lambda-infer.scala @@ -2,7 +2,7 @@ type F = (x: Int, erased y: Int) => Int -erased class A +class A extends compiletime.Erased @main def Test() = val a: F = (x, y) => x + 1 // error: Expected F got (Int, Int) => Int diff --git a/tests/neg/localReaches.check b/tests/neg/localReaches.check new file mode 100644 index 000000000000..18510e3a3616 --- /dev/null +++ b/tests/neg/localReaches.check @@ -0,0 +1,10 @@ +-- Error: tests/neg/localReaches.scala:24:30 --------------------------------------------------------------------------- +24 | var x: () ->{xs*} Unit = ys.head // error + | ^^^^^^^ + | Local reach capability ops* leaks into capture scope of method localReach3. + | To allow this, the parameter ops should be declared with a @use annotation +-- Error: tests/neg/localReaches.scala:27:11 --------------------------------------------------------------------------- +27 | x = ys.head // error + | ^^^^^^^ + | Local reach capability ops* leaks into capture scope of method localReach3. + | To allow this, the parameter ops should be declared with a @use annotation diff --git a/tests/neg/localReaches.scala b/tests/neg/localReaches.scala new file mode 100644 index 000000000000..d0935e2d491f --- /dev/null +++ b/tests/neg/localReaches.scala @@ -0,0 +1,28 @@ +import language.experimental.captureChecking +// no separation checking +import caps.consume + +def localReach() = + val xs: List[() => Unit] = ??? + var ys: List[() ->{xs*} Unit] = xs + var x: () ->{xs*} Unit = ys.head + while ys.nonEmpty do + ys = ys.tail + x = ys.head + +def localReach2(op: () => Unit) = + val xs: List[() => Unit] = op :: Nil + var ys: List[() ->{xs*} Unit] = xs + var x: () ->{xs*} Unit = ys.head + while ys.nonEmpty do + ys = ys.tail + x = ys.head + +def localReach3(ops: List[() => Unit]) = + val xs: List[() => Unit] = ops + var ys: List[() ->{xs*} Unit] = xs + var x: () ->{xs*} Unit = ys.head // error + while ys.nonEmpty do + ys = ys.tail + x = ys.head // error + diff --git a/tests/neg/magic-canthrow.scala b/tests/neg/magic-canthrow.scala new file mode 100644 index 000000000000..f167949ed0b0 --- /dev/null +++ b/tests/neg/magic-canthrow.scala @@ -0,0 +1,11 @@ +import language.experimental.erasedDefinitions +import java.io.IOException + +class CanThrow[-E <: Exception] + +def foo[E <: Exception](e: E)(using erased CanThrow[E]): Nothing = throw e + +inline def magic[E]: E = magic + +def Test = foo(new IOException)(using magic) // error + diff --git a/tests/neg/magic-offset-header-a.scala b/tests/neg/magic-offset-header-a.scala new file mode 100644 index 000000000000..48267a7853f0 --- /dev/null +++ b/tests/neg/magic-offset-header-a.scala @@ -0,0 +1,2 @@ + +def test1(): Int = "无穷" // error diff --git a/tests/neg/magic-offset-header-a_wrapper.check b/tests/neg/magic-offset-header-a_wrapper.check new file mode 100644 index 000000000000..0ab253c12804 --- /dev/null +++ b/tests/neg/magic-offset-header-a_wrapper.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-a.scala:2:19 ---------------------------------------------- +2 |def test1(): Int = "无穷" // error + | ^^^^ + | Found: ("无穷" : String) + | Required: Int + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-a_wrapper.scala b/tests/neg/magic-offset-header-a_wrapper.scala new file mode 100644 index 000000000000..af4f2b8bf8dd --- /dev/null +++ b/tests/neg/magic-offset-header-a_wrapper.scala @@ -0,0 +1,7 @@ +//> using options -Ymagic-offset-header:TEST_MARKER +val t1 = 1 +val t2 = 2 +val t3 = 3 +///TEST_MARKER:tests/neg/magic-offset-header-a.scala + +def test1(): Int = "无穷" // anypos-error diff --git a/tests/neg/magic-offset-header-b.scala b/tests/neg/magic-offset-header-b.scala new file mode 100644 index 000000000000..aeb569272523 --- /dev/null +++ b/tests/neg/magic-offset-header-b.scala @@ -0,0 +1,2 @@ + +def y: Int = false // error diff --git a/tests/neg/magic-offset-header-b_wrapper.check b/tests/neg/magic-offset-header-b_wrapper.check new file mode 100644 index 000000000000..5d862e5a6b10 --- /dev/null +++ b/tests/neg/magic-offset-header-b_wrapper.check @@ -0,0 +1,14 @@ +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-b_wrapper.scala:3:13 -------------------------------------- +3 |def x: Int = true // error + | ^^^^ + | Found: (true : Boolean) + | Required: Int + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-b.scala:2:13 ---------------------------------------------- +2 |def y: Int = false // error + | ^^^^^ + | Found: (false : Boolean) + | Required: Int + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-b_wrapper.scala b/tests/neg/magic-offset-header-b_wrapper.scala new file mode 100644 index 000000000000..ef0e552948d3 --- /dev/null +++ b/tests/neg/magic-offset-header-b_wrapper.scala @@ -0,0 +1,7 @@ +//> using options -Ymagic-offset-header:TEST_MARKER + +def x: Int = true // error + +///TEST_MARKER:tests/neg/magic-offset-header-b.scala + +def y: Int = false // anypos-error diff --git a/tests/neg/magic-offset-header-c.scala b/tests/neg/magic-offset-header-c.scala new file mode 100644 index 000000000000..be3cb333abff --- /dev/null +++ b/tests/neg/magic-offset-header-c.scala @@ -0,0 +1,3 @@ + +def userCode = + val x: String = 0 // error diff --git a/tests/neg/magic-offset-header-c_wrapper.check b/tests/neg/magic-offset-header-c_wrapper.check new file mode 100644 index 000000000000..0c33f5ea0338 --- /dev/null +++ b/tests/neg/magic-offset-header-c_wrapper.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-c.scala:3:18 ---------------------------------------------- +3 | val x: String = 0 // error + | ^ + | Found: (0 : Int) + | Required: String + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-c_wrapper.scala b/tests/neg/magic-offset-header-c_wrapper.scala new file mode 100644 index 000000000000..51804a647fbe --- /dev/null +++ b/tests/neg/magic-offset-header-c_wrapper.scala @@ -0,0 +1,8 @@ +//> using options -Ymagic-offset-header:SOURCE_CODE_START_MARKER + +val generatedCode = 123 + +///SOURCE_CODE_START_MARKER:tests/neg/magic-offset-header-c.scala + +def userCode = + val x: String = 0 // anypos-error diff --git a/tests/neg/magic-offset-header-d_wrapper.check b/tests/neg/magic-offset-header-d_wrapper.check new file mode 100644 index 000000000000..36421e857a1c --- /dev/null +++ b/tests/neg/magic-offset-header-d_wrapper.check @@ -0,0 +1,15 @@ +original source file not found: something_nonexist.scala +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-d_wrapper.scala:3:20 -------------------------------------- +3 |def test1: String = 0 // error + | ^ + | Found: (0 : Int) + | Required: String + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/magic-offset-header-d_wrapper.scala:5:17 -------------------------------------- +5 |def test2: Int = "0" // error + | ^^^ + | Found: ("0" : String) + | Required: Int + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/magic-offset-header-d_wrapper.scala b/tests/neg/magic-offset-header-d_wrapper.scala new file mode 100644 index 000000000000..85840e84b702 --- /dev/null +++ b/tests/neg/magic-offset-header-d_wrapper.scala @@ -0,0 +1,5 @@ +//> using options -Ymagic-offset-header:SOURCE_CODE_START_MARKER + +def test1: String = 0 // error +///SOURCE_CODE_START_MARKER:something_nonexist.scala +def test2: Int = "0" // error diff --git a/tests/neg/mixin-forwarder-clash1.check b/tests/neg/mixin-forwarder-clash1.check index 8c1e2a7911ef..328786f9b551 100644 --- a/tests/neg/mixin-forwarder-clash1.check +++ b/tests/neg/mixin-forwarder-clash1.check @@ -4,7 +4,7 @@ | Name clash between inherited members: | def concat(suffix: Int): X in trait One at line 4 and | def concat[Dummy](suffix: Int): Y in trait Two at line 8 - | have the same type after erasure. + | have the same type (suffix: Int): Object after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions | for disambiguation. diff --git a/tests/neg/mixin-forwarder-clash2.check b/tests/neg/mixin-forwarder-clash2.check index 8956d96e2071..13993192993d 100644 --- a/tests/neg/mixin-forwarder-clash2.check +++ b/tests/neg/mixin-forwarder-clash2.check @@ -5,7 +5,7 @@ | Name clash between inherited members: | def concat(suffix: Int): X in trait One at line 4 and | def concat[Dummy](suffix: Int): Y in trait Two at line 8 - | have the same type after erasure. + | have the same type (suffix: Int): Object after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions | for disambiguation. diff --git a/tests/neg/named-tuples.check b/tests/neg/named-tuples.check index 8ec958b6a75d..68ee61355107 100644 --- a/tests/neg/named-tuples.check +++ b/tests/neg/named-tuples.check @@ -47,14 +47,10 @@ 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: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:29:20 --------------------------------------------------------------------------- -29 | case (name = n, age = a) => () // error // error - | ^^^^^^^ - | No element named `age` is defined in selector type (String, Int) +-- [E215] Pattern Match Error: tests/neg/named-tuples.scala:29:9 ------------------------------------------------------- +29 | case (name = n, age = a) => () // error + | ^^^^^^^^^^^^^^^^^^^ + | Named patterns cannot be used with (String, Int), because it is not a named tuple or case class -- [E172] Type Error: tests/neg/named-tuples.scala:31:27 --------------------------------------------------------------- 31 | val pp = person ++ (1, 2) // error | ^ @@ -75,10 +71,10 @@ 41 | case (name, age = a) => () // error | ^^^^^^^ | Illegal combination of named and unnamed tuple elements --- Error: tests/neg/named-tuples.scala:44:10 --------------------------------------------------------------------------- +-- [E215] Pattern Match Error: tests/neg/named-tuples.scala:44:9 ------------------------------------------------------- 44 | case (age = x) => // error - | ^^^^^^^ - | No element named `age` is defined in selector type Tuple + | ^^^^^^^^^ + | Named patterns cannot be used with Tuple, because it is not a named tuple or case class -- [E172] Type Error: tests/neg/named-tuples.scala:46:27 --------------------------------------------------------------- 46 | val p2 = person ++ person // error | ^ diff --git a/tests/neg/named-tuples.scala b/tests/neg/named-tuples.scala index daae6e26bac2..2d8e0663dee0 100644 --- a/tests/neg/named-tuples.scala +++ b/tests/neg/named-tuples.scala @@ -26,7 +26,7 @@ object Test: val (name = x, agee = y) = person // error ("Ives", 2) match - case (name = n, age = a) => () // error // error + case (name = n, age = a) => () // error val pp = person ++ (1, 2) // error val qq = ("a", true) ++ (1, 2) diff --git a/tests/neg/no-patches.check b/tests/neg/no-patches.check new file mode 100644 index 000000000000..2636c9bbd04a --- /dev/null +++ b/tests/neg/no-patches.check @@ -0,0 +1,12 @@ +-- [E008] Not Found Error: tests/neg/no-patches.scala:3:23 ------------------------------------------------------------- +3 |val _ = scala.language.`3.4` // error: we do not patch `scala.language` + | ^^^^^^^^^^^^^^^^^^^^ + | value 3.4 is not a member of object language +-- [E008] Not Found Error: tests/neg/no-patches.scala:4:36 ------------------------------------------------------------- +4 |val _ = scala.language.experimental.modularity // error: we do not patch `scala.language.experimental` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | value modularity is not a member of object language.experimental +-- [E008] Not Found Error: tests/neg/no-patches.scala:5:15 ------------------------------------------------------------- +5 |val _ = Predef.summon[DummyImplicit] // error: we do not patch `scala.Predef` + | ^^^^^^^^^^^^^ + | value summon is not a member of object Predef diff --git a/tests/neg/no-patches.scala b/tests/neg/no-patches.scala new file mode 100644 index 000000000000..a07d9836412a --- /dev/null +++ b/tests/neg/no-patches.scala @@ -0,0 +1,5 @@ +//> using options -Yno-stdlib-patches + +val _ = scala.language.`3.4` // error: we do not patch `scala.language` +val _ = scala.language.experimental.modularity // error: we do not patch `scala.language.experimental` +val _ = Predef.summon[DummyImplicit] // error: we do not patch `scala.Predef` diff --git a/tests/neg/nowarn.check b/tests/neg/nowarn.check deleted file mode 100644 index ff01de1788bd..000000000000 --- a/tests/neg/nowarn.check +++ /dev/null @@ -1,110 +0,0 @@ --- [E002] Syntax Warning: tests/neg/nowarn.scala:11:10 ----------------------------------------------------------------- -11 |def t1a = try 1 // warning (parser) - | ^^^^^ - | A try without catch or finally is equivalent to putting - | its body in a block; no exceptions are handled. - | - | longer explanation available when compiling with `-explain` --- [E002] Syntax Warning: tests/neg/nowarn.scala:25:25 ----------------------------------------------------------------- -25 |@nowarn(o.inl) def t2d = try 1 // two warnings (`inl` is not a compile-time constant) - | ^^^^^ - | A try without catch or finally is equivalent to putting - | its body in a block; no exceptions are handled. - | - | longer explanation available when compiling with `-explain` --- [E002] Syntax Warning: tests/neg/nowarn.scala:33:26 ----------------------------------------------------------------- -33 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) - | ^^^^^ - | A try without catch or finally is equivalent to putting - | its body in a block; no exceptions are handled. - | - | longer explanation available when compiling with `-explain` --- [E002] Syntax Warning: tests/neg/nowarn.scala:35:28 ----------------------------------------------------------------- -35 |@nowarn("verbose") def t5 = try 1 // warning with details - | ^^^^^ - | 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` --- [E129] Potential Issue Warning: tests/neg/nowarn.scala:15:11 -------------------------------------------------------- -15 |def t2 = { 1; 2 } // warning (the invalid nowarn doesn't silence anything) - | ^ - | A pure expression does nothing in statement position - | - | longer explanation available when compiling with `-explain` --- Warning: tests/neg/nowarn.scala:14:8 -------------------------------------------------------------------------------- -14 |@nowarn("wat?") // warning (typer, invalid filter) - | ^^^^^^ - | Invalid message filter - | unknown filter: wat? --- [E129] Potential Issue Warning: tests/neg/nowarn.scala:18:12 -------------------------------------------------------- -18 |def t2a = { 1; 2 } // warning (invalid nowarn doesn't silence) - | ^ - | A pure expression does nothing in statement position - | - | longer explanation available when compiling with `-explain` --- Warning: tests/neg/nowarn.scala:17:8 -------------------------------------------------------------------------------- -17 |@nowarn(t1a.toString) // warning (typer, argument not a compile-time constant) - | ^^^^^^^^^^^^ - | filter needs to be a compile-time constant string --- Warning: tests/neg/nowarn.scala:25:10 ------------------------------------------------------------------------------- -25 |@nowarn(o.inl) def t2d = try 1 // two warnings (`inl` is not a compile-time constant) - | ^^^^^ - | filter needs to be a compile-time constant string --- Deprecation Warning: tests/neg/nowarn.scala:39:10 ------------------------------------------------------------------- -39 |def t6a = f // warning (refchecks, deprecation) - | ^ - | method f is deprecated --- Deprecation Warning: tests/neg/nowarn.scala:42:30 ------------------------------------------------------------------- -42 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) - | ^ - | method f is deprecated --- Deprecation Warning: tests/neg/nowarn.scala:49:10 ------------------------------------------------------------------- -49 |def t7c = f // warning (deprecation) - | ^ - | method f is deprecated --- [E092] Pattern Match Unchecked Warning: tests/neg/nowarn.scala:55:7 ------------------------------------------------- -55 | case _: List[Int] => 0 // warning (patmat, unchecked) - | ^ - |the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from Any - | - | longer explanation available when compiling with `-explain` --- Error: tests/neg/nowarn.scala:33:1 ---------------------------------------------------------------------------------- -33 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) - |^^^^^^^^^^^^^^^ - |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:42:1 ---------------------------------------------------------------------------------- -42 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) - |^^^^^^^^^^^^^^^^^^^ - |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:50:5 ---------------------------------------------------------------------------------- -50 | : @nowarn("msg=fish") // error (unused nowarn) - | ^^^^^^^^^^^^^^^^^^^ - | @nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:62:0 ---------------------------------------------------------------------------------- -62 |@nowarn def t9a = { 1: @nowarn; 2 } // error (outer @nowarn is unused) - |^^^^^^^ - |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:63:27 --------------------------------------------------------------------------------- -63 |@nowarn def t9b = { 1: Int @nowarn; 2 } // error (inner @nowarn is unused, it covers the type, not the expression) - | ^^^^^^^ - | @nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:68:0 ---------------------------------------------------------------------------------- -68 |@nowarn @ann(f) def t10b = 0 // error (unused nowarn) - |^^^^^^^ - |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:69:8 ---------------------------------------------------------------------------------- -69 |@ann(f: @nowarn) def t10c = 0 // error (unused nowarn), should be silent - | ^^^^^^^ - | @nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:72:0 ---------------------------------------------------------------------------------- -72 |@nowarn class I1a { // error (unused nowarn) - |^^^^^^^ - |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:77:0 ---------------------------------------------------------------------------------- -77 |@nowarn class I1b { // error (unused nowarn) - |^^^^^^^ - |@nowarn annotation does not suppress any warnings diff --git a/tests/neg/nowarn.scala b/tests/neg/nowarn.scala deleted file mode 100644 index 5b18ab5ccc51..000000000000 --- a/tests/neg/nowarn.scala +++ /dev/null @@ -1,89 +0,0 @@ -//> using options -deprecation -Wunused:nowarn "-Wconf:msg=@nowarn annotation does not suppress any warnings:e" - -import scala.annotation.{ nowarn, Annotation } - -// This test doesn't run with `-Werror`, because once there's an error, later phases are skipped and we would not see -// their warnings. -// Instead, this test runs with `-Wunused:nowarn -Wconf:msg=@nowarn annotation does not suppress any warnings:e`. -// Only "unused nowarn" warnings are reported as errors. Since these warnings are reported at the very end, all other -// phases of the compiler run normally. - -def t1a = try 1 // warning (parser) -@nowarn("msg=try without catch") def t1b = try 1 - -@nowarn("wat?") // warning (typer, invalid filter) -def t2 = { 1; 2 } // warning (the invalid nowarn doesn't silence anything) - -@nowarn(t1a.toString) // warning (typer, argument not a compile-time constant) -def t2a = { 1; 2 } // warning (invalid nowarn doesn't silence) - -object o: - final val const = "msg=try" - inline def inl = "msg=try" - -@nowarn(o.const) def t2c = try 1 // no warning -@nowarn(o.inl) def t2d = try 1 // two warnings (`inl` is not a compile-time constant) - -@nowarn("id=E129") def t3a = { 1; 2 } -@nowarn("name=PureExpressionInStatementPosition") def t3b = { 1; 2 } - -@nowarn("id=E002") def t4a = try 1 -@nowarn("id=E2") def t4b = try 1 -@nowarn("id=2") def t4c = try 1 -@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) - -@nowarn("verbose") def t5 = try 1 // warning with details - -@deprecated def f = 0 - -def t6a = f // warning (refchecks, deprecation) -@nowarn("cat=deprecation") def t6b = f -@nowarn("msg=deprecated") def t6c = f -@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) -@nowarn("") def t6e = f -@nowarn def t6f = f - -def t7a = f: @nowarn("cat=deprecation") -def t7b = f - : @nowarn("msg=deprecated") -def t7c = f // warning (deprecation) - : @nowarn("msg=fish") // error (unused nowarn) -def t7d = f: @nowarn("") -def t7e = f: @nowarn - -def t8a(x: Any) = x match - case _: List[Int] => 0 // warning (patmat, unchecked) - case _ => 1 - -@nowarn("cat=unchecked") def t8(x: Any) = x match - case _: List[Int] => 0 - case _ => 1 - -@nowarn def t9a = { 1: @nowarn; 2 } // error (outer @nowarn is unused) -@nowarn def t9b = { 1: Int @nowarn; 2 } // error (inner @nowarn is unused, it covers the type, not the expression) - -class ann(a: Any) extends Annotation - -@ann(f) def t10a = 0 // should be a deprecation warning, but currently isn't -@nowarn @ann(f) def t10b = 0 // error (unused nowarn) -@ann(f: @nowarn) def t10c = 0 // error (unused nowarn), should be silent - -def forceCompletionOfI1a = (new I1a).m -@nowarn class I1a { // error (unused nowarn) - @nowarn def m = { 1; 2 } -} - -// completion during type checking -@nowarn class I1b { // error (unused nowarn) - @nowarn def m = { 1; 2 } -} - -@nowarn class I1c { - def m = { 1; 2 } -} - -trait T { - @nowarn val t1 = { 0; 1 } -} - -class K extends T diff --git a/tests/warn/old-givens.scala b/tests/neg/old-givens.scala similarity index 57% rename from tests/warn/old-givens.scala rename to tests/neg/old-givens.scala index 83e650df47d3..bd2acdfd2f8a 100644 --- a/tests/warn/old-givens.scala +++ b/tests/neg/old-givens.scala @@ -5,14 +5,14 @@ trait Ord[T]: class C[T] trait T: - given intC: C[Int] // warn + given intC: C[Int] // error given intC2: C[Int] () // OK - given [T]: Ord[T] with // warn // warn + given [T]: Ord[T] with // error // error def compare(x: T, y: T): Boolean = ??? - given [T](using Ord[T]): Ord[List[T]] with // warn // warn + given [T](using Ord[T]): Ord[List[T]] with // error // error def compare(x: List[T], y: List[T]): Boolean = ??? - def f[T: Ord : C]() = ??? // warn + def f[T: Ord : C]() = ??? // error diff --git a/tests/neg/override-erasure-clash.check b/tests/neg/override-erasure-clash.check index e936a8de2397..136eb0b4885f 100644 --- a/tests/neg/override-erasure-clash.check +++ b/tests/neg/override-erasure-clash.check @@ -4,4 +4,6 @@ | Name clash between defined and inherited member: | def f(): Int in class A at line 3 and | def g(): Int in class B at line 5 - | have the same name and type after erasure. + | have the same name and type (): Int after erasure. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/parser-stability-11.scala b/tests/neg/parser-stability-11.scala index 582bcfb88117..b2ea61e594de 100644 --- a/tests/neg/parser-stability-11.scala +++ b/tests/neg/parser-stability-11.scala @@ -3,4 +3,4 @@ case class x0 // error // error } package x0 class x0 // error -// error \ No newline at end of file +// error diff --git a/tests/neg/safeThrowsStrawman.scala b/tests/neg/safeThrowsStrawman.scala index bc07eb0bb3f9..a94bec429899 100644 --- a/tests/neg/safeThrowsStrawman.scala +++ b/tests/neg/safeThrowsStrawman.scala @@ -3,7 +3,7 @@ import annotation.implicitNotFound object scalax: @implicitNotFound("The capability to throw exception ${E} is missing.\nThe capability can be provided by one of the following:\n - A using clause `(using CanThrow[${E}])`\n - A raises clause in a result type such as `X raises ${E}`\n - an enclosing `try` that catches ${E}") - erased class CanThrow[-E <: Exception] + class CanThrow[-E <: Exception] extends compiletime.Erased infix type raises[R, +E <: Exception] = CanThrow[E] ?=> R diff --git a/tests/neg/t5702-neg-bad-and-wild.check b/tests/neg/t5702-neg-bad-and-wild.check index be77cd255a66..a70c6db1cef9 100644 --- a/tests/neg/t5702-neg-bad-and-wild.check +++ b/tests/neg/t5702-neg-bad-and-wild.check @@ -54,13 +54,6 @@ | no pattern match extractor named * was found | | longer explanation available when compiling with `-explain` --- Warning: tests/neg/t5702-neg-bad-and-wild.scala:13:22 --------------------------------------------------------------- -13 | case List(1, _*3:) => // error // error - | ^ - | Type ascriptions after patterns other than: - | * variable pattern, e.g. `case x: String =>` - | * number literal pattern, e.g. `case 10.5: Double =>` - | are no longer supported. Remove the type ascription or move it to a separate variable pattern. -- Warning: tests/neg/t5702-neg-bad-and-wild.scala:22:20 --------------------------------------------------------------- 22 | val K(x @ _*) = k | ^ diff --git a/tests/neg/targetName-override.check b/tests/neg/targetName-override.check index 2d21e8cbfbd4..230b7fe77745 100644 --- a/tests/neg/targetName-override.check +++ b/tests/neg/targetName-override.check @@ -15,5 +15,5 @@ | method ++ of type (xs: Alpha[String]): Alpha[String] misses a target name annotation @targetName(append) -- Error: tests/neg/targetName-override.scala:14:6 --------------------------------------------------------------------- 14 |class Beta extends Alpha[String] { // error: needs to be abstract - | ^ + | ^^^^ |class Beta needs to be abstract, since there is a deferred declaration of method foo in class Alpha of type (x: String): String which is not implemented in a subclass diff --git a/tests/neg/type-params.check b/tests/neg/type-params.check index e1eefb4c6fe9..cebc311588c4 100644 --- a/tests/neg/type-params.check +++ b/tests/neg/type-params.check @@ -52,13 +52,15 @@ -- [E120] Naming Error: tests/neg/type-params.scala:44:6 --------------------------------------------------------------- 44 | def a = (p: A) => () // error | ^ - | Double definition: + | Conflicting definitions: | 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. + | have the same type (): Function1 after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions | for disambiguation. + | + | longer explanation available when compiling with `-explain` -- Error: tests/neg/type-params.scala:4:7 ------------------------------------------------------------------------------ 4 | "".==[Int] // error | ^^^^^^^^^^ diff --git a/tests/neg/typeclass-derivation2.scala b/tests/neg/typeclass-derivation2.scala index eca11fb326ed..ba89fb4c39c8 100644 --- a/tests/neg/typeclass-derivation2.scala +++ b/tests/neg/typeclass-derivation2.scala @@ -119,7 +119,7 @@ object TypeLevel { type Subtype[t] = Type[_, t] type Supertype[t] = Type[t, _] type Exactly[t] = Type[t, t] - erased def typeOf[T]: Type[T, T] = compiletime.erasedValue + inline def typeOf[T]: Type[T, T] = compiletime.erasedValue } // An algebraic datatype diff --git a/tests/neg/unroll-no-default.scala b/tests/neg/unroll-no-default.scala index 1058f34087e3..4529bc79f815 100644 --- a/tests/neg/unroll-no-default.scala +++ b/tests/neg/unroll-no-default.scala @@ -1,4 +1,4 @@ -//> using options -experimental -Xprint:unrollDefs +//> using options -experimental -Vprint:unrollDefs import scala.annotation.unroll diff --git a/tests/new/test.scala b/tests/new/test.scala index dc1891f3525c..de3073216898 100644 --- a/tests/new/test.scala +++ b/tests/new/test.scala @@ -1,8 +1,3 @@ -type Person = (name: String, age: Int) -trait A: - type T - -class B: - type U =:= A { type T = U } +def foo[T: Singleton](x: T) = x diff --git a/tests/patmat/patmat-lazy-nothing-not-exhaustive.check b/tests/patmat/patmat-lazy-nothing-not-exhaustive.check new file mode 100644 index 000000000000..aef9e1aed027 --- /dev/null +++ b/tests/patmat/patmat-lazy-nothing-not-exhaustive.check @@ -0,0 +1 @@ +8: Pattern Match Exhaustivity: Bar() \ No newline at end of file diff --git a/tests/patmat/patmat-lazy-nothing-not-exhaustive.scala b/tests/patmat/patmat-lazy-nothing-not-exhaustive.scala new file mode 100644 index 000000000000..b66eb63c110c --- /dev/null +++ b/tests/patmat/patmat-lazy-nothing-not-exhaustive.scala @@ -0,0 +1,10 @@ +sealed trait Adt +case class Foo() extends Adt +case class Bar() extends Adt { + lazy val x: Nothing = throw new Exception() +} + +def shouldThrowAWarning(x: Adt) = + x match { // warn + case Foo() => "Foo" + } diff --git a/tests/pending/neg-custom-args/sep-tvar-follow.scala b/tests/pending/neg-custom-args/sep-tvar-follow.scala new file mode 100644 index 000000000000..09ee3a17a054 --- /dev/null +++ b/tests/pending/neg-custom-args/sep-tvar-follow.scala @@ -0,0 +1,10 @@ +import language.experimental.captureChecking +trait Ref +def swap(x1: Ref^, x2: Ref^): Unit = () +def foo(a: Ref^)[X](op: (z: Ref^) -> X^{z}): X^{a} = op(a) +def test1(a: Ref^): Unit = + def bad(x: Ref^)(y: Ref^{a}): Unit = swap(x, y) + val t1 = bad + def t2[X] = foo(a)[X] + val t3 = t2[(y: Ref^{a}) -> Unit](t1) + t3(a) // boom diff --git a/tests/pending/neg/erased-impure.check b/tests/pending/neg/erased-impure.check new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/pending/neg/erased-impure.scala b/tests/pending/neg/erased-impure.scala new file mode 100644 index 000000000000..8dd668bbc529 --- /dev/null +++ b/tests/pending/neg/erased-impure.scala @@ -0,0 +1,44 @@ +//> using options -explain +import language.experimental.erasedDefinitions +import java.io.IOException +import caps.unsafe.unsafeErasedValue + +class CanThrow[-E <: Exception] + +def foo[E <: Exception](e: E)(using erased CanThrow[E]): Nothing = throw e + +erased val magic1: IOException = ??? // error +erased val magic2: IOException = compiletime.erasedValue[IOException] // error +erased val magic3: IOException = null.asInstanceOf[IOException] // error + +inline def inlineId[T](x: T) = x + +class C() + +def testPure[T](erased x: T): Unit = () + +case class Pair[A, B](x: A, y: B) +object Pair: + def apply(x: Int): Pair2[Int, Int] = + println("Pair2") + Pair2(x, x + 1) + +case class Box[A](x: A): + println(x) + +def Test = + foo(new IOException)(using ???) // error + foo(new IOException)(using inlineId(???)) // error + + testPure(C()) // OK + testPure(inlineId(C())) // OK + testPure(identity(C())) // error, identity is not an inline function + + testPure(Pair(unsafeErasedValue[Int], unsafeErasedValue[String])) // OK + testPure(Pair(unsafeErasedValue[Int])) // error + testPure(Box(unsafeErasedValue[Int])) // error + + + + + diff --git a/tests/pending/pos-custom-args/captures/SizeCompareOps-redux.scala b/tests/pending/pos-custom-args/captures/SizeCompareOps-redux.scala new file mode 100644 index 000000000000..f694699fded1 --- /dev/null +++ b/tests/pending/pos-custom-args/captures/SizeCompareOps-redux.scala @@ -0,0 +1,22 @@ +package collection +trait IterableOps[+A, +CC[_], +C] extends Any: + def sizeCompare(size: Int): Int + +object IterableOps: + + type AnyConstr[X] = Any + + final class SizeCompareOps private[collection](val it: IterableOps[_, AnyConstr, _]^) extends AnyVal: + this: SizeCompareOps^{it} => + /** Tests if the size of the collection is less than some value. */ + @inline def <(size: Int): Boolean = it.sizeCompare(size) < 0 + /** Tests if the size of the collection is less than or equal to some value. */ + @inline def <=(size: Int): Boolean = it.sizeCompare(size) <= 0 + /** Tests if the size of the collection is equal to some value. */ + @inline def ==(size: Int): Boolean = it.sizeCompare(size) == 0 + /** Tests if the size of the collection is not equal to some value. */ + @inline def !=(size: Int): Boolean = it.sizeCompare(size) != 0 + /** Tests if the size of the collection is greater than or equal to some value. */ + @inline def >=(size: Int): Boolean = it.sizeCompare(size) >= 0 + /** Tests if the size of the collection is greater than some value. */ + @inline def >(size: Int): Boolean = it.sizeCompare(size) > 0 diff --git a/tests/pending/pos-custom-args/captures/list-encoding.scala b/tests/pending/pos-custom-args/captures/list-encoding.scala new file mode 100644 index 000000000000..38e3affa8671 --- /dev/null +++ b/tests/pending/pos-custom-args/captures/list-encoding.scala @@ -0,0 +1,30 @@ +package listEncoding +import annotation.retains + +class Cap + +type Op[T, C] = + T => C => C + + +class STR // used to be pure class String but then the errors don't show up. + +type List[T] = + [C] -> (op: Op[T, C]) -> (s: C) ->{op} C + +def nil[T]: List[T] = + [C] => (op: Op[T, C]) => (s: C) => s + +def cons[T](hd: T, tl: List[T]): List[T] = + [C] => (op: Op[T, C]) => (s: C) => op(hd)(tl(op)(s)) + +def foo(c: Cap^) = + def f(x: STR @retains[c.type], y: STR @retains[c.type]) = + cons(x, cons(y, nil)) // error, should this work? + def f_explicit(x: STR @retains[c.type], y: STR @retains[c.type]) + : [C] => (op: STR^{x, y} => C => C) -> (s: C) ->{op} C = + cons(x, cons(y, nil)) // error, should this work? + def g(x: STR @retains[c.type], y: Any) = + cons(x, cons(y, nil)) + def h(x: STR, y: Any @retains[c.type]) = + cons(x, cons(y, nil)) diff --git a/tests/pending/pos/i4560.scala b/tests/pending/pos/i4560.scala new file mode 100644 index 000000000000..b18a05206040 --- /dev/null +++ b/tests/pending/pos/i4560.scala @@ -0,0 +1,12 @@ +class Ref + +class C { type T } + +trait Trait { + type A <: C { type T = B } + type B <: A +} + +trait SubTrait extends Trait { + val v: A +} \ No newline at end of file diff --git a/tests/pending/pos/singleton-infer.scala b/tests/pending/pos/singleton-infer.scala index 72e00baf3aab..adccba97cb57 100644 --- a/tests/pending/pos/singleton-infer.scala +++ b/tests/pending/pos/singleton-infer.scala @@ -1,8 +1,8 @@ -//> using options -Xprint:typer -language:experimental.modularity -source future +//> using options -Vprint:typer -language:experimental.modularity -source future def f1[S, T <: S : Singleton](x: S) = () def f2[S, T >: S : Singleton](x: S) = () def Test = f1(42) // f1[Int, Singleton & Int] // should infer (42 : Int) or throw an error? - f2(42) // f2[(42 : Int), (42 : Int)] \ No newline at end of file + f2(42) // f2[(42 : Int), (42 : Int)] diff --git a/tests/pending/pos/t5777.scala b/tests/pending/pos/t5777.scala index 24cea3616346..0d703b5fcce0 100644 --- a/tests/pending/pos/t5777.scala +++ b/tests/pending/pos/t5777.scala @@ -23,7 +23,7 @@ object MyApp { val r1 = new Poly[BigInt.type](BigInt) (null.asInstanceOf[BigInt.E] : r1.ring.E) - // Oddly, -Xprint:typer reports that r and r1 have the same inferred type. + // Oddly, -Vprint:typer reports that r and r1 have the same inferred type. // // private[this] val r: Poly[BigInt.type] = new Poly[BigInt.type](BigInt); // def r: Poly[BigInt.type] = MyApp.this.r; diff --git a/tests/pos-custom-args/captures/byname.scala b/tests/pos-custom-args/captures/byname.scala index 761496185a0d..ca1d02fb45dc 100644 --- a/tests/pos-custom-args/captures/byname.scala +++ b/tests/pos-custom-args/captures/byname.scala @@ -9,6 +9,6 @@ def test(cap1: Cap, cap2: Cap): I^{cap1} = def f() = if cap1 == cap1 then I() else I() def h(x: ->{cap} I) = x h(f()) // OK - def hh(x: -> I @retainsByName(cap1)) = x + def hh(x: -> I @retainsByName[cap1.type]) = x h(f()) diff --git a/tests/pos-custom-args/captures/cap-paramlist8-desugared.scala b/tests/pos-custom-args/captures/cap-paramlist8-desugared.scala index e57946bdadc1..15b829646c65 100644 --- a/tests/pos-custom-args/captures/cap-paramlist8-desugared.scala +++ b/tests/pos-custom-args/captures/cap-paramlist8-desugared.scala @@ -11,18 +11,18 @@ val z: Any^{cap} = ??? } val baz3: - Int -> [C >: caps.CapSet <: caps.CapSet^, - D >: caps.CapSet <: caps.CapSet^{C^}, E >: caps.CapSet <: - caps.CapSet^{C^, x}] => () -> [F >: caps.CapSet^{x, y} <: - caps.CapSet^{C^, E^}] => (x: Int) -> (Ctx[F]) ?-> Int + Int -> [C^ >: caps.CapSet <: caps.CapSet^, + D^ >: caps.CapSet <: caps.CapSet^{C}, E^ >: caps.CapSet <: + caps.CapSet^{C, x}] => () -> [F^ >: caps.CapSet^{x, y} <: + caps.CapSet^{C, E}] => (x: Int) -> (Ctx[F]) ?-> Int = (i: Int) => [ - C >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{cap}, - D >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{C^}, - E >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{C^, x}] => + C^ >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{cap}, + D^ >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{C}, + E^ >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{C, x}] => () => [ F >: _root_.scala.caps.CapSet^{x, y} <: - _root_.scala.caps.CapSet^{C^, E^} + _root_.scala.caps.CapSet^{C, E} ] => (x: Int) => (ev: Ctx[F]) ?=> 1 () } diff --git a/tests/pos-custom-args/captures/cap-paramlists.scala b/tests/pos-custom-args/captures/cap-paramlists.scala new file mode 100644 index 000000000000..da9bf0779a6c --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists.scala @@ -0,0 +1,21 @@ +import language.experimental.captureChecking + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + def foo[A^ >: {y} <: {x}, + B^, + C^ <: {x}, + D^, + E^ <: {C}, + F^ <: {C}, + G^ <: {x, y}, + H^ >: {x} <: {x,y}, T, U >: {x}]()[I^ <: {y, G, H}, + J^ <: {O.z}, + K^ <: {x, O.z}, + L^ <: {x, y, O.z}, + M^ >: {x, y, O.z} <: {C}, + N^ >: {x} <: {x}, + O^ >: {O.z} <: {O.z}] = ??? \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists2.scala b/tests/pos-custom-args/captures/cap-paramlists2.scala new file mode 100644 index 000000000000..2b62d0f0f34c --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists2.scala @@ -0,0 +1,6 @@ +import language.experimental.captureChecking + +trait Bar: + type C^ + +def useFoo[D^](x: Bar { type C = {D} } ): Any^{x.C} = ??? \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists3.scala b/tests/pos-custom-args/captures/cap-paramlists3.scala new file mode 100644 index 000000000000..29ae98cb98d6 --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists3.scala @@ -0,0 +1,10 @@ +import language.experimental.captureChecking + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + val bar = [C^, D <: {C}, E^ <: {C,x}, F >: {x,y} <: {C,E}] => (x: Int) => 1 \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists4.scala b/tests/pos-custom-args/captures/cap-paramlists4.scala new file mode 100644 index 000000000000..179da36ef1ed --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists4.scala @@ -0,0 +1,6 @@ +import language.experimental.captureChecking + +trait Foo[U^, V^, W^]: + type C = {caps.cap} + type D = {caps.cap} + type E >: {V,W} <: {U} \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists5.scala b/tests/pos-custom-args/captures/cap-paramlists5.scala new file mode 100644 index 000000000000..ec0933017c36 --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists5.scala @@ -0,0 +1,16 @@ +import language.experimental.captureChecking +import language.experimental.namedTypeArguments + +def test2 = + val x: Any^ = ??? + def foo[A^, B^ >: {A}, T, U](x: Int) = 1 + foo[{x}, {x}, Int, String](0) + foo[{}, {}, { def bar: Int }, { type D^ = {x} }](0) + trait Foo { type D^ } + foo[{}, {}, Foo, Foo](0) + foo[A = {x}, B = {x}](0) + foo[A = {x}](0) + foo[T = Int](0) + foo[T = Int, A = {x}](1) + foo[A = {x}, T = Int](1) + foo[B = {}, U = String, A = {}](1) \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists6.scala b/tests/pos-custom-args/captures/cap-paramlists6.scala new file mode 100644 index 000000000000..d0567089556c --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists6.scala @@ -0,0 +1,8 @@ +import language.experimental.captureChecking + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + val baz = () => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E}] => (x: Int) => 1 \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists7.scala b/tests/pos-custom-args/captures/cap-paramlists7.scala new file mode 100644 index 000000000000..abe59edd6835 --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists7.scala @@ -0,0 +1,8 @@ +import language.experimental.captureChecking + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + val baz2 = (i: Int) => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E}] => (x: Int) => 1 \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists8.scala b/tests/pos-custom-args/captures/cap-paramlists8.scala new file mode 100644 index 000000000000..f300e857282a --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists8.scala @@ -0,0 +1,8 @@ +import language.experimental.captureChecking + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + val baz3 = (i: Int) => [C^, D^ <: {C}, E^ <: {C,x}] => () => [F^ >: {x,y} <: {C,E}] => (x: Int) => 1 \ No newline at end of file diff --git a/tests/pos-custom-args/captures/caps-universal.scala b/tests/pos-custom-args/captures/caps-universal.scala index 3768c640fd68..6555062395c4 100644 --- a/tests/pos-custom-args/captures/caps-universal.scala +++ b/tests/pos-custom-args/captures/caps-universal.scala @@ -1,7 +1,7 @@ import annotation.retains val foo: Int => Int = x => x -val bar: (Int -> Int) @retains(caps.cap) = foo +val bar: (Int -> Int) @retains[caps.cap.type] = foo val baz: Int => Int = bar diff --git a/tests/pos-custom-args/captures/capset-members.scala b/tests/pos-custom-args/captures/capset-members.scala new file mode 100644 index 000000000000..4d05c5594dac --- /dev/null +++ b/tests/pos-custom-args/captures/capset-members.scala @@ -0,0 +1,24 @@ +import language.experimental.captureChecking + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + trait CaptureSet: + type A^ >: {y} <: {x} + type B^ = {x} + type C^ <: {x} + type D^ + type E^ <: {C} + type F^ <: {C} + type G^ <: {x, y} + type H^ >: {x} <: {x,y} + type I^ = {y, G, H} + type J^ = {O.z} + type K^ = {x, O.z} + type L^ <: {x, y, O.z} + type M^ >: {x, y, O.z} <: {C} + type N^ >: {x} <: {x} + type O^ >: {O.z} <: {O.z} + type P^ >: {B,D} \ No newline at end of file diff --git a/tests/pos-custom-args/captures/capset-members2.scala b/tests/pos-custom-args/captures/capset-members2.scala new file mode 100644 index 000000000000..13d527d0b556 --- /dev/null +++ b/tests/pos-custom-args/captures/capset-members2.scala @@ -0,0 +1,16 @@ +import language.experimental.captureChecking + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + + abstract class Foo[A^, T]: + type C^ <: {A} + abstract class Bar extends Foo[{x,y,O.z}, String]: + override type C^ <: {x,y} + class Baz extends Bar: + final override type C = {y} \ No newline at end of file diff --git a/tests/pos-custom-args/captures/capt-capability.scala b/tests/pos-custom-args/captures/capt-capability.scala index 03b5cb1bbabf..21c63d3fa608 100644 --- a/tests/pos-custom-args/captures/capt-capability.scala +++ b/tests/pos-custom-args/captures/capt-capability.scala @@ -1,4 +1,4 @@ -import caps.Capability +import caps.{Capability, Sharable} def f1(c: Capability): () ->{c} c.type = () => c // ok @@ -14,7 +14,7 @@ def f3: Int = x def foo() = - val x: Capability = ??? + val x: Sharable = ??? val y: Capability = x val x2: () ->{x} Capability = ??? val y2: () ->{x} Capability = x2 @@ -22,7 +22,10 @@ def foo() = val z1: () => Capability = f1(x) def h[X](a: X)(b: X) = a - val z2 = - if x == null then () => x else () => new Capability() {} + val z2: (y: Unit) ->{x} Capability^ = + if x == null then (y: Unit) => x else (y: Unit) => new Capability() {} + // z2's type cannot be inferred, see neg test + //val z3 = + // if x == null then (y: Unit) => x else (y: Unit) => new Capability() {} val _ = x diff --git a/tests/pos-custom-args/captures/capt-depfun.scala b/tests/pos-custom-args/captures/capt-depfun.scala index e3abbe0994c5..fb6866459aba 100644 --- a/tests/pos-custom-args/captures/capt-depfun.scala +++ b/tests/pos-custom-args/captures/capt-depfun.scala @@ -1,21 +1,24 @@ import annotation.retains +import caps.consume class C -type Cap = C @retains(caps.cap) +type Cap = C @retains[caps.cap.type] -type T = (x: Cap) -> String @retains(x) +type T = (x: Cap) -> STR @retains[x.type] type ID[X] = X -val aa: ((x: Cap) -> String @retains(x)) = (x: Cap) => "" +class STR(s: String) -def f(y: Cap, z: Cap): String @retains(caps.cap) = - val a: ((x: Cap) -> String @retains(x)) = (x: Cap) => "" +val aa: ((x: Cap) -> STR @retains[x.type]) = (x: Cap) => STR("") + +def f(@consume y: Cap, z: Cap): STR @retains[caps.cap.type] = + val a: ((x: Cap) -> STR @retains[x.type]) = (x: Cap) => STR("") val b = a(y) - val c: String @retains(y) = b - def g(): C @retains(y, z) = ??? + val c: STR @retains[y.type] = b + def g(): C @retains[y.type | z.type] = ??? val d = a(g()) - val ac: ((x: Cap) -> ID[String @retains(x) -> String @retains(x)]) = ??? - val bc: String^{y} -> String^{y} = ac(y) - val dc: String -> String^{y, z} = ac(g()) + val ac: ((x: Cap) -> ID[STR @retains[x.type] -> STR @retains[x.type]]) = ??? + val bc: STR^{y} -> STR^{y} = ac(y) + val dc: STR -> STR^{y, z} = ac(g()) c diff --git a/tests/pos-custom-args/captures/capt-depfun2.scala b/tests/pos-custom-args/captures/capt-depfun2.scala index e4645cfcc920..101ea6bb94e1 100644 --- a/tests/pos-custom-args/captures/capt-depfun2.scala +++ b/tests/pos-custom-args/captures/capt-depfun2.scala @@ -1,9 +1,11 @@ import annotation.retains +import caps.consume class C -type Cap = C @retains(caps.cap) +type Cap = C @retains[caps.cap.type] -def f(y: Cap, z: Cap) = - def g(): C @retains(y, z) = ??? - val ac: ((x: Cap) -> Array[String @retains(x)]) = ??? - val dc: Array[? >: String <: String]^{y, z} = ac(g()) // needs to be inferred +class STR +def f(@consume y: Cap, @consume z: Cap) = + def g(): C @retains[y.type | z.type] = ??? + val ac: ((x: Cap) -> Array[STR @retains[x.type]]) = ??? + val dc: Array[? >: STR <: STR^]^{y, z} = ac(g()) // needs to be inferred val ec = ac(y) diff --git a/tests/pos-custom-args/captures/capt0.scala b/tests/pos-custom-args/captures/capt0.scala index 013ff3a4ee19..ea535bbd433e 100644 --- a/tests/pos-custom-args/captures/capt0.scala +++ b/tests/pos-custom-args/captures/capt0.scala @@ -2,6 +2,6 @@ object Test: def test() = val x: Any^ = "abc" - val y: Object @scala.annotation.retains(x) = ??? - val z: Object @scala.annotation.retains(x, caps.cap) = y: Object @annotation.retains(x) + val y: Object @scala.annotation.retains[x.type] = ??? + val z: Object @scala.annotation.retains[x.type | caps.cap.type] = y: Object @annotation.retains[x.type] diff --git a/tests/pos-custom-args/captures/capt2.scala b/tests/pos-custom-args/captures/capt2.scala index 45381bf602ed..7690b892d120 100644 --- a/tests/pos-custom-args/captures/capt2.scala +++ b/tests/pos-custom-args/captures/capt2.scala @@ -1,18 +1,18 @@ import annotation.retains class C -type Cap = C @retains(caps.cap) +type Cap = C @retains[caps.cap.type] def test1() = - val y: String^ = "" - def x: Object @retains(y) = y + val y: C^ = C() + def x: Object @retains[y.type] = y def test2() = val x: Cap = C() val y = () => { x; () } - def z: (() -> Unit) @retains(x) = y - z: (() -> Unit) @retains(x) - def z2: (() -> Unit) @retains(y) = y - z2: (() -> Unit) @retains(y) + def z: (() -> Unit) @retains[x.type] = y + z: (() -> Unit) @retains[x.type] + def z2: (() -> Unit) @retains[y.type] = y + z2: (() -> Unit) @retains[y.type] val p: () => String = () => "abc" val q: C^{p} = ??? val _ = p: (() ->{p} String) diff --git a/tests/pos-custom-args/captures/caseclass.scala b/tests/pos-custom-args/captures/caseclass.scala index fe7e02b1b6c2..bae55a82c3bc 100644 --- a/tests/pos-custom-args/captures/caseclass.scala +++ b/tests/pos-custom-args/captures/caseclass.scala @@ -1,12 +1,13 @@ class C extends caps.Capability +class STR(s: String) object test1: - case class Ref(x: String^) + case class Ref(x: STR^) def test(c: C) = - val x1 = Ref("hello") + val x1 = Ref(STR("hello")) val y = x1 match case Ref(z) => z - val yc: String = y + val yc: STR = y object test2: case class Ref(x: () => Unit) diff --git a/tests/pos-custom-args/captures/cc-def-fresh.scala b/tests/pos-custom-args/captures/cc-def-fresh.scala new file mode 100644 index 000000000000..4e426326f434 --- /dev/null +++ b/tests/pos-custom-args/captures/cc-def-fresh.scala @@ -0,0 +1,10 @@ +import language.experimental.captureChecking +trait Collection[T] +trait IO +def empty[T]: Collection[T]^ = ??? +def emptyAlt[T](): Collection[T]^ = ??? +def newIO: IO^ = ??? +def test1(): Unit = + val t1: Collection[Int]^ = empty[Int] // ok + val t2: IO^ = newIO // ok + val t3: Collection[Int]^ = emptyAlt[Int]() // ok diff --git a/tests/pos-custom-args/captures/cc-expand.scala b/tests/pos-custom-args/captures/cc-expand.scala index 1bed7b1cf001..15529ad07ceb 100644 --- a/tests/pos-custom-args/captures/cc-expand.scala +++ b/tests/pos-custom-args/captures/cc-expand.scala @@ -5,18 +5,18 @@ object Test: class B class C class CTC - type CT = CTC @retains(caps.cap) + type CT = CTC @retains[caps.cap.type] def test(ct: CT, dt: CT) = def x0: A -> B^{ct} = ??? - def x1: A -> B @retains(ct) = ??? - def x2: A -> B -> C @retains(ct) = ??? - def x3: A -> () -> B -> C @retains(ct) = ??? + def x1: A -> B @retains[ct.type] = ??? + def x2: A -> B -> C @retains[ct.type] = ??? + def x3: A -> () -> B -> C @retains[ct.type] = ??? - def x4: (x: A @retains(ct)) -> B -> C = ??? + def x4: (x: A @retains[ct.type]) -> B -> C = ??? - def x5: A -> (x: B @retains(ct)) -> () -> C @retains(dt) = ??? - def x6: A -> (x: B @retains(ct)) -> (() -> C @retains(dt)) @retains(x, dt) = ??? - def x7: A -> (x: B @retains(ct)) -> (() -> C @retains(dt)) @retains(x) = ??? \ No newline at end of file + def x5: A -> (x: B @retains[ct.type]) -> () -> C @retains[dt.type] = ??? + def x6: A -> (x: B @retains[ct.type]) -> (() -> C @retains[dt.type]) @retains[x.type | dt.type] = ??? + def x7: A -> (x: B @retains[ct.type]) -> (() -> C @retains[dt.type]) @retains[x.type] = ??? \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cc-poly-1.scala b/tests/pos-custom-args/captures/cc-poly-1.scala index ed32d94f7a99..96bd58d65334 100644 --- a/tests/pos-custom-args/captures/cc-poly-1.scala +++ b/tests/pos-custom-args/captures/cc-poly-1.scala @@ -7,13 +7,13 @@ import caps.{CapSet, Capability} class C extends Capability class D - def f[X^](x: D^{X^}): D^{X^} = x - def g[X^](x: D^{X^}, y: D^{X^}): D^{X^} = x - def h[X^](): D^{X^} = ??? + def f[X^](x: D^{X}): D^{X} = x + def g[X^](x: D^{X}, y: D^{X}): D^{X} = x + def h[X^](): D^{X} = ??? def test(c1: C, c2: C) = val d: D^{c1, c2} = D() - val x = f[CapSet^{c1, c2}](d) + val x = f[{c1, c2}](d) val _: D^{c1, c2} = x val d1: D^{c1} = D() val d2: D^{c2} = D() diff --git a/tests/pos-custom-args/captures/cc-poly-source-capability.scala b/tests/pos-custom-args/captures/cc-poly-source-capability.scala index 6f987658923c..7d06edd36415 100644 --- a/tests/pos-custom-args/captures/cc-poly-source-capability.scala +++ b/tests/pos-custom-args/captures/cc-poly-source-capability.scala @@ -1,25 +1,25 @@ import language.experimental.captureChecking import annotation.experimental -import caps.{CapSet, SharedCapability} +import caps.{CapSet, Sharable} import caps.use @experimental object Test: - class Async extends SharedCapability + class Async extends Sharable def listener(async: Async): Listener^{async} = ??? class Listener class Source[X^]: - private var listeners: Set[Listener^{X^}] = Set.empty - def register(x: Listener^{X^}): Unit = + private var listeners: Set[Listener^{X}] = Set.empty + def register(x: Listener^{X}): Unit = listeners += x - def allListeners: Set[Listener^{X^}] = listeners + def allListeners: Set[Listener^{X}] = listeners def test1(async1: Async, @use others: List[Async]) = - val src = Source[CapSet^{async1, others*}] + val src = Source[{async1, others*}] val _: Set[Listener^{async1, others*}] = src.allListeners val lst1 = listener(async1) val lsts = others.map(listener) diff --git a/tests/pos-custom-args/captures/cc-poly-varargs.scala b/tests/pos-custom-args/captures/cc-poly-varargs.scala index 8bd0dc89bc7a..d39d4a9c30e5 100644 --- a/tests/pos-custom-args/captures/cc-poly-varargs.scala +++ b/tests/pos-custom-args/captures/cc-poly-varargs.scala @@ -1,13 +1,13 @@ abstract class Source[+T, Cap^] -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^} = ??? +def race[T, Cap^](sources: Source[T, {Cap}]^{Cap}*): Source[T, {Cap}]^{Cap} = ??? def either[T1, T2, Cap^]( - src1: Source[T1, Cap]^{Cap^}, - src2: Source[T2, Cap]^{Cap^}): Source[Either[T1, T2], Cap]^{Cap^} = + src1: Source[T1, {Cap}]^{Cap}, + src2: Source[T2, {Cap}]^{Cap}): Source[Either[T1, T2], {Cap}]^{Cap} = val left = src1.transformValuesWith(Left(_)) val right = src2.transformValuesWith(Right(_)) race(left, right) diff --git a/tests/pos-custom-args/captures/cc-use-iterable.scala b/tests/pos-custom-args/captures/cc-use-iterable.scala new file mode 100644 index 000000000000..84c497c0f6ce --- /dev/null +++ b/tests/pos-custom-args/captures/cc-use-iterable.scala @@ -0,0 +1,10 @@ +import language.experimental.captureChecking +trait IterableOnce[+T] +trait Iterable[+T] extends IterableOnce[T]: + def flatMap[U](@caps.use f: T => IterableOnce[U]^): Iterable[U]^{this, f*} + + +class IterableOnceExtensionMethods[T](val it: IterableOnce[T]) extends AnyVal: + def flatMap[U](@caps.use f: T => IterableOnce[U]^): IterableOnce[U]^{f*} = it match + case it: Iterable[T] => it.flatMap(f) + diff --git a/tests/pos-custom-args/captures/check-override-typebounds-2.scala b/tests/pos-custom-args/captures/check-override-typebounds-2.scala new file mode 100644 index 000000000000..308ed779f6f1 --- /dev/null +++ b/tests/pos-custom-args/captures/check-override-typebounds-2.scala @@ -0,0 +1,18 @@ +import language.experimental.captureChecking + +class Capbility + +trait A: + type T <: Capbility^{caps.cap.rd} + +class B extends A: + type T = C^{caps.cap.rd} + +class C extends Capbility + + +trait A2: + type T[Cap^] + + def takesCap[Cap^](t: T[Cap]): Unit + diff --git a/tests/pos-custom-args/captures/colltest.scala b/tests/pos-custom-args/captures/colltest.scala index eb2212f62b9b..e2f09297d571 100644 --- a/tests/pos-custom-args/captures/colltest.scala +++ b/tests/pos-custom-args/captures/colltest.scala @@ -1,3 +1,4 @@ +import language.experimental.captureChecking // Showing a problem with recursive references object CollectionStrawMan5 { @@ -17,14 +18,13 @@ object CollectionStrawMan5 { trait View[+A] extends Iterable[A] with IterableLike[A] case class Partition[A](val underlying: Iterable[A]^, p: A => Boolean) { - self: Partition[A]^{underlying, p} => class Partitioned(expected: Boolean) extends View[A]: - this: Partitioned^{self} => + this: Partitioned^{Partition.this} => def iterator: Iterator[A]^{this} = underlying.iterator.filter((x: A) => p(x) == expected) - val left: Partitioned^{self} = Partitioned(true) - val right: Partitioned^{self} = Partitioned(false) + val left: Partitioned^{Partition.this} = Partitioned(true) + val right: Partitioned^{Partition.this} = Partitioned(false) } } \ No newline at end of file diff --git a/tests/pos-custom-args/captures/correct-capture-variable-resolve.scala b/tests/pos-custom-args/captures/correct-capture-variable-resolve.scala new file mode 100644 index 000000000000..cc6ecf416c6d --- /dev/null +++ b/tests/pos-custom-args/captures/correct-capture-variable-resolve.scala @@ -0,0 +1,4 @@ +import language.experimental.captureChecking + +def f(x: AnyRef^, C: AnyRef^) = + def g[C^ <: {x}](y: AnyRef^{C}): AnyRef^{x} = y \ No newline at end of file diff --git a/tests/pos-custom-args/captures/deep-adapt.scala b/tests/pos-custom-args/captures/deep-adapt.scala new file mode 100644 index 000000000000..2a27763f53fa --- /dev/null +++ b/tests/pos-custom-args/captures/deep-adapt.scala @@ -0,0 +1,23 @@ +import caps.Mutable +import caps.cap + +trait Rdr[T]: + def get: T + +class Ref[T](init: T) extends Rdr[T], Mutable: + private var current = init + def get: T = current + update def put(x: T): Unit = current = x + +case class Pair[+A, +B](x: A, y: B) +class Swap[+A, +B](x: A, y: B) extends Pair[B, A](y, x) + +def Test(c: Object^): Unit = + val refs = List(Ref(1), Ref(2)) + val rdrs: List[Ref[Int]^{cap.rd}] = refs + val rdrs2: Seq[Ref[Int]^{cap.rd}] = refs + + val swapped = Swap(Ref(1), Ref("hello")) + val _: Swap[Ref[Int]^{cap.rd}, Ref[String]^{cap.rd}] = swapped + val _: Pair[Ref[String]^{cap.rd}, Ref[Int]^{cap.rd}] @unchecked = swapped + diff --git a/tests/pos-custom-args/captures/gears-problem-poly.scala b/tests/pos-custom-args/captures/gears-problem-poly.scala index fdbcf37a35a6..0b6fe3a9a0e9 100644 --- a/tests/pos-custom-args/captures/gears-problem-poly.scala +++ b/tests/pos-custom-args/captures/gears-problem-poly.scala @@ -7,8 +7,8 @@ trait Future[+T]: trait Channel[+T]: def read(): Ok[T] -class Collector[T, C^](val futures: Seq[Future[T]^{C^}]): - val results: Channel[Future[T]^{C^}] = ??? +class Collector[T, C^](val futures: Seq[Future[T]^{C}]): + val results: Channel[Future[T]^{C}] = ??? end Collector class Result[+T, +E]: @@ -17,13 +17,13 @@ class Result[+T, +E]: case class Err[+E](e: E) extends Result[Nothing, E] case class Ok[+T](x: T) extends Result[T, Nothing] -extension [T, C^](@use fs: Seq[Future[T]^{C^}]) +extension [T, C^](@use fs: Seq[Future[T]^{C}]) def awaitAllPoly = val collector = Collector(fs) - val fut: Future[T]^{C^} = collector.results.read().get + val fut: Future[T]^{C} = collector.results.read().get extension [T](@use fs: Seq[Future[T]^]) def awaitAll = fs.awaitAllPoly def awaitExplicit[T](@use fs: Seq[Future[T]^]): Unit = - awaitAllPoly[T, CapSet^{fs*}](fs) + awaitAllPoly[T, {fs*}](fs) diff --git a/tests/pos-custom-args/captures/i16116.scala b/tests/pos-custom-args/captures/i16116.scala index fdc386ac40e1..a103b9a740e2 100644 --- a/tests/pos-custom-args/captures/i16116.scala +++ b/tests/pos-custom-args/captures/i16116.scala @@ -26,12 +26,15 @@ object Test { def apply[A](expr: (CpsTransform[F], C) ?=> A): F[A] = ??? } - def asyncPlus[F[_]](a:Int, b:F[Int])(using cps: CpsTransform[F]): Int^{ cps } = - a + (cps.await(b).asInstanceOf[Int]) + class C(x: Int): + def +(that: C): C = ??? + + def asyncPlus[F[_]](a:C, b:F[C])(using cps: CpsTransform[F]): C^{ cps } = + a + (cps.await(b).asInstanceOf[C]) def testExample1Future(): Unit = val fr = cpsAsync[Future] { - val y = asyncPlus(1,Future successful 2).asInstanceOf[Int] + val y = asyncPlus(C(1),Future successful C(2)).asInstanceOf[Int] y+1 } diff --git a/tests/pos-custom-args/captures/i16226.scala b/tests/pos-custom-args/captures/i16226.scala index af0a44e6bdfc..79893d7266ba 100644 --- a/tests/pos-custom-args/captures/i16226.scala +++ b/tests/pos-custom-args/captures/i16226.scala @@ -1,4 +1,4 @@ -class Cap extends caps.SharedCapability +class Cap extends caps.Sharable class LazyRef[T](val elem: () => T): val get: () ->{elem} T = elem diff --git a/tests/pos-custom-args/captures/i20237.scala b/tests/pos-custom-args/captures/i20237.scala index 973f5d2025e3..53efbb2e7583 100644 --- a/tests/pos-custom-args/captures/i20237.scala +++ b/tests/pos-custom-args/captures/i20237.scala @@ -1,8 +1,12 @@ import language.experimental.captureChecking +import caps.* class Cap extends caps.Capability: def use[T](body: Cap ?=> T) = body(using this) +class Cap2 extends caps.Capability: + def use[T](body: Cap2 => T) = body(this) + class Box[T](body: Cap ?=> T): inline def open(using cap: Cap) = cap.use(body) diff --git a/tests/pos-custom-args/captures/i21313.scala b/tests/pos-custom-args/captures/i21313.scala index b388b6487cb5..4113c6a2998e 100644 --- a/tests/pos-custom-args/captures/i21313.scala +++ b/tests/pos-custom-args/captures/i21313.scala @@ -1,20 +1,21 @@ import caps.CapSet trait Async: - def await[T, Cap^](using caps.Contains[Cap, this.type])(src: Source[T, Cap]^): T = + def await[T, Cap^](using caps.Contains[Cap, this.type])(src: Source[T, {Cap}]^): T = + // FIXME: this is an irregularity: it works if we write caps.Contains[Cap, this.type], but we should expect to write caps.Contains[{Cap}, this.type]! val x: Async^{this} = ??? - val y: Async^{Cap^} = x + val y: Async^{Cap} = x val ac: Async^ = ??? - def f(using caps.Contains[Cap, ac.type]) = + def f(using caps.Contains[Cap, ac.type]) = // FIXME dito val x2: Async^{this} = ??? - val y2: Async^{Cap^} = x2 + val y2: Async^{Cap} = x2 val x3: Async^{ac} = ??? - val y3: Async^{Cap^} = x3 + val y3: Async^{Cap} = x3 ??? trait Source[+T, Cap^]: - final def await(using ac: Async^{Cap^}) = ac.await[T, Cap](this) // Contains[Cap, ac] is assured because {ac} <: Cap. + final def await(using ac: Async^{Cap}) = ac.await[T, {Cap}](this) // Contains[Cap, ac] is assured because {ac} <: Cap. def test(using ac1: Async^, ac2: Async^, x: String) = - val src1 = new Source[Int, CapSet^{ac1}] {} + val src1 = new Source[Int, {ac1}] {} ac1.await(src1) diff --git a/tests/pos-custom-args/captures/i21347.scala b/tests/pos-custom-args/captures/i21347.scala index a965b7e4f26b..a8fbb4fb6680 100644 --- a/tests/pos-custom-args/captures/i21347.scala +++ b/tests/pos-custom-args/captures/i21347.scala @@ -4,7 +4,7 @@ import language.experimental.captureChecking class Box[Cap^] {} -def run[Cap^](f: Box[Cap]^{Cap^} => Unit): Box[Cap]^{Cap^} = ??? +def run[Cap^](f: Box[Cap]^{Cap} => Unit): Box[{Cap}]^{Cap} = ??? def main() = val b = run(_ => ()) diff --git a/tests/pos-custom-args/captures/i21507.scala b/tests/pos-custom-args/captures/i21507.scala index bb80dafb3b45..455aa3940fb6 100644 --- a/tests/pos-custom-args/captures/i21507.scala +++ b/tests/pos-custom-args/captures/i21507.scala @@ -1,10 +1,10 @@ import language.experimental.captureChecking trait Box[Cap^]: - def store(f: (() -> Unit)^{Cap^}): Unit + def store(f: (() -> Unit)^{Cap}): Unit -def run[Cap^](f: Box[Cap]^{Cap^} => Unit): Box[Cap]^{Cap^} = - new Box[Cap]: - private var item: () ->{Cap^} Unit = () => () - def store(f: () ->{Cap^} Unit): Unit = +def run[Cap^](f: Box[{Cap}]^{Cap} => Unit): Box[{Cap}]^{Cap} = + new Box[{Cap}]: + private var item: () ->{Cap} Unit = () => () + def store(f: () ->{Cap} Unit): Unit = item = f // was error, now ok diff --git a/tests/pos-custom-args/captures/i21868.scala b/tests/pos-custom-args/captures/i21868.scala new file mode 100644 index 000000000000..b0d83efc03ca --- /dev/null +++ b/tests/pos-custom-args/captures/i21868.scala @@ -0,0 +1,16 @@ +import language.experimental.captureChecking +import caps.* + +class U + +trait AbstractWrong: + type C^ <: CapSet // no longer an error, the lower bound becomes CapSet now + def f(): U^{C} + +trait Abstract1: + type C^ >: CapSet <: CapSet^ + def f(): U^{C} + +trait Abstract2: + type C^ <: {cap} + def f(): U^{C} \ No newline at end of file diff --git a/tests/pos-custom-args/captures/i23027.scala b/tests/pos-custom-args/captures/i23027.scala new file mode 100644 index 000000000000..b7fc06d2bf27 --- /dev/null +++ b/tests/pos-custom-args/captures/i23027.scala @@ -0,0 +1,12 @@ +import language.experimental.captureChecking + +trait Test { + def foo(x: Test): Test = + Test.bar + ??? +} + +object Test { + val _bar: Any => Any = identity + def bar[T] = _bar.asInstanceOf[T => T] // error +} diff --git a/tests/pos-custom-args/captures/i23170.scala b/tests/pos-custom-args/captures/i23170.scala new file mode 100644 index 000000000000..886761a74e6e --- /dev/null +++ b/tests/pos-custom-args/captures/i23170.scala @@ -0,0 +1,8 @@ +import caps.* + +trait A + +extension (a: A^{cap.rd}) + def await = () + +def awaitA[C^ <: {cap.rd}](a: A^{C}) = a.await \ No newline at end of file diff --git a/tests/pos-custom-args/captures/i23421.scala b/tests/pos-custom-args/captures/i23421.scala new file mode 100644 index 000000000000..ef5e7564073e --- /dev/null +++ b/tests/pos-custom-args/captures/i23421.scala @@ -0,0 +1,16 @@ +import language.experimental.captureChecking +import caps.* + +trait Collection[T] extends Mutable: + update def add(elem: T): Unit + update def remove(elem: T): Unit + def get(index: Int): Option[T] + +object Collection: + def empty[T]: Collection[T] = ??? + +trait Foo: + val thunks: Collection[() => Unit] // that's fine + +object FooImpl1 extends Foo: + val thunks: Collection[() => Unit] = Collection.empty // was error, now ok diff --git a/tests/pos-custom-args/captures/i23422.scala b/tests/pos-custom-args/captures/i23422.scala new file mode 100644 index 000000000000..8e03d6701763 --- /dev/null +++ b/tests/pos-custom-args/captures/i23422.scala @@ -0,0 +1,10 @@ +import language.experimental.captureChecking +import caps.* +trait Cap +class Inv[T] extends Capability +class Inv2[T] +class Inv3[T] extends Mutable +def test(c: Cap^): Unit = + val t1: Inv[() ->{c} Unit] = Inv() // error + val t2: Inv2[() ->{c} Unit] = Inv2() // ok + val t3: Inv3[() ->{c} Unit] = Inv3() // error, too \ No newline at end of file diff --git a/tests/pos-custom-args/captures/i23570.scala b/tests/pos-custom-args/captures/i23570.scala new file mode 100644 index 000000000000..44f378792317 --- /dev/null +++ b/tests/pos-custom-args/captures/i23570.scala @@ -0,0 +1,7 @@ +def f[C^](xs: List[() ->{C} Unit]): List[() ->{C} Unit] = + xs.reverse + +def test(io: Object^, async: Object^): Unit = + val ok = f[{io}](Nil) + val x = f[{io}] // was error + val y = f[{io, async}] // was error diff --git a/tests/pos-custom-args/captures/i23737.scala b/tests/pos-custom-args/captures/i23737.scala new file mode 100644 index 000000000000..3d2bb4f6791b --- /dev/null +++ b/tests/pos-custom-args/captures/i23737.scala @@ -0,0 +1,14 @@ +import language.experimental.captureChecking + +class C + +trait A[T] + +trait B[CC^] extends A[C^{CC}] // error: CC not found + +trait D[CC^]: + val x: Object^{CC} = ??? + +def f(c: C^) = + val b = new B[{c}] {} + val a: A[C^{c}] = b \ No newline at end of file diff --git a/tests/pos-custom-args/captures/inferred-type-ok.scala b/tests/pos-custom-args/captures/inferred-type-ok.scala new file mode 100644 index 000000000000..49b5f05662fe --- /dev/null +++ b/tests/pos-custom-args/captures/inferred-type-ok.scala @@ -0,0 +1,5 @@ +class C + +def t = + val x: C^ = C() + x diff --git a/tests/pos-custom-args/captures/list-encoding.scala b/tests/pos-custom-args/captures/list-encoding.scala deleted file mode 100644 index 7c593bb524e3..000000000000 --- a/tests/pos-custom-args/captures/list-encoding.scala +++ /dev/null @@ -1,24 +0,0 @@ -package listEncoding -import annotation.retains - -class Cap - -type Op[T, C] = - T => C => C - -type List[T] = - [C] -> (op: Op[T, C]) -> (s: C) ->{op} C - -def nil[T]: List[T] = - [C] => (op: Op[T, C]) => (s: C) => s - -def cons[T](hd: T, tl: List[T]): List[T] = - [C] => (op: Op[T, C]) => (s: C) => op(hd)(tl(op)(s)) - -def foo(c: Cap^) = - def f(x: String @retains(c), y: String @retains(c)) = - cons(x, cons(y, nil)) - def g(x: String @retains(c), y: Any) = - cons(x, cons(y, nil)) - def h(x: String, y: Any @retains(c)) = - cons(x, cons(y, nil)) diff --git a/tests/pos-custom-args/captures/matrix.scala b/tests/pos-custom-args/captures/matrix.scala new file mode 100644 index 000000000000..11c314fa6e51 --- /dev/null +++ b/tests/pos-custom-args/captures/matrix.scala @@ -0,0 +1,33 @@ +import caps.Mutable +import caps.cap + +trait Rdr[T]: + def get: T + +class Ref[T](init: T) extends Rdr[T], Mutable: + private var current = init + def get: T = current + update def put(x: T): Unit = current = x + +abstract class IMatrix: + def apply(i: Int, j: Int): Double + +class Matrix(nrows: Int, ncols: Int) extends IMatrix, Mutable: + val arr = Array.fill(nrows, ncols)(0.0) + def apply(i: Int, j: Int): Double = arr(i)(j) + update def update(i: Int, j: Int, x: Double): Unit = arr(i)(j) = x + + +def mul(x: Matrix, y: Matrix, z: Matrix^): Unit = ??? +def mul1(x: Matrix^{cap.rd}, y: Matrix^{cap.rd}, z: Matrix^): Unit = ??? + +def Test(c: Object^): Unit = + val m1 = Matrix(10, 10) + val m2 = Matrix(10, 10) + mul(m1, m1, m2) // should be ok + mul(m1, m1, m2) // should be ok + + def f2(): Matrix^ = Matrix(10, 10) + + val i1: IMatrix^{cap.rd} = m1 + val i2: IMatrix^{cap.rd} = f2() diff --git a/tests/pos-custom-args/captures/mutRef.scala b/tests/pos-custom-args/captures/mutRef.scala index 5fe82c9b987a..303188678df8 100644 --- a/tests/pos-custom-args/captures/mutRef.scala +++ b/tests/pos-custom-args/captures/mutRef.scala @@ -2,4 +2,4 @@ import caps.Mutable class Ref(init: Int) extends Mutable: private var current = init def get: Int = current - mut def put(x: Int): Unit = current = x + update def put(x: Int): Unit = current = x diff --git a/tests/pos-custom-args/captures/polycap.scala b/tests/pos-custom-args/captures/polycap.scala index 684f46454595..4d830ed279b8 100644 --- a/tests/pos-custom-args/captures/polycap.scala +++ b/tests/pos-custom-args/captures/polycap.scala @@ -2,10 +2,10 @@ import language.experimental.captureChecking class Source[+T, Cap^] -def completed[T, Cap^](result: T): Source[T, Cap] = +def completed[T, Cap^](result: T): Source[T, {Cap}] = //val fut = new Source[T, Cap]() - val fut2 = new Source[T, Cap]() - fut2: Source[T, Cap] + val fut2 = new Source[T, {Cap}]() + fut2: Source[T, {Cap}] diff --git a/tests/pos-custom-args/captures/reach-capability.scala b/tests/pos-custom-args/captures/reach-capability.scala index 7160b280ce4f..77bd91957fa0 100644 --- a/tests/pos-custom-args/captures/reach-capability.scala +++ b/tests/pos-custom-args/captures/reach-capability.scala @@ -1,6 +1,6 @@ import language.experimental.captureChecking import annotation.experimental -import caps.SharedCapability +import caps.Sharable import caps.use @experimental object Test2: @@ -8,7 +8,7 @@ import caps.use class List[+A]: def map[B](f: A => B): List[B] = ??? - class Label extends SharedCapability + class Label extends Sharable class Listener diff --git a/tests/pos-custom-args/captures/reach-in-results.scala b/tests/pos-custom-args/captures/reach-in-results.scala new file mode 100644 index 000000000000..a00f7a4499da --- /dev/null +++ b/tests/pos-custom-args/captures/reach-in-results.scala @@ -0,0 +1,21 @@ +import language.experimental.captureChecking + +class IO extends caps.Capability + +def test(io: IO^): Unit = + val f = (x: () => IO^) => x() + val _: (x: () => IO^) => IO^{x} = f//(x: () => IO^) => f(x) + def g(x: IO^ => IO^) = x(io) + def h(x: (y: IO^) => IO^) = x(io) + + val a: () -> IO^ = ??? + val _: () -> IO^{a*} = a + val b: (x: IO^) -> IO^ = ??? + val _: (x: IO^) -> IO^ = b + val c: IO^ -> IO^ = ??? + val cc: IO^ -> IO^{c*} = c + +def testByName(io: IO^): Unit = + def f(x: => () => IO^) = x() + def g(x: => IO^ => IO^) = x(io) + def h(x: => (y: IO^) => IO^) = x(io) diff --git a/tests/pos-custom-args/captures/restrict-subsumes.scala b/tests/pos-custom-args/captures/restrict-subsumes.scala new file mode 100644 index 000000000000..b22c11371fe2 --- /dev/null +++ b/tests/pos-custom-args/captures/restrict-subsumes.scala @@ -0,0 +1,11 @@ +import caps.{cap, Classifier, Capability} + +trait Read extends Capability, Classifier + +trait A extends Read + +def weird(f: () ->{cap.only[Read]} Unit) = ??? + +def test(x: A^) = + val g = () => println(x) + weird(g) diff --git a/tests/pos-custom-args/captures/restrict-try.scala b/tests/pos-custom-args/captures/restrict-try.scala new file mode 100644 index 000000000000..ac23abc71eb9 --- /dev/null +++ b/tests/pos-custom-args/captures/restrict-try.scala @@ -0,0 +1,29 @@ +import caps.{Capability, Control, Mutable} + +class Try[+T] +case class Ok[T](x: T) extends Try[T] +case class Fail(ex: Exception) extends Try[Nothing] + +trait Matrix extends Mutable: + update def update(): Unit + +trait Label extends Control: + def break(): Unit + +object Try: + def apply[T](body: () => T): Try[T]^{body.only[Control]} = + try Ok(body()) + catch case ex: Exception => Fail(ex) + +def Test(m: Matrix^, l: Label) = + val x = + Try: + val b = () => + m.update() + l.break() + val _: () ->{m, l} Unit = b + b + val y: Try[Unit]^{l} = x + + + diff --git a/tests/pos-custom-args/captures/rinaudo.scala b/tests/pos-custom-args/captures/rinaudo.scala new file mode 100644 index 000000000000..7091f8e94d6d --- /dev/null +++ b/tests/pos-custom-args/captures/rinaudo.scala @@ -0,0 +1,12 @@ +import language.experimental.captureChecking +import caps.* + +trait FileSystem extends Capability: + def print(msg: String): Unit + +class Logger(using fs: FileSystem): + def info(msg: String): Unit = fs.print(msg) + +def log(msg: String): FileSystem ?-> Unit = + val l = new Logger + l.info(msg) \ No newline at end of file diff --git a/tests/pos-custom-args/captures/sep-pairs.scala b/tests/pos-custom-args/captures/sep-pairs.scala index df1a3098d22d..494996c12161 100644 --- a/tests/pos-custom-args/captures/sep-pairs.scala +++ b/tests/pos-custom-args/captures/sep-pairs.scala @@ -4,7 +4,7 @@ import caps.{cap, consume, use} class Ref extends Mutable: var x = 0 def get: Int = x - mut def put(y: Int): Unit = x = y + update def put(y: Int): Unit = x = y case class Pair[+A, +B](fst: A, snd: B) diff --git a/tests/pos-custom-args/captures/setup/a_1.scala b/tests/pos-custom-args/captures/setup/a_1.scala index 21afde8be3ea..8c7de185e71b 100644 --- a/tests/pos-custom-args/captures/setup/a_1.scala +++ b/tests/pos-custom-args/captures/setup/a_1.scala @@ -1,6 +1,5 @@ // a.scala import language.experimental.captureChecking -import scala.caps.CapSet trait A: - def f[C^](x: AnyRef^{C^}): Unit + 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 index d5ba925970ba..c10048a78cce 100644 --- a/tests/pos-custom-args/captures/setup/b_1.scala +++ b/tests/pos-custom-args/captures/setup/b_1.scala @@ -1,5 +1,4 @@ import language.experimental.captureChecking -import scala.caps.CapSet class B extends A: - def f[C^](x: AnyRef^{C^}): Unit = ??? + 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 index d5ba925970ba..c10048a78cce 100644 --- a/tests/pos-custom-args/captures/setup/b_2.scala +++ b/tests/pos-custom-args/captures/setup/b_2.scala @@ -1,5 +1,4 @@ import language.experimental.captureChecking -import scala.caps.CapSet class B extends A: - def f[C^](x: AnyRef^{C^}): Unit = ??? + def f[C^](x: AnyRef^{C}): Unit = ??? diff --git a/tests/pos-custom-args/captures/singleton-conformance.scala b/tests/pos-custom-args/captures/singleton-conformance.scala new file mode 100644 index 000000000000..7858e2adee2c --- /dev/null +++ b/tests/pos-custom-args/captures/singleton-conformance.scala @@ -0,0 +1,6 @@ +import language.experimental.captureChecking + +def id[T](x: T): T = x + +trait A: + def t(): this.type = id(this) \ No newline at end of file diff --git a/tests/pos-custom-args/captures/singleton-subtyping.scala b/tests/pos-custom-args/captures/singleton-subtyping.scala new file mode 100644 index 000000000000..b8ee88634905 --- /dev/null +++ b/tests/pos-custom-args/captures/singleton-subtyping.scala @@ -0,0 +1,16 @@ +class Box[+T](x: T) + +def Test(c: Object^): Unit = + val x: Object^{c} = c + + val x2: x.type^{x} = x + val x3: x.type = x2 + + val b: Box[x.type] = Box(x) + val b1: Box[x.type^{x}] = b + val b2: Box[x.type] = b1 + + + + + diff --git a/tests/pos-custom-args/captures/skolems2.scala b/tests/pos-custom-args/captures/skolems2.scala index a9ff6e258317..53fbe935e362 100644 --- a/tests/pos-custom-args/captures/skolems2.scala +++ b/tests/pos-custom-args/captures/skolems2.scala @@ -13,6 +13,17 @@ def Test(@consume c: Object^, @consume f: Object^ => Object^) = val x4: Object^ = { unsafeAssumeSeparate(f)(cc) } +def Test2(@consume c: Object^, @consume f: Object^ => Object^) = + def cc(): Object^ = unsafeAssumeSeparate(c) + val x1 = + { f(cc()) } + val x2 = + f(cc()) + val x3: Object^ = + f(cc()) + val x4: Object^ = + { unsafeAssumeSeparate(f)(cc()) } + diff --git a/tests/pos-custom-args/captures/trickyTrailingUpArrow.scala b/tests/pos-custom-args/captures/trickyTrailingUpArrow.scala index 71b663de5354..1ca4403a54b7 100644 --- a/tests/pos-custom-args/captures/trickyTrailingUpArrow.scala +++ b/tests/pos-custom-args/captures/trickyTrailingUpArrow.scala @@ -1,9 +1,10 @@ -object Test: + class STR + object Test: var x = 0 - type FreshContext = String^ + type FreshContext = STR^ x += 1 - inline def ctx(using c: String) = c + inline def ctx(using c: STR) = c - val y: String^ -> Unit = ??? - val z: String^ ?-> Unit = ??? + val y: STR^ -> Unit = ??? + val z: STR^ ?-> Unit = ??? diff --git a/tests/pos-custom-args/captures/try.scala b/tests/pos-custom-args/captures/try.scala index 05c41be69001..c88c842babc5 100644 --- a/tests/pos-custom-args/captures/try.scala +++ b/tests/pos-custom-args/captures/try.scala @@ -2,7 +2,7 @@ import annotation.retains import language.experimental.erasedDefinitions class CT[E <: Exception] -type CanThrow[E <: Exception] = CT[E] @retains(caps.cap) +type CanThrow[E <: Exception] = CT[E] @retains[caps.cap.type] infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?-> R @@ -14,7 +14,7 @@ def foo(x: Boolean): Int throws Fail = if x then 1 else raise(Fail()) def handle[E <: Exception, R](op: (erased CanThrow[E]) -> R)(handler: E -> R): R = - erased val x: CanThrow[E] = ??? : CanThrow[E] + erased val x = caps.unsafe.unsafeErasedValue[CanThrow[E]] try op(x) catch case ex: E => handler(ex) diff --git a/tests/pos-custom-args/captures/tuple-ops-2.scala b/tests/pos-custom-args/captures/tuple-ops-2.scala new file mode 100644 index 000000000000..322a52feaaab --- /dev/null +++ b/tests/pos-custom-args/captures/tuple-ops-2.scala @@ -0,0 +1,19 @@ +sealed trait Tup +case object Emp extends Tup +type Emp = Emp.type +case class Cons[h, t <: Tup](hh: h, tt: t) extends Tup + +type Union[T <: Tup] = T match + case Emp => Nothing + case Cons[h, t] => h | Union[t] + +type Concat[T <: Tup, U <: Tup] <: Tup = T match + case Emp => U + case Cons[h, t] => Cons[h, Concat[t, U]] + +type FlatMap[T <: Tup, F[_ <: Union[T]] <: Tup] <: Tup = T match + case Emp => Emp + case Cons[h, t] => Concat[F[h], FlatMap[t, F]] + +type A = + FlatMap[Cons[Boolean, Cons[String, Emp]], [T] =>> Cons[T, Cons[List[T], Emp]]] \ No newline at end of file diff --git a/tests/pos-custom-args/captures/tuple-ops.scala b/tests/pos-custom-args/captures/tuple-ops.scala new file mode 100644 index 000000000000..6259328dd9ab --- /dev/null +++ b/tests/pos-custom-args/captures/tuple-ops.scala @@ -0,0 +1,13 @@ +sealed trait Tupp + +case object EmptyTupp extends Tupp +type EmptyTupp = EmptyTupp.type +infix case class `*::`[H, T <: Tupp](h: H, t: T) extends Tupp + +type Union[T <: Tupp] = T match + case EmptyTupp => Nothing + case h *:: t => h | Union[t] + +type Map[T <: Tupp, F[_ <: Union[T]]] <: Tupp = T match + case EmptyTupp => EmptyTupp + case h *:: t => F[h] *:: Map[t, F] \ No newline at end of file diff --git a/tests/pos-custom-args/captures/use-alternatives.scala b/tests/pos-custom-args/captures/use-alternatives.scala new file mode 100644 index 000000000000..b4fd896210d4 --- /dev/null +++ b/tests/pos-custom-args/captures/use-alternatives.scala @@ -0,0 +1,25 @@ +import language.experimental.captureChecking +import caps.{cap, use} + +def foo1(@use xs: List[() => Unit]): Unit = + var x: () ->{xs*} Unit = xs.head + var ys = xs + while ys.nonEmpty do + ys = ys.tail + x = ys.head + +def foo2(@use xs: List[() => Unit]): Unit = + def inner[@use C^](xs: List[() ->{C} Unit]): Unit = + var x: () ->{C} Unit = xs.head + var ys = xs + while ys.nonEmpty do + ys = ys.tail + x = ys.head + inner(xs) + +def foo3[@use C^](xs: List[() ->{C} Unit]): Unit = + var x: () ->{C} Unit = xs.head + var ys = xs + while ys.nonEmpty do + ys = ys.tail + x = ys.head diff --git a/tests/pos-java16+/java-records/FromScala.scala b/tests/pos-java16+/java-records/FromScala.scala index 8654f88f1835..14f54b6d3868 100644 --- a/tests/pos-java16+/java-records/FromScala.scala +++ b/tests/pos-java16+/java-records/FromScala.scala @@ -49,3 +49,7 @@ object C: def useR4: Unit = val r4 = R4(1) val i: Int = r4.t + + def useR5: Unit = + val r5 = R5("hi", 1, 2, 3) + val xs: Array[Int] = r5.values diff --git a/tests/pos-java16+/java-records/R5.java b/tests/pos-java16+/java-records/R5.java new file mode 100644 index 000000000000..ee63bda40198 --- /dev/null +++ b/tests/pos-java16+/java-records/R5.java @@ -0,0 +1 @@ +public record R5(String s, int... values) {} diff --git a/tests/pos-macros/i13821-a/Macro.scala b/tests/pos-macros/i13821-a/Macro.scala new file mode 100644 index 000000000000..3583a44ffddc --- /dev/null +++ b/tests/pos-macros/i13821-a/Macro.scala @@ -0,0 +1,6 @@ +trait Conversions: + given conv(using DFBits.Candidate): Conversion[Int, DFVal] = ??? + +import scala.quoted.* +transparent inline def f: Short = ${ getWidthMacro } +private def getWidthMacro(using Quotes): Expr[Short] = '{ ??? } diff --git a/tests/pos-macros/i13821-a/Test.scala b/tests/pos-macros/i13821-a/Test.scala new file mode 100644 index 000000000000..86f5a6289a80 --- /dev/null +++ b/tests/pos-macros/i13821-a/Test.scala @@ -0,0 +1,12 @@ +type DFBits = Long +object DFBits: + def a: Unit = f // forces suspension of this compilation unit in typer + def b: DFVal = 2 // uses implicit conversion `DFVal.conv` + + trait Candidate + object Candidate: + given candidate: Candidate = ??? // completed in run 3 but created in run 2 +end DFBits + +trait DFVal +object DFVal extends Conversions diff --git a/tests/pos-macros/i13821-b/Macro.scala b/tests/pos-macros/i13821-b/Macro.scala new file mode 100644 index 000000000000..c1c9c5e17851 --- /dev/null +++ b/tests/pos-macros/i13821-b/Macro.scala @@ -0,0 +1,7 @@ +import outer._ +trait Conversions: + given conv(using DFBits.Candidate): Conversion[Int, DFVal] = ??? + +import scala.quoted.* +transparent inline def f: Short = ${ getWidthMacro } +private def getWidthMacro(using Quotes): Expr[Short] = '{ ??? } diff --git a/tests/pos-macros/i13821-b/Test.scala b/tests/pos-macros/i13821-b/Test.scala new file mode 100644 index 000000000000..5c9b8755a33c --- /dev/null +++ b/tests/pos-macros/i13821-b/Test.scala @@ -0,0 +1,14 @@ +type outer = Int +object outer: + type DFBits = Long + object DFBits: + def a: Unit = f // forces suspension of this compilation unit in typer + def b: DFVal = 2 // uses implicit conversion `DFVal.conv` + + trait Candidate + object Candidate: + given candidate: Candidate = ??? // completed in run 3 but created in run 2 + end DFBits + + trait DFVal + object DFVal extends Conversions diff --git a/tests/pos-macros/i19351/A.scala b/tests/pos-macros/i19351/A.scala new file mode 100644 index 000000000000..0b2c7acc3797 --- /dev/null +++ b/tests/pos-macros/i19351/A.scala @@ -0,0 +1,6 @@ +// object A: + val x: Int = 1 + inline def myMacro(): x.type = ${myMacroExpr} + def test = myMacro() + +@main def main() = () \ No newline at end of file diff --git a/tests/neg/i19351/B.scala b/tests/pos-macros/i19351/B.scala similarity index 84% rename from tests/neg/i19351/B.scala rename to tests/pos-macros/i19351/B.scala index 8c8c071f3607..a22b4d5c51b6 100644 --- a/tests/neg/i19351/B.scala +++ b/tests/pos-macros/i19351/B.scala @@ -1,3 +1,3 @@ import scala.quoted.* -//import A.* +// import A.* def myMacroExpr(using Quotes): Expr[x.type] = '{???} \ No newline at end of file diff --git a/tests/neg/i19351a/Macro.scala b/tests/pos-macros/i19351a/Macro.scala similarity index 100% rename from tests/neg/i19351a/Macro.scala rename to tests/pos-macros/i19351a/Macro.scala diff --git a/tests/neg/i19351a/Test.scala b/tests/pos-macros/i19351a/Test.scala similarity index 87% rename from tests/neg/i19351a/Test.scala rename to tests/pos-macros/i19351a/Test.scala index 84fb6ca4ae78..7f9e6dbc9786 100644 --- a/tests/neg/i19351a/Test.scala +++ b/tests/pos-macros/i19351a/Test.scala @@ -5,7 +5,7 @@ type Bool = [R] => (R, R) => R val True: Bool = [R] => (t: R, _: R) => t val False: Bool = [R] => (_: R, f: R) => f -inline def not(b: Bool): Bool = ${notMacro('b)} // error +inline def not(b: Bool): Bool = ${notMacro('b)} inline def show(b: Bool): String = ${showMacro('b)} //inline def not(b: Bool): Bool = ${foldMacro('b, 'False, 'True)} //inline def show(b: Bool): String = ${foldMacro('b, '{"TRUE"}, '{"FALSE"})} diff --git a/tests/pos-macros/i22585/Macro.scala b/tests/pos-macros/i22585/Macro.scala new file mode 100644 index 000000000000..0daf304ab3a7 --- /dev/null +++ b/tests/pos-macros/i22585/Macro.scala @@ -0,0 +1,22 @@ +import scala.quoted.* + +trait Hammer[I, O] { + def hammer(input: I): O +} + +object Hammer { + inline def makeProductHammerMacro[I, O](): Hammer[I, O] = + ${ makeProductHammerMacroImpl[I, O] } + + def makeProductHammerMacroImpl[I: Type, O: Type](using Quotes): Expr[Hammer[I, O]] = + '{ makeHammer[I, O]() } + + inline def makeHammer[S, O](): Hammer[S, O] = + new Hammer[S, O] { + lazy val (hammer: Hammer[?, Int], idx: Int) = ??? + + override def hammer(input: S): O = { + hammer.hammer(???.asInstanceOf).asInstanceOf[O] + } + } +} \ No newline at end of file diff --git a/tests/pos-macros/i22585/Main.scala b/tests/pos-macros/i22585/Main.scala new file mode 100644 index 000000000000..6b09d1fb719c --- /dev/null +++ b/tests/pos-macros/i22585/Main.scala @@ -0,0 +1,5 @@ +object HammerSpec { + case class A(x: Int) + case class B(x: Int) + Hammer.makeProductHammerMacro[A, B]() +} \ No newline at end of file diff --git a/tests/pos-macros/i23423/A_1.scala b/tests/pos-macros/i23423/A_1.scala new file mode 100644 index 000000000000..62455c74eed0 --- /dev/null +++ b/tests/pos-macros/i23423/A_1.scala @@ -0,0 +1,16 @@ +package pkg + +import scala.quoted.* + +trait HasElem { + type Elem + type Alias = Elem +} + +object Macro: + inline def foo: Unit = ${fooImpl} + def fooImpl(using Quotes): Expr[Unit] = + '{ + val lll: (he: HasElem) => he.Alias = + (hx: HasElem) => ??? + } diff --git a/tests/pos-macros/i23423/B_2.scala b/tests/pos-macros/i23423/B_2.scala new file mode 100644 index 000000000000..144eaf4ce1da --- /dev/null +++ b/tests/pos-macros/i23423/B_2.scala @@ -0,0 +1,6 @@ +object Test: + def test: Unit = pkg.Macro.foo + // used to be error: + // Found: (hx: pkg.HasElem) => hx.Elem + // Required: (he: pkg.HasElem) => he.Elem + diff --git a/tests/pos-macros/i23589/Macro_1.scala b/tests/pos-macros/i23589/Macro_1.scala new file mode 100644 index 000000000000..19df8f6c68f6 --- /dev/null +++ b/tests/pos-macros/i23589/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.quoted.* +import scala.language.experimental.quotedPatternsWithPolymorphicFunctions + +inline def testMacro(inline body: Any) = ${test('body)} +def test(outsideBody: Expr[Any])(using Quotes): Expr[Unit] = + val insideBody = '{[B] => (a : B, b : B) => (a, b)} + outsideBody match + case '{ [A] => (x : A, y : A) => $b[A](x, y): (A, A) } => () + insideBody match + case '{ [A] => (x : A, y : A) => $b[A](x, y): (A, A) } => () + '{()} diff --git a/tests/pos-macros/i23589/Main_2.scala b/tests/pos-macros/i23589/Main_2.scala new file mode 100644 index 000000000000..64dd8e6698a3 --- /dev/null +++ b/tests/pos-macros/i23589/Main_2.scala @@ -0,0 +1,3 @@ +//> using options -experimental +@main def main() = + testMacro([B] => (a : B, b : B) => (a, b)) diff --git a/tests/pos/22359a.scala b/tests/pos/22359a.scala deleted file mode 100644 index a3b9ef63257a..000000000000 --- a/tests/pos/22359a.scala +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index f6b7cbb462c1..000000000000 --- a/tests/pos/22359b.scala +++ /dev/null @@ -1,17 +0,0 @@ -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-18064.scala b/tests/pos/annot-18064.scala index b6a67ea9ebe7..465f627ff861 100644 --- a/tests/pos/annot-18064.scala +++ b/tests/pos/annot-18064.scala @@ -1,4 +1,4 @@ -//> using options "-Xprint:typer" +//> using options "-Vprint:typer" class myAnnot[T]() extends annotation.Annotation diff --git a/tests/pos/applied_constructor_types.scala b/tests/pos/applied_constructor_types.scala new file mode 100644 index 000000000000..8ae7f663dd5e --- /dev/null +++ b/tests/pos/applied_constructor_types.scala @@ -0,0 +1,55 @@ +import scala.language.experimental.modularity + +class Box(tracked val v: Any) +class C(tracked val x: Int) +class NC(tracked val c: C) +class NNC(tracked val c: NC) +class F[A](tracked val a: Int) +class G[A](tracked val a: A) +class NF[A](tracked val f: F[A]) + +class Person(val name: String, tracked val age: Int) +class PersonPrime(val name: String)(tracked val age: Int) +class PersonBis(tracked val name: String)(val age: Int) + +class Generic[A](val a: A) + +object O: + val m: Int = 27 + + class InnerClass(tracked val x: Int) + +object Test extends App { + val c: C(42) = C(42) + val nc: NC(C(42)) = NC(C(42)) + val nc1: NC(c) = NC(c) + val nnc: NNC(NC(C(42))) = NNC(NC(C(42))) + val f: F[Int](42) = F[Int](42) + val f2: F[Int](42) = F(42) + val f3: F(42) = F(42) + val g: G(42) = G(42) + + val n: Int = 27 + val c2: C(n) = C(n) + val c3: C(O.m) = C(O.m) + + val box: Box(O.InnerClass(42)) = Box(O.InnerClass(42)) + val box2: Box(O.InnerClass(n)) = Box(O.InnerClass(n)) + val box3: Box(O.InnerClass(O.m)) = Box(O.InnerClass(O.m)) + + val person: Person("Kasia", 27) = Person("Kasia", 27) // warn + val person1: Person("Kasia", n) = Person("Kasia", n) // warn + val person2: Person("Kasia", O.m) = Person("Kasia", O.m) // warn + + val personPrime: PersonPrime("Kasia")(27) = PersonPrime("Kasia")(27) // warn + val personPrime1: PersonPrime("Kasia")(n) = PersonPrime("Kasia")(n) // warn + val personPrime2: PersonPrime("Kasia")(O.m) = PersonPrime("Kasia")(O.m) // warn + + val personBis: PersonBis("Kasia")(27) = PersonBis("Kasia")(27) // warn + val personBis1: PersonBis("Kasia")(n) = PersonBis("Kasia")(n) // warn + val personBis2: PersonBis("Kasia")(O.m) = PersonBis("Kasia")(O.m) // warn + + val generic1: Generic(compiletime.erasedValue[Int]) = Generic(42) // warn + val generic2: Generic(??? : Int) = Generic(42) // warn + val generic3: Generic(43) = Generic(42) // warn +} diff --git a/tests/pos/caps-universal.scala b/tests/pos/caps-universal.scala index 3451866ed8f3..fc9942c2a99a 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.cap) = foo +val bar: (Int -> Int) @retains[caps.cap.type] = foo diff --git a/tests/pos/cc-use-alternatives.scala b/tests/pos/cc-use-alternatives.scala new file mode 100644 index 000000000000..ddfcc3b62f8b --- /dev/null +++ b/tests/pos/cc-use-alternatives.scala @@ -0,0 +1,24 @@ +import language.experimental.captureChecking +// no separation checking +import caps.{cap, use} + +def foo1(@use xs: List[() => Unit]): Unit = + var x: () ->{xs*} Unit = xs.head + var ys = xs + while ys.nonEmpty do + ys = ys.tail + x = ys.head + +def foo2(@use xs: List[() => Unit]): Unit = + var x: () => Unit = xs.head // note: this would fail separation checking + var ys = xs + while ys.nonEmpty do + ys = ys.tail + x = ys.head + +def foo3[@use C^](xs: List[() ->{C} Unit]): Unit = + var x: () ->{C} Unit = xs.head + var ys = xs + while ys.nonEmpty do + ys = ys.tail + x = ys.head diff --git a/tests/neg/erased-24.scala b/tests/pos/erased-24.scala similarity index 77% rename from tests/neg/erased-24.scala rename to tests/pos/erased-24.scala index bf2f1d21435e..410a1900a1c1 100644 --- a/tests/neg/erased-24.scala +++ b/tests/pos/erased-24.scala @@ -12,8 +12,8 @@ object Test { null.asInstanceOf[foo.X] // ok } - def fun2(erased foo: Foo)(erased bar: foo.B): bar.X = { // error - null.asInstanceOf[bar.X] // error + def fun2(erased foo: Foo)(erased bar: foo.B): bar.X = { // was error + null.asInstanceOf[bar.X] // was error } } diff --git a/tests/pos/erased-asInstanceOf.scala b/tests/pos/erased-asInstanceOf.scala index 692ff3a16b05..7029c298452c 100644 --- a/tests/pos/erased-asInstanceOf.scala +++ b/tests/pos/erased-asInstanceOf.scala @@ -11,7 +11,7 @@ object Test { val ds: Dataset = ??? - lazy val collD = new Column + val collD = new Column ds.select(collD) diff --git a/tests/pos/erased-class-as-args.scala b/tests/pos/erased-class-as-args.scala index 128cd2b818e4..c223e583aed5 100644 --- a/tests/pos/erased-class-as-args.scala +++ b/tests/pos/erased-class-as-args.scala @@ -1,8 +1,8 @@ -//> using options -language:experimental.erasedDefinitions +import language.experimental.erasedDefinitions -erased class A +class A extends compiletime.Erased -erased class B(val x: Int) extends A +class B(val x: Int) extends A type T = (x: A, y: Int) => Int diff --git a/tests/pos/erased-class-separate/A_1.scala b/tests/pos/erased-class-separate/A_1.scala index 5c874ce6d89b..778f271463da 100644 --- a/tests/pos/erased-class-separate/A_1.scala +++ b/tests/pos/erased-class-separate/A_1.scala @@ -1,3 +1,3 @@ import language.experimental.erasedDefinitions -erased class A +class A extends compiletime.Erased diff --git a/tests/pos/erased-conforms.scala b/tests/pos/erased-conforms.scala index 426490d5a53a..d26ca1a80e26 100644 --- a/tests/pos/erased-conforms.scala +++ b/tests/pos/erased-conforms.scala @@ -1,11 +1,11 @@ import language.experimental.erasedDefinitions -erased class ErasedTerm +class ErasedTerm extends compiletime.Erased -erased class <::<[-From, +To] extends ErasedTerm +class <::<[-From, +To] extends ErasedTerm -erased class =::=[From, To] extends (From <::< To) +class =::=[From, To] extends (From <::< To) -erased given [X] => (X =::= X) = scala.compiletime.erasedValue +inline given [X] => (X =::= X) = new =::= extension [From](x: From) inline def cast[To](using From <::< To): To = x.asInstanceOf[To] // Safe cast because we know `From <:< To` diff --git a/tests/pos/erased-export.scala b/tests/pos/erased-export.scala new file mode 100644 index 000000000000..c11e3cc57d8a --- /dev/null +++ b/tests/pos/erased-export.scala @@ -0,0 +1,9 @@ +import language.experimental.erasedDefinitions + +class C(x: Int): + erased val e = x + +class D: + val c = C(22) + export c.* + erased val x = e diff --git a/tests/neg/erased-pathdep-1.scala b/tests/pos/erased-pathdep-1.scala similarity index 67% rename from tests/neg/erased-pathdep-1.scala rename to tests/pos/erased-pathdep-1.scala index 422ceb5e37fe..e696c48df328 100644 --- a/tests/neg/erased-pathdep-1.scala +++ b/tests/pos/erased-pathdep-1.scala @@ -1,16 +1,14 @@ //> using options -language:experimental.erasedDefinitions -// Could become a neg test if we had totality checking for erased arguments - object Test { fun1(new Bar) val _ = fun2(new Bar) val _ = fun3(new Bar) - def fun1[F >: Bar <: Foo](erased f: F): f.X = null.asInstanceOf[f.X] // error // error - def fun2[F >: Bar <: Foo](erased f: F)(erased bar: f.B): f.B = null.asInstanceOf[f.B] // error // error // error - def fun3[F >: Bar <: Foo](erased f: F)(erased b: f.B): b.X = null.asInstanceOf[b.X] // error // error // error + def fun1[F >: Bar <: Foo](erased f: F): f.X = null.asInstanceOf[f.X] + def fun2[F >: Bar <: Foo](erased f: F)(erased bar: f.B): f.B = null.asInstanceOf[f.B] + def fun3[F >: Bar <: Foo](erased f: F)(erased b: f.B): b.X = null.asInstanceOf[b.X] } class Foo { diff --git a/tests/neg/erased-pathdep-2.scala b/tests/pos/erased-pathdep-2.scala similarity index 81% rename from tests/neg/erased-pathdep-2.scala rename to tests/pos/erased-pathdep-2.scala index 0b50acbf3b30..8c9f7b414a98 100644 --- a/tests/neg/erased-pathdep-2.scala +++ b/tests/pos/erased-pathdep-2.scala @@ -7,8 +7,8 @@ object Test { type F >: Bar <: Foo class A(erased val f: F) { - type F1 <: f.X // error - type F2[Z <: f.X] // error + type F1 <: f.X // was error + type F2[Z <: f.X] // was error } } diff --git a/tests/pos/erased-pure.scala b/tests/pos/erased-pure.scala new file mode 100644 index 000000000000..e62563669e66 --- /dev/null +++ b/tests/pos/erased-pure.scala @@ -0,0 +1,26 @@ +import language.experimental.erasedDefinitions +import caps.unsafe.unsafeErasedValue + +inline def id[T](x: T) = x + +class C() + +def foo[T](erased x: T): Unit = () + +class Pair[A, B](x: A, y: B) + +case class Pair2[A, B](x: A, y: B) + +def Test = + foo(C()) + foo(id(C())) + foo(Pair(C(), C())) + foo(Pair(C(), 22)) + foo(Pair(C(), "hello" + "world")) + foo(id(Pair(id(C()), id("hello" + "world")))) + + //erased val x1 = Pair(unsafeErasedValue[Int], unsafeErasedValue[String]) + //erased val x2 = Pair2(unsafeErasedValue[Int], unsafeErasedValue[String]) + erased val x3 = Tuple2(unsafeErasedValue[Int], unsafeErasedValue[String]) + + diff --git a/tests/neg/erased-singleton.scala b/tests/pos/erased-singleton.scala similarity index 67% rename from tests/neg/erased-singleton.scala rename to tests/pos/erased-singleton.scala index 5ffa78e24b07..f7ad5165ec0a 100644 --- a/tests/neg/erased-singleton.scala +++ b/tests/pos/erased-singleton.scala @@ -5,5 +5,5 @@ trait Sys trait Obj { erased val s: Sys - type S = s.type // error: non final + type S = s.type // now OK, was error: non final } diff --git a/tests/pos/expeimental-flag-with-lang-feature.scala b/tests/pos/expeimental-flag-with-lang-feature.scala deleted file mode 100644 index 96069c332e02..000000000000 --- a/tests/pos/expeimental-flag-with-lang-feature.scala +++ /dev/null @@ -1,10 +0,0 @@ -//> using options -experimental - -import scala.language.experimental.erasedDefinitions -import scala.language.experimental.namedTypeArguments - -erased def erasedFun(erased x: Int): Int = x - -def namedTypeArgumentsFun[T, U]: Int = - namedTypeArgumentsFun[T = Int, U = Int] - namedTypeArgumentsFun[U = Int, T = Int] diff --git a/tests/pos/experimental-erased-2.scala b/tests/pos/experimental-erased-2.scala index f3b524e18463..33bf4f4abf2b 100644 --- a/tests/pos/experimental-erased-2.scala +++ b/tests/pos/experimental-erased-2.scala @@ -3,6 +3,6 @@ import annotation.experimental @experimental object Test: - erased class CanThrow[-E <: Exception] + class CanThrow[-E <: Exception] extends compiletime.Erased def other = 1 diff --git a/tests/pos/experimental-erased.scala b/tests/pos/experimental-erased.scala index 156ad639f42d..1031cf9423c2 100644 --- a/tests/pos/experimental-erased.scala +++ b/tests/pos/experimental-erased.scala @@ -2,7 +2,7 @@ import language.experimental.erasedDefinitions import annotation.experimental @experimental -erased class CanThrow[-E <: Exception](val i: Int = 0) +class CanThrow[-E <: Exception](val i: Int = 0) extends compiletime.Erased @experimental object Foo diff --git a/tests/pos/experimental-imports-top.scala b/tests/pos/experimental-imports-top.scala index 9ba2b5cd2c99..595caac66fe7 100644 --- a/tests/pos/experimental-imports-top.scala +++ b/tests/pos/experimental-imports-top.scala @@ -4,4 +4,4 @@ import language.experimental.erasedDefinitions import annotation.experimental @experimental -erased def f = 1 +erased val f = 1 diff --git a/tests/pos/experimentalErased.scala b/tests/pos/experimentalErased.scala index 358c134c714a..6045f96164a5 100644 --- a/tests/pos/experimentalErased.scala +++ b/tests/pos/experimentalErased.scala @@ -2,14 +2,9 @@ import language.experimental.erasedDefinitions import annotation.experimental @experimental -erased class Foo +class Foo extends compiletime.Erased -erased class Bar - -@experimental -erased def foo = 2 - -erased def bar = 2 +class Bar extends compiletime.Erased @experimental erased val foo2 = 2 diff --git a/tests/pos/gadt-alt-doc1.scala b/tests/pos/gadt-alt-doc1.scala new file mode 100644 index 000000000000..f6457b4660e0 --- /dev/null +++ b/tests/pos/gadt-alt-doc1.scala @@ -0,0 +1,8 @@ +trait Document[Doc <: Document[Doc]] +sealed trait Conversion[Doc, V] + +case class C[Doc <: Document[Doc]]() extends Conversion[Doc, Doc] + +def Test[Doc <: Document[Doc], V](conversion: Conversion[Doc, V]) = + conversion match + case C() | C() => ??? // error diff --git a/tests/pos/hylolib-cb/BitArray.scala b/tests/pos/hylolib-cb/BitArray.scala index 0c8f98fb1ba4..2de41facf8e8 100644 --- a/tests/pos/hylolib-cb/BitArray.scala +++ b/tests/pos/hylolib-cb/BitArray.scala @@ -22,86 +22,79 @@ final class BitArray private ( /** Reserves enough storage to store `n` elements in `this`. */ def reserveCapacity(n: Int, assumeUniqueness: Boolean = false): BitArray = - if (n == 0) { + if n == 0 then this - } else { + else val k = 1 + ((n - 1) >> 5) - if (assumeUniqueness) { + if assumeUniqueness then _bits = _bits.reserveCapacity(k, assumeUniqueness) this - } else { + else new BitArray(_bits.reserveCapacity(k), _count) - } - } /** Adds a new element at the end of the array. */ def append(bit: Boolean, assumeUniqueness: Boolean = false): BitArray = val result = if assumeUniqueness && (count < capacity) then this else copy(count + 1) val p = BitArray.Position(count) - if (p.bucket >= _bits.count) { + if p.bucket >= _bits.count then result._bits = _bits.append(if bit then 1 else 0) - } else { + else result.setValue(bit, p) - } result._count += 1 result /** Removes and returns the last element, or returns `None` if the array is empty. */ def popLast(assumeUniqueness: Boolean = false): (BitArray, Option[Boolean]) = - if (isEmpty) { + if isEmpty then (this, None) - } else { + else val result = if assumeUniqueness then this else copy() val bit = result.at(BitArray.Position(count)) result._count -= 1 (result, Some(bit)) - } /** Removes all elements in the array, keeping allocated storage iff `keepStorage` is true. */ def removeAll( keepStorage: Boolean = false, assumeUniqueness: Boolean = false ): BitArray = - if (isEmpty) { + if isEmpty then this - } else if (keepStorage) { + else if keepStorage then val result = if assumeUniqueness then this else copy() result._bits.removeAll(keepStorage, assumeUniqueness = true) result._count = 0 result - } else { + else BitArray() - } /** Returns `true` iff all elements in `this` are `false`. */ def allFalse: Boolean = - if (isEmpty) { + if isEmpty then true - } else { + else val k = (count - 1) >> 5 def loop(i: Int): Boolean = - if (i == k) { + if i == k then val m = (1 << (count & 31)) - 1 (_bits.at(k) & m) == 0 - } else if (_bits.at(i) != 0) { + else if _bits.at(i) != 0 then false - } else { + else loop(i + 1) - } loop(0) - } /** Returns `true` iff all elements in `this` are `true`. */ def allTrue: Boolean = - if (isEmpty) { + if isEmpty then { true } else { val k = (count - 1) >> 5 def loop(i: Int): Boolean = - if (i == k) { + if i == k then { val m = (1 << (count & 31)) - 1 (_bits.at(k) & m) == m - } else if (_bits.at(i) != ~0) { + } else if _bits.at(i) != ~0 then { false } else { loop(i + 1) @@ -136,14 +129,14 @@ final class BitArray private ( assumeUniqueness: Boolean = false ): BitArray = require(this.count == other.count) - if (isEmpty) { + if isEmpty then { this } else { val result = if assumeUniqueness then this else copy() var u = assumeUniqueness val k = (count - 1) >> 5 - for (i <- 0 until k) { + for i <- 0 until k do { result._bits = result._bits.modifyAt( i, (n) => operation(n, other._bits.at(n)), assumeUniqueness = u @@ -184,7 +177,7 @@ final class BitArray private ( * O(1). */ def positionAfter(p: BitArray.Position): BitArray.Position = - if (p.offsetInBucket == 63) { + if p.offsetInBucket == 63 then { BitArray.Position(p.bucket + 1, 0) } else { BitArray.Position(p.bucket, p.offsetInBucket + 1) @@ -244,7 +237,7 @@ final class BitArray private ( /** Returns an independent copy of `this`. */ def copy(minimumCapacity: Int = 0): BitArray = - if (minimumCapacity > capacity) { + if minimumCapacity > capacity then { // If the requested capacity on the copy is greater than what we have, `reserveCapacity` will // create an independent value. reserveCapacity(minimumCapacity) @@ -313,7 +306,7 @@ object BitArray { /** Creates an array with the given `bits`. */ def apply[T](bits: Boolean*): BitArray = var result = new BitArray(HyArray[Int](), 0) - for (b <- bits) result = result.append(b, assumeUniqueness = true) + for b <- bits do result = result.append(b, assumeUniqueness = true) result } diff --git a/tests/pos/hylolib-cb/Collection.scala b/tests/pos/hylolib-cb/Collection.scala index 2fc04f02b9ac..f5d65e1f13c7 100644 --- a/tests/pos/hylolib-cb/Collection.scala +++ b/tests/pos/hylolib-cb/Collection.scala @@ -69,15 +69,15 @@ trait Collection[Self] { */ def isBefore(i: Position, j: Position): Boolean = val e = self.endPosition - if (i.eq(e)) { + if i.eq(e) then { false - } else if (j.eq(e)) { + } else if j.eq(e) then { true } else { def _isBefore(n: Position): Boolean = - if (n.eq(j)) { + if n.eq(j) then { true - } else if (n.eq(e)) { + } else if n.eq(e) then { false } else { _isBefore(self.positionAfter(n)) @@ -98,7 +98,7 @@ extension [Self: Collection as s](self: Self) { * O(1) */ def headAndTail: Option[(s.Element, Slice[Self])] = - if (self.isEmpty) { + if self.isEmpty then { None } else { val p = self.startPosition @@ -115,7 +115,7 @@ extension [Self: Collection as s](self: Self) { def reduce[T](partialResult: T, combine: (T, s.Element) => T): T = val e = self.endPosition def loop(p: s.Position, r: T): T = - if (p.eq(e)) { + if p.eq(e) then { r } else { loop(self.positionAfter(p), combine(r, self.at(p))) @@ -134,9 +134,9 @@ extension [Self: Collection as s](self: Self) { def forEach(action: (s.Element) => Boolean): Boolean = val e = self.endPosition def loop(p: s.Position): Boolean = - if (p.eq(e)) { + if p.eq(e) then { true - } else if (!action(self.at(p))) { + } else if !action(self.at(p)) then { false } else { loop(self.positionAfter(p)) @@ -190,9 +190,9 @@ extension [Self: Collection as s](self: Self) { def firstPositionWhere(predicate: (s.Element) => Boolean): Option[s.Position] = val e = self.endPosition def loop(p: s.Position): Option[s.Position] = - if (p.eq(e)) { + if p.eq(e) then { None - } else if (predicate(self.at(p))) { + } else if predicate(self.at(p)) then { Some(p) } else { loop(self.positionAfter(p)) @@ -238,12 +238,12 @@ extension [Self: Collection as s](self: Self) { * O(n) where n is the number of elements in `self`. */ def leastElement(isOrderedBefore: (s.Element, s.Element) => Boolean): Option[s.Element] = - if (self.isEmpty) { + if self.isEmpty then { None } else { val e = self.endPosition def _least(p: s.Position, least: s.Element): s.Element = - if (p.eq(e)) { + if p.eq(e) then { least } else { val x = self.at(p) @@ -264,11 +264,11 @@ extension [Self: Collection as s](self: Self)(using /** Returns `true` if `self` contains the same elements as `other`, in the same order. */ def elementsEqual[T](using o: Collection[T] { type Element = s.Element })(other: T): Boolean = def loop(i: s.Position, j: o.Position): Boolean = - if (i `eq` self.endPosition) { + if i `eq` self.endPosition then { j `eq` other.endPosition - } else if (j `eq` other.endPosition) { + } else if j `eq` other.endPosition then { false - } else if (self.at(i) `neq` other.at(j)) { + } else if self.at(i) `neq` other.at(j) then { false } else { loop(self.positionAfter(i), other.positionAfter(j)) diff --git a/tests/pos/hylolib-cb/HyArray.scala b/tests/pos/hylolib-cb/HyArray.scala index e4ccab000448..93dad7751adf 100644 --- a/tests/pos/hylolib-cb/HyArray.scala +++ b/tests/pos/hylolib-cb/HyArray.scala @@ -27,21 +27,21 @@ final class HyArray[Element: Value as elementIsCValue]( /** Reserves enough storage to store `n` elements in `this`. */ def reserveCapacity(n: Int, assumeUniqueness: Boolean = false): HyArray[Element] = - if (n <= capacity) { + if n <= capacity then { this } else { var newCapacity = max(1, capacity) - while (newCapacity < n) { newCapacity = newCapacity << 1 } + while newCapacity < n do { newCapacity = newCapacity << 1 } val newStorage = new scala.Array[AnyRef | Null](newCapacity) val s = _storage.asInstanceOf[scala.Array[AnyRef | Null]] var i = 0 - while (i < count) { + while i < count do { newStorage(i) = _storage(i).asInstanceOf[Element].copy().asInstanceOf[AnyRef] i += 1 } - if (assumeUniqueness) { + if assumeUniqueness then { _storage = newStorage this } else { @@ -69,7 +69,7 @@ final class HyArray[Element: Value as elementIsCValue]( /** Removes and returns the last element, or returns `None` if the array is empty. */ def popLast(assumeUniqueness: Boolean = false): (HyArray[Element], Option[Element]) = - if (isEmpty) { + if isEmpty then { (this, None) } else { val result = if assumeUniqueness then this else copy() @@ -82,9 +82,9 @@ final class HyArray[Element: Value as elementIsCValue]( keepStorage: Boolean = false, assumeUniqueness: Boolean = false ): HyArray[Element] = - if (isEmpty) { + if isEmpty then { this - } else if (keepStorage) { + } else if keepStorage then { val result = if assumeUniqueness then this else copy() Arrays.fill(result._storage, null) result._count = 0 @@ -123,8 +123,8 @@ final class HyArray[Element: Value as elementIsCValue]( override def toString: String = var s = "[" var i = 0 - while (i < count) { - if (i > 0) { s += ", " } + while i < count do { + if i > 0 then { s += ", " } s += s"${at(i)}" i += 1 } @@ -134,14 +134,14 @@ final class HyArray[Element: Value as elementIsCValue]( * allocating new storage. */ def copy(minimumCapacity: Int = 0): HyArray[Element] = - if (minimumCapacity > capacity) { + if minimumCapacity > capacity then { // If the requested capacity on the copy is greater than what we have, `reserveCapacity` will // create an independent value. reserveCapacity(minimumCapacity) } else { val clone = HyArray[Element]().reserveCapacity(max(minimumCapacity, count)) var i = 0 - while (i < count) { + while i < count do { clone._storage(i) = _storage(i).asInstanceOf[Element].copy().asInstanceOf[AnyRef] i += 1 } @@ -156,7 +156,7 @@ object HyArray { /** Creates an array with the given `elements`. */ def apply[T: Value](elements: T*): HyArray[T] = var a = new HyArray[T](null, 0) - for (e <- elements) a = a.append(e, assumeUniqueness = true) + for e <- elements do a = a.append(e, assumeUniqueness = true) a } diff --git a/tests/pos/hylolib-deferred-given/AnyCollection.scala b/tests/pos/hylolib-deferred-given/AnyCollection.scala index e2a946fca484..e13232c64954 100644 --- a/tests/pos/hylolib-deferred-given/AnyCollection.scala +++ b/tests/pos/hylolib-deferred-given/AnyCollection.scala @@ -48,7 +48,7 @@ given anyCollectionIsCollection: [T] => (tIsValue: Value[T]) => Collection[AnyCo //given elementIsValue: Value[Element] = tIsValue type Position = AnyValue - given positionIsValue: Value[Position] = anyValueIsValue + override given positionIsValue: Value[Position] = anyValueIsValue extension (self: AnyCollection[T]) { diff --git a/tests/pos/hylolib-deferred-given/BitArray.scala b/tests/pos/hylolib-deferred-given/BitArray.scala index d653f5e4d630..885f9b9ba60a 100644 --- a/tests/pos/hylolib-deferred-given/BitArray.scala +++ b/tests/pos/hylolib-deferred-given/BitArray.scala @@ -22,11 +22,11 @@ final class BitArray private ( /** Reserves enough storage to store `n` elements in `this`. */ def reserveCapacity(n: Int, assumeUniqueness: Boolean = false): BitArray = - if (n == 0) { + if n == 0 then { this } else { val k = 1 + ((n - 1) >> 5) - if (assumeUniqueness) { + if assumeUniqueness then { _bits = _bits.reserveCapacity(k, assumeUniqueness) this } else { @@ -38,7 +38,7 @@ final class BitArray private ( def append(bit: Boolean, assumeUniqueness: Boolean = false): BitArray = val result = if assumeUniqueness && (count < capacity) then this else copy(count + 1) val p = BitArray.Position(count) - if (p.bucket >= _bits.count) { + if p.bucket >= _bits.count then { result._bits = _bits.append(if bit then 1 else 0) } else { result.setValue(bit, p) @@ -48,7 +48,7 @@ final class BitArray private ( /** Removes and returns the last element, or returns `None` if the array is empty. */ def popLast(assumeUniqueness: Boolean = false): (BitArray, Option[Boolean]) = - if (isEmpty) { + if isEmpty then { (this, None) } else { val result = if assumeUniqueness then this else copy() @@ -62,9 +62,9 @@ final class BitArray private ( keepStorage: Boolean = false, assumeUniqueness: Boolean = false ): BitArray = - if (isEmpty) { + if isEmpty then { this - } else if (keepStorage) { + } else if keepStorage then { val result = if assumeUniqueness then this else copy() result._bits.removeAll(keepStorage, assumeUniqueness = true) result._count = 0 @@ -75,15 +75,15 @@ final class BitArray private ( /** Returns `true` iff all elements in `this` are `false`. */ def allFalse: Boolean = - if (isEmpty) { + if isEmpty then { true } else { val k = (count - 1) >> 5 def loop(i: Int): Boolean = - if (i == k) { + if i == k then { val m = (1 << (count & 31)) - 1 (_bits.at(k) & m) == 0 - } else if (_bits.at(i) != 0) { + } else if _bits.at(i) != 0 then { false } else { loop(i + 1) @@ -93,15 +93,15 @@ final class BitArray private ( /** Returns `true` iff all elements in `this` are `true`. */ def allTrue: Boolean = - if (isEmpty) { + if isEmpty then { true } else { val k = (count - 1) >> 5 def loop(i: Int): Boolean = - if (i == k) { + if i == k then { val m = (1 << (count & 31)) - 1 (_bits.at(k) & m) == m - } else if (_bits.at(i) != ~0) { + } else if _bits.at(i) != ~0 then { false } else { loop(i + 1) @@ -136,14 +136,14 @@ final class BitArray private ( assumeUniqueness: Boolean = false ): BitArray = require(this.count == other.count) - if (isEmpty) { + if isEmpty then { this } else { val result = if assumeUniqueness then this else copy() var u = assumeUniqueness val k = (count - 1) >> 5 - for (i <- 0 until k) { + for i <- 0 until k do { result._bits = result._bits.modifyAt( i, (n) => operation(n, other._bits.at(n)), assumeUniqueness = u @@ -184,7 +184,7 @@ final class BitArray private ( * O(1). */ def positionAfter(p: BitArray.Position): BitArray.Position = - if (p.offsetInBucket == 63) { + if p.offsetInBucket == 63 then { BitArray.Position(p.bucket + 1, 0) } else { BitArray.Position(p.bucket, p.offsetInBucket + 1) @@ -244,7 +244,7 @@ final class BitArray private ( /** Returns an independent copy of `this`. */ def copy(minimumCapacity: Int = 0): BitArray = - if (minimumCapacity > capacity) { + if minimumCapacity > capacity then { // If the requested capacity on the copy is greater than what we have, `reserveCapacity` will // create an independent value. reserveCapacity(minimumCapacity) @@ -313,7 +313,7 @@ object BitArray { /** Creates an array with the given `bits`. */ def apply[T](bits: Boolean*): BitArray = var result = new BitArray(HyArray[Int](), 0) - for (b <- bits) result = result.append(b, assumeUniqueness = true) + for b <- bits do result = result.append(b, assumeUniqueness = true) result } @@ -341,7 +341,7 @@ given bitArrayIsCollection: Collection[BitArray] { //given elementIsValue: Value[Boolean] = booleanIsValue type Position = BitArray.Position - given positionIsValue: Value[BitArray.Position] = bitArrayPositionIsValue + override given positionIsValue: Value[BitArray.Position] = bitArrayPositionIsValue extension (self: BitArray) { diff --git a/tests/pos/hylolib-deferred-given/Collection.scala b/tests/pos/hylolib-deferred-given/Collection.scala index 6b5e7a762dc8..d0577023a619 100644 --- a/tests/pos/hylolib-deferred-given/Collection.scala +++ b/tests/pos/hylolib-deferred-given/Collection.scala @@ -10,7 +10,7 @@ trait Collection[Self] { /** The type of a position in the collection. */ type Position - given positionIsValue: Value[Position] + given positionIsValue: Value[Position] = compiletime.deferred extension (self: Self) { @@ -71,15 +71,15 @@ trait Collection[Self] { */ def isBefore(i: Position, j: Position): Boolean = val e = self.endPosition - if (i.eq(e)) { + if i.eq(e) then { false - } else if (j.eq(e)) { + } else if j.eq(e) then { true } else { def _isBefore(n: Position): Boolean = - if (n.eq(j)) { + if n.eq(j) then { true - } else if (n.eq(e)) { + } else if n.eq(e) then { false } else { _isBefore(self.positionAfter(n)) @@ -100,7 +100,7 @@ extension [Self](self: Self)(using s: Collection[Self]) { * O(1) */ def headAndTail: Option[(s.Element, Slice[Self])] = - if (self.isEmpty) { + if self.isEmpty then { None } else { val p = self.startPosition @@ -117,7 +117,7 @@ extension [Self](self: Self)(using s: Collection[Self]) { def reduce[T](partialResult: T, combine: (T, s.Element) => T): T = val e = self.endPosition def loop(p: s.Position, r: T): T = - if (p.eq(e)) { + if p.eq(e) then { r } else { loop(self.positionAfter(p), combine(r, self.at(p))) @@ -136,9 +136,9 @@ extension [Self](self: Self)(using s: Collection[Self]) { def forEach(action: (s.Element) => Boolean): Boolean = val e = self.endPosition def loop(p: s.Position): Boolean = - if (p.eq(e)) { + if p.eq(e) then { true - } else if (!action(self.at(p))) { + } else if !action(self.at(p)) then { false } else { loop(self.positionAfter(p)) @@ -192,9 +192,9 @@ extension [Self](self: Self)(using s: Collection[Self]) { def firstPositionWhere(predicate: (s.Element) => Boolean): Option[s.Position] = val e = self.endPosition def loop(p: s.Position): Option[s.Position] = - if (p.eq(e)) { + if p.eq(e) then { None - } else if (predicate(self.at(p))) { + } else if predicate(self.at(p)) then { Some(p) } else { loop(self.positionAfter(p)) @@ -240,12 +240,12 @@ extension [Self](self: Self)(using s: Collection[Self]) { * O(n) where n is the number of elements in `self`. */ def leastElement(isOrderedBefore: (s.Element, s.Element) => Boolean): Option[s.Element] = - if (self.isEmpty) { + if self.isEmpty then { None } else { val e = self.endPosition def _least(p: s.Position, least: s.Element): s.Element = - if (p.eq(e)) { + if p.eq(e) then { least } else { val x = self.at(p) @@ -267,11 +267,11 @@ extension [Self](self: Self)(using /** Returns `true` if `self` contains the same elements as `other`, in the same order. */ def elementsEqual[T](using o: Collection[T] { type Element = s.Element })(other: T): Boolean = def loop(i: s.Position, j: o.Position): Boolean = - if (i `eq` self.endPosition) { + if i `eq` self.endPosition then { j `eq` other.endPosition - } else if (j `eq` other.endPosition) { + } else if j `eq` other.endPosition then { false - } else if (self.at(i) `neq` other.at(j)) { + } else if self.at(i) `neq` other.at(j) then { false } else { loop(self.positionAfter(i), other.positionAfter(j)) diff --git a/tests/pos/hylolib-deferred-given/HyArray.scala b/tests/pos/hylolib-deferred-given/HyArray.scala index e82ce06d920b..e93c826a11eb 100644 --- a/tests/pos/hylolib-deferred-given/HyArray.scala +++ b/tests/pos/hylolib-deferred-given/HyArray.scala @@ -28,21 +28,21 @@ final class HyArray[Element] private (using /** Reserves enough storage to store `n` elements in `this`. */ def reserveCapacity(n: Int, assumeUniqueness: Boolean = false): HyArray[Element] = - if (n <= capacity) { + if n <= capacity then { this } else { var newCapacity = max(1, capacity) - while (newCapacity < n) { newCapacity = newCapacity << 1 } + while newCapacity < n do { newCapacity = newCapacity << 1 } val newStorage = new scala.Array[AnyRef | Null](newCapacity) val s = _storage.asInstanceOf[scala.Array[AnyRef | Null]] var i = 0 - while (i < count) { + while i < count do { newStorage(i) = _storage(i).asInstanceOf[Element].copy().asInstanceOf[AnyRef] i += 1 } - if (assumeUniqueness) { + if assumeUniqueness then { _storage = newStorage this } else { @@ -70,7 +70,7 @@ final class HyArray[Element] private (using /** Removes and returns the last element, or returns `None` if the array is empty. */ def popLast(assumeUniqueness: Boolean = false): (HyArray[Element], Option[Element]) = - if (isEmpty) { + if isEmpty then { (this, None) } else { val result = if assumeUniqueness then this else copy() @@ -83,9 +83,9 @@ final class HyArray[Element] private (using keepStorage: Boolean = false, assumeUniqueness: Boolean = false ): HyArray[Element] = - if (isEmpty) { + if isEmpty then { this - } else if (keepStorage) { + } else if keepStorage then { val result = if assumeUniqueness then this else copy() Arrays.fill(result._storage, null) result._count = 0 @@ -124,8 +124,8 @@ final class HyArray[Element] private (using override def toString: String = var s = "[" var i = 0 - while (i < count) { - if (i > 0) { s += ", " } + while i < count do { + if i > 0 then { s += ", " } s += s"${at(i)}" i += 1 } @@ -135,14 +135,14 @@ final class HyArray[Element] private (using * allocating new storage. */ def copy(minimumCapacity: Int = 0): HyArray[Element] = - if (minimumCapacity > capacity) { + if minimumCapacity > capacity then { // If the requested capacity on the copy is greater than what we have, `reserveCapacity` will // create an independent value. reserveCapacity(minimumCapacity) } else { val clone = HyArray[Element]().reserveCapacity(max(minimumCapacity, count)) var i = 0 - while (i < count) { + while i < count do { clone._storage(i) = _storage(i).asInstanceOf[Element].copy().asInstanceOf[AnyRef] i += 1 } @@ -157,7 +157,7 @@ object HyArray { /** Creates an array with the given `elements`. */ def apply[T](using t: Value[T])(elements: T*): HyArray[T] = var a = new HyArray[T](null, 0) - for (e <- elements) a = a.append(e, assumeUniqueness = true) + for e <- elements do a = a.append(e, assumeUniqueness = true) a } @@ -185,7 +185,7 @@ given hyArrayIsCollection: [T] => (tIsValue: Value[T]) => Collection[HyArray[T]] //given elementIsValue: Value[T] = tIsValue type Position = Int - given positionIsValue: Value[Int] = intIsValue + override given positionIsValue: Value[Int] = intIsValue extension (self: HyArray[T]) { diff --git a/tests/pos/hylolib-deferred-given/Slice.scala b/tests/pos/hylolib-deferred-given/Slice.scala index 234b16dfc428..ed3a1cb2e4b8 100644 --- a/tests/pos/hylolib-deferred-given/Slice.scala +++ b/tests/pos/hylolib-deferred-given/Slice.scala @@ -32,7 +32,7 @@ given sliceIsCollection: [T] => (c: Collection[T]) => Collection[Slice[T]] { //given elementIsValue: Value[Element] = c.elementIsValue type Position = c.Position - given positionIsValue: Value[Position] = c.positionIsValue + override given positionIsValue: Value[Position] = c.positionIsValue extension (self: Slice[T]) { diff --git a/tests/pos/hylolib/BitArray.scala b/tests/pos/hylolib/BitArray.scala index 6ef406e5ad83..03c3c0663e20 100644 --- a/tests/pos/hylolib/BitArray.scala +++ b/tests/pos/hylolib/BitArray.scala @@ -22,11 +22,11 @@ final class BitArray private ( /** Reserves enough storage to store `n` elements in `this`. */ def reserveCapacity(n: Int, assumeUniqueness: Boolean = false): BitArray = - if (n == 0) { + if n == 0 then { this } else { val k = 1 + ((n - 1) >> 5) - if (assumeUniqueness) { + if assumeUniqueness then { _bits = _bits.reserveCapacity(k, assumeUniqueness) this } else { @@ -38,7 +38,7 @@ final class BitArray private ( def append(bit: Boolean, assumeUniqueness: Boolean = false): BitArray = val result = if assumeUniqueness && (count < capacity) then this else copy(count + 1) val p = BitArray.Position(count) - if (p.bucket >= _bits.count) { + if p.bucket >= _bits.count then { result._bits = _bits.append(if bit then 1 else 0) } else { result.setValue(bit, p) @@ -48,7 +48,7 @@ final class BitArray private ( /** Removes and returns the last element, or returns `None` if the array is empty. */ def popLast(assumeUniqueness: Boolean = false): (BitArray, Option[Boolean]) = - if (isEmpty) { + if isEmpty then { (this, None) } else { val result = if assumeUniqueness then this else copy() @@ -62,9 +62,9 @@ final class BitArray private ( keepStorage: Boolean = false, assumeUniqueness: Boolean = false ): BitArray = - if (isEmpty) { + if isEmpty then { this - } else if (keepStorage) { + } else if keepStorage then { val result = if assumeUniqueness then this else copy() result._bits.removeAll(keepStorage, assumeUniqueness = true) result._count = 0 @@ -75,15 +75,15 @@ final class BitArray private ( /** Returns `true` iff all elements in `this` are `false`. */ def allFalse: Boolean = - if (isEmpty) { + if isEmpty then { true } else { val k = (count - 1) >> 5 def loop(i: Int): Boolean = - if (i == k) { + if i == k then { val m = (1 << (count & 31)) - 1 (_bits.at(k) & m) == 0 - } else if (_bits.at(i) != 0) { + } else if _bits.at(i) != 0 then { false } else { loop(i + 1) @@ -93,15 +93,15 @@ final class BitArray private ( /** Returns `true` iff all elements in `this` are `true`. */ def allTrue: Boolean = - if (isEmpty) { + if isEmpty then { true } else { val k = (count - 1) >> 5 def loop(i: Int): Boolean = - if (i == k) { + if i == k then { val m = (1 << (count & 31)) - 1 (_bits.at(k) & m) == m - } else if (_bits.at(i) != ~0) { + } else if _bits.at(i) != ~0 then { false } else { loop(i + 1) @@ -136,14 +136,14 @@ final class BitArray private ( assumeUniqueness: Boolean = false ): BitArray = require(this.count == other.count) - if (isEmpty) { + if isEmpty then { this } else { val result = if assumeUniqueness then this else copy() var u = assumeUniqueness val k = (count - 1) >> 5 - for (i <- 0 until k) { + for i <- 0 until k do { result._bits = result._bits.modifyAt( i, (n) => operation(n, other._bits.at(n)), assumeUniqueness = u @@ -184,7 +184,7 @@ final class BitArray private ( * O(1). */ def positionAfter(p: BitArray.Position): BitArray.Position = - if (p.offsetInBucket == 63) { + if p.offsetInBucket == 63 then { BitArray.Position(p.bucket + 1, 0) } else { BitArray.Position(p.bucket, p.offsetInBucket + 1) @@ -244,7 +244,7 @@ final class BitArray private ( /** Returns an independent copy of `this`. */ def copy(minimumCapacity: Int = 0): BitArray = - if (minimumCapacity > capacity) { + if minimumCapacity > capacity then { // If the requested capacity on the copy is greater than what we have, `reserveCapacity` will // create an independent value. reserveCapacity(minimumCapacity) @@ -313,7 +313,7 @@ object BitArray { /** Creates an array with the given `bits`. */ def apply[T](bits: Boolean*): BitArray = var result = new BitArray(HyArray[Int](), 0) - for (b <- bits) result = result.append(b, assumeUniqueness = true) + for b <- bits do result = result.append(b, assumeUniqueness = true) result } diff --git a/tests/pos/hylolib/HyArray.scala b/tests/pos/hylolib/HyArray.scala index de5e83d3b1a3..b386b8ed4375 100644 --- a/tests/pos/hylolib/HyArray.scala +++ b/tests/pos/hylolib/HyArray.scala @@ -27,21 +27,21 @@ final class HyArray[Element: Value as elementIsCValue]( /** Reserves enough storage to store `n` elements in `this`. */ def reserveCapacity(n: Int, assumeUniqueness: Boolean = false): HyArray[Element] = - if (n <= capacity) { + if n <= capacity then { this } else { var newCapacity = max(1, capacity) - while (newCapacity < n) { newCapacity = newCapacity << 1 } + while newCapacity < n do { newCapacity = newCapacity << 1 } val newStorage = new scala.Array[AnyRef | Null](newCapacity) val s = _storage.asInstanceOf[scala.Array[AnyRef | Null]] var i = 0 - while (i < count) { + while i < count do { newStorage(i) = _storage(i).asInstanceOf[Element].copy().asInstanceOf[AnyRef] i += 1 } - if (assumeUniqueness) { + if assumeUniqueness then { _storage = newStorage this } else { @@ -60,13 +60,13 @@ final class HyArray[Element: Value as elementIsCValue]( def appendContents[C: Collection { type Element = HyArray.this.Element }]( source: C, assumeUniqueness: Boolean = false ): HyArray[Element] = - val result = if (assumeUniqueness) { this } else { copy(count + source.count) } + val result = if assumeUniqueness then { this } else { copy(count + source.count) } source.reduce(result): (r, e) => r.append(e, assumeUniqueness = true) /** Removes and returns the last element, or returns `None` if the array is empty. */ def popLast(assumeUniqueness: Boolean = false): (HyArray[Element], Option[Element]) = - if (isEmpty) { + if isEmpty then { (this, None) } else { val result = if assumeUniqueness then this else copy() @@ -79,9 +79,9 @@ final class HyArray[Element: Value as elementIsCValue]( keepStorage: Boolean = false, assumeUniqueness: Boolean = false ): HyArray[Element] = - if (isEmpty) { + if isEmpty then { this - } else if (keepStorage) { + } else if keepStorage then { val result = if assumeUniqueness then this else copy() Arrays.fill(result._storage, null) result._count = 0 @@ -120,8 +120,8 @@ final class HyArray[Element: Value as elementIsCValue]( override def toString: String = var s = "[" var i = 0 - while (i < count) { - if (i > 0) { s += ", " } + while i < count do { + if i > 0 then { s += ", " } s += s"${at(i)}" i += 1 } @@ -131,14 +131,14 @@ final class HyArray[Element: Value as elementIsCValue]( * allocating new storage. */ def copy(minimumCapacity: Int = 0): HyArray[Element] = - if (minimumCapacity > capacity) { + if minimumCapacity > capacity then { // If the requested capacity on the copy is greater than what we have, `reserveCapacity` will // create an independent value. reserveCapacity(minimumCapacity) } else { val clone = HyArray[Element]().reserveCapacity(max(minimumCapacity, count)) var i = 0 - while (i < count) { + while i < count do { clone._storage(i) = _storage(i).asInstanceOf[Element].copy().asInstanceOf[AnyRef] i += 1 } @@ -153,7 +153,7 @@ object HyArray { /** Creates an array with the given `elements`. */ def apply[T: Value](elements: T*): HyArray[T] = var a = new HyArray[T](null, 0) - for (e <- elements) a = a.append(e, assumeUniqueness = true) + for e <- elements do a = a.append(e, assumeUniqueness = true) a } diff --git a/tests/pos/i11864.scala b/tests/pos/i11864.scala index ba43336e13ca..0301b50d7021 100644 --- a/tests/pos/i11864.scala +++ b/tests/pos/i11864.scala @@ -40,7 +40,7 @@ final class CallbackTo[+A] { object CallbackTo { type MapGuard[A] = { type Out = A } - erased given MapGuard: [A] => MapGuard[A] = compiletime.erasedValue + inline given MapGuard: [A] => MapGuard[A] = caps.unsafe.unsafeErasedValue def traverse[A, B](ta: List[A]): CallbackTo[List[B]] = val x: CallbackTo[List[A] => List[B]] = ??? diff --git a/tests/pos/i11896.scala b/tests/pos/i11896.scala index 49e5307f1a49..a4816eb5ad18 100644 --- a/tests/pos/i11896.scala +++ b/tests/pos/i11896.scala @@ -1,7 +1,7 @@ import scala.language.experimental.erasedDefinitions type X -erased def x: X = compiletime.erasedValue +inline def x: X = caps.unsafe.unsafeErasedValue def foo(using erased X): Unit = () diff --git a/tests/pos/i13392.scala b/tests/pos/i13392.scala index 614f711eebb5..5e5e2908722e 100644 --- a/tests/pos/i13392.scala +++ b/tests/pos/i13392.scala @@ -4,7 +4,7 @@ import annotation.{implicitNotFound, experimental} @experimental @implicitNotFound("The capability to throw exception ${E} is missing.\nThe capability can be provided by one of the following:\n - A using clause `(using CanThrow[${E}])`\n - A `throws` clause in a result type such as `X throws ${E}`\n - an enclosing `try` that catches ${E}") -erased class CanThrow[-E <: Exception] +class CanThrow[-E <: Exception] extends compiletime.Erased @experimental object unsafeExceptions: diff --git a/tests/pos/i14152.scala b/tests/pos/i14152.scala index 2377d5ffeae3..d72180a7b5d4 100644 --- a/tests/pos/i14152.scala +++ b/tests/pos/i14152.scala @@ -25,6 +25,6 @@ def foo[F[_]](fn: [A] => Inv[A] => F[A]) = object O1 extends AnyRef val res0 = fn(new Inv(fn(new Inv[O1.type](O1)))) val res1: F[F[O1.type]] = res0 - res1 // checked with -Xprint:typer that this widens to Any + res1 // checked with -Vprint:typer that this widens to Any // instead of the original F[F[O1.type]] // or the incorrectly avoided F[? <: F[? <: Object]] diff --git a/tests/pos/i15784.scala b/tests/pos/i15784.scala index ab4b82638a42..dff40f4a4ddc 100644 --- a/tests/pos/i15784.scala +++ b/tests/pos/i15784.scala @@ -1,8 +1,19 @@ -def i15784 = List(42) match +//> using options -Werror + +def case1 = List(42) match case List(_, rest @ _*) => rest + case _ => ??? + +def case2 = List(42) match case List(_, Rest @ _*) => Rest + case _ => ??? + +def case3 = List(42) match case List(_, `Rest` @ _*) => Rest case _ => ??? -def i15784_auxiliary = 42 match +def case4 = 42 match case `type` : Int => `type` + +def case5 = 42 match + case X @ (_: Int) => 32 diff --git a/tests/pos/i17243.scala b/tests/pos/i17243.scala deleted file mode 100644 index 3d42495b26b0..000000000000 --- a/tests/pos/i17243.scala +++ /dev/null @@ -1,17 +0,0 @@ -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/i18450.scala b/tests/pos/i18450.scala new file mode 100644 index 000000000000..42c4830a85e7 --- /dev/null +++ b/tests/pos/i18450.scala @@ -0,0 +1,15 @@ +//> using options -explain-cyclic -Ydebug-cyclic + +class C: + extension (s: String) + def f = "hello, world" + def g = f + + //def k = k // Overloaded or recursive method k needs return type + // if k is not forced, it fails with: + // value k is not a member of String. + // Extension methods were tried, ... + + def e = + import this.{f as hw} + hw // this.f diff --git a/tests/pos/i20070.scala b/tests/pos/i20070.scala new file mode 100644 index 000000000000..c3061239f6d7 --- /dev/null +++ b/tests/pos/i20070.scala @@ -0,0 +1,7 @@ + +trait F[A] +given F[Int] = new F {} +def f[A: F] = { (x: A) => x } + +@main def test = + println(f[Int](1)) diff --git a/tests/pos/i20206.scala b/tests/pos/i20206.scala index 07ef3dc0ba73..89c3c7971f01 100644 --- a/tests/pos/i20206.scala +++ b/tests/pos/i20206.scala @@ -2,7 +2,7 @@ import language.experimental.erasedDefinitions -erased trait A +trait A extends compiletime.Erased trait B def foo1: A ?=> B ?=> Nothing = ??? diff --git a/tests/pos/i20225.scala b/tests/pos/i20225.scala new file mode 100644 index 000000000000..3704aa2034b5 --- /dev/null +++ b/tests/pos/i20225.scala @@ -0,0 +1,12 @@ +sealed abstract class Parent +class A extends Parent +class B extends Parent + +inline def matchAs[T <: Parent](p: Parent): Unit = p match + case _: T => () + case _ => () + +object Test: + def main(args: Array[String]): Unit = + matchAs[A](new B) + diff --git a/tests/pos/i20335.scala b/tests/pos/i20335.scala new file mode 100644 index 000000000000..81d1cbd9f760 --- /dev/null +++ b/tests/pos/i20335.scala @@ -0,0 +1,14 @@ +import java.time.OffsetDateTime +import scala.concurrent.duration.* + +val dateTime: OffsetDateTime = OffsetDateTime.now() + +implicit class DateTimeOps(val dateTime: OffsetDateTime) { + def plus(amount: FiniteDuration): OffsetDateTime = + dateTime +} + +def test = { + dateTime.plus(Duration.Zero) + dateTime.plus(if (true) Duration.Zero else Duration.Zero) +} \ No newline at end of file diff --git a/tests/pos/i20395.scala b/tests/pos/i20395.scala new file mode 100644 index 000000000000..3b0be1e31b5a --- /dev/null +++ b/tests/pos/i20395.scala @@ -0,0 +1,13 @@ +sealed trait NodeId +case object Hi extends NodeId +case object Hello extends NodeId + +extension (value: NodeId) + inline def parse[T <: NodeId] = value match + case _: T => () + case _ => () + +object Test: + def main(args: Array[String]): Unit = + Hi.parse[Hello.type] + diff --git a/tests/pos/i21410.scala b/tests/pos/i21410.scala deleted file mode 100644 index c3ba3ea862bc..000000000000 --- a/tests/pos/i21410.scala +++ /dev/null @@ -1,12 +0,0 @@ -class A -object Test: - type F[X] <: Any = X match - case A => Int - - def foo[T](x: String): T = ??? - def foo[U](x: U): F[U] = ??? - - val x1 = foo(A()) - val y: Int = x1 - - val x2: Int = foo(A()) // error diff --git a/tests/pos/i21410b.scala b/tests/pos/i21410b.scala deleted file mode 100644 index a17ad59bc59e..000000000000 --- a/tests/pos/i21410b.scala +++ /dev/null @@ -1,10 +0,0 @@ -object Test: - def foo[T](x: Option[T]): T = ??? - def foo[T <: Tuple](x: T): Tuple.Map[T, List] = ??? - - val tup: (Int, String) = (1, "") - - val x = foo(tup) - val y: (List[Int], List[String]) = x - - val x2: (List[Int], List[String]) = foo(tup) // error diff --git a/tests/pos/i21410c.scala b/tests/pos/i21410c.scala deleted file mode 100644 index 21f69fec20fa..000000000000 --- a/tests/pos/i21410c.scala +++ /dev/null @@ -1,11 +0,0 @@ -class AppliedPIso[A, B]() -case class User(age: Int) - -object Test: - extension [From, To](from: From) - def focus(): AppliedPIso[From, From] = ??? - transparent inline def focus(inline lambda: (From => To)): Any = ??? - - - val u = User(1) - val ap: AppliedPIso[User, User] = u.focus(_.age) // error diff --git a/tests/pos/i21749.scala b/tests/pos/i21749.scala new file mode 100644 index 000000000000..6180b629f06b --- /dev/null +++ b/tests/pos/i21749.scala @@ -0,0 +1,9 @@ +//> using options -Xfatal-warnings -no-indent + +object Test { + 1 match { + case 1 => + case class Test(name: String) + Nil + } +} diff --git a/tests/pos/i21951.scala b/tests/pos/i21951.scala new file mode 100644 index 000000000000..5b2b5b932fab --- /dev/null +++ b/tests/pos/i21951.scala @@ -0,0 +1,33 @@ +class A +object A: + given g[F[_]]: F[A] = ??? + +object Test: + summon[List[A]] // ok + def foo[F[_]] = + summon[F[A]] // error + +final case class X(val i: Int) +object X { + implicit final class XOps[F[_]](xs: F[X]) { + def unpack(implicit ev: F[X] <:< Iterable[X]): Iterable[Int] = xs.map(_.i) + } +} + +object App extends App { + // good + val ys: List[X] = List(X(1)) + println(ys.unpack) + + // bad + def printPolymorphic[F[_]](xs: F[X])(implicit ev: F[X] <:< Iterable[X]) = { + locally { + // implicit XOps is correct + import X.XOps + println(xs.unpack) // found + } + // but it's not being searched for in the companion object of X + println(xs.unpack) // error: unpack is not a member of F[X] + } + printPolymorphic[List](ys) +} \ No newline at end of file diff --git a/tests/pos/i21951b.scala b/tests/pos/i21951b.scala new file mode 100644 index 000000000000..30068b89d994 --- /dev/null +++ b/tests/pos/i21951b.scala @@ -0,0 +1,12 @@ + +class A +object A: + given A = ??? + +class B[X] +object B: + given g[T]: B[T] = ??? + +object Test: + def foo[X >: A] = summon[X] // was error + def bar[F[T] >: B[T]] = summon[F[Int]] // was error diff --git a/tests/pos/i22257.scala b/tests/pos/i22257.scala index 8cd797529097..7f0dd75293f3 100644 --- a/tests/pos/i22257.scala +++ b/tests/pos/i22257.scala @@ -24,3 +24,11 @@ type Bound5 <: FP5 type Node6 = NodeK { type FBound <: Bound6 } type Bound6 <: FP6 type FP6 = Fixed[Node6] + +// type X^ +// type Y^ +// type Z >: X <: Y +// val Z + + +// type C <: \ No newline at end of file diff --git a/tests/pos/i22436/atest.scala b/tests/pos/i22436/atest.scala new file mode 100644 index 000000000000..7f77a5562385 --- /dev/null +++ b/tests/pos/i22436/atest.scala @@ -0,0 +1,3 @@ +object Case1 { + def myProps(transport: ProtocolTransport): Unit = ??? +} diff --git a/tests/pos/i22436/defs.scala b/tests/pos/i22436/defs.scala new file mode 100644 index 000000000000..c4bc3d74df25 --- /dev/null +++ b/tests/pos/i22436/defs.scala @@ -0,0 +1,7 @@ +object ProtocolTransport + +import ProtocolTransport.* + +@annotation.nowarn() +class ProtocolTransport() + diff --git a/tests/pos/i22593a.scala b/tests/pos/i22593a.scala new file mode 100644 index 000000000000..a1cf650193c0 --- /dev/null +++ b/tests/pos/i22593a.scala @@ -0,0 +1,16 @@ +import scala.quoted.* + +package jam { + trait JamCoreDsl { + implicit inline def defaultJamConfig: this.JamConfig = + new JamConfig(brewRecRegex = ".*") + class JamConfig(val brewRecRegex: String) + inline def brew(implicit inline config: JamConfig): Unit = ??? + } + private object internal extends JamCoreDsl + export internal._ +} + +object test { + jam.brew +} diff --git a/tests/pos/i22593b/Main.scala b/tests/pos/i22593b/Main.scala new file mode 100644 index 000000000000..dcc28e6a767c --- /dev/null +++ b/tests/pos/i22593b/Main.scala @@ -0,0 +1,27 @@ +import scala.quoted.* + +package jam { + trait JamCoreDsl { + implicit inline def defaultJamConfig: this.JamConfig = + new JamConfig(brewRecRegex = ".*") + class JamConfig(val brewRecRegex: String) + inline def brew(implicit inline config: JamConfig): Unit = ${ brewImpl() } + } + private object internal extends JamCoreDsl + export internal._ + + def brewImpl(using q: Quotes)(): Expr[Unit] = { + findSelf + '{()} + } + + private def findSelf(using q: Quotes): Unit = { + import q.reflect.* + def rec(s: Symbol): Option[Symbol] = s.maybeOwner match { + case o if o.isNoSymbol => None + case o if o.isClassDef => Option(o) + case o => rec(o) + } + rec(Symbol.spliceOwner) + } +} \ No newline at end of file diff --git a/tests/pos/i22593b/Test.scala b/tests/pos/i22593b/Test.scala new file mode 100644 index 000000000000..73c2f9ad7cb3 --- /dev/null +++ b/tests/pos/i22593b/Test.scala @@ -0,0 +1,3 @@ +object CoreSpec { + jam.brew +} \ No newline at end of file diff --git a/tests/pos/i22648.scala b/tests/pos/i22648.scala new file mode 100644 index 000000000000..e1c7f73bb15e --- /dev/null +++ b/tests/pos/i22648.scala @@ -0,0 +1,14 @@ +import scala.quoted.* + +def fooImpl(using Quotes): Expr[Any] = + '{ + new AnyRef { + type T = Unit + def make: T = () + def take(t: T): Unit = () + }: { + type T + def make: T + def take(t: T): Unit + } + } diff --git a/tests/pos/i22676.scala b/tests/pos/i22676.scala new file mode 100644 index 000000000000..5654559b133f --- /dev/null +++ b/tests/pos/i22676.scala @@ -0,0 +1,11 @@ +package example + +trait Example { + class Input + + type Output[A] = A match { + case Input => Int + } +} + +class Ref(ref: Example#Input) diff --git a/tests/pos/i22713.scala b/tests/pos/i22713.scala new file mode 100644 index 000000000000..ec3e12cebaec --- /dev/null +++ b/tests/pos/i22713.scala @@ -0,0 +1,13 @@ +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit + +def Test = { + val worker: ScheduledExecutorService = ??? + object Worker extends Runnable { + def fails(): Either[Exception, Unit] = Right(worker.schedule(this, 5, TimeUnit.NANOSECONDS)) + def works(): Either[Exception, Unit] = Right { + worker.schedule(this, 5, TimeUnit.NANOSECONDS); () + } + def run(): Unit = ??? + } +} diff --git a/tests/pos/i22922.scala b/tests/pos/i22922.scala new file mode 100644 index 000000000000..a65c99d6bd46 --- /dev/null +++ b/tests/pos/i22922.scala @@ -0,0 +1,57 @@ +abstract class Computation[+A, -U] +type !![+A, -U] = Computation[A, U] +type Const[C] = [_] =>> C + +final class EffectImpl[Fx]: + sealed trait ThisInterpreter extends Interpreter.Unsealed: + final override type Elim = Fx + abstract class Stateless[F[+_], G[+_], Fx] + extends Interpreter.Stateless[F, G, Fx] + with ThisInterpreter + +trait Effect: + val impl: EffectImpl[this.type] = ??? + +trait SourceEffect[O] extends Effect: + abstract class Stateless[U] extends StatelessReturn[Unit, U] + abstract class StatelessReturn[R, U] extends impl.Stateless[Const[Unit], Const[R], U] + +sealed trait Handler[From[+_], To[+_], Elim, Intro]: + final def handle[V] = new HandleSyntax[V] + final class HandleSyntax[V]: + def apply[A, W](comp: From[A] !! W)(using CanPartiallyHandle[V, W, Elim]): To[A] !! (V & Intro) = ??? + +sealed trait CanPartiallyHandle[U, V, W] // missing in StreamImpl.map +object CanPartiallyHandle: + given [U, V, W](using (W & U) <:< V): CanPartiallyHandle[U, V, W] = ??? + +sealed trait Interpreter: + type From[+A] + type To[+A] + type Elim + type Intro + + final def toHandler: Handler[From, To, Elim, Intro] = ??? +object Interpreter: + trait Unsealed extends Interpreter + abstract class Stateless[F[+_], G[+_], Fx] extends Interpreter: + final override type From[+A] = F[A] + final override type To[+A] = G[A] + final override type Intro = Fx + +object Syntax: + extension [U](comp: Unit !! U) + def asStream[A, V](fx: SourceEffect[A])(using (fx.type & V) =:= U): Stream[A, V] = ??? + +sealed abstract class Stream[+A, -U]: + def map[B](f: A => B): Stream[B, U] + +import Syntax.* +final case class StreamImpl[A, U](Fx: SourceEffect[A])(val compute: Unit !! (U & Fx.type)) + extends Stream[A, U]: + type Fx = Fx.type + override def map[B](f: A => B): Stream[B, U] = + case object Fx2 extends SourceEffect[B] + new Fx.Stateless[Fx2.type] {}.toHandler + .handle(compute) + .asStream(Fx2) // error diff --git a/tests/pos/i22974a/Maybe_1.scala b/tests/pos/i22974a/Maybe_1.scala new file mode 100644 index 000000000000..bd4964b29582 --- /dev/null +++ b/tests/pos/i22974a/Maybe_1.scala @@ -0,0 +1,14 @@ +package pack +import Maybe._ +opaque type Maybe[+A] >: (Absent | Present[A]) = Absent | Present[A] +object Maybe: + sealed abstract class Absent + case object Absent extends Absent + object internal: + case class PresentAbsent(val depth: Int) + opaque type Present[+A] = A | internal.PresentAbsent + + extension [A](self: Maybe[A]) { + inline def flatten[B]: Maybe[B] = ??? + inline def isDefined: Boolean = ??? + } diff --git a/tests/pos/i22974a/macro_1.scala b/tests/pos/i22974a/macro_1.scala new file mode 100644 index 000000000000..61d7783bd38a --- /dev/null +++ b/tests/pos/i22974a/macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted._ + +inline def passThorugh(inline condition: Boolean): Any = + ${ passThorughImpl('{condition}) } + +def passThorughImpl(condition: Expr[Boolean])(using Quotes): Expr[Any] = condition diff --git a/tests/pos/i22974a/main_2.scala b/tests/pos/i22974a/main_2.scala new file mode 100644 index 000000000000..a0b55aff7f52 --- /dev/null +++ b/tests/pos/i22974a/main_2.scala @@ -0,0 +1,6 @@ +object Test { + def main(): Unit = + import pack.Maybe + val res: Maybe[Maybe[Int]] = ??? + passThorugh(res.flatten.isDefined) +} diff --git a/tests/pos/i22974b.scala b/tests/pos/i22974b.scala new file mode 100644 index 000000000000..4f6e23c0f97d --- /dev/null +++ b/tests/pos/i22974b.scala @@ -0,0 +1,18 @@ +object outer: + opaque type Queue = Queue.Unsafe + object Queue: + abstract class Unsafe + opaque type Unbounded = Queue + object Unbounded: + inline def initWith()(f: Unbounded => Unit): Unit = + f(Unsafe.init()) + + opaque type Unsafe <: Queue.Unsafe = Queue + object Unsafe: + def init[A](): Unsafe = ??? + +object Resource: + def run: Unit = + outer.Queue.Unbounded.initWith() { q => + ??? + } diff --git a/tests/pos/i22974c.scala b/tests/pos/i22974c.scala new file mode 100644 index 000000000000..77833340de19 --- /dev/null +++ b/tests/pos/i22974c.scala @@ -0,0 +1,18 @@ +object other: + sealed abstract class Absent + case object Absent extends Absent + case class PresentAbsent(val depth: Int) + opaque type Present[+A] = A | PresentAbsent + opaque type Maybe[+A] >: (Absent | Present[A]) = Absent | Present[A] + + extension [A](self: Maybe[A]) { + inline def flatten[B]: Maybe[B] = if self.isEmpty then Absent else ??? + def isEmpty: Boolean = self.isInstanceOf[Absent] + } + +class Test { + def main(): Unit = + import other.Maybe + val res: Maybe[Maybe[Int]] = ??? + res.flatten +} diff --git a/tests/pos/i23022.scala b/tests/pos/i23022.scala new file mode 100644 index 000000000000..0d88674b7f8d --- /dev/null +++ b/tests/pos/i23022.scala @@ -0,0 +1,12 @@ +trait ExtractorWithImplicit: + + object Yikes: + def unapply(implicit M: String): Option[Any] = ??? + + def expand: Any = + given String = "Hey" + "Wut" match + case Yikes(_) => ??? + case _ => ??? + + diff --git a/tests/pos/i23025.scala b/tests/pos/i23025.scala new file mode 100644 index 000000000000..23a2eb78613d --- /dev/null +++ b/tests/pos/i23025.scala @@ -0,0 +1,5 @@ + +class A { + def f: PartialFunction[Int, Int] = + a => { (try a catch { case e : Throwable => throw e}) match { case n => n } } +} diff --git a/tests/pos/i23032.scala b/tests/pos/i23032.scala new file mode 100644 index 000000000000..7e56d0f8b153 --- /dev/null +++ b/tests/pos/i23032.scala @@ -0,0 +1,2 @@ +def f[F[_], T, U]: F[T] | F[U] = ??? +def x = f.toString diff --git a/tests/pos/i23054.scala b/tests/pos/i23054.scala new file mode 100644 index 000000000000..8d5bf9de12fb --- /dev/null +++ b/tests/pos/i23054.scala @@ -0,0 +1,15 @@ + +object Bug: + + def m0(f: PartialFunction[Char, Unit]): Unit = () + + def m1(): Unit = + m0: x => + "abc".filter(_ == x) match + case _ => () + + def m2(): Unit = + m0: x => + x match + case _ => () + diff --git a/tests/pos/i23086.scala b/tests/pos/i23086.scala new file mode 100644 index 000000000000..2f8ee8689ad4 --- /dev/null +++ b/tests/pos/i23086.scala @@ -0,0 +1,2 @@ +class Test: + val _: this.type = summon[ValueOf[this.type]].value diff --git a/tests/pos/i23095/defs.scala b/tests/pos/i23095/defs.scala new file mode 100644 index 000000000000..085c63019ef6 --- /dev/null +++ b/tests/pos/i23095/defs.scala @@ -0,0 +1,18 @@ +import scala.compiletime.* +import scala.deriving.* + +trait SeqStringCodec[A]: + def decode(value: Seq[String]): Either[Any, A] + +trait UrlForm +trait FormCodec[A] + +object FormCodec { + inline given derived[A](using p: Mirror.ProductOf[A]): FormCodec[A] = + val codecs = summonAll[Tuple.Map[p.MirroredElemTypes, SeqStringCodec]].toArray + val values = codecs.map { + _.asInstanceOf[SeqStringCodec[?]] + .decode(List.empty[String]) + } + ??? +} diff --git a/tests/pos/i23095/test.scala b/tests/pos/i23095/test.scala new file mode 100644 index 000000000000..cf7a321cbcdf --- /dev/null +++ b/tests/pos/i23095/test.scala @@ -0,0 +1,3 @@ +given SeqStringCodec[Int] = ??? +given SeqStringCodec[String] = ??? +final case class Form(int: Int, string: String) derives FormCodec diff --git a/tests/pos/i23119.scala b/tests/pos/i23119.scala new file mode 100644 index 000000000000..cd8026005447 --- /dev/null +++ b/tests/pos/i23119.scala @@ -0,0 +1,29 @@ +//> using options -Wunused:patvars -Werror + +def make: IndexedSeq[FalsePositive] = + for { + i <- 1 to 2 + given Int = i + fp = FalsePositive() + } yield fp + +def broken = + for + i <- List(42) + (x, y) = "hello" -> "world" + yield + s"$x, $y" * i + +def alt: IndexedSeq[FalsePositive] = + given String = "hi" + for + given Int <- 1 to 2 + j: Int = summon[Int] // simple assign because irrefutable + _ = j + 1 + k :: Nil = j :: Nil : @unchecked // pattern in one var + fp = FalsePositive(using k) + yield fp + +class FalsePositive(using Int): + def usage(): Unit = + println(summon[Int]) diff --git a/tests/pos/i23194.scala b/tests/pos/i23194.scala new file mode 100644 index 000000000000..347b98efd877 --- /dev/null +++ b/tests/pos/i23194.scala @@ -0,0 +1,14 @@ +class R[T] extends annotation.StaticAnnotation + +class A[T]: + val next: A[T] = null + val self: this.type = this + val selfnext: this.next.type = this.next + def f: (A[T] @R[this.type], A[T] @R[this.next.type]) = ??? + def g: (A[T] @R[self.type], A[T] @R[selfnext.type]) = ??? + +class Test: + def test = + val (a, b) = A[String]().f + val (a2, b2) = A[String]().g + \ No newline at end of file diff --git a/tests/pos/i23217.scala b/tests/pos/i23217.scala new file mode 100644 index 000000000000..cf3427a2649e --- /dev/null +++ b/tests/pos/i23217.scala @@ -0,0 +1,20 @@ +trait HasA[T]: + type A = T + +object Test1: + def foo1(h: HasA[?])(a: h.A): Unit = {} + + def foo2(h1: HasA[?])(a1: h1.A): Unit = + foo1(h1)(a1) + + def foo3(h1: HasA[?], a1: h1.A): Unit = + foo1(h1)(a1) + +object Test2: + def bar1(h: HasA[?], a: h.A): Unit = {} + + def bar2(h1: HasA[?], a1: h1.A): Unit = + bar1(h1, a1) + + def foo3(h1: HasA[?])(a1: h1.A): Unit = + bar2(h1, a1) diff --git a/tests/pos/i23237a.scala b/tests/pos/i23237a.scala new file mode 100644 index 000000000000..bcac4c6f7050 --- /dev/null +++ b/tests/pos/i23237a.scala @@ -0,0 +1,29 @@ +sealed trait Dim +trait _2D extends Dim +trait _3D extends Dim + +sealed abstract class IntVector[D] +object IntVector{ + def apply[D](d: Array[Int]): IntVector[D] = ??? + def apply(x: Int, y: Int): IntVector2D = IntVector2D(x, y) + def apply(x: Int, y: Int, z: Int): IntVector3D = IntVector3D(x, y, z) +} +case class IntVector2D(i: Int, j: Int) extends IntVector[_2D] +case class IntVector3D(i: Int, j: Int, k: Int) extends IntVector[_3D] + +type DiscreteImage[D, A] = DiscreteField[D, DiscreteImageDomain, A] +class DiscreteField[D, DDomain[D] <: DiscreteDomain[D], A](val domain: DDomain[D], val data: IndexedSeq[A]) extends PartialFunction[PointId, A] { + override def apply(v1: PointId) = ??? + override def isDefinedAt(ptId: PointId) = ??? +} +object DiscreteField{ + implicit class DiscreteImageOps[D, A](discreteField: DiscreteField[D, DiscreteImageDomain, A]) { + def apply(idx: IntVector[D]): A = ??? + def isDefinedAt(idx: IntVector[D]): Boolean = ??? + } +} +trait DiscreteDomain[D] +trait DiscreteImageDomain[D] extends DiscreteDomain[D] +final case class PointId(id: Int) extends AnyVal + +def test[S](img: DiscreteImage[_3D, S]) = img(IntVector(1, 2, 3)) diff --git a/tests/pos/i23237b.scala b/tests/pos/i23237b.scala new file mode 100644 index 000000000000..b49df924e691 --- /dev/null +++ b/tests/pos/i23237b.scala @@ -0,0 +1,23 @@ +object types{ + final case class vec2(x: Float, y: Float) + final case class vec4(x: Float, y: Float, z: Float, w: Float) + object vec4: + def apply(xy: vec2, z: Float, w: Float): vec4 = vec4(xy.x, xy.y, z, w) + + opaque type Shader[In, Out] = In => Out + object Shader: + inline def apply[In, Out](f: In => Out): Shader[In, Out] = f + inline def apply[In](f: In => Unit): Shader[In, Unit] = f + inline def apply(body: => Any): Shader[Unit, Unit] = (_: Unit) => body +} +import types.* + +class GLSLEnvTests { + case class FragEnv(UV: vec2) + + inline def fragment: Shader[FragEnv, vec4] = + Shader { env => + val x = env.UV + vec4(env.UV, 0.0f, 1.0f) + } +} diff --git a/tests/pos/i23261.scala b/tests/pos/i23261.scala new file mode 100644 index 000000000000..04b0e3a2f93c --- /dev/null +++ b/tests/pos/i23261.scala @@ -0,0 +1,46 @@ +type DoubleToString[D <: Double] <: String = D match + case 0.0 => "0.0" + case -0.0 => "-0.0" + case _ => "_" + +type DoubleToString2[D <: Double] <: String = D match + case 0.0 => "0.0" + case _ => "_" + +type DoubleToString3[D <: Double] <: String = D match + case -0.0 => "-0.0" + case _ => "_" + +type FloatToString[F <: Float] <: String = F match + case 0.0f => "0.0f" + case -0.0f => "-0.0f" + case _ => "_" + +type FloatToString2[F <: Float] <: String = F match + case 0.0f => "0.0f" + case _ => "_" + +type FloatToString3[F <: Float] <: String = F match + case -0.0f => "-0.0f" + case _ => "_" + +@main def main(): Unit = { + summon[0.0 =:= 0.0] + summon[-0.0 =:= -0.0] + summon[DoubleToString[0.0] =:= "0.0"] + summon[DoubleToString[-0.0] =:= "-0.0"] + summon[DoubleToString[3.14] =:= "_"] + summon[DoubleToString2[0.0] =:= "0.0"] + summon[DoubleToString2[-0.0] =:= "_"] + summon[DoubleToString3[-0.0] =:= "-0.0"] + summon[DoubleToString3[0.0] =:= "_"] + summon[0.0f =:= 0.0f] + summon[-0.0f =:= -0.0f] + summon[FloatToString[0.0f] =:= "0.0f"] + summon[FloatToString[-0.0f] =:= "-0.0f"] + summon[FloatToString[3.14f] =:= "_"] + summon[FloatToString2[0.0f] =:= "0.0f"] + summon[FloatToString2[-0.0f] =:= "_"] + summon[FloatToString3[-0.0f] =:= "-0.0f"] + summon[FloatToString3[0.0f] =:= "_"] +} diff --git a/tests/pos/i23266.scala b/tests/pos/i23266.scala new file mode 100644 index 000000000000..3ff104c65cd7 --- /dev/null +++ b/tests/pos/i23266.scala @@ -0,0 +1,8 @@ +//> using scala 3.7.0 + +class Foo(v: Any) extends AnyVal: + def bar[X](bar: X)[Y]: Any = v + +@main def run: Unit = + val f = new Foo("lol") + println(f.bar[String]("")[Boolean]) \ No newline at end of file diff --git a/tests/pos/i23299.scala b/tests/pos/i23299.scala new file mode 100644 index 000000000000..bc643ac7215d --- /dev/null +++ b/tests/pos/i23299.scala @@ -0,0 +1,17 @@ + +def kek(t: Table, ids: t.Id*) = ??? + +trait Table { + type Id = String +} + +object Table1 extends Table { + val id: Id = "table1_id" +} + +class Table2() extends Table { + val id: Id = "table2_id" +} + +val x = kek(Table1, Table1.id) +val y = kek(Table2(), Table2().id) \ No newline at end of file diff --git a/tests/pos/i23310.scala b/tests/pos/i23310.scala new file mode 100644 index 000000000000..3b2c7eda9141 --- /dev/null +++ b/tests/pos/i23310.scala @@ -0,0 +1,16 @@ + +object Example { + val pf: PartialFunction[Unit, Unit] = s => (s match { + case a => a + }) match { + case a => () + } +} + +object ExampleB: + def test = + List(42).collect: + _.match + case x => x + .match + case y => y + 27 diff --git a/tests/pos/i23398.scala b/tests/pos/i23398.scala new file mode 100644 index 000000000000..b38b139cc485 --- /dev/null +++ b/tests/pos/i23398.scala @@ -0,0 +1,16 @@ +//> using options -feature -Werror +import scala.language.experimental.into +import Conversion.into + +case class Foo(x: Int) + +given Conversion[Int, Foo] = Foo(_) + +def takeFoo(f: into[Foo]) = f +inline def inlineTakeFoo(f: into[Foo]) = f +inline def takeInlineFoo(inline f: into[Foo]) = f + +def test = + val f1 = takeFoo(1) + val f2 = inlineTakeFoo(1) + val f3 = takeInlineFoo(1) diff --git a/tests/pos/i23451.scala b/tests/pos/i23451.scala new file mode 100644 index 000000000000..2263d532665c --- /dev/null +++ b/tests/pos/i23451.scala @@ -0,0 +1,12 @@ +trait Inliner[A]: + inline def apply[T]: A + +class SummonInliner[F[_]] extends Inliner[ForSome[F]]: + inline def apply[T]: ForSome[F] = ForSome(compiletime.summonInline[F[T]]) + +type ForSome[F[_]] = ForSome.Type[F] +object ForSome: + type Type[F[_]] = Unwrap[F, ?] + class Unwrap[F[_], A](val unwrap: F[A]) extends AnyVal + + inline def apply[F[_], A](v: F[A]): Type[F] = Unwrap(v) diff --git a/tests/pos/i23489.scala b/tests/pos/i23489.scala new file mode 100644 index 000000000000..f4215416e5b8 --- /dev/null +++ b/tests/pos/i23489.scala @@ -0,0 +1,13 @@ +import scala.language.experimental.modularity + +class Box1[T <: Singleton](val x: T) +class Box2[T : Singleton](x: => T) +def id(x: Int): x.type = x +def readInt(): Int = ??? + +def Test = () + val x = Box1(id(readInt())) + + val _: Box1[? <: Int] = x + + val y = Box2(id(readInt())) diff --git a/tests/pos/i23496/BaseLineSpec_2.scala b/tests/pos/i23496/BaseLineSpec_2.scala new file mode 100644 index 000000000000..71e731a11b1b --- /dev/null +++ b/tests/pos/i23496/BaseLineSpec_2.scala @@ -0,0 +1,2 @@ +package scalaql +import scalaql.fixture.* diff --git a/tests/pos/i23496/Query_1.scala b/tests/pos/i23496/Query_1.scala new file mode 100644 index 000000000000..f142e6086328 --- /dev/null +++ b/tests/pos/i23496/Query_1.scala @@ -0,0 +1,3 @@ +package scalaql + +import scala.annotation.unchecked.uncheckedVariance // unused diff --git a/tests/pos/i23496/ScalaqlSyntax_1.scala b/tests/pos/i23496/ScalaqlSyntax_1.scala new file mode 100644 index 000000000000..9fc0fba46030 --- /dev/null +++ b/tests/pos/i23496/ScalaqlSyntax_1.scala @@ -0,0 +1,4 @@ +package scalaql.syntax + +@scalaql.forbiddenInheritance +trait ScalaqlSyntax diff --git a/tests/pos/i23496/annotations_1.scala b/tests/pos/i23496/annotations_1.scala new file mode 100644 index 000000000000..d96737787b51 --- /dev/null +++ b/tests/pos/i23496/annotations_1.scala @@ -0,0 +1,3 @@ +package scalaql + +class forbiddenInheritance extends scala.annotation.StaticAnnotation diff --git a/tests/pos/i23496/fixture_2.scala b/tests/pos/i23496/fixture_2.scala new file mode 100644 index 000000000000..aa564742ec25 --- /dev/null +++ b/tests/pos/i23496/fixture_2.scala @@ -0,0 +1 @@ +package scalaql.fixture diff --git a/tests/pos/i23496/package.scala_1.scala b/tests/pos/i23496/package.scala_1.scala new file mode 100644 index 000000000000..46ada0b9bb94 --- /dev/null +++ b/tests/pos/i23496/package.scala_1.scala @@ -0,0 +1,3 @@ +import scalaql.syntax.ScalaqlSyntax + +package object scalaql extends ScalaqlSyntax diff --git a/tests/pos/i23526.scala b/tests/pos/i23526.scala new file mode 100644 index 000000000000..e530608435c7 --- /dev/null +++ b/tests/pos/i23526.scala @@ -0,0 +1,14 @@ +trait B[-A, +To] { + def addOne(e: A): this.type = this + def res(): To +} + +class Col[A] + +object Factory { + def newB[A](using reflect.ClassTag[A]) = new B[A, Col[A]] { def res(): Col[A] = new Col[A] } +} + +def test = + val a = Factory.newB.addOne(1).res() + val b = collection.immutable.ArraySeq.newBuilder.addOne(1).result() diff --git a/tests/pos/i23530.scala b/tests/pos/i23530.scala new file mode 100644 index 000000000000..822526f7562d --- /dev/null +++ b/tests/pos/i23530.scala @@ -0,0 +1,18 @@ +trait TestContainer: + trait TestPath: + type AbsMember + + extension (path: TestPath) + infix def ext(color: path.AbsMember): Unit = ??? + infix def ext(other: Int): Unit = ??? + +object Repro: + val dc2: TestContainer = ??? + import dc2.TestPath + + def transition(path: TestPath)(using DummyImplicit): TestPath = ??? + + def test: Unit = + val di: TestPath = ??? + // error + val z1 = transition(di).ext(1) diff --git a/tests/pos/i23611.scala b/tests/pos/i23611.scala new file mode 100644 index 000000000000..0fef178b9c32 --- /dev/null +++ b/tests/pos/i23611.scala @@ -0,0 +1,26 @@ +import java.io.{File, IOException} +import java.net.URI +import java.nio.file.{Path, Paths} +import scala.reflect.ClassTag + +trait FileConnectors { + def listPath(path: => Path): ZStream[Any, IOException, Path] + + final def listFile(file: => File): ZStream[Any, IOException, File] = + for { + path <- null.asInstanceOf[ZStream[Any, IOException, Path]] + r <- listPath(path).mapZIO(a => ZIO.attempt(a.toFile).refineToOrDie) + } yield r +} + +sealed trait ZIO[-R, +E, +A] +extension [R, E <: Throwable, A](self: ZIO[R, E, A]) + def refineToOrDie[E1 <: E: ClassTag]: ZIO[R, E1, A] = ??? + +object ZIO: + def attempt[A](code: => A): ZIO[Any, Throwable, A] = ??? + +sealed trait ZStream[-R, +E, +A]: + def map[B](f: A => B): ZStream[R, E, B] = ??? + def flatMap[R1 <: R, E1 >: E, B](f: A => ZStream[R1, E1, B]): ZStream[R1, E1, B] + def mapZIO[R1 <: R, E1 >: E, A1](f: A => ZIO[R1, E1, A1]): ZStream[R1, E1, A1] \ No newline at end of file diff --git a/tests/pos/i23611a.scala b/tests/pos/i23611a.scala new file mode 100644 index 000000000000..fbaf709e2f0e --- /dev/null +++ b/tests/pos/i23611a.scala @@ -0,0 +1,30 @@ +import java.io.{File, IOException} +import java.net.URI +import java.nio.file.{Path, Paths} +import scala.reflect.ClassTag + +trait FileConnectors { + def listPath(path: => Path): ZStream[Any, IOException, Path] + + final def listFile(file: => File): ZStream[Any, IOException, File] = + for { + path <- null.asInstanceOf[ZStream[Any, IOException, Path]] + r <- listPath(path).mapZIO(a => ZIO.attempt(a.toFile).refineToOrDie) + } yield r +} + +sealed abstract class CanFail[-E] +object CanFail: + given [E]: CanFail[E] = ??? + +sealed trait ZIO[-R, +E, +A] +extension [R, E <: Throwable, A](self: ZIO[R, E, A]) + def refineToOrDie[E1 <: E: ClassTag](using CanFail[E]): ZIO[R, E1, A] = ??? + +object ZIO: + def attempt[A](code: => A): ZIO[Any, Throwable, A] = ??? + +sealed trait ZStream[-R, +E, +A]: + def map[B](f: A => B): ZStream[R, E, B] = ??? + def flatMap[R1 <: R, E1 >: E, B](f: A => ZStream[R1, E1, B]): ZStream[R1, E1, B] + def mapZIO[R1 <: R, E1 >: E, A1](f: A => ZIO[R1, E1, A1]): ZStream[R1, E1, A1] \ No newline at end of file diff --git a/tests/pos/i23616.scala b/tests/pos/i23616.scala new file mode 100644 index 000000000000..48f91d8cd77f --- /dev/null +++ b/tests/pos/i23616.scala @@ -0,0 +1,22 @@ +trait Apply[F[_]]: + extension [A](fa: F[A]) + def map[B](f: A => B): F[B] + def map2[B, Z](fb: F[B])(f: (A, B) => Z): F[Z] = ??? + + private case class IsMap[T <: Tuple](value: Tuple.Map[T, F]) + private inline def tupledGeneric[T <: Tuple](tuple: Tuple.Map[T, F]): F[T] = + inline IsMap(tuple) match + case t: IsMap[h *: EmptyTuple] => t.value.head.map(_ *: EmptyTuple) + case t: IsMap[h *: t] => + val head = t.value.head + val tail = tupledGeneric(t.value.tail) + head.map2(tail)(_ *: _) + +trait Monad[F[_]] extends Apply[F]: + extension [A](fa: F[A]) def flatMap[B](f: A => F[B]): F[B] + extension [A](fa: F[A]) override def map[B](f: A => B): F[B] = ??? + +opaque type Kleisli[F[_], A, B] = A => F[B] +given [F[_], A](using F: Monad[F]): Monad[[B] =>> Kleisli[F, A, B]] with + extension [B](k: Kleisli[F, A, B]) + def flatMap[C](f: B => Kleisli[F, A, C]) = ??? \ No newline at end of file diff --git a/tests/pos/i23627.scala b/tests/pos/i23627.scala new file mode 100644 index 000000000000..1480a80c9c00 --- /dev/null +++ b/tests/pos/i23627.scala @@ -0,0 +1,18 @@ +trait TestContainer: + trait TestPath[T]: + type AbsMember + + extension (path: TestPath[?]) + infix def ext(color: path.AbsMember): Unit = ??? + infix def ext(other: Int): Unit = ??? + +object Repro: + val dc2: TestContainer = ??? + import dc2.TestPath + + def transition(path: TestPath[?])(using DummyImplicit): TestPath[?] = ??? + + def test: Unit = + val di: TestPath[?] = ??? + // error + val z1 = transition(di).ext(1) diff --git a/tests/pos/i23734.scala b/tests/pos/i23734.scala new file mode 100644 index 000000000000..308bfdae3fa9 --- /dev/null +++ b/tests/pos/i23734.scala @@ -0,0 +1,18 @@ +trait Nodes1 { + sealed trait B + final case class R1() extends B +} + +trait Nodes2 extends Nodes1 { + final case class R2[T]() extends B +} + + +object Impl1 extends Nodes1 + +object test2 { + val a: Impl1.B = ??? + a match { + case Impl1.R1() => ??? + } +} diff --git a/tests/pos/i3920.scala b/tests/pos/i3920.scala index 6cd74187098f..61d01156427a 100644 --- a/tests/pos/i3920.scala +++ b/tests/pos/i3920.scala @@ -8,11 +8,10 @@ class SetFunctor(tracked val ord: Ordering) { type Set = List[ord.T] def empty: Set = Nil - implicit class helper(s: Set) { + extension (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 { diff --git a/tests/pos/i5938.scala b/tests/pos/i5938.scala index 17a20dcd0f1f..f392de153b4c 100644 --- a/tests/pos/i5938.scala +++ b/tests/pos/i5938.scala @@ -1,7 +1,6 @@ import scala.language.experimental.erasedDefinitions import compiletime.summonFrom -import compiletime.erasedValue trait Link[T, A] @@ -15,7 +14,7 @@ transparent inline def link[T] = class Foo object Foo { - erased implicit val barLink: Link[Foo, Bar.type] = erasedValue + erased implicit val barLink: Link[Foo, Bar.type] = caps.unsafe.unsafeErasedValue } implicit object Bar { diff --git a/tests/pos/i6419.scala b/tests/pos/i6419.scala index 550922f48d76..44136d9e48a3 100644 --- a/tests/pos/i6419.scala +++ b/tests/pos/i6419.scala @@ -9,8 +9,4 @@ class Foo { inline def bar: Unit = { foo } - - erased def baz: Unit = { - foo - } } diff --git a/tests/pos/i7741.scala b/tests/pos/i7741.scala index af9912915cc0..981789f14e2a 100644 --- a/tests/pos/i7741.scala +++ b/tests/pos/i7741.scala @@ -3,9 +3,6 @@ import scala.language.experimental.erasedDefinitions class A1 { @native private def a: Unit } -trait A2 { - erased def i(erased a: Int): Int -} trait A3 { erased val a: Int } \ No newline at end of file diff --git a/tests/pos/i7868.scala b/tests/pos/i7868.scala deleted file mode 100644 index fa31bd131b0c..000000000000 --- a/tests/pos/i7868.scala +++ /dev/null @@ -1,42 +0,0 @@ -//> using options -language:experimental.erasedDefinitions - -import language.experimental.namedTypeArguments -import scala.compiletime.* -import scala.compiletime.ops.int.* - -final case class Coproduct[+Set, +Value, Index <: Int](value: Value & Set, index: Index) - -object Coproduct { - opaque type +:[+A, +B] = A | B - - trait At[+Set, -Value, Index <: Int] { - def cast: Value <:< Set - } - - object At { - - given atHead: [Head, Tail] => At[Head +: Tail, Head, 0]: - def cast: Head <:< Head +: Tail = summon[Head <:< Head +: Tail] - - given atTail[Head, Tail, Value, NextIndex <: Int] - (using atNext: At[Tail, Value, NextIndex]) - : At[Head +: Tail, Value, S[NextIndex]] with - val cast: Value <:< Head +: Tail = atNext.cast - - given [A] => A => (() => A) = { () => summon[A] } - } - - def upCast[A, B](a: A)(using erased evidence: (A <:< B) ): B = a.asInstanceOf[B] - - def from[Set, Value, Index <: Int](value: Value)(using erased at: At[Set, Value, Index]) : ValueOf[Index] ?=> Coproduct[Set, Value, Index] = { - Coproduct[Set, Value, Index](upCast(value: Value)(using at.cast.liftCo[[X] =>> Value & X]), valueOf[Index]) - } - -} - -object Test extends App { - import Coproduct.* - - // Error: No singleton value available for scala.compiletime.ops.int.S[scala.compiletime.ops.int.S[(0 : Int)]]. - val c = from[Set = Int +: String +: Seq[Double] +: Nothing](Nil) -} diff --git a/tests/pos/i8875.scala b/tests/pos/i8875.scala index c0de263417e0..e4ac9e057f64 100644 --- a/tests/pos/i8875.scala +++ b/tests/pos/i8875.scala @@ -1,7 +1,7 @@ -//> using options -Xprint:getters +//> using options -Vprint:getters class A { extension (a: Int) { def foo: Int = 1 } -} \ No newline at end of file +} diff --git a/tests/pos/infer-tracked-1.scala b/tests/pos/infer-tracked-1.scala index b4976a963074..0b7568d54159 100644 --- a/tests/pos/infer-tracked-1.scala +++ b/tests/pos/infer-tracked-1.scala @@ -10,11 +10,10 @@ class SetFunctor(val ord: Ordering) { type Set = List[ord.T] def empty: Set = Nil - implicit class helper(s: Set) { + extension (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 { diff --git a/tests/pos/inline-match-gadt.scala b/tests/pos/inline-match-gadt.scala index cf2aae00b402..7c966f33bf48 100644 --- a/tests/pos/inline-match-gadt.scala +++ b/tests/pos/inline-match-gadt.scala @@ -2,7 +2,7 @@ import scala.language.experimental.erasedDefinitions object `inline-match-gadt` { class Exactly[T] - erased def exactType[T]: Exactly[T] = compiletime.erasedValue + inline def exactType[T]: Exactly[T] = compiletime.erasedValue inline def foo[T](t: T): T = inline exactType[T] match { diff --git a/tests/pos/into-bigint.scala b/tests/pos/into-bigint.scala index 409b5e79da2c..9ecac8c68dcd 100644 --- a/tests/pos/into-bigint.scala +++ b/tests/pos/into-bigint.scala @@ -1,13 +1,14 @@ import language.experimental.into +import Conversion.into class BigInt(x: Int): - def + (other: into BigInt): BigInt = ??? - def * (other: into BigInt): BigInt = ??? + def + (other: into[BigInt]): BigInt = ??? + def * (other: into[BigInt]): BigInt = ??? object BigInt: given Conversion[Int, BigInt] = BigInt(_) - extension (x: into BigInt) + extension (x: into[BigInt]) def + (other: BigInt): BigInt = ??? def * (other: BigInt): BigInt = ??? diff --git a/tests/pos/into-class.scala b/tests/pos/into-class.scala index 2638b9a0234a..989c932475cb 100644 --- a/tests/pos/into-class.scala +++ b/tests/pos/into-class.scala @@ -1,8 +1,9 @@ import language.experimental.into +import Conversion.into class Text(str: String) -case class C(x: into Text) +case class C(x: into[Text]) case class D(x: Text) diff --git a/tests/pos/into-expr.scala b/tests/pos/into-expr.scala new file mode 100644 index 000000000000..85b287668ee8 --- /dev/null +++ b/tests/pos/into-expr.scala @@ -0,0 +1,16 @@ + +//> using options -feature -Xfatal-warnings + +import language.experimental.into +import Conversion.into + +enum Expr: + case Neg(e: into[Expr]) + case Add(e1: into[Expr], e2: into[Expr]) + case Const(n: Int) +import Expr.* + +given Conversion[Int, Const] = Const(_) + +def Test = + Add(1, Neg(2)) diff --git a/tests/pos/into-sam.scala b/tests/pos/into-sam.scala index 7513b5c5b0de..c4b528fce215 100644 --- a/tests/pos/into-sam.scala +++ b/tests/pos/into-sam.scala @@ -2,16 +2,17 @@ //> using options -feature -Xfatal-warnings import language.experimental.into +import Conversion.into class Text(val str: String) given Conversion[String, Text] = Text(_) object Test: - def f(x: Int)(y: into Text): Unit = () + def f(x: Int)(y: into[Text]): Unit = () val _: Text => Unit = f(3) trait ConvArg: - def apply(x: into Text): Unit + def apply(x: into[Text]): Unit val x: ConvArg = f(3)(_) diff --git a/tests/pos/into-separate/Test_2.scala b/tests/pos/into-separate/Test_2.scala new file mode 100644 index 000000000000..afcd46522229 --- /dev/null +++ b/tests/pos/into-separate/Test_2.scala @@ -0,0 +1,16 @@ +//> using options -feature +package test +import language.experimental.into + +object Test: + given Conversion[Int, C] = C(_) + + def f(x: T) = () + f(1) // ok + + given stringToKeyword: Conversion[String, Keyword] = Keyword(_) + + val dclKeywords = Set[Keyword]("def", "val") // ok + val keywords = dclKeywords + "if" + "then" + "else" // ok + + diff --git a/tests/pos/into-separate/classes_1.scala b/tests/pos/into-separate/classes_1.scala new file mode 100644 index 000000000000..b39bfe840e3e --- /dev/null +++ b/tests/pos/into-separate/classes_1.scala @@ -0,0 +1,8 @@ +package test + +import language.experimental.into + +into trait T +class C(x: Int) extends T + +into class Keyword(str: String) diff --git a/tests/pos/matchtype.scala b/tests/pos/matchtype.scala index 21c074deafd7..90d8f0dc6400 100644 --- a/tests/pos/matchtype.scala +++ b/tests/pos/matchtype.scala @@ -1,5 +1,5 @@ import scala.language.experimental.erasedDefinitions -import compiletime.erasedValue +import caps.unsafe.unsafeErasedValue as erasedValue import compiletime.ops.int.S object Test { type T[X] = X match { diff --git a/tests/pos/parsercombinators-ctx-bounds.scala b/tests/pos/parsercombinators-ctx-bounds.scala index 50338dbc2fa5..c337e19faf3a 100644 --- a/tests/pos/parsercombinators-ctx-bounds.scala +++ b/tests/pos/parsercombinators-ctx-bounds.scala @@ -25,8 +25,9 @@ given apply: [C, E] => Combinator[Apply[C, E]] { } } -given combine[A: Combinator, B: [X] =>> Combinator[X] { type Context = A.Context }] - : Combinator[Combine[A, B]] with +given combine + : [A: Combinator, B: [X] =>> Combinator[X] { type Context = A.Context }] + => Combinator[Combine[A, B]]: type Context = A.Context type Element = (A.Element, B.Element) extension(self: Combine[A, B]) diff --git a/tests/pos/parsercombinators-givens-2.scala b/tests/pos/parsercombinators-givens-2.scala index 4b00c3801716..f708b2613ffa 100644 --- a/tests/pos/parsercombinators-givens-2.scala +++ b/tests/pos/parsercombinators-givens-2.scala @@ -26,16 +26,14 @@ given apply: [C, E] => Combinator[Apply[C, E]] { } } -given combine[A, B, C](using - f: Combinator[A] { type Context = C }, - s: Combinator[B] { type Context = C } -): Combinator[Combine[A, B]] with { +given combine: [A, B, C] + => (f: Combinator[A] { type Context = C }, s: Combinator[B] { type Context = C }) + => 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] = ??? } -} extension [A] (buf: mutable.ListBuffer[A]) def popFirst() = if buf.isEmpty then None diff --git a/tests/pos/parsercombinators-givens.scala b/tests/pos/parsercombinators-givens.scala index 1fa8080b0688..71000ac8134d 100644 --- a/tests/pos/parsercombinators-givens.scala +++ b/tests/pos/parsercombinators-givens.scala @@ -26,16 +26,14 @@ given apply: [C, E] => Combinator[Apply[C, E]] { } } -given combine[A, B](using - tracked val f: Combinator[A], - tracked val s: Combinator[B] { type Context = f.Context } -): Combinator[Combine[A, B]] with { +given combine + : [A, B] + => (tracked val f: Combinator[A], tracked val s: Combinator[B] { type Context = f.Context }) + => Combinator[Combine[A, B]]: type Context = f.Context type Element = (f.Element, s.Element) - extension(self: Combine[A, B]) { + extension (self: Combine[A, B]) def parse(context: Context): Option[Element] = ??? - } -} extension [A] (buf: mutable.ListBuffer[A]) def popFirst() = if buf.isEmpty then None diff --git a/tests/pos/parsercombinators-this.scala b/tests/pos/parsercombinators-this.scala index 19ac878c888f..cf25aea94d25 100644 --- a/tests/pos/parsercombinators-this.scala +++ b/tests/pos/parsercombinators-this.scala @@ -28,8 +28,9 @@ given apply: [C, E] => Combinator { } } -given combine[A: Combinator, B: Combinator { type Context = A.Context }] - : Combinator with +given combine + : [A: Combinator, B: Combinator { type Context = A.Context }] + => Combinator: type Self = Combine[A, B] type Context = A.Context type Element = (A.Element, B.Element) diff --git a/tests/pos/phantom-Eq.scala b/tests/pos/phantom-Eq.scala index d844c4b110c6..f3a4af02a186 100644 --- a/tests/pos/phantom-Eq.scala +++ b/tests/pos/phantom-Eq.scala @@ -16,18 +16,19 @@ object PhantomEq { object EqUtil { - type PhantomEq[-L, -R] + class PhantomEq[-L, -R] type PhantomEqEq[T] = PhantomEq[T, T] + erased val phantomEq = PhantomEq[Any, Any]() extension [T](x: T) def ===[U](y: U)(using erased PhantomEq[T, U]) = x.equals(y) - erased given eqString: PhantomEqEq[String] = compiletime.erasedValue - erased given eqInt: PhantomEqEq[Int] = compiletime.erasedValue - erased given eqDouble: PhantomEqEq[Double] = compiletime.erasedValue + inline given eqString: PhantomEqEq[String] = phantomEq + inline given eqInt: PhantomEqEq[Int] = phantomEq + inline given eqDouble: PhantomEqEq[Double] = phantomEq - erased given eqByteNum: PhantomEq[Byte, Number] = compiletime.erasedValue - erased given eqNumByte: PhantomEq[Number, Byte] = compiletime.erasedValue + inline given eqByteNum: PhantomEq[Byte, Number] = phantomEq + inline given eqNumByte: PhantomEq[Number, Byte] = phantomEq - erased given eqSeq: [T, U] => (erased PhantomEq[T, U]) => PhantomEq[Seq[T], Seq[U]] = compiletime.erasedValue + inline given eqSeq: [T, U] => (erased PhantomEq[T, U]) => PhantomEq[Seq[T], Seq[U]] = phantomEq } diff --git a/tests/pos/phantom-Eq2/Phantom-Eq_1.scala b/tests/pos/phantom-Eq2/Phantom-Eq_1.scala index b041a4a87efe..b5021a30b09b 100644 --- a/tests/pos/phantom-Eq2/Phantom-Eq_1.scala +++ b/tests/pos/phantom-Eq2/Phantom-Eq_1.scala @@ -1,19 +1,20 @@ import scala.language.experimental.erasedDefinitions +import scala.annotation.publicInBinary /* This is a version of ../pos/phantomEq.scala that tests phantom with separate compilation */ object EqUtil { - final class PhantomEq[-L, -R] private[EqUtil]() + final class PhantomEq[-L, -R] @publicInBinary private[EqUtil]() type PhantomEqEq[T] = PhantomEq[T, T] extension [T](x: T) def ===[U] (y: U) (using erased PhantomEq[T, U]) = x.equals(y) - erased given eqString: PhantomEqEq[String] = new PhantomEq[String, String] - erased given eqInt: PhantomEqEq[Int] = new PhantomEq[Int, Int] - erased given eqDouble: PhantomEqEq[Double] = new PhantomEq[Double, Double] - erased given eqByteNum: PhantomEq[Byte, Number] = new PhantomEq[Byte, Number] - erased given eqNumByte: PhantomEq[Number, Byte] = new PhantomEq[Number, Byte] - erased given eqSeq: [T, U] => (erased eq: PhantomEq[T, U]) => PhantomEq[Seq[T], Seq[U]] = + inline given eqString: PhantomEqEq[String] = new PhantomEq[String, String] + inline given eqInt: PhantomEqEq[Int] = new PhantomEq[Int, Int] + inline given eqDouble: PhantomEqEq[Double] = new PhantomEq[Double, Double] + inline given eqByteNum: PhantomEq[Byte, Number] = new PhantomEq[Byte, Number] + inline given eqNumByte: PhantomEq[Number, Byte] = new PhantomEq[Number, Byte] + inline given eqSeq: [T, U] => (erased eq: PhantomEq[T, U]) => PhantomEq[Seq[T], Seq[U]] = new PhantomEq[Seq[T], Seq[U]] } diff --git a/tests/pos/phantom-Evidence.scala b/tests/pos/phantom-Evidence.scala index f56ce3b798ee..e322e48c8d47 100644 --- a/tests/pos/phantom-Evidence.scala +++ b/tests/pos/phantom-Evidence.scala @@ -1,4 +1,5 @@ import scala.language.experimental.erasedDefinitions +import annotation.publicInBinary /** In this implementation variant of =:= (called =::=) we erase all instantiations and definitions of =::= */ object WithNormalState { @@ -11,9 +12,9 @@ object WithNormalState { object Instance { def newInstance(): Instance[Off] = new Instance[Off] } - class Instance[S <: State] private { - def getOnInstance (using erased ev: S =::= Off): Instance[On] = new Instance[On] // phantom parameter ev is erased - def getOffInstance (using erased ev: S =::= On): Instance[Off] = new Instance[Off] // phantom parameter ev is erased + class Instance[S <: State] @publicInBinary private { + inline def getOnInstance (using erased ev: S =::= Off): Instance[On] = new Instance[On] // phantom parameter ev is erased + inline def getOffInstance (using erased ev: S =::= On): Instance[Off] = new Instance[Off] // phantom parameter ev is erased } def run() = { @@ -26,5 +27,5 @@ object WithNormalState { object Utils { type =::=[From, To] - erased given tpEquals: [A] => (A =::= A) = compiletime.erasedValue +inline given tpEquals: [A] => (A =::= A) = compiletime.erasedValue } diff --git a/tests/pos/poly-erased-functions.scala b/tests/pos/poly-erased-functions.scala index 8c7385edb86a..50ba245e782c 100644 --- a/tests/pos/poly-erased-functions.scala +++ b/tests/pos/poly-erased-functions.scala @@ -7,7 +7,7 @@ object Test: val t1 = [X] => (erased x: X, y: Int) => y val t2 = [X] => (x: X, erased y: Int) => x - erased class A + class A extends compiletime.Erased type T3 = [X] => (x: A, y: X) => X diff --git a/tests/pos/printbounds.scala b/tests/pos/printbounds.scala index a3db104f4a33..87e74da90dc9 100644 --- a/tests/pos/printbounds.scala +++ b/tests/pos/printbounds.scala @@ -5,7 +5,7 @@ class Test { val x: Tree[_] = ??? - val y = x // With -Xprint:typer this should print val x: Tree[_] = x + val y = x // With -Vprint:typer this should print val x: Tree[_] = x // used to print Tree[Nothing], which is confusing. } diff --git a/tests/pos/simple-tuple-extract.scala b/tests/pos/simple-tuple-extract.scala new file mode 100644 index 000000000000..736deb7ada3a --- /dev/null +++ b/tests/pos/simple-tuple-extract.scala @@ -0,0 +1,43 @@ + +class Test: + def f1: (Int, String, AnyRef) = (1, "2", "3") + def f2: (x: Int, y: String) = (0, "y") + + def test1 = + val (a, b, c) = f1 + // Desugared to: + // val $2$: (Int, String, AnyRef) = + // this.f1:(Int, String, AnyRef) @unchecked match + // { + // case $1$ @ Tuple3.unapply[Int, String, Object](_, _, _) => + // $1$:(Int, String, AnyRef) + // } + // val a: Int = $2$._1 + // val b: String = $2$._2 + // val c: AnyRef = $2$._3 + a + b.length() + c.toString.length() + + // This pattern will not be optimized: + // val (a1, b1, c1: String) = f1 + + def test2 = + val (_, b, c) = f1 + b.length() + c.toString.length() + + val (a2, _, c2) = f1 + a2 + c2.toString.length() + + val (a3, _, _) = f1 + a3 + 1 + + def test3 = + val (_, b, _) = f1 + b.length() + 1 + + def test4 = + val (x, y) = f2 + x + y.length() + + def test5 = + val (_, b) = f2 + b.length() + 1 \ No newline at end of file diff --git a/tests/pos/singleton-ctx-bound.scala b/tests/pos/singleton-ctx-bound.scala index c6b0d2fb823c..a6af8f9038ab 100644 --- a/tests/pos/singleton-ctx-bound.scala +++ b/tests/pos/singleton-ctx-bound.scala @@ -1,4 +1,4 @@ -//> using options -language:experimental.modularity -source future +//> using options -language:experimental.modularity -source future -language:experimental.erasedDefinitions object Test: class Wrap[T](x: T) @@ -11,7 +11,7 @@ object Test: val x1 = f1(1) val _: Wrap[1] = x1 - def f2[T](x: T)(using Singleton { type Self = T}): Wrap[T] = Wrap(x) + def f2[T](x: T)(using erased Singleton { type Self = T}): Wrap[T] = Wrap(x) val x2 = f2(1) val _: Wrap[1] = x2 @@ -19,7 +19,7 @@ object Test: val x3 = f3(1) val _: Wrap[1] = x3 - def f4[T](x: T)(using T is Singleton): Wrap[T] = Wrap(x) + def f4[T](x: T)(using erased T is Singleton): Wrap[T] = Wrap(x) val x4 = f4(1) val _: Wrap[1] = x4 @@ -33,7 +33,7 @@ object Test: val y1 = C1("hi") val _: "hi" = y1.fld - class C2[T](x: T)(using T is Singleton): + class C2[T](x: T)(using erased T is Singleton): def fld: T = x val y2 = C2("hi") val _: "hi" = y2.fld diff --git a/tests/pos/tailrec.scala b/tests/pos/tailrec.scala index 95e667c07515..902ccbf4e6ea 100644 --- a/tests/pos/tailrec.scala +++ b/tests/pos/tailrec.scala @@ -2,7 +2,7 @@ import scala.annotation.tailrec -erased class Foo1 +class Foo1 extends compiletime.Erased class Foo2 @tailrec diff --git a/tests/pos/test-implicits2.scala b/tests/pos/test-implicits2.scala index 6d744395902e..7849c06e6dd1 100644 --- a/tests/pos/test-implicits2.scala +++ b/tests/pos/test-implicits2.scala @@ -1,6 +1,6 @@ /* Compile with - dotc implicits2.scala -Xprint:typer -Xprint-types -verbose + dotc implicits2.scala -Vprint:typer -Xprint-types -verbose and verify that the inserted wrapString comes from Predef. You should see diff --git a/tests/pos/typeclass-scaling.scala b/tests/pos/typeclass-scaling.scala index 0db663de4989..8f2e5381c6f6 100644 --- a/tests/pos/typeclass-scaling.scala +++ b/tests/pos/typeclass-scaling.scala @@ -5,7 +5,7 @@ import scala.annotation.tailrec // The following command: // -// sc typeclass-scaling.scala -Xmax-inlines 100 -Xprint:typer -color:never -pagewidth 1000 >& x +// sc typeclass-scaling.scala -Xmax-inlines 100 -Vprint:typer -color:never -pagewidth 1000 >& x // // produces an output file with `wc` measures (lines/words/chars): // diff --git a/tests/pos/typeclasses-arrow.scala b/tests/pos/typeclasses-arrow.scala index 4b2a25122b0d..bbe9d97e1a16 100644 --- a/tests/pos/typeclasses-arrow.scala +++ b/tests/pos/typeclasses-arrow.scala @@ -50,7 +50,7 @@ object Instances extends Common: case (_, Nil) => +1 case (x :: xs1, y :: ys1) => val fst = x.compareTo(y) - if (fst != 0) fst else xs1.compareTo(ys1) + if fst != 0 then fst else xs1.compareTo(ys1) given listMonad: List is Monad: extension [A](xs: List[A]) def flatMap[B](f: A => List[B]): List[B] = diff --git a/tests/pos/typeclasses-this.scala b/tests/pos/typeclasses-this.scala index ebe0dc348a06..198fb0142a8f 100644 --- a/tests/pos/typeclasses-this.scala +++ b/tests/pos/typeclasses-this.scala @@ -51,7 +51,7 @@ object Instances extends Common: case (_, Nil) => +1 case (x :: xs1, y :: ys1) => val fst = x.compareTo(y) - if (fst != 0) fst else xs1.compareTo(ys1) + if fst != 0 then fst else xs1.compareTo(ys1) given listMonad: (List is Monad): extension [A](xs: List[A]) def flatMap[B](f: A => List[B]): List[B] = diff --git a/tests/pos/typeclasses.scala b/tests/pos/typeclasses.scala index 40f992cbcb57..c009f4b38a97 100644 --- a/tests/pos/typeclasses.scala +++ b/tests/pos/typeclasses.scala @@ -46,7 +46,7 @@ object Instances extends Common: case (_, Nil) => +1 case (x :: xs1, y :: ys1) => val fst = x.compareTo(y) - if (fst != 0) fst else xs1.compareTo(ys1) + if fst != 0 then fst else xs1.compareTo(ys1) end listOrd given listMonad: List is Monad: @@ -80,7 +80,7 @@ object Instances extends Common: xss.flatMap(identity) def maximum[T](xs: List[T])(using T is Ord): T = - xs.reduceLeft((x, y) => if (x < y) y else x) + xs.reduceLeft((x, y) => if x < y then y else x) def descending[T](using asc: T is Ord): T is Ord = new: extension (x: T) def compareTo(y: T) = asc.compareTo(y)(x) diff --git a/tests/pos/vec-access-syntax.scala b/tests/pos/vec-access-syntax.scala new file mode 100644 index 000000000000..524ede685529 --- /dev/null +++ b/tests/pos/vec-access-syntax.scala @@ -0,0 +1,23 @@ +import scala.language.postfixOps + +class Vector(values: Int*) { + val data = values.toArray + class Getter(i: Int) { + def `>_=`(x: Int) = + data(i) = x + def > : Int = + data(i) + } + def < (i:Int) = new Getter(i) + override def toString = data.mkString("<", ", ", ">") +} + +object Test { + def main(args: Array[String]): Unit = { + val v = new Vector(1, 2, 3) + println(v) // prints <1, 2, 3> + v<1> = 10 // assign 10 to element at index 1 + println(v) // prints <1, 10, 3> + println(v<1>) // prints: value at 1 is 10 + } +} diff --git a/tests/printing/annot-18064.scala b/tests/printing/annot-18064.scala index b6a67ea9ebe7..465f627ff861 100644 --- a/tests/printing/annot-18064.scala +++ b/tests/printing/annot-18064.scala @@ -1,4 +1,4 @@ -//> using options "-Xprint:typer" +//> using options "-Vprint:typer" class myAnnot[T]() extends annotation.Annotation diff --git a/tests/rewrites/i22792.check b/tests/rewrites/i22792.check new file mode 100644 index 000000000000..6bb8d5f013c7 --- /dev/null +++ b/tests/rewrites/i22792.check @@ -0,0 +1,15 @@ +//> using options -source 3.7-migration + +trait Permit +class Foo: + def run(implicit ev: Permit): Unit = ??? + def apply(implicit ev: Permit): Unit = ??? + +given Permit = ??? +@main def Test = new Foo().run + +def ctorProxy = Foo().run + +def otherSyntax = new Foo().apply // Foo().apply does not work + +def kwazySyntax = new Foo() . run // that was fun diff --git a/tests/rewrites/i22792.scala b/tests/rewrites/i22792.scala new file mode 100644 index 000000000000..c8c6c4164a0b --- /dev/null +++ b/tests/rewrites/i22792.scala @@ -0,0 +1,15 @@ +//> using options -source 3.7-migration + +trait Permit +class Foo: + def run(implicit ev: Permit): Unit = ??? + def apply(implicit ev: Permit): Unit = ??? + +given Permit = ??? +@main def Test = new Foo().run() + +def ctorProxy = Foo().run() + +def otherSyntax = new Foo()() // Foo().apply does not work + +def kwazySyntax = new Foo() . run ( /* your args here! */ ) // that was fun diff --git a/tests/rewrites/i23449.check b/tests/rewrites/i23449.check new file mode 100644 index 000000000000..4c7476003ffe --- /dev/null +++ b/tests/rewrites/i23449.check @@ -0,0 +1,14 @@ +trait T +class C[A] + +def f(x: C[? <: T]) = () + +def g(x: C[? >: T]) = () + +def h(x: C[? <: T]) = () + +def k(x: C[? >: T]) = () + +def m(x: C[? >: Nothing <: T]) = () + +def n(x: C[ ? >: Nothing <: T ]) = () diff --git a/tests/rewrites/i23449.scala b/tests/rewrites/i23449.scala new file mode 100644 index 000000000000..64a29d7ea35c --- /dev/null +++ b/tests/rewrites/i23449.scala @@ -0,0 +1,14 @@ +trait T +class C[A] + +def f(x: C[_<:T]) = () + +def g(x: C[_>:T]) = () + +def h(x: C[_<: T]) = () + +def k(x: C[_ >: T]) = () + +def m(x: C[_>:Nothing<:T]) = () + +def n(x: C[ _>:Nothing <:T ]) = () diff --git a/tests/rewrites/i24213.check b/tests/rewrites/i24213.check new file mode 100644 index 000000000000..d06d2f63c7c2 --- /dev/null +++ b/tests/rewrites/i24213.check @@ -0,0 +1,5 @@ +def Test = + try () + catch { + case x: Throwable if x.getMessage `contains` "error" => ??? + } \ No newline at end of file diff --git a/tests/rewrites/i24213.scala b/tests/rewrites/i24213.scala new file mode 100644 index 000000000000..49148abace00 --- /dev/null +++ b/tests/rewrites/i24213.scala @@ -0,0 +1,5 @@ +def Test = + try () + catch { + case x: Throwable if x.getMessage contains "error" => ??? + } \ No newline at end of file diff --git a/tests/rewrites/implicit-rewrite.check b/tests/rewrites/implicit-rewrite.check new file mode 100644 index 000000000000..47f5c6c24059 --- /dev/null +++ b/tests/rewrites/implicit-rewrite.check @@ -0,0 +1,9 @@ +//> using options source `future-migration` -rewrite + +class Ord[T] + +object Test: + + implicit def ol[T](using x: Ord[T]): Ord[List[T]] = foo[T] // error // error + + def foo[T](using x: Ord[T]): Ord[List[T]] = new Ord[List[T]]() diff --git a/tests/rewrites/implicit-rewrite.scala b/tests/rewrites/implicit-rewrite.scala new file mode 100644 index 000000000000..424300a30499 --- /dev/null +++ b/tests/rewrites/implicit-rewrite.scala @@ -0,0 +1,9 @@ +//> using options source `future-migration` -rewrite + +class Ord[T] + +object Test: + + implicit def ol[T](implicit x: Ord[T]): Ord[List[T]] = foo[T] + + def foo[T](implicit x: Ord[T]): Ord[List[T]] = new Ord[List[T]]() diff --git a/tests/rewrites/implicit-to-given.check b/tests/rewrites/implicit-to-given.check new file mode 100644 index 000000000000..15342cf4e180 --- /dev/null +++ b/tests/rewrites/implicit-to-given.check @@ -0,0 +1,23 @@ +object Def: + trait A + trait B + implicit def conv1(a: A): B = ??? // should not change + given conv2: A => B = ??? + final given nonConv: A = ??? + given conv3: A => B = ??? + final given nonConv2: A = ??? + + implicit class Extension(a: Int): // should not change + def addedMethod(): A = ??? + implicit class ExtensionWithImplicit(t: String)(using a: Int): + def addedMethod(): String = ??? + class NoNonimplicitParams()(using a: Int) + + def applicationTest(using a: Int): Unit = ??? + val application = applicationTest(using 0) + val implicitArg: Int => Unit = (implicit a => applicationTest) // should not change + + given refined: (A {type B = Int}) = ??? + + class EmptyParamListClass()(using a: Int) + def emptyParamListTest() = new EmptyParamListClass()(using 0) diff --git a/tests/rewrites/implicit-to-given.scala b/tests/rewrites/implicit-to-given.scala new file mode 100644 index 000000000000..b4ef5927ab0e --- /dev/null +++ b/tests/rewrites/implicit-to-given.scala @@ -0,0 +1,23 @@ +object Def: + trait A + trait B + implicit def conv1(a: A): B = ??? // should not change + implicit def conv2: A => B = ??? + final implicit def nonConv: A = ??? + implicit val conv3: A => B = ??? + final implicit val nonConv2: A = ??? + + implicit class Extension(a: Int): // should not change + def addedMethod(): A = ??? + implicit class ExtensionWithImplicit(t: String)(implicit a: Int): + def addedMethod(): String = ??? + class NoNonimplicitParams(implicit a: Int) + + def applicationTest(implicit a: Int): Unit = ??? + val application = applicationTest(0) + val implicitArg: Int => Unit = (implicit a => applicationTest) // should not change + + implicit def refined(): A {type B = Int} = ??? + + class EmptyParamListClass(implicit a: Int) + def emptyParamListTest() = new EmptyParamListClass()(0) diff --git a/tests/rewrites/unused.check b/tests/rewrites/unused.check index 1ff93bfb6ef2..585dc46ec39e 100644 --- a/tests/rewrites/unused.check +++ b/tests/rewrites/unused.check @@ -53,3 +53,16 @@ package p11: package p12: import java.lang.System, java.lang.Runnable class C extends Runnable { def run() = System.out.println() } + +package p13: + import java.lang.{Runnable, + + System}, System.out + class C extends Runnable { def run() = out.println() } + +package p14: + import collection.mutable + + import mutable.ListBuffer + + def buf = ListBuffer.empty[String] diff --git a/tests/rewrites/unused.scala b/tests/rewrites/unused.scala index 85a83c7c0015..866a36a7ca4b 100644 --- a/tests/rewrites/unused.scala +++ b/tests/rewrites/unused.scala @@ -59,3 +59,20 @@ package p11: package p12: import collection.mutable, java.lang.System, java.lang.Runnable class C extends Runnable { def run() = System.out.println() } + +package p13: + import java.lang.{Runnable, + + Thread, // leave one blank line instead of two + + System}, System.out + class C extends Runnable { def run() = out.println() } + +package p14: + import collection.mutable + + import java.lang.{Runnable, Thread} + + import mutable.ListBuffer + + def buf = ListBuffer.empty[String] diff --git a/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala b/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala index 0d09e5c8cb40..ea0bdc240e0c 100644 --- a/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala +++ b/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala @@ -8,6 +8,8 @@ import annotation.tailrec import caps.cap import caps.unsafe.unsafeAssumeSeparate +import language.experimental.captureChecking + /** A strawman architecture for new collections. It contains some * example collection classes and methods with the intent to expose * some key issues. It would be good to compare this to other @@ -448,7 +450,6 @@ object CollectionStrawMan5 { } case class Filter[A](val underlying: Iterable[A]^, p: A => Boolean) extends View[A] { - this: Filter[A]^{underlying, p} => def iterator: Iterator[A]^{this} = underlying.iterator.filter(p) } @@ -456,47 +457,40 @@ object CollectionStrawMan5 { def apply[A](underlying: Iterable[A]^, pp: A => Boolean, isFlipped: Boolean): Filter[A]^{underlying, pp} = underlying match case filter: Filter[A] => - new Filter(filter.underlying, a => filter.p(a) && pp(a)) - .asInstanceOf[Filter[A]^{underlying, pp}] - //unsafeAssumeSeparate: + unsafeAssumeSeparate: + new Filter(filter.underlying, a => filter.p(a) && pp(a)) + .asInstanceOf[Filter[A]^{underlying, pp}] // See filter-iterable.scala for a test where a variant of Filter // works without the unsafeAssumeSeparate. But it requires significant // changes compared to the version here. - //new Filter(filter.underlying, a => filter.p(a) && pp(a)) case _ => new Filter(underlying, pp) case class Partition[A](val underlying: Iterable[A]^, p: A => Boolean) { - self: Partition[A]^{underlying, p} => - class Partitioned(expected: Boolean) extends View[A]: - this: Partitioned^{self} => + this: Partitioned^{Partition.this} => def iterator: Iterator[A]^{this} = underlying.iterator.filter((x: A) => p(x) == expected) - val left: Partitioned^{self} = Partitioned(true) - val right: Partitioned^{self} = Partitioned(false) + val left: Partitioned^{this} = Partitioned(true) + val right: Partitioned^{this} = Partitioned(false) } case class Drop[A](underlying: Iterable[A]^, n: Int) extends View[A] { - this: Drop[A]^{underlying} => def iterator: Iterator[A]^{this} = underlying.iterator.drop(n) override def knownLength = if (underlying.knownLength >= 0) underlying.knownLength - n max 0 else -1 } case class Map[A, B](underlying: Iterable[A]^, f: A => B) extends View[B] { - this: Map[A, B]^{underlying, f} => def iterator: Iterator[B]^{this} = underlying.iterator.map(f) override def knownLength = underlying.knownLength } case class FlatMap[A, B](underlying: Iterable[A]^, f: A => IterableOnce[B]^) extends View[B] { - this: FlatMap[A, B]^{underlying, f} => def iterator: Iterator[B]^{this} = underlying.iterator.flatMap(f) } case class Concat[A](underlying: Iterable[A]^, other: IterableOnce[A]^) extends View[A] { - this: Concat[A]^{underlying, other} => def iterator: Iterator[A]^{this} = underlying.iterator ++ other override def knownLength = other match { case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => @@ -507,7 +501,6 @@ object CollectionStrawMan5 { } case class Zip[A, B](underlying: Iterable[A]^, other: IterableOnce[B]^) extends View[(A, B)] { - this: Zip[A, B]^{underlying, other} => def iterator: Iterator[(A, B)]^{this} = underlying.iterator.zip(other) override def knownLength = other match { case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => diff --git a/tests/run-custom-args/captures/minicheck.scala b/tests/run-custom-args/captures/minicheck.scala index a6aca38ae704..2824f9ff6e24 100644 --- a/tests/run-custom-args/captures/minicheck.scala +++ b/tests/run-custom-args/captures/minicheck.scala @@ -1,9 +1,7 @@ - // A mini typechecker to experiment with arena allocated contexts import compiletime.uninitialized import annotation.{experimental, tailrec, constructorOnly} import collection.mutable -import language.`3.3` case class Symbol(name: String, initOwner: Symbol | Null) extends Pure: def owner = initOwner.nn diff --git a/tests/run-macros/i12021/Test_2.scala b/tests/run-macros/i12021/Test_2.scala index a542b14f1175..437a18959785 100644 --- a/tests/run-macros/i12021/Test_2.scala +++ b/tests/run-macros/i12021/Test_2.scala @@ -1,6 +1,6 @@ import scala.language.experimental.erasedDefinitions -erased class EC +class EC extends compiletime.Erased class X1(implicit i: Int) class X2(using i: Int) diff --git a/tests/run-macros/i14902.check b/tests/run-macros/i14902.check index 9b27fcb7e5dc..5e6373a9516c 100644 --- a/tests/run-macros/i14902.check +++ b/tests/run-macros/i14902.check @@ -1,4 +1,4 @@ List(X) -List(X, Y, Z) +List(Y, Z, X) List(X) List(Y, Z) diff --git a/tests/run-macros/i22260.check b/tests/run-macros/i22260.check new file mode 100644 index 000000000000..a50c8c916b8c --- /dev/null +++ b/tests/run-macros/i22260.check @@ -0,0 +1,6 @@ +42 +42 +42 +42 +42 +42 \ No newline at end of file diff --git a/tests/run-macros/i22260/Macros_1.scala b/tests/run-macros/i22260/Macros_1.scala new file mode 100644 index 000000000000..44538bfcd521 --- /dev/null +++ b/tests/run-macros/i22260/Macros_1.scala @@ -0,0 +1,57 @@ +import scala.quoted.* + +object Macros: + inline def crMethod: Int = ${ createMethod } + inline def inMethod: Int = ${ insideMethod } + inline def usMethod: Int = ${ usingMethod } + inline def inObject: Int = ${ insideObject } + inline def inClass: Int = ${ insideClass } + inline def usClass: Int = ${ usingClass } + + def summon(using Quotes) = + Expr.summon[Int].getOrElse('{0}) + + def createMethod(using Quotes): Expr[Int] = + import quotes.reflect.* + val tpe = MethodType(MethodTypeKind.Contextual)("x" :: Nil)(_ => TypeRepr.of[Int] :: Nil, _ => TypeRepr.of[Int]) + val sym = Symbol.newMethod(Symbol.spliceOwner, "foo", tpe) + val method = DefDef(sym, _ => Some(summon(using sym.asQuotes).asTerm)) + Block(method :: Nil, Apply(Ref(sym), '{42}.asTerm :: Nil)).asExprOf[Int] + + def insideMethod(using Quotes): Expr[Int] = '{ + def foo = + given Int = 42 + ${summon} + + foo + } + + def usingMethod(using Quotes): Expr[Int] = '{ + def foo(using Int) = + ${summon} + + foo(using 42) + } + + def insideObject(using Quotes): Expr[Int] = '{ + object Foo: + given Int = 42 + val x = ${summon} + + Foo.x + } + + def insideClass(using Quotes): Expr[Int] = '{ + class Foo: + given Int = 42 + val x = ${summon} + + new Foo().x + } + + def usingClass(using Quotes): Expr[Int] = '{ + class Foo(using Int): + val x = ${summon} + + new Foo(using 42).x + } diff --git a/tests/run-macros/i22260/Test_2.scala b/tests/run-macros/i22260/Test_2.scala new file mode 100644 index 000000000000..3f512fa6308a --- /dev/null +++ b/tests/run-macros/i22260/Test_2.scala @@ -0,0 +1,7 @@ +@main def Test = + println(Macros.crMethod) + println(Macros.inMethod) + println(Macros.usMethod) + println(Macros.inObject) + println(Macros.inClass) + println(Macros.usClass) diff --git a/tests/run-macros/i22616c.check b/tests/run-macros/i22616c.check new file mode 100644 index 000000000000..d1918272a8f7 --- /dev/null +++ b/tests/run-macros/i22616c.check @@ -0,0 +1 @@ +_B_ diff --git a/tests/run-macros/i22616c/Macro_4.scala b/tests/run-macros/i22616c/Macro_4.scala new file mode 100644 index 000000000000..02e677db3428 --- /dev/null +++ b/tests/run-macros/i22616c/Macro_4.scala @@ -0,0 +1,11 @@ +import scala.quoted.* + +object Macro: + inline def myMacro[T](): String = + ${ myMacroImpl[T]() } + + def myMacroImpl[T: Type]()(using Quotes): Expr[String] = + import quotes.reflect.* + val myTypeRepr = MyTypeRepr(TypeRepr.of[T]) + val `caseName`(name) = myTypeRepr.requiredAnnotationValue[caseName] + Expr(name) diff --git a/tests/run-macros/i22616c/MyTypeRepr_3.scala b/tests/run-macros/i22616c/MyTypeRepr_3.scala new file mode 100644 index 000000000000..1a35889d403d --- /dev/null +++ b/tests/run-macros/i22616c/MyTypeRepr_3.scala @@ -0,0 +1,33 @@ +import scala.quoted.* + +final class MyTypeRepr(using val quotes: Quotes)(val unwrap: quotes.reflect.TypeRepr) { + import quotes.reflect.* + + def getAnnotation(annotTpe: quotes.reflect.Symbol): Option[quotes.reflect.Term] = + unwrap.typeSymbol.getAnnotation(annotTpe) + + def optionalAnnotation[Annot: Type]: Option[Expr[Annot]] = { + val annotTpe = TypeRepr.of[Annot] + val annotFlags = annotTpe.typeSymbol.flags + + if (annotFlags.is(Flags.Abstract) || annotFlags.is(Flags.Trait)) + report.errorAndAbort(s"Bad annotation type ${annotTpe.show} is abstract") + + this.getAnnotation(annotTpe.typeSymbol) match + case Some(tree) if tree.tpe <:< annotTpe => Some(tree.asExprOf[Annot]) + case _ => None + } + + def requiredAnnotation[Annot: Type]: Expr[Annot] = + optionalAnnotation[Annot].getOrElse(report.errorAndAbort(s"Missing required annotation `${TypeRepr.of[Annot].show}` for `$this`")) + + def optionalAnnotationValue[Annot: {Type, FromExpr}]: Option[Annot] = + optionalAnnotation[Annot].map { expr => + expr.value.getOrElse(report.errorAndAbort(s"Found annotation `${TypeRepr.of[Annot].show}` for `$this`, but are unable to extract Expr.value\n${expr.show}")) + } + + def requiredAnnotationValue[Annot: {Type, FromExpr}]: Annot = { + val expr = requiredAnnotation[Annot] + expr.value.getOrElse(report.errorAndAbort(s"Found annotation `${TypeRepr.of[Annot].show}` for `$this`, but are unable to extract Expr.value\n${expr.show}")) + } +} diff --git a/tests/run-macros/i22616c/SealedTrait3_2.scala b/tests/run-macros/i22616c/SealedTrait3_2.scala new file mode 100644 index 000000000000..9141a9ebd08b --- /dev/null +++ b/tests/run-macros/i22616c/SealedTrait3_2.scala @@ -0,0 +1,8 @@ +sealed trait SealedTrait3[+A, +B] +object SealedTrait3 { + final case class AB1[+B, +A](a: B, b: A) extends SealedTrait3[B, A] + final case class AB2[+C, +D](a: C, b: D) extends SealedTrait3[D, C] + final case class A[+T](a: T) extends SealedTrait3[T, Nothing] + @caseName("_B_") final case class B[+T](b: T) extends SealedTrait3[Nothing, T] + case object Neither extends SealedTrait3[Nothing, Nothing] +} diff --git a/tests/run-macros/i22616c/Test_5.scala b/tests/run-macros/i22616c/Test_5.scala new file mode 100644 index 000000000000..c8d177a5ae1b --- /dev/null +++ b/tests/run-macros/i22616c/Test_5.scala @@ -0,0 +1,2 @@ +@main def Test = + println(Macro.myMacro[SealedTrait3.B[Any]]()) diff --git a/tests/run-macros/i22616c/caseName_1.scala b/tests/run-macros/i22616c/caseName_1.scala new file mode 100644 index 000000000000..c8fee116e2e9 --- /dev/null +++ b/tests/run-macros/i22616c/caseName_1.scala @@ -0,0 +1,14 @@ +import scala.quoted.* + +final case class caseName(name: String) extends scala.annotation.Annotation +object caseName { + // This demonstrates a workaround for issue #22616. + given FromExpr[caseName] = + new FromExpr[caseName] { + override def unapply(x: Expr[caseName])(using Quotes): Option[caseName] = + x match { + case '{ new `caseName`(${ Expr(name) }) } => Some(caseName(name)) + case _ => println(x.show); None + } + } +} diff --git a/tests/run-macros/type-members.check b/tests/run-macros/type-members.check new file mode 100644 index 000000000000..6805e6158e8e --- /dev/null +++ b/tests/run-macros/type-members.check @@ -0,0 +1,8 @@ +class FooSmall: List(type A, type B, type C, type D) +class FooLarge: List(type A, type B, type C, type D, type E) +type FooUnion: List() +type FooAnd: List(type A, type B, type C, type D, type E) +trait CLS1: List(type A, type B, type C, type B1, type B2, type A3, type B3, type B4) +type SharedAnd1: List(type B, type Shared, type A, type C) +type SharedAnd2: List(type C, type Shared, type A, type B) +type SharedUnion: List(type A, type Shared) diff --git a/tests/run-macros/type-members/Macro_1.scala b/tests/run-macros/type-members/Macro_1.scala new file mode 100644 index 000000000000..1aff04803798 --- /dev/null +++ b/tests/run-macros/type-members/Macro_1.scala @@ -0,0 +1,12 @@ +package example + +import scala.quoted.* + +object Macro { + inline def typeMembers[T <: AnyKind]: String = ${ typeMembersImpl[T] } + + def typeMembersImpl[T <: AnyKind: Type](using quotes: Quotes): Expr[String] = { + import quotes.reflect.* + Expr(s"${TypeRepr.of[T].typeSymbol}: ${TypeRepr.of[T].typeSymbol.typeMembers.toString}") + } +} diff --git a/tests/run-macros/type-members/Test_2.scala b/tests/run-macros/type-members/Test_2.scala new file mode 100644 index 000000000000..506e67068492 --- /dev/null +++ b/tests/run-macros/type-members/Test_2.scala @@ -0,0 +1,33 @@ +import example.Macro + +class FooSmall[A, B] { type D; type C } +class FooLarge[A, B, C] { type E; type D } + +type FooUnion[A, B] = FooSmall[A, B] | FooLarge[A, B, Int] +type FooAnd[A, B] = FooSmall[A, B] & FooLarge[A, B, Int] + +trait CLS4[A] { type B4 } +trait CLS3[A] extends CLS4[A] { type B3; type A3 } +trait CLS2[A] { type B2 } +trait CLS1[A, B, C] extends CLS2[A] with CLS3[B] { type B1 } + +trait SharedParent[A] { type Shared } +trait SharedA[A] extends SharedParent[A] { type B } +trait SharedB[A] extends SharedParent[A] { type C } +type SharedAnd1[A] = SharedA[A] & SharedB[A] +type SharedAnd2[A] = SharedB[A] & SharedA[A] +type SharedUnion[A] = SharedA[A] | SharedB[A] + +@main def Test(): Unit = { + println(Macro.typeMembers[FooSmall]) + println(Macro.typeMembers[FooLarge]) + + println(Macro.typeMembers[FooUnion]) + println(Macro.typeMembers[FooAnd]) + + println(Macro.typeMembers[CLS1]) + + println(Macro.typeMembers[SharedAnd1]) + println(Macro.typeMembers[SharedAnd2]) + println(Macro.typeMembers[SharedUnion]) +} diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index 36075f0a2cee..f95c25635d9f 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -30,16 +30,17 @@ val experimentalDefinitionInLibrary = Set( "scala.annotation.retains", "scala.annotation.retainsByName", "scala.annotation.retainsCap", - "scala.annotation.retainsArg", "scala.Pure", "scala.caps.CapSet", "scala.caps.Capability", + "scala.caps.Classifier", "scala.caps.Contains", "scala.caps.Contains$", "scala.caps.Contains$.containsImpl", "scala.caps.Exists", "scala.caps.Mutable", - "scala.caps.SharedCapability", + "scala.caps.Sharable", + "scala.caps.Control", "scala.caps.consume", "scala.caps.internal", "scala.caps.internal$", @@ -50,7 +51,8 @@ val experimentalDefinitionInLibrary = Set( "scala.caps.use", //// New feature: into - "scala.annotation.into", + "scala.Conversion$.into", + "scala.Conversion$.underlying", "scala.annotation.internal.$into", //// New feature: Macro annotations @@ -105,7 +107,10 @@ val experimentalDefinitionInLibrary = Set( "scala.Predef$.runtimeChecked", "scala.annotation.internal.RuntimeChecked", // New feature: SIP 61 - @unroll annotation - "scala.annotation.unroll" + "scala.annotation.unroll", + + // New feature: Erased trait + "scala.compiletime.Erased", ) diff --git a/tests/run/Parser.scala b/tests/run/Parser.scala index 48c3af73ecec..33b3379d2c76 100644 --- a/tests/run/Parser.scala +++ b/tests/run/Parser.scala @@ -1,4 +1,5 @@ import language.experimental.into +import Conversion.into type Input = List[String] @@ -12,7 +13,7 @@ def empty[T](x: T) = Parser(in => Success(x, in)) def fail(msg: String) = Parser(in => Failure(msg)) class ParserOps[T](p: Parser[T]): - def ~ [U](q: => into Parser[U]): Parser[(T, U)] = Parser(in => + def ~ [U](q: => into[Parser[U]]): Parser[(T, U)] = Parser(in => p.parse(in) match case Success(x, in1) => q.parse(in1) match @@ -20,7 +21,7 @@ class ParserOps[T](p: Parser[T]): case fail: Failure => fail case fail: Failure => fail ) - def | [U](q: => into Parser[T]): Parser[T] = Parser(in => + def | [U](q: => into[Parser[T]]): Parser[T] = Parser(in => p.parse(in) match case s: Success[_] => s case fail: Failure => q.parse(in) @@ -30,9 +31,9 @@ class ParserOps[T](p: Parser[T]): case Success(x, in1) => Success(f(x), in1) case fail: Failure => fail ) - def ~> [U](q: => into Parser[U]): Parser[U] = + def ~> [U](q: => into[Parser[U]]): Parser[U] = (p ~ q).map(_(1)) - def <~ [U](q: => into Parser[U]): Parser[T] = + def <~ [U](q: => into[Parser[U]]): Parser[T] = (p ~ q).map(_(0)) def parseAll(in: Input): ParseResult[T] = p.parse(in) match @@ -64,10 +65,10 @@ def token(p: String => Boolean, expected: String): Parser[String] = Parser { def token(str: String): Parser[String] = token(str == _, s"`$str`") -def opt[T](p: into Parser[T]): Parser[Option[T]] = +def opt[T](p: into[Parser[T]]): Parser[Option[T]] = p.map(Some(_)) | empty(None) -def rep[T](p: into Parser[T]): Parser[List[T]] = +def rep[T](p: into[Parser[T]]): Parser[List[T]] = (p ~ rep(p)).map(_ :: _) | empty(Nil) diff --git a/tests/run/bind-tuple-pattern.scala b/tests/run/bind-tuple-pattern.scala new file mode 100644 index 000000000000..e4d425fabe2c --- /dev/null +++ b/tests/run/bind-tuple-pattern.scala @@ -0,0 +1,19 @@ +import annotation.experimental + +def getNamedTuple: (x: Int, y: String) = (x = 42, y = "Hello") + +@main def Test = + getNamedTuple match + case (x, y) => assert(x == 42 && y == "Hello") + + getNamedTuple match + case t @ (x = a, y = b) => + // t binds to a named tuple pattern + // t: (x: Int, y: String) + assert(a == t.x && b == t.y) + + getNamedTuple match + case t @ (a, b) => + // t binds to a regular tuple pattern + // t: (Int, String) + assert(t._1 == a && t._2 == b) \ No newline at end of file diff --git a/tests/run/convertible.scala b/tests/run/convertible.scala index 7a92964a1f31..affb0698ed70 100644 --- a/tests/run/convertible.scala +++ b/tests/run/convertible.scala @@ -1,6 +1,7 @@ //> using options -feature -Xfatal-warnings import language.experimental.into +import Conversion.into class Text(val str: String) @@ -8,20 +9,20 @@ given Conversion[String, Text] = Text(_) @main def Test = - def f(xxx: into Text, yyy: => into Text, zs: (into Text)*) = + def f(xxx: into[Text], yyy: => into[Text], zs: into[Text]*) = println(s"${xxx.str} ${yyy.str} ${zs.map(_.str).mkString(" ")}") f("abc", "def") // ok f("abc", "def", "xyz", "uvw") // ok f("abc", "def", "xyz", Text("uvw")) // ok - def g(x: () => into Text) = + def g(x: () => into[Text]) = println(x().str) g(() => "hi") trait C[X]: - def f(x: into X) = x + def f(x: into[X]) = x class D[X] extends C[X] diff --git a/tests/run/erased-18.scala b/tests/run/erased-18.scala index 46f7e44c7309..2e5275690ea2 100644 --- a/tests/run/erased-18.scala +++ b/tests/run/erased-18.scala @@ -11,8 +11,8 @@ object Test { )(foo) } - def foo = { - println("foo") + inline def foo = { + //println("foo") 42 } } diff --git a/tests/run/erased-machine-state.check b/tests/run/erased-machine-state.check index f9d7929a8fc9..730786f063a9 100644 --- a/tests/run/erased-machine-state.check +++ b/tests/run/erased-machine-state.check @@ -1,4 +1,3 @@ -newMachine -turnedOn turnedOn turnedOff +turnedOn diff --git a/tests/run/erased-machine-state.scala b/tests/run/erased-machine-state.scala index c84f1619366d..17bbef55b753 100644 --- a/tests/run/erased-machine-state.scala +++ b/tests/run/erased-machine-state.scala @@ -1,4 +1,4 @@ -//> using options -language:experimental.erasedDefinitions +import language.experimental.erasedDefinitions import scala.annotation.implicitNotFound @@ -8,52 +8,31 @@ final class Off extends State @implicitNotFound("State must be Off") class IsOff[S <: State] -object IsOff { - implicit def isOff: IsOff[Off] = { - println("isOff") - new IsOff[Off] - } -} +object IsOff: + inline given IsOff[Off]() @implicitNotFound("State must be On") class IsOn[S <: State] -object IsOn { - implicit def isOn: IsOn[On] = { - println("isOn") - new IsOn[On] - } -} - -class Machine[S <: State] private { - def turnedOn (using erased s: IsOff[S]): Machine[On] = { +object IsOn: + inline given IsOn[On]() + +class Machine[S <: State]: + def turnOn(using erased IsOff[S]): Machine[On] = println("turnedOn") new Machine[On] - } - def turnedOff (using erased s: IsOn[S]): Machine[Off] = { + + def turnOff (using erased IsOn[S]): Machine[Off] = println("turnedOff") new Machine[Off] - } -} -object Machine { - def newMachine(): Machine[Off] = { - println("newMachine") - new Machine[Off] - } -} - -object Test { - def main(args: Array[String]): Unit = { - val m = Machine.newMachine() - m.turnedOn - m.turnedOn.turnedOff - - // m.turnedOff - // ^ - // State must be On - - // m.turnedOn.turnedOn - // ^ - // State must be Off - } -} + +@main def Test = + val m = Machine[Off]() + val m1 = m.turnOn + val m2 = m1.turnOff + m2.turnOn + + // m1.turnOn + // ^ error: State must be Off + // m2.turnOff + // ^ error: State must be On diff --git a/tests/run/erased-poly-ref.scala b/tests/run/erased-poly-ref.scala index 59badb71255d..975a576cc15b 100644 --- a/tests/run/erased-poly-ref.scala +++ b/tests/run/erased-poly-ref.scala @@ -8,10 +8,9 @@ object Test { def fun(erased a: Int): Unit = println("fun") - def foo[P](erased x: Int)(erased y: Int): Int = 0 + inline def foo[P](erased x: Int)(erased y: Int): Int = 0 - def bar(x: Int) = { - println(x) + inline def bar(x: Int) = { x } } diff --git a/tests/run/i11996.scala b/tests/run/i11996.scala index 9724e12b575e..a4318ace6c86 100644 --- a/tests/run/i11996.scala +++ b/tests/run/i11996.scala @@ -3,8 +3,8 @@ final class UnivEq[A] object UnivEq: - erased def force[A]: UnivEq[A] = - compiletime.erasedValue + inline def force[A]: UnivEq[A] = + caps.unsafe.unsafeErasedValue extension [A](a: A) inline def ==*[B >: A](b: B)(using erased UnivEq[B]): Boolean = a == b diff --git a/tests/run/i13691.scala b/tests/run/i13691.scala index 224656d87923..04f953d2da6b 100644 --- a/tests/run/i13691.scala +++ b/tests/run/i13691.scala @@ -1,7 +1,7 @@ import language.experimental.erasedDefinitions -erased class CanThrow[-E <: Exception] -erased class Foo +class CanThrow[-E <: Exception] extends compiletime.Erased +class Foo extends compiletime.Erased class Bar object unsafeExceptions: diff --git a/tests/run/i16943.scala b/tests/run/i16943.scala index 68e1f8fb5aa3..697e9a2f38b7 100644 --- a/tests/run/i16943.scala +++ b/tests/run/i16943.scala @@ -1,6 +1,6 @@ @main @annotation.experimental -def Test(): Unit = fail(compiletime.erasedValue, 1) +def Test(): Unit = fail(caps.unsafe.unsafeErasedValue, 1) @annotation.experimental def fail(dumb: CanThrow[Exception], x: Int) = println(x) diff --git a/tests/run/i20225.check b/tests/run/i20225.check new file mode 100644 index 000000000000..0f06315755de --- /dev/null +++ b/tests/run/i20225.check @@ -0,0 +1 @@ +unreachable case reached diff --git a/tests/run/i20225.scala b/tests/run/i20225.scala new file mode 100644 index 000000000000..ad9babf832f7 --- /dev/null +++ b/tests/run/i20225.scala @@ -0,0 +1,12 @@ +sealed abstract class Parent +class A extends Parent +class B extends Parent + +inline def matchAs[T <: Parent](p: Parent): Unit = p match + case _: T => () + case _ => println("unreachable case reached") + +object Test: + def main(args: Array[String]): Unit = + matchAs[A](new B) + diff --git a/tests/run/i20395.check b/tests/run/i20395.check new file mode 100644 index 000000000000..60653bdaa7f2 --- /dev/null +++ b/tests/run/i20395.check @@ -0,0 +1 @@ +not match diff --git a/tests/run/i20395.scala b/tests/run/i20395.scala new file mode 100644 index 000000000000..e432ea4bca6b --- /dev/null +++ b/tests/run/i20395.scala @@ -0,0 +1,13 @@ +sealed trait NodeId +case object Hi extends NodeId +case object Hello extends NodeId + +extension (value: NodeId) + inline def parse[T <: NodeId]: Unit = value match + case _: T => println("match") + case _ => println("not match") + +object Test: + def main(args: Array[String]): Unit = + Hi.parse[Hello.type] + diff --git a/tests/run/i22345.scala b/tests/run/i22345.scala new file mode 100644 index 000000000000..86cc3a01930e --- /dev/null +++ b/tests/run/i22345.scala @@ -0,0 +1,2 @@ +@main def Test: Unit = + val a: Array[(Int, String)] = Array[Int *: String *: EmptyTuple]() diff --git a/tests/run/i22345b.scala b/tests/run/i22345b.scala new file mode 100644 index 000000000000..a331a66ea80a --- /dev/null +++ b/tests/run/i22345b.scala @@ -0,0 +1,2 @@ +@main def Test: Unit = + val a: Array[(Int, String)] = Array[Int *: String *: EmptyTuple]((1, "hello")) diff --git a/tests/run/i22345c.scala b/tests/run/i22345c.scala new file mode 100644 index 000000000000..25bafae0c390 --- /dev/null +++ b/tests/run/i22345c.scala @@ -0,0 +1,4 @@ +def makeSeq[T](args: T*): Seq[T] = args + +@main def Test: Unit = + val a: Array[(Int, String)] = makeSeq[Int *: String *: EmptyTuple]().toArray diff --git a/tests/run/i22649/Macro_1.scala b/tests/run/i22649/Macro_1.scala new file mode 100644 index 000000000000..14b94f1e7f5f --- /dev/null +++ b/tests/run/i22649/Macro_1.scala @@ -0,0 +1,9 @@ +import scala.quoted.* + +inline def structureOf[T]: String = + ${ structureOfImpl[T] } + +private def structureOfImpl[T](using Quotes, Type[T]): Expr[String] = + import quotes.reflect.* + val str = Printer.TypeReprStructure.show(TypeRepr.of[T]) + Expr(str) diff --git a/tests/run/i22649/Test_2.scala b/tests/run/i22649/Test_2.scala new file mode 100644 index 000000000000..16347ae66140 --- /dev/null +++ b/tests/run/i22649/Test_2.scala @@ -0,0 +1,11 @@ +@main def Test = + val str = + structureOf[{ + type T + def make: T + }] + + assert( + str == """RecursiveType(rec1 => Refinement(Refinement(TypeRef(ThisType(TypeRef(NoPrefix(), "lang")), "Object"), "T", TypeBounds(TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Nothing"), TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Any"))), "make", ByNameType(TypeRef(RecursiveThis(), "T"))))""", + s"Was $str" + ) diff --git a/tests/run/i23131.scala b/tests/run/i23131.scala new file mode 100644 index 000000000000..2fdee0d9a618 --- /dev/null +++ b/tests/run/i23131.scala @@ -0,0 +1,6 @@ +import scala.NamedTuple +@main +def Test = + Some((name = "Bob")) match { + case Some(name = a) => println(a) + } \ No newline at end of file diff --git a/tests/run/i23279.scala b/tests/run/i23279.scala new file mode 100644 index 000000000000..8774e5afcd79 --- /dev/null +++ b/tests/run/i23279.scala @@ -0,0 +1,28 @@ +inline def simpleInlineWrap(f: => Any): Unit = f + +@main def Test(): Unit = { + simpleInlineWrap { + object lifecycle { + object Lifecycle { + trait FromZIO + } + } + object defn { + val Lifecycle: lifecycle.Lifecycle.type = lifecycle.Lifecycle + } + val xa: defn.Lifecycle.type = defn.Lifecycle + } + + // more nested case + simpleInlineWrap { + object lifecycle { + object Lifecycle { + object FromZIO + } + } + object defn { + val Lifecycle: lifecycle.Lifecycle.type = lifecycle.Lifecycle + } + val xa: defn.Lifecycle.FromZIO.type = defn.Lifecycle.FromZIO + } +} diff --git a/tests/run/i23305.scala b/tests/run/i23305.scala new file mode 100644 index 000000000000..862aed9d3362 --- /dev/null +++ b/tests/run/i23305.scala @@ -0,0 +1,24 @@ +//> using options -language:experimental.erasedDefinitions + +trait DBMeta[A] extends compiletime.Erased + +trait Table[A] + +case class Saved[A]() +object Saved { + def table[A](using dbm: DBMeta[A]): Table[Saved[A]] = new Table[Saved[A]] {} +} + +case class deleteBulk[A](as: List[Saved[A]])(using dbm: DBMeta[A], tbl: Table[A]) { + final given savedTable: Table[Saved[A]] = Saved.table[A] +} + +case class Foo() +object Foo { + given dbMeta: DBMeta[Foo] = new DBMeta[Foo] {} + given table: Table[Foo] = new Table[Foo] {} +} + +@main def Test = + val del = deleteBulk(List(Saved[Foo]())) + del.savedTable diff --git a/tests/run/i23440.scala b/tests/run/i23440.scala new file mode 100644 index 000000000000..58885bd3bf05 --- /dev/null +++ b/tests/run/i23440.scala @@ -0,0 +1,12 @@ +@main def Test: Unit = + List((42, "asdads")) match + case List((a, b)) => 1 + List((a = 42, b = "asdads")) match + case List((a, b)) => 1 + val tuple_list = List((42, "asdads")) + tuple_list.map((a, b) => println(s"$a $b")) + val named_tuple_list = List((a = 42, b = "asdads")) + named_tuple_list.foreach((a, b) => println(s"$a $b")) + named_tuple_list.foreach { case (a, b) => println(s"$a $b") } + val l = Seq.empty[(name: String, age: Int)] + l.map((name, i) => name + i) \ No newline at end of file diff --git a/tests/run/i23444.scala b/tests/run/i23444.scala new file mode 100644 index 000000000000..ada7545e077f --- /dev/null +++ b/tests/run/i23444.scala @@ -0,0 +1,22 @@ + +import annotation.* + +class Path(action: () => Unit, parent: Option[Path]): + object O: + @tailrec + def apply(): Unit = + action() + + parent match + case Some(p) => + p.O.apply() + case None => + +@main def Test: Unit = + var counter = 0 + val fun = () => { + counter += 1 + if counter > 2 then throw AssertionError("bad loop") + } + val path = Path(fun, Some(Path(fun, None))) + path.O() diff --git a/tests/run/i23693.scala b/tests/run/i23693.scala new file mode 100644 index 000000000000..c77959d99263 --- /dev/null +++ b/tests/run/i23693.scala @@ -0,0 +1,22 @@ +//> using options -Wtostring-interpolated + +// verify ~warning messages and~ runtime result +// never mind, the test rig doesn't log diagnostics! unlike beloved partest. + +// Sadly, junit is not available. +//import org.junit.Assert.assertEquals as jassert + +def assertEquals(expected: String)(actual: String): Unit = assert(expected == actual) + +case class K(i: Int) + +@main def Test = + val k = K(42) + assertEquals("k == K(42)"): + s"k == $k" + assertEquals("\\k == \\K(42)"): + raw"\k == \$k" + assertEquals("k == K(42)"): + f"k == $k" + assertEquals("k == K(42)"): + f"k == $k%s" diff --git a/tests/run/i23776.check b/tests/run/i23776.check new file mode 100644 index 000000000000..f2b2a017d25a --- /dev/null +++ b/tests/run/i23776.check @@ -0,0 +1 @@ +a = 0, b = 1, c = 2 diff --git a/tests/run/i23776.scala b/tests/run/i23776.scala new file mode 100644 index 000000000000..9d4a98144f2e --- /dev/null +++ b/tests/run/i23776.scala @@ -0,0 +1,7 @@ +inline def f(t0: Int, t1: Int, t2: Int) = { + inline (t0, t1, t2) match { + case (a: Int, b: Int, c: Int) => println(s"a = $a, b = $b, c = $c") + } +} + +@main def Test = f(0, 1, 2) diff --git a/tests/run/i23875.check b/tests/run/i23875.check new file mode 100644 index 000000000000..36bc6136b8bf --- /dev/null +++ b/tests/run/i23875.check @@ -0,0 +1,3 @@ +true +false +false diff --git a/tests/run/i23875.scala b/tests/run/i23875.scala new file mode 100644 index 000000000000..6993fb39f50b --- /dev/null +++ b/tests/run/i23875.scala @@ -0,0 +1,11 @@ +object Foo { + override def equals(that : Any) = that match { + case _: this.type => true + case _ => false + } +} + +@main def Test = + println(Foo.equals(Foo)) + println(Foo.equals(new AnyRef {})) + println(Foo.equals(0)) diff --git a/tests/run/i23901.scala b/tests/run/i23901.scala new file mode 100644 index 000000000000..7bfcfff23551 --- /dev/null +++ b/tests/run/i23901.scala @@ -0,0 +1,8 @@ +import scala.reflect.ClassTag + +object MyArray: + def empty[T: ClassTag]: Array[Array[T]] = new Array[Array[T]](0) + +@main def Test = + val arr: Array[Array[String]] = MyArray.empty[String] + assert(arr.length == 0) diff --git a/tests/run/inline-numeric/test.scala b/tests/run/inline-numeric/test.scala index 9ca88aee0374..b77e8f3a4266 100644 --- a/tests/run/inline-numeric/test.scala +++ b/tests/run/inline-numeric/test.scala @@ -29,7 +29,7 @@ object tests: def a: Int = 0 def b: Int = 1 - val v1 = foo(a, b) // should be a + b * b // can check with -Xprint:inlining + val v1 = foo(a, b) // should be a + b * b // can check with -Vprint:inlining val v2 = foo(a.toShort, b.toShort) // should be a + b * b val v3 = div(BigDecimal(a), BigDecimal(b))(using BigDecimalAsIfIntegral) // should be BigDecimal(a) quot BigDecimal(b) remainder BigDecimal(b) diff --git a/tests/run/quotes-reflection/Test_2.scala b/tests/run/quotes-reflection/Test_2.scala index ce1cc8d3dff1..4ad0b17da9fa 100644 --- a/tests/run/quotes-reflection/Test_2.scala +++ b/tests/run/quotes-reflection/Test_2.scala @@ -1,6 +1,6 @@ import scala.language.experimental.erasedDefinitions -erased class EC +class EC extends compiletime.Erased trait X { def m1(using i: Int): Int diff --git a/tests/run/safeThrowsStrawman.scala b/tests/run/safeThrowsStrawman.scala index 973c9d8f5137..dc8da2f828c3 100644 --- a/tests/run/safeThrowsStrawman.scala +++ b/tests/run/safeThrowsStrawman.scala @@ -1,7 +1,7 @@ import language.experimental.erasedDefinitions object scalax: - erased class CanThrow[-E <: Exception] + class CanThrow[-E <: Exception] extends compiletime.Erased infix type raises[R, +E <: Exception] = CanThrow[E] ?=> R diff --git a/tests/run/safeThrowsStrawman2.scala b/tests/run/safeThrowsStrawman2.scala index 1c84d84babc7..4de8f9bc7e2b 100644 --- a/tests/run/safeThrowsStrawman2.scala +++ b/tests/run/safeThrowsStrawman2.scala @@ -1,7 +1,7 @@ import language.experimental.erasedDefinitions object scalax: - erased class CanThrow[-E <: Exception] + class CanThrow[-E <: Exception] extends compiletime.Erased infix type raises[R, +E <: Exception] = CanThrow[E] ?=> R diff --git a/tests/run/stable-enum-hashcodes.check b/tests/run/stable-enum-hashcodes.check new file mode 100644 index 000000000000..2a3cb493c4c9 --- /dev/null +++ b/tests/run/stable-enum-hashcodes.check @@ -0,0 +1,12 @@ +65 +65 +66 +66 +67 +67 +68 +68 +-1449359058 +-1449359058 +194551161 +194551161 diff --git a/tests/run/stable-enum-hashcodes.scala b/tests/run/stable-enum-hashcodes.scala new file mode 100644 index 000000000000..60b5af11e437 --- /dev/null +++ b/tests/run/stable-enum-hashcodes.scala @@ -0,0 +1,23 @@ +enum Enum: + case A + case B + case C() + case D() + case E(x: Int) + +@main def Test = + // Enum values (were not stable from run to run before #23218) + println(Enum.A.hashCode) + println(Enum.A.hashCode) + println(Enum.B.hashCode) + println(Enum.B.hashCode) + + // Other enum cases (were already stable from run to run) + println(Enum.C().hashCode) + println(Enum.C().hashCode) + println(Enum.D().hashCode) + println(Enum.D().hashCode) + println(Enum.E(1).hashCode) + println(Enum.E(1).hashCode) + println(Enum.E(2).hashCode) + println(Enum.E(2).hashCode) diff --git a/tests/semanticdb/expect/InfoMacro.expect.scala b/tests/semanticdb/expect/InfoMacro.expect.scala new file mode 100644 index 000000000000..67122d6cbf37 --- /dev/null +++ b/tests/semanticdb/expect/InfoMacro.expect.scala @@ -0,0 +1,14 @@ +import scala.quoted.* + +object InfoMacro/*<-_empty_::InfoMacro.*/ { + inline def reportInfo/*<-_empty_::InfoMacro.reportInfo().*/(msg/*<-_empty_::InfoMacro.reportInfo().(msg)*/: String/*->scala::Predef.String#*/): Unit/*->scala::Unit#*/ = ${ reportInfoMacro/*->_empty_::InfoMacro.reportInfoMacro().*/('msg) } + + def reportInfoMacro/*<-_empty_::InfoMacro.reportInfoMacro().*/(msg/*<-_empty_::InfoMacro.reportInfoMacro().(msg)*/: Expr/*->scala::quoted::Expr#*/[String/*->scala::Predef.String#*/])(using Quotes/*->scala::quoted::Quotes#*/): Expr/*->scala::quoted::Expr#*/[Unit/*->scala::Unit#*/] = { + import quotes/*->scala::quoted::Quotes$package.quotes().*/.reflect/*->scala::quoted::Quotes#reflect.*/.report/*->scala::quoted::Quotes#reflectModule#report.*/ + + // Report an info diagnostic + report/*->scala::quoted::Quotes#reflectModule#report.*/.info/*->scala::quoted::Quotes#reflectModule#reportModule#info().*/(s/*->scala::StringContext#s().*/"Info from macro: ${msg/*->_empty_::InfoMacro.reportInfoMacro().(msg)*/.valueOrAbort/*->scala::quoted::Quotes#valueOrAbort().*/}") + + '{ () } + } +} diff --git a/tests/semanticdb/expect/InfoMacro.scala b/tests/semanticdb/expect/InfoMacro.scala new file mode 100644 index 000000000000..9248d51dd03a --- /dev/null +++ b/tests/semanticdb/expect/InfoMacro.scala @@ -0,0 +1,14 @@ +import scala.quoted.* + +object InfoMacro { + inline def reportInfo(msg: String): Unit = ${ reportInfoMacro('msg) } + + def reportInfoMacro(msg: Expr[String])(using Quotes): Expr[Unit] = { + import quotes.reflect.report + + // Report an info diagnostic + report.info(s"Info from macro: ${msg.valueOrAbort}") + + '{ () } + } +} diff --git a/tests/semanticdb/expect/InfoMacroTest.expect.scala b/tests/semanticdb/expect/InfoMacroTest.expect.scala new file mode 100644 index 000000000000..19977da0c79f --- /dev/null +++ b/tests/semanticdb/expect/InfoMacroTest.expect.scala @@ -0,0 +1,7 @@ + +object InfoMacroTest/*<-_empty_::InfoMacroTest.*/ { + def main/*<-_empty_::InfoMacroTest.main().*/(): Unit/*->scala::Unit#*/ = { + InfoMacro/*->_empty_::InfoMacro.*/.reportInfo/*->_empty_::InfoMacro.reportInfo().*/("This is a test info message") + InfoMacro/*->_empty_::InfoMacro.*/.reportInfo/*->_empty_::InfoMacro.reportInfo().*/("Another info message") + } +} diff --git a/tests/semanticdb/expect/InfoMacroTest.scala b/tests/semanticdb/expect/InfoMacroTest.scala new file mode 100644 index 000000000000..e53a840efc8a --- /dev/null +++ b/tests/semanticdb/expect/InfoMacroTest.scala @@ -0,0 +1,7 @@ + +object InfoMacroTest { + def main(): Unit = { + InfoMacro.reportInfo("This is a test info message") + InfoMacro.reportInfo("Another info message") + } +} diff --git a/tests/semanticdb/expect/ShadowedParameters.expect.scala b/tests/semanticdb/expect/ShadowedParameters.expect.scala new file mode 100644 index 000000000000..10cc77d08c44 --- /dev/null +++ b/tests/semanticdb/expect/ShadowedParameters.expect.scala @@ -0,0 +1,27 @@ +package example + +class Shadow/*<-example::Shadow#*/{ + + def shadowParam/*<-example::Shadow#shadowParam().*/(x/*<-example::Shadow#shadowParam().(x)*/: Int/*->scala::Int#*/)= { + val x/*<-local0*/ = true + } + + def curriedParams/*<-example::Shadow#curriedParams().*/(x/*<-example::Shadow#curriedParams().(x)*/: Int/*->scala::Int#*/)(y/*<-example::Shadow#curriedParams().(y)*/: List/*->scala::package.List#*/[Int/*->scala::Int#*/]) = { + val x/*<-local1*/ = "shadow" + val y/*<-local2*/ = 1 + } + + + def multiParams/*<-example::Shadow#multiParams().*/(x/*<-example::Shadow#multiParams().(x)*/: List/*->scala::package.List#*/[Int/*->scala::Int#*/], y/*<-example::Shadow#multiParams().(y)*/: String/*->scala::Predef.String#*/) = { + val x/*<-local3*/ = 1 + val y/*<-local4*/ = List/*->scala::package.List.*/(1,2,3) + } + + + def shadowInParamBlock/*<-example::Shadow#shadowInParamBlock().*/(x/*<-example::Shadow#shadowInParamBlock().(x)*/: Int/*->scala::Int#*/, y/*<-example::Shadow#shadowInParamBlock().(y)*/: Int/*->scala::Int#*/) = { + val y/*<-local5*/ = 1 + { + val x/*<-local6*/ = 2 + } + } +} \ No newline at end of file diff --git a/tests/semanticdb/expect/ShadowedParameters.scala b/tests/semanticdb/expect/ShadowedParameters.scala new file mode 100644 index 000000000000..3b191e1b1c3f --- /dev/null +++ b/tests/semanticdb/expect/ShadowedParameters.scala @@ -0,0 +1,27 @@ +package example + +class Shadow{ + + def shadowParam(x: Int)= { + val x = true + } + + def curriedParams(x: Int)(y: List[Int]) = { + val x = "shadow" + val y = 1 + } + + + def multiParams(x: List[Int], y: String) = { + val x = 1 + val y = List(1,2,3) + } + + + def shadowInParamBlock(x: Int, y: Int) = { + val y = 1 + { + val x = 2 + } + } +} \ No newline at end of file diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index f674c6fb4159..13f68886dd87 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -519,6 +519,7 @@ Text => empty Language => Scala Symbols => 22 entries Occurrences => 17 entries +Synthetics => 2 entries Symbols: caseclass/CaseClass# => case class CaseClass extends Object with Product with Serializable { self: CaseClass => +8 decls } @@ -563,6 +564,10 @@ Occurrences: [6:15..6:24): CaseClass -> caseclass/CaseClass# [6:27..6:36): CaseClass -> caseclass/CaseClass. +Synthetics: +[5:35..5:52):CaseClass(int, 0) => apply(*) +[6:27..6:42):CaseClass(0, 0) => apply(*) + expect/Classes.scala -------------------- @@ -574,7 +579,7 @@ Language => Scala Symbols => 108 entries Occurrences => 127 entries Diagnostics => 11 entries -Synthetics => 2 entries +Synthetics => 3 entries Symbols: classes/C1# => final class C1 extends AnyVal { self: C1 => +2 decls } @@ -837,6 +842,7 @@ This construct can be rewritten automatically under -rewrite -source 3.4-migrati Synthetics: [51:16..51:27):List(1).map => *[Int] [51:16..51:20):List => *.apply[Int] +[51:16..51:23):List(1) => List.apply[Int](*) expect/Deprecated.scala ----------------------- @@ -926,6 +932,7 @@ Language => Scala Symbols => 30 entries Occurrences => 49 entries Diagnostics => 3 entries +Synthetics => 2 entries Symbols: endmarkers/Container# => class Container extends Object { self: Container => +5 decls } @@ -1015,6 +1022,10 @@ Diagnostics: [42:8..42:16): [warning] unused local definition [46:8..46:16): [warning] unused local definition +Synthetics: +[23:6..23:13):(1,2,3) => Tuple3.apply[Int, Int, Int](*) +[27:6..27:13):(4,5,6) => Tuple3.apply[Int, Int, Int](*) + expect/EndMarkers2.scala ------------------------ @@ -1097,7 +1108,7 @@ Language => Scala Symbols => 181 entries Occurrences => 159 entries Diagnostics => 1 entries -Synthetics => 6 entries +Synthetics => 9 entries Symbols: _empty_/Enums. => final object Enums extends Object { self: Enums.type => +30 decls } @@ -1447,11 +1458,14 @@ Diagnostics: [30:12..30:17): [warning] unused explicit parameter Synthetics: +[49:27..49:33):Refl() => Refl.apply[T](*) [52:9..52:13):Refl => *.unapply[Option[B]] [52:31..52:50):identity[Option[B]] => *[Function1[A, Option[B]]] [54:14..54:18):Some => *.apply[Some[Int]] +[54:14..54:27):Some(Some(1)) => Some.apply[Some[Int]](*) [54:14..54:34):Some(Some(1)).unwrap => *(given_<:<_T_T[Option[Int]]) [54:19..54:23):Some => *.apply[Int] +[54:19..54:26):Some(1) => Some.apply[Int](*) [54:28..54:34):unwrap => *[Some[Int], Int] expect/EtaExpansion.scala @@ -1464,7 +1478,7 @@ Text => empty Language => Scala Symbols => 3 entries Occurrences => 9 entries -Synthetics => 5 entries +Synthetics => 7 entries Symbols: example/EtaExpansion# => class EtaExpansion extends Object { self: EtaExpansion => +1 decls } @@ -1485,9 +1499,11 @@ Occurrences: Synthetics: [3:2..3:13):Some(1).map => *[Int] [3:2..3:6):Some => *.apply[Int] +[3:2..3:9):Some(1) => Some.apply[Int](*) [3:14..3:22):identity => *[Int] [4:2..4:18):List(1).foldLeft => *[String] [4:2..4:6):List => *.apply[Int] +[4:2..4:9):List(1) => List.apply[Int](*) expect/Example.scala -------------------- @@ -1500,6 +1516,7 @@ Language => Scala Symbols => 5 entries Occurrences => 23 entries Diagnostics => 1 entries +Synthetics => 1 entries Symbols: example/Example. => final object Example extends Object { self: Example.type => +3 decls } @@ -1536,6 +1553,9 @@ Occurrences: Diagnostics: [2:24..2:30): [warning] unused import +Synthetics: +[9:37..9:37): => ClassTag.apply[Int](*) + expect/Extension.scala ---------------------- @@ -1546,7 +1566,7 @@ Text => empty Language => Scala Symbols => 32 entries Occurrences => 66 entries -Synthetics => 1 entries +Synthetics => 2 entries Symbols: ext/DeckUsage. => final object DeckUsage extends Object { self: DeckUsage.type => +2 decls } @@ -1651,6 +1671,7 @@ Occurrences: [26:7..26:14): fooSize -> ext/Extension$package.Deck.fooSize(). Synthetics: +[4:36..4:42):(s, i) => Tuple2.apply[String, Int](*) [14:46..14:61):summon[Read[T]] => *(x$2) expect/ForComprehension.scala @@ -1663,7 +1684,7 @@ Text => empty Language => Scala Symbols => 13 entries Occurrences => 53 entries -Synthetics => 6 entries +Synthetics => 23 entries Symbols: example/ForComprehension# => class ForComprehension extends Object { self: ForComprehension => +1 decls } @@ -1737,11 +1758,50 @@ Occurrences: Synthetics: [4:9..4:13):List => *.apply[Int] +[4:9..4:16):List(1) => List.apply[Int](*) +[5:4..7:5):b <- List(1) + if b > 1 + c => Tuple2.apply[Int, Int](*) [5:9..5:13):List => *.apply[Int] +[5:9..5:16):List(1) => List.apply[Int](*) +[8:10..8:19):(a, b, c) => Tuple3.apply[Int, Int, Int](*) [10:9..10:13):List => *.apply[Int] +[10:9..10:16):List(1) => List.apply[Int](*) [11:9..11:13):List => *.apply[Int] +[11:9..11:16):List(a) => List.apply[Int](*) +[12:7..15:5):( + a, + b + ) => Tuple2.apply[Int, Int](*) +[15:9..15:15):(1, 2) => Tuple2.apply[Int, Int](*) [19:9..19:13):List => *.apply[Tuple2[Int, Int]] +[19:9..19:21):List((a, b)) => List.apply[Tuple2[Int, Int]](*) +[19:14..19:20):(a, b) => Tuple2.apply[Int, Int](*) +[20:7..25:5):( + a, + b, + c, + d + ) => Tuple4.apply[Int, Int, Int, Int](*) +[25:9..25:21):(1, 2, 3, 4) => Tuple4.apply[Int, Int, Int, Int](*) +[26:4..26:5):e => Tuple2.apply[Tuple2[Int, Int], Tuple4[Int, Int, Int, Int]](*) +[26:8..31:5):( + a, + b, + c, + d + ) => Tuple4.apply[Int, Int, Int, Int](*) +[32:12..32:24):(1, 2, 3, 4) => Tuple4.apply[Int, Int, Int, Int](*) [33:9..33:13):List => *.apply[Tuple4[Int, Int, Int, Int]] +[33:9..33:16):List(e) => List.apply[Tuple4[Int, Int, Int, Int]](*) +[35:4..42:5):( + a, + b, + c, + d, + e, + f + ) => Tuple6.apply[Int, Int, Int, Int, Tuple4[Int, Int, Int, Int], Tuple4[Int, Int, Int, Int]](*) expect/Givens.scala ------------------- @@ -1753,7 +1813,7 @@ Text => empty Language => Scala Symbols => 33 entries Occurrences => 72 entries -Synthetics => 3 entries +Synthetics => 6 entries Symbols: a/b/Givens. => final object Givens extends Object { self: Givens.type => +13 decls } @@ -1865,6 +1925,9 @@ Occurrences: [27:59..27:64): empty -> a/b/Givens.Monoid#empty(). Synthetics: +[6:21..6:37):Hello, I am $any => apply(*) +[9:23..9:41):Goodbye, from $any => apply(*) +[10:22..10:40):So Long, from $any => apply(*) [12:17..12:25):sayHello => *[Int] [13:19..13:29):sayGoodbye => *[Int] [14:18..14:27):saySoLong => *[Int] @@ -1879,7 +1942,7 @@ Text => empty Language => Scala Symbols => 23 entries Occurrences => 52 entries -Synthetics => 6 entries +Synthetics => 9 entries Symbols: example/ImplicitConversion# => class ImplicitConversion extends Object { self: ImplicitConversion => +9 decls } @@ -1961,12 +2024,17 @@ Occurrences: [34:58..34:63): other -> example/ImplicitConversion.newAny2stringadd#`+`().(other) Synthetics: +[11:14..11:20):(1, 2) => Tuple2.apply[Int, Int](*) [15:2..15:9):message => augmentString(*) [17:2..17:7):tuple => newAny2stringadd[Tuple2[Int, Int]](*) [20:15..20:22):message => string2Number(*) +[23:4..23:26):Hello $message $number => apply(*) [24:2..26:16):s"""Hello |$message |$number""" => augmentString(*) +[24:6..26:13):Hello + |$message + |$number => apply(*) [28:15..28:19):char => char2int(*) [29:16..29:20):char => char2long(*) @@ -2007,6 +2075,90 @@ Occurrences: Diagnostics: [0:26..0:34): [warning] unused import +expect/InfoMacro.scala +---------------------- + +Summary: +Schema => SemanticDB v4 +Uri => InfoMacro.scala +Text => empty +Language => Scala +Symbols => 7 entries +Occurrences => 23 entries +Synthetics => 7 entries + +Symbols: +_empty_/InfoMacro. => final object InfoMacro extends Object { self: InfoMacro.type => +3 decls } +_empty_/InfoMacro.reportInfo(). => inline macro reportInfo (param msg: String): Unit +_empty_/InfoMacro.reportInfo().(msg) => param msg: String +_empty_/InfoMacro.reportInfoMacro(). => method reportInfoMacro (param msg: Expr[String])(implicit given param x$2: Quotes): Expr[Unit] +_empty_/InfoMacro.reportInfoMacro().(msg) => param msg: Expr[String] +_empty_/InfoMacro.reportInfoMacro().(x$2) => implicit given param x$2: Quotes +local0 => implicit given param contextual$1: Quotes + +Occurrences: +[0:7..0:12): scala -> scala/ +[0:13..0:19): quoted -> scala/quoted/ +[2:7..2:16): InfoMacro <- _empty_/InfoMacro. +[3:13..3:23): reportInfo <- _empty_/InfoMacro.reportInfo(). +[3:24..3:27): msg <- _empty_/InfoMacro.reportInfo().(msg) +[3:29..3:35): String -> scala/Predef.String# +[3:38..3:42): Unit -> scala/Unit# +[3:48..3:63): reportInfoMacro -> _empty_/InfoMacro.reportInfoMacro(). +[5:6..5:21): reportInfoMacro <- _empty_/InfoMacro.reportInfoMacro(). +[5:22..5:25): msg <- _empty_/InfoMacro.reportInfoMacro().(msg) +[5:27..5:31): Expr -> scala/quoted/Expr# +[5:32..5:38): String -> scala/Predef.String# +[5:47..5:53): Quotes -> scala/quoted/Quotes# +[5:56..5:60): Expr -> scala/quoted/Expr# +[5:61..5:65): Unit -> scala/Unit# +[6:11..6:17): quotes -> scala/quoted/Quotes$package.quotes(). +[6:18..6:25): reflect -> scala/quoted/Quotes#reflect. +[6:26..6:32): report -> scala/quoted/Quotes#reflectModule#report. +[9:4..9:10): report -> scala/quoted/Quotes#reflectModule#report. +[9:11..9:15): info -> scala/quoted/Quotes#reflectModule#reportModule#info(). +[9:16..9:17): s -> scala/StringContext#s(). +[9:37..9:40): msg -> _empty_/InfoMacro.reportInfoMacro().(msg) +[9:41..9:53): valueOrAbort -> scala/quoted/Quotes#valueOrAbort(). + +Synthetics: +[3:48..3:69):reportInfoMacro('msg) => *(contextual$1) +[3:64..3:68):'msg => orig()(contextual$1) +[6:11..6:17):quotes => *(x$2) +[9:18..9:54):Info from macro: ${msg.valueOrAbort} => apply(*) +[9:37..9:53):msg.valueOrAbort => *(StringFromExpr[String]) +[9:41..9:53):valueOrAbort => *[String] +[11:4..11:11):'{ () } => orig(())(x$2) + +expect/InfoMacroTest.scala +-------------------------- + +Summary: +Schema => SemanticDB v4 +Uri => InfoMacroTest.scala +Text => empty +Language => Scala +Symbols => 2 entries +Occurrences => 7 entries +Diagnostics => 2 entries + +Symbols: +_empty_/InfoMacroTest. => final object InfoMacroTest extends Object { self: InfoMacroTest.type => +2 decls } +_empty_/InfoMacroTest.main(). => method main (): Unit + +Occurrences: +[1:7..1:20): InfoMacroTest <- _empty_/InfoMacroTest. +[2:6..2:10): main <- _empty_/InfoMacroTest.main(). +[2:14..2:18): Unit -> scala/Unit# +[3:4..3:13): InfoMacro -> _empty_/InfoMacro. +[3:14..3:24): reportInfo -> _empty_/InfoMacro.reportInfo(). +[4:4..4:13): InfoMacro -> _empty_/InfoMacro. +[4:14..4:24): reportInfo -> _empty_/InfoMacro.reportInfo(). + +Diagnostics: +[3:4..3:55): [info] Info from macro: This is a test info message +[4:4..4:48): [info] Info from macro: Another info message + expect/InstrumentTyper.scala ---------------------------- @@ -2017,7 +2169,7 @@ Text => empty Language => Scala Symbols => 8 entries Occurrences => 53 entries -Synthetics => 2 entries +Synthetics => 4 entries Symbols: example/InstrumentTyper# => class InstrumentTyper extends Object { self: AnyRef & InstrumentTyper => +5 decls } @@ -2086,7 +2238,22 @@ Occurrences: Synthetics: [8:12..8:16):List => *.apply[Char | String | LinkOption | Int | Long | Class[Option[Int]] | Float | Double | Boolean | Unit | List[Nothing]] +[8:12..21:3):List( + Literal.int, + Literal.long, + Literal.float, + Literal.double, + Literal.nil, + Literal.char, + Literal.string, + Literal.bool, + Literal.unit, + Literal.javaEnum, + Literal.clazzOf, + List() + ) => List.apply[Char | String | LinkOption | Int | Long | Class[Option[Int]] | Float | Double | Boolean | Unit | List[Nothing]](*) [20:4..20:8):List => *.apply[Nothing] +[20:4..20:10):List() => List.apply[Nothing](*) expect/InventedNames.scala -------------------------- @@ -2234,7 +2401,7 @@ Text => empty Language => Scala Symbols => 7 entries Occurrences => 24 entries -Synthetics => 3 entries +Synthetics => 5 entries Symbols: example/Issue1749# => class Issue1749 extends Object { self: Issue1749 => +3 decls } @@ -2273,8 +2440,10 @@ Occurrences: Synthetics: [8:2..8:10):(x1, x1) => orderingToOrdered[Tuple2[Int, Int]](*) +[8:2..8:10):(x1, x1) => Tuple2.apply[Int, Int](*) [8:2..8:10):(x1, x1) => *(Tuple2(Int, Int)) [8:10..8:10): => *(Int, Int) +[9:13..9:21):(x2, x2) => Tuple2.apply[Int, Int](*) expect/JavaStaticVar.scala -------------------------- @@ -2349,7 +2518,7 @@ Text => empty Language => Scala Symbols => 3 entries Occurrences => 6 entries -Synthetics => 1 entries +Synthetics => 2 entries Symbols: local0 => val local x: Int @@ -2366,6 +2535,7 @@ Occurrences: Synthetics: [5:4..5:8):List => *.apply[Int] +[5:4..5:11):List(x) => List.apply[Int](*) expect/MatchType.scala ---------------------- @@ -2877,6 +3047,7 @@ Text => empty Language => Scala Symbols => 41 entries Occurrences => 41 entries +Synthetics => 1 entries Symbols: example/NamedApplyBlockCaseClassConstruction. => final object NamedApplyBlockCaseClassConstruction extends Object { self: NamedApplyBlockCaseClassConstruction.type => +6 decls } @@ -2964,6 +3135,9 @@ Occurrences: [12:16..12:24): bodyText -> example/NamedApplyBlockCaseClassConstruction.bodyText. [12:26..12:30): tail -> example/NamedApplyBlockCaseClassConstruction.Msg.apply().(tail) +Synthetics: +[12:12..12:40):Msg(bodyText, tail = "tail") => apply(*) + expect/NamedArguments.scala --------------------------- @@ -2974,6 +3148,8 @@ Text => empty Language => Scala Symbols => 16 entries Occurrences => 12 entries +Diagnostics => 2 entries +Synthetics => 1 entries Symbols: example/NamedArguments# => class NamedArguments extends Object { self: NamedArguments => +4 decls } @@ -3007,6 +3183,13 @@ Occurrences: [5:7..5:12): apply -> example/NamedArguments#User.apply(). [5:13..5:17): name -> example/NamedArguments#User.apply().(name) +Diagnostics: +[4:2..4:21): [warning] A pure expression does nothing in statement position +[5:2..5:27): [warning] A pure expression does nothing in statement position + +Synthetics: +[4:2..4:21):User(name = "John") => apply(*) + expect/NewModifiers.scala ------------------------- @@ -3199,7 +3382,7 @@ Language => Scala Symbols => 68 entries Occurrences => 115 entries Diagnostics => 1 entries -Synthetics => 3 entries +Synthetics => 4 entries Symbols: example/C# => class C extends Object { self: C => +3 decls } @@ -3395,6 +3578,7 @@ This construct can be rewritten automatically under -rewrite -source 3.4-migrati Synthetics: [15:23..15:34):elems.toMap => *[String, Any] [15:23..15:34):elems.toMap => *(refl[Tuple2[String, Any]]) +[16:41..16:53):fields(name) => apply(*) [32:47..32:56):s.pickOne => *[String] expect/RightAssociativeExtension.scala @@ -3407,6 +3591,7 @@ Text => empty Language => Scala Symbols => 5 entries Occurrences => 12 entries +Synthetics => 1 entries Symbols: ext/RightAssociativeExtension$package. => final package object ext extends Object { self: ext.type => +3 decls } @@ -3429,6 +3614,9 @@ Occurrences: [5:4..5:5): b <- ext/RightAssociativeExtension$package.b. [5:14..5:17): :*: -> ext/RightAssociativeExtension$package.`:*:`(). +Synthetics: +[3:36..3:42):(s, i) => Tuple2.apply[String, Int](*) + expect/Selfs.scala ------------------ @@ -3484,6 +3672,94 @@ Diagnostics: [10:29..10:33): [warning] with as a type operator has been deprecated; use & instead This construct can be rewritten automatically under -rewrite -source 3.4-migration. +expect/ShadowedParameters.scala +------------------------------- + +Summary: +Schema => SemanticDB v4 +Uri => ShadowedParameters.scala +Text => empty +Language => Scala +Symbols => 20 entries +Occurrences => 31 entries +Diagnostics => 14 entries +Synthetics => 2 entries + +Symbols: +example/Shadow# => class Shadow extends Object { self: Shadow => +5 decls } +example/Shadow#``(). => primary ctor (): Shadow +example/Shadow#curriedParams(). => method curriedParams (param x: Int)(param y: List[Int]): Unit +example/Shadow#curriedParams().(x) => param x: Int +example/Shadow#curriedParams().(y) => param y: List[Int] +example/Shadow#multiParams(). => method multiParams (param x: List[Int], param y: String): Unit +example/Shadow#multiParams().(x) => param x: List[Int] +example/Shadow#multiParams().(y) => param y: String +example/Shadow#shadowInParamBlock(). => method shadowInParamBlock (param x: Int, param y: Int): Unit +example/Shadow#shadowInParamBlock().(x) => param x: Int +example/Shadow#shadowInParamBlock().(y) => param y: Int +example/Shadow#shadowParam(). => method shadowParam (param x: Int): Unit +example/Shadow#shadowParam().(x) => param x: Int +local0 => val local x: Boolean +local1 => val local x: String +local2 => val local y: Int +local3 => val local x: Int +local4 => val local y: List[Int] +local5 => val local y: Int +local6 => val local x: Int + +Occurrences: +[0:8..0:15): example <- example/ +[2:6..2:12): Shadow <- example/Shadow# +[4:4..4:4): <- example/Shadow#``(). +[4:8..4:19): shadowParam <- example/Shadow#shadowParam(). +[4:20..4:21): x <- example/Shadow#shadowParam().(x) +[4:23..4:26): Int -> scala/Int# +[5:12..5:13): x <- local0 +[8:8..8:21): curriedParams <- example/Shadow#curriedParams(). +[8:22..8:23): x <- example/Shadow#curriedParams().(x) +[8:25..8:28): Int -> scala/Int# +[8:30..8:31): y <- example/Shadow#curriedParams().(y) +[8:33..8:37): List -> scala/package.List# +[8:38..8:41): Int -> scala/Int# +[9:12..9:13): x <- local1 +[10:12..10:13): y <- local2 +[14:8..14:19): multiParams <- example/Shadow#multiParams(). +[14:20..14:21): x <- example/Shadow#multiParams().(x) +[14:23..14:27): List -> scala/package.List# +[14:28..14:31): Int -> scala/Int# +[14:34..14:35): y <- example/Shadow#multiParams().(y) +[14:37..14:43): String -> scala/Predef.String# +[15:12..15:13): x <- local3 +[16:12..16:13): y <- local4 +[16:16..16:20): List -> scala/package.List. +[20:8..20:26): shadowInParamBlock <- example/Shadow#shadowInParamBlock(). +[20:27..20:28): x <- example/Shadow#shadowInParamBlock().(x) +[20:30..20:33): Int -> scala/Int# +[20:35..20:36): y <- example/Shadow#shadowInParamBlock().(y) +[20:38..20:41): Int -> scala/Int# +[21:12..21:13): y <- local5 +[23:16..23:17): x <- local6 + +Diagnostics: +[4:20..4:21): [warning] unused explicit parameter +[5:12..5:13): [warning] unused local definition +[8:22..8:23): [warning] unused explicit parameter +[8:30..8:31): [warning] unused explicit parameter +[9:12..9:13): [warning] unused local definition +[10:12..10:13): [warning] unused local definition +[14:20..14:21): [warning] unused explicit parameter +[14:34..14:35): [warning] unused explicit parameter +[15:12..15:13): [warning] unused local definition +[16:12..16:13): [warning] unused local definition +[20:27..20:28): [warning] unused explicit parameter +[20:35..20:36): [warning] unused explicit parameter +[21:12..21:13): [warning] unused local definition +[23:16..23:17): [warning] unused local definition + +Synthetics: +[16:16..16:20):List => *.apply[Int] +[16:16..16:27):List(1,2,3) => List.apply[Int](*) + expect/StructuralTypes.scala ---------------------------- @@ -3567,8 +3843,8 @@ Text => empty Language => Scala Symbols => 62 entries Occurrences => 165 entries -Diagnostics => 3 entries -Synthetics => 39 entries +Diagnostics => 4 entries +Synthetics => 48 entries Symbols: example/Synthetic# => class Synthetic extends Object { self: Synthetic => +23 decls } @@ -3803,17 +4079,21 @@ Occurrences: Diagnostics: [19:21..19:22): [warning] unused pattern variable +[28:4..28:9): [warning] A pure expression does nothing in statement position [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] +[5:2..5:9):List(1) => List.apply[Int](*) [6:2..6:18):Array.empty[Int] => intArrayOps(*) +[6:18..6:18): => ClassTag.apply[Int](*) [7:2..7:8):"fooo" => augmentString(*) [10:13..10:24):"name:(.*)" => augmentString(*) [11:8..11:11):#:: => *.unapply[Int] [11:17..11:25):LazyList => *.apply[Int] +[11:17..11:31):LazyList(1, 2) => LazyList.apply[Int](*) [13:4..13:28):#:: 2 #:: LazyList.empty => *[Int] [13:8..13:28):2 #:: LazyList.empty => toDeferrer[Int](*) [13:10..13:28):#:: LazyList.empty => *[Int] @@ -3822,6 +4102,7 @@ Synthetics: [15:9..15:12):#:: => *.unapply[Int] [15:16..15:19):#:: => *.unapply[Int] [15:25..15:33):LazyList => *.apply[Int] +[15:25..15:39):LazyList(1, 2) => LazyList.apply[Int](*) [17:14..17:38):#:: 2 #:: LazyList.empty => *[Int] [17:18..17:38):2 #:: LazyList.empty => toDeferrer[Int](*) [17:20..17:38):#:: LazyList.empty => *[Int] @@ -3833,8 +4114,13 @@ Synthetics: [19:46..19:47):x => ArrowAssoc[Int](*) [20:12..20:13):1 => intWrapper(*) [20:26..20:27):0 => intWrapper(*) +[20:44..20:50):(i, j) => Tuple2.apply[Int, Int](*) [21:12..21:13):1 => intWrapper(*) [21:26..21:27):0 => intWrapper(*) +[21:58..21:64):(i, j) => Tuple2.apply[Int, Int](*) +[25:4..25:7):s() => apply(*) +[28:4..28:9):Bar() => apply(*) +[29:4..29:36):null.asInstanceOf[Int => Int](2) => apply(*) [32:35..32:49):Array.empty[T] => *(evidence$1) [36:22..36:27):new F => orderingToOrdered[F](*) [36:22..36:27):new F => *(ordering) @@ -3857,7 +4143,7 @@ Text => empty Language => Scala Symbols => 2 entries Occurrences => 5 entries -Synthetics => 2 entries +Synthetics => 3 entries Symbols: example/Tabs$package. => final package object example extends Object { self: example.type => +2 decls } @@ -3874,6 +4160,7 @@ Synthetics: [3:1..4:6):List(1,2,3) .map => *[Int] [3:1..3:5):List => *.apply[Int] +[3:1..3:12):List(1,2,3) => List.apply[Int](*) expect/TargetName.scala ----------------------- @@ -3968,7 +4255,7 @@ Language => Scala Symbols => 22 entries Occurrences => 45 entries Diagnostics => 3 entries -Synthetics => 11 entries +Synthetics => 21 entries Symbols: example/ValPattern# => class ValPattern extends Object { self: ValPattern => +14 decls } @@ -4047,17 +4334,41 @@ Diagnostics: [31:15..31:25): [warning] unset local variable, consider using an immutable val instead Synthetics: +[4:22..4:28):(1, 2) => Tuple2.apply[Int, Int](*) [5:6..5:10):Some => *.unapply[Int] [6:4..6:8):Some => *.apply[Int] +[6:4..6:11):Some(1) => Some.apply[Int](*) [8:6..8:10):List => *.unapplySeq[Nothing] [8:11..8:15):Some => *.unapply[Nothing] +[10:28..10:34):(1, 2) => Tuple2.apply[Int, Int](*) [11:6..11:10):Some => *.unapply[Int] [12:4..12:8):Some => *.apply[Int] +[12:4..12:11):Some(1) => Some.apply[Int](*) +[16:6..23:7):( + number1, + left, + right, + number1Var, + leftVar, + rightVar + ) => Tuple6.apply[Int, Int, Int, Int, Int, Int](*) [25:4..25:11):locally => *[Unit] +[26:26..26:32):(1, 2) => Tuple2.apply[Int, Int](*) [27:10..27:14):Some => *.unapply[Int] [28:8..28:12):Some => *.apply[Int] +[28:8..28:15):Some(1) => Some.apply[Int](*) +[30:32..30:38):(1, 2) => Tuple2.apply[Int, Int](*) [31:10..31:14):Some => *.unapply[Int] [32:8..32:12):Some => *.apply[Int] +[32:8..32:15):Some(1) => Some.apply[Int](*) +[34:8..41:9):( + number1, + left, + right, + number1Var, + leftVar, + rightVar + ) => Tuple6.apply[Int, Int, Int, Int, Int, Int](*) expect/Vals.scala ----------------- @@ -4657,6 +4968,7 @@ Language => Scala Symbols => 24 entries Occurrences => 63 entries Diagnostics => 2 entries +Synthetics => 1 entries Symbols: _empty_/Copy# => trait Copy [typeparam In <: Txn[In], typeparam Out <: Txn[Out]] extends Object { self: Copy[In, Out] => +5 decls } @@ -4753,6 +5065,9 @@ Diagnostics: [13:12..13:17): [warning] unused pattern variable [13:28..13:34): [warning] unused pattern variable +Synthetics: +[12:4..12:13):(in, out) => Tuple2.apply[Repr[In], Repr[Out]](*) + expect/inlineconsume.scala -------------------------- @@ -4847,7 +5162,7 @@ Text => empty Language => Scala Symbols => 17 entries Occurrences => 31 entries -Synthetics => 1 entries +Synthetics => 2 entries Symbols: _empty_/Concrete# => class Concrete extends NullaryTest[Int, List] { self: Concrete => +3 decls } @@ -4903,6 +5218,7 @@ Occurrences: Synthetics: [13:17..13:21):List => *.apply[Int] +[13:17..13:28):List(1,2,3) => List.apply[Int](*) expect/recursion.scala ---------------------- @@ -4914,6 +5230,7 @@ Text => empty Language => Scala Symbols => 36 entries Occurrences => 48 entries +Synthetics => 2 entries Symbols: local0 => type N$1 <: Nat @@ -5003,6 +5320,10 @@ Occurrences: [23:35..23:39): Zero -> recursion/Nats.Zero. [23:40..23:42): ++ -> recursion/Nats.Nat#`++`(). +Synthetics: +[5:50..5:60):Succ(this) => Succ.apply[Nat.this.type](*) +[5:50..5:60):Succ(this) => Succ.apply[Nat.this.type](*) + expect/semanticdb-Definitions.scala ----------------------------------- @@ -5622,7 +5943,7 @@ Text => empty Language => Scala Symbols => 18 entries Occurrences => 21 entries -Synthetics => 3 entries +Synthetics => 6 entries Symbols: _empty_/AnObject. => final object AnObject extends Object { self: AnObject.type => +6 decls } @@ -5669,8 +5990,11 @@ Occurrences: Synthetics: [11:2..11:6):List => *.apply[Int] +[11:2..11:12):List(1, 2) => List.apply[Int](*) [12:2..12:12):List.apply => *[Nothing] +[12:2..12:14):List.apply() => List.apply[Nothing](*) [13:2..13:14):List.`apply` => *[Nothing] +[13:2..13:16):List.`apply`() => List.apply[Nothing](*) expect/toplevel.scala --------------------- diff --git a/tests/warn/applied_constructor_types.check b/tests/warn/applied_constructor_types.check new file mode 100644 index 000000000000..b2d2b82f4e09 --- /dev/null +++ b/tests/warn/applied_constructor_types.check @@ -0,0 +1,4 @@ +-- [E212] Type Warning: tests/warn/applied_constructor_types.scala:6:10 ------------------------------------------------ +6 | val v1: UnspecificBox(4) = UnspecificBox(4) // warn + | ^^^^^^^^^^^^^^^^ + |Applied constructor type can only be used with classes where all parameters in the first parameter list are tracked diff --git a/tests/warn/applied_constructor_types.scala b/tests/warn/applied_constructor_types.scala new file mode 100644 index 000000000000..d575dabe80f0 --- /dev/null +++ b/tests/warn/applied_constructor_types.scala @@ -0,0 +1,6 @@ +import scala.language.experimental.modularity + +class UnspecificBox(val v: Any) + +def test = + val v1: UnspecificBox(4) = UnspecificBox(4) // warn diff --git a/tests/warn/convertible.scala b/tests/warn/convertible.scala index b701cac32cec..655d935adb59 100644 --- a/tests/warn/convertible.scala +++ b/tests/warn/convertible.scala @@ -1,6 +1,7 @@ //> using options -feature import language.experimental.into +import Conversion.into class Text(val str: String) @@ -15,10 +16,10 @@ object Test: f("abc", "def", "xyz", "uvw") // warn // warn // warn // warn f("abc", "def", "xyz", Text("uvw")) // warn // warn // warn - def g(x: into Text) = + def g(x: into[Text]) = println(x.str) - def g2(x: into Text) = + def g2(x: into[Text]) = println(x.str) def g3(x: Text) = @@ -26,19 +27,19 @@ object Test: g("abc") // OK val gg = g - gg("abc") // warn, eta expansion does not preserve into + gg("abc") // ok val c1 = if ??? then g else g2 - c1("abc") // warn, eta expansion does not preserve into + c1("abc") // ok, lub type = into[Text] => Unit val c2 = if ??? then g else g3 - c2("abc") // warn, eta expansion does not preserve into + c2("abc") // warn, lub type is Text => Unit val c3 = if ??? then g3 else g - c3("abc") // warn, eta expansion does not preserve into + c3("abc") // warn, lub type is Text => Unit def h1[X](x: X)(y: X): Unit = () - def h(x: into Text) = + def h(x: into[Text]) = val y = h1(x) - y("abc") // warn, eta expansion does not preserve into \ No newline at end of file + y("abc") // ok \ No newline at end of file diff --git a/tests/warn/i15503a.scala b/tests/warn/i15503a.scala index 884261e140a7..78234e6e777b 100644 --- a/tests/warn/i15503a.scala +++ b/tests/warn/i15503a.scala @@ -39,6 +39,10 @@ object FooGiven: val foo = summon[Int] +object SomeGivenImports: + given Int = 0 + given String = "foo" + /** * Import used as type name are considered * as used. @@ -69,7 +73,7 @@ object InlineChecks: object InlinedBar: import collection.mutable.Set // warn (don't be fooled by inline expansion) import collection.mutable.Map // warn - val a = InlineFoo.getSet + val a = InlineFoo.getSet // expansion is attributed mutable.Set.apply(1) object MacroChecks: object StringInterpol: @@ -91,12 +95,7 @@ object IgnoreExclusion: def check = val a = Set(1) val b = Map(1 -> 2) -/** - * Some given values for the test - */ -object SomeGivenImports: - given Int = 0 - given String = "foo" + def c = Seq(42) /* BEGIN : Check on packages*/ package nestedpackageimport: @@ -460,3 +459,11 @@ package ancient: } } end ancient + +object `i22970 assign lhs was ignored`: + object X: + var global = 0 + object Main: + import X.global // no warn + def main(args: Array[String]): Unit = + global = 1 diff --git a/tests/warn/i15503e.scala b/tests/warn/i15503e.scala index 63a9dea163d4..0a7fadc7189a 100644 --- a/tests/warn/i15503e.scala +++ b/tests/warn/i15503e.scala @@ -92,4 +92,4 @@ object UnwrapTyped: 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 + def f(i: Int)(j: Int = i * 2) = j // ~warn~ I guess (see tests/warn/i23349.scala) diff --git a/tests/warn/i15503f.scala b/tests/warn/i15503f.scala index c2eef8a529c5..70d0b9182eb2 100644 --- a/tests/warn/i15503f.scala +++ b/tests/warn/i15503f.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:implicits +//> using options -Wunused:implicits /* This goes around the "trivial method" detection */ val default_int = 1 @@ -67,6 +67,8 @@ package givens: trait Y: def doY: String + trait Z + given X: def doX = 7 @@ -84,6 +86,9 @@ package givens: given namely: (x: X) => Y: // warn protected param to given class def doY = "8" + + def f(using => X) = println() // warn + def g(using => Z) = println() // nowarn marker trait end givens object i22895: diff --git a/tests/warn/i15503g.check b/tests/warn/i15503g.check new file mode 100644 index 000000000000..105df82e8a1a --- /dev/null +++ b/tests/warn/i15503g.check @@ -0,0 +1,28 @@ +-- [E198] Unused Symbol Warning: tests/warn/i15503g.scala:8:17 --------------------------------------------------------- +8 | private def f2(a: Int) = default_int // warn + | ^ + | unused explicit parameter +-- [E198] Unused Symbol Warning: tests/warn/i15503g.scala:9:31 --------------------------------------------------------- +9 | private def f3(a: Int)(using Int) = a // warn + | ^ + | unused implicit parameter +-- [E198] Unused Symbol Warning: tests/warn/i15503g.scala:10:17 -------------------------------------------------------- +10 | private def f4(a: Int)(using Int) = default_int // warn // warn + | ^ + | unused explicit parameter +-- [E198] Unused Symbol Warning: tests/warn/i15503g.scala:10:31 -------------------------------------------------------- +10 | private def f4(a: Int)(using Int) = default_int // warn // warn + | ^ + | unused implicit parameter +-- [E198] Unused Symbol Warning: tests/warn/i15503g.scala:11:17 -------------------------------------------------------- +11 | private def f6(a: Int)(using Int) = summon[Int] // warn + | ^ + | unused explicit parameter +-- [E198] Unused Symbol Warning: tests/warn/i15503g.scala:23:18 -------------------------------------------------------- +23 | def isAnIssue(y: A): Boolean = x == x // warn + | ^ + | unused explicit parameter in extension method isAnIssue +-- [E198] Unused Symbol Warning: tests/warn/i15503g.scala:29:30 -------------------------------------------------------- +29 | extension (s: String)(using Show) // warn not used in repeat + | ^ + | unused implicit parameter in extension method repeat diff --git a/tests/warn/i15503g.scala b/tests/warn/i15503g.scala index cfbfcdb04d1e..f63f7ea73475 100644 --- a/tests/warn/i15503g.scala +++ b/tests/warn/i15503g.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:params +//> using options -Wunused:params /* This goes around the "trivial method" detection */ object Foo { @@ -15,10 +15,17 @@ object Foo { private def g2(x: Int) = ??? // OK } -package foo.test.i17101: +object i17101: type Test[A] = A extension[A] (x: Test[A]) { // OK def value: A = x def causesIssue: Unit = println("oh no") def isAnIssue(y: A): Boolean = x == x // warn } + +object i23125: + trait Show: + def show(s: String) = s + extension (s: String)(using Show) // warn not used in repeat + def echo = println(summon[Show].show(s)) + def repeat = s * 2 diff --git a/tests/warn/i17314.scala b/tests/warn/i17314.scala index cff90d843c38..3b5a486a6785 100644 --- a/tests/warn/i17314.scala +++ b/tests/warn/i17314.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:all -deprecation -feature +//> using options -Wunused:all -deprecation -feature -Werror import java.net.URI @@ -10,7 +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] = // warn // warn + inline final def derived[A](using conf: Configuration)(using inline mirror: Mirror.Of[A]): ConfiguredCodec[A] = class InlinedConfiguredCodec extends ConfiguredCodec[A]: val codec = summonInline[Codec[URI]] // simplification new InlinedConfiguredCodec diff --git a/tests/warn/i21187.scala b/tests/warn/i21187.scala new file mode 100644 index 000000000000..d6ea131afdfb --- /dev/null +++ b/tests/warn/i21187.scala @@ -0,0 +1,22 @@ +//> using options -Wall + +def oops(msg: String) = sys.error(msg) + +class Zone +object Zone: + inline def apply[T](inline f: Zone ?=> T): T = f(using new Zone) + +inline def zone[A](inline f: Zone ?=> A) = Zone.apply(z => f(using z)) // warn suspicious contextualizing + +def zone_?[A](f: Zone ?=> A) = Zone.apply(z => f(using z)) // warn + +// intended +//inline def zone[A](inline f: Zone ?=> A): A = Zone.apply(z ?=> f(using z)) + +@main def hello = + // this swallows exceptions! + zone(oops("here")) // warn function value is not used + zone_?(oops("here")) // warn + + // this doesn't + Zone(oops("not here")) diff --git a/tests/warn/i21805.scala b/tests/warn/i21805.scala new file mode 100644 index 000000000000..4fdcae85910d --- /dev/null +++ b/tests/warn/i21805.scala @@ -0,0 +1,31 @@ +//> using options -Wunused:imports + +def i23967: Boolean = { + //import scala.compiletime.testing.typeCheckErrors + import scala.compiletime.testing.* // nowarn + typeChecks("2 + 2") +} + +package p: + val code = """"hello, world"""" +package c: + class C(i: Int) + +package q: + import c.* // warn should be nowarn + import p.* // warn should be nowarn + import scala.compiletime.testing.* + def test() = typeCheckErrors("""println(C("hello, world"))""") + def ok() = typeChecks("println(code)") + inline def f(inline i: Int) = 42 + i + +package i23967b: + package ok: + import scala.compiletime.testing.* // nowarn + def test() = typeChecks("42 + 27") + package nok: + import scala.compiletime.testing.typeChecks // nowarn + def test() = typeChecks("42 + 27") + +@main def Test = println: + q.f(27) diff --git a/tests/warn/i22440.check b/tests/warn/i22440.check index eaa357661a59..018a31e57044 100644 --- a/tests/warn/i22440.check +++ b/tests/warn/i22440.check @@ -3,5 +3,5 @@ | ^ | 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: + | 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/i22681.scala b/tests/warn/i22681.scala index e15d186479c3..81f12923d4d6 100644 --- a/tests/warn/i22681.scala +++ b/tests/warn/i22681.scala @@ -6,12 +6,10 @@ trait T: 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 + def g = 42 // warn effectively private member is unused + def t = 42 // nowarn + def u = 42 // 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 + def u = 42 // nowarn because leaked by refinement diff --git a/tests/warn/i22731.check b/tests/warn/i22731.check index f7b25eb3b384..a4701ed8a076 100644 --- a/tests/warn/i22731.check +++ b/tests/warn/i22731.check @@ -3,11 +3,11 @@ | ^^^^^^^^ | 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: + | To disable the warning, please use the following option: | "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s" -- Warning: tests/warn/i22731.scala:7:6 -------------------------------------------------------------------------------- 7 | foo { () => 43 } // warn | ^^^^^^^^^^^^ | Implicit parameters should be provided with a `using` clause. - | To disable the warning, please use the following option: + | 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/i22795.check b/tests/warn/i22795.check new file mode 100644 index 000000000000..2ce9b16e0477 --- /dev/null +++ b/tests/warn/i22795.check @@ -0,0 +1,12 @@ + +-- Deprecation Warning: tests/warn/i22795/test_1.scala:4:7 ------------------------------------------------------------- +4 | lib.m() // warn + | ^^^^^^^ + | object A is deprecated + |--------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from test_1.scala:5 +5 | + | ^^^^^^^^ + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/warn/i22795/lib_0.scala b/tests/warn/i22795/lib_0.scala new file mode 100644 index 000000000000..13225a865dc4 --- /dev/null +++ b/tests/warn/i22795/lib_0.scala @@ -0,0 +1,11 @@ + +import scala.quoted._ + +@deprecated object A + +object lib: + inline def m() = ${mImpl} + + def mImpl(using Quotes): Expr[Any] = + import quotes.reflect._ + Ref(Symbol.classSymbol("A$").companionModule).asExpr diff --git a/tests/warn/i22795/test_1.scala b/tests/warn/i22795/test_1.scala new file mode 100644 index 000000000000..e75ca03d5cf6 --- /dev/null +++ b/tests/warn/i22795/test_1.scala @@ -0,0 +1,4 @@ +//> using options -deprecation + +@main def Test = println: + lib.m() // warn diff --git a/tests/warn/i22971.scala b/tests/warn/i22971.scala new file mode 100644 index 000000000000..63eee06a8c52 --- /dev/null +++ b/tests/warn/i22971.scala @@ -0,0 +1,34 @@ +//> using options -Wunused:imports + +package p: + + trait Base + class Class extends Base + + abstract class Entity[T: GetType] + + class Thing extends Entity[Class] + + trait GetType[T] + + object GetType { + //implicit object GetTypeClass extends GetType[Class] + implicit val GetTypeClass: GetType[Class] = new GetType[Class] {} + } + object Main { + def main(args: Array[String]): Unit = { + import GetType.* + val e = GetTypeClass + } + } + +package q: + + class C: + def f = + import p.* + GetType.GetTypeClass + def g = + import p.GetType.* + GetTypeClass + class D extends p.Entity[p.Class] diff --git a/tests/warn/i23033.scala b/tests/warn/i23033.scala index 6170664e05e2..24153c235f4b 100644 --- a/tests/warn/i23033.scala +++ b/tests/warn/i23033.scala @@ -11,3 +11,5 @@ object Useful: given [T](using @unused ec: ExecutionContext): AnyRef with {} object Syntax: given [T] => (@unused ec: ExecutionContext) => AnyRef + +class i23122(@unused param: AnyRef) diff --git a/tests/warn/i23047.scala b/tests/warn/i23047.scala new file mode 100644 index 000000000000..1922797c3185 --- /dev/null +++ b/tests/warn/i23047.scala @@ -0,0 +1,25 @@ +//> using options -Wunused:imports + +package some.example: + package demo: + + import some.example // no warn because enclosing package example is not available as a simple name in some + + object Main { + + def generic[T](x: Any): T = null.asInstanceOf[T] + + def main(args: Array[String]): Unit = { + generic[example.Util](0) + + import some.example.demo.Main // warn + println(Main) + + import some.example.demo // warn because enclosing package demo is available as a simple name + println(demo.Main) + } + } + +package some.example: + + class Util diff --git a/tests/warn/i23049.scala b/tests/warn/i23049.scala new file mode 100644 index 000000000000..30797ea61689 --- /dev/null +++ b/tests/warn/i23049.scala @@ -0,0 +1,12 @@ +trait TC[X] +object TC { + given [X: scala.deriving.Mirror.ProductOf]: TC[X] = ??? +} + +trait Base[T] { + given TC[T] = scala.compiletime.deferred +} + +case class P(x: Int) + +object A extends Base[P] diff --git a/tests/warn/i23164.scala b/tests/warn/i23164.scala new file mode 100644 index 000000000000..ac068555a5b3 --- /dev/null +++ b/tests/warn/i23164.scala @@ -0,0 +1,72 @@ +class T1[F[_]] +class T2[F[_]] +class T3[F[_]] +class T4[F[_]] +class T5[F[_]] +class T6[F[_]] +class T7[F[_]] +class T8[F[_]] +class T9[F[_]] +class T10[F[_]] +class T11[F[_]] +class T12[F[_]] +class T13[F[_]] +class T14[F[_]] +class T15[F[_]] +class T16[F[_]] +class T17[F[_]] +class T18[F[_]] +class T19[F[_]] +class T20[F[_]] +class T21[F[_]] +class T22[F[_]] + +class Result[F[_]: {T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,T17,T18,T19,T20,T21,T22}] + +val r = for + t1 <- Option(new T1[Option]) + t2 <- Option(new T2[Option]) + t3 <- Option(new T3[Option]) + t4 <- Option(new T4[Option]) + t5 <- Option(new T5[Option]) + t6 <- Option(new T6[Option]) + t7 <- Option(new T7[Option]) + t8 <- Option(new T8[Option]) + t9 <- Option(new T9[Option]) + t10 <- Option(new T10[Option]) + t11 <- Option(new T11[Option]) + t12 <- Option(new T12[Option]) + t13 <- Option(new T13[Option]) + t14 <- Option(new T14[Option]) + t15 <- Option(new T15[Option]) + t16 <- Option(new T16[Option]) + t17 <- Option(new T17[Option]) + t18 <- Option(new T18[Option]) + t19 <- Option(new T19[Option]) + t20 <- Option(new T20[Option]) + t21 <- Option(new T21[Option]) + t22 <- Option(new T22[Option]) + given T1[Option] = t1 + given T2[Option] = t2 + given T3[Option] = t3 + given T4[Option] = t4 + given T5[Option] = t5 + given T6[Option] = t6 + given T7[Option] = t7 + given T8[Option] = t8 + given T9[Option] = t9 + given T10[Option] = t10 + given T11[Option] = t11 + given T12[Option] = t12 + given T13[Option] = t13 + given T14[Option] = t14 + given T15[Option] = t15 + given T16[Option] = t16 + given T17[Option] = t17 + given T18[Option] = t18 + given T19[Option] = t19 + given T20[Option] = t20 + given T21[Option] = t21 + given T22[Option] = t22 + result <- Option(new Result[Option]) +yield result \ No newline at end of file diff --git a/tests/warn/i23200.check b/tests/warn/i23200.check new file mode 100644 index 000000000000..75d663f0f338 --- /dev/null +++ b/tests/warn/i23200.check @@ -0,0 +1,8 @@ +-- [E198] Unused Symbol Warning: tests/warn/i23200.scala:8:8 ----------------------------------------------------------- +8 | var x: Int = 42 // warn + | ^ + | unset private variable, consider using an immutable val instead +-- [E198] Unused Symbol Warning: tests/warn/i23200.scala:40:6 ---------------------------------------------------------- +40 | var x: Int = 42 // warn local var + | ^ + | unset local variable, consider using an immutable val instead diff --git a/tests/warn/i23200.scala b/tests/warn/i23200.scala new file mode 100644 index 000000000000..613e8df32262 --- /dev/null +++ b/tests/warn/i23200.scala @@ -0,0 +1,41 @@ +//> using options -Wunused:all + +trait Foo +trait Bar + +def `anon not updated` = + new Foo { + var x: Int = 42 // warn + val _ = new Bar: + println(x) + //x = 27 + //x_=(27) + } +def `anon yes updated` = + new Foo { + var x: Int = 42 // nowarn + val _ = new Bar: + println(x) + x = 27 + //x_=(27) + } +def `anon yes updated from nested context` = + new Foo { + var x: Int = 42 // nowarn + val _ = new Bar: + println(x) + x = 27 + //x_=(27) + } +def `anon yes updated in daring use of setter` = + new Foo { + var x: Int = 42 // nowarn + val _ = new Bar: + println(x) + //x = 27 + x_=(27) + } + +def f: Unit = + var x: Int = 42 // warn local var + println(x) diff --git a/tests/warn/i23250.scala b/tests/warn/i23250.scala new file mode 100644 index 000000000000..ac9183ed1fd1 --- /dev/null +++ b/tests/warn/i23250.scala @@ -0,0 +1,17 @@ +//> using options -Wunused:all -Werror + +trait MonadError[F[_], E] +type MonadThrow[F[_]] = MonadError[F, Throwable] + +trait MetaStreams[F[_]]: + def use[A]: F[A] = ??? +trait WriteResult + +trait MetaStreamsSyntax: + extension [F[_]](ms: MetaStreams[F])(using MonadThrow[F]) + def setMaxAge(): F[WriteResult] = + summon[MonadThrow[F]] + ms.use[WriteResult] + + def setTruncateBefore(): F[WriteResult] = + ms.use[WriteResult] diff --git a/tests/warn/i23250b.scala b/tests/warn/i23250b.scala new file mode 100644 index 000000000000..f9e18559fb46 --- /dev/null +++ b/tests/warn/i23250b.scala @@ -0,0 +1,18 @@ +//> using options -Wunused:all + +trait Memberly: + def member: Int + +object Members: + type MemberType <: Memberly + type Empty + +object Test: + import Members.* + + type MT = MemberType + def membered(using MT) = println() // warn abstract type offers member in upper bound + def remembered(using mt: MT) = mt.member + + type Ignore = Empty + def emptily(using Ignore) = println() diff --git a/tests/warn/i23277.scala b/tests/warn/i23277.scala new file mode 100644 index 000000000000..f99f5a373d43 --- /dev/null +++ b/tests/warn/i23277.scala @@ -0,0 +1,22 @@ + +enum Test: + case One + case Two(i: Int) + +object Test: + object Two: + def apply(i: Int): Test.Two = Test.Two(i) // warn + def apply(i: Int, j: Int): Test.Two = new Test.Two(i+j) + def apply(i: Int, s: String): Two = Two(i, s) // warn because unprefixed Two is deemed pure + def apply(): Test.Two = other.apply() // nowarn prefix is method call + def other: this.type = this + +object R extends Runnable: + def r: this.type = this + override def run() = r.run() + +final class C(c: C): + def f(i: Int): Int = c.f(i) + +@main def main = println: + Test.Two(1) diff --git a/tests/warn/i23323.scala b/tests/warn/i23323.scala new file mode 100644 index 000000000000..85a5b0646b6e --- /dev/null +++ b/tests/warn/i23323.scala @@ -0,0 +1,14 @@ +//> using options -Wunused:all + +class C: + val x = new reflect.Selectable: + def f = 42 + def g = 27 + val y: Selectable = new reflect.Selectable: + def f = 42 // warn + def g = 27 // warn + val z = new scala.Selectable: + def f = 42 + def g = 27 + def selectDynamic(name: String): Any = ??? + def applyDynamic(name: String)(args: Any*): Any = ??? diff --git a/tests/warn/i23347.scala b/tests/warn/i23347.scala new file mode 100644 index 000000000000..82e362070f03 --- /dev/null +++ b/tests/warn/i23347.scala @@ -0,0 +1,22 @@ +//> using options -Wunused:all + +object USED { + case class A(value: Int) +} + +object UNUSED { + // In reality UNUSED would contain several other necessary members! + private type A = USED.A // warn private + class B +} + +object Test { + import USED.* + import UNUSED.* + + def foo(a: A): Int = a.value + + def g(b: B) = () + +} + diff --git a/tests/warn/i23349.scala b/tests/warn/i23349.scala new file mode 100644 index 000000000000..4d0fcfc759db --- /dev/null +++ b/tests/warn/i23349.scala @@ -0,0 +1,30 @@ +//> using options -Wunused:explicits,implicits + +// An external class that doesn't get its own `copy` method. +class Foo(val a: String, val b: Int) + +// +// Example 1: add `copy` method via an extension method. +// +extension (self: Foo) + def copy(a: String = self.a, b: Int = self.b): Foo = Foo(a, b) // nowarn + +// +// Example 2: implement `copyFoo` with parameter groups. +// +def copyFoo(foo: Foo)(a: String = foo.a, b: Int = foo.b): Foo = Foo(a, b) + +class C: + def copyFoo(foo: Foo, bar: String)(a: String = foo.a, b: Int = foo.b)(c: String = bar): Foo = Foo(a, b) // warn c + def copyUsing(using foo: Foo, bar: String)(a: String = foo.a, b: Int = foo.b)(c: String = bar): Foo = // warn c + Foo(a, b) + +class K(k: Int)(s: String = "*"*k): + override val toString = s + +class KU(using k: Int)(s: String = "*"*k): + override val toString = s + +class KK(s: String): + def this(k: Int)(s: String = "*"*k) = this(s) + override val toString = s diff --git a/tests/warn/i23369.scala b/tests/warn/i23369.scala new file mode 100644 index 000000000000..5f4130ab96d3 --- /dev/null +++ b/tests/warn/i23369.scala @@ -0,0 +1,26 @@ +class Module { + type BarTy + sealed trait Adt[A] + case class Foo() extends Adt[String] + case class Bar[A <: BarTy](x: BarTy) extends Adt[A] +} + +object Basic extends Module { + type BarTy = String +} + +def test(a: Basic.Adt[String]) = { + a match { // warn: match may not be exhaustive + case Basic.Foo() => + } +} + +object Basic2 extends Module { + type BarTy = Int +} + +def test2(a: Basic2.Adt[String]) = { + a match { + case Basic2.Foo() => + } +} diff --git a/tests/warn/i23494.scala b/tests/warn/i23494.scala new file mode 100644 index 000000000000..c3c0c5a2e0bc --- /dev/null +++ b/tests/warn/i23494.scala @@ -0,0 +1,15 @@ +//> using options -Wunused:implicits + +import scala.deriving.Mirror + +abstract class EnumerationValues[A]: + type Out + +object EnumerationValues: + type Aux[A, B] = EnumerationValues[A] { type Out = B } + + def apply[A, B](): EnumerationValues.Aux[A, B] = new EnumerationValues[A]: + override type Out = B + + given sum[A, B <: Tuple](using mirror: Mirror.SumOf[A] { type MirroredElemTypes = B }): EnumerationValues.Aux[A, A] = + EnumerationValues[A, A]() diff --git a/tests/warn/i23541.check b/tests/warn/i23541.check new file mode 100644 index 000000000000..64ffbd9808d2 --- /dev/null +++ b/tests/warn/i23541.check @@ -0,0 +1,18 @@ +-- [E220] Type Warning: tests/warn/i23541.scala:29:13 ------------------------------------------------------------------ +29 | println(f(using s = "ab")) // warn uses default instead of given // prints "ab" + | ^^^^^^^^^^^^^^^^^ + | Argument for implicit parameter i was supplied using a default argument. + | + | longer explanation available when compiling with `-explain` +-- [E221] Type Warning: tests/warn/i23541.scala:5:17 ------------------------------------------------------------------- +5 | else fun(x - 1)(using p = p + x) // warn recurse uses default (instead of given passed down the stack) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Recursive call used a default argument for parameter q. + | + | longer explanation available when compiling with `-explain` +-- [E221] Type Warning: tests/warn/i23541.scala:9:17 ------------------------------------------------------------------- +9 | else gun(x - 1)(p = p + x) // warn recurse uses default (value not passed down the stack) + | ^^^^^^^^^^^^^^^^^^^^^ + | Recursive call used a default argument for parameter q. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/warn/i23541.scala b/tests/warn/i23541.scala new file mode 100644 index 000000000000..7f374b421ba3 --- /dev/null +++ b/tests/warn/i23541.scala @@ -0,0 +1,30 @@ +//> using options -Wrecurse-with-default + +def fun(x: Int)(using p: Int, q: Int = 0): Int = + if x <= 0 then p * q + else fun(x - 1)(using p = p + x) // warn recurse uses default (instead of given passed down the stack) + +def gun(x: Int)(p: Int, q: Int = 0): Int = + if x <= 0 then p * q + else gun(x - 1)(p = p + x) // warn recurse uses default (value not passed down the stack) + +def nested(using x: Int, y: Int = 42): Int = + def f: Int = nested(using x) // nowarn only self-recursive tailrec is eligible for warning + f + +def f(using s: String, i: Int = 1): String = s * i +def g(using s: String)(using i: Int = 1): String = s * i + +@main def Test = + println(fun(3)(using p = 0, q = 1)) + locally: + given String = "ab" + println(f) // prints "ab" + println(g) // prints "ab" + locally: + println(f(using s = "ab")) // prints "ab" + println(g(using s = "ab")) // prints "ab" + locally: + given Int = 2 + println(f(using s = "ab")) // warn uses default instead of given // prints "ab" + println(g(using s = "ab")) // prints "abab" diff --git a/tests/warn/i23651.scala b/tests/warn/i23651.scala new file mode 100644 index 000000000000..c81ddb4249f8 --- /dev/null +++ b/tests/warn/i23651.scala @@ -0,0 +1,27 @@ +//> using options -deprecation -Wunused:nowarn + +import scala.annotation.nowarn + +@deprecated +class A + +@deprecated +class B + +@nowarn("msg=trait C is deprecated") // warn + // @nowarn annotation does not suppress any warnings +@nowarn("msg=class A is deprecated") +@nowarn("cat=deprecation&msg=class A is deprecated") // warn + // @nowarn annotation does not suppress any warnings but matches a diagnostic +@nowarn("cat=deprecation&msg=class B is deprecated") +trait C1: + def a: A + def b: B + +@nowarn("cat=deprecation&msg=class B is deprecated") +@nowarn("cat=deprecation&msg=class B is deprecated") // warn + // @nowarn annotation does not suppress any warnings but matches a diagnostic +@nowarn("cat=deprecation&msg=class A is deprecated") +trait C2: + def a: A + def b: B diff --git a/tests/warn/i23693.check b/tests/warn/i23693.check new file mode 100644 index 000000000000..66b238de13a2 --- /dev/null +++ b/tests/warn/i23693.check @@ -0,0 +1,20 @@ +-- [E209] Interpolation Warning: tests/warn/i23693.scala:11:12 --------------------------------------------------------- +11 | s"k == $k" // warn + | ^ + | interpolation uses toString +-- [E209] Interpolation Warning: tests/warn/i23693.scala:13:16 --------------------------------------------------------- +13 | raw"\k == \$k" // warn + | ^ + | interpolation uses toString +-- [E209] Interpolation Warning: tests/warn/i23693.scala:15:12 --------------------------------------------------------- +15 | f"k == $k" // warn + | ^ + | interpolation uses toString +-- [E209] Interpolation Warning: tests/warn/i23693.scala:17:14 --------------------------------------------------------- +17 | f"k == $k%s" // warn + | ^ + | interpolation uses toString +-- [E209] Interpolation Warning: tests/warn/i23693.scala:19:18 --------------------------------------------------------- +19 | s"show == ${k.show}" // warn + | ^^^^^^ + | interpolated Unit value diff --git a/tests/warn/i23693.scala b/tests/warn/i23693.scala new file mode 100644 index 000000000000..341977dc717c --- /dev/null +++ b/tests/warn/i23693.scala @@ -0,0 +1,19 @@ +//> using options -Wtostring-interpolated + +// verify warning messages; cf run test; we must verify runtime while warning. + +case class K(i: Int): + def show: Unit = () + +@main def Test = + val k = K(42) + println: + s"k == $k" // warn + println: + raw"\k == \$k" // warn + println: + f"k == $k" // warn + println: + f"k == $k%s" // warn + println: + s"show == ${k.show}" // warn diff --git a/tests/warn/i23704.check b/tests/warn/i23704.check new file mode 100644 index 000000000000..71a69aefef11 --- /dev/null +++ b/tests/warn/i23704.check @@ -0,0 +1,20 @@ +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:9:8 ----------------------------------------------------------- +9 | var position: Int = 0 // warn position is assigned but not read + | ^^^^^^^^ + | private variable was mutated but not read +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:16:14 --------------------------------------------------------- +16 | private var myvar: Int = 0 // warn for the same case with simpler syntax + | ^^^^^ + | private variable was mutated but not read +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:26:8 ---------------------------------------------------------- +26 | var localvar = 0 // warn local variable was mutated but not read + | ^^^^^^^^ + | local variable was mutated but not read +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:33:8 ---------------------------------------------------------- +33 | var settable: Int = 0 // warn private variable was mutated but not read + | ^^^^^^^^ + | private variable was mutated but not read +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:39:14 --------------------------------------------------------- +39 | private var myvar: Int = 0 // warn plain unused + | ^^^^^ + | unused private member diff --git a/tests/warn/i23704.scala b/tests/warn/i23704.scala new file mode 100644 index 000000000000..9cfaae3278c1 --- /dev/null +++ b/tests/warn/i23704.scala @@ -0,0 +1,39 @@ +//> using options -Wunused:all + +trait Test { + def incr(): Unit +} + +object Test { + val test = new Test { + var position: Int = 0 // warn position is assigned but not read + + def incr(): Unit = { position += 1 } + } +} + +class C: + private var myvar: Int = 0 // warn for the same case with simpler syntax + def mine: Int = + myvar = 42 + 27 + +class D: + private var myvar: Int = 0 // nowarn (although read is RHS of assignment) + def incr(): Unit = myvar = myvar + 1 + + def local(): Unit = + var localvar = 0 // warn local variable was mutated but not read + localvar += 1 + +class E: + trait Setting: + def setting(): Unit + val test = new Test: + var settable: Int = 0 // warn private variable was mutated but not read + def setting(): Unit = + settable_=(42) + def incr() = setting() + +class F: + private var myvar: Int = 0 // warn plain unused diff --git a/tests/warn/i24034/circe.scala b/tests/warn/i24034/circe.scala new file mode 100644 index 000000000000..5d2e646f67b2 --- /dev/null +++ b/tests/warn/i24034/circe.scala @@ -0,0 +1,97 @@ + +// circe.scala + +package io.circe + +import scala.compiletime.* +import scala.deriving.Mirror +import scala.quoted.* + +trait Encoder[A]: + def encode(value: A): String + +object Encoder: + trait AsObject[A] extends Encoder[A] + given Encoder[String] = ??? + +trait Configuration +object Configuration: + val default: Configuration = ??? + +object Default: + given [A]: Default[A] = ??? +trait Default[T] + +trait Codec[A] extends Encoder[A] +object Codec: + trait AsObject[A] extends Encoder.AsObject[A] + object AsObject: + inline final def derived[A](using inline A: Mirror.Of[A]): Codec.AsObject[A] = + ConfiguredCodec.derived[A](using Configuration.default) + inline final def derivedConfigured[A](using + inline A: Mirror.Of[A], + inline conf: Configuration + ): Codec.AsObject[A] = ConfiguredCodec.derived[A] + +trait ConfiguredEncoder[A](using conf: Configuration) extends Encoder.AsObject[A] +trait ConfiguredCodec[A] extends Codec.AsObject[A], ConfiguredEncoder[A] +object ConfiguredCodec: + inline final def derive[A: Mirror.Of](): ConfiguredCodec[A] = + derived[A](using Configuration.default) + inline final def derived[A](using + conf: Configuration, + inline mirror: Mirror.Of[A] + ): ConfiguredCodec[A] = ${ derivedImpl[A]('conf, 'mirror) } + def ofProduct[A]( + encoders: => List[Encoder[?]] + )(using Configuration, Default[A]): ConfiguredCodec[A] = ??? + def derivedImpl[A: Type](conf: Expr[Configuration], mirror: Expr[Mirror.Of[A]])(using + q: Quotes + ): Expr[ConfiguredCodec[A]] = { + mirror match { + case '{ + ${ _ }: Mirror.ProductOf[A] { + type MirroredLabel = l + type MirroredElemLabels = el + type MirroredElemTypes = et + } + } => + '{ + ConfiguredCodec.ofProduct[A]( + derivation.summonEncoders[et & Tuple](false)(using $conf) + )(using $conf) + } + } + } + +object derivation: + sealed trait Inliner[A, Arg]: + inline def apply[T](inline arg: Arg): A + + class EncoderNotDeriveSum(using config: Configuration) extends Inliner[Encoder[?], Unit]: + inline def apply[T](inline arg: Unit): Encoder[?] = summonEncoder[T](false) + + inline final def loopUnrolled[A, Arg, T <: Tuple](f: Inliner[A, Arg], inline arg: Arg): List[A] = + inline erasedValue[T] match + case _: EmptyTuple => Nil + case _: (h *: ts) => f[h](arg) :: loopUnrolled[A, Arg, ts](f, arg) + + inline def loopUnrolledNoArg[A, T <: Tuple](f: Inliner[A, Unit]): List[A] = + loopUnrolled[A, Unit, T](f, ()) + + inline final def summonEncoders[T <: Tuple](inline derivingForSum: Boolean)(using + Configuration + ): List[Encoder[?]] = + loopUnrolledNoArg[Encoder[?], T]( + inline if (derivingForSum) compiletime.error("unreachable") + else new EncoderNotDeriveSum + ) + + private[circe] inline final def summonEncoder[A]( + inline derivingForSum: Boolean + )(using Configuration): Encoder[A] = summonFrom { + case encodeA: Encoder[A] => encodeA + case _: Mirror.Of[A] => + inline if (derivingForSum) compiletime.error("unreachable") + else error("Failed to find an instance of Encoder[]") + } diff --git a/tests/warn/i24034/iron.scala b/tests/warn/i24034/iron.scala new file mode 100644 index 000000000000..17cb26bf6408 --- /dev/null +++ b/tests/warn/i24034/iron.scala @@ -0,0 +1,30 @@ + +// iron.scala + +package iron + +import io.circe.* + +opaque type IronType[A, C] <: A = A +type :|[A, C] = IronType[A, C] +trait Constraint[A, C] + +package constraint: + object string: + final class StartWith[V <: String] + object StartWith: + inline given [V <: String]: Constraint[String, StartWith[V]] = ??? + +object circe: + inline given XXX[A, B](using inline encoder: Encoder[A]): Encoder[A :| B] = ??? + inline given YYY[A, B](using inline encoder: Encoder[A], dummy: scala.util.NotGiven[DummyImplicit]): Encoder[A :| B] = ??? + // inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Encoder[mirror.IronType]): Encoder[T] = ??? + +// trait RefinedTypeOps[A, C, T]: +// inline given RefinedTypeOps.Mirror[T] = ??? +// object RefinedTypeOps: +// trait Mirror[T]: +// type BaseType +// type ConstraintType +// type IronType = BaseType :| ConstraintType + diff --git a/tests/warn/i24034/test.scala b/tests/warn/i24034/test.scala new file mode 100644 index 000000000000..f87be8da2c36 --- /dev/null +++ b/tests/warn/i24034/test.scala @@ -0,0 +1,10 @@ +//> using options -Wunused:all + +import io.circe.Codec + +import iron.:| +import iron.circe.given +import iron.constraint.string.StartWith + +case class Alien(name: String :| StartWith["alen"]) derives Codec.AsObject + diff --git a/tests/warn/i24248/lib.scala b/tests/warn/i24248/lib.scala new file mode 100644 index 000000000000..8244d509f8f4 --- /dev/null +++ b/tests/warn/i24248/lib.scala @@ -0,0 +1,18 @@ + +import scala.quoted.* + +trait Thing +object Stuff: + given Thing() + +object lib: + inline def m: Thing = ${ mImpl[Thing] } + + def mImpl[T](using Quotes, Type[T]): Expr[T] = + import quotes.reflect.* + val thing = Implicits.search(TypeRepr.of[T]) match + case iss: ImplicitSearchSuccess => iss.tree.asExprOf[T] + '{ + val res = $thing + res + } diff --git a/tests/warn/i24248/test.scala b/tests/warn/i24248/test.scala new file mode 100644 index 000000000000..35137a5646c9 --- /dev/null +++ b/tests/warn/i24248/test.scala @@ -0,0 +1,6 @@ +//> using options -Werror -Wunused:all + +import Stuff.given + +@main def test = println: + lib.m diff --git a/tests/warn/i24263.scala b/tests/warn/i24263.scala new file mode 100644 index 000000000000..fc4768de2597 --- /dev/null +++ b/tests/warn/i24263.scala @@ -0,0 +1,6 @@ +//> using options -Werror -Wunused:all + +object test { + def f(t: Tuple): Nothing = ??? + val _ = (inputTuple: NamedTuple.NamedTuple[Tuple, Tuple]) => f(inputTuple) +} diff --git a/tests/warn/i24265.scala b/tests/warn/i24265.scala new file mode 100644 index 000000000000..9c5f189b26d6 --- /dev/null +++ b/tests/warn/i24265.scala @@ -0,0 +1,10 @@ +//> using options -Wall -Werror + +object test { + inline def f(testFun: => Any) = testFun + + f { + val i = 1 + summon[i.type <:< Int] + } +} diff --git a/tests/warn/i9266.check b/tests/warn/i9266.check index 90dfe43bd2b2..ff8f57c9ded1 100644 --- a/tests/warn/i9266.check +++ b/tests/warn/i9266.check @@ -1,5 +1,13 @@ --- Migration Warning: tests/warn/i9266.scala:5:22 ---------------------------------------------------------------------- -5 |def test = { implicit x: Int => x + x } // warn - | ^ - | This syntax is no longer supported; parameter needs to be enclosed in (...) - | This construct can be rewritten automatically under -rewrite -source future-migration. +-- Migration Warning: tests/warn/i9266.scala:5:14 ---------------------------------------------------------------------- +5 |def test1 = { implicit (x: Int) => x + x } // warn + | ^ + | `implicit` lambdas are no longer supported, use a lambda with `?=>` instead +-- Migration Warning: tests/warn/i9266.scala:7:14 ---------------------------------------------------------------------- +7 |def test2 = { implicit x: Int => x + x } // warn // warn + | ^ + | `implicit` lambdas are no longer supported, use a lambda with `?=>` instead +-- Migration Warning: tests/warn/i9266.scala:7:23 ---------------------------------------------------------------------- +7 |def test2 = { implicit x: Int => x + x } // warn // warn + | ^ + | This syntax is no longer supported; parameter needs to be enclosed in (...) + | This construct can be rewritten automatically under -rewrite -source future-migration. diff --git a/tests/warn/i9266.scala b/tests/warn/i9266.scala index c621e9e20b99..0d5f10687063 100644 --- a/tests/warn/i9266.scala +++ b/tests/warn/i9266.scala @@ -2,5 +2,6 @@ import language.`future-migration` -def test = { implicit x: Int => x + x } // warn +def test1 = { implicit (x: Int) => x + x } // warn +def test2 = { implicit x: Int => x + x } // warn // warn diff --git a/tests/warn/into-as-mod.scala b/tests/warn/into-as-mod.scala new file mode 100644 index 000000000000..587bb375e4d1 --- /dev/null +++ b/tests/warn/into-as-mod.scala @@ -0,0 +1,29 @@ +//> using options -feature + +import language.experimental.into +import Conversion.into + +into trait T +class C(x: Int) extends T + +object K: + into opaque type A = C + given Conversion[Int, A] = C(_) + +object Test: + given Conversion[Int, C] = C(_) + + def f(x: T) = () + def g(x: C) = () + f(1) // ok + g(1) // warn + + import K.* + def h(x: A) = () + h(1) + + into class Keyword(str: String) + given stringToKeyword: Conversion[String, Keyword] = Keyword(_) + + val dclKeywords = Set[Keyword]("def", "val") // ok + val keywords = dclKeywords + "if" + "then" + "else" // ok diff --git a/tests/warn/nowarn.check b/tests/warn/nowarn.check new file mode 100644 index 000000000000..89ff8d51161b --- /dev/null +++ b/tests/warn/nowarn.check @@ -0,0 +1,110 @@ +-- [E002] Syntax Warning: tests/warn/nowarn.scala:5:10 ----------------------------------------------------------------- +5 |def t1a = try 1 // warn (parser) + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. + | + | longer explanation available when compiling with `-explain` +-- [E002] Syntax Warning: tests/warn/nowarn.scala:19:25 ---------------------------------------------------------------- +19 |@nowarn(o.inl) def t2d = try 1 // warn // warn (`inl` is not a compile-time constant) + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. + | + | longer explanation available when compiling with `-explain` +-- [E002] Syntax Warning: tests/warn/nowarn.scala:27:26 ---------------------------------------------------------------- +27 |@nowarn("id=1") def t4d = try 1 // warn // warn (unused nowarn, wrong id) + | ^^^^^ + | A try without catch or finally is equivalent to putting + | its body in a block; no exceptions are handled. + | + | longer explanation available when compiling with `-explain` +-- [E002] Syntax Warning: tests/warn/nowarn.scala:29:28 ---------------------------------------------------------------- +29 |@nowarn("verbose") def t5 = try 1 // warn with details + | ^^^^^ + | 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` +-- [E129] Potential Issue Warning: tests/warn/nowarn.scala:9:11 -------------------------------------------------------- +9 |def t2 = { 1; 2 } // warn (the invalid nowarn doesn't silence anything) + | ^ + | A pure expression does nothing in statement position + | + | longer explanation available when compiling with `-explain` +-- Warning: tests/warn/nowarn.scala:8:8 -------------------------------------------------------------------------------- +8 |@nowarn("wat?") // warn (typer, invalid filter) + | ^^^^^^ + | Invalid message filter + | unknown filter: wat? +-- [E129] Potential Issue Warning: tests/warn/nowarn.scala:12:12 ------------------------------------------------------- +12 |def t2a = { 1; 2 } // warn (invalid nowarn doesn't silence) + | ^ + | A pure expression does nothing in statement position + | + | longer explanation available when compiling with `-explain` +-- Warning: tests/warn/nowarn.scala:11:8 ------------------------------------------------------------------------------- +11 |@nowarn(t1a.toString) // warn (typer, argument not a compile-time constant) + | ^^^^^^^^^^^^ + | filter needs to be a compile-time constant string +-- Warning: tests/warn/nowarn.scala:19:10 ------------------------------------------------------------------------------ +19 |@nowarn(o.inl) def t2d = try 1 // warn // warn (`inl` is not a compile-time constant) + | ^^^^^ + | filter needs to be a compile-time constant string +-- Deprecation Warning: tests/warn/nowarn.scala:33:10 ------------------------------------------------------------------ +33 |def t6a = f // warn (refchecks, deprecation) + | ^ + | method f is deprecated +-- Deprecation Warning: tests/warn/nowarn.scala:36:30 ------------------------------------------------------------------ +36 |@nowarn("msg=fish") def t6d = f // warn (unused nowarn) // warn (deprecation) + | ^ + | method f is deprecated +-- Deprecation Warning: tests/warn/nowarn.scala:43:10 ------------------------------------------------------------------ +43 |def t7c = f // warn (deprecation) + | ^ + | method f is deprecated +-- [E092] Pattern Match Unchecked Warning: tests/warn/nowarn.scala:49:7 ------------------------------------------------ +49 | case _: List[Int] => 0 // warn (patmat, unchecked) + | ^ + |the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from Any + | + | longer explanation available when compiling with `-explain` +-- Warning: tests/warn/nowarn.scala:27:1 ------------------------------------------------------------------------------- +27 |@nowarn("id=1") def t4d = try 1 // warn // warn (unused nowarn, wrong id) + |^^^^^^^^^^^^^^^ + |@nowarn annotation does not suppress any warnings +-- Warning: tests/warn/nowarn.scala:36:1 ------------------------------------------------------------------------------- +36 |@nowarn("msg=fish") def t6d = f // warn (unused nowarn) // warn (deprecation) + |^^^^^^^^^^^^^^^^^^^ + |@nowarn annotation does not suppress any warnings +-- Warning: tests/warn/nowarn.scala:44:5 ------------------------------------------------------------------------------- +44 | : @nowarn("msg=fish") // warn (unused nowarn) + | ^^^^^^^^^^^^^^^^^^^ + | @nowarn annotation does not suppress any warnings +-- Warning: tests/warn/nowarn.scala:56:0 ------------------------------------------------------------------------------- +56 |@nowarn def t9a = { 1: @nowarn; 2 } // warn (outer @nowarn is unused) + |^^^^^^^ + |@nowarn annotation does not suppress any warnings but matches a diagnostic +-- Warning: tests/warn/nowarn.scala:57:27 ------------------------------------------------------------------------------ +57 |@nowarn def t9b = { 1: Int @nowarn; 2 } // warn (inner @nowarn is unused, it covers the type, not the expression) + | ^^^^^^^ + | @nowarn annotation does not suppress any warnings +-- Warning: tests/warn/nowarn.scala:62:0 ------------------------------------------------------------------------------- +62 |@nowarn @ann(f) def t10b = 0 // warn (unused nowarn) + |^^^^^^^ + |@nowarn annotation does not suppress any warnings +-- Warning: tests/warn/nowarn.scala:63:8 ------------------------------------------------------------------------------- +63 |@ann(f: @nowarn) def t10c = 0 // warn (unused nowarn), should be silent + | ^^^^^^^ + | @nowarn annotation does not suppress any warnings +-- Warning: tests/warn/nowarn.scala:66:0 ------------------------------------------------------------------------------- +66 |@nowarn class I1a { // warn (unused nowarn) + |^^^^^^^ + |@nowarn annotation does not suppress any warnings but matches a diagnostic +-- Warning: tests/warn/nowarn.scala:71:0 ------------------------------------------------------------------------------- +71 |@nowarn class I1b { // warn (unused nowarn) + |^^^^^^^ + |@nowarn annotation does not suppress any warnings but matches a diagnostic diff --git a/tests/warn/nowarn.scala b/tests/warn/nowarn.scala new file mode 100644 index 000000000000..f6cacdd677d0 --- /dev/null +++ b/tests/warn/nowarn.scala @@ -0,0 +1,83 @@ +//> using options -deprecation -Wunused:nowarn + +import scala.annotation.{nowarn, Annotation} + +def t1a = try 1 // warn (parser) +@nowarn("msg=try without catch") def t1b = try 1 + +@nowarn("wat?") // warn (typer, invalid filter) +def t2 = { 1; 2 } // warn (the invalid nowarn doesn't silence anything) + +@nowarn(t1a.toString) // warn (typer, argument not a compile-time constant) +def t2a = { 1; 2 } // warn (invalid nowarn doesn't silence) + +object o: + final val const = "msg=try" + inline def inl = "msg=try" + +@nowarn(o.const) def t2c = try 1 // no warn +@nowarn(o.inl) def t2d = try 1 // warn // warn (`inl` is not a compile-time constant) + +@nowarn("id=E129") def t3a = { 1; 2 } +@nowarn("name=PureExpressionInStatementPosition") def t3b = { 1; 2 } + +@nowarn("id=E002") def t4a = try 1 +@nowarn("id=E2") def t4b = try 1 +@nowarn("id=2") def t4c = try 1 +@nowarn("id=1") def t4d = try 1 // warn // warn (unused nowarn, wrong id) + +@nowarn("verbose") def t5 = try 1 // warn with details + +@deprecated def f = 0 + +def t6a = f // warn (refchecks, deprecation) +@nowarn("cat=deprecation") def t6b = f +@nowarn("msg=deprecated") def t6c = f +@nowarn("msg=fish") def t6d = f // warn (unused nowarn) // warn (deprecation) +@nowarn("") def t6e = f +@nowarn def t6f = f + +def t7a = f: @nowarn("cat=deprecation") +def t7b = f + : @nowarn("msg=deprecated") +def t7c = f // warn (deprecation) + : @nowarn("msg=fish") // warn (unused nowarn) +def t7d = f: @nowarn("") +def t7e = f: @nowarn + +def t8a(x: Any) = x match + case _: List[Int] => 0 // warn (patmat, unchecked) + case _ => 1 + +@nowarn("cat=unchecked") def t8(x: Any) = x match + case _: List[Int] => 0 + case _ => 1 + +@nowarn def t9a = { 1: @nowarn; 2 } // warn (outer @nowarn is unused) +@nowarn def t9b = { 1: Int @nowarn; 2 } // warn (inner @nowarn is unused, it covers the type, not the expression) + +class ann(a: Any) extends Annotation + +@ann(f) def t10a = 0 // should be a deprecation warning, but currently isn't +@nowarn @ann(f) def t10b = 0 // warn (unused nowarn) +@ann(f: @nowarn) def t10c = 0 // warn (unused nowarn), should be silent + +def forceCompletionOfI1a = (new I1a).m +@nowarn class I1a { // warn (unused nowarn) + @nowarn def m = { 1; 2 } +} + +// completion during type checking +@nowarn class I1b { // warn (unused nowarn) + @nowarn def m = { 1; 2 } +} + +@nowarn class I1c { + def m = { 1; 2 } +} + +trait T { + @nowarn val t1 = { 0; 1 } +} + +class K extends T diff --git a/tests/warn/patmat-type-member-nothing-exhaustive.scala b/tests/warn/patmat-type-member-nothing-exhaustive.scala new file mode 100644 index 000000000000..c04270b78a3a --- /dev/null +++ b/tests/warn/patmat-type-member-nothing-exhaustive.scala @@ -0,0 +1,18 @@ +trait Phase { + type FooTy + type BarTy + sealed trait Adt + case class Foo(x: FooTy) extends Adt + case class Bar(x: BarTy) extends Adt +} + +object Basic extends Phase { + type FooTy = Unit + type BarTy = Nothing +} + + +def test(a: Basic.Adt) = { + a match + case Basic.Foo(x) => +} \ No newline at end of file diff --git a/tests/warn/tostring-interpolated.scala b/tests/warn/tostring-interpolated.scala index 165bc374b5ef..f81dab3a518b 100644 --- a/tests/warn/tostring-interpolated.scala +++ b/tests/warn/tostring-interpolated.scala @@ -11,14 +11,18 @@ trait T { def format = f"${c.x}%d in $c or $c%s" // warn using c.toString // warn - def bool = f"$c%b" // warn just a null check - - def oops = s"${null} slipped thru my fingers" // warn - def ok = s"${c.toString}" def sb = new StringBuilder().append("hello") def greeting = s"$sb, world" // warn + + def literally = s"Hello, ${"world"}" // nowarn literal, widened to String + + def bool = f"$c%b" // warn just a null check (quirk of Java format) + + def oops = s"${null} slipped thru my fingers" // warn although conforms to String + + def exceptionally = s"Hello, ${???}" // warn although conforms to String } class Mitigations { @@ -29,8 +33,47 @@ class Mitigations { def ok = s"$s is ok" def jersey = s"number $i" - def unitized = s"unfortunately $shown" // maybe tell them about unintended ()? + def unitized = s"unfortunately $shown" // warn accidental unit value + def funitized = f"unfortunately $shown" // warn accidental unit value def nopct = f"$s is ok" def nofmt = f"number $i" } + +class Branches { + + class C { + val shouldCaps = true + val greeting = s"Hello ${if (shouldCaps) "WORLD" else "world"}" + } + + class D { + val shouldCaps = true + object world { override def toString = "world" } + val greeting = s"Hello ${if (shouldCaps) "WORLD" else world}" // warn + } + + class E { + def x = 42 + val greeting = s"Hello ${x match { case 42 => "WORLD" case 27 => "world" case _ => ??? }}" + } + + class F { + def x = 42 + object world { override def toString = "world" } + val greeting = s"Hello ${ + x match { // warn + case 17 => "Welt" + case 42 => "WORLD" + case 27 => world + case _ => ??? } + }" + } + + class Z { + val shouldCaps = true + val greeting = s"Hello ${if (shouldCaps) ??? else null}" // warn + val farewell = s"Bye-bye ${if (shouldCaps) "Bob" else null}" // warn + } + +} diff --git a/tests/warn/warn-value-discard.check b/tests/warn/warn-value-discard.check index dcd6d62c00e0..03ada5c68621 100644 --- a/tests/warn/warn-value-discard.check +++ b/tests/warn/warn-value-discard.check @@ -2,12 +2,12 @@ 27 | mutable.Set.empty[String].remove("") // warn | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | discarded non-Unit value of type Boolean. Add `: Unit` to discard silently. --- [E175] Potential Issue Warning: tests/warn/warn-value-discard.scala:39:41 ------------------------------------------- -39 | mutable.Set.empty[String].subtractOne("") // warn +-- [E175] Potential Issue Warning: tests/warn/warn-value-discard.scala:37:41 ------------------------------------------- +37 | mutable.Set.empty[String].subtractOne("") // warn | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | discarded non-Unit value of type scala.collection.mutable.Set[String]. Add `: Unit` to discard silently. --- [E175] Potential Issue Warning: tests/warn/warn-value-discard.scala:59:4 -------------------------------------------- -59 | mutable.Set.empty[String] += "" // warn +-- [E175] Potential Issue Warning: tests/warn/warn-value-discard.scala:57:4 -------------------------------------------- +57 | mutable.Set.empty[String] += "" // warn | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | discarded non-Unit value of type scala.collection.mutable.Set[String]. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/warn-value-discard.scala:15:35 ------------------------------------------- diff --git a/tests/warn/warn-value-discard.scala b/tests/warn/warn-value-discard.scala index 8bb9aebf515b..93c2c056b67c 100644 --- a/tests/warn/warn-value-discard.scala +++ b/tests/warn/warn-value-discard.scala @@ -26,11 +26,9 @@ class ValueDiscardTest: // --> Warning mutable.Set.empty[String].remove("") // warn - // TODO IMHO we don't need to support this, - // as it's just as easy to add a @nowarn annotation as a Unit ascription - //def removeAscribed(): Unit = { - // mutable.Set.empty[String].remove(""): Unit // nowarn - //} + def removeAscribed(): Unit = { + mutable.Set.empty[String].remove(""): Unit // nowarn + } def subtract(): Unit = // - Set#subtractOne returns this.type @@ -63,4 +61,37 @@ class ValueDiscardTest: // - receiver is a local variable // --> No warning val s: mutable.Set[String] = mutable.Set.empty[String] - s += "" \ No newline at end of file + s += "" + +// see also tests/warn/21557.scala +class UnitAscription: + import scala.concurrent.*, ExecutionContext.Implicits.given + + case class C(c: Int): + def f(i: Int, j: Int = c) = i + j + + def f(i: Int, j: Int = 27) = i + j + + def g[A]: List[A] = Nil + + def i: Int = 42 + + def `default arg is inline`: Unit = + f(i = 42): Unit // nowarn + + def `default arg requires block`: Unit = + C(27).f(i = 42): Unit // nowarn + + def `application requires implicit arg`: Unit = + Future(42): Unit // nowarn + + def `application requires inferred type arg`: Unit = + g: Unit // nowarn + + def `implicit selection from this`: Unit = + i: Unit // nowarn + +object UnitAscription: + def g[A]: List[A] = Nil + def `application requires inferred type arg`: Unit = + g: Unit // nowarn UnitAscription.g