diff --git a/.github/Dockerfile b/.github/Dockerfile index 59d46fd169d9..3ba5466c30b8 100644 --- a/.github/Dockerfile +++ b/.github/Dockerfile @@ -22,5 +22,5 @@ RUN apt-get update && \ # Install sbt ENV SBT_HOME /usr/local/sbt ENV PATH ${SBT_HOME}/bin:${PATH} -ENV SBT_VERSION 1.9.0 +ENV SBT_VERSION 1.10.5 RUN curl -sL "https://github.com/sbt/sbt/releases/download/v$SBT_VERSION/sbt-$SBT_VERSION.tgz" | gunzip | tar -x -C /usr/local \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE/fix-issue.md b/.github/PULL_REQUEST_TEMPLATE/fix-issue.md index f7cf22eb59c7..005be5daef4f 100644 --- a/.github/PULL_REQUEST_TEMPLATE/fix-issue.md +++ b/.github/PULL_REQUEST_TEMPLATE/fix-issue.md @@ -8,7 +8,7 @@ assignees: '' ## Fix #XYZ diff --git a/.github/PULL_REQUEST_TEMPLATE/other-pr.md b/.github/PULL_REQUEST_TEMPLATE/other-pr.md index fad49836df92..a9948b717932 100644 --- a/.github/PULL_REQUEST_TEMPLATE/other-pr.md +++ b/.github/PULL_REQUEST_TEMPLATE/other-pr.md @@ -8,7 +8,7 @@ assignees: '' ## Description diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5931219f472a..bc5648aa7626 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,6 +26,14 @@ 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) +# 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 +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + env: DOTTY_CI_RUN: true DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} @@ -818,7 +826,7 @@ jobs: path: . - name: Prepare MSI package shell: bash - run: | + run: | msiInstaller="scala3-${{ env.RELEASE_TAG }}.msi" mv scala.msi "${msiInstaller}" sha256sum "${msiInstaller}" > "${msiInstaller}.sha256" @@ -826,14 +834,15 @@ jobs: - name: Install GH CLI uses: dev-hanz-ops/install-gh-cli-action@v0.2.0 with: - gh-cli-version: 2.59.0 - + gh-cli-version: 2.59.0 # Create the GitHub release - name: Create GitHub Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token shell: bash run: | + # We need to config safe.directory in every step that might reference git + # It is not persisted between steps git config --global --add safe.directory /__w/scala3/scala3 gh release create \ --draft \ @@ -901,14 +910,14 @@ jobs: uses: ./.github/workflows/build-chocolatey.yml needs: [ build-sdk-package ] with: - version: 3.6.0-local # TODO: FIX THIS + version: 3.6.0-SNAPSHOT # Fake version, used only for choco tests url : https://api.github.com/repos/scala/scala3/actions/artifacts/${{ needs.build-sdk-package.outputs.win-x86_64-id }}/zip digest : ${{ needs.build-sdk-package.outputs.win-x86_64-digest }} test-chocolatey-package: uses: ./.github/workflows/test-chocolatey.yml with: - version : 3.6.0-local # TODO: FIX THIS + version : 3.6.0-SNAPSHOT # Fake version, used only for choco tests java-version: 8 if: github.event_name == 'pull_request' && contains(github.event.pull_request.body, '[test_chocolatey]') needs: [ build-chocolatey-package ] diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index f370cb2b541c..d052dc5eb6fc 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -3,20 +3,11 @@ on: pull_request: branches-ignore: - 'language-reference-stable' - push: - branches: - - 'language-reference-stable' - merge_group: -permissions: - contents: write - pull-requests: write - jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - run: ./project/scripts/check-cla.sh - if: github.event_name == 'pull_request' - env: - AUTHOR: ${{ github.event.pull_request.user.login }} + - name: Verify CLA + uses: scala/cla-checker@v1 + with: + author: ${{ github.event.pull_request.user.login }} diff --git a/.github/workflows/lts-backport.yaml b/.github/workflows/lts-backport.yaml index 24d2329ed9da..b26075c180e9 100644 --- a/.github/workflows/lts-backport.yaml +++ b/.github/workflows/lts-backport.yaml @@ -15,7 +15,7 @@ jobs: with: fetch-depth: 0 - uses: coursier/cache-action@v6 - - uses: VirtusLab/scala-cli-setup@v1.5.3 + - uses: VirtusLab/scala-cli-setup@v1.5.4 - 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 3b31728a50ba..88a8a7913188 100644 --- a/.github/workflows/publish-chocolatey.yml +++ b/.github/workflows/publish-chocolatey.yml @@ -35,5 +35,5 @@ jobs: with: name: scala.nupkg - name: Publish the package to Chocolatey - run: choco push scala.nupkg --source https://push.chocolatey.org/ --api-key ${{ secrets.API-KEY }} + run: choco push scala.${{inputs.version}}.nupkg --source https://push.chocolatey.org/ --api-key ${{ secrets.API-KEY }} \ No newline at end of file diff --git a/.github/workflows/publish-winget.yml b/.github/workflows/publish-winget.yml index 03ebc5d0fa7d..ba5877e63ec7 100644 --- a/.github/workflows/publish-winget.yml +++ b/.github/workflows/publish-winget.yml @@ -26,7 +26,7 @@ jobs: publish: runs-on: windows-latest steps: - - uses: vedantmgoyal9/winget-releaser@b87a066d9e624db1394edcd947f8c4e5a7e30cd7 + - uses: vedantmgoyal9/winget-releaser@4ffc7888bffd451b357355dc214d43bb9f23917e with: identifier : Scala.Scala.3 version : ${{ inputs.version }} diff --git a/.github/workflows/test-cc.yml b/.github/workflows/test-cc.yml new file mode 100644 index 000000000000..69996fb6d74e --- /dev/null +++ b/.github/workflows/test-cc.yml @@ -0,0 +1,31 @@ +name: Scala 3 with Capture Checking + +on: + push: + branches: + - main + pull_request: + paths: + - .github/workflows/test-cc.yml + - scala2-library-cc/** + - scala2-library-cc-tasty/** + - compiler/src/dotty/tools/dotc/cc/** + ## Capture Checking Tests + - tests/pos-custom-args/captures/** + - tests/run-custom-args/captures/** + - tests/neg-custom-args/captures/** + +env: + DOTTY_CI_RUN: true + DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + +jobs: + suite-with-stdlib-cc: + name: Test Suite with the CC Standard Library + runs-on: ubuntu-latest + steps: + - name: Git Checkout + uses: actions/checkout@v4 + - uses: sbt/setup-sbt@v1 + - name: Test with Scala 2 library with CC TASTy + run: ./project/scripts/sbt ";set ThisBuild/Build.scala2Library := Build.Scala2LibraryCCTasty ;scala3-bootstrapped/test" diff --git a/.github/workflows/test-chocolatey.yml b/.github/workflows/test-chocolatey.yml index b6ca9bf74b12..e302968b9129 100644 --- a/.github/workflows/test-chocolatey.yml +++ b/.github/workflows/test-chocolatey.yml @@ -21,7 +21,10 @@ on: env: CHOCOLATEY-REPOSITORY: chocolatey-pkgs - DOTTY_CI_INSTALLATION: ${{ secrets.GITHUB_TOKEN }} + # Controls behaviour of chocolatey{Install,Uninstall}.ps1 scripts + # During snapshot releases it uses a different layout and requires access token to GH Actions artifacts + # During stable releases it uses publically available archives + DOTTY_CI_INSTALLATION: ${{ endsWith(inputs.version, '-SNAPSHOT') && secrets.GITHUB_TOKEN || '' }} jobs: test: diff --git a/.vscode-template/settings.json b/.vscode-template/settings.json index 257da27b118f..8cf2d29e3bae 100644 --- a/.vscode-template/settings.json +++ b/.vscode-template/settings.json @@ -9,7 +9,6 @@ "**/*.class": true, "**/*.tasty": true, "**/target/": true, - "community-build/community-projects": true, - "tests/pos-with-compiler-cc/dotc/**/*.scala": true + "community-build/community-projects": true } } diff --git a/MAINTENANCE.md b/MAINTENANCE.md index 79e55e11d4a9..47339e17eee5 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -63,39 +63,48 @@ At the end of their supervision period, the supervisor reports to the team durin ## Maintenance List -The following is the list of all the principal areas of the compiler and the core team members who are responsible for their maintenance: +The following is the list of all the principal areas of the compiler and the internal team members who are responsible for their maintenance: ### Compiler -- Parser: @odersky, @hamzaremmal -- Typer: @odersky, @smarter, (@dwijnand), @noti0nal +- Parser: @odersky, @hamzaremmal, @KacperFKorban +- Typer: @odersky, @smarter, (@dwijnand), @noti0nal, @EugeneFlesselle, @KacperFKorban, @bracevac - Erasure: @smarter, @odersky -- Enums: @bishabosha -- Derivation & Mirrors: @bishabosha, (@dwijnand), @EugeneFlesselle -- Export: @bishabosha, @odersky -- Pattern Matching: @dwijnand, (@liufengyun), @sjrd -- Inline: @nicolasstucki, @odersky, @hamzaremmal -- Metaprogramming (Quotes, Reflect, Staging): @nicolasstucki, @jchyb, @hamzaremmal -- Match types: @sjrd, @dwijnand, @Decel, @Linyxus +- Enums: +- Derivation & Mirrors: (@dwijnand), @EugeneFlesselle +- Export: @odersky +- Pattern Matching: @dwijnand, @sjrd, @noti0na1 +- Inline: @odersky, @jchyb, @hamzaremmal, @EugeneFlesselle +- Metaprogramming (Quotes, Reflect, Staging): @jchyb, @hamzaremmal +- Match types: @sjrd, @dwijnand, @Linyxus, @EugeneFlesselle - GADT: @dwijnand, @Linyxus -- Initialization checker: @olhotak, @liufengyun -- Safe nulls: @noti0na1, @olhotak -- Transforms: @szymon-rd, @sjrd, @odersky, @smarter -- tailrec: @sjrd, @mbovel +- Initialization checker: +- Transforms: @sjrd, @odersky, @smarter +- Tailrec: @sjrd, @mbovel - JS backend: @sjrd -- JVM backend: @sjrd -- Java-compat: @smarter, @dwijnand -- Capture checker: @odersky, @Linyxus +- JVM backend: @sjrd, @hamzaremmal +- Java-compat: @smarter, @dwijnand, @hamzaremmal +- Extension Methods: @odersky, @dwijnand +- Safe nulls (experimental): @noti0na1 +- Capture checker (experimental): @odersky, @Linyxus, @bracevac, @noti0na1 +- Modularity (experimental): @KacperFKorban +- Named Tuples (experimental): @odersky, @aherlihy ### Tooling -- REPL: @dwijnand, @prolativ +- REPL: @dwijnand +- Runner/CLI: @Gedochao, (@tgodzik) +- IDE: @tgodzik, (@kasiaMarek) - Scaladoc: @Florian3k -- SemanticDB: @tanishiking -- Coverage: @TheElectronWill -- Linting (especially unused warnings) / Reporting UX: @szymon-rd +- SemanticDB: @natsukagami, (@tanishiking) +- Coverage: @KacperFKorban +- Linting (especially unused warnings) / Reporting UX: @KacperFKorban +- Presentation Compiler: @rochala, @tgodzik, @kasiaMarek, @natsukagami +- Debug Adapter: @adpi2, (@tgodzik) +- Scastie: @rochala ### Infrastructure -- CI: @hamzaremmal +- CI: @hamzaremmal, (@WojciechMazur) - Community Build: @hamzaremmal - Open Community Build: @WojciechMazur -- Vulpix: @dwijnand, @prolativ +- Vulpix: @dwijnand, @prolativ, @hamzaremmal - Benchmarks: @mbovel +- Releases: @WojciechMazur, @prolativ diff --git a/build.sbt b/build.sbt index 9d29bfcb6d6a..9425c4eed1e9 100644 --- a/build.sbt +++ b/build.sbt @@ -36,6 +36,7 @@ val `dist-linux-aarch64` = Build.`dist-linux-aarch64` val `community-build` = Build.`community-build` val `sbt-community-build` = Build.`sbt-community-build` val `scala3-presentation-compiler` = Build.`scala3-presentation-compiler` +val `scala3-presentation-compiler-testcases` = Build.`scala3-presentation-compiler-testcases` val sjsSandbox = Build.sjsSandbox val sjsJUnitTests = Build.sjsJUnitTests diff --git a/changelogs/3.6.4-RC1.md b/changelogs/3.6.4-RC1.md new file mode 100644 index 000000000000..d2889b89a0ab --- /dev/null +++ b/changelogs/3.6.4-RC1.md @@ -0,0 +1,157 @@ +# Highlights of the release + +- Add REPL init script setting [#22206](https://github.com/scala/scala3/pull/22206) +- Support for JDK 24 [#22250](https://github.com/scala/scala3/pull/22250) +- Merge -Xno-decode-stacktraces with -Xno-enrich-error-messages [#22208](https://github.com/scala/scala3/pull/22208) +- Do not lift annotation arguments [#22035](https://github.com/scala/scala3/pull/22035) + +# Other changes and fixes + +## Annotations + +- Make sure symbols in annotation trees are fresh before pickling [#22002](https://github.com/scala/scala3/pull/22002) +- Consider all arguments in Annotations.refersToParamOf [#22001](https://github.com/scala/scala3/pull/22001) +- Do not lift annotation arguments (bis) [#22046](https://github.com/scala/scala3/pull/22046) + +## Desugaring + +- Fix #22051: only trust the type application part for case class unapplies [#22099](https://github.com/scala/scala3/pull/22099) + +## Documentation + +- Update example code linked to obsolete content in macros-spec.md [#22256](https://github.com/scala/scala3/pull/22256) + +## Experimental: Capture Checking + +- Fix #21868, #21869, and #21870: handle CapsOf in more places [#21875](https://github.com/scala/scala3/pull/21875) +- Refine rules for capture parameters and members [#22000](https://github.com/scala/scala3/pull/22000) +- Add a hint for using CC with REPL [#22220](https://github.com/scala/scala3/pull/22220) +- Consolidate CC [#21863](https://github.com/scala/scala3/pull/21863) + +## Experimental: Global Initialization + +- Fix crash when initializing val in ByName closure [#22354](https://github.com/scala/scala3/pull/22354) + +## Experimental: Named Tuples + +- Handle TypeProxy of Named Tuples in unapply [#22325](https://github.com/scala/scala3/pull/22325) +- Fail more eagerly when trying to adapt named unapply patterns [#22315](https://github.com/scala/scala3/pull/22315) +- Widen singleton types when computing fields from .Fields [#22149](https://github.com/scala/scala3/pull/22149) +- Fix .toTuple insertion [#22028](https://github.com/scala/scala3/pull/22028) + +## Extension Methods + +- Tweak ExtensionNullifiedByMember [#22268](https://github.com/scala/scala3/pull/22268) +- Nowarn extension matching nonpublic member [#21825](https://github.com/scala/scala3/pull/21825) + +## Implicits + +- Rollback constraints in compareAppliedTypeParamRef [#22339](https://github.com/scala/scala3/pull/22339) +- Try implicit searching after finding dynamic select [#22318](https://github.com/scala/scala3/pull/22318) + +## Inline + +- Drop phase.isTyper use in isLegalPrefix/asf [#21954](https://github.com/scala/scala3/pull/21954) + +## Linting + +- Allow discarding "Discarded non-Unit" warnings with `: Unit` [#21927](https://github.com/scala/scala3/pull/21927) + +## Match Types + +- Fix #21841: Check more that an `unapplySeq` on a `NonEmptyTuple` is valid. [#22366](https://github.com/scala/scala3/pull/22366) +- Type avoidance in MT bound inference [#22142](https://github.com/scala/scala3/pull/22142) + +## Metaprogramming + +- Rethrow SuspendExceptions caught in CodeGen phase [#22009](https://github.com/scala/scala3/pull/22009) + +## Metaprogramming: Compile-time + +- Extend compiletime.testing.typechecks with certain transform phases [#21185](https://github.com/scala/scala3/pull/21185) + +## Nullability + +- Fix #21619: Refactor NotNullInfo to record every reference which is retracted once. [#21624](https://github.com/scala/scala3/pull/21624) + +## Presentation Compiler + +- Use new infer expected type for singleton complations [#21421](https://github.com/scala/scala3/pull/21421) +- Fix match error in keyword completions [#22138](https://github.com/scala/scala3/pull/22138) + +## Reflection + +- Do not return java outline dummy constructor in `primaryConstructor` [#22104](https://github.com/scala/scala3/pull/22104) + +## Reporting + +- Normalise the types for Type Mismatch Error (E007) [#22337](https://github.com/scala/scala3/pull/22337) +- Improve given search preference warning [#22189](https://github.com/scala/scala3/pull/22189) +- Better error messages when an enum derives from AnyVal [#22236](https://github.com/scala/scala3/pull/22236) +- Correctly print litteral types in the refined printer [#22351](https://github.com/scala/scala3/pull/22351) + +## Rewrites + +- Undo patch of double-block apply [#21982](https://github.com/scala/scala3/pull/21982) + +## Scaladoc + +- Scaladoc: Add support for named tuples [#22263](https://github.com/scala/scala3/pull/22263) + +## Settings + +- Limit exposure to ConcurrentModificationException when sys props are replaced or mutated [#22180](https://github.com/scala/scala3/pull/22180) + +## Specification + +- Align the spec to allow the marker [#22323](https://github.com/scala/scala3/pull/22323) +- Integrate the specification for match types. [#22164](https://github.com/scala/scala3/pull/22164) + +## Transform + +- Fix #22226: Use `classOf[BoxedUnit]` for Unit array in `ArrayConstructors`. [#22238](https://github.com/scala/scala3/pull/22238) + +## Typer + +- Fixes for isLegalPrefix change [#22241](https://github.com/scala/scala3/pull/22241) +- Resolve name when named imp is behind wild imps [#21888](https://github.com/scala/scala3/pull/21888) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.6.3..3.6.4-RC1` these are: + +``` + 46 Martin Odersky + 33 noti0na1 + 17 Wojciech Mazur + 14 Dale Wijnand + 13 Matt Bovel + 11 Hamza Remmal + 7 Jan Chyb + 6 aherlihy + 5 Kacper Korban + 5 Seth Tisue + 5 Som Snytt + 4 Oliver Bračevac + 4 Yichen Xu + 3 Sébastien Doeraene + 3 dependabot[bot] + 3 kasiaMarek + 2 João Ferreira + 1 David Hua + 1 Eugene Flesselle + 1 Eugene Yokota + 1 Florian3k + 1 Jędrzej Rochala + 1 Kenji Yoshida + 1 Mathias + 1 Natsu Kagami + 1 Oleg Zenzin + 1 Piotr Chabelski + 1 Rui Chen + 1 philippus + 1 rochala + 1 xiaoshihou +``` diff --git a/changelogs/3.6.4-RC2.md b/changelogs/3.6.4-RC2.md new file mode 100644 index 000000000000..1edfad6321ee --- /dev/null +++ b/changelogs/3.6.4-RC2.md @@ -0,0 +1,13 @@ +# Reverted changes + +- Revert "Drop phase.isTyper use in isLegalPrefix/asf" from Scala 3.6.4 [#22653](https://github.com/lampepfl/dotty/pull/22653) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.6.4-RC1..3.6.4-RC2` these are: + +``` + 5 Wojciech Mazur +``` diff --git a/changelogs/3.6.4.md b/changelogs/3.6.4.md new file mode 100644 index 000000000000..43d2502f7218 --- /dev/null +++ b/changelogs/3.6.4.md @@ -0,0 +1,168 @@ + +# Highlights of the release + +- Support for JDK 24 [#22250](https://github.com/scala/scala3/pull/22250) +- REPL `:silent` command to toggle automatic printing of outputs [#22248](https://github.com/scala/scala3/pull/22248) +- REPL `--repl-init-script:` setting to run a code on startup [#22206](https://github.com/scala/scala3/pull/22206) +- Deprecated setting `-Xno-decode-stacktraces` is now an alias to `-Xno-enrich-error-messages` [#22208](https://github.com/scala/scala3/pull/22208) +- Annotation arguments are no longer lifted [#22035](https://github.com/scala/scala3/pull/22035) +- Experimental Capture Checking: Implement tracked members [#21761](https://github.com/scala/scala3/pull/21761) + +## Breaking changes + +- Align `@implicitNotFound` and `@implicitAmbigous` with the language specification [#22371](https://github.com/scala/scala3/pull/22371) + + This change may impact users who previously used these annotations using variables or string interpolation. + + Previously, a bug in the Scala 3 compiler allowed non-literal strings to be passed as arguments to the `@implicitNotFound` and `@implicitAmbiguous` annotations. + This could have affected how failed implicit search results were reported by the compiler. + + Starting from Scala 3.6.4, the arguments for these annotations must be string literals. + If a message is too long, it can be concatenated using the `+` operator, allowing for constant folding. + +# Other changes and fixes + +## Annotations + +- Make sure symbols in annotation trees are fresh before pickling [#22002](https://github.com/scala/scala3/pull/22002) +- Consider all arguments in Annotations.refersToParamOf [#22001](https://github.com/scala/scala3/pull/22001) +- Do not lift annotation arguments (bis) [#22046](https://github.com/scala/scala3/pull/22046) + +## Desugaring + +- Fix #22051: only trust the type application part for case class unapplies [#22099](https://github.com/scala/scala3/pull/22099) + +## Documentation + +- Update example code linked to obsolete content in macros-spec.md [#22256](https://github.com/scala/scala3/pull/22256) + +## Experimental: Capture Checking + +- Fix #21868, #21869, and #21870: handle CapsOf in more places [#21875](https://github.com/scala/scala3/pull/21875) +- Refine rules for capture parameters and members [#22000](https://github.com/scala/scala3/pull/22000) +- Add a hint for using CC with REPL [#22220](https://github.com/scala/scala3/pull/22220) +- Consolidate CC [#21863](https://github.com/scala/scala3/pull/21863) + +## Experimental: Global Initialization + +- Fix crash when initializing val in ByName closure [#22354](https://github.com/scala/scala3/pull/22354) + +## Experimental: Named Tuples + +- Handle TypeProxy of Named Tuples in unapply [#22325](https://github.com/scala/scala3/pull/22325) +- Fail more eagerly when trying to adapt named unapply patterns [#22315](https://github.com/scala/scala3/pull/22315) +- Widen singleton types when computing fields from .Fields [#22149](https://github.com/scala/scala3/pull/22149) +- Fix .toTuple insertion [#22028](https://github.com/scala/scala3/pull/22028) + +## Extension Methods + +- Tweak ExtensionNullifiedByMember [#22268](https://github.com/scala/scala3/pull/22268) +- Nowarn extension matching nonpublic member [#21825](https://github.com/scala/scala3/pull/21825) + +## Implicits + +- Rollback constraints in compareAppliedTypeParamRef [#22339](https://github.com/scala/scala3/pull/22339) +- Try implicit searching after finding dynamic select [#22318](https://github.com/scala/scala3/pull/22318) + +## Linting + +- Allow discarding "Discarded non-Unit" warnings with `: Unit` [#21927](https://github.com/scala/scala3/pull/21927) + +## Match Types + +- Fix #21841: Check more that an `unapplySeq` on a `NonEmptyTuple` is valid. [#22366](https://github.com/scala/scala3/pull/22366) +- Type avoidance in MT bound inference [#22142](https://github.com/scala/scala3/pull/22142) + +## Metaprogramming + +- Rethrow SuspendExceptions caught in CodeGen phase [#22009](https://github.com/scala/scala3/pull/22009) + +## Metaprogramming: Compile-time + +- Extend compiletime.testing.typechecks with certain transform phases [#21185](https://github.com/scala/scala3/pull/21185) + +## Nullability + +- Fix #21619: Refactor NotNullInfo to record every reference which is retracted once. [#21624](https://github.com/scala/scala3/pull/21624) + +## Presentation Compiler + +- Use new infer expected type for singleton complations [#21421](https://github.com/scala/scala3/pull/21421) +- Fix match error in keyword completions [#22138](https://github.com/scala/scala3/pull/22138) + +## Reflection + +- Do not return java outline dummy constructor in `primaryConstructor` [#22104](https://github.com/scala/scala3/pull/22104) + +## Reporting + +- Normalise the types for Type Mismatch Error (E007) [#22337](https://github.com/scala/scala3/pull/22337) +- Improve given search preference warning [#22189](https://github.com/scala/scala3/pull/22189) +- Better error messages when an enum derives from AnyVal [#22236](https://github.com/scala/scala3/pull/22236) +- Correctly print litteral types in the refined printer [#22351](https://github.com/scala/scala3/pull/22351) + +## Rewrites + +- Undo patch of double-block apply [#21982](https://github.com/scala/scala3/pull/21982) + +## Scaladoc + +- Scaladoc: Add support for named tuples [#22263](https://github.com/scala/scala3/pull/22263) + +## Settings + +- Limit exposure to ConcurrentModificationException when sys props are replaced or mutated [#22180](https://github.com/scala/scala3/pull/22180) + +## Specification + +- Align the spec to allow the marker [#22323](https://github.com/scala/scala3/pull/22323) +- Integrate the specification for match types. [#22164](https://github.com/scala/scala3/pull/22164) + +## Transform + +- Fix #22226: Use `classOf[BoxedUnit]` for Unit array in `ArrayConstructors`. [#22238](https://github.com/scala/scala3/pull/22238) + +## Typer + +- Fixes for isLegalPrefix change [#22241](https://github.com/scala/scala3/pull/22241) +- Resolve name when named imp is behind wild imps [#21888](https://github.com/scala/scala3/pull/21888) + +# Contributors + +Thank you to all the contributors who made this release possible 🎉 + +According to `git shortlog -sn --no-merges 3.6.3..3.6.4` these are: + +``` + 46 Martin Odersky + 33 noti0na1 + 24 Wojciech Mazur + 14 Dale Wijnand + 13 Matt Bovel + 11 Hamza Remmal + 7 Jan Chyb + 6 aherlihy + 5 Kacper Korban + 5 Seth Tisue + 5 Som Snytt + 4 Oliver Bračevac + 4 Yichen Xu + 3 Sébastien Doeraene + 3 dependabot[bot] + 3 kasiaMarek + 2 João Ferreira + 1 David Hua + 1 Eugene Flesselle + 1 Eugene Yokota + 1 Florian3k + 1 Jędrzej Rochala + 1 Kenji Yoshida + 1 Mathias + 1 Natsu Kagami + 1 Oleg Zenzin + 1 Piotr Chabelski + 1 Rui Chen + 1 philippus + 1 rochala + 1 xiaoshihou +``` diff --git a/community-build/community-projects/fastparse b/community-build/community-projects/fastparse index 8b93438064ae..4c416b222ad3 160000 --- a/community-build/community-projects/fastparse +++ b/community-build/community-projects/fastparse @@ -1 +1 @@ -Subproject commit 8b93438064aecc7b94f4647af47c0fa237bf89a9 +Subproject commit 4c416b222ad3dcf0bc71656e22407fa49aec193a diff --git a/community-build/src/scala/dotty/communitybuild/projects.scala b/community-build/src/scala/dotty/communitybuild/projects.scala index 31c1bb95743c..2575bb2037c0 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.9.7", + "-sbt-version", "1.10.5", "-Dsbt.supershell=false", s"-Ddotty.communitybuild.dir=$communitybuildDir", s"--addPluginSbtFile=$sbtPluginFilePath" diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 4f4caf36d92a..e632def24700 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -515,7 +515,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { */ object locals { - private val slots = mutable.AnyRefMap.empty[Symbol, Local] // (local-or-param-sym -> Local(BType, name, idx, isSynth)) + private val slots = mutable.HashMap.empty[Symbol, Local] // (local-or-param-sym -> Local(BType, name, idx, isSynth)) private var nxtIdx = -1 // next available index for local-var diff --git a/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala b/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala index cb7ed3d54788..ae423b6b80dd 100644 --- a/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala +++ b/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala @@ -185,6 +185,7 @@ object BackendUtils { 20 -> asm.Opcodes.V20, 21 -> asm.Opcodes.V21, 22 -> asm.Opcodes.V22, - 23 -> asm.Opcodes.V23 + 23 -> asm.Opcodes.V23, + 24 -> asm.Opcodes.V24 ) } diff --git a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala index c5b0ec0929b8..d4843cd56639 100644 --- a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala +++ b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala @@ -84,6 +84,7 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( registerGeneratedClass(mirrorClassNode, isArtifact = true) catch 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) diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index 0975c94e916a..0d755797d026 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -155,6 +155,14 @@ object CompilationUnit { unit1 } + /** Create a compilation unit corresponding to an in-memory String. + * Used for `compiletime.testing.typeChecks`. + */ + def apply(name: String, source: String)(using Context): CompilationUnit = { + val src = SourceFile.virtual(name = name, content = source, maybeIncomplete = false) + new CompilationUnit(src, null) + } + /** Create a compilation unit corresponding to `source`. * If `mustExist` is true, this will fail if `source` does not exist. */ diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 56c153498f87..4ddd0006dc26 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -694,15 +694,15 @@ object desugar { val originalTparams = constr1.leadingTypeParams val originalVparamss = asTermOnly(constr1.trailingParamss) lazy val derivedEnumParams = enumClass.typeParams.map(derivedTypeParamWithVariance) - val impliedTparams = - if (isEnumCase) { + val enumTParams = + if isEnumCase then val tparamReferenced = typeParamIsReferenced( - enumClass.typeParams, originalTparams, originalVparamss, parents) - if (originalTparams.isEmpty && (parents.isEmpty || tparamReferenced)) + enumClass.typeParams, originalTparams, originalVparamss, parents) + if originalTparams.isEmpty && (parents.isEmpty || tparamReferenced) then derivedEnumParams.map(tdef => tdef.withFlags(tdef.mods.flags | PrivateLocal)) - else originalTparams - } - else originalTparams + else Nil + else Nil + val impliedTparams = enumTParams ++ originalTparams if mods.is(Trait) then for vparams <- originalVparamss; vparam <- vparams do @@ -735,6 +735,11 @@ object desugar { derived.withAnnotations(Nil) val constr = cpy.DefDef(constr1)(paramss = joinParams(constrTparams, constrVparamss)) + if enumTParams.nonEmpty then + defaultGetters = defaultGetters.map: + case ddef: DefDef => + val tParams = enumTParams.map(tparam => toMethParam(tparam, KeepAnnotations.All)) + cpy.DefDef(ddef)(paramss = joinParams(tParams, ddef.trailingParamss)) val (normalizedBody, enumCases, enumCompanionRef) = { // Add constructor type parameters and evidence implicit parameters @@ -1081,12 +1086,13 @@ object desugar { if mods.isAllOf(Given | Inline | Transparent) then report.error("inline given instances cannot be trasparent", cdef) var classMods = if mods.is(Given) then mods &~ (Inline | Transparent) | Synthetic else mods - if vparamAccessors.exists(_.mods.is(Tracked)) then + val newBody = tparamAccessors ::: vparamAccessors ::: normalizedBody ::: caseClassMeths + if newBody.collect { case d: ValOrDefDef => d }.exists(_.mods.is(Tracked)) then classMods |= Dependent cpy.TypeDef(cdef: TypeDef)( name = className, rhs = cpy.Template(impl)(constr, parents1, clsDerived, self1, - tparamAccessors ::: vparamAccessors ::: normalizedBody ::: caseClassMeths) + newBody) ).withMods(classMods) } @@ -1556,6 +1562,12 @@ object desugar { rhsOK(rhs) } + val legalTracked: Context ?=> MemberDefTest = { + case valdef @ ValDef(_, _, _) => + val sym = valdef.symbol + !ctx.owner.exists || ctx.owner.isClass || ctx.owner.is(Case) || ctx.owner.isConstructor || valdef.mods.is(Param) || valdef.mods.is(ParamAccessor) + } + def checkOpaqueAlias(tree: MemberDef)(using Context): MemberDef = def check(rhs: Tree): MemberDef = rhs match case bounds: TypeBoundsTree if bounds.alias.isEmpty => @@ -1581,6 +1593,7 @@ object desugar { } else tested tested = checkOpaqueAlias(tested) tested = checkApplicable(Opaque, legalOpaque) + tested = checkApplicable(Tracked, legalTracked) tested case _ => tree @@ -1654,7 +1667,7 @@ object desugar { AppliedTypeTree( TypeTree(defn.throwsAlias.typeRef).withSpan(op.span), tpt :: excepts :: Nil) - private def checkWellFormedTupleElems(elems: List[Tree])(using Context): List[Tree] = + def checkWellFormedTupleElems(elems: List[Tree])(using Context): List[Tree] = val seen = mutable.Set[Name]() for case arg @ NamedArg(name, _) <- elems do if seen.contains(name) then @@ -1731,7 +1744,7 @@ object desugar { def adaptPatternArgs(elems: List[Tree], pt: Type)(using Context): List[Tree] = def reorderedNamedArgs(wildcardSpan: Span): List[untpd.Tree] = - var selNames = pt.namedTupleElementTypes.map(_(0)) + 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 diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 09c855847fac..e0fe17755257 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -144,7 +144,7 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] => def allTermArguments(tree: Tree): List[Tree] = unsplice(tree) match { case Apply(fn, args) => allTermArguments(fn) ::: args case TypeApply(fn, args) => allTermArguments(fn) - case Block(_, expr) => allTermArguments(expr) + case Block(Nil, expr) => allTermArguments(expr) case _ => Nil } @@ -152,7 +152,7 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] => def allArguments(tree: Tree): List[Tree] = unsplice(tree) match { case Apply(fn, args) => allArguments(fn) ::: args case TypeApply(fn, args) => allArguments(fn) ::: args - case Block(_, expr) => allArguments(expr) + case Block(Nil, expr) => allArguments(expr) case _ => Nil } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 55021bf50ace..77e3387c5ce0 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1137,6 +1137,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { /** Replace Ident nodes references to the underlying tree that defined them */ def underlying(using Context): Tree = MapToUnderlying().transform(tree) + /** Collect all the TypeSymbol's of the type Bind nodes in the tree. */ + def bindTypeSymbols(using Context): List[TypeSymbol] = + tree.collectSubTrees { case b: Bind if b.isType => b.symbol.asType } + // --- Higher order traversal methods ------------------------------- /** Apply `f` to each subtree of this tree */ diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index e8e3646bd087..2acfc4cf86e3 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -527,10 +527,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def makeCapsOf(tp: RefTree)(using Context): Tree = TypeApply(Select(scalaDot(nme.caps), nme.capsOf), tp :: Nil) - def makeCapsBound()(using Context): Tree = - makeRetaining( + // Capture set variable `[C^]` becomes: `[C >: CapSet <: CapSet^{cap}]` + def makeCapsBound()(using Context): TypeBoundsTree = + TypeBoundsTree( Select(scalaDot(nme.caps), tpnme.CapSet), - Nil, tpnme.retainsCap) + makeRetaining( + Select(scalaDot(nme.caps), tpnme.CapSet), + Nil, tpnme.retainsCap)) def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(using Context): DefDef = DefDef(nme.CONSTRUCTOR, joinParams(tparams, vparamss), TypeTree(), rhs) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala index e437a8ad5d5f..f0018cc93d7e 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala @@ -42,6 +42,7 @@ case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) exte case cr: TermRef => ref(cr) case cr: TermParamRef => untpd.Ident(cr.paramName).withType(cr) case cr: ThisType => This(cr.cls) + // TODO: Will crash if the type is an annotated type, for example `cap?` } val arg = repeated(elems, TypeTree(defn.AnyType)) New(symbol.typeRef, arg :: Nil) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index aad6ca8ddeac..bc4eb92234eb 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -27,32 +27,25 @@ object ccConfig: */ inline val allowUnsoundMaps = false - /** If true, when computing the memberinfo of a refined type created - * by addCaptureRefinements take the refineInfo directly without intersecting - * with the parent info. - */ - inline val optimizedRefinements = false - /** If enabled, use a special path in recheckClosure for closures - * that are eta expansions. This can improve some error messages but - * currently leads to unsoundess for handling reach capabilities. - * TODO: The unsoundness needs followin up. + * that are eta expansions. This can improve some error messages. */ - inline val handleEtaExpansionsSpecially = false + inline val handleEtaExpansionsSpecially = true - /** If true, use existential capture set variables */ - def useExistentials(using Context) = - Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.5`) + /** Don't require @use for reach capabilities that are accessed + * only in a nested closure. This is unsound without additional + * mitigation measures, as shown by unsound-reach-5.scala. + */ + inline val deferredReaches = false /** If true, use "sealed" as encapsulation mechanism, meaning that we * check that type variable instantiations don't have `cap` in any of * their capture sets. This is an alternative of the original restriction - * that `cap` can't be boxed or unboxed. It is used in 3.3 and 3.4 but - * dropped again in 3.5. + * that `cap` can't be boxed or unboxed. It is dropped in 3.5 but used + * again in 3.6. */ def useSealed(using Context) = - Feature.sourceVersion.stable == SourceVersion.`3.3` - || Feature.sourceVersion.stable == SourceVersion.`3.4` + Feature.sourceVersion.stable != SourceVersion.`3.5` end ccConfig @@ -134,10 +127,6 @@ end CCState def ccState(using Context) = Phases.checkCapturesPhase.asInstanceOf[CheckCaptures].ccState1 -class NoCommonRoot(rs: Symbol*)(using Context) extends Exception( - i"No common capture root nested in ${rs.mkString(" and ")}" -) - extension (tree: Tree) /** Map tree with CaptureRef type to its type, @@ -198,7 +187,7 @@ extension (tp: Type) || tp.isRootCapability ) && !tp.symbol.isOneOf(UnstableValueFlags) case tp: TypeRef => - tp.symbol.isAbstractOrParamType && tp.derivesFrom(defn.Caps_CapSet) + tp.symbol.isType && tp.derivesFrom(defn.Caps_CapSet) case tp: TypeParamRef => tp.derivesFrom(defn.Caps_CapSet) case AnnotatedType(parent, annot) => @@ -221,19 +210,25 @@ extension (tp: Type) case tp: SingletonCaptureRef => tp.captureSetOfInfo case _ => CaptureSet.ofType(tp, followResult = false) - /** The deep capture set of a type. - * For singleton capabilities `x` and reach capabilities `x*`, this is `{x*}`, provided - * the underlying capture set resulting from traversing the type is non-empty. - * For other types this is the union of all covariant capture sets embedded - * in the type, as computed by `CaptureSet.ofTypeDeeply`. + /** The deep capture set of a type. This is by default the union of all + * covariant capture sets embedded in the widened type, as computed by + * `CaptureSet.ofTypeDeeply`. If that set is nonempty, and the type is + * a singleton capability `x` or a reach capability `x*`, the deep capture + * set can be narrowed to`{x*}`. */ - def deepCaptureSet(using Context): CaptureSet = - val dcs = CaptureSet.ofTypeDeeply(tp) - if dcs.isAlwaysEmpty then dcs + def deepCaptureSet(includeTypevars: Boolean)(using Context): CaptureSet = + val dcs = CaptureSet.ofTypeDeeply(tp.widen.stripCapturing, includeTypevars) + if dcs.isAlwaysEmpty then tp.captureSet else tp match - case tp @ ReachCapability(_) => tp.singletonCaptureSet - case tp: SingletonCaptureRef => tp.reach.singletonCaptureSet - case _ => dcs + case tp @ ReachCapability(_) => + tp.singletonCaptureSet + case tp: SingletonCaptureRef 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 = @@ -273,6 +268,29 @@ extension (tp: Type) case _ => tp + /** The first element of this path type */ + final def pathRoot(using Context): Type = tp.dealias match + case tp1: NamedType if tp1.symbol.owner.isClass => 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. @@ -456,53 +474,35 @@ extension (tp: Type) * occurrences of cap are allowed in instance types of type variables. */ def withReachCaptures(ref: Type)(using Context): Type = - class CheckContraCaps extends TypeTraverser: - var ok = true - def traverse(t: Type): Unit = - if ok then - t.dealias match - case CapturingType(_, cs) if cs.isUniversal && variance <= 0 => - ok = false - case _ => - traverseChildren(t) - end CheckContraCaps - object narrowCaps extends TypeMap: - /** Has the variance been flipped at this point? */ - private var isFlipped: Boolean = false - + var change = false def apply(t: Type) = - val saved = isFlipped - try - if variance <= 0 then isFlipped = true - t.dealias match - case t1 @ CapturingType(p, cs) if cs.isUniversal && !isFlipped => - t1.derivedCapturingType(apply(p), ref.reach.singletonCaptureSet) - case t1 @ FunctionOrMethod(args, res @ Existential(_, _)) - if args.forall(_.isAlwaysPure) => - // Also map existentials in results to reach capabilities if all - // preceding arguments are known to be always pure - apply(t1.derivedFunctionOrMethod(args, Existential.toCap(res))) - case Existential(_, _) => - t - case _ => t match - case t @ CapturingType(p, cs) => - t.derivedCapturingType(apply(p), cs) // don't map capture set variables - case t => - mapOver(t) - finally isFlipped = saved + if variance <= 0 then t + else t.dealiasKeepAnnots match + case t @ CapturingType(p, cs) if cs.isUniversal => + change = true + t.derivedCapturingType(apply(p), ref.reach.singletonCaptureSet) + case t @ AnnotatedType(parent, ann) => + // Don't map annotations, which includes capture sets + t.derivedAnnotatedType(this(parent), ann) + case t @ FunctionOrMethod(args, res @ Existential(_, _)) + if args.forall(_.isAlwaysPure) => + // Also map existentials in results to reach capabilities if all + // preceding arguments are known to be always pure + apply(t.derivedFunctionOrMethod(args, Existential.toCap(res))) + case Existential(_, _) => + t + case _ => + mapOver(t) end narrowCaps ref match case ref: CaptureRef if ref.isTrackableRef => - val checker = new CheckContraCaps - if !ccConfig.useExistentials then checker.traverse(tp) - if checker.ok then - val tp1 = narrowCaps(tp) - if tp1 ne tp then capt.println(i"narrow $tp of $ref to $tp1") + val tp1 = narrowCaps(tp) + if narrowCaps.change then + capt.println(i"narrow $tp of $ref to $tp1") tp1 else - capt.println(i"cannot narrow $tp of $ref") tp case _ => tp @@ -588,17 +588,14 @@ extension (sym: Symbol) case _ => false containsEnclTypeParam(sym.info.finalResultType) && !sym.allowsRootCapture - && sym != defn.Caps_unsafeBox - && sym != defn.Caps_unsafeUnbox && !defn.isPolymorphicAfterErasure(sym) && !defn.isTypeTestOrCast(sym) + /** It's a parameter accessor that is not annotated @constructorOnly or @uncheckedCaptures */ def isRefiningParamAccessor(using Context): Boolean = sym.is(ParamAccessor) && { - val param = sym.owner.primaryConstructor.paramSymss - .nestedFind(_.name == sym.name) - .getOrElse(NoSymbol) + val param = sym.owner.primaryConstructor.paramNamed(sym.name) !param.hasAnnotation(defn.ConstructorOnlyAnnot) && !param.hasAnnotation(defn.UntrackedCapturesAnnot) } @@ -606,6 +603,18 @@ 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 + * @use-annotated term parameter that contains `sym` in its deep capture set. + */ + def isUseParam(using Context): Boolean = + sym.hasAnnotation(defn.UseAnnot) + || sym.is(TypeParam) + && sym.owner.rawParamss.nestedExists: param => + param.is(TermParam) && param.hasAnnotation(defn.UseAnnot) + && param.info.deepCaptureSet.elems.exists: + case c: TypeRef => c.symbol == sym + case _ => false + extension (tp: AnnotatedType) /** Is this a boxed capturing type? */ def isBoxed(using Context): Boolean = tp.annot match @@ -639,8 +648,8 @@ object CapsOfApply: class AnnotatedCapability(annot: Context ?=> ClassSymbol): def apply(tp: Type)(using Context) = AnnotatedType(tp, Annotation(annot, util.Spans.NoSpan)) - def unapply(tree: AnnotatedType)(using Context): Option[SingletonCaptureRef] = tree match - case AnnotatedType(parent: SingletonCaptureRef, ann) if ann.symbol == annot => Some(parent) + def unapply(tree: AnnotatedType)(using Context): Option[CaptureRef] = tree match + case AnnotatedType(parent: CaptureRef, ann) if ann.symbol == annot => Some(parent) case _ => None /** An extractor for `ref @annotation.internal.reachCapability`, which is used to express diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala index 590beda42903..9bda9102cbb8 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala @@ -93,12 +93,19 @@ trait CaptureRef extends TypeProxy, ValueType: final def invalidateCaches() = myCaptureSetRunId = NoRunId - /** x subsumes x - * this subsumes this.f + /** 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 - * TODO: Document path cases + * 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 + * + * TODO: Document cases with more comments. */ final def subsumes(y: CaptureRef)(using Context): Boolean = @@ -135,12 +142,29 @@ trait CaptureRef extends TypeProxy, ValueType: case _ => false || viaInfo(y.info)(subsumingRefs(this, _)) case MaybeCapability(y1) => this.stripMaybe.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) => + 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: TermParamRef => subsumesExistentially(x, y) - case x: TypeRef => assumedContainsOf(x).contains(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 diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 81b4287961ba..1750e98f708a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -161,7 +161,9 @@ sealed abstract class CaptureSet extends Showable: def debugInfo(using Context) = i"$this accountsFor $x, which has capture set ${x.captureSetOfInfo}" def test(using Context) = reporting.trace(debugInfo): elems.exists(_.subsumes(x)) - || !x.isMaxCapability && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK + || !x.isMaxCapability + && !x.derivesFrom(defn.Caps_CapSet) + && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK comparer match case comparer: ExplainingTypeComparer => comparer.traceIndented(debugInfo)(test) case _ => test @@ -185,10 +187,12 @@ sealed abstract class CaptureSet extends Showable: /** A more optimistic version of subCaptures used to choose one of two typing rules * for selections and applications. `cs1 mightSubcapture cs2` if `cs2` might account for - * every element currently known to be in `cs1`. + * every element currently known to be in `cs1`, and the same is not true in reverse + * when we compare elements of cs2 vs cs1. */ def mightSubcapture(that: CaptureSet)(using Context): Boolean = elems.forall(that.mightAccountFor) + && !that.elems.forall(this.mightAccountFor) /** The subcapturing test. * @param frozen if true, no new variables or dependent sets are allowed to @@ -1064,8 +1068,9 @@ object CaptureSet: case ref: (TermRef | TermParamRef) if ref.isMaxCapability => if ref.isTrackableRef then ref.singletonCaptureSet else CaptureSet.universal - case ReachCapability(ref1) => deepCaptureSet(ref1.widen) - .showing(i"Deep capture set of $ref: ${ref1.widen} = $result", capt) + case ReachCapability(ref1) => + ref1.widen.deepCaptureSet(includeTypevars = true) + .showing(i"Deep capture set of $ref: ${ref1.widen} = ${result}", capt) case _ => ofType(ref.underlying, followResult = true) /** Capture set of a type */ @@ -1115,17 +1120,33 @@ object CaptureSet: /** The deep capture set of a type is the union of all covariant occurrences of * capture sets. Nested existential sets are approximated with `cap`. + * NOTE: The traversal logic needs to be in sync with narrowCaps in CaptureOps, which + * replaces caps with reach capabilties. The one exception to this is invariant + * arguments. This have to be included to be conservative in dcs but must be + * excluded in narrowCaps. */ - def ofTypeDeeply(tp: Type)(using Context): CaptureSet = + def ofTypeDeeply(tp: Type, includeTypevars: Boolean = false)(using Context): CaptureSet = val collect = new TypeAccumulator[CaptureSet]: - def apply(cs: CaptureSet, t: Type) = t.dealias match - case t @ CapturingType(p, cs1) => - val cs2 = apply(cs, p) - if variance > 0 then cs2 ++ cs1 else cs2 - case t @ Existential(_, _) => - apply(cs, Existential.toCap(t)) - case _ => - foldOver(cs, t) + val seen = util.HashSet[Symbol]() + def apply(cs: CaptureSet, t: Type) = + if variance < 0 then cs + else t.dealias match + case t @ CapturingType(p, cs1) => + this(cs, p) ++ cs1 + case t @ AnnotatedType(parent, ann) => + this(cs, parent) + case t: TypeRef if t.symbol.isAbstractOrParamType && !seen.contains(t.symbol) => + seen += t.symbol + val upper = t.info.bounds.hi + if includeTypevars && upper.isExactlyAny then CaptureSet.universal + else this(cs, upper) + case t @ FunctionOrMethod(args, res @ Existential(_, _)) + if args.forall(_.isAlwaysPure) => + this(cs, Existential.toCap(res)) + case t @ Existential(_, _) => + cs + case _ => + foldOver(cs, t) collect(CaptureSet.empty, tp) type AssumedContains = immutable.Map[TypeRef, SimpleIdentitySet[CaptureRef]] diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 77d893ad49b9..830d9ad0a4d4 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -36,20 +36,22 @@ object CheckCaptures: case NestedInOwner // environment is a temporary one nested in the owner's environment, // and does not have a different actual owner symbol // (this happens when doing box adaptation). - case ClosureResult // environment is for the result of a closure case Boxed // environment is inside a box (in which case references are not counted) /** A class describing environments. - * @param owner the current owner - * @param kind the environment's kind - * @param captured the capture set containing all references to tracked free variables outside of boxes - * @param outer0 the next enclosing environment + * @param owner the current owner + * @param kind the environment's kind + * @param captured the capture set containing all references to tracked free variables outside of boxes + * @param outer0 the next enclosing environment + * @param nestedClosure under deferredReaches: If this is an env of a method with an anonymous function or + * anonymous class as RHS, the symbol of that function or class. NoSymbol in all other cases. */ case class Env( owner: Symbol, kind: EnvKind, captured: CaptureSet, - outer0: Env | Null): + outer0: Env | Null, + nestedClosure: Symbol = NoSymbol): def outer = outer0.nn @@ -88,6 +90,9 @@ object CheckCaptures: mapOver(tp) end SubstParamsMap + /** Used for substituting parameters in a special case: when all actual arguments + * are mutually distinct capabilities. + */ final class SubstParamsBiMap(from: LambdaType, to: List[Type])(using Context) extends BiTypeMap: thisMap => @@ -121,6 +126,9 @@ object CheckCaptures: def inverse = thisMap end SubstParamsBiMap + /** A prototype that indicates selection with an immutable value */ + class PathSelectionProto(val sym: Symbol, val pt: Type)(using Context) extends WildcardSelectionProto + /** Check that a @retains annotation only mentions references that can be tracked. * This check is performed at Typer. */ @@ -135,7 +143,7 @@ object CheckCaptures: elem match case CapsOfApply(arg) => def isLegalCapsOfArg = - arg.symbol.isAbstractOrParamType && arg.symbol.info.derivesFrom(defn.Caps_CapSet) + arg.symbol.isType && arg.symbol.info.derivesFrom(defn.Caps_CapSet) if !isLegalCapsOfArg then report.error( em"""$arg is not a legal prefix for `^` here, @@ -144,9 +152,9 @@ object CheckCaptures: case ReachCapabilityApply(arg) => check(arg, elem.srcPos) case _ => check(elem, elem.srcPos) - /** Report an error if some part of `tp` contains the root capability in its capture set - * or if it refers to an unsealed type parameter that could possibly be instantiated with - * cap in a way that's visible at the type. + /** 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) = val check = new TypeTraverser: @@ -174,8 +182,7 @@ object CheckCaptures: def part = if t eq tp then "" else i"the part $t of " report.error( em"""$what cannot $have $tp since - |${part}that type captures the root capability `cap`. - |$addendum""", + |${part}that type captures the root capability `cap`.$addendum""", pos) traverse(parent) case t => @@ -183,11 +190,54 @@ object CheckCaptures: if ccConfig.useSealed then check.traverse(tp) end disallowRootCapabilitiesIn - /** Attachment key for bodies of closures, provided they are values */ - val ClosureBodyValue = Property.Key[Unit] + /** If we are not under the sealed policy, and a tree is an application that unboxes + * its result or is a try, check that the tree's type does not have covariant universal + * capabilities. + */ + private def checkNotUniversalInUnboxedResult(tpe: Type, tree: Tree)(using Context): Unit = + def needsUniversalCheck = tree match + case _: RefTree | _: Apply | _: TypeApply => tree.symbol.unboxesResult + case _: Try => true + case _ => false + + object checkNotUniversal extends TypeTraverser: + def traverse(tp: Type) = + tp.dealias match + case wtp @ CapturingType(parent, refs) => + if variance > 0 then + refs.disallowRootCapability: () => + def part = if wtp eq tpe.widen then "" else i" in its part $wtp" + report.error( + em"""The expression's type ${tpe.widen} is not allowed to capture the root capability `cap`$part. + |This usually means that a capability persists longer than its allowed lifetime.""", + tree.srcPos) + if !wtp.isBoxed then traverse(parent) + case tp => + traverseChildren(tp) - /** A prototype that indicates selection with an immutable value */ - class PathSelectionProto(val sym: Symbol, val pt: Type)(using Context) extends WildcardSelectionProto + if !ccConfig.useSealed + && !tpe.hasAnnotation(defn.UncheckedCapturesAnnot) + && needsUniversalCheck + && tpe.widen.isValueType + then + checkNotUniversal.traverse(tpe.widen) + end checkNotUniversalInUnboxedResult + + trait CheckerAPI: + /** Complete symbol info of a val or a def */ + def completeDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type + + extension [T <: Tree](tree: T) + + /** Set new type of the tree if none was installed yet. */ + def setNuType(tpe: Type): Unit + + /** The new type of the tree, or if none was installed, the original type */ + def nuType(using Context): Type + + /** Was a new type installed for this tree? */ + def hasNuType: Boolean + end CheckerAPI class CheckCaptures extends Recheck, SymTransformer: thisPhase => @@ -209,11 +259,23 @@ class CheckCaptures extends Recheck, SymTransformer: val ccState1 = new CCState // Dotty problem: Rename to ccState ==> Crash in ExplicitOuter - class CaptureChecker(ictx: Context) extends Rechecker(ictx): + class CaptureChecker(ictx: Context) extends Rechecker(ictx), CheckerAPI: - override def keepType(tree: Tree) = - super.keepType(tree) - || tree.isInstanceOf[Try] // type of `try` needs tp be checked for * escapes + /** The current environment */ + private val rootEnv: Env = inContext(ictx): + Env(defn.RootClass, EnvKind.Regular, CaptureSet.empty, null) + private var curEnv = rootEnv + + /** Currently checked closures and their expected types, used for error reporting */ + private var openClosures: List[(Symbol, Type)] = Nil + + private val myCapturedVars: util.EqHashMap[Symbol, CaptureSet] = EqHashMap() + + /** A list of actions to perform at postCheck. The reason to defer these actions + * is that it is sometimes better for type inference to not constrain too early + * with a checkConformsExpr. + */ + private val todoAtPostCheck = new mutable.ListBuffer[() => Unit] /** Instantiate capture set variables appearing contra-variantly to their * upper approximation. @@ -236,16 +298,17 @@ class CheckCaptures extends Recheck, SymTransformer: */ private def interpolateVarsIn(tpt: Tree)(using Context): Unit = if tpt.isInstanceOf[InferredTypeTree] then - interpolator().traverse(tpt.knownType) - .showing(i"solved vars in ${tpt.knownType}", capt) + interpolator().traverse(tpt.nuType) + .showing(i"solved vars in ${tpt.nuType}", capt) for msg <- ccState.approxWarnings do report.warning(msg, tpt.srcPos) ccState.approxWarnings.clear() - /** Assert subcapturing `cs1 <: cs2` */ + /** Assert subcapturing `cs1 <: cs2` (available for debugging, otherwise unused) */ def assertSub(cs1: CaptureSet, cs2: CaptureSet)(using Context) = assert(cs1.subCaptures(cs2, frozen = false).isOK, i"$cs1 is not a subset of $cs2") + /** If `res` is not CompareResult.OK, report an error */ def checkOK(res: CompareResult, prefix: => String, pos: SrcPos, provenance: => String = "")(using Context): Unit = if !res.isOK then def toAdd: String = CaptureSet.levelErrors.toAdd.mkString @@ -270,60 +333,6 @@ class CheckCaptures extends Recheck, SymTransformer: else i"references $cs1$cs1description are not all", pos, provenance) - def showRef(ref: CaptureRef)(using Context): String = - ctx.printer.toTextCaptureRef(ref).show - - // Uses 4-space indent as a trial - private def checkReachCapsIsolated(tpe: Type, pos: SrcPos)(using Context): Unit = - - object checker extends TypeTraverser: - var refVariances: Map[Boolean, Int] = Map.empty - var seenReach: CaptureRef | Null = null - def traverse(tp: Type) = - tp.dealias match - case CapturingType(parent, refs) => - traverse(parent) - for ref <- refs.elems do - if ref.isReach && !ref.stripReach.isInstanceOf[TermParamRef] - || ref.isRootCapability - then - val isReach = ref.isReach - def register() = - refVariances = refVariances.updated(isReach, variance) - seenReach = ref - refVariances.get(isReach) match - case None => register() - case Some(v) => if v != 0 && variance == 0 then register() - case _ => - traverseChildren(tp) - - checker.traverse(tpe) - if checker.refVariances.size == 2 - && checker.refVariances(true) >= 0 - && checker.refVariances(false) <= 0 - then - report.error( - em"""Reach capability ${showRef(checker.seenReach.nn)} and universal capability cap cannot both - |appear in the type $tpe of this expression""", - pos) - end checkReachCapsIsolated - - /** The current environment */ - private val rootEnv: Env = inContext(ictx): - Env(defn.RootClass, EnvKind.Regular, CaptureSet.empty, null) - private var curEnv = rootEnv - - /** Currently checked closures and their expected types, used for error reporting */ - private var openClosures: List[(Symbol, Type)] = Nil - - private val myCapturedVars: util.EqHashMap[Symbol, CaptureSet] = EqHashMap() - - /** A list of actions to perform at postCheck. The reason to defer these actions - * is that it is sometimes better for type inference to not constrain too early - * with a checkConformsExpr. - */ - private var todoAtPostCheck = new mutable.ListBuffer[() => Unit] - /** If `sym` is a class or method nested inside a term, a capture set variable representing * the captured variables of the environment associated with `sym`. */ @@ -333,20 +342,17 @@ class CheckCaptures extends Recheck, SymTransformer: then CaptureSet.Var(sym.owner, level = sym.ccLevel) else CaptureSet.empty) - /** For all nested environments up to `limit` or a closed environment perform `op`, - * but skip environmenrts directly enclosing environments of kind ClosureResult. +// ---- Record Uses with MarkFree ---------------------------------------------------- + + /** The next environment enclosing `env` that needs to be charged + * with free references. + * @param included Whether an environment is included in the range of + * environments to charge. Once `included` is false, no + * more environments need to be charged. */ - def forallOuterEnvsUpTo(limit: Symbol)(op: Env => Unit)(using Context): Unit = - def recur(env: Env, skip: Boolean): Unit = - if env.isOpen && env.owner != limit then - if !skip then op(env) - if !env.isOutermost then - var nextEnv = env.outer - if env.owner.isConstructor then - if nextEnv.owner != limit && !nextEnv.isOutermost then - nextEnv = nextEnv.outer - recur(nextEnv, skip = env.kind == EnvKind.ClosureResult) - recur(curEnv, skip = false) + def nextEnvToCharge(env: Env, included: Env => Boolean)(using Context): Env = + if env.owner.isConstructor && included(env.outer) then env.outer.outer + else env.outer /** A description where this environment comes from */ private def provenance(env: Env)(using Context): String = @@ -360,6 +366,14 @@ 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 + * and (b) not the method of an anonympus function? + */ + def isOfNestedMethod(env: Env | Null)(using Context) = + env != null + && env.owner.is(Method) + && env.owner.owner.isTerm + && !env.owner.isAnonymousFunction /** Include `sym` in the capture sets of all enclosing environments nested in the * the environment in which `sym` is defined. @@ -368,12 +382,9 @@ class CheckCaptures extends Recheck, SymTransformer: markFree(sym, sym.termRef, pos) def markFree(sym: Symbol, ref: TermRef, pos: SrcPos)(using Context): Unit = - if sym.exists && ref.isTracked then - forallOuterEnvsUpTo(sym.enclosure): env => - capt.println(i"Mark $sym with cs ${ref.captureSet} free in ${env.owner}") - checkElem(ref, env.captured, pos, provenance(env)) + if sym.exists && ref.isTracked then markFree(ref.captureSet, pos) - /** Make sure (projected) `cs` is a subset of the capture sets of all enclosing + /** 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 * the environment's owner */ @@ -381,111 +392,157 @@ class CheckCaptures extends Recheck, SymTransformer: // A captured reference with the symbol `sym` is visible from the environment // if `sym` is not defined inside the owner of the environment. inline def isVisibleFromEnv(sym: Symbol, env: Env) = - if env.kind == EnvKind.NestedInOwner then - !sym.isProperlyContainedIn(env.owner) - else - !sym.isContainedIn(env.owner) - - def checkSubsetEnv(cs: CaptureSet, env: Env)(using Context): Unit = - // Only captured references that are visible from the environment - // should be included. - val included = cs.filter: c => - c.stripReach match - case ref: NamedType => - val refSym = ref.symbol - val refOwner = refSym.owner - val isVisible = isVisibleFromEnv(refOwner, env) - if isVisible && !ref.isRootCapability then - ref match - case ref: TermRef if ref.prefix `ne` NoPrefix => - // If c is a path of a class defined outside the environment, - // we check the capture set of its info. - checkSubsetEnv(ref.captureSetOfInfo, env) - case _ => - if !isVisible - && (c.isReach || ref.isType) - && (!ccConfig.useSealed || refSym.is(Param)) - && refOwner == env.owner - then - if refSym.hasAnnotation(defn.UnboxAnnot) then - capt.println(i"exempt: $ref in $refOwner") - else - // Reach capabilities that go out of scope have to be approximated - // by their underlying capture set, which cannot be universal. - // Reach capabilities of @unboxed parameters are exempted. - val cs = CaptureSet.ofInfo(c) - cs.disallowRootCapability: () => - report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", pos) - checkSubset(cs, env.captured, pos, provenance(env)) - isVisible - case ref: ThisType => isVisibleFromEnv(ref.cls, env) - case _ => false - checkSubset(included, env.captured, pos, provenance(env)) - capt.println(i"Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}") - - if !cs.isAlwaysEmpty then - forallOuterEnvsUpTo(ctx.owner.topLevelClass): env => - checkSubsetEnv(cs, env) - end markFree + sym.exists && { + if env.kind == EnvKind.NestedInOwner then + !sym.isProperlyContainedIn(env.owner) + else + !sym.isContainedIn(env.owner) + } - /** Include references captured by the called method in the current environment stack */ - def includeCallCaptures(sym: Symbol, pos: SrcPos)(using Context): Unit = - if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos) - - private val prefixCalls = util.EqHashSet[GenericApply]() - private val unboxedArgs = util.EqHashSet[Tree]() - - def handleCall(meth: Symbol, call: GenericApply, eval: () => Type)(using Context): Type = - if prefixCalls.remove(call) then return eval() - - val unboxedParamNames = - meth.rawParamss.flatMap: params => - params.collect: - case param if param.hasAnnotation(defn.UnboxAnnot) => - param.name - .toSet - - def markUnboxedArgs(call: GenericApply): Unit = call.fun.tpe.widen match - case MethodType(pnames) => - for (pname, arg) <- pnames.lazyZip(call.args) do - if unboxedParamNames.contains(pname) then - unboxedArgs.add(arg) - case _ => + /** 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""", pos) + case _ => - def markPrefixCalls(tree: Tree): Unit = tree match - case tree: GenericApply => - prefixCalls.add(tree) - markUnboxedArgs(tree) - markPrefixCalls(tree.fun) + /** 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 = + if c.isParamPath then + c match + case ReachCapability(_) | _: TypeRef => + checkUseDeclared(c, env, lastEnv) + 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: () => + report.error(em"Local capability $c in ${env.ownerString} cannot have `cap` as underlying capture set", pos) + 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}") + underlying.disallowRootCapability: () => + report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", pos) + recur(underlying, env, null) + case c: TypeRef if c.isParamPath => + checkUseDeclared(c, env, null) case _ => - markUnboxedArgs(call) - markPrefixCalls(call.fun) - val res = eval() - includeCallCaptures(meth, call.srcPos) - res - end handleCall + def recur(cs: CaptureSet, env: Env, lastEnv: Env | Null): Unit = + if env.isOpen && !env.owner.isStaticOwner && !cs.isAlwaysEmpty then + // Only captured references that are visible from the environment + // should be included. + val included = cs.filter: c => + val isVisible = isVisibleFromEnv(c.pathOwner, env) + if !isVisible then + if ccConfig.deferredReaches + then avoidLocalCapability(c, env, lastEnv) + else avoidLocalReachCapability(c, env) + isVisible + checkSubset(included, env.captured, pos, provenance(env)) + 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. + + recur(cs, curEnv, null) + end markFree + + /** Include references captured by the called method in the current environment stack */ + def includeCallCaptures(sym: Symbol, resType: Type, pos: SrcPos)(using Context): Unit = resType match + case _: MethodOrPoly => // wait until method is fully applied + case _ => + if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos) + + /** 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. + * Also, if a corresponding formal type parameter is declared or implied @use, + * charge the deep capture set of the argument to the environent. + * @param fn the type application, of type TypeApply or TypeTree + * @param sym the constructor symbol (could be a method or a val or a class) + * @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 + if ccConfig.useSealed && !isExempt then + val paramNames = atPhase(thisPhase.prev): + fn.tpe.widenDealias match + case tl: TypeLambda => tl.paramNames + case ref: AppliedType if ref.typeSymbol.isClass => ref.typeSymbol.typeParams.map(_.name) + case t => + println(i"parent type: $t") + args.map(_ => EmptyTypeName) + + for case (arg: TypeTree, pname) <- args.lazyZip(paramNames) do + def where = if sym.exists then i" in an argument of $sym" else "" + val (addendum, pos) = + if arg.isInferred + then ("\nThis is often caused by a local capability$where\nleaking as part of its result.", fn.srcPos) + else if arg.span.exists then ("", arg.srcPos) + else ("", fn.srcPos) + disallowRootCapabilitiesIn(arg.nuType, NoSymbol, + i"Type variable $pname of $sym", "be instantiated to", addendum, pos) + + val param = fn.symbol.paramNamed(pname) + if param.isUseParam then markFree(arg.nuType.deepCaptureSet, pos) + end disallowCapInTypeArgs override def recheckIdent(tree: Ident, pt: Type)(using Context): Type = - if tree.symbol.is(Method) then - if tree.symbol.info.isParameterless then - // there won't be an apply; need to include call captures now - includeCallCaptures(tree.symbol, tree.srcPos) - else if !tree.symbol.isStatic then - //debugShowEnvs() + val sym = tree.symbol + if sym.is(Method) then + // If ident refers to a parameterless method, charge its cv to the environment + includeCallCaptures(sym, sym.info, tree.srcPos) + else if !sym.isStatic then + // Otherwise charge its symbol, but add all selections implied by the e + // expected type `pt`. + // Example: If we have `x` and the expected type says we select that with `.a.b`, + // we charge `x.a.b` instead of `x`. def addSelects(ref: TermRef, pt: Type): TermRef = pt match case pt: PathSelectionProto if ref.isTracked => // if `ref` is not tracked then the selection could not give anything new // class SerializationProxy in stdlib-cc/../LazyListIterable.scala has an example where this matters. addSelects(ref.select(pt.sym).asInstanceOf[TermRef], pt.pt) case _ => ref - val ref = tree.symbol.termRef - val pathRef = addSelects(ref, pt) - //if pathRef ne ref then - // println(i"add selects $ref --> $pathRef") - markFree(tree.symbol, if false then ref else pathRef, tree.srcPos) + val pathRef = addSelects(sym.termRef, pt) + markFree(sym, pathRef, tree.srcPos) super.recheckIdent(tree, pt) + /** The expected type for the qualifier of a selection. If the selection + * could be part of a capabaility path, we return a PathSelectionProto. + */ override def selectionProto(tree: Select, pt: Type)(using Context): Type = val sym = tree.symbol if !sym.isOneOf(UnstableValueFlags) && !sym.isStatic then PathSelectionProto(sym, pt) @@ -498,8 +555,8 @@ class CheckCaptures extends Recheck, SymTransformer: * E |- f.m: R^C * * The implementation picks as `C` one of `{f}` or `Cr`, depending on the - * outcome of a `mightSubcapture` test. It picks `{f}` if this might subcapture Cr - * and Cr otherwise. + * outcome of a `mightSubcapture` test. It picks `{f}` if it might subcapture Cr + * and picks Cr otherwise. */ override def recheckSelection(tree: Select, qualType: Type, name: Name, pt: Type)(using Context) = { def disambiguate(denot: Denotation): Denotation = denot match @@ -519,10 +576,14 @@ class CheckCaptures extends Recheck, SymTransformer: val selType = recheckSelection(tree, qualType, name, disambiguate) 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 if pt == LhsProto || qualType.isBoxedCapturing - || selType.isTrackableRef || selWiden.isBoxedCapturing + || selType.isTrackableRef || selWiden.captureSet.isAlwaysEmpty then selType @@ -532,7 +593,7 @@ class CheckCaptures extends Recheck, SymTransformer: capt.println(i"pick one of $qualType, ${selType.widen}, $qualCs, $selCs ${selWiden.captureSet} in $tree") if qualCs.mightSubcapture(selCs) - && !selCs.mightSubcapture(qualCs) + //&& !selCs.mightSubcapture(qualCs) && !pt.stripCapturing.isInstanceOf[SingletonType] then selWiden.stripCapturing.capturing(qualCs) @@ -541,16 +602,22 @@ 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 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) + param.getAnnotation(defn.UseAnnot) match + case Some(ann) => AnnotatedType(formal, ann) + case _ => formal + funtpe.derivedLambdaType(paramInfos = paramInfosWithUses) + + /** Recheck applications, with special handling of unsafeAssumePure. + * More work is done in `recheckApplication`, `recheckArg` and `instantiate` below. + */ override def recheckApply(tree: Apply, pt: Type)(using Context): Type = val meth = tree.fun.symbol - - // Unsafe box/unbox handling, only for versions < 3.3 - def mapArgUsing(f: Type => Type) = - val arg :: Nil = tree.args: @unchecked - val argType0 = f(recheckStart(arg, pt)) - val argType = super.recheckFinish(argType0, arg, pt) - super.recheckFinish(argType, tree, pt) - if meth == defn.Caps_unsafeAssumePure then val arg :: Nil = tree.args: @unchecked val argType0 = recheck(arg, pt.capturing(CaptureSet.universal)) @@ -559,34 +626,26 @@ class CheckCaptures extends Recheck, SymTransformer: else argType0.widen.stripCapturing capt.println(i"rechecking $arg with $pt: $argType") super.recheckFinish(argType, tree, pt) - else if meth == defn.Caps_unsafeBox then - mapArgUsing(_.forceBoxStatus(true)) - else if meth == defn.Caps_unsafeUnbox then - mapArgUsing(_.forceBoxStatus(false)) - else if meth == defn.Caps_unsafeBoxFunArg then - def forceBox(tp: Type): Type = tp.strippedDealias match - case defn.FunctionOf(paramtpe :: Nil, restpe, isContextual) => - defn.FunctionOf(paramtpe.forceBoxStatus(true) :: Nil, restpe, isContextual) - case tp @ RefinedType(parent, rname, rinfo: MethodType) => - tp.derivedRefinedType(parent, rname, - rinfo.derivedLambdaType( - paramInfos = rinfo.paramInfos.map(_.forceBoxStatus(true)))) - case tp @ CapturingType(parent, refs) => - tp.derivedCapturingType(forceBox(parent), refs) - mapArgUsing(forceBox) else - handleCall(meth, tree, () => super.recheckApply(tree, pt)) - end recheckApply + val res = super.recheckApply(tree, pt) + includeCallCaptures(meth, res, tree.srcPos) + res - protected override - def recheckArg(arg: Tree, formal: Type)(using Context): Type = + /** Recheck argument, and, 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 argType = recheck(arg, formal) - if unboxedArgs.contains(arg) then - capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}") - markFree(argType.deepCaptureSet, arg.srcPos) + formal match + case AnnotatedType(formal1, ann) if ann.symbol == defn.UseAnnot => + // The UseAnnot is added to `formal` by `prepareFunction` + capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}") + markFree(argType.deepCaptureSet, arg.srcPos) + case _ => argType - /** A specialized implementation of the apply rule. + /** Map existential captures in result to `cap` and implement the following + * rele: * * E |- q: Tq^Cq * E |- q.f: Ta^Ca ->Cf Tr^Cr @@ -594,16 +653,16 @@ class CheckCaptures extends Recheck, SymTransformer: * --------------------- * E |- f(a): Tr^C * - * If the function `f` does not have an `@unboxed` parameter, then + * If the function `f` does not have an `@use` parameter, then * any unboxing it does would be charged to the environment of the function * so they have to appear in Cq. Since any capabilities of the result of the * application must already be present in the application, an upper * approximation of the result capture set is Cq \union Ca, where `Ca` * is the capture set of the argument. - * If the function `f` does have an `@unboxed` parameter, then it could in addition + * If the function `f` does have an `@use` parameter, then it could in addition * unbox reach capabilities over its formal parameter. Therefore, the approximation - * would be `Cq \union dcs(Ca)` instead. - * If the approximation is known to subcapture the declared result Cr, we pick it for C + * would be `Cq \union dcs(Ta)` instead. + * If the approximation might subcapture the declared result Cr, we pick it for C * otherwise we pick Cr. */ protected override @@ -611,10 +670,10 @@ class CheckCaptures extends Recheck, SymTransformer: val appType = Existential.toCap(super.recheckApplication(tree, qualType, funType, argTypes)) val qualCaptures = qualType.captureSet val argCaptures = - for (arg, argType) <- tree.args.lazyZip(argTypes) yield - if unboxedArgs.remove(arg) // need to ensure the remove happens, that's why argCaptures is computed even if not needed. - then argType.deepCaptureSet - else argType.captureSet + for (argType, formal) <- argTypes.lazyZip(funType.paramInfos) yield + formal match + case AnnotatedType(_, ann) if ann.symbol == defn.UseAnnot => argType.deepCaptureSet + case _ => argType.captureSet appType match case appType @ CapturingType(appType1, refs) if qualType.exists @@ -632,12 +691,9 @@ class CheckCaptures extends Recheck, SymTransformer: case Nil => true /** Handle an application of method `sym` with type `mt` to arguments of types `argTypes`. - * This means: + * This means * - Instantiate result type with actual arguments - * - If call is to a constructor: - * - remember types of arguments corresponding to tracked - * parameters in refinements. - * - add capture set of instantiated class to capture set of result type. + * - if `sym` is a constructor, refine its type with `refineInstanceType` * If all argument types are mutually different trackable capture references, use a BiTypeMap, * since that is more precise. Otherwise use a normal idempotent map, which might lose information * in the case where the result type contains captureset variables that are further @@ -651,75 +707,82 @@ class CheckCaptures extends Recheck, SymTransformer: SubstParamsBiMap(mt, argTypes)(mt.resType) else SubstParamsMap(mt, argTypes)(mt.resType) - - if sym.isConstructor then - val cls = sym.owner.asClass - - /** First half of result pair: - * Refine the type of a constructor call `new C(t_1, ..., t_n)` - * to C{val x_1: T_1, ..., x_m: T_m} where x_1, ..., x_m are the tracked - * parameters of C and T_1, ..., T_m are the types of the corresponding arguments. - * - * Second half: union of all capture sets of arguments to tracked parameters. - */ - def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) = - var refined: Type = core - var allCaptures: CaptureSet = - if core.derivesFromCapability then defn.universalCSImpliedByCapability 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, argType) - allCaptures ++= argType.captureSet - (refined, allCaptures) - - /** Augment result type of constructor with refinements and captures. - * @param core The result type of the constructor - * @param initCs The initial capture set to add, not yet counting capture sets from arguments - */ - def augmentConstructorType(core: Type, initCs: CaptureSet): Type = core match - case core: MethodType => - // more parameters to follow; augment result type - core.derivedLambdaType(resType = augmentConstructorType(core.resType, initCs)) - case CapturingType(parent, refs) => - // can happen for curried constructors if instantiate of a previous step - // added capture set to result. - augmentConstructorType(parent, initCs ++ refs) - case _ => - val (refined, cs) = addParamArgRefinements(core, initCs) - refined.capturing(cs) - - augmentConstructorType(ownType, capturedVars(cls) ++ capturedVars(sym)) - .showing(i"constr type $mt with $argTypes%, % in $cls = $result", capt) + if sym.isConstructor then refineConstructorInstance(ownType, mt, argTypes, sym) else ownType - end instantiate + /** Refine the type returned from a constructor as follows: + * - remember types of arguments corresponding to tracked parameters in refinements. + * - add capture set of instantiated class and capture set of constructor to capture set of result type. + * Note: This scheme does not distinguish whether a capture is made by the constructor + * only or by a method in the class. Both captures go into the result type. We + * could be more precise by distinguishing the two capture sets. + */ + private def refineConstructorInstance(resType: Type, mt: MethodType, argTypes: List[Type], constr: Symbol)(using Context): Type = + val cls = constr.owner.asClass + + /** First half of result pair: + * Refine the type of a constructor call `new C(t_1, ..., t_n)` + * to C{val x_1: T_1, ..., x_m: T_m} where x_1, ..., x_m are the tracked + * parameters of C and T_1, ..., T_m are the types of the corresponding arguments. + * + * Second half: union of initial capture set and all capture sets of arguments + * to tracked parameters. + */ + def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) = + var refined: Type = core + var allCaptures: CaptureSet = + if core.derivesFromCapability then defn.universalCSImpliedByCapability 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, argType.unboxed) // Yichen you might want to check this + allCaptures ++= argType.captureSet + (refined, allCaptures) + + /** Augment result type of constructor with refinements and captures. + * @param core The result type of the constructor + * @param initCs The initial capture set to add, not yet counting capture sets from arguments + */ + def augmentConstructorType(core: Type, initCs: CaptureSet): Type = core match + case core: MethodType => + // more parameters to follow; augment result type + core.derivedLambdaType(resType = augmentConstructorType(core.resType, initCs)) + case CapturingType(parent, refs) => + // can happen for curried constructors if instantiate of a previous step + // added capture set to result. + augmentConstructorType(parent, initCs ++ refs) + case _ => + val (refined, cs) = addParamArgRefinements(core, initCs) + refined.capturing(cs) + + augmentConstructorType(resType, capturedVars(cls) ++ capturedVars(constr)) + .showing(i"constr type $mt with $argTypes%, % in $constr = $result", capt) + end refineConstructorInstance + + /** Recheck type applications: + * - Map existential captures in result to `cap` + * - include captures of called methods in environment + * - don't allow cap to appear covariantly in type arguments + * - special handling of `contains[A, B]` calls + */ override def recheckTypeApply(tree: TypeApply, pt: Type)(using Context): Type = - val meth = tree.symbol - if ccConfig.useSealed then - val TypeApply(fn, args) = tree - val polyType = atPhase(thisPhase.prev): - fn.tpe.widen.asInstanceOf[TypeLambda] - def isExempt(sym: Symbol) = - sym.isTypeTestOrCast || sym == defn.Compiletime_erasedValue - for case (arg: TypeTree, formal, pname) <- args.lazyZip(polyType.paramRefs).lazyZip((polyType.paramNames)) do - if !isExempt(meth) then - def where = if meth.exists then i" in an argument of $meth" else "" - disallowRootCapabilitiesIn(arg.knownType, NoSymbol, - i"Sealed type variable $pname", "be instantiated to", - i"This is often caused by a local capability$where\nleaking as part of its result.", - tree.srcPos) - try handleCall(meth, tree, () => Existential.toCap(super.recheckTypeApply(tree, pt))) - finally checkContains(tree) + val meth = tree.fun match + case fun @ Select(qual, nme.apply) => qual.symbol.orElse(fun.symbol) + case fun => fun.symbol + disallowCapInTypeArgs(tree.fun, meth, tree.args) + val res = Existential.toCap(super.recheckTypeApply(tree, pt)) + includeCallCaptures(tree.symbol, res, tree.srcPos) + checkContains(tree) + res end recheckTypeApply /** Faced with a tree of form `caps.contansImpl[CS, r.type]`, check that `R` is a tracked - * capability and assert that `{r} <:CS`. + * capability and assert that `{r} <: CS`. */ def checkContains(tree: TypeApply)(using Context): Unit = tree match case ContainsImpl(csArg, refArg) => - val cs = csArg.knownType.captureSet - val ref = refArg.knownType + val cs = csArg.nuType.captureSet + val ref = refArg.nuType capt.println(i"check contains $cs , $ref") ref match case ref: CaptureRef if ref.isTracked => @@ -731,34 +794,34 @@ class CheckCaptures extends Recheck, SymTransformer: override def recheckBlock(tree: Block, pt: Type)(using Context): Type = 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. + */ override def recheckClosure(tree: Closure, pt: Type, forceDependent: Boolean)(using Context): Type = val cs = capturedVars(tree.meth.symbol) capt.println(i"typing closure $tree with cvs $cs") super.recheckClosure(tree, pt, forceDependent).capturing(cs) .showing(i"rechecked closure $tree / $pt = $result", capt) + /** Recheck a lambda of the form + * { def $anonfun(...) = ...; closure($anonfun, ...)} + */ override def recheckClosureBlock(mdef: DefDef, expr: Closure, pt: Type)(using Context): Type = - mdef.rhs match - case rhs @ closure(_, _, _) => - // In a curried closure `x => y => e` don't leak capabilities retained by - // the second closure `y => e` into the first one. This is an approximation - // of the CC rule which says that a closure contributes captures to its - // environment only if a let-bound reference to the closure is used. - mdef.rhs.putAttachment(ClosureBodyValue, ()) - case _ => - openClosures = (mdef.symbol, pt) :: openClosures try // Constrain closure's parameters and result from the expected type before // rechecking the body. val res = recheckClosure(expr, pt, forceDependent = true) if !(isEtaExpansion(mdef) && ccConfig.handleEtaExpansionsSpecially) then - // If closure is an eta expanded method reference it's better to not constrain + // Check whether the closure's results conforms to the expected type + // This constrains parameter types of the closure which can give better + // error messages. + // But if the closure is an eta expanded method reference it's better to not constrain // its internals early since that would give error messages in generated code - // which are less intelligible. - // Example is the line `a = x` in neg-custom-args/captures/vars.scala. - // For all other closures, early constraints are preferred since they - // give more localized error messages. + // which are less intelligible. An example is the line `a = x` in + // neg-custom-args/captures/vars.scala. That's why this code is conditioned. + // to apply only to closures that are not eta expansions. val res1 = Existential.toCapDeeply(res) val pt1 = Existential.toCapDeeply(pt) // We need to open existentials here in order not to get vars mixed up in them @@ -771,42 +834,18 @@ class CheckCaptures extends Recheck, SymTransformer: openClosures = openClosures.tail end recheckClosureBlock + /** Elements of a SeqLiteral instantiate a Seq or Array parameter, so they + * should be boxed. + */ override def seqLiteralElemProto(tree: SeqLiteral, pt: Type, declared: Type)(using Context) = super.seqLiteralElemProto(tree, pt, declared).boxed - /** Maps mutable variables to the symbols that capture them (in the - * CheckCaptures sense, i.e. symbol is referred to from a different method - * than the one it is defined in). + /** Recheck val and var definitions: + * - disallow cap in the type of mutable vars. + * - for externally visible definitions: check that their inferred type + * does not refine what was known before capture checking. + * - Interpolate contravariant capture set variables in result type. */ - private val capturedBy = util.HashMap[Symbol, Symbol]() - - /** Maps anonymous functions appearing as function arguments to - * the function that is called. - */ - private val anonFunCallee = util.HashMap[Symbol, Symbol]() - - /** Populates `capturedBy` and `anonFunCallee`. Called by `checkUnit`. - */ - private def collectCapturedMutVars(using Context) = new TreeTraverser: - def traverse(tree: Tree)(using Context) = tree match - case id: Ident => - val sym = id.symbol - if sym.is(Mutable, butNot = Method) && sym.owner.isTerm then - val enclMeth = ctx.owner.enclosingMethod - if sym.enclosingMethod != enclMeth then - capturedBy(sym) = enclMeth - case Apply(fn, args) => - for case closureDef(mdef) <- args do - anonFunCallee(mdef.symbol) = fn.symbol - traverseChildren(tree) - case Inlined(_, bindings, expansion) => - traverse(bindings) - traverse(expansion) - case mdef: DefDef => - if !mdef.symbol.isInlineMethod then traverseChildren(tree) - case _ => - traverseChildren(tree) - override def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Type = try if sym.is(Module) then sym.info // Modules are checked by checking the module class @@ -821,11 +860,11 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => "" s"an anonymous function$location" else encl.show - (NoSymbol, i"\nNote that $sym does not count as local since it is captured by $enclStr") + (NoSymbol, i"\n\nNote that $sym does not count as local since it is captured by $enclStr") case _ => (sym, "") disallowRootCapabilitiesIn( - tree.tpt.knownType, carrier, i"Mutable $sym", "have type", addendum, sym.srcPos) + tree.tpt.nuType, carrier, i"Mutable $sym", "have type", addendum, sym.srcPos) checkInferredResult(super.recheckValDef(tree, sym), tree) finally if !sym.is(Param) then @@ -835,13 +874,34 @@ class CheckCaptures extends Recheck, SymTransformer: // function is compiled since we do not propagate expected types into blocks. interpolateVarsIn(tree.tpt) + /** Recheck method definitions: + * - check body in a nested environment that tracks uses, in a nested level, + * and in a nested context that knows abaout Contains parameters so that we + * can assume they are true. + * - for externally visible definitions: check that their inferred type + * does not refine what was known before capture checking. + * - Interpolate contravariant capture set variables in result type unless + * def is anonymous. + */ override def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Type = if Synthetics.isExcluded(sym) then sym.info else + // Under the deferredReaches setting: If rhs ends in a closure or + // anonymous class, the corresponding symbol + def nestedClosure(rhs: Tree)(using Context): Symbol = + if !ccConfig.deferredReaches then NoSymbol + else rhs match + case Closure(_, meth, _) => meth.symbol + case Apply(fn, _) if fn.symbol.isConstructor && fn.symbol.owner.isAnonymousClass => fn.symbol.owner + case Block(_, expr) => nestedClosure(expr) + case Inlined(_, _, expansion) => nestedClosure(expansion) + case Typed(expr, _) => nestedClosure(expr) + case _ => NoSymbol + val saved = curEnv val localSet = capturedVars(sym) if !localSet.isAlwaysEmpty then - curEnv = Env(sym, EnvKind.Regular, localSet, curEnv) + curEnv = Env(sym, EnvKind.Regular, localSet, curEnv, nestedClosure(tree.rhs)) // ctx with AssumedContains entries for each Contains parameter val bodyCtx = @@ -852,7 +912,7 @@ class CheckCaptures extends Recheck, SymTransformer: if ac.isEmpty then ctx else ctx.withProperty(CaptureSet.AssumedContains, Some(ac)) - inNestedLevel: // TODO: needed here? + inNestedLevel: // TODO: nestedLevel needed here? try checkInferredResult(super.recheckDefDef(tree, sym)(using bodyCtx), tree) finally if !sym.isAnonymousFunction then @@ -872,13 +932,14 @@ class CheckCaptures extends Recheck, SymTransformer: val sym = tree.symbol def canUseInferred = // If canUseInferred is false, all capturing types in the type of `sym` need to be given explicitly - sym.is(Private) // private symbols can always have inferred types - || sym.name.is(DefaultGetterName) // default getters are exempted since otherwise it would be + 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 + // these could theoretically be accessed from other files in the empty package, but + // 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. - || // non-local symbols cannot have inferred types since external capture types are not inferred - sym.isLocalToCompilationUnit // local symbols still need explicit types if - && !sym.owner.is(Trait) // they are defined in a trait, since we do OverridingPairs checking before capture inference def addenda(expected: Type) = new Addenda: override def toAdd(using Context) = @@ -895,23 +956,60 @@ class CheckCaptures extends Recheck, SymTransformer: 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. case _ => tp end checkInferredResult - /** Class-specific capture set relations: + /** The set of symbols that were rechecked via a completer */ + private val completed = new mutable.HashSet[Symbol] + + /** The normal rechecking if `sym` was already completed before */ + override def skipRecheck(sym: Symbol)(using Context): Boolean = + completed.contains(sym) + + /** Check a ValDef or DefDef as an action performed in a completer. Since + * these checks can appear out of order, we need to first create the correct + * environment for checking the definition. + */ + def completeDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type = + val saved = curEnv + 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 = + val localSet = capturedVars(sym) + if localSet.isAlwaysEmpty then rootEnv + else envForOwner.get(sym) match + case Some(e) => e + case None => Env(sym, EnvKind.Regular, localSet, restoreEnvFor(sym.owner)) + curEnv = restoreEnvFor(sym.owner) + capt.println(i"Complete $sym in ${curEnv.outersIterator.toList.map(_.owner)}") + try recheckDef(tree, sym) + finally completed += sym + finally + curEnv = saved + + /** 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. * 3. The capture set of the self type of a class includes the capture set of every class parameter, - * unless the parameter is marked @constructorOnly. + * unless the parameter is marked @constructorOnly or @untrackedCaptures. * 4. If the class extends a pure base class, the capture set of the self type must be empty. + * Also, check that trait parents represented as applied types don't have cap in their + * type arguments. Other generic parents are represented as TypeApplys, where the same check + * is already done in the TypeApply. */ override def recheckClassDef(tree: TypeDef, impl: Template, cls: ClassSymbol)(using Context): Type = - val saved = curEnv 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.isAlwaysEmpty then curEnv = Env(cls, EnvKind.Regular, localSet, curEnv) try @@ -922,15 +1020,20 @@ class CheckCaptures extends Recheck, SymTransformer: && !param.hasAnnotation(defn.UntrackedCapturesAnnot) then checkSubset(param.termRef.captureSet, thisSet, param.srcPos) // (3) for pureBase <- cls.pureBaseClass do // (4) - def selfType = impl.body + def selfTypeTree = impl.body .collect: case TypeDef(tpnme.SELF, rhs) => rhs .headOption - .getOrElse(tree) - .orElse(tree) + .getOrElse(tree) // Use class tree if self type tree is missing ... + .orElse(tree) // ... or empty. checkSubset(thisSet, CaptureSet.empty.withDescription(i"of pure base class $pureBase"), - selfType.srcPos, cs1description = " captured by this self type") + selfTypeTree.srcPos, cs1description = " captured by this self type") + for case tpt: TypeTree <- impl.parents do + tpt.tpe match + case AppliedType(fn, args) => + disallowCapInTypeArgs(tpt, fn.typeSymbol, args.map(TypeTree(_))) + case _ => inNestedLevelUnless(cls.is(Module)): super.recheckClassDef(tree, impl, cls) finally @@ -950,12 +1053,15 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => super.recheckTyped(tree) + /** Under the sealed policy and with saferExceptions, disallow cap in the + * result type of a try + */ override def recheckTry(tree: Try, pt: Type)(using Context): Type = val tp = super.recheckTry(tree, pt) if ccConfig.useSealed && Feature.enabled(Feature.saferExceptions) then disallowRootCapabilitiesIn(tp, ctx.owner, - "result of `try`", "have type", - "This is often caused by a locally generated exception capability leaking as part of its result.", + "The result of `try`", "have type", + "\nThis is often caused by a locally generated exception capability leaking as part of its result.", tree.srcPos) tp @@ -975,18 +1081,18 @@ class CheckCaptures extends Recheck, SymTransformer: recheckFinish(result, arg, pt) */ - /** If expected type `pt` is boxed and the tree is a function or a reference, - * don't propagate free variables. - * Otherwise, if the result type is boxed, simulate an unboxing by - * adding all references in the boxed capture set to the current environment. + /** The main recheck method does some box adapation for all nodes: + * - If expected type `pt` is boxed and the tree is a lambda or a reference, + * don't propagate free variables. + * - If the expected type is not boxed but the result type is boxed, + * simulate an unboxing by adding all references in the boxed capture set + * of the result type to the current environment. */ override def recheck(tree: Tree, pt: Type = WildcardType)(using Context): Type = val saved = curEnv tree match case _: RefTree | closureDef(_) if pt.isBoxedCapturing => curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner, level = currentLevel), curEnv) - case _ if tree.hasAttachment(ClosureBodyValue) => - curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(curEnv.owner, level = currentLevel), curEnv) case _ => val res = try @@ -995,44 +1101,14 @@ class CheckCaptures extends Recheck, SymTransformer: else trace.force(i"rechecking $tree with pt = $pt", recheckr, show = true): super.recheck(tree, pt) - catch case ex: NoCommonRoot => - report.error(ex.getMessage.nn) - tree.tpe finally curEnv = saved - if tree.isTerm then - if !ccConfig.useExistentials then - checkReachCapsIsolated(res.widen, tree.srcPos) - if !pt.isBoxedCapturing && pt != LhsProto then - markFree(res.boxedCaptureSet, tree.srcPos) + if tree.isTerm && !pt.isBoxedCapturing && pt != LhsProto then + markFree(res.boxedCaptureSet, tree.srcPos) res + /** Under the old unsealed policy: check that cap is ot unboxed */ override def recheckFinish(tpe: Type, tree: Tree, pt: Type)(using Context): Type = - def needsUniversalCheck = tree match - case _: RefTree | _: Apply | _: TypeApply => tree.symbol.unboxesResult - case _: Try => true - case _ => false - - object checkNotUniversal extends TypeTraverser: - def traverse(tp: Type) = - tp.dealias match - case wtp @ CapturingType(parent, refs) => - if variance > 0 then - refs.disallowRootCapability: () => - def part = if wtp eq tpe.widen then "" else i" in its part $wtp" - report.error( - em"""The expression's type ${tpe.widen} is not allowed to capture the root capability `cap`$part. - |This usually means that a capability persists longer than its allowed lifetime.""", - tree.srcPos) - if !wtp.isBoxed then traverse(parent) - case tp => - traverseChildren(tp) - - if !ccConfig.useSealed - && !tpe.hasAnnotation(defn.UncheckedCapturesAnnot) - && needsUniversalCheck - && tpe.widen.isValueType - then - checkNotUniversal.traverse(tpe.widen) + checkNotUniversalInUnboxedResult(tpe, tree) super.recheckFinish(tpe, tree, pt) end recheckFinish @@ -1060,6 +1136,10 @@ class CheckCaptures extends Recheck, SymTransformer: | |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. + */ private def addApproxAddenda(using Context) = new TypeAccumulator[Addenda]: def apply(add: Addenda, t: Type) = t match @@ -1161,22 +1241,12 @@ class CheckCaptures extends Recheck, SymTransformer: (erefs /: erefs.elems): (erefs, eref) => eref match case eref: ThisType if isPureContext(ctx.owner, eref.cls) => - - def pathRoot(aref: Type): Type = aref match - case aref: NamedType if aref.symbol.owner.isClass => pathRoot(aref.prefix) - case _ => aref - - def isOuterRef(aref: Type): Boolean = pathRoot(aref) match - case aref: NamedType => eref.cls.isProperlyContainedIn(aref.symbol.owner) - case aref: ThisType => eref.cls.isProperlyContainedIn(aref.cls) - case _ => false - - val outerRefs = arefs.filter(isOuterRef) - + val outerRefs = arefs.filter: aref => + eref.cls.isProperlyContainedIn(aref.pathOwner) // Include implicitly added outer references in the capture set of the class of `eref`. for outerRef <- outerRefs.elems do if !erefs.elems.contains(outerRef) - && !pathRoot(outerRef).isInstanceOf[ThisType] + && !outerRef.pathRoot.isInstanceOf[ThisType] // we don't need to add outer ThisTypes as these are anyway added as path // prefixes at the use site. And this exemption is required since capture sets // of non-local classes are always empty, so we can't add an outer this to them. @@ -1250,6 +1320,7 @@ class CheckCaptures extends Recheck, SymTransformer: def adaptStr = i"adapting $actual ${if covariant then "~~>" else "<~~"} $expected" + // Get existentials and wildcards out of the way actual match case actual @ Existential(_, actualUnpacked) => return Existential.derivedExistentialType(actual): @@ -1283,11 +1354,10 @@ class CheckCaptures extends Recheck, SymTransformer: else if !leaked.subCaptures(cs, frozen = false).isOK then report.error( - em"""$expected cannot be box-converted to $actual + em"""$expected cannot be box-converted to ${actual.capturing(leaked)} |since the additional capture set $leaked resulted from box conversion is not allowed in $actual""", pos) cs - // Compute the adapted type def adaptedType(resultBoxed: Boolean) = if (adaptedShape eq actualShape) && leaked.isAlwaysEmpty && actualIsBoxed == resultBoxed then actual @@ -1296,35 +1366,43 @@ class CheckCaptures extends Recheck, SymTransformer: .forceBoxStatus(resultBoxed) if needsAdaptation then - val criticalSet = // the set which is not allowed to have `cap` - if covariant then captures // can't box with `cap` - else expected.captureSet // can't unbox with `cap` + val criticalSet = // the set with which we box or unbox + if covariant then captures // covariant: we box with captures of actual type plus captures leaked by inner adapation + else expected.captureSet // contravarant: we unbox with captures of epected type def msg = em"""$actual cannot be box-converted to $expected |since at least one of their capture sets contains the root capability `cap`""" def allowUniversalInBoxed = ccConfig.useSealed || expected.hasAnnotation(defn.UncheckedCapturesAnnot) || actual.widen.hasAnnotation(defn.UncheckedCapturesAnnot) - if criticalSet.isUnboxable && expected.isValueType && !allowUniversalInBoxed then - // We can't box/unbox the universal capability. Leave `actual` as it is - // so we get an error in checkConforms. Add the error message generated - // from boxing as an addendum. This tends to give better error - // messages than disallowing the root capability in `criticalSet`. - if boxErrors != null then boxErrors += msg - if ctx.settings.YccDebug.value then - println(i"cannot box/unbox $actual vs $expected") - actual - else - if !allowUniversalInBoxed then - // Disallow future addition of `cap` to `criticalSet`. - criticalSet.disallowRootCapability: () => - report.error(msg, pos) - if !insertBox then // unboxing - //debugShowEnvs() - markFree(criticalSet, pos) - adaptedType(!actualIsBoxed) - else - adaptedType(actualIsBoxed) + if !allowUniversalInBoxed then + if criticalSet.isUnboxable && expected.isValueType then + // We can't box/unbox the universal capability. Leave `actual` as it is + // so we get an error in checkConforms. Add the error message generated + // from boxing as an addendum. This tends to give better error + // messages than disallowing the root capability in `criticalSet`. + if boxErrors != null then boxErrors += msg + if ctx.settings.YccDebug.value then + println(i"cannot box/unbox $actual vs $expected") + return actual + // Disallow future addition of `cap` to `criticalSet`. + criticalSet.disallowRootCapability: () => + report.error(msg, pos) + + if !insertBox then // we are unboxing + //debugShowEnvs() + markFree(criticalSet, pos) + end if + + // Compute the adapted type. + // The result is boxed if actual is boxed and we don't need to adapt, + // or if actual is unboxed and we do need to adapt. + val resultIsBoxed = actualIsBoxed != needsAdaptation + if (adaptedShape eq actualShape) && leaked.isAlwaysEmpty && actualIsBoxed == resultIsBoxed + then actual + else adaptedShape + .capturing(if alwaysConst then CaptureSet(captures.elems) else captures) + .forceBoxStatus(resultIsBoxed) } end recur @@ -1333,6 +1411,12 @@ class CheckCaptures extends Recheck, SymTransformer: /** If actual is a tracked CaptureRef `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. + * class Foo { def a: C^{io}; val def: C^{async} } + * val foo: Foo^{io, async} + * Then + * foo: Foo { def a: C^{foo}; def b: C^{foo} }^{foo} */ private def improveCaptures(widened: Type, actual: Type)(using Context): Type = actual match case ref: CaptureRef if ref.isTracked => @@ -1343,9 +1427,10 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => widened case _ => widened - /** Adapt `actual` type to `expected` type by inserting boxing and unboxing conversions - * - * @param alwaysConst always make capture set variables constant after adaptation + /** Adapt `actual` type to `expected` type. This involves: + * - narrow toplevel captures of `x`'s underlying type to `{x}` according to CC's VAR rule + * - narrow nested captures of `x`'s underlying type to `{x*}` + * - do box adaptation */ def adapt(actual: Type, expected: Type, pos: SrcPos, boxErrors: BoxErrors)(using Context): Type = if expected == LhsProto || expected.isSingleton && actual.isSingleton then @@ -1359,6 +1444,8 @@ class CheckCaptures extends Recheck, SymTransformer: else adapted.showing(i"adapt boxed $actual vs $expected = $adapted", capt) end adapt +// ---- Unit-level rechecking ------------------------------------------- + /** Check overrides again, taking capture sets into account. * TODO: Can we avoid doing overrides checks twice? * We need to do them here since only at this phase CaptureTypes are relevant @@ -1389,21 +1476,22 @@ class CheckCaptures extends Recheck, SymTransformer: finally curEnv = saved actual1 frozen_<:< expected1 + /** Omit the check if one of {overriding,overridden} was nnot capture checked */ override def needsCheck(overriding: Symbol, overridden: Symbol)(using Context): Boolean = !setup.isPreCC(overriding) && !setup.isPreCC(overridden) override def checkInheritedTraitParameters: Boolean = false - /** Check that overrides don't change the @unbox status of their parameters */ + /** Check that overrides don't change the @use status of their parameters */ override def additionalChecks(member: Symbol, other: Symbol)(using Context): Unit = for (params1, params2) <- member.rawParamss.lazyZip(other.rawParamss) (param1, param2) <- params1.lazyZip(params2) do - if param1.hasAnnotation(defn.UnboxAnnot) != param2.hasAnnotation(defn.UnboxAnnot) then + if param1.hasAnnotation(defn.UseAnnot) != param2.hasAnnotation(defn.UseAnnot) then report.error( OverrideError( - i"has a parameter ${param1.name} with different @unbox status than the corresponding parameter in the overridden definition", + i"has a parameter ${param1.name} with different @use status than the corresponding parameter in the overridden definition", self, member, other, self.memberInfo(member), self.memberInfo(other) ), if member.owner == clazz then member.srcPos else clazz.srcPos @@ -1416,41 +1504,48 @@ class CheckCaptures extends Recheck, SymTransformer: checkAllOverrides(ctx.owner.asClass, OverridingPairsCheckerCC(_, _, t)) case _ => traverseChildren(t) + end checkOverrides - private val completed = new mutable.HashSet[Symbol] + /** Used for error reporting: + * Maps mutable variables to the symbols that capture them (in the + * CheckCaptures sense, i.e. symbol is referred to from a different method + * than the one it is defined in). + */ + private val capturedBy = util.HashMap[Symbol, Symbol]() - override def skipRecheck(sym: Symbol)(using Context): Boolean = - completed.contains(sym) + /** Used for error reporting: + * Maps anonymous functions appearing as function arguments to + * the function that is called. + */ + private val anonFunCallee = util.HashMap[Symbol, Symbol]() - /** Check a ValDef or DefDef as an action performed in a completer. Since - * these checks can appear out of order, we need to firsty create the correct - * environment for checking the definition. + /** Used for error reporting: + * Populates `capturedBy` and `anonFunCallee`. Called by `checkUnit`. */ - def completeDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type = - val saved = curEnv - 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 beyind this point - .map(e => (e.owner, e)) - .toMap - def restoreEnvFor(sym: Symbol): Env = - val localSet = capturedVars(sym) - if localSet.isAlwaysEmpty then rootEnv - else envForOwner.get(sym) match - case Some(e) => e - case None => Env(sym, EnvKind.Regular, localSet, restoreEnvFor(sym.owner)) - curEnv = restoreEnvFor(sym.owner) - capt.println(i"Complete $sym in ${curEnv.outersIterator.toList.map(_.owner)}") - try recheckDef(tree, sym) - finally completed += sym - finally - curEnv = saved + private def collectCapturedMutVars(using Context) = new TreeTraverser: + def traverse(tree: Tree)(using Context) = tree match + case id: Ident => + val sym = id.symbol + if sym.is(Mutable, butNot = Method) && sym.owner.isTerm then + val enclMeth = ctx.owner.enclosingMethod + if sym.enclosingMethod != enclMeth then + capturedBy(sym) = enclMeth + case Apply(fn, args) => + for case closureDef(mdef) <- args do + anonFunCallee(mdef.symbol) = fn.symbol + traverseChildren(tree) + case Inlined(_, bindings, expansion) => + traverse(bindings) + traverse(expansion) + case mdef: DefDef => + if !mdef.symbol.isInlineMethod then traverseChildren(tree) + case _ => + traverseChildren(tree) private val setup: SetupAPI = thisPhase.prev.asInstanceOf[Setup] override def checkUnit(unit: CompilationUnit)(using Context): Unit = - setup.setupUnit(unit.tpdTree, completeDef) + setup.setupUnit(unit.tpdTree, this) collectCapturedMutVars.traverse(unit.tpdTree) if ctx.settings.YccPrintSetup.value then @@ -1466,6 +1561,9 @@ class CheckCaptures extends Recheck, SymTransformer: postCheckWF(unit.tpdTree) if ctx.settings.YccDebug.value then show(unit.tpdTree) // this does not print tree, but makes its variables visible for dependency printing + end checkUnit + +// ----- Checks to do after the rechecking traversal -------------------------- /** Check that self types of subclasses conform to self types of super classes. * (See comment below how this is achieved). The check assumes that classes @@ -1590,9 +1688,13 @@ class CheckCaptures extends Recheck, SymTransformer: traverseChildren(tp) if tree.isInstanceOf[InferredTypeTree] then - checker.traverse(tree.knownType) + checker.traverse(tree.nuType) end healTypeParam + /** Under the unsealed policy: Arrays are like vars, check that their element types + * do not contains `cap` (in fact it would work also to check on array creation + * like we do under sealed). + */ def checkArraysAreSealedIn(tp: Type, pos: SrcPos)(using Context): Unit = val check = new TypeTraverser: def traverse(t: Type): Unit = @@ -1602,8 +1704,7 @@ class CheckCaptures extends Recheck, SymTransformer: && !arg.typeSymbol.name.is(WildcardParamName) then CheckCaptures.disallowRootCapabilitiesIn(arg, NoSymbol, - "Array", "have element type", - "Since arrays are mutable, they have to be treated like variables,\nso their element type must be sealed.", + "Array", "have element type", "", pos) traverseChildren(t) case defn.RefinedFunctionOf(rinfo: MethodType) => @@ -1627,15 +1728,15 @@ class CheckCaptures extends Recheck, SymTransformer: check(tree) def check(tree: Tree)(using Context) = tree match case TypeApply(fun, args) => - fun.knownType.widen match + fun.nuType.widen match case tl: PolyType => val normArgs = args.lazyZip(tl.paramInfos).map: (arg, bounds) => - arg.withType(arg.knownType.forceBoxStatus( + arg.withType(arg.nuType.forceBoxStatus( bounds.hi.isBoxedCapturing | bounds.lo.isBoxedCapturing)) checkBounds(normArgs, tl) args.lazyZip(tl.paramNames).foreach(healTypeParam(_, _, fun.symbol)) case _ => - case tree: TypeTree => + case tree: TypeTree if !ccConfig.useSealed => checkArraysAreSealedIn(tree.tpe, tree.srcPos) case _ => end check @@ -1650,7 +1751,7 @@ class CheckCaptures extends Recheck, SymTransformer: def traverse(t: Tree)(using Context) = t match case tree: InferredTypeTree => case tree: New => - case tree: TypeTree => checkAppliedTypesIn(tree.withKnownType) + case tree: TypeTree => checkAppliedTypesIn(tree.withType(tree.nuType)) case _ => traverseChildren(t) checkApplied.traverse(unit) end postCheck diff --git a/compiler/src/dotty/tools/dotc/cc/Existential.scala b/compiler/src/dotty/tools/dotc/cc/Existential.scala index 732510789e28..ea979e0b9f7f 100644 --- a/compiler/src/dotty/tools/dotc/cc/Existential.scala +++ b/compiler/src/dotty/tools/dotc/cc/Existential.scala @@ -333,12 +333,11 @@ object Existential: override def toString = "Wrap.inverse" end Wrap - if ccConfig.useExistentials then - val wrapped = apply(Wrap(_)(tp)) - if needsWrap then wrapped else tp - else tp + val wrapped = apply(Wrap(_)(tp)) + if needsWrap then wrapped else tp end mapCap + /** Map `cap` in function results to fresh existentials */ def mapCapInResults(fail: Message => Unit)(using Context): TypeMap = new: def mapFunOrMethod(tp: Type, args: List[Type], res: Type): Type = diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 3147a0f7bd47..ebe128d7776c 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -13,17 +13,29 @@ import ast.tpd, tpd.* import transform.{PreRecheck, Recheck}, Recheck.* import CaptureSet.{IdentityCaptRefMap, IdempotentCaptRefMap} import Synthetics.isExcluded -import util.Property +import util.{Property, SimpleIdentitySet} import reporting.Message import printing.{Printer, Texts}, Texts.{Text, Str} import collection.mutable import CCState.* +import dotty.tools.dotc.util.NoSourcePosition +import CheckCaptures.CheckerAPI /** Operations accessed from CheckCaptures */ trait SetupAPI: - type DefRecheck = (tpd.ValOrDefDef, Symbol) => Context ?=> Type - def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit + + /** Setup procedure to run for each compilation unit + * @param tree the typed tree of the unit to check + * @param checker the capture checker which will run subsequently. + */ + def setupUnit(tree: Tree, checker: CheckerAPI)(using Context): Unit + + /** Symbol is a term member of a class that was not capture checked + * The info of these symbols is made fluid. + */ def isPreCC(sym: Symbol)(using Context): Boolean + + /** Check to do after the capture checking traversal */ def postCheck()(using Context): Unit object Setup: @@ -41,7 +53,9 @@ object Setup: end Setup import Setup.* -/** A tree traverser that prepares a compilation unit to be capture checked. +/** Phase that sets up everthing for capture checking. + * + * A tree traverser that prepares a compilation unit to be capture checked. * It does the following: * - For every inferred type, drop any retains annotations, * add capture sets to all its parts, add refinements to class types and function types. @@ -63,16 +77,28 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: override def isRunnable(using Context) = super.isRunnable && Feature.ccEnabledSomewhere + /** A set containing symbols whose denotation is in train of being updated + * Used to suppress transforming the denotations of these symbols. + */ private val toBeUpdated = new mutable.HashSet[Symbol] + /** Drops `private` from the flags of `symd` provided it is + * a parameter accessor that's not `constructorOnly` or `uncheckedCaptured` + * and that contains at least one @retains in co- or in-variant position. + * The @retains mught be implicit for a type deriving from `Capability`. + */ private def newFlagsFor(symd: SymDenotation)(using Context): FlagSet = object containsCovarRetains extends TypeAccumulator[Boolean]: + val seen = util.HashSet[Symbol]() def apply(x: Boolean, tp: Type): Boolean = if x then true else if tp.derivesFromCapability && variance >= 0 then true else tp match case AnnotatedType(_, ann) if ann.symbol.isRetains && variance >= 0 => true + case t: TypeRef if t.symbol.isAbstractOrParamType && !seen.contains(t.symbol) => + seen += t.symbol + apply(x, t.info.bounds.hi) case _ => foldOver(x, tp) def apply(tp: Type): Boolean = apply(false, tp) @@ -84,30 +110,17 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else symd.flags end newFlagsFor + /** Symbol is a term member of a class that was not capture checked + * The info of these symbols is made fluid. + */ def isPreCC(sym: Symbol)(using Context): Boolean = sym.isTerm && sym.maybeOwner.isClass && !sym.is(Module) && !sym.owner.is(CaptureChecked) && !defn.isFunctionSymbol(sym.owner) - private def fluidify(using Context) = new TypeMap with IdempotentCaptRefMap: - def apply(t: Type): Type = t match - case t: MethodType => - mapOver(t) - case t: TypeLambda => - t.derivedLambdaType(resType = this(t.resType)) - case CapturingType(_, _) => - t - case _ => - val t1 = t match - case t @ defn.RefinedFunctionOf(rinfo: MethodType) => - t.derivedRefinedType(t.parent, t.refinedName, this(rinfo)) - case _ => - mapOver(t) - if variance > 0 then t1 - else decorate(t1, addedSet = Function.const(CaptureSet.Fluid)) - - /** - Reset `private` flags of parameter accessors so that we can refine them + /** The symbol transformer of this phase. + * - Resets `private` flags of parameter accessors so that we can refine them * in Setup if they have non-empty capture sets. * - Special handling of some symbols defined for case classes. * Enabled only until recheck is finished, and provided some compilation unit @@ -117,7 +130,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if !pastRecheck && Feature.ccEnabledSomewhere then val sym = symd.symbol def mappedInfo = - if toBeUpdated.contains(sym) then symd.info + if toBeUpdated.contains(sym) + then symd.info // don't transform symbols that will anyway be updated else transformExplicitType(symd.info) if Synthetics.needsTransform(symd) then Synthetics.transform(symd, mappedInfo) @@ -166,7 +180,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tp: MethodOrPoly => tp // don't box results of methods outside refinements case _ => recur(tp) - /** Perform the following transformation steps everywhere in a type: + /** Transform the type of an InferredTypeTree by performing the following transformation + * steps everywhere in the type: * 1. Drop retains annotations * 2. Turn plain function types into dependent function types, so that * we can refer to their parameters in capture sets. Currently this is @@ -185,8 +200,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: override def toString = "map inferred" /** Refine a possibly applied class type C where the class has tracked parameters - * x_1: T_1, ..., x_n: T_n to C { val x_1: CV_1 T_1, ..., val x_n: CV_n T_n } - * where CV_1, ..., CV_n are fresh capture sets. + * x_1: T_1, ..., x_n: T_n to C { val x_1: T_1^{CV_1}, ..., val x_n: T_n^{CV_n} } + * where CV_1, ..., CV_n are fresh capture set variables. */ def addCaptureRefinements(tp: Type): Type = tp match case _: TypeRef | _: AppliedType if refine && tp.typeParams.isEmpty => @@ -270,14 +285,22 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: try val tp1 = mapInferred(refine = true)(tp) val tp2 = Existential.mapCapInResults(_ => assert(false))(tp1) - if tp2 ne tp then capt.println(i"expanded implicit in ${ctx.owner}: $tp --> $tp1 --> $tp2") + if tp2 ne tp then capt.println(i"expanded inferred in ${ctx.owner}: $tp --> $tp1 --> $tp2") tp2 catch case ex: AssertionError => println(i"error while mapping inferred $tp") throw ex end transformInferredType - private def transformExplicitType(tp: Type, tptToCheck: Option[Tree] = None)(using Context): Type = + /** Transform an explicitly given type by performing the following transformation + * steps everywhere in the type: + * 1. Expand throws aliases to contextual function type with CanThrow parameters + * 2. Map types with retains annotations to CapturingTypes + * 3. Add universal capture sets to types deriving from Capability + * 4. Map `cap` in function result types to existentially bound variables. + * 5. Schedule deferred well-formed tests for types with retains annotations. + */ + private def transformExplicitType(tp: Type, tptToCheck: Tree = EmptyTree)(using Context): Type = val toCapturing = new DeepTypeMap: override def toString = "expand aliases" @@ -301,11 +324,15 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => res ) val fntpe = defn.PolyFunctionOf(mt) - if !encl.isEmpty && resDecomposed.isEmpty then + if !encl.isEmpty then val cs = CaptureSet(encl.map(_.paramRefs.head)*) CapturingType(fntpe, cs, boxed = false) else fntpe + /** If C derives from Capability and we have a C^cs in source, we leave it as is + * instead of expanding it to C^{cap}^cs. We do this by stripping capability-generated + * universal capture sets from the parent of a CapturingType. + */ def stripImpliedCaptureSet(tp: Type): Type = tp match case tp @ CapturingType(parent, refs) if (refs eq defn.universalCSImpliedByCapability) && !tp.isBoxedCapturing => @@ -321,9 +348,13 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val parent1 = this(parent) if ann.symbol.isRetains then val parent2 = stripImpliedCaptureSet(parent1) - for tpt <- tptToCheck do - checkWellformedLater(parent2, ann.tree, tpt) - CapturingType(parent2, ann.tree.toCaptureSet) + if !tptToCheck.isEmpty then + checkWellformedLater(parent2, ann.tree, tptToCheck) + try + CapturingType(parent2, ann.tree.toCaptureSet) + catch case ex: IllegalCaptureRef => + report.error(em"Illegal capture reference: ${ex.getMessage.nn}", tptToCheck.srcPos) + parent2 else t.derivedAnnotatedType(parent1, ann) case throwsAlias(res, exc) => @@ -336,7 +367,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: end toCapturing def fail(msg: Message) = - for tree <- tptToCheck do report.error(msg, tree.srcPos) + if !tptToCheck.isEmpty then report.error(msg, tptToCheck.srcPos) val tp1 = toCapturing(tp) val tp2 = Existential.mapCapInResults(fail)(tp1) @@ -344,15 +375,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tp2 end transformExplicitType - /** Transform type of type tree, and remember the transformed type as the type the tree */ - private def transformTT(tree: TypeTree, boxed: Boolean)(using Context): Unit = - if !tree.hasRememberedType then - val transformed = - if tree.isInferred - then transformInferredType(tree.tpe) - else transformExplicitType(tree.tpe, tptToCheck = Some(tree)) - tree.rememberType(if boxed then box(transformed) else transformed) - /** Substitute parameter symbols in `from` to paramRefs in corresponding * method or poly types `to`. We use a single BiTypeMap to do everything. * @param from a list of lists of type or term parameter symbols of a curried method @@ -396,27 +418,42 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: toBeUpdated += sym sym.updateInfo(thisPhase, info, newFlagsFor(sym)) toBeUpdated -= sym - sym.namedType match - case ref: CaptureRef if ref.isTrackableRef => ref.invalidateCaches() // TODO: needed? - case _ => + /** The info of `sym` at the CheckCaptures phase */ extension (sym: Symbol) def nextInfo(using Context): Type = atPhase(thisPhase.next)(sym.info) - def setupTraverser(recheckDef: DefRecheck) = new TreeTraverserWithPreciseImportContexts: + /** A traverser that adds knownTypes and updates symbol infos */ + def setupTraverser(checker: CheckerAPI) = new TreeTraverserWithPreciseImportContexts: + import checker.* + /** Transform type of tree, and remember the transformed type as the type the tree */ + private def transformTT(tree: TypeTree, boxed: Boolean)(using Context): Unit = + if !tree.hasNuType then + val transformed = + if tree.isInferred + then transformInferredType(tree.tpe) + else transformExplicitType(tree.tpe, tptToCheck = tree) + tree.setNuType(if boxed then box(transformed) else transformed) + + /** Transform the type of a val or var or the result type of a def */ def transformResultType(tpt: TypeTree, sym: Symbol)(using Context): Unit = + // First step: Transform the type and record it as knownType of tpt. try transformTT(tpt, boxed = sym.is(Mutable, butNot = Method) && !ccConfig.useSealed && !sym.hasAnnotation(defn.UncheckedCapturesAnnot), - // types of mutable variables are boxed in pre 3.3 code + // Under the sealed policy, we disallow root capabilities in the type of mutable + // variables, no need to box them here. ) catch case ex: IllegalCaptureRef => capt.println(i"fail while transforming result type $tpt of $sym") throw ex + + // Second step: Add descriptions for all capture set variables created in + // step one stating that they belong to `sym`. val addDescription = new TypeTraverser: def traverse(tp: Type) = tp match case tp @ CapturingType(parent, refs) => @@ -425,7 +462,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: traverse(parent) case _ => traverseChildren(tp) - addDescription.traverse(tpt.knownType) + addDescription.traverse(tpt.nuType) + end transformResultType def traverse(tree: Tree)(using Context): Unit = tree match @@ -440,7 +478,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: paramss.foreach(traverse) transformResultType(tpt, meth) traverse(tree.rhs) - //println(i"TYPE of ${tree.symbol.showLocated} = ${tpt.knownType}") case tree @ ValDef(_, tpt: TypeTree, _) => val sym = tree.symbol @@ -448,7 +485,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym) inContext(defCtx): transformResultType(tpt, sym) - capt.println(i"mapped $tree = ${tpt.knownType}") traverse(tree.rhs) case tree @ TypeApply(fn, args) => @@ -466,7 +502,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tree @ SeqLiteral(elems, tpt: TypeTree) => traverse(elems) - tpt.rememberType(box(transformInferredType(tpt.tpe))) + tpt.setNuType(box(transformInferredType(tpt.tpe))) case tree: Block => inNestedLevel(traverseChildren(tree)) @@ -474,46 +510,50 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => traverseChildren(tree) postProcess(tree) + checkProperUse(tree) end traverse + /** Processing done on node `tree` after its children are traversed */ def postProcess(tree: Tree)(using Context): Unit = tree match case tree: TypeTree => transformTT(tree, boxed = false) case tree: ValOrDefDef => + // Make sure denotation of tree's symbol is correct val sym = tree.symbol - /** The return type of a constructor instantiated with local type and value - * parameters. Constructors have `unit` result type, that's why we can't - * get this type by reading the result type tree, and have to construct it - * explicitly. - */ + // The return type of a constructor instantiated with local type and value + // parameters. Constructor defs have `Unit` as result type tree, that's why + // we can't get this type by reading the result type tree, and have to construct + // it explicitly. def constrReturnType(info: Type, psymss: List[List[Symbol]]): Type = info match case info: MethodOrPoly => constrReturnType(info.instantiate(psymss.head.map(_.namedType)), psymss.tail) case _ => info - /** The local result type, which is the known type of the result type tree, - * with special treatment for constructors. - */ + // The local result type, which is the known type of the result type tree, + // with special treatment for constructors. def localReturnType = if sym.isConstructor then constrReturnType(sym.info, sym.paramSymss) - else tree.tpt.knownType + else tree.tpt.nuType + // A test whether parameter signature might change. This returns true if one of + // the parameters has a new type installee. The idea here is that we store a new + // type only if the transformed type is different from the original. def paramSignatureChanges = tree.match case tree: DefDef => tree.paramss.nestedExists: - case param: ValDef => param.tpt.hasRememberedType - case param: TypeDef => param.rhs.hasRememberedType + case param: ValDef => param.tpt.hasNuType + case param: TypeDef => param.rhs.hasNuType case _ => false // A symbol's signature changes if some of its parameter types or its result type // have a new type installed here (meaning hasRememberedType is true) def signatureChanges = - tree.tpt.hasRememberedType && !sym.isConstructor || paramSignatureChanges + tree.tpt.hasNuType && !sym.isConstructor || paramSignatureChanges // Replace an existing symbol info with inferred types where capture sets of - // TypeParamRefs and TermParamRefs put in correspondence by BiTypeMaps with the + // TypeParamRefs and TermParamRefs are put in correspondence by BiTypeMaps with the // capture sets of the types of the method's parameter symbols and result type. def integrateRT( info: Type, // symbol info to replace @@ -530,13 +570,16 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // substitute `x.f.type`, `x` becomes a `TermParamRef`. But the new method // type is still under initialization and `paramInfos` is still `null`, // so the new `NamedType` will not have a denoation. + def adaptedInfo(psym: Symbol, info: mt.PInfo): mt.PInfo = mt.companion match + case mtc: MethodTypeCompanion => mtc.adaptParamInfo(psym, info).asInstanceOf[mt.PInfo] + case _ => info mt.companion(mt.paramNames)( mt1 => if !paramSignatureChanges && !mt.isParamDependent && prevLambdas.isEmpty then mt.paramInfos else val subst = SubstParams(psyms :: prevPsymss, mt1 :: prevLambdas) - psyms.map(psym => subst(psym.nextInfo).asInstanceOf[mt.PInfo]), + psyms.map(psym => adaptedInfo(psym, subst(psym.nextInfo).asInstanceOf[mt.PInfo])), mt1 => integrateRT(mt.resType, psymss.tail, resType, psyms :: prevPsymss, mt1 :: prevLambdas) ) @@ -547,6 +590,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if prevLambdas.isEmpty then resType else SubstParams(prevPsymss, prevLambdas)(resType) + // If there's a change in the signature, update the info of `sym` if sym.exists && signatureChanges then val newInfo = Existential.mapCapInResults(report.error(_, tree.srcPos)): @@ -563,14 +607,14 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // the expected type and only afterwards we recheck the definition newInfo else new LazyType: + // infos of other methods are determined from their definitions, which + // are checked on demand def complete(denot: SymDenotation)(using Context) = - // infos of other methods are determined from their definitions which - // are checked on demand assert(ctx.phase == thisPhase.next, i"$sym") capt.println(i"forcing $sym, printing = ${ctx.mode.is(Mode.Printing)}") //if ctx.mode.is(Mode.Printing) then new Error().printStackTrace() denot.info = newInfo - recheckDef(tree, sym) + completeDef(tree, sym) updateInfo(sym, updatedInfo) case tree: Bind => @@ -581,9 +625,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case cls: ClassSymbol => inNestedLevelUnless(cls.is(Module)): val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo - def innerModule = cls.is(ModuleClass) && !cls.isStatic + + // Compute new self type + def isInnerModule = cls.is(ModuleClass) && !cls.isStatic val selfInfo1 = - if (selfInfo ne NoType) && !innerModule then + if (selfInfo ne NoType) && !isInnerModule then // if selfInfo is explicitly given then use that one, except if // self info applies to non-static modules, these still need to be inferred selfInfo @@ -598,8 +644,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // 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 = currentLevel)) + + // Compute new parent types val ps1 = inContext(ctx.withOwner(cls)): ps.mapConserve(transformExplicitType(_)) + + // 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) @@ -613,9 +663,25 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => case _ => end postProcess + + /** Check that @use annotations only appear on parameters and not on anonymous function parameters */ + def checkProperUse(tree: Tree)(using Context): Unit = tree match + case tree: MemberDef => + def useAllowed(sym: Symbol) = + (sym.is(Param) || sym.is(ParamAccessor)) && !sym.owner.isAnonymousFunction + for ann <- tree.symbol.annotations do + if ann.symbol == defn.UseAnnot && !useAllowed(tree.symbol) then + report.error(i"Only parameters of methods can have @use annotations", tree.srcPos) + case _ => + end checkProperUse end setupTraverser - /** Checks whether an abstract type could be impure. See also: [[needsVariable]]. */ +// --------------- Adding capture set variables ---------------------------------- + + /** Checks whether an abstract type or its bound could be impure. If that's the case, + * the abstract type gets a capture set variable in `decorate`. + * See also: [[needsVariable]]. + */ private def instanceCanBeImpure(tp: Type)(using Context): Boolean = { tp.dealiasKeepAnnots match case CapturingType(_, refs) => @@ -638,7 +704,13 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: false }.showing(i"instance can be impure $tp = $result", capt) - /** Should a capture set variable be added on type `tp`? */ + /** Should a capture set variable be added on type `tp`? + * Notable exceptions are: + * - If type refers to a class which is Pure, or it is Any, it does not need a variable. + * - If type is an abstract or parameter type we decide according to `instanceCanBeImpure` + * - If type is a capturing type that has already a capture set variable or has + * the universal capture set, it does not need a variable. + */ def needsVariable(tp: Type)(using Context): Boolean = { tp.typeParams.isEmpty && tp.match case tp: (TypeRef | AppliedType) => @@ -670,6 +742,61 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: false }.showing(i"can have inferred capture $tp = $result", captDebug) + /** Add a capture set variable or set to `tp` if necessary. + * Dealias `tp` (but keep annotations and opaque types) if doing + * so ends up adding a capture set. + * @param tp the type to add a capture set to + * @param added A function producing the added capture set from a set of initial elements. + */ + def decorate(tp: Type, added: CaptureSet.Refs => CaptureSet)(using Context): Type = + if tp.typeSymbol == defn.FromJavaObjectSymbol then + // For capture checking, we assume Object from Java is the same as Any + tp + else + def maybeAdd(target: Type, fallback: Type) = + if needsVariable(target) then + target match + case CapturingType(_, CaptureSet.Fluid) => + target + case CapturingType(target1, cs1) if cs1.isConst => + CapturingType(target1, added(cs1.elems)) + case _ => + CapturingType(target, added(SimpleIdentitySet.empty)) + else fallback + val dealiased = tp.dealiasKeepAnnotsAndOpaques + if dealiased ne tp then + val transformed = transformInferredType(dealiased) + maybeAdd(transformed, if transformed ne dealiased then transformed else tp) + else maybeAdd(tp, tp) + + /** Add a capture set variable to `tp` if necessary. */ + private def addVar(tp: Type, owner: Symbol)(using Context): Type = + decorate(tp, CaptureSet.Var(owner, _, level = currentLevel)) + + /** A map that adds capture sets at all contra- and invariant positions + * in a type where a capture set would be needed. This is used to make types + * that were not capture checked compatible with types that are capture checked. + * We don't need to add in covariant positions since pure types are + * anyway compatible with capturing types. + */ + private def fluidify(using Context) = new TypeMap with IdempotentCaptRefMap: + def apply(t: Type): Type = t match + case t: MethodType => + mapOver(t) + case t: TypeLambda => + t.derivedLambdaType(resType = this(t.resType)) + case CapturingType(_, _) => + t + case _ => + val t1 = t match + case t @ defn.RefinedFunctionOf(rinfo: MethodType) => + // Just refine the apply method, don't bother with the parent + t.derivedRefinedType(t.parent, t.refinedName, this(rinfo)) + case _ => + mapOver(t) + if variance > 0 then t1 + else decorate(t1, Function.const(CaptureSet.Fluid)) + /** Pull out an embedded capture set from a part of `tp` */ def normalizeCaptures(tp: Type)(using Context): Type = tp match case tp @ RefinedType(parent @ CapturingType(parent1, refs), rname, rinfo) => @@ -701,34 +828,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => tp - /** Add a capture set variable to `tp` if necessary, or maybe pull out - * an embedded capture set variable from a part of `tp`. + /** Run setup on a compilation unit with given `tree`. + * @param recheckDef the function to run for completing a val or def */ - def decorate(tp: Type, addedSet: Type => CaptureSet)(using Context): Type = - if tp.typeSymbol == defn.FromJavaObjectSymbol then - // For capture checking, we assume Object from Java is the same as Any - tp - else - def maybeAdd(target: Type, fallback: Type) = - if needsVariable(target) then CapturingType(target, addedSet(target)) - else fallback - val dealiased = tp.dealiasKeepAnnotsAndOpaques - if dealiased ne tp then - val transformed = transformInferredType(dealiased) - maybeAdd(transformed, if transformed ne dealiased then transformed else tp) - else maybeAdd(tp, tp) - - /** Add a capture set variable to `tp` if necessary, or maybe pull out - * an embedded capture set variable from a part of `tp`. - */ - private def addVar(tp: Type, owner: Symbol)(using Context): Type = - decorate(tp, - addedSet = _.dealias.match - case CapturingType(_, refs) => CaptureSet.Var(owner, refs.elems, level = currentLevel) - case _ => CaptureSet.Var(owner, level = currentLevel)) - - def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit = - setupTraverser(recheckDef).traverse(tree)(using ctx.withPhase(thisPhase)) + def setupUnit(tree: Tree, checker: CheckerAPI)(using Context): Unit = + setupTraverser(checker).traverse(tree)(using ctx.withPhase(thisPhase)) // ------ Checks to run after main capture checking -------------------------- @@ -741,8 +845,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * This check is performed after capture sets are computed in phase cc. * Note: We need to perform the check on the original annotation rather than its * capture set since the conversion to a capture set already eliminates redundant elements. + * @param parent the parent of the capturing type + * @param ann the original retains annotation + * @param tpt the tree for which an error or warning should be reported */ - private def checkWellformedPost(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = + 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 @@ -757,8 +864,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if others.accountsFor(ref) then report.warning(em"redundant capture: $dom already accounts for $ref", pos) - if ref.captureSetOfInfo.elems.isEmpty && !ref.derivesFrom(defn.Caps_Capability) then - report.error(em"$ref cannot be tracked since its capture set is empty", pos) + if ref.captureSetOfInfo.elems.isEmpty + && !ref.derivesFrom(defn.Caps_Capability) + && !ref.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) val others = @@ -770,14 +880,18 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: check(remaining, remaining) end for end for - end checkWellformedPost + end checkWellformed - /** Check well formed at post check time */ + /** 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. + */ private def checkWellformedLater(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = if !tpt.span.isZeroExtent && enclosingInlineds.isEmpty then todoAtPostCheck += (ctx1 => - checkWellformedPost(parent, ann, tpt)(using ctx1.withOwner(ctx.owner))) + checkWellformed(parent, ann, tpt)(using ctx1.withOwner(ctx.owner))) + /** Run all well formedness tests that were deferred to post check */ def postCheck()(using Context): Unit = for chk <- todoAtPostCheck do chk(ctx) todoAtPostCheck.clear() diff --git a/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala index aed5be45cb0d..2d659b532d7b 100644 --- a/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala @@ -229,7 +229,7 @@ final class CtSymClassPath(ctSym: java.nio.file.Path, release: Int) extends Clas // e.g. "java.lang" -> Seq(/876/java/lang, /87/java/lang, /8/java/lang)) private val packageIndex: scala.collection.Map[String, scala.collection.Seq[Path]] = { - val index = collection.mutable.AnyRefMap[String, collection.mutable.ListBuffer[Path]]() + val index = collection.mutable.HashMap[String, collection.mutable.ListBuffer[Path]]() val isJava12OrHigher = scala.util.Properties.isJavaAtLeast("12") rootsForRelease.foreach(root => Files.walk(root).iterator().asScala.filter(Files.isDirectory(_)).foreach { p => val moduleNamePathElementCount = if (isJava12OrHigher) 1 else 0 diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 6ef33d24f8be..599812a9a390 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -126,6 +126,7 @@ trait CommonScalaSettings: val encoding: Setting[String] = StringSetting(RootSetting, "encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding, aliases = List("--encoding")) val usejavacp: Setting[Boolean] = BooleanSetting(RootSetting, "usejavacp", "Utilize the java.class.path in classpath resolution.", aliases = List("--use-java-class-path")) val scalajs: Setting[Boolean] = BooleanSetting(RootSetting, "scalajs", "Compile in Scala.js mode (requires scalajs-library.jar on the classpath).", aliases = List("--scalajs")) + val replInitScript: Setting[String] = StringSetting(RootSetting, "repl-init-script", "code", "The code will be run on REPL startup.", "", aliases = List("--repl-init-script")) end CommonScalaSettings /** -P "plugin" settings. Various tools might support plugins. */ @@ -353,8 +354,7 @@ private sealed trait XSettings: val XreadComments: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xread-docs", "Read documentation from tasty.") /** Area-specific debug output */ - val XnoDecodeStacktraces: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xno-decode-stacktraces", "Show raw StackOverflow stacktraces, instead of decoding them into triggering operations.") - val XnoEnrichErrorMessages: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xno-enrich-error-messages", "Show raw error messages, instead of enriching them with contextual information.") + val XnoEnrichErrorMessages: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xno-enrich-error-messages", "Show raw error messages, instead of enriching them with contextual information.", aliases = List("Xno-decode-stacktraces")) val XdebugMacros: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xdebug-macros", "Show debug info when quote pattern match fails") /** Pipeline compilation options */ @@ -487,7 +487,7 @@ private sealed trait YSettings: @deprecated(message = "Lifted to -X, Scheduled for removal.", since = "3.5.0") val YreadComments: Setting[Boolean] = BooleanSetting(ForkSetting, "Yread-docs", "Read documentation from tasty.", deprecation = Deprecation.renamed("-Xread-docs")) @deprecated(message = "Lifted to -X, Scheduled for removal.", since = "3.5.0") - val YnoDecodeStacktraces: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-decode-stacktraces", "Show raw StackOverflow stacktraces, instead of decoding them into triggering operations.", deprecation = Deprecation.renamed("-Xno-decode-stacktraces")) + val YnoDecodeStacktraces: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-decode-stacktraces", "Show raw StackOverflow stacktraces, instead of decoding them into triggering operations.", deprecation = Deprecation.renamed("-Xno-enrich-error-messages")) @deprecated(message = "Lifted to -X, Scheduled for removal.", since = "3.5.0") val YnoEnrichErrorMessages: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-enrich-error-messages", "Show raw error messages, instead of enriching them with contextual information.", deprecation = Deprecation.renamed("-Xno-enrich-error-messages")) @deprecated(message = "Lifted to -X, Scheduled for removal.", since = "3.5.0") diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index d6a99b12e3b3..d7f50d4638ab 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -73,7 +73,7 @@ object Annotations { /** Does this annotation refer to a parameter of `tl`? */ def refersToParamOf(tl: TermLambda)(using Context): Boolean = - val args = arguments + val args = tpd.allArguments(tree) if args.isEmpty then false else tree.existsSubTree: case id: (Ident | This) => id.tpe.stripped match diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index d8241f3ff304..81b03d765676 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -131,8 +131,8 @@ class CheckRealizable(using Context) { /** `Realizable` if `tp` has good bounds, a `HasProblem...` instance * pointing to a bad bounds member otherwise. "Has good bounds" means: * - * - all type members have good bounds (except for opaque helpers) - * - all refinements of the underlying type have good bounds (except for opaque companions) + * - all type members have good bounds + * - all refinements of the underlying type have good bounds * - all base types are class types, and if their arguments are wildcards * they have good bounds. * - base types do not appear in multiple instances with different arguments. diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index d69c7408d0b1..7c54d1392720 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -41,6 +41,7 @@ import util.Store import plugins.* import java.util.concurrent.atomic.AtomicInteger import java.nio.file.InvalidPathException +import dotty.tools.dotc.coverage.Coverage object Contexts { @@ -777,13 +778,13 @@ object Contexts { extension (c: Context) def addNotNullInfo(info: NotNullInfo) = - c.withNotNullInfos(c.notNullInfos.extendWith(info)) + if c.explicitNulls then c.withNotNullInfos(c.notNullInfos.extendWith(info)) else c def addNotNullRefs(refs: Set[TermRef]) = - c.addNotNullInfo(NotNullInfo(refs, Set())) + if c.explicitNulls then c.addNotNullInfo(NotNullInfo(refs, Set())) else c def withNotNullInfos(infos: List[NotNullInfo]): Context = - if c.notNullInfos eq infos then c else c.fresh.setNotNullInfos(infos) + if !c.explicitNulls || (c.notNullInfos eq infos) then c else c.fresh.setNotNullInfos(infos) def relaxedOverrideContext: Context = c.withModeBits(c.mode &~ Mode.SafeNulls | Mode.RelaxedOverriding) @@ -982,6 +983,11 @@ object Contexts { /** Was best effort file used during compilation? */ private[core] var usedBestEffortTasty = false + /** If coverage option is used, it stores all instrumented statements (for InstrumentCoverage). + * We need this information to be persisted across different runs, so it's stored here. + */ + private[dotc] var coverage: Coverage | Null = null + // Types state /** A table for hash consing unique types */ private[core] val uniques: Uniques = Uniques() diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 0195a4ddbf34..dd20c2db9192 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -999,9 +999,6 @@ class Definitions { @tu lazy val Caps_Exists: ClassSymbol = requiredClass("scala.caps.Exists") @tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe") @tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure") - @tu lazy val Caps_unsafeBox: Symbol = CapsUnsafeModule.requiredMethod("unsafeBox") - @tu lazy val Caps_unsafeUnbox: Symbol = CapsUnsafeModule.requiredMethod("unsafeUnbox") - @tu lazy val Caps_unsafeBoxFunArg: Symbol = CapsUnsafeModule.requiredMethod("unsafeBoxFunArg") @tu lazy val Caps_ContainsTrait: TypeSymbol = CapsModule.requiredType("Contains") @tu lazy val Caps_containsImpl: TermSymbol = CapsModule.requiredMethod("containsImpl") @@ -1057,12 +1054,12 @@ class Definitions { @tu lazy val ExperimentalAnnot: ClassSymbol = requiredClass("scala.annotation.experimental") @tu lazy val ThrowsAnnot: ClassSymbol = requiredClass("scala.throws") @tu lazy val TransientAnnot: ClassSymbol = requiredClass("scala.transient") - @tu lazy val UnboxAnnot: ClassSymbol = requiredClass("scala.caps.unbox") @tu lazy val UncheckedAnnot: ClassSymbol = requiredClass("scala.unchecked") @tu lazy val UncheckedStableAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedStable") @tu lazy val UncheckedVarianceAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedVariance") @tu lazy val UncheckedCapturesAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedCaptures") @tu lazy val UntrackedCapturesAnnot: ClassSymbol = requiredClass("scala.caps.untrackedCaptures") + @tu lazy val UseAnnot: ClassSymbol = requiredClass("scala.caps.use") @tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile") @tu lazy val BeanGetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanGetter") @tu lazy val BeanSetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanSetter") @@ -1340,10 +1337,25 @@ class Definitions { object NamedTuple: def apply(nmes: Type, vals: Type)(using Context): Type = AppliedType(NamedTupleTypeRef, nmes :: vals :: Nil) - def unapply(t: Type)(using Context): Option[(Type, Type)] = t match - case AppliedType(tycon, nmes :: vals :: Nil) if tycon.typeSymbol == NamedTupleTypeRef.symbol => - Some((nmes, vals)) - case _ => None + def unapply(t: Type)(using Context): Option[(Type, Type)] = + t match + case AppliedType(tycon, nmes :: vals :: Nil) if tycon.typeSymbol == NamedTupleTypeRef.symbol => + Some((nmes, vals)) + case tp: TypeProxy => + val t = unapply(tp.superType); t + case tp: OrType => + (unapply(tp.tp1), unapply(tp.tp2)) match + case (Some(lhsName, lhsVal), Some(rhsName, rhsVal)) if lhsName == rhsName => + Some(lhsName, lhsVal | rhsVal) + case _ => None + case tp: AndType => + (unapply(tp.tp1), unapply(tp.tp2)) match + case (Some(lhsName, lhsVal), Some(rhsName, rhsVal)) if lhsName == rhsName => + Some(lhsName, lhsVal & rhsVal) + case (lhs, None) => lhs + case (None, rhs) => rhs + case _ => None + case _ => None final def isCompiletime_S(sym: Symbol)(using Context): Boolean = sym.name == tpnme.S && sym.owner == CompiletimeOpsIntModuleClass diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 816b28177333..85ff51bc19de 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -2,7 +2,7 @@ package dotty.tools package dotc package core -import SymDenotations.{ SymDenotation, ClassDenotation, NoDenotation, LazyType, stillValid, movedToCompanionClass, acceptStale, traceInvalid } +import SymDenotations.{ SymDenotation, ClassDenotation, NoDenotation, LazyType, stillValid, acceptStale, traceInvalid } import Contexts.* import Names.* import NameKinds.* @@ -742,6 +742,8 @@ object Denotations { * the old version otherwise. * - If the symbol did not have a denotation that was defined at the current phase * return a NoDenotation instead. + * - If the symbol was first defined in one of the transform phases (after pickling), it should not + * be visible in new runs, so also return a NoDenotation. */ private def bringForward()(using Context): SingleDenotation = { this match { @@ -755,11 +757,7 @@ object Denotations { } if (!symbol.exists) return updateValidity() if (!coveredInterval.containsPhaseId(ctx.phaseId)) return NoDenotation - // Moved to a companion class, likely at a later phase (in MoveStatics) - this match { - case symd: SymDenotation if movedToCompanionClass(symd) => return NoDenotation - case _ => - } + if (coveredInterval.firstPhaseId >= Phases.firstTransformPhase.id) return NoDenotation if (ctx.debug) traceInvalid(this) staleSymbolError } diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index b915373da021..0775b3caaf0c 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -480,7 +480,7 @@ object Flags { */ val AfterLoadFlags: FlagSet = commonFlags( FromStartFlags, AccessFlags, Final, AccessorOrSealed, - Abstract, LazyOrTrait, SelfName, JavaDefined, JavaAnnotation, Transparent, Tracked) + Abstract, LazyOrTrait, SelfName, JavaDefined, JavaAnnotation, Transparent) /** A value that's unstable unless complemented with a Stable flag */ val UnstableValueFlags: FlagSet = Mutable | Method diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 015cf6fc0f2c..e3351628e43e 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -222,6 +222,7 @@ object Phases { private var mySbtExtractDependenciesPhase: Phase = uninitialized private var mySbtExtractAPIPhase: Phase = uninitialized private var myPicklerPhase: Phase = uninitialized + private var mySetRootTreePhase: Phase = uninitialized private var myInliningPhase: Phase = uninitialized private var myStagingPhase: Phase = uninitialized private var mySplicingPhase: Phase = uninitialized @@ -249,6 +250,7 @@ object Phases { final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase final def sbtExtractAPIPhase: Phase = mySbtExtractAPIPhase final def picklerPhase: Phase = myPicklerPhase + final def setRootTreePhase: Phase = mySetRootTreePhase final def inliningPhase: Phase = myInliningPhase final def stagingPhase: Phase = myStagingPhase final def splicingPhase: Phase = mySplicingPhase @@ -278,6 +280,7 @@ object Phases { myPostTyperPhase = phaseOfClass(classOf[PostTyper]) mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies]) mySbtExtractAPIPhase = phaseOfClass(classOf[sbt.ExtractAPI]) + mySetRootTreePhase = phaseOfClass(classOf[SetRootTree]) myPicklerPhase = phaseOfClass(classOf[Pickler]) myInliningPhase = phaseOfClass(classOf[Inlining]) myStagingPhase = phaseOfClass(classOf[Staging]) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index f54b8a62fa25..be651842d9b0 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2539,7 +2539,9 @@ object SymDenotations { ) if compiledNow.exists then compiledNow else - val assocFiles = multi.aggregate(d => Set(d.symbol.associatedFile.nn), _ union _) + val assocFiles = multi + .filterWithPredicate(_.symbol.maybeOwner.isPackageObject) + .aggregate(d => Set(d.symbol.associatedFile.nn), _ union _) if assocFiles.size == 1 then multi // they are all overloaded variants from the same file else @@ -2680,10 +2682,6 @@ object SymDenotations { stillValidInOwner(denot) } - def movedToCompanionClass(denot: SymDenotation)(using Context): Boolean = - val ownerCompanion = denot.maybeOwner.companionClass - stillValid(ownerCompanion) && ownerCompanion.unforcedDecls.contains(denot.name, denot.symbol) - private[SymDenotations] def stillValidInOwner(denot: SymDenotation)(using Context): Boolean = try val owner = denot.maybeOwner.denot stillValid(owner) diff --git a/compiler/src/dotty/tools/dotc/core/SymUtils.scala b/compiler/src/dotty/tools/dotc/core/SymUtils.scala index 3a97a0053dbd..1a762737d52f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/SymUtils.scala @@ -271,6 +271,9 @@ class SymUtils: self.owner.info.decl(fieldName).suchThat(!_.is(Method)).symbol } + def paramNamed(name: Name)(using Context): Symbol = + self.rawParamss.nestedFind(_.name == name).getOrElse(NoSymbol) + /** Is this symbol a constant expression final val? * * This is the case if all of the following are true: diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 17d427513e58..ca3df65625a8 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1243,8 +1243,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling tl => otherTycon.appliedTo(bodyArgs(tl))) else otherTycon - (assumedTrue(tycon) || directionalIsSubType(tycon, adaptedTycon)) && - directionalRecur(adaptedTycon.appliedTo(args), other) + rollbackConstraintsUnless: + (assumedTrue(tycon) || directionalIsSubType(tycon, adaptedTycon)) + && directionalRecur(adaptedTycon.appliedTo(args), other) } } end compareAppliedTypeParamRef @@ -2133,11 +2134,16 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // resort to reflection to invoke the member. And Java reflection needs to know exact // erased parameter types. See neg/i12211.scala. Other reflection algorithms could // conceivably dispatch without knowing precise parameter signatures. One can signal - // this by inheriting from the `scala.reflect.SignatureCanBeImprecise` marker trait, + // this by inheriting from the `scala.Selectable.WithoutPreciseParameterTypes` marker trait, // in which case the signature test is elided. + // We also relax signature checking when checking bounds, + // for instance in tests/pos/i17222.izumi.min.scala + // the `go` method info as seen from `Foo` is `>: (in: Any): Unit <: (Nothing): Unit` + // So the parameter types conform but their signatures don't match. def sigsOK(symInfo: Type, info2: Type) = tp2.underlyingClassRef(refinementOK = true).member(name).exists || tp2.derivesFrom(defn.WithoutPreciseParameterTypesClass) + || ctx.mode.is(Mode.CheckBoundsOrSelfType) || symInfo.isInstanceOf[MethodType] && symInfo.signature.consistentParams(info2.signature) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index 1c9696da67d1..4761beae8bd0 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -117,7 +117,7 @@ extends TypeError: em"""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-decode-stacktraces. + |For the unprocessed stack trace, compile with -Xno-enrich-error-messages. |A recurring operation is (inner to outer): |${opsString(mostCommon).stripMargin}""" @@ -137,7 +137,7 @@ object handleRecursive: e def apply(op: String, details: => String, exc: Throwable, weight: Int = 1)(using Context): Nothing = - if ctx.settings.XnoDecodeStacktraces.value then + if ctx.settings.XnoEnrichErrorMessages.value then throw exc else exc match case _: RecursionOverflow => diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 1106ba68fb97..e3168ca5a27d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -511,7 +511,22 @@ object TypeOps: tp else (if pre.isSingleton then NoType else tryWiden(tp, tp.prefix)).orElse { if (tp.isTerm && variance > 0 && !pre.isSingleton) - apply(tp.info.widenExpr) + tp.prefix match + case inlines.Inliner.OpaqueProxy(ref) => + // Strip refinements on an opaque alias proxy + // Using pos/i22068 as an example, + // Inliner#addOpaqueProxies add the following opaque alias proxy: + // val $proxy1: foos.type { type Foo[T] = String } = + // foos.$asInstanceOf[foos.type { type Foo[T] = String }] + // Then when InlineCall#expand creates a typed Inlined, + // we type avoid any local bindings, which includes that opaque alias proxy. + // To avoid that the replacement is a non-singleton RefinedType, + // we drop the refinements too and return foos.type. + // That way, when we inline `def m1` and we calculate the asSeenFrom + // of `b1.and(..)` b1 doesn't have an unstable prefix. + derivedSelect(tp, ref) + case _ => + apply(tp.info.widenExpr) else if (upper(pre).member(tp.name).exists) super.derivedSelect(tp, pre) else diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index f343d7227bf8..14ccf32c7787 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -127,8 +127,17 @@ class TypeUtils: case Some(types) => TypeOps.nestedPairs(types) case None => throw new AssertionError("not a tuple") - def namedTupleElementTypesUpTo(bound: Int, normalize: Boolean = true)(using Context): List[(TermName, Type)] = + def namedTupleElementTypesUpTo(bound: Int, derived: Boolean, normalize: Boolean = true)(using Context): List[(TermName, Type)] = (if normalize then self.normalized else self).dealias match + // for desugaring and printer, ignore derived types to avoid infinite recursion in NamedTuple.unapply + case AppliedType(tycon, nmes :: vals :: Nil) if !derived && tycon.typeSymbol == defn.NamedTupleTypeRef.symbol => + val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: + case ConstantType(Constant(str: String)) => str.toTermName + case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") + val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) + names.zip(values) + case t if !derived => Nil + // default cause, used for post-typing case defn.NamedTuple(nmes, vals) => val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: case ConstantType(Constant(str: String)) => str.toTermName @@ -138,8 +147,8 @@ class TypeUtils: case t => Nil - def namedTupleElementTypes(using Context): List[(TermName, Type)] = - namedTupleElementTypesUpTo(Int.MaxValue) + def namedTupleElementTypes(derived: Boolean)(using Context): List[(TermName, Type)] = + namedTupleElementTypesUpTo(Int.MaxValue, derived) def isNamedTupleType(using Context): Boolean = self match case defn.NamedTuple(_, _) => true diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 31e11487ae38..f77f268d6ee6 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -38,8 +38,7 @@ import config.Printers.{core, typr, matchTypes} import reporting.{trace, Message} import java.lang.ref.WeakReference import compiletime.uninitialized -import cc.{CapturingType, CaptureRef, CaptureSet, SingletonCaptureRef, isTrackableRef, - derivedCapturingType, isBoxedCapturing, isCaptureChecking, isRetains, isRetainsLike} +import cc.* import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable @@ -867,23 +866,19 @@ object Types extends TypeUtils { } else val isRefinedMethod = rinfo.isInstanceOf[MethodOrPoly] - rinfo match - case CapturingType(_, refs: CaptureSet.RefiningVar) if ccConfig.optimizedRefinements => - pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, rinfo) + val joint = 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 + && (rinfo <:< joint.info + || name == nme.apply && defn.isFunctionType(tp.parent)) => + // use `rinfo` to keep the right parameter names for named args. See i8516.scala. + joint.derivedSingleDenotation(joint.symbol, rinfo, pre, isRefinedMethod) case _ => - val joint = 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 - && (rinfo <:< joint.info - || name == nme.apply && defn.isFunctionType(tp.parent)) => - // use `rinfo` to keep the right parameter names for named args. See i8516.scala. - joint.derivedSingleDenotation(joint.symbol, rinfo, pre, isRefinedMethod) - case _ => - joint + joint } def goApplied(tp: AppliedType, tycon: HKTypeLambda) = @@ -4007,7 +4002,7 @@ object Types extends TypeUtils { (compute(status, parent, theAcc) /: refs.elems) { (s, ref) => ref.stripReach match case tp: TermParamRef if tp.binder eq thisLambdaType => combine(s, CaptureDeps) - case _ => s + case tp => combine(s, compute(status, tp, theAcc)) } case _ => if tp.annot.refersToParamOf(thisLambdaType) then TrueDeps @@ -4078,6 +4073,10 @@ object Types extends TypeUtils { 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 _ => defn.captureRoot.termRef case AnnotatedType(parent, ann) if ann.refersToParamOf(thisLambdaType) => val parent1 = mapOver(parent) if ann.symbol.isRetainsLike then @@ -4179,24 +4178,28 @@ object Types extends TypeUtils { * - wrap types of parameters that have an @allowConversions annotation with Into[_] */ def fromSymbols(params: List[Symbol], resultType: Type)(using Context): MethodType = + apply(params.map(_.name.asTermName))( + tl => params.map(p => tl.integrate(params, adaptParamInfo(p))), + tl => tl.integrate(params, resultType)) + + /** Adapt info of parameter symbol to be integhrated into corresponding MethodType + * using the scheme described in `fromSymbols`. + */ + def adaptParamInfo(param: Symbol, pinfo: Type)(using Context): Type = 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)) - - def paramInfo(param: Symbol) = - var paramType = param.info - .annotatedToRepeated - .mapIntoAnnot(defn.IntoAnnot, defn.IntoParamAnnot) - if param.is(Inline) then - paramType = addAnnotation(paramType, defn.InlineParamAnnot, param) - if param.is(Erased) then - paramType = addAnnotation(paramType, defn.ErasedParamAnnot, param) - paramType - - apply(params.map(_.name.asTermName))( - tl => params.map(p => tl.integrate(params, paramInfo(p))), - tl => tl.integrate(params, resultType)) - end fromSymbols + var paramType = pinfo + .annotatedToRepeated + .mapIntoAnnot(defn.IntoAnnot, defn.IntoParamAnnot) + if param.is(Inline) then + paramType = addAnnotation(paramType, defn.InlineParamAnnot, param) + if param.is(Erased) then + paramType = addAnnotation(paramType, defn.ErasedParamAnnot, param) + paramType + + def adaptParamInfo(param: Symbol)(using Context): Type = + adaptParamInfo(param, param.info) def apply(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type)(using Context): MethodType = checkValid(unique(new CachedMethodType(paramNames)(paramInfosExp, resultTypeExp, self))) @@ -4634,6 +4637,7 @@ object Types extends TypeUtils { */ def isUnreducibleWild(using Context): Boolean = tycon.isLambdaSub && hasWildcardArg && !isMatchAlias + && !(args.sizeIs == 1 && defn.isCompiletime_S(tycon.typeSymbol)) // S is a pseudo Match Alias def tryCompiletimeConstantFold(using Context): Type = if myEvalRunId == ctx.runId then myEvalued @@ -6078,6 +6082,8 @@ object Types extends TypeUtils { 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 ref: $result, ${result.underlyingIterator.toList}") ensureTrackable(result) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 7fd6444746ce..7b80c7c80a21 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -392,7 +392,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { } catch case ex: Throwable => - if !ctx.settings.XnoDecodeStacktraces.value + if !ctx.settings.XnoEnrichErrorMessages.value && handleRecursive.underlyingStackOverflowOrNull(ex) != null then throw StackSizeExceeded(mdef) else @@ -941,7 +941,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { em"""Recursion limit exceeded while pickling ${ex.mdef} |in ${ex.mdef.symbol.showLocated}. |You could try to increase the stacksize using the -Xss JVM option. - |For the unprocessed stack trace, compile with -Xno-decode-stacktraces.""", + |For the unprocessed stack trace, compile with -Xno-enrich-error-messages.""", ex.mdef.srcPos) def missing = forwardSymRefs.keysIterator diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index de99ce0105ea..c07121a52191 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -395,9 +395,10 @@ class TreeUnpickler(reader: TastyReader, case TYPEREFin => val name = readName().toTypeName val prefix = readType() + def pre = if TypeOps.isLegalPrefix(prefix) then prefix else QualSkolemType(prefix) val space = readType() space.decl(name) match { - case symd: SymDenotation if prefix.isArgPrefixOf(symd.symbol) => TypeRef(prefix, symd.symbol) + case symd: SymDenotation if prefix.isArgPrefixOf(symd.symbol) => TypeRef(pre, symd.symbol) case _ => TypeRef(prefix, name, space.decl(name).asSeenFrom(prefix)) } case REFINEDtype => @@ -1432,7 +1433,7 @@ class TreeUnpickler(reader: TastyReader, extendOnly(namedArgs) else // needs reordering, and possibly fill in holes for default arguments - val argsByName = mutable.AnyRefMap.from(namedArgs.map(arg => arg.name -> arg)) + val argsByName = mutable.HashMap.from(namedArgs.map(arg => arg.name -> arg)) val reconstructedArgs = formalNames.lazyZip(methType.paramInfos).map { (name, tpe) => argsByName.remove(name).getOrElse(makeDefault(name, tpe)) } diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 611fda9c1d41..3b91312740d1 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -161,7 +161,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas private val entries = new Array[AnyRef](index.length) /** A map from symbols to their associated `decls` scopes */ - private val symScopes = mutable.AnyRefMap[Symbol, Scope]() + private val symScopes = mutable.HashMap[Symbol, Scope]() /** A mapping from method types to the parameters used in constructing them */ private val paramsOfMethodType = new java.util.IdentityHashMap[MethodType, List[Symbol]] diff --git a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala index 98e67178fb69..3061bfa4ee5c 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala @@ -2,15 +2,27 @@ package dotty.tools.dotc package coverage import scala.collection.mutable +import java.nio.file.Path /** Holds a list of statements to include in the coverage reports. */ class Coverage: private val statementsById = new mutable.LongMap[Statement](256) + private var statementId: Int = 0 + + def nextStatementId(): Int = + statementId += 1 + statementId - 1 + + def statements: Iterable[Statement] = statementsById.values def addStatement(stmt: Statement): Unit = statementsById(stmt.id) = stmt + def removeStatementsFromFile(sourcePath: Path) = + val removedIds = statements.filter(_.location.sourcePath == sourcePath).map(_.id.toLong) + removedIds.foreach(statementsById.remove) + /** * A statement that can be invoked, and thus counted as "covered" by code coverage tools. diff --git a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala index 26fd52fb7138..3edb323e6b3b 100644 --- a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala +++ b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala @@ -216,31 +216,21 @@ class InlineReducer(inliner: Inliner)(using Context): type TypeBindsMap = SimpleIdentityMap[TypeSymbol, java.lang.Boolean] def getTypeBindsMap(pat: Tree, tpt: Tree): TypeBindsMap = { - val getBinds = new TreeAccumulator[Set[TypeSymbol]] { - def apply(syms: Set[TypeSymbol], t: Tree)(using Context): Set[TypeSymbol] = { - val syms1 = t match { - case t: Bind if t.symbol.isType => - syms + t.symbol.asType - case _ => syms - } - foldOver(syms1, t) - } - } - // Extractors can contain Bind nodes in type parameter lists, // for that case tree looks like this: // UnApply[t @ t](pats)(implicits): T[t] // Test case is pos/inline-caseclass.scala. + // // Alternatively, for explicitly specified type binds in type annotations like in // case A(B): A[t] // the tree will look like this: // Unapply[t](pats)(implicits) : T[t @ t] // and the binds will be found in the type tree instead // Test case is pos-macros/i15971 - val tptBinds = getBinds(Set.empty[TypeSymbol], tpt) + val tptBinds = tpt.bindTypeSymbols.toSet val binds: Set[TypeSymbol] = pat match { case UnApply(TypeApply(_, tpts), _, _) => - getBinds(Set.empty[TypeSymbol], tpts) ++ tptBinds + tpts.flatMap(_.bindTypeSymbols).toSet ++ tptBinds case _ => tptBinds } @@ -372,7 +362,7 @@ class InlineReducer(inliner: Inliner)(using Context): val (caseBindings, from, to) = substBindings(caseBindingMap.toList, mutable.ListBuffer(), Nil, Nil) val (guardOK, canReduceGuard) = if cdef.guard.isEmpty then (true, true) - else typer.typed(cdef.guard.subst(from, to), defn.BooleanType) match { + else stripInlined(typer.typed(cdef.guard.subst(from, to), defn.BooleanType)) match { case ConstantValue(v: Boolean) => (v, true) case _ => (false, false) } diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index e06e6b3e1615..2242174f78f2 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -131,6 +131,39 @@ object Inliner: case _ => tree else super.transformInlined(tree) end InlinerMap + + object OpaqueProxy: + + def apply(ref: TermRef, cls: ClassSymbol, span: Span)(using Context): TermRef = + def openOpaqueAliases(selfType: Type): List[(Name, Type)] = selfType match + case RefinedType(parent, rname, TypeAlias(alias)) => + val opaq = cls.info.member(rname).symbol + if opaq.isOpaqueAlias then + (rname, alias.stripLazyRef.asSeenFrom(ref, cls)) + :: openOpaqueAliases(parent) + else Nil + case _ => Nil + val refinements = openOpaqueAliases(cls.givenSelfType) + val refinedType = refinements.foldLeft(ref: Type): (parent, refinement) => + RefinedType(parent, refinement._1, TypeAlias(refinement._2)) + val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, refinedType, span) + refiningSym.termRef + + def unapply(refiningRef: TermRef)(using Context): Option[TermRef] = + val refiningSym = refiningRef.symbol + if refiningSym.name.is(InlineBinderName) && refiningSym.is(Synthetic, butNot=InlineProxy) then + refiningRef.info match + case refinedType: RefinedType => refinedType.stripRefinement match + case ref: TermRef => Some(ref) + case _ => None + case _ => None + else + None + + end OpaqueProxy + + private[inlines] def newSym(name: Name, flags: FlagSet, info: Type, span: Span)(using Context): Symbol = + newSymbol(ctx.owner, name, flags, info, coord = span) end Inliner /** Produces an inlined version of `call` via its `inlined` method. @@ -189,7 +222,7 @@ class Inliner(val call: tpd.Tree)(using Context): private val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] private[inlines] def newSym(name: Name, flags: FlagSet, info: Type)(using Context): Symbol = - newSymbol(ctx.owner, name, flags, info, coord = call.span) + Inliner.newSym(name, flags, info, call.span) /** A binding for the parameter of an inline method. This is a `val` def for * by-value parameters and a `def` def for by-name parameters. `val` defs inherit @@ -351,20 +384,9 @@ class Inliner(val call: tpd.Tree)(using Context): && (forThisProxy || inlinedMethod.isContainedIn(cls)) && mapRef(ref).isEmpty then - def openOpaqueAliases(selfType: Type): List[(Name, Type)] = selfType match - case RefinedType(parent, rname, TypeAlias(alias)) => - val opaq = cls.info.member(rname).symbol - if opaq.isOpaqueAlias then - (rname, alias.stripLazyRef.asSeenFrom(ref, cls)) - :: openOpaqueAliases(parent) - else Nil - case _ => - Nil - val refinements = openOpaqueAliases(cls.givenSelfType) - val refinedType = refinements.foldLeft(ref: Type) ((parent, refinement) => - RefinedType(parent, refinement._1, TypeAlias(refinement._2)) - ) - val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, refinedType).asTerm + val refiningRef = OpaqueProxy(ref, cls, call.span) + val refiningSym = refiningRef.symbol.asTerm + val refinedType = refiningRef.info val refiningDef = ValDef(refiningSym, tpd.ref(ref).cast(refinedType), inferred = true).withSpan(span) inlining.println(i"add opaque alias proxy $refiningDef for $ref in $tp") bindingsBuf += refiningDef @@ -768,6 +790,13 @@ class Inliner(val call: tpd.Tree)(using Context): override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { val locked = ctx.typerState.ownedVars val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan)) + + // Make sure that the named type has the correct denotation. + // For instance in tests/pos/i22070 when we type `Featureful[?]#toFeatures`, + // `selectionType` will skolemize the prefix, find the denotation, + // and then set that denotation for the `TermRef(Featureful[?], symbol toFeatures)`. + selectionType(tree, qual1) + val resNoReduce = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) val reducedProjection = reducer.reduceProjection(resNoReduce) if reducedProjection.isType then diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index aeecd9c376e3..85b1234461c8 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -12,6 +12,9 @@ import SymDenotations.SymDenotation import config.Printers.inlining import ErrorReporting.errorTree import dotty.tools.dotc.util.{SourceFile, SourcePosition, SrcPos} +import dotty.tools.dotc.transform.* +import dotty.tools.dotc.transform.MegaPhase +import dotty.tools.dotc.transform.MegaPhase.MiniPhase import parsing.Parsers.Parser import transform.{PostTyper, Inlining, CrossVersionChecks} import staging.StagingLevel @@ -19,6 +22,7 @@ import staging.StagingLevel import collection.mutable import reporting.{NotConstant, trace} import util.Spans.Span +import dotty.tools.dotc.core.Periods.PhaseId /** Support for querying inlineable methods and for inlining calls to such methods */ object Inlines: @@ -345,10 +349,58 @@ object Inlines: // We should not be rewriting tested strings val noRewriteSettings = ctx.settings.rewrite.updateIn(ctx.settingsState.reinitializedCopy(), None) + class MegaPhaseWithCustomPhaseId(miniPhases: Array[MiniPhase], startId: PhaseId, endId: PhaseId) + extends MegaPhase(miniPhases) { + override def start: Int = startId + override def end: Int = endId + } + + // Let's reconstruct necessary transform MegaPhases, without anything + // that could cause problems here (like `CrossVersionChecks`). + // The individiual lists here should line up with Compiler.scala, i.e + // separate chunks there should also be kept separate here. + // For now we create a single MegaPhase, since there does not seem to + // be any important checks later (e.g. ForwardDepChecks could be applicable here, + // but the equivalent is also not run in the scala 2's `ctx.typechecks`, + // so let's leave it out for now). + lazy val reconstructedTransformPhases = + val transformPhases: List[List[(Class[?], () => MiniPhase)]] = List( + List( + (classOf[InlineVals], () => new InlineVals), + (classOf[ElimRepeated], () => new ElimRepeated), + (classOf[RefChecks], () => new RefChecks), + ), + ) + + transformPhases.flatMap( (megaPhaseList: List[(Class[?], () => MiniPhase)]) => + val (newMegaPhasePhases, phaseIds) = + megaPhaseList.flatMap { + case (filteredPhaseClass, miniphaseConstructor) => + ctx.base.phases + .find(phase => filteredPhaseClass.isInstance(phase)) + .map(phase => (miniphaseConstructor(), phase.id)) + } + .unzip + if newMegaPhasePhases.isEmpty then None + else Some(MegaPhaseWithCustomPhaseId(newMegaPhasePhases.toArray, phaseIds.head, phaseIds.last)) + ) + ConstFold(underlyingCodeArg).tpe.widenTermRefExpr match { case ConstantType(Constant(code: String)) => - val source2 = SourceFile.virtual("tasty-reflect", code) - inContext(ctx.fresh.setSettings(noRewriteSettings).setNewTyperState().setTyper(new Typer(ctx.nestingLevel + 1)).setSource(source2)) { + val unitName = "tasty-reflect" + val source2 = SourceFile.virtual(unitName, code) + // We need a dummy owner, as the actual one does not have a computed denotation yet, + // but might be inspected in a transform phase, leading to cyclic errors + val dummyOwner = newSymbol(ctx.owner, "$dummySymbol$".toTermName, Private, defn.AnyType, NoSymbol) + val newContext = + ctx.fresh + .setSettings(noRewriteSettings) + .setNewTyperState() + .setTyper(new Typer(ctx.nestingLevel + 1)) + .setSource(source2) + .withOwner(dummyOwner) + + inContext(newContext) { val tree2 = new Parser(source2).block() if ctx.reporter.allErrors.nonEmpty then ctx.reporter.allErrors.map((ErrorKind.Parser, _)) @@ -357,10 +409,23 @@ object Inlines: ctx.base.postTyperPhase match case postTyper: PostTyper if ctx.reporter.allErrors.isEmpty => val tree4 = atPhase(postTyper) { postTyper.newTransformer.transform(tree3) } - ctx.base.inliningPhase match - case inlining: Inlining if ctx.reporter.allErrors.isEmpty => - atPhase(inlining) { inlining.newTransformer.transform(tree4) } - case _ => + ctx.base.setRootTreePhase match + case setRootTree => + val tree5 = + val compilationUnit = CompilationUnit(unitName, code) + compilationUnit.tpdTree = tree4 + compilationUnit.untpdTree = tree2 + var units = List(compilationUnit) + atPhase(setRootTree)(setRootTree.runOn(units).head.tpdTree) + ctx.base.inliningPhase match + case inlining: Inlining if ctx.reporter.allErrors.isEmpty => + val tree6 = atPhase(inlining) { inlining.newTransformer.transform(tree5) } + if ctx.reporter.allErrors.isEmpty && reconstructedTransformPhases.nonEmpty then + var transformTree = tree6 + for phase <- reconstructedTransformPhases do + if ctx.reporter.allErrors.isEmpty then + transformTree = atPhase(phase.end + 1)(phase.transformUnit(transformTree)) + case _ => case _ => ctx.reporter.allErrors.map((ErrorKind.Typer, _)) } diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 7a0a19552f48..333af6a26b3b 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -532,7 +532,7 @@ object Completion: def namedTupleCompletionsFromType(tpe: Type): CompletionMap = val freshCtx = ctx.fresh.setExploreTyperState() inContext(freshCtx): - tpe.namedTupleElementTypes + tpe.namedTupleElementTypes(true) .map { (name, tpe) => val symbol = newSymbol(owner = NoSymbol, name, EmptyFlags, tpe) val denot = SymDenotation(symbol, NoSymbol, name, EmptyFlags, tpe) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 220053e277a5..38f554c10b0a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -27,7 +27,7 @@ import ScriptParsers.* import Decorators.* import util.Chars import scala.annotation.tailrec -import rewrites.Rewrites.{patch, overlapsPatch} +import rewrites.Rewrites.{overlapsPatch, patch, unpatch} import reporting.* import config.Feature import config.Feature.{sourceVersion, migrateTo3} @@ -2240,7 +2240,7 @@ object Parsers { atSpan(in.offset): if in.isIdent(nme.UPARROW) && Feature.ccEnabled then in.nextToken() - TypeBoundsTree(EmptyTree, makeCapsBound()) + makeCapsBound() else TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE)) @@ -2779,6 +2779,12 @@ object Parsers { simpleExprRest(tapp, location, canApply = true) case LPAREN | LBRACE | INDENT if canApply => val app = atSpan(startOffset(t), in.offset) { mkApply(t, argumentExprs()) } + if in.rewriteToIndent then + app match + case Apply(Apply(_, List(Block(_, _))), List(blk @ Block(_, _))) => + unpatch(blk.srcPos.sourcePos.source, Span(blk.span.start, blk.span.start + 1)) + unpatch(blk.srcPos.sourcePos.source, Span(blk.span.end, blk.span.end + 1)) + case _ => simpleExprRest(app, location, canApply = true) case USCORE => atSpan(startOffset(t), in.skipToken()) { PostfixOp(t, Ident(nme.WILDCARD)) } @@ -3519,7 +3525,7 @@ object Parsers { * UsingClsTermParamClause::= ‘(’ ‘using’ [‘erased’] (ClsParams | ContextTypes) ‘)’ * ClsParams ::= ClsParam {‘,’ ClsParam} * ClsParam ::= {Annotation} - * [{Modifier | ‘tracked’} (‘val’ | ‘var’)] Param + * [{Modifier} (‘val’ | ‘var’)] Param * TypelessClause ::= DefTermParamClause * | UsingParamClause * @@ -3557,8 +3563,6 @@ object Parsers { if isErasedKw then mods = addModifier(mods) if paramOwner.isClass then - if isIdent(nme.tracked) && in.featureEnabled(Feature.modularity) && !in.lookahead.isColon then - mods = addModifier(mods) mods = addFlag(modifiers(start = mods), ParamAccessor) mods = if in.token == VAL then @@ -3706,7 +3710,15 @@ object Parsers { in.languageImportContext = in.languageImportContext.importContext(imp, NoSymbol) for case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors do if Feature.handleGlobalLanguageImport(prefix, imported) && !outermost then - syntaxError(em"this language import is only allowed at the toplevel", id.span) + val desc = + if ctx.mode.is(Mode.Interactive) then + "not allowed in the REPL" + else "only allowed at the toplevel" + val hint = + if ctx.mode.is(Mode.Interactive) then + f"\nTo use this language feature, include the flag `-language:$prefix.$imported` when starting the REPL" + else "" + syntaxError(em"this language import is $desc$hint", id.span) if allSourceVersionNames.contains(imported) && prefix.isEmpty then if !outermost then syntaxError(em"source version import is only allowed at the toplevel", id.span) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 2dc0a1a8d805..2007b633a7c5 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -212,6 +212,7 @@ object Scanners { def featureEnabled(name: TermName) = Feature.enabled(name)(using languageImportContext) def erasedEnabled = featureEnabled(Feature.erasedDefinitions) + def trackedEnabled = featureEnabled(Feature.modularity) private var postfixOpsEnabledCache = false private var postfixOpsEnabledCtx: Context = NoContext @@ -1195,7 +1196,7 @@ object Scanners { def isSoftModifier: Boolean = token == IDENTIFIER - && (softModifierNames.contains(name) || name == nme.erased && erasedEnabled) + && (softModifierNames.contains(name) || name == nme.erased && erasedEnabled || name == nme.tracked && trackedEnabled) def isSoftModifierInModifierPosition: Boolean = isSoftModifier && inModifierPosition() diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 3caba59a091f..27ab73f0fe4d 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -30,7 +30,7 @@ import config.SourceVersion.* import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} -import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef, isRetains, ReachCapability, MaybeCapability} +import cc.* import dotty.tools.dotc.parsing.JavaParsers class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { @@ -248,8 +248,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def appliedText(tp: Type): Text = tp match case tp @ AppliedType(tycon, args) => val namedElems = - try tp.namedTupleElementTypesUpTo(200, normalize = false) - catch case ex: TypeError => Nil + try tp.namedTupleElementTypesUpTo(200, false, normalize = false) + catch + case ex: TypeError => Nil if namedElems.nonEmpty then toTextNamedTuple(namedElems) else tp.tupleElementTypesUpTo(200, normalize = false) match @@ -285,6 +286,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if !printDebug && appliedText(tp.asInstanceOf[HKLambda].resType).isEmpty => // don't eta contract if the application would be printed specially toText(tycon) + case Existential(boundVar, unpacked) + if !printDebug && !ctx.settings.YccDebug.value && !unpacked.existsPart(_ == boundVar) => + toText(unpacked) case tp: RefinedType if defn.isFunctionType(tp) && !printDebug => toTextMethodAsFunction(tp.refinedInfo, isPure = Feature.pureFunsEnabled && !tp.typeSymbol.name.isImpureFunction, @@ -569,6 +573,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case tree: TypeTree => typeText(toText(tree.typeOpt)) ~ Str("(inf)").provided(tree.isInferred && printDebug) + case SingletonTypeTree(ref: Literal) => toTextLocal(ref) case SingletonTypeTree(ref) => toTextLocal(ref) ~ "." ~ keywordStr("type") case RefinedTypeTree(tpt, refines) => @@ -1121,7 +1126,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def recur(t: untpd.Tree): Text = t match case Apply(fn, Nil) => recur(fn) case Apply(fn, args) => - val explicitArgs = args.filterNot(_.symbol.name.is(DefaultGetterName)) + val explicitArgs = args.filterNot(untpd.stripNamedArg(_).symbol.name.is(DefaultGetterName)) recur(fn) ~ "(" ~ toTextGlobal(explicitArgs, ", ") ~ ")" case TypeApply(fn, args) => recur(fn) ~ "[" ~ toTextGlobal(args, ", ") ~ "]" case Select(qual, nme.CONSTRUCTOR) => recur(qual) diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index d3467fe70c52..cc78203e873f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -219,6 +219,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case DeprecatedAssignmentSyntaxID // errorNumber: 203 case DeprecatedInfixNamedArgumentSyntaxID // errorNumber: 204 case GivenSearchPriorityID // errorNumber: 205 + case EnumMayNotBeValueClassesID // errorNumber: 206 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index bb3194558cae..3fe7b51c7aa0 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -343,7 +343,7 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre val (found2, expected2) = if (found1 frozen_<:< expected1) || reported.fbounded then (found, expected) else (found1, expected1) - val (foundStr, expectedStr) = Formatting.typeDiff(found2, expected2) + val (foundStr, expectedStr) = Formatting.typeDiff(found2.normalized, expected2.normalized) i"""|Found: $foundStr |Required: $expectedStr${reported.notes}""" end msg @@ -2469,7 +2469,7 @@ class PureExpressionInStatementPosition(stat: untpd.Tree, val exprOwner: Symbol) class PureUnitExpression(stat: untpd.Tree, tpe: Type)(using Context) extends Message(PureUnitExpressionID) { def kind = MessageKind.PotentialIssue - def msg(using Context) = i"Discarded non-Unit value of type ${tpe.widen}. You may want to use `()`." + def msg(using Context) = i"Discarded non-Unit value of type ${tpe.widen}. Add `: Unit` to discard silently." def explain(using Context) = i"""As this expression is not of type Unit, it is desugared into `{ $stat; () }`. |Here the `$stat` expression is a pure statement that can be discarded. @@ -2504,12 +2504,15 @@ class ExtensionNullifiedByMember(method: Symbol, target: Symbol)(using Context) extends Message(ExtensionNullifiedByMemberID): def kind = MessageKind.PotentialIssue def msg(using Context) = - i"""Extension method ${hl(method.name.toString)} will never be selected - |because ${hl(target.name.toString)} already has a member with the same name and compatible parameter types.""" + val targetName = hl(target.name.toString) + i"""Extension method ${hl(method.name.toString)} will never be selected from type $targetName + |because $targetName already has a member with the same name and compatible parameter types.""" def explain(using Context) = - i"""An extension method can be invoked as a regular method, but if that is intended, + i"""Although extensions can be overloaded, they do not overload existing member methods. + |An extension method can be invoked as a regular method, but if that is the intended usage, |it should not be defined as an extension. - |Although extensions can be overloaded, they do not overload existing member methods.""" + | + |The extension may be invoked as though selected from an arbitrary type if conversions are in play.""" class TraitCompanionWithMutableStatic()(using Context) extends SyntaxMsg(TraitCompanionWithMutableStaticID) { @@ -3173,7 +3176,7 @@ class InlinedAnonClassWarning()(using Context) class ValueDiscarding(tp: Type)(using Context) extends Message(ValueDiscardingID): def kind = MessageKind.PotentialIssue - def msg(using Context) = i"discarded non-Unit value of type $tp" + def msg(using Context) = i"discarded non-Unit value of type ${tp.widen}. Add `: Unit` to discard silently." def explain(using Context) = "" class UnusedNonUnitValue(tp: Type)(using Context) @@ -3331,7 +3334,7 @@ final class QuotedTypeMissing(tpe: Type)(using Context) extends StagingMessage(Q private def witness = defn.QuotedTypeClass.typeRef.appliedTo(tpe) - override protected def msg(using Context): String = + override protected def msg(using Context): String = i"Reference to $tpe within quotes requires a given ${witness} in scope" override protected def explain(using Context): String = @@ -3399,3 +3402,9 @@ class GivenSearchPriorityWarning( |$migrationHints""" def explain(using Context) = "" + +final class EnumMayNotBeValueClasses(sym: Symbol)(using Context) extends SyntaxMsg(EnumMayNotBeValueClassesID): + def msg(using Context): String = i"$sym may not be a value class" + + def explain(using Context) = "" +end EnumMayNotBeValueClasses diff --git a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala index 305f61dfa177..3c7216625a7c 100644 --- a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala +++ b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala @@ -37,6 +37,11 @@ object Rewrites { def addPatch(span: Span, replacement: String): Unit = pbuf += Patch(span, replacement) + // remove patches which match either end point + def removePatch(span: Span): Unit = + def p(other: Span): Boolean = span.start == other.start || span.end == other.end + 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) @@ -87,6 +92,16 @@ object Rewrites { def patch(span: Span, replacement: String)(using Context): Unit = patch(ctx.compilationUnit.source, span, replacement) + /** Delete patches matching the given span, + * where a match has the same start or end offset. + */ + def unpatch(source: SourceFile, span: Span)(using Context): Unit = + if ctx.reporter != Reporter.NoReporter // NoReporter is used for syntax highlighting + then ctx.settings.rewrite.value.foreach: rewrites => + rewrites.patched + .get(source) + .foreach(_.removePatch(span)) + /** Does `span` overlap with a patch region of `source`? */ def overlapsPatch(source: SourceFile, span: Span)(using Context): Boolean = ctx.settings.rewrite.value.exists(rewrites => diff --git a/compiler/src/dotty/tools/dotc/transform/ArrayConstructors.scala b/compiler/src/dotty/tools/dotc/transform/ArrayConstructors.scala index e94fa612e6cf..84cb64533532 100644 --- a/compiler/src/dotty/tools/dotc/transform/ArrayConstructors.scala +++ b/compiler/src/dotty/tools/dotc/transform/ArrayConstructors.scala @@ -27,7 +27,10 @@ class ArrayConstructors extends MiniPhase { override def transformApply(tree: tpd.Apply)(using Context): tpd.Tree = { def expand(elemType: Type, dims: List[Tree]) = - tpd.newArray(elemType, tree.tpe, tree.span, JavaSeqLiteral(dims, TypeTree(defn.IntClass.typeRef))) + val elemTypeNonVoid = + if elemType.isValueSubType(defn.UnitType) then defn.BoxedUnitClass.typeRef + else elemType + tpd.newArray(elemTypeNonVoid, tree.tpe, tree.span, JavaSeqLiteral(dims, TypeTree(defn.IntClass.typeRef))) if (tree.fun.symbol eq defn.ArrayConstructor) { val TypeApply(tycon, targ :: Nil) = tree.fun: @unchecked diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index d647d50560d3..6e626fc5dd9e 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -1,6 +1,7 @@ package dotty.tools.dotc.transform import scala.annotation.tailrec +import scala.collection.mutable import dotty.tools.uncheckedNN import dotty.tools.dotc.ast.tpd @@ -24,7 +25,7 @@ import dotty.tools.dotc.core.Mode import dotty.tools.dotc.core.Types.{AnnotatedType, ConstantType, NoType, TermRef, Type, TypeTraverser} import dotty.tools.dotc.core.Flags.flagsString import dotty.tools.dotc.core.Flags -import dotty.tools.dotc.core.Names.Name +import dotty.tools.dotc.core.Names.{Name, TermName} import dotty.tools.dotc.core.NameOps.isReplWrapperName import dotty.tools.dotc.transform.MegaPhase.MiniPhase import dotty.tools.dotc.core.Annotations @@ -211,7 +212,7 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke /** * This traverse is the **main** component of this phase * - * It traverse the tree the tree and gather the data in the + * It traverses the tree and gathers the data in the * corresponding context property */ private def traverser = new TreeTraverser: @@ -456,14 +457,21 @@ object CheckUnused: val (wildcardSels, nonWildcardSels) = imp.selectors.partition(_.isWildcard) nonWildcardSels ::: wildcardSels + val excludedMembers: mutable.Set[TermName] = mutable.Set.empty + val newDataInScope = for sel <- reorderdSelectors yield val data = new ImportSelectorData(qualTpe, sel) if shouldSelectorBeReported(imp, sel) || isImportExclusion(sel) || isImportIgnored(imp, sel) then // Immediately mark the selector as used data.markUsed() + if isImportExclusion(sel) then + excludedMembers += sel.name + if sel.isWildcard && excludedMembers.nonEmpty then + // mark excluded members for the wildcard import + data.markExcluded(excludedMembers.toSet) data - impInScope.top.prependAll(newDataInScope) + impInScope.top.appendAll(newDataInScope) end registerImport /** Register (or not) some `val` or `def` according to the context, scope and flags */ @@ -703,7 +711,7 @@ object CheckUnused: /** Given an import and accessibility, return selector that matches import<->symbol */ private def isInImport(selData: ImportSelectorData, altName: Option[Name], isDerived: Boolean)(using Context): Boolean = - assert(sym.exists) + assert(sym.exists, s"Symbol $sym does not exist") val selector = selData.selector @@ -719,7 +727,10 @@ object CheckUnused: selData.allSymbolsForNamed.contains(sym) else // Wildcard - if !selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) then + if selData.excludedMembers.contains(altName.getOrElse(sym.name).toTermName) then + // Wildcard with exclusions that match the symbol + false + else if !selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) then // The qualifier does not have the target symbol as a member false else @@ -832,11 +843,14 @@ object CheckUnused: final class ImportSelectorData(val qualTpe: Type, val selector: ImportSelector): private var myUsed: Boolean = false + var excludedMembers: Set[TermName] = Set.empty def markUsed(): Unit = myUsed = true def isUsed: Boolean = myUsed + def markExcluded(excluded: Set[TermName]): Unit = excludedMembers ++= excluded + private var myAllSymbols: Set[Symbol] | Null = null def allSymbolsForNamed(using Context): Set[Symbol] = diff --git a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala index eca3928569f1..0bf2dc107ce9 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala @@ -103,6 +103,9 @@ class ElimByName extends MiniPhase, InfoTransformer: Closure(meth, _ => arg.changeOwnerAfter(ctx.owner, meth, thisPhase), targetType = defn.ByNameFunction(argType) + // Note: this will forget any captures on the original by-name type + // But that's not a problem since we treat these closures specially + // anyway during recheck. ).withSpan(arg.span) private def isByNameRef(tree: Tree)(using Context): Boolean = diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index f5e0f8c63b58..0229284a1b5f 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -38,12 +38,6 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: override def isEnabled(using ctx: Context) = ctx.settings.coverageOutputDir.value.nonEmpty - // counter to assign a unique id to each statement - private var statementId = 0 - - // stores all instrumented statements - private val coverage = Coverage() - private var coverageExcludeClasslikePatterns: List[Pattern] = Nil private var coverageExcludeFilePatterns: List[Pattern] = Nil @@ -61,12 +55,17 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: .foreach(_.nn.delete()) end if + // Initialise a coverage object if it does not exist yet + if ctx.base.coverage == null then + ctx.base.coverage = Coverage() + coverageExcludeClasslikePatterns = ctx.settings.coverageExcludeClasslikes.value.map(_.r.pattern) coverageExcludeFilePatterns = ctx.settings.coverageExcludeFiles.value.map(_.r.pattern) + ctx.base.coverage.nn.removeStatementsFromFile(ctx.compilationUnit.source.file.absolute.jpath) super.run - Serializer.serialize(coverage, outputPath, ctx.settings.sourceroot.value) + Serializer.serialize(ctx.base.coverage.nn, outputPath, ctx.settings.sourceroot.value) private def isClassIncluded(sym: Symbol)(using Context): Boolean = val fqn = sym.fullName.toText(ctx.printerFn(ctx)).show @@ -110,8 +109,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: * @return the statement's id */ private def recordStatement(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Int = - val id = statementId - statementId += 1 + val id = ctx.base.coverage.nn.nextStatementId() val sourceFile = pos.source val statement = Statement( @@ -127,7 +125,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: treeName = tree.getClass.getSimpleName.nn, branch ) - coverage.addStatement(statement) + ctx.base.coverage.nn.addStatement(statement) id /** diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 9750c41b7252..ee608a4297bf 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -107,7 +107,7 @@ object PatternMatcher { // TODO: Drop Case once we use everywhere else `isPatmatGenerated`. private def dropNamedTuple(tree: Tree): Tree = - val tpe = tree.tpe.widen + val tpe = tree.tpe.widenDealias if tpe.isNamedTupleType then tree.cast(tpe.stripNamedTuple) else tree /** The plan `let x = rhs in body(x)` where `x` is a fresh variable */ diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 0feee53ca50f..898517806e50 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -2,7 +2,7 @@ package dotty.tools package dotc package transform -import dotty.tools.dotc.ast.{Trees, tpd, untpd, desugar} +import dotty.tools.dotc.ast.{Trees, tpd, untpd, desugar, TreeTypeMap} import scala.collection.mutable import core.* import dotty.tools.dotc.typer.Checking @@ -16,7 +16,7 @@ import Symbols.*, NameOps.* import ContextFunctionResults.annotateContextResults import config.Printers.typr import config.Feature -import util.SrcPos +import util.{SrcPos, Stats} import reporting.* import NameKinds.WildcardParamName import cc.* @@ -154,7 +154,21 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => case _ => case _ => - private def transformAnnot(annot: Tree)(using Context): Tree = { + /** Returns a copy of the given tree with all symbols fresh. + * + * Used to guarantee that no symbols are shared between trees in different + * annotations. + */ + private def copySymbols(tree: Tree)(using Context) = + Stats.trackTime("Annotations copySymbols"): + val ttm = + new TreeTypeMap: + override def withMappedSyms(syms: List[Symbol]) = + withMappedSyms(syms, mapSymbols(syms, this, true)) + ttm(tree) + + /** Transforms the given annotation tree. */ + private def transformAnnotTree(annot: Tree)(using Context): Tree = { val saved = inJavaAnnot inJavaAnnot = annot.symbol.is(JavaDefined) if (inJavaAnnot) checkValidJavaAnnotation(annot) @@ -163,7 +177,19 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => } private def transformAnnot(annot: Annotation)(using Context): Annotation = - annot.derivedAnnotation(transformAnnot(annot.tree)) + val tree1 = + annot match + case _: BodyAnnotation => annot.tree + case _ => copySymbols(annot.tree) + annot.derivedAnnotation(transformAnnotTree(tree1)) + + /** Transforms all annotations in the given type. */ + private def transformAnnotsIn(using Context) = + new TypeMap: + def apply(tp: Type) = tp match + case tp @ AnnotatedType(parent, annot) => + tp.derivedAnnotatedType(mapOver(parent), transformAnnot(annot)) + case _ => mapOver(tp) private def processMemberDef(tree: Tree)(using Context): tree.type = { val sym = tree.symbol @@ -501,7 +527,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => Checking.checkRealizable(tree.tpt.tpe, tree.srcPos, "SAM type") super.transform(tree) case tree @ Annotated(annotated, annot) => - cpy.Annotated(tree)(transform(annotated), transformAnnot(annot)) + cpy.Annotated(tree)(transform(annotated), transformAnnotTree(annot)) case tree: AppliedTypeTree => if (tree.tpt.symbol == defn.andType) Checking.checkNonCyclicInherited(tree.tpe, tree.args.tpes, EmptyScope, tree.srcPos) @@ -524,11 +550,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => super.transform(tree) case tree: TypeTree => val tpe = if tree.isInferred then CleanupRetains()(tree.tpe) else tree.tpe - tree.withType: - tpe match - case AnnotatedType(parent, annot) => - AnnotatedType(parent, transformAnnot(annot)) // TODO: Also map annotations embedded in type? - case _ => tpe + tree.withType(transformAnnotsIn(tpe)) case Typed(Ident(nme.WILDCARD), _) => withMode(Mode.Pattern)(super.transform(tree)) // The added mode signals that bounds in a pattern need not diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 8df9e5966920..9631136a1c4e 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -28,18 +28,29 @@ import dotty.tools.dotc.cc.boxed object Recheck: import tpd.* - /** Attachment key for rechecked types of TypeTrees */ - val RecheckedType = Property.Key[Type] - - val addRecheckedTypes = new TreeMap: - override def transform(tree: Tree)(using Context): Tree = - try - val tree1 = super.transform(tree) - tree.getAttachment(RecheckedType) match - case Some(tpe) => tree1.withType(tpe) - case None => tree1 - catch - case _:TypeError => tree + /** Attachment key for a toplevel tree of a unit that contains a map + * from nodes in that tree to their rechecked "new" types + */ + val RecheckedTypes = Property.Key[util.EqHashMap[Tree, Type]] + + /** If tree carries a RecheckedTypes attachment, use the associated `nuTypes` + * map to produce a new tree that contains at each node the type in the + * map as the node's .tpe field + */ + def addRecheckedTypes(tree: Tree)(using Context): Tree = + tree.getAttachment(RecheckedTypes) match + case Some(nuTypes) => + val withNuTypes = new TreeMap: + override def transform(tree: Tree)(using Context): Tree = + try + val tree1 = super.transform(tree) + val tpe = nuTypes.lookup(tree) + if tpe != null then tree1.withType(tpe) else tree1 + catch + case _: TypeError => tree + withNuTypes.transform(tree) + case None => + tree extension (sym: Symbol)(using Context) @@ -61,30 +72,6 @@ object Recheck: val symd = sym.denot symd.validFor.firstPhaseId == phase.id + 1 && (sym.originDenotation ne symd) - extension [T <: Tree](tree: T) - - /** Remember `tpe` as the type of `tree`, which might be different from the - * type stored in the tree itself, unless a type was already remembered for `tree`. - */ - def rememberType(tpe: Type)(using Context): Unit = - if !tree.hasAttachment(RecheckedType) then rememberTypeAlways(tpe) - - /** Remember `tpe` as the type of `tree`, which might be different from the - * type stored in the tree itself - */ - def rememberTypeAlways(tpe: Type)(using Context): Unit = - if tpe ne tree.knownType then tree.putAttachment(RecheckedType, tpe) - - /** The remembered type of the tree, or if none was installed, the original type */ - def knownType: Type = - tree.attachmentOrElse(RecheckedType, tree.tpe) - - def hasRememberedType: Boolean = tree.hasAttachment(RecheckedType) - - def withKnownType(using Context): T = tree.getAttachment(RecheckedType) match - case Some(tpe) => tree.withType(tpe).asInstanceOf[T] - case None => tree - /** Map ExprType => T to () ?=> T (and analogously for pure versions). * Even though this phase runs after ElimByName, ExprTypes can still occur * as by-name arguments of applied types. See note in doc comment for @@ -99,7 +86,7 @@ object Recheck: * - in function and method parameter types * - under annotations */ - def normalizeByName(tp: Type)(using Context): Type = tp.dealias match + def normalizeByName(tp: Type)(using Context): Type = tp.dealiasKeepAnnots match case tp: ExprType => mapExprType(tp) case tp: PolyType => @@ -172,17 +159,32 @@ abstract class Recheck extends Phase, SymTransformer: class Rechecker(@constructorOnly ictx: Context): private val ta = ictx.typeAssigner - /** If true, remember types of all tree nodes in attachments so that they - * can be retrieved with `knownType` - */ - private val keepAllTypes = inContext(ictx) { - ictx.settings.Xprint.value.containsPhase(thisPhase) - } + private val nuTypes = util.EqHashMap[Tree, Type]() + + extension [T <: Tree](tree: T) + + /** Set new type of the tree if none was installed yet and the new type is different + * from the current type. + */ + def setNuType(tpe: Type): Unit = + if nuTypes.lookup(tree) == null && (tpe ne tree.tpe) then nuTypes(tree) = tpe + + /** The new type of the tree, or if none was installed, the original type */ + def nuType(using Context): Type = + val ntpe = nuTypes.lookup(tree) + if ntpe != null then ntpe else tree.tpe + + /** Was a new type installed for this tree? */ + def hasNuType: Boolean = + nuTypes.lookup(tree) != null + end extension - /** Should type of `tree` be kept in an attachment so that it can be retrieved with - * `knownType`? By default true only is `keepAllTypes` hold, but can be overridden. + /** 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. */ - def keepType(tree: Tree): Boolean = keepAllTypes + def keepNuTypes(using Context): Boolean = + ctx.settings.Xprint.value.containsPhase(thisPhase) /** A map from NamedTypes to the denotations they had before this phase. * Needed so that we can `reset` them after this phase. @@ -291,7 +293,7 @@ abstract class Recheck extends Phase, SymTransformer: protected def instantiate(mt: MethodType, argTypes: List[Type], sym: Symbol)(using Context): Type = mt.instantiate(argTypes) - /** A hook to massage the type of an applied method; currently not overridden */ + /** 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 = @@ -336,14 +338,13 @@ abstract class Recheck extends Phase, SymTransformer: assert(formals.isEmpty) Nil val argTypes = recheckArgs(tree.args, formals, fntpe.paramRefs) - recheckApplication(tree, qualType, fntpe1, argTypes) + recheckApplication(tree, qualType, fntpe, argTypes) //.showing(i"typed app $tree : $fntpe with ${tree.args}%, % : $argTypes%, % = $result") case tp => assert(false, i"unexpected type of ${tree.fun}: $tp") def recheckTypeApply(tree: TypeApply, pt: Type)(using Context): Type = val funtpe = recheck(tree.fun) - tree.fun.rememberType(funtpe) // remember type to support later bounds checks funtpe.widen match case fntpe: PolyType => assert(fntpe.paramInfos.hasSameLengthAs(tree.args)) @@ -387,6 +388,10 @@ abstract class Recheck extends Phase, SymTransformer: def recheckClosure(tree: Closure, pt: Type, forceDependent: Boolean = false)(using Context): Type = if tree.tpt.isEmpty then tree.meth.tpe.widen.toFunctionType(tree.meth.symbol.is(JavaDefined), alwaysDependent = forceDependent) + else if defn.isByNameFunction(tree.tpt.tpe) then + val mt @ MethodType(Nil) = tree.meth.tpe.widen: @unchecked + val cmt = ContextualMethodType(Nil, Nil, mt.resultType) + cmt.toFunctionType(alwaysDependent = forceDependent) else recheck(tree.tpt) @@ -455,7 +460,7 @@ abstract class Recheck extends Phase, SymTransformer: seqLitType(tree, TypeComparer.lub(declaredElemType :: elemTypes)) def recheckTypeTree(tree: TypeTree)(using Context): Type = - tree.knownType // allows to install new types at Setup + tree.nuType // allows to install new types at Setup def recheckAnnotated(tree: Annotated)(using Context): Type = tree.tpe match @@ -554,7 +559,7 @@ abstract class Recheck extends Phase, SymTransformer: */ def recheckFinish(tpe: Type, tree: Tree, pt: Type)(using Context): Type = val tpe1 = checkConforms(tpe, pt, tree) - if keepType(tree) then tree.rememberType(tpe1) + tree.setNuType(tpe1) tpe1 def recheck(tree: Tree, pt: Type = WildcardType)(using Context): Type = @@ -613,6 +618,7 @@ abstract class Recheck extends Phase, SymTransformer: def checkUnit(unit: CompilationUnit)(using Context): Unit = recheck(unit.tpdTree) + if keepNuTypes then unit.tpdTree.putAttachment(RecheckedTypes, nuTypes) end Rechecker @@ -620,7 +626,8 @@ abstract class Recheck extends Phase, SymTransformer: override def show(tree: untpd.Tree)(using Context): String = atPhase(thisPhase): withMode(Mode.Printing): - super.show(addRecheckedTypes.transform(tree.asInstanceOf[tpd.Tree])) + super.show: + addRecheckedTypes(tree.asInstanceOf[tpd.Tree]) end Recheck /** A class that can be used to test basic rechecking without any customaization */ diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index dd3f41be5a8e..6bc5db79fee5 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -109,8 +109,8 @@ object ResolveSuper { sym = other.symbol // Having a matching denotation is not enough: it should also be a subtype // of the superaccessor's type, see i5433.scala for an example where this matters - val otherTp = other.asSeenFrom(base.typeRef).info - val accTp = acc.asSeenFrom(base.typeRef).info + val otherTp = other.asSeenFrom(base.thisType).info + val accTp = acc.asSeenFrom(base.thisType).info // Since the super class can be Java defined, // we use relaxed overriding check for explicit nulls if one of the symbols is Java defined. // This forces `Null` to be a subtype of non-primitive value types during override checking. diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index c1dd6bc6509e..a8c8ec8ce1d8 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -56,7 +56,7 @@ object TypeTestsCasts { * 9. if `X` is `T1 | T2`, checkable(T1, P) && checkable(T2, P). * 10. otherwise, "" */ - def whyUncheckable(X: Type, P: Type, span: Span)(using Context): String = atPhase(Phases.refchecksPhase.next) { + def whyUncheckable(X: Type, P: Type, span: Span, trustTypeApplication: Boolean)(using Context): String = atPhase(Phases.refchecksPhase.next) { extension (inline s1: String) inline def &&(inline s2: String): String = if s1 == "" then s2 else s1 extension (inline b: Boolean) inline def |||(inline s: String): String = if b then "" else s @@ -143,7 +143,7 @@ object TypeTestsCasts { case defn.ArrayOf(tpE) => recur(tpE, tpT) case _ => recur(defn.AnyType, tpT) } - case tpe @ AppliedType(tycon, targs) => + case tpe @ AppliedType(tycon, targs) if !trustTypeApplication => X.widenDealias match { case OrType(tp1, tp2) => // This case is required to retrofit type inference, @@ -366,8 +366,7 @@ object TypeTestsCasts { if (sym.isTypeTest) { val argType = tree.args.head.tpe val isTrusted = tree.hasAttachment(PatternMatcher.TrustedTypeTestKey) - if !isTrusted then - checkTypePattern(expr.tpe, argType, expr.srcPos) + checkTypePattern(expr.tpe, argType, expr.srcPos, isTrusted) transformTypeTest(expr, argType, flagUnrelated = enclosingInlineds.isEmpty) // if test comes from inlined code, dont't flag it even if it always false } @@ -392,10 +391,10 @@ object TypeTestsCasts { def checkBind(tree: Bind)(using Context) = checkTypePattern(defn.ThrowableType, tree.body.tpe, tree.srcPos) - private def checkTypePattern(exprTpe: Type, castTpe: Type, pos: SrcPos)(using Context) = + private def checkTypePattern(exprTpe: Type, castTpe: Type, pos: SrcPos, trustTypeApplication: Boolean = false)(using Context) = val isUnchecked = exprTpe.widenTermRefExpr.hasAnnotation(defn.UncheckedAnnot) if !isUnchecked then - val whyNot = whyUncheckable(exprTpe, castTpe, pos.span) + val whyNot = whyUncheckable(exprTpe, castTpe, pos.span, trustTypeApplication) if whyNot.nonEmpty then report.uncheckedWarning(UncheckedTypePattern(castTpe, whyNot), pos) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 52760cf8b6c7..316213a94f8d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -405,11 +405,18 @@ class Objects(using Context @constructorOnly): def getVar(x: Symbol)(using data: Data, ctx: Context): Option[Heap.Addr] = data.getVar(x) - def of(ddef: DefDef, args: List[Value], outer: Data)(using Context): Data = + 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 = 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) - new LocalEnv(params.zip(args).toMap, ddef.symbol, outer)(valsMap = mutable.Map.empty, varsMap = mutable.Map.empty) + _of(params.zip(args).toMap, ddef.symbol, outer) + + def ofByName(byNameParam: Symbol, outer: Data): Data = + assert(byNameParam.is(Flags.Param) && byNameParam.info.isInstanceOf[ExprType]); + _of(Map.empty, byNameParam, outer) def setLocalVal(x: Symbol, value: Value)(using data: Data, ctx: Context): Unit = assert(!x.isOneOf(Flags.Param | Flags.Mutable), "Only local immutable variable allowed") @@ -719,7 +726,7 @@ class Objects(using Context @constructorOnly): else Env.resolveEnv(meth.owner.enclosingMethod, ref, summon[Env.Data]).getOrElse(Cold -> Env.NoEnv) - val env2 = Env.of(ddef, args.map(_.value), outerEnv) + val env2 = Env.ofDefDef(ddef, args.map(_.value), outerEnv) extendTrace(ddef) { given Env.Data = env2 cache.cachedEval(ref, ddef.rhs, cacheResult = true) { expr => @@ -750,7 +757,7 @@ class Objects(using Context @constructorOnly): code match case ddef: DefDef => if meth.name == nme.apply then - given Env.Data = Env.of(ddef, args.map(_.value), env) + given Env.Data = Env.ofDefDef(ddef, args.map(_.value), env) extendTrace(code) { eval(ddef.rhs, thisV, klass, cacheResult = true) } else // The methods defined in `Any` and `AnyRef` are trivial and don't affect initialization. @@ -786,7 +793,7 @@ class Objects(using Context @constructorOnly): val ddef = ctor.defTree.asInstanceOf[DefDef] val argValues = args.map(_.value) - given Env.Data = Env.of(ddef, argValues, Env.NoEnv) + given Env.Data = Env.ofDefDef(ddef, argValues, Env.NoEnv) if ctor.isPrimaryConstructor then val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] extendTrace(cls.defTree) { eval(tpl, ref, cls, cacheResult = true) } @@ -1013,7 +1020,7 @@ class Objects(using Context @constructorOnly): if isByNameParam(sym) then value match case fun: Fun => - given Env.Data = fun.env + given Env.Data = Env.ofByName(sym, fun.env) eval(fun.code, fun.thisV, fun.klass) case Cold => report.warning("Calling cold by-name alias. " + Trace.show, Trace.position) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 5fb91694b8a6..98bfbe69ff8c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -68,6 +68,21 @@ object Applications { unapplySeqTypeElemTp(productSelectorTypes(tp, errorPos).last).exists } + /** Does `tp` fit the "product-seq match" conditions for a `NonEmptyTuple` as + * an unapply result type for a pattern with `numArgs` subpatterns? + * This is the case if (1) `tp` derives from `NonEmptyTuple`. + * (2) `tp.tupleElementTypes` exists. + * (3) `tp.tupleElementTypes.last` conforms to Seq match + */ + def isNonEmptyTupleSeqMatch(tp: Type, numArgs: Int, errorPos: SrcPos = NoSourcePosition)(using Context): Boolean = { + tp.derivesFrom(defn.NonEmptyTupleClass) + && tp.tupleElementTypes.exists { elemTypes => + val arity = elemTypes.size + arity > 0 && arity <= numArgs + 1 && + unapplySeqTypeElemTp(elemTypes.last).exists + } + } + /** Does `tp` fit the "get match" conditions as an unapply result type? * This is the case of `tp` has a `get` member as well as a * parameterless `isEmpty` member of result type `Boolean`. @@ -109,6 +124,10 @@ object Applications { if (isValid) elemTp else NoType } + def namedTupleOrProductTypes(tp: Type)(using Context): List[Type] = + if tp.isNamedTupleType then tp.namedTupleElementTypes(true).map(_(1)) + 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 @@ -136,12 +155,17 @@ object Applications { sels.takeWhile(_.exists).toList } - def productSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] = { - val selTps = productSelectorTypes(tp, pos) - val arity = selTps.length - val elemTp = unapplySeqTypeElemTp(selTps.last) - (0 until argsNum).map(i => if (i < arity - 1) selTps(i) else elemTp).toList - } + def productSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] = + seqSelectors(productSelectorTypes(tp, pos), argsNum) + + def nonEmptyTupleSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] = + seqSelectors(tp.tupleElementTypes.get, argsNum) + + private def seqSelectors(selectorTypes: List[Type], argsNum: Int)(using Context): List[Type] = + val arity = selectorTypes.length + val elemTp = unapplySeqTypeElemTp(selectorTypes.last) + (0 until argsNum).map(i => if (i < arity - 1) selectorTypes(i) else elemTp).toList + end seqSelectors /** A utility class that matches results of unapplys with patterns. Two queriable members: * val argTypes: List[Type] @@ -172,14 +196,19 @@ object Applications { args.map(Function.const(elemTp)) else if isProductSeqMatch(tp, args.length, pos) then productSeqSelectors(tp, args.length, pos) - else if tp.derivesFrom(defn.NonEmptyTupleClass) then - tp.tupleElementTypes.getOrElse(Nil) + else if isNonEmptyTupleSeqMatch(tp, args.length, pos) then + nonEmptyTupleSeqSelectors(tp, args.length, pos) else fallback private def tryAdaptPatternArgs(elems: List[untpd.Tree], pt: Type)(using Context): Option[List[untpd.Tree]] = - tryEither[Option[List[untpd.Tree]]] - (Some(desugar.adaptPatternArgs(elems, pt))) - ((_, _) => None) + namedTupleOrProductTypes(pt) match + 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))) + ((_, _) => None) + case pts => + Some(desugar.adaptPatternArgs(elems, pt)) private def getUnapplySelectors(tp: Type)(using Context): List[Type] = // We treat patterns as product elements if @@ -199,20 +228,22 @@ object Applications { else tp :: Nil private def productUnapplySelectors(tp: Type)(using Context): Option[List[Type]] = - if defn.isProductSubType(tp) then - tryAdaptPatternArgs(args, tp) match + val validatedTupleElements = desugar.checkWellFormedTupleElems(args) + + if defn.isProductSubType(tp) && args.lengthCompare(productArity(tp)) <= 0 then + tryAdaptPatternArgs(validatedTupleElements, tp) match case Some(args1) if isProductMatch(tp, args1.length, pos) => args = args1 Some(productSelectorTypes(tp, pos)) case _ => None - else tp.widen.normalized.dealias match - case tp @ defn.NamedTuple(_, tt) => - tryAdaptPatternArgs(args, tp) match - case Some(args1) => - args = args1 - tt.tupleElementTypes - case _ => None - case _ => None + else tp.widen.normalized.dealias match + case tp @ defn.NamedTuple(_, tt) => + tryAdaptPatternArgs(validatedTupleElements, tp) match + case Some(args1) => + args = args1 + tt.tupleElementTypes + case _ => None + case _ => None /** The computed argument types which will be the scutinees of the sub-patterns. */ val argTypes: List[Type] = @@ -513,7 +544,7 @@ trait Applications extends Compatibility { case tp => args.size } - !isJavaAnnotConstr(methRef.symbol) && + !isAnnotConstr(methRef.symbol) && args.size < requiredArgNum(funType) } @@ -662,6 +693,11 @@ trait Applications extends Compatibility { def isJavaAnnotConstr(sym: Symbol): Boolean = sym.is(JavaDefined) && sym.isConstructor && sym.owner.is(JavaAnnotation) + + /** Is `sym` a constructor of an annotation? */ + def isAnnotConstr(sym: Symbol): Boolean = + sym.isConstructor && sym.owner.isAnnotation + /** Match re-ordered arguments against formal parameters * @param n The position of the first parameter in formals in `methType`. */ @@ -958,6 +994,8 @@ trait Applications extends Compatibility { case (arg: NamedArg, _) => arg case (arg, name) => NamedArg(name, arg) } + else if isAnnotConstr(methRef.symbol) then + typedArgs else if !sameSeq(args, orderedArgs) && !typedArgs.forall(isSafeArg) then // need to lift arguments to maintain evaluation order in the // presence of argument reorderings. @@ -1129,7 +1167,7 @@ trait Applications extends Compatibility { case _ => () else () - fun1.tpe match { + val result = fun1.tpe match { case err: ErrorType => cpy.Apply(tree)(fun1, proto.typedArgs()).withType(err) case TryDynamicCallType => val isInsertedApply = fun1 match { @@ -1203,6 +1241,11 @@ trait Applications extends Compatibility { else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail)) } } + + if result.tpe.isNothingType then + val nnInfo = result.notNullInfo + result.withNotNullInfo(nnInfo.terminatedInfo) + else result } /** Convert expression like diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 1cd531046753..e870ffd0fc90 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -734,11 +734,16 @@ object Checking { case _ => report.error(ValueClassesMayNotContainInitalization(clazz), stat.srcPos) } - if (clazz.isDerivedValueClass) { + // We don't check synthesised enum anonymous classes that are generated from + // enum extending a value class type (AnyVal or an alias of it) + // The error message 'EnumMayNotBeValueClassesID' will take care of generating the error message (See #22236) + if (clazz.isDerivedValueClass && !clazz.isEnumAnonymClass) { if (clazz.is(Trait)) report.error(CannotExtendAnyVal(clazz), clazz.srcPos) if clazz.is(Module) then report.error(CannotExtendAnyVal(clazz), clazz.srcPos) + if (clazz.is(Enum)) + report.error(EnumMayNotBeValueClasses(clazz), clazz.srcPos) if (clazz.is(Abstract)) report.error(ValueClassesMayNotBeAbstract(clazz), clazz.srcPos) if (!clazz.isStatic) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 87dec93f8040..e8b22325d1e9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -66,9 +66,9 @@ class Namer { typer: Typer => /** A partial map from unexpanded member and pattern defs and to their expansions. * Populated during enterSyms, emptied during typer. */ - //lazy val expandedTree = new mutable.AnyRefMap[DefTree, Tree] + //lazy val expandedTree = new mutable.HashMap[DefTree, Tree] /*{ - override def default(tree: DefTree) = tree // can't have defaults on AnyRefMaps :-( + override def default(tree: DefTree) = tree // can't have defaults on HashMaps :-( }*/ /** A map from expanded MemberDef, PatDef or Import trees to their symbols. @@ -76,12 +76,12 @@ class Namer { typer: Typer => * with the same symbol is created (this can be when the symbol is completed * or at the latest when the tree is typechecked. */ - //lazy val symOfTree = new mutable.AnyRefMap[Tree, Symbol] + //lazy val symOfTree = new mutable.HashMap[Tree, Symbol] /** A map from expanded trees to their typed versions. * Populated when trees are typechecked during completion (using method typedAhead). */ - // lazy val typedTree = new mutable.AnyRefMap[Tree, tpd.Tree] + // lazy val typedTree = new mutable.HashMap[Tree, tpd.Tree] /** A map from method symbols to nested typers. * Populated when methods are completed. Emptied when they are typechecked. @@ -89,7 +89,7 @@ class Namer { typer: Typer => * one, so that trees that are shared between different DefDefs can be independently * used as indices. It also contains a scope that contains nested parameters. */ - lazy val nestedTyper: mutable.AnyRefMap[Symbol, Typer] = new mutable.AnyRefMap + lazy val nestedTyper: mutable.HashMap[Symbol, Typer] = new mutable.HashMap /** We are entering symbols coming from a SourceLoader */ private var lateCompile = false @@ -878,7 +878,7 @@ class Namer { typer: Typer => case original: untpd.MemberDef => lazy val annotCtx = annotContext(original, sym) original.setMods: - original.mods.withAnnotations : + original.mods.withAnnotations: original.mods.annotations.mapConserve: annotTree => val cls = typedAheadAnnotationClass(annotTree)(using annotCtx) if (cls eq sym) @@ -1626,7 +1626,8 @@ class Namer { typer: Typer => } else { val pclazz = pt.typeSymbol - if pclazz.is(Final) then + // The second condition avoids generating a useless message (See #22236 for more details) + if pclazz.is(Final) && !(pclazz.is(Enum) && pclazz.isDerivedValueClass) then report.error(ExtendFinalClass(cls, pclazz), cls.srcPos) else if pclazz.isEffectivelySealed && pclazz.associatedFile != cls.associatedFile then if pclazz.is(Sealed) && !pclazz.is(JavaDefined) then @@ -2016,6 +2017,11 @@ class Namer { typer: Typer => paramFn: Type => Type, fallbackProto: Type )(using Context): Type = + /** Is this member tracked? This is true if it is marked as `tracked` or if + * it overrides a `tracked` member. To account for the later, `isTracked` + * is overriden to `true` as a side-effect of computing `inherited`. + */ + var isTracked: Boolean = sym.is(Tracked) /** A type for this definition that might be inherited from elsewhere: * If this is a setter parameter, the corresponding getter type. @@ -2051,8 +2057,10 @@ class Namer { typer: Typer => if paramss.isEmpty then info.widenExpr else NoType - val iRawInfo = - cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema, sym.targetName).info + val iDenot = cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema, sym.targetName) + val iSym = iDenot.symbol + if iSym.is(Tracked) then isTracked = true + val iRawInfo = iDenot.info val iResType = instantiatedResType(iRawInfo, paramss).asSeenFrom(site, cls) if (iResType.exists) typr.println(i"using inherited type for ${mdef.name}; raw: $iRawInfo, inherited: $iResType") @@ -2146,6 +2154,7 @@ class Namer { typer: Typer => if defaultTp.exists then TypeOps.SimplifyKeepUnchecked() else null) match case ctp: ConstantType if sym.isInlineVal => ctp + case tp if isTracked => tp case tp => TypeComparer.widenInferred(tp, pt, Widen.Unions) // Replace aliases to Unit by Unit itself. If we leave the alias in @@ -2156,7 +2165,7 @@ class Namer { typer: Typer => def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.srcPos) //if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType") if (inherited.exists) - if sym.isInlineVal then lhsType else inherited + if sym.isInlineVal || isTracked then lhsType else inherited else { if (sym.is(Implicit)) mdef match { diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 3f071dad2d03..310ca999f4c5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -52,34 +52,46 @@ object Nullables: val hiTree = if(hiTpe eq hi.typeOpt) hi else TypeTree(hiTpe) TypeBoundsTree(lo, hiTree, alias) - /** A set of val or var references that are known to be not null, plus a set of - * variable references that are not known (anymore) to be not null + /** A set of val or var references that are known to be not null + * after the tree finishes executing normally (non-exceptionally), + * plus a set of variable references that are ever assigned to null, + * and may therefore be null if execution of the tree is interrupted + * by an exception. */ - case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef]): - assert((asserted & retracted).isEmpty) - + case class NotNullInfo(asserted: Set[TermRef] | Null, retracted: Set[TermRef]): def isEmpty = this eq NotNullInfo.empty def retractedInfo = NotNullInfo(Set(), retracted) + def terminatedInfo = NotNullInfo(null, retracted) + /** The sequential combination with another not-null info */ def seq(that: NotNullInfo): NotNullInfo = if this.isEmpty then that else if that.isEmpty then this - else NotNullInfo( - this.asserted.union(that.asserted).diff(that.retracted), - this.retracted.union(that.retracted).diff(that.asserted)) + else + val newAsserted = + if this.asserted == null || that.asserted == null then null + else this.asserted.diff(that.retracted).union(that.asserted) + val newRetracted = this.retracted.union(that.retracted) + NotNullInfo(newAsserted, newRetracted) /** The alternative path combination with another not-null info. Used to merge - * the nullability info of the two branches of an if. + * the nullability info of the branches of an if or match. */ def alt(that: NotNullInfo): NotNullInfo = - NotNullInfo(this.asserted.intersect(that.asserted), this.retracted.union(that.retracted)) + val newAsserted = + if this.asserted == null then that.asserted + else if that.asserted == null then this.asserted + else this.asserted.intersect(that.asserted) + val newRetracted = this.retracted.union(that.retracted) + NotNullInfo(newAsserted, newRetracted) + end NotNullInfo object NotNullInfo: val empty = new NotNullInfo(Set(), Set()) - def apply(asserted: Set[TermRef], retracted: Set[TermRef]): NotNullInfo = - if asserted.isEmpty && retracted.isEmpty then empty + def apply(asserted: Set[TermRef] | Null, retracted: Set[TermRef]): NotNullInfo = + if asserted != null && asserted.isEmpty && retracted.isEmpty then empty else new NotNullInfo(asserted, retracted) end NotNullInfo @@ -223,7 +235,7 @@ object Nullables: */ @tailrec def impliesNotNull(ref: TermRef): Boolean = infos match case info :: infos1 => - if info.asserted.contains(ref) then true + if info.asserted == null || info.asserted.contains(ref) then true else if info.retracted.contains(ref) then false else infos1.impliesNotNull(ref) case _ => @@ -233,16 +245,15 @@ object Nullables: * or retractions in `info` supersede infos in existing entries of `infos`. */ def extendWith(info: NotNullInfo) = - if info.isEmpty - || info.asserted.forall(infos.impliesNotNull(_)) - && !info.retracted.exists(infos.impliesNotNull(_)) - then infos + if info.isEmpty then infos else info :: infos /** Retract all references to mutable variables */ def retractMutables(using Context) = - val mutables = infos.foldLeft(Set[TermRef]())((ms, info) => - ms.union(info.asserted.filter(_.symbol.is(Mutable)))) + val mutables = infos.foldLeft(Set[TermRef]()): + (ms, info) => ms.union( + if info.asserted == null then Set.empty + else info.asserted.filter(_.symbol.is(Mutable))) infos.extendWith(NotNullInfo(Set(), mutables)) end extension @@ -304,15 +315,35 @@ object Nullables: extension (tree: Tree) /* The `tree` with added nullability attachment */ - def withNotNullInfo(info: NotNullInfo): tree.type = - if !info.isEmpty then tree.putAttachment(NNInfo, info) + def withNotNullInfo(info: NotNullInfo)(using Context): tree.type = + if ctx.explicitNulls && !info.isEmpty then tree.putAttachment(NNInfo, info) tree + /* Collect the nullability info from parts of `tree` */ + def collectNotNullInfo(using Context): NotNullInfo = tree match + case Typed(expr, _) => + expr.notNullInfo + case Apply(fn, args) => + val argsInfo = args.map(_.notNullInfo) + val fnInfo = fn.notNullInfo + argsInfo.foldLeft(fnInfo)(_ seq _) + case TypeApply(fn, _) => + fn.notNullInfo + case _ => + // Other cases are handled specially in typer. + NotNullInfo.empty + /* The nullability info of `tree` */ def notNullInfo(using Context): NotNullInfo = - stripInlined(tree).getAttachment(NNInfo) match - case Some(info) if !ctx.erasedTypes => info - case _ => NotNullInfo.empty + if !ctx.explicitNulls then NotNullInfo.empty + else + val tree1 = stripInlined(tree) + tree1.getAttachment(NNInfo) match + case Some(info) if !ctx.erasedTypes => info + case _ => + val nnInfo = tree1.collectNotNullInfo + tree1.withNotNullInfo(nnInfo) + nnInfo /* The nullability info of `tree`, assuming it is a condition that evaluates to `c` */ def notNullInfoIf(c: Boolean)(using Context): NotNullInfo = @@ -393,21 +424,23 @@ object Nullables: end extension extension (tree: Assign) - def computeAssignNullable()(using Context): tree.type = tree.lhs match - case TrackedRef(ref) => - val rhstp = tree.rhs.typeOpt - if ctx.explicitNulls && ref.isNullableUnion then - if rhstp.isNullType || rhstp.isNullableUnion then - // If the type of rhs is nullable (`T|Null` or `Null`), then the nullability of the - // lhs variable is no longer trackable. We don't need to check whether the type `T` - // is correct here, as typer will check it. - tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) - else - // If the initial type is nullable and the assigned value is non-null, - // we add it to the NotNull. - tree.withNotNullInfo(NotNullInfo(Set(ref), Set())) - else tree - case _ => tree + def computeAssignNullable()(using Context): tree.type = + var nnInfo = tree.rhs.notNullInfo + tree.lhs match + case TrackedRef(ref) if ctx.explicitNulls && ref.isNullableUnion => + nnInfo = nnInfo.seq: + val rhstp = tree.rhs.typeOpt + if rhstp.isNullType || rhstp.isNullableUnion then + // If the type of rhs is nullable (`T|Null` or `Null`), then the nullability of the + // lhs variable is no longer trackable. We don't need to check whether the type `T` + // is correct here, as typer will check it. + NotNullInfo(Set(), Set(ref)) + else + // If the initial type is nullable and the assigned value is non-null, + // we add it to the NotNull. + NotNullInfo(Set(ref), Set()) + case _ => + tree.withNotNullInfo(nnInfo) end extension private val analyzedOps = Set(nme.EQ, nme.NE, nme.eq, nme.ne, nme.ZAND, nme.ZOR, nme.UNARY_!) @@ -515,7 +548,10 @@ object Nullables: && assignmentSpans.getOrElse(sym.span.start, Nil).exists(whileSpan.contains(_)) && ctx.notNullInfos.impliesNotNull(ref) - val retractedVars = ctx.notNullInfos.flatMap(_.asserted.filter(isRetracted)).toSet + val retractedVars = ctx.notNullInfos.flatMap(info => + if info.asserted == null then Set.empty + else info.asserted.filter(isRetracted) + ).toSet ctx.addNotNullInfo(NotNullInfo(Set(), retractedVars)) end whileContext diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 0ec9458cac5c..dcdabaf3a72d 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -21,6 +21,7 @@ import config.MigrationVersion import config.Printers.refcheck import reporting.* import Constants.Constant +import cc.stripCapturing object RefChecks { import tpd.* @@ -84,7 +85,7 @@ object RefChecks { * (Forwarding tends to hide problems by binding parameter names). */ private def upwardsThisType(cls: Symbol)(using Context) = cls.info match { - case ClassInfo(_, _, _, _, tp: Type) if (tp ne cls.typeRef) && !cls.isOneOf(FinalOrModuleClass) => + case ClassInfo(_, _, _, _, tp: Type) if (tp.stripCapturing ne cls.typeRef) && !cls.isOneOf(FinalOrModuleClass) => SkolemType(cls.appliedRef).withName(nme.this_) case _ => cls.thisType @@ -1155,6 +1156,11 @@ object RefChecks { * * If the extension method is nullary, it is always hidden by a member of the same name. * (Either the member is nullary, or the reference is taken as the eta-expansion of the member.) + * + * This check is in lieu of a more expensive use-site check that an application failed to use an extension. + * That check would account for accessibility and opacity. As a limitation, this check considers + * only public members, a target receiver that is not an alias, and corresponding method parameters + * that are either both opaque types or both not. */ def checkExtensionMethods(sym: Symbol)(using Context): Unit = if sym.is(Extension) && !sym.nextOverriddenSymbol.exists then @@ -1178,7 +1184,9 @@ object RefChecks { val memberParamTps = member.info.stripPoly.firstParamTypes !memberParamTps.isEmpty && memberParamTps.lengthCompare(paramTps) == 0 - && memberParamTps.lazyZip(paramTps).forall((m, x) => x frozen_<:< m) + && memberParamTps.lazyZip(paramTps).forall: (m, x) => + m.typeSymbol.denot.isOpaqueAlias == x.typeSymbol.denot.isOpaqueAlias + && (x frozen_<:< m) } } .exists diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 8751bd7dc9bb..6ce0f5f2517c 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -429,13 +429,7 @@ trait TypeAssigner { def assignType(tree: untpd.CaseDef, pat: Tree, body: Tree)(using Context): CaseDef = { val ownType = if (body.isType) { - val getParams = new TreeAccumulator[mutable.ListBuffer[TypeSymbol]] { - def apply(ps: mutable.ListBuffer[TypeSymbol], t: Tree)(using Context) = t match { - case t: Bind if t.symbol.isType => foldOver(ps += t.symbol.asType, t) - case _ => foldOver(ps, t) - } - } - val params1 = getParams(new mutable.ListBuffer[TypeSymbol](), pat).toList + val params1 = pat.bindTypeSymbols val params2 = pat.tpe match case AppliedType(tycon, args) => val tparams = tycon.typeParamSymbols diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 5c5ca8af46c6..9b7e4fe36668 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -81,6 +81,9 @@ object Typer { /** Indicates that a definition was copied over from the parent refinements */ val RefinementFromParent = new Property.StickyKey[Unit] + /** Indicates that an expression is explicitly ascribed to [[Unit]] type. */ + val AscribedToUnit = new Property.StickyKey[Unit] + /** An attachment on a Select node with an `apply` field indicating that the `apply` * was inserted by the Typer. */ @@ -265,6 +268,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // special cases: definitions beat imports, and named imports beat // wildcard imports, provided both are in contexts with same scope found + else if newPrec == WildImport && ctx.outersIterator.exists: ctx => + ctx.isImportContext && namedImportRef(ctx.importInfo.uncheckedNN).exists + then + // Don't let two ambiguous wildcard imports rule over + // a winning named import. See pos/i18529. + found else if !scala2pkg && !previous.isError && !found.isError then fail(AmbiguousReference(name, newPrec, prevPrec, prevCtx, @@ -790,7 +799,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Otherwise, try to expand a named tuple selection def tryNamedTupleSelection() = - val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes + val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes(true) val nameIdx = namedTupleElems.indexWhere(_._1 == selName) if nameIdx >= 0 && Feature.enabled(Feature.namedTuples) then typed( @@ -843,11 +852,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else EmptyTree def dynamicSelect(pt: Type) = - val tree2 = cpy.Select(tree0)(untpd.TypedSplice(qual), selName) - if pt.isInstanceOf[FunOrPolyProto] || pt == LhsProto then - assignType(tree2, TryDynamicCallType) - else - typedDynamicSelect(tree2, Nil, pt) + val tree2 = cpy.Select(tree0)(untpd.TypedSplice(qual), selName) + if pt.isInstanceOf[FunOrPolyProto] || pt == LhsProto then + assignType(tree2, TryDynamicCallType) + else + typedDynamicSelect(tree2, Nil, pt) // Otherwise, if the qualifier derives from class Dynamic, expand to a // dynamic dispatch using selectDynamic or applyDynamic @@ -865,8 +874,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer && pt != LhsProto then val pre = if !TypeOps.isLegalPrefix(qual.tpe) then SkolemType(qual.tpe) else qual.tpe - val fieldsType = pre.select(tpnme.Fields).dealias.simplified - val fields = fieldsType.namedTupleElementTypes + val fieldsType = pre.select(tpnme.Fields).widenDealias.simplified + val fields = fieldsType.namedTupleElementTypes(true) typr.println(i"try dyn select $qual, $selName, $fields") fields.find(_._1 == selName) match case Some((_, fieldType)) => @@ -876,7 +885,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Reject corner case where selectDynamic needs annother selectDynamic to be called. E.g. as in neg/unselectable-fields.scala. report.error(i"Cannot use selectDynamic here since it needs another selectDynamic to be invoked", tree.srcPos) case _ => - dynSelected.ensureConforms(fieldType) + adapt(dynSelected, defn.AnyType).ensureConforms(fieldType) case _ => EmptyTree else EmptyTree @@ -1193,9 +1202,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else tpt val expr1 = if isWildcard then tree.expr.withType(underlyingTreeTpe.tpe) - else typed(tree.expr, underlyingTreeTpe.tpe.widenSkolem) + else + if underlyingTreeTpe.tpe.isRef(defn.UnitClass) then + untpd.unsplice(tree.expr).putAttachment(AscribedToUnit, ()) + typed(tree.expr, underlyingTreeTpe.tpe.widenSkolem) assignType(cpy.Typed(tree)(expr1, tpt), underlyingTreeTpe) - .withNotNullInfo(expr1.notNullInfo) } if (untpd.isWildcardStarArg(tree)) { @@ -1545,11 +1556,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def thenPathInfo = cond1.notNullInfoIf(true).seq(result.thenp.notNullInfo) def elsePathInfo = cond1.notNullInfoIf(false).seq(result.elsep.notNullInfo) - result.withNotNullInfo( - if result.thenp.tpe.isRef(defn.NothingClass) then elsePathInfo - else if result.elsep.tpe.isRef(defn.NothingClass) then thenPathInfo - else thenPathInfo.alt(elsePathInfo) - ) + result.withNotNullInfo(thenPathInfo.alt(elsePathInfo)) end typedIf /** Decompose function prototype into a list of parameter prototypes and a result @@ -2133,20 +2140,25 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case1 } .asInstanceOf[List[CaseDef]] - var nni = sel.notNullInfo - if cases1.nonEmpty then nni = nni.seq(cases1.map(_.notNullInfo).reduce(_.alt(_))) - assignType(cpy.Match(tree)(sel, cases1), sel, cases1).cast(pt).withNotNullInfo(nni) + assignType(cpy.Match(tree)(sel, cases1), sel, cases1).cast(pt) + .withNotNullInfo(notNullInfoFromCases(sel.notNullInfo, cases1)) } // Overridden in InlineTyper for inline matches def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(using Context): Tree = { val cases1 = harmonic(harmonize, pt)(typedCases(cases, sel, wideSelType, pt.dropIfProto)) .asInstanceOf[List[CaseDef]] - var nni = sel.notNullInfo - if cases1.nonEmpty then nni = nni.seq(cases1.map(_.notNullInfo).reduce(_.alt(_))) - assignType(cpy.Match(tree)(sel, cases1), sel, cases1).withNotNullInfo(nni) + assignType(cpy.Match(tree)(sel, cases1), sel, cases1) + .withNotNullInfo(notNullInfoFromCases(sel.notNullInfo, cases1)) } + private def notNullInfoFromCases(initInfo: NotNullInfo, cases: List[CaseDef])(using Context): NotNullInfo = + if cases.isEmpty then + // Empty cases is not allowed for match tree in the source code, + // but it can be generated by inlining: `tests/pos/i19198.scala`. + initInfo + else cases.map(_.notNullInfo).reduce(_.alt(_)) + def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType0: Type, pt: Type)(using Context): List[CaseDef] = var caseCtx = ctx var wideSelType = wideSelType0 @@ -2235,7 +2247,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedLabeled(tree: untpd.Labeled)(using Context): Labeled = { val bind1 = typedBind(tree.bind, WildcardType).asInstanceOf[Bind] val expr1 = typed(tree.expr, bind1.symbol.info) - assignType(cpy.Labeled(tree)(bind1, expr1)) + assignType(cpy.Labeled(tree)(bind1, expr1)).withNotNullInfo(expr1.notNullInfo.retractedInfo) } /** Type a case of a type match */ @@ -2285,7 +2297,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Hence no adaptation is possible, and we assume WildcardType as prototype. (from, proto) val expr1 = typedExpr(tree.expr orElse untpd.syntheticUnitLiteral.withSpan(tree.span), proto) - assignType(cpy.Return(tree)(expr1, from)) + assignType(cpy.Return(tree)(expr1, from)).withNotNullInfo(expr1.notNullInfo.terminatedInfo) end typedReturn def typedWhileDo(tree: untpd.WhileDo)(using Context): Tree = @@ -2326,7 +2338,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val capabilityProof = caughtExceptions.reduce(OrType(_, _, true)) untpd.Block(makeCanThrow(capabilityProof), expr) - def typedTry(tree: untpd.Try, pt: Type)(using Context): Try = { + def typedTry(tree: untpd.Try, pt: Type)(using Context): Try = + var nnInfo = NotNullInfo.empty val expr2 :: cases2x = harmonic(harmonize, pt) { // We want to type check tree.expr first to comput NotNullInfo, but `addCanThrowCapabilities` // uses the types of patterns in `tree.cases` to determine the capabilities. @@ -2338,18 +2351,26 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val casesEmptyBody1 = tree.cases.mapconserve(cpy.CaseDef(_)(body = EmptyTree)) val casesEmptyBody2 = typedCases(casesEmptyBody1, EmptyTree, defn.ThrowableType, WildcardType) val expr1 = typed(addCanThrowCapabilities(tree.expr, casesEmptyBody2), pt.dropIfProto) - val casesCtx = ctx.addNotNullInfo(expr1.notNullInfo.retractedInfo) + + // Since we don't know at which point the the exception is thrown in the body, + // we have to collect any reference that is once retracted. + nnInfo = expr1.notNullInfo.retractedInfo + + val casesCtx = ctx.addNotNullInfo(nnInfo) val cases1 = typedCases(tree.cases, EmptyTree, defn.ThrowableType, pt.dropIfProto)(using casesCtx) expr1 :: cases1 }: @unchecked val cases2 = cases2x.asInstanceOf[List[CaseDef]] - var nni = expr2.notNullInfo.retractedInfo - if cases2.nonEmpty then nni = nni.seq(cases2.map(_.notNullInfo.retractedInfo).reduce(_.alt(_))) - val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(nni)) - nni = nni.seq(finalizer1.notNullInfo) - assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nni) - } + // It is possible to have non-exhaustive cases, and some exceptions are thrown and not caught. + // Therefore, the code in the finalizer and after the try block can only rely on the retracted + // info from the cases' body. + if cases2.nonEmpty then + nnInfo = nnInfo.seq(cases2.map(_.notNullInfo.retractedInfo).reduce(_.alt(_))) + + val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(nnInfo)) + nnInfo = nnInfo.seq(finalizer1.notNullInfo) + assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nnInfo) def typedTry(tree: untpd.ParsedTry, pt: Type)(using Context): Try = val cases: List[untpd.CaseDef] = tree.handler match @@ -2363,15 +2384,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedThrow(tree: untpd.Throw)(using Context): Tree = val expr1 = typed(tree.expr, defn.ThrowableType) val cap = checkCanThrow(expr1.tpe.widen, tree.span) - val res = Throw(expr1).withSpan(tree.span) + var res = Throw(expr1).withSpan(tree.span) if Feature.ccEnabled && !cap.isEmpty && !ctx.isAfterTyper then // Record access to the CanThrow capabulity recovered in `cap` by wrapping // the type of the `throw` (i.e. Nothing) in a `@requiresCapability` annotation. - Typed(res, + res = Typed(res, TypeTree( AnnotatedType(res.tpe, Annotation(defn.RequiresCapabilityAnnot, cap, tree.span)))) - else res + res.withNotNullInfo(expr1.notNullInfo.terminatedInfo) def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(using Context): SeqLiteral = { val elemProto = pt.stripNull().elemType match { @@ -2412,7 +2433,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else if ctx.reporter.errorsReported then UnspecifiedErrorType else errorType(em"cannot infer type; expected type $pt is not fully defined", tree.srcPos)) - def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): Tree = + def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): Tree = { tree match case tree: untpd.DerivedTypeTree => tree.ensureCompletions @@ -2428,6 +2449,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } case _ => completeTypeTree(InferredTypeTree(), pt, tree) + } def typedInLambdaTypeTree(tree: untpd.InLambdaTypeTree, pt: Type)(using Context): Tree = val tp = @@ -2643,7 +2665,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val lub = cases1.foldLeft(defn.NothingType: Type): (acc, case1) => if !acc.exists then NoType else if case1.body.tpe.isProvisional then NoType - else acc | case1.body.tpe + else acc | TypeOps.avoid(case1.body.tpe, case1.pat.bindTypeSymbols) if lub.exists then if !lub.isAny then val msg = em"Match type upper bound inferred as $lub, where previously it was defaulted to Any" @@ -2700,7 +2722,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer body1.isInstanceOf[RefTree] && !isWildcardArg(body1) || body1.isInstanceOf[Literal] val symTp = - if isStableIdentifierOrLiteral || pt.isNamedTupleType then pt + if isStableIdentifierOrLiteral || pt.dealias.isNamedTupleType then pt // need to combine tuple element types with expected named type else if isWildcardStarArg(body1) || pt == defn.ImplicitScrutineeTypeRef @@ -2836,8 +2858,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) postProcessInfo(vdef1, sym) vdef1.setDefTree + 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 = @@ -3374,7 +3397,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else if (ctx.mode.is(Mode.Pattern)) typedUnApply(cpy.Apply(tree)(op, l :: r :: Nil), pt) else { - val app = typedApply(desugar.binop(l, op, r), pt) + val app = typedApply(desugar.binop(l, op, r).withAttachmentsFrom(tree), pt) if op.name.isRightAssocOperatorName && !ctx.mode.is(Mode.QuotedExprPattern) then val defs = new mutable.ListBuffer[Tree] def lift(app: Tree): Tree = (app: @unchecked) match @@ -3649,7 +3672,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } /** Typecheck and adapt tree, returning a typed tree. Parameters as for `typedUnadapted` */ - def typed(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = + def typed(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = { trace(i"typing $tree, pt = $pt", typr, show = true) { record(s"typed $getClass") record("typed total") @@ -3661,6 +3684,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer tree.withType(WildcardType) else adapt(typedUnadapted(tree, pt, locked), pt, locked) } + } def typed(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree = typed(tree, pt, ctx.typerState.ownedVars) @@ -3776,7 +3800,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedExpr(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree = withoutMode(Mode.PatternOrTypeBits)(typed(tree, pt)) - def typedType(tree: untpd.Tree, pt: Type = WildcardType, mapPatternBounds: Boolean = false)(using Context): Tree = + def typedType(tree: untpd.Tree, pt: Type = WildcardType, mapPatternBounds: Boolean = false)(using Context): Tree = { val tree1 = withMode(Mode.Type) { typed(tree, pt) } if mapPatternBounds && ctx.mode.is(Mode.Pattern) && !ctx.isAfterTyper then tree1 match @@ -3792,6 +3816,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case tree1 => tree1 else tree1 + } def typedPattern(tree: untpd.Tree, selType: Type = WildcardType)(using Context): Tree = withMode(Mode.Pattern)(typed(tree, selType)) @@ -4581,9 +4606,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // so will take the code path that decides on inlining val tree1 = adapt(tree, WildcardType, locked) checkStatementPurity(tree1)(tree, ctx.owner, isUnitExpr = true) - if (!ctx.isAfterTyper && !tree.isInstanceOf[Inlined] && ctx.settings.Whas.valueDiscard && !isThisTypeResult(tree)) { + + if ctx.settings.Whas.valueDiscard + && !ctx.isAfterTyper + && !tree.isInstanceOf[Inlined] + && !isThisTypeResult(tree) + && !tree.hasAttachment(AscribedToUnit) then report.warning(ValueDiscarding(tree.tpe), tree.srcPos) - } + return tpd.Block(tree1 :: Nil, unitLiteral) } @@ -4633,7 +4663,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _: SelectionProto => tree // adaptations for selections are handled in typedSelect case _ if ctx.mode.is(Mode.ImplicitsEnabled) && tree.tpe.isValueType => - if tree.tpe.widen.isNamedTupleType && pt.derivesFrom(defn.TupleClass) then + if tree.tpe.isNamedTupleType && pt.derivesFrom(defn.TupleClass) then readapt(typed(untpd.Select(untpd.TypedSplice(tree), nme.toTuple))) else if pt.isRef(defn.AnyValClass, skipRefined = false) || pt.isRef(defn.ObjectClass, skipRefined = false) @@ -4861,6 +4891,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // sometimes we do not have the original anymore and use the transformed tree instead. // But taken together, the two criteria are quite accurate. missingArgs(tree, tree.tpe.widen) + case _ if tree.hasAttachment(AscribedToUnit) => + // The tree was ascribed to `Unit` explicitly to silence the warning. + () case _ if isUnitExpr => report.warning(PureUnitExpression(original, tree.tpe), original.srcPos) case _ => diff --git a/compiler/src/dotty/tools/dotc/util/FreshNameCreator.scala b/compiler/src/dotty/tools/dotc/util/FreshNameCreator.scala index f3375028c95f..f95b4bf85a96 100644 --- a/compiler/src/dotty/tools/dotc/util/FreshNameCreator.scala +++ b/compiler/src/dotty/tools/dotc/util/FreshNameCreator.scala @@ -14,7 +14,7 @@ abstract class FreshNameCreator { object FreshNameCreator { class Default extends FreshNameCreator { protected var counter: Int = 0 - protected val counters: mutable.Map[String, Int] = mutable.AnyRefMap() withDefaultValue 0 + protected val counters: mutable.Map[String, Int] = mutable.HashMap() withDefaultValue 0 /** * Create a fresh name with the given prefix. It is guaranteed diff --git a/compiler/src/dotty/tools/repl/ParseResult.scala b/compiler/src/dotty/tools/repl/ParseResult.scala index 24a624173050..6c9f95d4dca2 100644 --- a/compiler/src/dotty/tools/repl/ParseResult.scala +++ b/compiler/src/dotty/tools/repl/ParseResult.scala @@ -93,6 +93,10 @@ object Reset { val command: String = ":reset" } +/** Toggle automatic printing of results */ +case object Silent extends Command: + val command: String = ":silent" + /** `:quit` exits the repl */ case object Quit extends Command { val command: String = ":quit" @@ -113,6 +117,7 @@ case object Help extends Command { |:imports show import history |:reset [options] reset the repl to its initial state, forgetting all session entries |:settings update compiler options, if possible + |:silent disable/enable automatic printing of results """.stripMargin } @@ -137,6 +142,7 @@ object ParseResult { TypeOf.command -> (arg => TypeOf(arg)), DocOf.command -> (arg => DocOf(arg)), Settings.command -> (arg => Settings(arg)), + Silent.command -> (_ => Silent), ) def apply(source: SourceFile)(using state: State): ParseResult = { diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 589ea6c3c677..58b40f648627 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -60,12 +60,14 @@ import scala.util.Using * @param valIndex the index of next value binding for free expressions * @param imports a map from object index to the list of user defined imports * @param invalidObjectIndexes the set of object indexes that failed to initialize + * @param quiet whether we print evaluation results * @param context the latest compiler context */ case class State(objectIndex: Int, valIndex: Int, imports: Map[Int, List[tpd.Import]], invalidObjectIndexes: Set[Int], + quiet: Boolean, context: Context): def validObjectIndexes = (1 to objectIndex).filterNot(invalidObjectIndexes.contains(_)) @@ -114,7 +116,12 @@ class ReplDriver(settings: Array[String], } /** the initial, empty state of the REPL session */ - final def initialState: State = State(0, 0, Map.empty, Set.empty, rootCtx) + final def initialState: State = + val emptyState = State(0, 0, Map.empty, Set.empty, false, rootCtx) + val initScript = rootCtx.settings.replInitScript.value(using rootCtx) + initScript.trim() match + case "" => emptyState + case script => run(script)(using emptyState) /** Reset state of repl to the initial state * @@ -217,11 +224,6 @@ class ReplDriver(settings: Array[String], interpret(ParseResult.complete(input)) } - final def runQuietly(input: String)(using State): State = runBody { - val parsed = ParseResult(input) - interpret(parsed, quiet = true) - } - protected def runBody(body: => State): State = rendering.classLoader()(using rootCtx).asContext(withRedirectedOutput(body)) // TODO: i5069 @@ -304,10 +306,10 @@ class ReplDriver(settings: Array[String], .getOrElse(Nil) end completionsWithSignatures - protected def interpret(res: ParseResult, quiet: Boolean = false)(using state: State): State = { + protected def interpret(res: ParseResult)(using state: State): State = { res match { case parsed: Parsed if parsed.trees.nonEmpty => - compile(parsed, state, quiet) + compile(parsed, state) case SyntaxErrors(_, errs, _) => displayErrors(errs) @@ -325,7 +327,7 @@ class ReplDriver(settings: Array[String], } /** Compile `parsed` trees and evolve `state` in accordance */ - private def compile(parsed: Parsed, istate: State, quiet: Boolean = false): State = { + private def compile(parsed: Parsed, istate: State): State = { def extractNewestWrapper(tree: untpd.Tree): Name = tree match { case PackageDef(_, (obj: untpd.ModuleDef) :: Nil) => obj.name.moduleClassName case _ => nme.NO_NAME @@ -376,11 +378,9 @@ class ReplDriver(settings: Array[String], given Ordering[Diagnostic] = Ordering[(Int, Int, Int)].on(d => (d.pos.line, -d.level, d.pos.column)) - if (!quiet) { - (definitions ++ warnings) - .sorted - .foreach(printDiagnostic) - } + (if istate.quiet then warnings else definitions ++ warnings) + .sorted + .foreach(printDiagnostic) updatedState } @@ -556,6 +556,8 @@ class ReplDriver(settings: Array[String], rootCtx = setupRootCtx(tokenize(arg).toArray, rootCtx) state.copy(context = rootCtx) + case Silent => state.copy(quiet = !state.quiet) + case Quit => // end of the world! state diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 22be293c3562..fd5b635e11e2 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2822,7 +2822,26 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler self.typeRef.info.decls.toList def paramSymss: List[List[Symbol]] = self.denot.paramSymss - def primaryConstructor: Symbol = self.denot.primaryConstructor + def primaryConstructor: Symbol = + val initialPrimary = self.denot.primaryConstructor + // Java outline parser creates a dummyConstructor. We want to avoid returning it here, + // instead returning the first non-dummy one, which is what happens when a java classfile + // is read from classpath instead of using the java outline parser. + // We check if the constructor is dummy if it has the same parameters as defined in JavaParsers.scala, + // incliding the private[this] flags and parameter shape with scala.Unit argument. + val isJavaDummyConstructor = + val paramSymss = initialPrimary.paramSymss + initialPrimary.flags.is(Flags.JavaDefined | Flags.Local | Flags.Method | Flags.Private | Flags.PrivateLocal) + && { + paramSymss match + case List(List(typeTree)) if self.typeRef.memberType(typeTree).typeSymbol == defn.UnitClass => true + case _ => false + } + if isJavaDummyConstructor then + declarations.filter(sym => sym != initialPrimary && sym.isConstructor).headOption.getOrElse(Symbol.noSymbol) + else + initialPrimary + def allOverriddenSymbols: Iterator[Symbol] = self.denot.allOverriddenSymbols def overridingSymbol(ofclazz: Symbol): Symbol = if ofclazz.isClass then self.denot.overridingSymbol(ofclazz.asClass) diff --git a/compiler/test-resources/repl/init-script-flag b/compiler/test-resources/repl/init-script-flag new file mode 100644 index 000000000000..373f21e15e93 --- /dev/null +++ b/compiler/test-resources/repl/init-script-flag @@ -0,0 +1,5 @@ +scala>:reset --repl-init-script:'println("Hello from init script!")' +Resetting REPL state with the following settings: + --repl-init-script:println("Hello from init script!") + +Hello from init script! diff --git a/compiler/test-resources/repl/silent b/compiler/test-resources/repl/silent new file mode 100644 index 000000000000..9e851e8adb01 --- /dev/null +++ b/compiler/test-resources/repl/silent @@ -0,0 +1,25 @@ +scala>:silent +scala> 1+1 +scala> case class A(x: Int) +scala> A("string") +-- [E007] Type Mismatch Error: ------------------------------------------------- +1 | A("string") + | ^^^^^^^^ + | Found: ("string" : String) + | Required: Int + | + | longer explanation available when compiling with `-explain` +1 error found +scala> Option[Int](2) match { case Some(x) => x } +1 warning found +-- [E029] Pattern Match Exhaustivity Warning: ---------------------------------- +1 | Option[Int](2) match { case Some(x) => x } + | ^^^^^^^^^^^^^^ + | match may not be exhaustive. + | + | It would fail on pattern case: None + | + | longer explanation available when compiling with `-explain` +scala>:silent +scala> 1 + 2 +val res2: Int = 3 diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 032b53150e49..07c157793f5d 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -19,6 +19,7 @@ i12299a.scala i13871.scala i15181.scala i15922.scala +i15926.scala t5031_2.scala i16997.scala i7414.scala @@ -68,6 +69,7 @@ i18211.scala 10867.scala named-tuples1.scala i20897.scala +i20512.scala # Opaque type i5720.scala diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index 31304e061bc7..c880a4b78f23 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -48,4 +48,5 @@ named-tuples-strawman-2.scala # typecheckErrors method unpickling typeCheckErrors.scala +i18150.scala diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 9f72db6fc390..70f55e569784 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -81,6 +81,7 @@ class CompilationTests { compileFile("tests/rewrites/i21418.scala", unindentOptions.and("-rewrite", "-source:3.5-migration")), compileFile("tests/rewrites/infix-named-args.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), compileFile("tests/rewrites/ambigious-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), + compileFile("tests/rewrites/i21382.scala", defaultOptions.and("-indent", "-rewrite")), ).checkRewrites() } @@ -263,7 +264,6 @@ class CompilationTests { } // parallel backend tests - @Ignore("Temporarily disabled due to frequent timeouts") @Test def parallelBackend: Unit = { given TestGroup = TestGroup("parallelBackend") val parallelism = Runtime.getRuntime().availableProcessors().min(16) diff --git a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala index a412848eaa98..07834684d33b 100644 --- a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala @@ -105,7 +105,7 @@ class ScalaSettingsTests: createTestCase(settings.YdropComments , settings.XdropComments), createTestCase(settings.YcookComments , settings.XcookComments), createTestCase(settings.YreadComments , settings.XreadComments), - createTestCase(settings.YnoDecodeStacktraces , settings.XnoDecodeStacktraces), + createTestCase(settings.YnoDecodeStacktraces , settings.XnoEnrichErrorMessages), createTestCase(settings.YnoEnrichErrorMessages, settings.XnoEnrichErrorMessages), createTestCase(settings.YdebugMacros , settings.XdebugMacros), // createTestCase(settings.YjavaTasty , settings.XjavaTasty), @@ -134,7 +134,7 @@ class ScalaSettingsTests: createTestCase(settings.YdropComments , settings.XdropComments), createTestCase(settings.YcookComments , settings.XcookComments), createTestCase(settings.YreadComments , settings.XreadComments), - createTestCase(settings.YnoDecodeStacktraces , settings.XnoDecodeStacktraces), + createTestCase(settings.YnoDecodeStacktraces , settings.XnoEnrichErrorMessages), createTestCase(settings.YnoEnrichErrorMessages, settings.XnoEnrichErrorMessages), createTestCase(settings.YdebugMacros , settings.XdebugMacros), // createTestCase(settings.YjavaTasty , settings.XjavaTasty), @@ -175,7 +175,7 @@ class ScalaSettingsTests: createTestCase(settings.YdropComments , settings.XdropComments), createTestCase(settings.YcookComments , settings.XcookComments), createTestCase(settings.YreadComments , settings.XreadComments), - createTestCase(settings.YnoDecodeStacktraces , settings.XnoDecodeStacktraces), + createTestCase(settings.YnoDecodeStacktraces , settings.XnoEnrichErrorMessages), createTestCase(settings.YnoEnrichErrorMessages, settings.XnoEnrichErrorMessages), createTestCase(settings.YdebugMacros , settings.XdebugMacros), // createTestCase(settings.YjavaTasty , settings.XjavaTasty), diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala index 7efa1f6f564e..f6460180cab9 100644 --- a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -54,15 +54,27 @@ class CoverageTests: lines end fixWindowsPaths - def runOnFile(p: Path): Boolean = - scalaFile.matches(p) && - (Properties.testsFilter.isEmpty || Properties.testsFilter.exists(p.toString.contains)) + def runOnFileOrDir(p: Path): Boolean = + (scalaFile.matches(p) || Files.isDirectory(p)) + && (p != dir) + && (Properties.testsFilter.isEmpty || Properties.testsFilter.exists(p.toString.contains)) + + Files.walk(dir, 1).filter(runOnFileOrDir).forEach(path => { + // measurement files only exist in the "run" category + // as these are generated at runtime by the scala.runtime.coverage.Invoker + val (targetDir, expectFile, expectMeasurementFile) = + if Files.isDirectory(path) then + val dirName = path.getFileName().toString + assert(!Files.walk(path).filter(scalaFile.matches(_)).toArray.isEmpty, s"No scala files found in test directory: ${path}") + val targetDir = computeCoverageInTmp(path, isDirectory = true, dir, run) + (targetDir, path.resolve(s"test.scoverage.check"), path.resolve(s"test.measurement.check")) + else + val fileName = path.getFileName.toString.stripSuffix(".scala") + val targetDir = computeCoverageInTmp(path, isDirectory = false, dir, run) + (targetDir, path.resolveSibling(s"${fileName}.scoverage.check"), path.resolveSibling(s"${fileName}.measurement.check")) - Files.walk(dir).filter(runOnFile).forEach(path => { - val fileName = path.getFileName.toString.stripSuffix(".scala") - val targetDir = computeCoverageInTmp(path, dir, run) val targetFile = targetDir.resolve(s"scoverage.coverage") - val expectFile = path.resolveSibling(s"$fileName.scoverage.check") + if updateCheckFiles then Files.copy(targetFile, expectFile, StandardCopyOption.REPLACE_EXISTING) else @@ -72,9 +84,6 @@ class CoverageTests: val instructions = FileDiff.diffMessage(expectFile.toString, targetFile.toString) fail(s"Coverage report differs from expected data.\n$instructions") - // measurement files only exist in the "run" category - // as these are generated at runtime by the scala.runtime.coverage.Invoker - val expectMeasurementFile = path.resolveSibling(s"$fileName.measurement.check") if run && Files.exists(expectMeasurementFile) then // Note that this assumes that the test invoked was single threaded, @@ -95,14 +104,20 @@ class CoverageTests: }) /** Generates the coverage report for the given input file, in a temporary directory. */ - def computeCoverageInTmp(inputFile: Path, sourceRoot: Path, run: Boolean)(using TestGroup): Path = + def computeCoverageInTmp(inputFile: Path, isDirectory: Boolean, sourceRoot: Path, run: Boolean)(using TestGroup): Path = val target = Files.createTempDirectory("coverage") val options = defaultOptions.and("-Ycheck:instrumentCoverage", "-coverage-out", target.toString, "-sourceroot", sourceRoot.toString) if run then - val test = compileDir(inputFile.getParent.toString, options) + val path = if isDirectory then inputFile.toString else inputFile.getParent.toString + val test = compileDir(path, options) + test.checkFilePaths.foreach { checkFilePath => + assert(checkFilePath.exists, s"Expected checkfile for $path $checkFilePath does not exist.") + } test.checkRuns() else - val test = compileFile(inputFile.toString, options) + val test = + if isDirectory then compileDir(inputFile.toString, options) + else compileFile(inputFile.toString, options) test.checkCompile() target diff --git a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala index 221eb8acb9de..d32b28647c32 100644 --- a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala +++ b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala @@ -493,6 +493,24 @@ class ReplCompilerTests extends ReplTest: assertTrue(all.head.startsWith("-- [E103] Syntax Error")) assertTrue(all.exists(_.trim().startsWith("| Illegal start of statement: this modifier is not allowed here"))) + @Test def `i16250a`: Unit = initially: + val hints = List( + "this language import is not allowed in the REPL", + "To use this language feature, include the flag `-language:experimental.captureChecking` when starting the REPL" + ) + run("import language.experimental.captureChecking") + val all = lines() + assertTrue(hints.forall(hint => all.exists(_.contains(hint)))) + + @Test def `i16250b`: Unit = initially: + val hints = List( + "this language import is not allowed in the REPL", + "To use this language feature, include the flag `-language:experimental.pureFunctions` when starting the REPL" + ) + run("import language.experimental.pureFunctions") + val all = lines() + assertTrue(hints.forall(hint => all.exists(_.contains(hint)))) + object ReplCompilerTests: private val pattern = Pattern.compile("\\r[\\n]?|\\n"); diff --git a/compiler/test/dotty/tools/repl/TabcompleteTests.scala b/compiler/test/dotty/tools/repl/TabcompleteTests.scala index 95419824d9d1..01b9d1109994 100644 --- a/compiler/test/dotty/tools/repl/TabcompleteTests.scala +++ b/compiler/test/dotty/tools/repl/TabcompleteTests.scala @@ -217,6 +217,7 @@ class TabcompleteTests extends ReplTest { ":quit", ":reset", ":settings", + ":silent", ":type" ), tabComplete(":") diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 09d3614b64a5..f565ac328d46 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -258,15 +258,7 @@ trait ParallelTesting extends RunnerOrchestration { self => * For a given test source, returns a check file against which the result of the test run * should be compared. Is used by implementations of this trait. */ - final def checkFile(testSource: TestSource): Option[JFile] = (testSource match { - case ts: JointCompilationSource => - ts.files.collectFirst { - case f if !f.isDirectory => - new JFile(f.getPath.replaceFirst("\\.(scala|java)$", ".check")) - } - case ts: SeparateCompilationSource => - Option(new JFile(ts.dir.getPath + ".check")) - }).filter(_.exists) + final def checkFile(testSource: TestSource): Option[JFile] = (CompilationLogic.checkFilePath(testSource)).filter(_.exists) /** * Checks if the given actual lines are the same as the ones in the check file. @@ -343,6 +335,18 @@ trait ParallelTesting extends RunnerOrchestration { self => } } + object CompilationLogic { + private[ParallelTesting] def checkFilePath(testSource: TestSource) = testSource match { + case ts: JointCompilationSource => + ts.files.collectFirst { + case f if !f.isDirectory => + new JFile(f.getPath.replaceFirst("\\.(scala|java)$", ".check")) + } + case ts: SeparateCompilationSource => + Option(new JFile(ts.dir.getPath + ".check")) + } + } + /** Each `Test` takes the `testSources` and performs the compilation and assertions * according to the implementing class "neg", "run" or "pos". */ @@ -1157,6 +1161,8 @@ trait ParallelTesting extends RunnerOrchestration { self => def this(targets: List[TestSource]) = this(targets, 1, true, None, false, false) + def checkFilePaths: List[JFile] = targets.map(CompilationLogic.checkFilePath).flatten + def copy(targets: List[TestSource], times: Int = times, shouldDelete: Boolean = shouldDelete, diff --git a/docs/_docs/contributing/getting-started.md b/docs/_docs/contributing/getting-started.md index b6e3e4fac00a..c660edb2400e 100644 --- a/docs/_docs/contributing/getting-started.md +++ b/docs/_docs/contributing/getting-started.md @@ -146,6 +146,6 @@ The main development discussion channels are: [java11]: https://www.oracle.com/java/technologies/javase-jdk11-downloads.html [adopt]: https://adoptopenjdk.net/ [compat]: https://docs.scala-lang.org/overviews/jdk-compatibility/overview.html -[scala-cla]: https://www.lightbend.com/contribute/cla/scala +[scala-cla]: https://contribute.akka.io/cla/scala [dotty-issue]: https://github.com/scala/scala3/issues [dotty-discussion]: https://github.com/scala/scala3/discussions diff --git a/docs/_docs/internals/cc/use-design.md b/docs/_docs/internals/cc/use-design.md new file mode 100644 index 000000000000..4732f45a84ab --- /dev/null +++ b/docs/_docs/internals/cc/use-design.md @@ -0,0 +1,71 @@ + +Possible design: + + 1. Have @use annotation on type parameters and value parameters of regular methods + (not anonymous functions). + 2. In markFree, keep track whether a capture set variable or reach capability + is used directly in the method where it is defined, or in a nested context + (either unbound nested closure or unbound anonymous class). + 3. Disallow charging a reach capability `xs*` to the environment of the method where + `xs` is a parameter unless `xs` is declared `@use`. + 4. Analogously, disallow charging a capture set variable `C^` to the environment of the method where `C^` is a parameter unless `C^` is declared `@use`. + 5. When passing an argument to a `@use`d term parameter, charge the `dcs` of the argument type to the environments via markFree. + 6. When instantiating a `@use`d type parameter, charge the capture set of the argument + to the environments via markFree. + +It follows that we cannot refer to methods with @use term parameters as values. Indeed, +their eta expansion would produce an anonymous function that includes a reach capability of +its parameter in its use set, violating (3). + +Example: + +```scala + def runOps(@use ops: List[() => Unit]): Unit = ops.foreach(_()) +``` +Then `runOps` expands to +```scala +(xs: List[() => Unit]) => runOps(xs) +``` +Note that `xs` does not carry a `@use` since this is disallowed by (1) for anonymous functions. By (5), we charge the deep capture set of `xs`, which is `xs*` to the environment. By (3), this is actually disallowed. + +Now, if we express this with explicit capture set parameters we get: +```scala + def runOpsPoly[@use C^](ops: List[() ->{C^} Unit]): Unit = ops.foreach[C^](_()) +``` +Then `runOpsPoly` expands to `runOpsPoly[cs]` for some inferred capture set `cs`. And this expands to: +```scala +(xs: List[() ->{cs} Unit]) => runOpsPoly[cs](xs) +``` +Since `cs` is passed to the `@use` parameter of `runOpsPoly` it is charged +to the environment of the function body, so the type of the previous expression is +```scala +List[() ->{cs} Unit]) ->{cs} Unit +``` + +We can also use explicit capture set parameters to eta expand the first `runOps` manually: + +```scala +[C^] => (xs: List[() ->{C^} Unit]) => runOps(xs) + : [C^] -> List[() ->{C^} Unit] ->[C^] Unit +``` +Except that this currently runs afoul of the implementation restriction that polymorphic functions cannot wrap capturing functions. But that's a restriction we need to lift anyway. + +## `@use` inference + + - `@use` is implied for a term parameter `x` of a method if `x`'s type contains a boxed cap and `x` or `x*` is not referred to in the result type of the method. + + - `@use` is implied for a capture set parameter `C` of a method if `C` is not referred to in the result type of the method. + +If `@use` is implied, one can override to no use by giving an explicit use annotation +`@use(false)` instead. Example: +```scala + def f(@use(false) xs: List[() => Unit]): Int = xs.length +``` + +This works since `@use` is defined like this: +```scala +class use(cond: Boolean = true) extends StaticAnnotation +``` + + + diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index d0074bb503c2..665b4f5144ba 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 transparent using | * + - +as derives end erased extension infix inline opaque open throws tracked transparent using | * + - ``` See the [separate section on soft keywords](../reference/soft-modifier.md) for additional @@ -381,7 +381,7 @@ ClsParamClause ::= [nl] ‘(’ ClsParams ‘)’ | [nl] ‘(’ ‘using’ (ClsParams | FunArgTypes) ‘)’ ClsParams ::= ClsParam {‘,’ ClsParam} ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var - [{Modifier | ‘tracked’} (‘val’ | ‘var’)] Param + [{Modifier} (‘val’ | ‘var’)] Param DefParamClauses ::= DefParamClause { DefParamClause } -- and two DefTypeParamClause cannot be adjacent DefParamClause ::= DefTypeParamClause @@ -418,6 +418,7 @@ LocalModifier ::= ‘abstract’ | ‘transparent’ | ‘infix’ | ‘erased’ + | ‘tracked’ AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] AccessQualifier ::= ‘[’ id ‘]’ diff --git a/docs/_docs/reference/experimental/modularity.md b/docs/_docs/reference/experimental/modularity.md index a989b71770af..66d4c0c23ede 100644 --- a/docs/_docs/reference/experimental/modularity.md +++ b/docs/_docs/reference/experimental/modularity.md @@ -108,14 +108,6 @@ This works as it should now. Without the addition of `tracked` to the parameter of `SetFunctor` typechecking would immediately lose track of the element type `T` after an `add`, and would therefore fail. -**Syntax Change** - -``` -ClsParam ::= {Annotation} [{Modifier | ‘tracked’} (‘val’ | ‘var’)] Param -``` - -The (soft) `tracked` modifier is only allowed for `val` parameters of classes. - **Discussion** Since `tracked` is so useful, why not assume it by default? First, `tracked` makes sense only for `val` parameters. If a class parameter is not also a field declared using `val` then there's nothing to refine in the constructor result type. One could think of at least making all `val` parameters tracked by default, but that would be a backwards incompatible change. For instance, the following code would break: @@ -134,6 +126,38 @@ only if the class refers to a type member of `x`. But it turns out that this scheme is unimplementable since it would quickly lead to cyclic references when typechecking recursive class graphs. So an explicit `tracked` looks like the best available option. +## Tracked members + +The `tracked` modifier can also be used for `val` members of classes and traits +to force the type of the member (or it's overriding member) to be as exact as +possible. More precisely, it will will assign the `tracked` member the infered +type of the rhs. For instance, consider the following definition: + +```scala +trait F: + tracked val a: Int + tracked val b: Int + +class N extends F: + val a = 22 // a.type =:= 22 + val b: Int = 22 // b.type =:= Int + tracked val c = 22 // c.type =:= 22 +``` + +Here, the `tracked` modifier ensures that the type of `a` in `N` is `22` and not +`Int`. But the type of `b` is `N` is `Int` since it's explicitly declared as +`Int`. `tracked` members can also be immediately initialized, as in the case of +`c`. + +## Tracked syntax change + +``` +LocalModifier ::= ‘tracked’ +``` + +The (soft) `tracked` modifier is allowed as a local modifier. + + ## Allow Class Parents to be Refined Types Since `tracked` parameters create refinements in constructor types, diff --git a/docs/_docs/reference/metaprogramming/staging.md b/docs/_docs/reference/metaprogramming/staging.md index 1c154e09f50e..001ae622eabc 100644 --- a/docs/_docs/reference/metaprogramming/staging.md +++ b/docs/_docs/reference/metaprogramming/staging.md @@ -98,9 +98,9 @@ scala -with-compiler -classpath out Test ## Example Now take exactly the same example as in [Macros](./macros.md). Assume that we -do not want to pass an array statically but generate code at run-time and pass +do not want to pass a base double value statically but generate code at run-time and pass the value, also at run-time. Note, how we make a future-stage function of type -`Expr[Array[Int] => Int]` in line 6 below. Using `staging.run { ... }` we can evaluate an +`Expr[Double => Double]` in line 6 below. Using `staging.run { ... }` we can evaluate an expression at runtime. Within the scope of `staging.run` we can also invoke `show` on an expression to get a source-like representation of the expression. @@ -110,12 +110,12 @@ import scala.quoted.* // make available the necessary compiler for runtime code generation given staging.Compiler = staging.Compiler.make(getClass.getClassLoader) -val f: Array[Int] => Int = staging.run { - val stagedSum: Expr[Array[Int] => Int] = - '{ (arr: Array[Int]) => ${sum('arr)}} - println(stagedSum.show) // Prints "(arr: Array[Int]) => { var sum = 0; ... }" - stagedSum +val power3: Double => Double = staging.run { + val stagedPower3: Expr[Double => Double] = + '{ (x: Double) => ${ unrolledPowerCode('x, 3) } } + println(stagedPower3.show) // Prints "((x: scala.Double) => x.*(x.*(x)))" + stagedPower3 } -f.apply(Array(1, 2, 3)) // Returns 6 +power3.apply(2.0) // Returns 8.0 ``` diff --git a/docs/_spec/01-lexical-syntax.md b/docs/_spec/01-lexical-syntax.md index e1686204116e..b7fb455fc2fd 100644 --- a/docs/_spec/01-lexical-syntax.md +++ b/docs/_spec/01-lexical-syntax.md @@ -137,7 +137,7 @@ Otherwise, soft keywords are treated as actual keywords in the following situati - `as`, if it appears in a renaming import clause. - `derives`, if it appears after an extension clause or after the name and possibly parameters of a class, trait, object, or enum definition. - `end`, if it appears at the start of a line following a statement (i.e. definition or toplevel expression) and is followed on the same line by a single non-comment token that is: - - one of the keywords `for`, `given`, `if`, `match`, `new`, `this`, `throw`, `try`, `val`, `while`, or + - one of the keywords `for`, `given`, `if`, `match`, `new`, `this`, `throw`, `try`, `val`, `while`, `extension` or - an identifier. - `extension`, if it appears at the start of a statement and is followed by `(` or `[`. - `inline`, if it is followed by any token that can start an expression. diff --git a/docs/_spec/03-types.md b/docs/_spec/03-types.md index 6bc7886c5677..4b1293258495 100644 --- a/docs/_spec/03-types.md +++ b/docs/_spec/03-types.md @@ -9,10 +9,12 @@ chapter: 3 ```ebnf Type ::= FunType | TypeLambda + | MatchType | InfixType FunType ::= FunTypeArgs ‘=>’ Type | TypeLambdaParams '=>' Type TypeLambda ::= TypeLambdaParams ‘=>>’ Type +MatchType ::= InfixType ‘match’ <<< TypeCaseClauses >>> InfixType ::= RefinedType | RefinedTypeOrWildcard id [nl] RefinedTypeOrWildcard {id [nl] RefinedTypeOrWildcard} RefinedType ::= AnnotType {[nl] Refinement} @@ -51,6 +53,9 @@ TypeLambdaParam ::= {Annotation} (id | ‘_’) [TypeParamClause] TypeBou TypeParamClause ::= ‘[’ VariantTypeParam {‘,’ VariantTypeParam} ‘]’ VariantTypeParam ::= {Annotation} [‘+’ | ‘-’] (id | ‘_’) [TypeParamClause] TypeBounds +TypeCaseClauses ::= TypeCaseClause { TypeCaseClause } +TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi] + RefineDef ::= ‘val’ ValDef | ‘def’ DefDef | ‘type’ {nl} TypeDef @@ -91,6 +96,7 @@ Type ::= ‘AnyKind‘ | RecursiveThis | UnionType | IntersectionType + | MatchType | SkolemType TypeLambda ::= ‘[‘ TypeParams ‘]‘ ‘=>>‘ Type @@ -127,6 +133,14 @@ RecursiveThis ::= recid ‘.‘ ‘this‘ UnionType ::= Type ‘|‘ Type IntersectionType ::= Type ‘&‘ Type +MatchType ::= Type ‘match‘ ‘<:‘ Type ‘{‘ {TypeCaseClause} ‘}‘ +TypeCaseClause ::= ‘case‘ TypeCasePattern ‘=>‘ Type +TypeCasePattern ::= TypeCapture + | TypeCaseAppliedPattern + | Type +TypeCaseAppliedPattern ::= Type ‘[‘ TypeCasePattern { ‘,‘ TypeCasePattern } ‘]‘ +TypeCapture ::= (id | ‘_‘) TypeBounds + SkolemType ::= ‘∃‘ skolemid ‘:‘ Type TypeOrMethodic ::= Type @@ -141,7 +155,7 @@ MethodTypeParam ::= id ‘:‘ Type PolyType ::= ‘[‘ PolyTypeParams ‘]‘ TypeOrMethodic PolyTypeParams ::= PolyTypeParam {‘,‘ PolyTypeParam} -PolyTypeParam ::= ‘id‘ TypeBounds +PolyTypeParam ::= id TypeBounds TypeAliasOrBounds ::= TypeAlias | TypeBounds @@ -273,6 +287,36 @@ The conversion from the concrete syntax to the abstract syntax works as follows: 3. Create nested [refined types](#refined-types), one for every refined definition. 4. Unless ´\alpha´ was never actually used, wrap the result in a [recursive type](#recursive-types) `{ ´\alpha´ => ´...´ }`. +### Concrete Match Types + +```ebnf +MatchType ::= InfixType ‘match’ <<< TypeCaseClauses >>> +TypeCaseClauses ::= TypeCaseClause { TypeCaseClause } +TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi] +``` + +In the concrete syntax of match types, patterns are arbitrary `InfixType`s, and there is no explicit notion of type capture. +In the abstract syntax, however, captures are made explicit and can only appear as arguments to `TypeCaseAppliedPattern`s. + +If the concrete pattern is `_`, its conversion is the internal type `scala.Any`. +If it is a concrete `InfixType`, it is first converted to an internal type ´P´. +If ´P´ is not a `ParameterizedType`, then use ´P´ as the internal pattern. +Otherwise, ´P´ is recursively converted into a `TypeCasePattern` as follows: + +1. If ´P´ is a `WildcardTypeArg` of the form `? >: ´L´ <: ´H´`, return a `TypeCapture` of the form `_ >: ´L´ <: ´H´`. +2. If ´P´ is a direct type designator `´t´` whose name starts with a lowercase and was not written using backticks, return a `TypeCapture` `´t´ >: ´L´ <: ´H´` where `>: ´L´ <: ´H´` is the declared type definition of `´t´`. +3. If ´P´ is a `ParameterizedType` of the form `´T´[´T_1´, ..., ´T_n´]`: + 1. Recursively convert each ´T_i´ into a pattern ´P_i´. + 2. If ´P_i´ is a `Type` for all ´i´, return ´P´. + 3. Otherwise, return the `TypeCaseAppliedPattern` `´T´[´P_1´, ..., ´P_n´]`. +4. Otherwise, return ´P´. + +This conversion ensures that every `TypeCaseAppliedPattern` recursively contains at least one `TypeCapture`. +Moreover, at the top level, the pattern is never a `TypeCapture`: all `TypeCapture`s are nested within a `TypeCaseAppliedPattern`. + +The bound of the internal `MatchType` is always `<: scala.Any` by default. +It can be overridden in a [type member definition](./04-basic-definitions.html#type-member-definitions). + ### Concrete Type Lambdas ```ebnf @@ -807,6 +851,310 @@ class B extends C[B] with D with E The join of ´A | B´ is ´C[A | B] & D´ +### Match Types + +```ebnf +MatchType ::= Type ‘match‘ ‘<:‘ Type ‘{‘ {TypeCaseClause} ‘}‘ +TypeCaseClause ::= ‘case‘ TypeCasePattern ‘=>‘ Type +TypeCasePattern ::= TypeCapture + | TypeCaseAppliedPattern + | Type +TypeCaseAppliedPattern ::= Type ‘[‘ TypeCasePattern { ‘,‘ TypeCasePattern } ‘]‘ +TypeCapture ::= (id | ‘_‘) TypeBounds +``` + +A match type contains a scrutinee, a list of case clauses, and an upper bound. +The scrutinee and the upper bound must both be proper types. +A match type can be *reduced* to the body of a case clause if the scrutinee matches its pattern, and if it is *provably disjoint* from every earlier pattern. + +#### Legal patterns + +A `TypeCasePattern` is a legal pattern if and only if one of the following is true: + +* It is a `Type`, or +* It is a `TypeCaseAppliedPattern` of the form `´P´[´P_1´, ..., ´P_n´]` where ´P´ is a "pattern-legal type constructor" and for each ´i´, either: + * ´P_i´ is a `TypeCapture`, or + * ´P_i´ is a `Type`, or + * ´P_i´ is a `TypeCaseAppliedPattern`, the type constructor ´P´ is *covariant* in its ´i´th type parameter, and ´P_i´ is recursively a legal pattern. + +A type ´P´ is a "pattern-legal type constructor" if one of the following is true: + +* It is a *class* type constructor, or +* It is the `scala.compiletime.ops.int.S` type constructor, or +* It is an *abstract* type constructor, or +* It is a type lambda with a refined result type of the form `[´a´ >: ´L´ <: ´H´] =>> ´B´ { type ´T´ = ´a´ }` where: + * ´B´ contains no occurrence of ´a´, + * there exists a type member ´T´ in ´B´, and + * the bounds `>: ´L´ <: ´H´` are not any more restrictive than those of ´T´ in ´B´, i.e., `´L´ <: ´(∃ \alpha : B).T´ <: ´H´`. +* It is a type lambda of the form `[´\pm a_1 >: L_1 <: H_1´, ..., ´\pm a_n >: L_n <: H_n´] =>> ´U´` such that: + * Its bounds contain all possible values of its arguments, and + * When applied to the type arguments, it beta-reduces to a new legal `MatchTypeAppliedPattern` that contains exactly one instance of every type capture present in the type arguments. +* It is a concrete type designator with underlying type definition `= ´U´` and ´U´ is recursively a "pattern-legal type constructor". + +##### Examples of legal patterns + +Given the following definitions: + +```scala +class Inv[A] +class Cov[+A] +class Contra[-A] + +class Base { + type Y +} + +type YExtractor[t] = Base { type Y = t } +type ZExtractor[t] = Base { type Z = t } + +type IsSeq[t <: Seq[Any]] = t +``` + +Here are examples of legal patterns: + +```scala +// TypeWithoutCapture's +case Any => // also the desugaring of `case _ =>` when the _ is at the top-level +case Int => +case List[Int] => +case Array[String] => + +// Class type constructors with direct captures +case scala.collection.immutable.List[t] => // not Predef.List; it is a type alias +case Array[t] => +case Contra[t] => +case Either[s, t] => +case Either[s, Contra[Int]] => +case h *: t => +case Int *: t => + +// The S type constructor +case S[n] => + +// An abstract type constructor +// given a [F[_]] or `type F[_] >: L <: H` in scope +case F[t] => + +// Nested captures in covariant position +case Cov[Inv[t]] => +case Cov[Cov[t]] => +case Cov[Contra[t]] => +case Array[h] *: t => // sugar for *:[Array[h], t] +case g *: h *: EmptyTuple => + +// Type aliases +case List[t] => // which is Predef.List, itself defined as `type List[+A] = scala.collection.immutable.List[A]` + +// Refinements (through a type alias) +case YExtractor[t] => +``` + +The following patterns are *not* legal: + +```scala +// Type capture nested two levels below a non-covariant type constructor +case Inv[Cov[t]] => +case Inv[Inv[t]] => +case Contra[Cov[t]] => + +// Type constructor with bounds that do not contain all possible instantiations +case IsSeq[t] => + +// Type refinement where the refined type member is not a member of the parent +case ZExtractor[t] => +``` + +#### Matching + +Given a scrutinee `X` and a match type case `case P => R` with type captures `ts`, matching proceeds in three steps: + +1. Compute instantiations for the type captures `ts'`, and check that they are *specific* enough. +2. If successful, check that `X <:< [ts := ts']P`. +3. If successful, reduce to `[ts := ts']R`. + +The instantiations are computed by the recursive function `matchPattern(X, P, variance, scrutIsWidenedAbstract)`. +At the top level, `variance = 1` and `scrutIsWidenedAbstract = false`. + +`matchPattern` behaves according to what kind is `P`: + +- If `P` is a `TypeWithoutCapture`: + - Do nothing (always succeed). +- If `P` is a `WildcardCapture` `ti = _`: + - If `X` is of the form `_ >: L <: H`, instantiate `ti := H` (anything between `L` and `H` would work here), + - Otherwise, instantiate `ti := X`. +- If `P` is a `TypeCapture` `ti`: + - If `X` is of the form `_ >: L <: H`, + - If `scrutIsWidenedAbstract` is `true`, fail as not specific. + - Otherwise, if `variance = 1`, instantiate `ti := H`. + - Otherwise, if `variance = -1`, instantiate `ti := L`. + - Otherwise, fail as not specific. + - Otherwise, if `variance = 0` or `scrutIsWidenedAbstract` is `false`, instantiate `ti := X`. + - Otherwise, fail as not specific. +- If `P` is a `MatchTypeAppliedPattern` of the form `T[Qs]`: + - Assert: `variance = 1` (from the definition of legal patterns). + - If `T` is a class type constructor of the form `p.C`: + - If `baseType(X, C)` is not defined, fail as not matching. + - Otherwise, it is of the form `q.C[Us]`. + - If `p =:= q` is false, fail as not matching. + - Let `innerScrutIsWidenedAbstract` be true if either `scrutIsWidenedAbstract` or `X` is not a concrete type. + - For each pair of `(Ui, Qi)`, compute `matchPattern(Ui, Qi, vi, innerScrutIsWidenedAbstract)` where `vi` is the variance of the `i`th type parameter of `T`. + - If `T` is `scala.compiletime.ops.int.S`: + - If `n = natValue(X)` is undefined or `n <= 0`, fail as not matching. + - Otherwise, compute `matchPattern(n - 1, Q1, 1, scrutIsWidenedAbstract)`. + - If `T` is an abstract type constructor: + - If `X` is not of the form `F[Us]` or `F =:= T` is false, fail as not matching. + - Otherwise, for each pair of `(Ui, Qi)`, compute `matchPattern(Ui, Qi, vi, scrutIsWidenedAbstract)` where `vi` is the variance of the `i`th type parameter of `T`. + - If `T` is a refined type of the form `Base { type Y = ti }`: + - Let `q` be `X` if `X` is a stable type, or the skolem type `∃α:X` otherwise. + - If `q` does not have a type member `Y`, fail as not matching (that implies that `X <:< Base` is false, because `Base` must have a type member `Y` for the pattern to be legal). + - If `q` is not a skolem type: + - Compute `matchPattern(ti, q.Y, 0, scrutIsWidenedAbstract)`. + - Otherwise, if `q.Y` is a type alias with underlying type definition `= U`: + - Let `U'` be the result of perform type avoidance on `U` to remove references to `q`. + - If successful, compute `matchPattern(ti, U', 0, scrutIsWidenedAbstract)`. + - Otherwise, fail as not specific. + - If `T` is a concrete type alias to a type lambda: + - Let `P'` be the beta-reduction of `P`. + - Compute `matchPattern(P', X, variance, scrutIsWidenedAbstract)`. + +#### Disjointness + +A scrutinee ´X´ is *provably disjoint* from a pattern ´P´ iff it is provably disjoint from the type ´P'´ obtained by replacing every type capture in ´P´ by a wildcard type argument with the same bounds. + +We note ´X ⋔ Y´ to say that ´X´ and ´Y´ are provably disjoint. +Intuitively, that notion is based on the following properties of the Scala language: + +- Single inheritance of classes +- Final classes cannot be extended +- Sealed traits have a known set of direct children +- Constant types with distinct values are nonintersecting +- Singleton paths to distinct `enum` case values are nonintersecting + +However, a precise definition of provably-disjoint is complicated and requires some helpers. +We start with the notion of "simple types", which are a minimal subset of Scala internal types that capture the concepts mentioned above. + +A "simple type" is one of: + +- `Nothing` +- `AnyKind` +- ´p.C[...X_i]´ a possibly parameterized class type, where ´p´ and ´...X_i´ are arbitrary types (not just simple types) +- ´c´ a literal type +- ´p.C.x´ where `C` is an `enum` class and `x` is one of its value `case`s +- ´X_1 & X_2´ where ´X_1´ and ´X_2´ are both simple types +- ´X_1 | X_2´ where ´X_1´ and ´X_2´ are both simple types +- `´[...a_i]´ =>> ´X_1´` where ´X_1´ is a simple type + +We define ´⌈X⌉´ a function from a full Scala type to a simple type. +Intuitively, it returns the "smallest" simple type that is a supertype of `X`. +It is defined as follows: + +- ´⌈X⌉ = X´ if ´X´ is a simple type +- ´⌈X⌉ = ⌈U⌉´ if ´X´ is a stable type but not a simple type and its underlying type is ´U´ +- ´⌈X⌉ = ⌈H⌉´ if ´X´ is a non-class type designator with upper bound ´H´ +- ´⌈X⌉ = ⌈η(X)⌉´ if ´X´ is a polymorphic class type designator, where ´η(X)´ is its eta-expansion +- ´⌈X⌉ = ⌈Y⌉´ if ´X´ is a match type that reduces to ´Y´ +- ´⌈X⌉ = ⌈H⌉´ if ´X´ is a match type that does not reduce and ´H´ is its upper bound +- ´⌈X[...T_i]⌉ = ⌈Y⌉´ where `Y` is the beta-reduction of ´X[...T_i]´ if ´X´ is a type lambda +- ´⌈X[...T_i]⌉ = ⌈⌈X⌉[...T_i]⌉´ if ´X´ is neither a type lambda nor a class type designator +- ´⌈X @a⌉ = ⌈X⌉´ +- `´⌈X´ { ´R´ }´⌉ = ⌈X⌉´` +- `´⌈´{ ´α´ => ´X´ }´ = ⌈X⌉⌉´` +- ´⌈X_1 & X_2⌉ = ⌈X_1⌉ & ⌈X_2⌉´ +- ´⌈X_1 | X_2⌉ = ⌈X_1⌉ | ⌈X_2⌉´ +- `´⌈[...a_i]´ =>> ´X_1⌉ = [...a_i]´ =>> ´⌈X_1⌉´` + +The following properties hold about ´⌈X⌉´ (we have paper proofs for those): + +- ´X <: ⌈X⌉´ for all type ´X´. +- If ´S <: T´, and the subtyping derivation does not use the "lower-bound rule" of ´<:´ anywhere, then ´⌈S⌉ <: ⌈T⌉´. + +The "lower-bound rule" states that ´S <: T´ if ´T = q.X´ and ´q.X´ is a non-class type designator and ´S <: L´ where ´L´ is the lower bound of the underlying type definition of ´q.X´". +That rule is known to break transitivy of subtyping in Scala already. + +Second, we define the relation ´⋔´ on *classes* (including traits and hidden classes of objects) as: + +- ´C ⋔ D´ if `´C ∉´ baseClasses´(D)´` and ´D´ is `final` +- ´C ⋔ D´ if `´D ∉´ baseClasses´(C)´` and ´C´ is `final` +- ´C ⋔ D´ if there exists `class`es `´C' ∈´ baseClasses´(C)´` and `´D' ∈´ baseClasses´(D)´` such that `´C' ∉´ baseClasses´(D')´` and `´D' ∉´ baseClasses´(C')´`. +- ´C ⋔ D´ if ´C´ is `sealed` without anonymous child and ´C_i ⋔ D´ for all direct children ´C_i´ of ´C´ +- ´C ⋔ D´ if ´D´ is `sealed` without anonymous child and ´C ⋔ D_i´ for all direct children ´D_i´ of ´D´ + +We can now define ´⋔´ for *types*. + +For arbitrary types ´X´ and ´Y´, we define ´X ⋔ Y´ as ´⌈X⌉ ⋔ ⌈Y⌉´. + +Two simple types ´S´ and ´T´ are provably disjoint if there is a finite derivation tree for ´S ⋔ T´ using the following rules. +Most rules go by pair, which makes the whole relation symmetric: + +- `Nothing` is disjoint from everything (including itself): + - `Nothing ´⋔ T´` + - `´S ⋔´ Nothing` +- A union type is disjoint from another type if both of its parts are disjoint from that type: + - ´S ⋔ T_1 | T_2´ if ´S ⋔ T_1´ and ´S ⋔ T_2´ + - ´S_1 | S_2 ⋔ T´ if ´S_1 ⋔ T´ and ´S_2 ⋔ T´ +- An intersection type is disjoint from another type if at least one of its parts is disjoint from that type: + - ´S ⋔ T_1 & T_2´ if ´S ⋔ T_1´ or ´S ⋔ T_2´ + - ´S_1 & S_2 ⋔ T´ if ´S_1 ⋔ T´ or ´S_1 ⋔ T´ +- A type lambda is disjoint from any other type that is not a type lambda with the same number of parameters: + - `´[...a_i]´ =>> ´S_1 ⋔ q.D.y´` + - `´[...a_i]´ =>> ´S_1 ⋔ d´` + - `´[...a_i]´ =>> ´S_1 ⋔ q.D[...T_i]´` + - `´p.C.x ⋔ [...b_i]´ =>> ´T_1´` + - `´c ⋔ [...b_i]´ =>> ´T_1´` + - `´p.C[...S_i] ⋔ [...b_i]´ =>> ´T_1´` + - `´[a_1, ..., a_n]´ =>> ´S_1 ⋔ [b_1, ..., b_m]´ =>> ´T_1´` if ´m \neq n´ +- Two type lambdas with the same number of type parameters are disjoint if their result types are disjoint: + - `´[a_1, ..., a_n]´ =>> ´S_1 ⋔ [b_1, ..., b_n]´ =>> ´T_1´` if ´S_1 ⋔ T_1´ +- An `enum` value case is disjoint from any other `enum` value case (identified by either not being in the same `enum` class, or having a different name): + - ´p.C.x ⋔ q.D.y´ if ´C \neq D´ or ´x \neq y´ +- Two literal types are disjoint if they are different: + - ´c ⋔ d´ if ´c \neq d´ +- An `enum` value case is always disjoint from a literal type: + - ´c ⋔ q.D.y´ + - ´p.C.x ⋔ d´ +- An `enum` value case or a constant is disjoint from a class type if it does not extend that class (because it's essentially final): + - ´p.C.x ⋔ q.D[...T_i]´ if `baseType´(p.C.x, D)´` is not defined + - ´p.C[...S_i] ⋔ q.D.y´ if `baseType´(q.D.y, C)´` is not defined + - ´c ⋔ q.D[...T_i]´ if `baseType´(c, D)´` is not defined + - ´p.C[...S_i] ⋔ d´ if `baseType´(d, C)´` is not defined +- Two class types are disjoint if the classes themselves are disjoint, or if there exists a common super type with conflicting type arguments. + - ´p.C[...S_i] ⋔ q.D[...T_i]´ if ´C ⋔ D´ + - ´p.C[...S_i] ⋔ q.D[...T_i]´ if there exists a class ´E´ such that `baseType´(p.C[...S_i], E) = a.E[...A_i]´` and `baseType´(q.D[...T_i], E) = b.E[...B_i]´` and there exists a pair ´(A_i, B_i)´ such that + - ´A_i ⋔ B_i´ and it is in covariant position and there exists a field of that type parameter in ´E´, or + - ´A_i ⋔ B_i´ and it is in invariant position, and: + - there exists a field of that type parameter in ´E´, or + - ´A_i´ cannot be instantiated to `Nothing`, or + - ´B_i´ cannot be instantiated to `Nothing`. + +It is worth noting that this definition disregards prefixes entirely. +´p.C´ and ´q.C´ are never provably disjoint, even if ´p´ could be proven disjoint from ´q´. +It also disregards type members. + +We have a proof sketch of the following property for ´⋔´: + +* If ´S <: T´ and ´T ⋔ U´, then ´S ⋔ U´. + +This is a very desirable property. +It means that if we make the scrutinee of a match type more precise (a subtype) through substitution, and the match type previously reduced, then the match type will still reduce to the same case. + +Note: if ´⋔´ were a "true" disjointness relationship, and not a *provably*-disjoint relationship, that property would trivially hold based on elementary set theoretic properties. +It would amount to proving that if ´S ⊆ T´ and ´T ⋂ U = ∅´, then ´S ⋂ U = ∅´. + +#### Reduction + +The result of reducing `´X´ match { case ´P_1´ => ´R_1; ...;´ case ´P_n´ => ´R_n´ }` can be a type, undefined, or a compile error. + +For ´n \geq 1´, it is specified as: + +* If ´X´ matches ´P_1´ with type capture instantiations `´[...t_i \to t_i']´`: + * If ´X ⋔ P_1´, do not reduce. + * Otherwise, reduce as `´[...t_i \to t_i']R_1´`. +* Otherwise, + * If ´X ⋔ P1´, the result of reducing `´X´ match { case ´P_2´ => ´R_2; ...;´ case ´P_n´ => ´R_n´ }` (i.e., proceed with subsequent cases). + * Otherwise, do not reduce. + +The reduction of an "empty" match type `´X´ match { }` (which cannot be written in user programs) is a compile error. + ### Skolem Types ```ebnf @@ -1141,6 +1489,10 @@ Note that the conditions are not all mutually exclusive. - `´T =´ { ´\beta´ => ´T_1´ }` and ´S´ is a proper type but not a recursive type and ´p' <: [\beta := p]T_1´ where: - ´p´ is ´S´ if ´S´ is a stable type and ´∃ \alpha : S´ otherwise, and - ´p'´ is the result of replacing any top-level recursive type `{ ´\gamma´ => ´Z´ }` in ´p´ with ´[\gamma := p]Z´ (TODO specify this better). +- `´(X´ match <: ´H´ { ... }´) <: T´` if `´X´ match ...` reduces to ´S_1´ and ´S_1 <: T´ +- `´S <: (X´ match <: ´H´ { ... }´)´` if `´X´ match ...` reduces to ´T_1´ and ´S <: T_1´ +- `´(X´ match <: ´H´ { ... }´) <: T´` if ´H <: T´ +- `´(X´ match <: ´H_X´ { case ´P_1´ => ´A_1´; ...; case ´P_n´ => ´A_n´ }´) <: (Y´ match <: ´H_Y´ { case ´Q_1´ => ´B_1´; ...; ´Q_n´ => ´B_n´ }´)´` if ´X =:= Y´ and ´P_i =:= Q_i´ for each ´i´ and ´A_i <: B_i´ for each ´i´ - `´S = (´=> ´S_1)´` and `´T = (´=> ´T_1)´` and ´S_1 <: T_1´. - `´S =´ scala.Null` and: - ´T = q.C[T_1, ..., T_n]´ with ´n \geq 0´ and ´C´ does not derive from `scala.AnyVal` and ´C´ is not the hidden class of an `object`, or diff --git a/docs/_spec/04-basic-definitions.md b/docs/_spec/04-basic-definitions.md index 28eb2d43a627..3fa58fd32bd0 100644 --- a/docs/_spec/04-basic-definitions.md +++ b/docs/_spec/04-basic-definitions.md @@ -220,6 +220,7 @@ TypeDcl ::= id [TypeParamClause] [‘>:’ Type] [‘<:’ Type] Def ::= ‘type’ {nl} TypeDef | ‘opaque‘ ‘type‘ {nl} OpaqueTypeDef TypeDef ::= id [TypeParamClause] ‘=’ Type + | id [TypeParamClause] ‘<:’ Type ‘=’ MatchType OpaqueTypeDef ::= id [TypeParamClause] [‘>:’ Type] [‘<:’ Type] ‘=’ Type ``` @@ -229,6 +230,7 @@ A possibly parameterized _abstract type member_ definition `type ´t´[´\mathit If omitted, ´L´ and ´H´ are implied to be `Nothing` and `scala.Any`, respectively. A possibly parameterized _type alias_ definition `type ´t´[´\mathit{tps}\,´] = ´T´` defines ´t´ to be a concrete type member. +The alternative `type ´t´[´\mathit{tps}\,´] <: ´H´ = ´T´ match { ´Cs´ }` desugars to `type ´t´[´\mathit{tps}\,´] = (´T´ match { ´Cs´ } <: ´H´)`, i.e., a match type with an upper bound, which cannot otherwise be expressed in the concrete syntax. A possibly parameterized _opaque type alias_ definition `opaque type ´t´[´\mathit{tps}\,´] >: ´L´ <: ´H´ = ´T´` defines ´t´ to be an opaque type alias with public bounds `>: ´L´ <: ´H´` and a private alias `= ´T´`. diff --git a/docs/_spec/Gemfile.lock b/docs/_spec/Gemfile.lock index c703a87bf993..38a790c676eb 100644 --- a/docs/_spec/Gemfile.lock +++ b/docs/_spec/Gemfile.lock @@ -41,7 +41,7 @@ GEM sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - webrick (1.8.2) + webrick (1.9.1) PLATFORMS ruby diff --git a/library/src/scala/NamedTuple.scala b/library/src/scala/NamedTuple.scala index 6da7f940dc47..0d1deffce513 100644 --- a/library/src/scala/NamedTuple.scala +++ b/library/src/scala/NamedTuple.scala @@ -139,9 +139,7 @@ object NamedTupleDecomposition: extension [N <: Tuple, V <: Tuple](x: NamedTuple[N, V]) /** The value (without the name) at index `n` of this tuple */ inline def apply(n: Int): Elem[NamedTuple[N, V], n.type] = - inline x.toTuple match - case tup: NonEmptyTuple => tup(n).asInstanceOf[Elem[NamedTuple[N, V], n.type]] - case tup => tup.productElement(n).asInstanceOf[Elem[NamedTuple[N, V], n.type]] + x.toTuple.apply(n).asInstanceOf[Elem[NamedTuple[N, V], n.type]] /** The number of elements in this tuple */ inline def size: Size[NamedTuple[N, V]] = x.toTuple.size diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index d07f2c89e004..57d1572772e2 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -65,7 +65,7 @@ sealed trait Tuple extends Product { inline def size[This >: this.type <: Tuple]: Size[This] = runtime.Tuples.size(this).asInstanceOf[Size[This]] - /** Given two tuples, `(a1, ..., an)` and `(a1, ..., an)`, returns a tuple + /** Given two tuples, `(a1, ..., an)` and `(b1, ..., bn)`, returns a tuple * `((a1, b1), ..., (an, bn))`. If the two tuples have different sizes, * the extra elements of the larger tuple will be disregarded. * The result is typed as `((A1, B1), ..., (An, Bn))` if at least one of the diff --git a/library/src/scala/caps.scala b/library/src/scala/caps.scala index 9911ef920116..c35b3b55e813 100644 --- a/library/src/scala/caps.scala +++ b/library/src/scala/caps.scala @@ -22,18 +22,18 @@ import annotation.{experimental, compileTimeOnly, retainsCap} /** A type constraint expressing that the capture set `C` needs to contain * the capability `R` */ - sealed trait Contains[C <: CapSet @retainsCap, R <: Singleton] + sealed trait Contains[+C >: CapSet <: CapSet @retainsCap, R <: Singleton] /** The only implementation of `Contains`. The constraint that `{R} <: C` is * added separately by the capture checker. */ - given containsImpl[C <: CapSet @retainsCap, R <: Singleton]: Contains[C, R]() + given containsImpl[C >: CapSet <: CapSet @retainsCap, R <: Singleton]: Contains[C, R]() /** A wrapper indicating a type variable in a capture argument list of a * @retains annotation. E.g. `^{x, Y^}` is represented as `@retains(x, capsOf[Y])`. */ @compileTimeOnly("Should be be used only internally by the Scala compiler") - def capsOf[CS]: Any = ??? + 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 @@ -55,7 +55,7 @@ import annotation.{experimental, compileTimeOnly, retainsCap} /** This should go into annotations. For now it is here, so that we * can experiment with it quickly between minor releases */ - final class unbox extends annotation.StaticAnnotation + final class use extends annotation.StaticAnnotation object unsafe: @@ -66,20 +66,4 @@ import annotation.{experimental, compileTimeOnly, retainsCap} */ def unsafeAssumePure: T = x - /** If argument is of type `cs T`, converts to type `box cs T`. This - * avoids the error that would be raised when boxing `cap`. - */ - def unsafeBox: T = x - - /** If argument is of type `box cs T`, converts to type `cs T`. This - * avoids the error that would be raised when unboxing `cap`. - */ - def unsafeUnbox: T = x - - extension [T, U](f: T => U) - /** If argument is of type `box cs T`, converts to type `cs T`. This - * avoids the error that would be raised when unboxing `cap`. - */ - def unsafeBoxFunArg: T => U = f - end unsafe diff --git a/library/src/scala/runtime/coverage/Invoker.scala b/library/src/scala/runtime/coverage/Invoker.scala index c35c6c2ec7df..b3216ec37c67 100644 --- a/library/src/scala/runtime/coverage/Invoker.scala +++ b/library/src/scala/runtime/coverage/Invoker.scala @@ -3,7 +3,7 @@ package scala.runtime.coverage import scala.annotation.internal.sharable import scala.annotation.nowarn import scala.collection.concurrent.TrieMap -import scala.collection.mutable.{BitSet, AnyRefMap} +import scala.collection.mutable.{BitSet, HashMap} import java.io.{File, FileWriter} import java.nio.file.Files @@ -12,7 +12,7 @@ object Invoker { private val runtimeUUID = java.util.UUID.randomUUID() private val MeasurementsPrefix = "scoverage.measurements." - private val threadFiles = new ThreadLocal[AnyRefMap[String, FileWriter]] + private val threadFiles = new ThreadLocal[HashMap[String, FileWriter]] private val dataDirToSet = TrieMap.empty[String, BitSet] /** We record that the given id has been invoked by appending its id to the coverage data file. @@ -38,7 +38,7 @@ object Invoker { if added then var writers = threadFiles.get() if writers == null then - writers = AnyRefMap.empty + writers = HashMap.empty threadFiles.set(writers) val writer = writers.getOrElseUpdate( dataDir, diff --git a/presentation-compiler-testcases/src/tests/macros/20560.scala b/presentation-compiler-testcases/src/tests/macros/20560.scala new file mode 100644 index 000000000000..f72fc473f452 --- /dev/null +++ b/presentation-compiler-testcases/src/tests/macros/20560.scala @@ -0,0 +1,16 @@ +package tests.macros + +import scala.quoted.{Expr, Quotes} + +object Macro20560: + transparent inline def loadJavaSqlDriver: Int = ${ loadJavaSqlDriverImpl } + + private def loadJavaSqlDriverImpl(using Quotes): Expr[42] = + Class.forName("java.sql.Driver") + '{42} + + transparent inline def loadJavaSqlInexisting: Int = ${ loadJavaSqlInexistingImpl } + + private def loadJavaSqlInexistingImpl(using Quotes): Expr[42] = + Class.forName("java.sql.Inexisting") + '{42} diff --git a/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala index 260a28392093..3d65f69621e1 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/InferExpectedType.scala @@ -7,6 +7,7 @@ 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 @@ -61,7 +62,7 @@ class InferExpectedType( object InterCompletionType: def inferType(path: List[Tree])(using Context): Option[Type] = path match - case (lit: Literal) :: Select(Literal(_), _) :: Apply(Select(Literal(_), _), List(Literal(Constant(null)))) :: rest => inferType(rest, lit.span) + case (lit: Literal) :: Select(Literal(_), _) :: Apply(Select(Literal(_), _), List(s: Select)) :: rest if s.symbol == defn.Predef_undefined => inferType(rest, lit.span) case ident :: rest => inferType(rest, ident.span) case _ => None diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/KeywordsCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/KeywordsCompletions.scala index abbf43174bc7..5dd3c8dead4d 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/KeywordsCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/KeywordsCompletions.scala @@ -216,6 +216,7 @@ object KeywordsCompletions: case untpd.TypeDef(_, template: Template) => checkForPossibleKeywords(template) case untpd.ModuleDef(_, template: Template) => checkForPossibleKeywords(template) case template: Template => checkForPossibleKeywords(template) + case _ => TemplateKeywordAvailability.default }.getOrElse(TemplateKeywordAvailability.default) 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 6e59c9afca3a..53c4e01980bc 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala @@ -7,18 +7,13 @@ import dotty.tools.pc.completions.CompletionValue.SingletonValue 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.Types.AndType import dotty.tools.dotc.core.Types.AppliedType import dotty.tools.dotc.core.Types.ConstantType import dotty.tools.dotc.core.Types.OrType -import dotty.tools.dotc.core.Types.TermRef import dotty.tools.dotc.core.Types.Type import dotty.tools.dotc.core.Types.TypeRef -import dotty.tools.dotc.util.Spans.Span -import dotty.tools.dotc.core.Symbols.defn object SingletonCompletions: def contribute( @@ -55,79 +50,3 @@ object SingletonCompletions: collectSingletons(tpe1).intersect(collectSingletons(tpe2)) case _ => Nil -object InterCompletionType: - 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) - case ident :: rest => inferType(rest, ident.span) - case _ => None - - def inferType(path: List[Tree], span: Span)(using Context): Option[Type] = - path match - case Apply(head, List(p : Select)) :: rest if p.name == StdNames.nme.??? && p.qualifier.symbol.name == StdNames.nme.Predef && p.span.isSynthetic => - inferType(rest, span) - case Block(_, expr) :: rest if expr.span.contains(span) => - inferType(rest, span) - case If(cond, _, _) :: rest if !cond.span.contains(span) => - inferType(rest, span) - case Typed(expr, tpt) :: _ if expr.span.contains(span) && !tpt.tpe.isErroneous => Some(tpt.tpe) - case Block(_, expr) :: rest if expr.span.contains(span) => - inferType(rest, span) - case Bind(_, body) :: rest if body.span.contains(span) => inferType(rest, span) - case Alternative(_) :: rest => inferType(rest, span) - 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 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) - // x match - // case @@ - case CaseDef(pat, _, _) :: Match(sel, cases) :: rest if pat.span.contains(span) && cases.exists(_.span.contains(span)) && !sel.tpe.isErroneous => - sel.tpe match - case tpe: TermRef => Some(tpe.symbol.info).filterNot(_.isErroneous) - case tpe => Some(tpe) - // List(@@) - case SeqLiteral(_, tpe) :: _ if !tpe.tpe.isErroneous => - Some(tpe.tpe) - // val _: T = @@ - // def _: T = @@ - case (defn: ValOrDefDef) :: rest if !defn.tpt.tpe.isErroneous => Some(defn.tpt.tpe) - // 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 _ => None - diff --git a/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala b/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala index 1158e433e732..a26c31ef084d 100644 --- a/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/base/BasePCSuite.scala @@ -25,9 +25,9 @@ import org.junit.runner.RunWith import scala.meta.pc.CompletionItemPriority object TestResources: - val scalaLibrary = BuildInfo.ideTestsDependencyClasspath.map(_.toPath).toSeq + val classpath = BuildInfo.ideTestsDependencyClasspath.map(_.toPath).toSeq val classpathSearch = - ClasspathSearch.fromClasspath(scalaLibrary, ExcludedPackagesHandler.default) + ClasspathSearch.fromClasspath(classpath, ExcludedPackagesHandler.default) @RunWith(classOf[ReusableClassRunner]) abstract class BasePCSuite extends PcAssertions: @@ -38,11 +38,11 @@ abstract class BasePCSuite extends PcAssertions: val executorService: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor() val testingWorkspaceSearch = TestingWorkspaceSearch( - TestResources.scalaLibrary.map(_.toString) + TestResources.classpath.map(_.toString) ) lazy val presentationCompiler: PresentationCompiler = - val myclasspath: Seq[Path] = TestResources.scalaLibrary + val myclasspath: Seq[Path] = TestResources.classpath val scalacOpts = scalacOptions(myclasspath) val search = new MockSymbolSearch( testingWorkspaceSearch, 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 ab28baea994b..4a30f9b06efa 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -2150,3 +2150,21 @@ class CompletionSuite extends BaseCompletionSuite: |""".stripMargin, assertSingleItem = false ) + + @Test def `metals-i6861` = + check( + """|trait Builder[Alg]: + | def withTraces: String + | + |trait BuilderFactory: + | def transformRouter(f: [Alg] => Builder[Alg] => String): BuilderFactory + | def build: Unit + | + |def demo = + | (??? : BuilderFactory) + | .transformRouter([Alg] => _.withTraces) + | .build@@ + |""".stripMargin, + """|build: Unit + |""".stripMargin, + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala index 3e7a2549cbe0..ec1431187e56 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala @@ -596,6 +596,21 @@ class HoverTermSuite extends BaseHoverSuite: |""".stripMargin ) + @Test def `i20560`= + check( + "val re@@s = tests.macros.Macro20560.loadJavaSqlDriver", + """```scala + |val res: Int + |``` + |""".stripMargin + ) + + @Test def `i20560-2`= + check( + "val re@@s = tests.macros.Macro20560.loadJavaSqlInexisting", + "", // crashes in the Macro; no type info + ) + @Test def `import-rename` = check( """ diff --git a/project/Build.scala b/project/Build.scala index 7f98e87fcaaa..60f8e4d87be1 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -93,11 +93,12 @@ object Build { /** Version of the Scala compiler used to build the artifacts. * Reference version should track the latest version pushed to Maven: - * - In main branch it should be the last RC version (using experimental TASTy required for non-bootstrapped tests) + * - In main branch it should be the last RC version * - In release branch it should be the last stable release - * 3.6.0-RC1 was released as 3.6.0 - it's having and experimental TASTy version + * + * Warning: Change of this variable needs to be consulted with `expectedTastyVersion` */ - val referenceVersion = "3.6.2" + val referenceVersion = "3.6.3" /** Version of the Scala compiler targeted in the current release cycle * Contains a version without RC/SNAPSHOT/NIGHTLY specific suffixes @@ -105,8 +106,10 @@ object Build { * * Should only be referred from `dottyVersion` or settings/tasks requiring simplified version string, * eg. `compatMode` or Windows native distribution version. + * + * Warning: Change of this variable might require updating `expectedTastyVersion` */ - val developedVersion = "3.6.3" + val developedVersion = "3.6.4" /** The version of the compiler including the RC prefix. * Defined as common base before calculating environment specific suffixes in `dottyVersion` @@ -117,6 +120,25 @@ object Build { */ 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: + * - https://github.com/scala/scala3/issues/13447#issuecomment-912447107 + * - https://github.com/scala/scala3/issues/14306#issuecomment-1069333516 + * - https://github.com/scala/scala3/pull/19321 + * + * Simplified rules, given 3.$minor.$patch = $developedVersion + * - Major version is always 28 + * - TASTY minor version: + * - in main (NIGHTLY): {if $patch == 0 then $minor else ${minor + 1}} + * - in release branch is always equal to $minor + * - TASTY experimental version: + * - in main (NIGHTLY) is always experimental + * - in release candidate branch is experimental if {patch == 0} + * - in stable release is always non-experimetnal + */ + val expectedTastyVersion = "28.6" + checkReleasedTastyVersion() + /** Final version of Scala compiler, controlled by environment variables. */ val dottyVersion = { if (isRelease) baseVersion @@ -149,7 +171,7 @@ 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.1 is an exception from this rule - 3.6.0 was a broken release + * 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.6.2" @@ -279,6 +301,7 @@ object Build { // Avoid various sbt craziness involving classloaders and parallelism run / fork := true, + run / connectInput := true, Test / fork := true, Test / parallelExecution := false, @@ -327,7 +350,7 @@ object Build { .withTestRetryConfiguration( config.testRetryConfiguration .withFlakyTestPolicy(FlakyTestPolicy.Fail) - .withMaxRetries(1) + .withMaxRetries(if (isInsideCI) 1 else 0) .withMaxFailures(10) .withClassesFilter((className, _) => !noRetryTestClasses.contains(className)) ) @@ -739,7 +762,7 @@ object Build { // get libraries onboard libraryDependencies ++= Seq( - "org.scala-lang.modules" % "scala-asm" % "9.7.0-scala-2", // used by the backend + "org.scala-lang.modules" % "scala-asm" % "9.7.1-scala-1", // used by the backend Dependencies.compilerInterface, "org.jline" % "jline-reader" % "3.27.1", // used by the REPL "org.jline" % "jline-terminal" % "3.27.1", @@ -1425,7 +1448,7 @@ object Build { lazy val `scala3-presentation-compiler` = project.in(file("presentation-compiler")) .withCommonSettings(Bootstrapped) - .dependsOn(`scala3-compiler-bootstrapped`, `scala3-library-bootstrapped`) + .dependsOn(`scala3-compiler-bootstrapped`, `scala3-library-bootstrapped`, `scala3-presentation-compiler-testcases` % "test->test") .settings(presentationCompilerSettings) .settings(scala3PresentationCompilerBuildInfo) .settings( @@ -1436,6 +1459,7 @@ object Build { def scala3PresentationCompilerBuildInfo = Seq( ideTestsDependencyClasspath := { + val testCasesLib = (`scala3-presentation-compiler-testcases` / Compile / classDirectory).value val dottyLib = (`scala3-library-bootstrapped` / Compile / classDirectory).value val scalaLib = (`scala3-library-bootstrapped` / Compile / dependencyClasspath) @@ -1443,7 +1467,7 @@ object Build { .map(_.data) .filter(_.getName.matches("scala-library.*\\.jar")) .toList - dottyLib :: scalaLib + testCasesLib :: dottyLib :: scalaLib // Nil }, Compile / buildInfoPackage := "dotty.tools.pc.buildinfo", @@ -1503,6 +1527,10 @@ object Build { ) } + lazy val `scala3-presentation-compiler-testcases` = project.in(file("presentation-compiler-testcases")) + .dependsOn(`scala3-compiler-bootstrapped`) + .settings(commonBootstrappedSettings) + lazy val `scala3-language-server` = project.in(file("language-server")). dependsOn(dottyCompiler(Bootstrapped)). settings(commonBootstrappedSettings). @@ -2426,6 +2454,9 @@ object Build { settings(disableDocSetting). settings( versionScheme := Some("semver-spec"), + Test / envVars ++= Map( + "EXPECTED_TASTY_VERSION" -> expectedTastyVersion, + ), if (mode == Bootstrapped) Def.settings( commonMiMaSettings, mimaForwardIssueFilters := MiMaFilters.TastyCore.ForwardsBreakingChanges, @@ -2475,6 +2506,34 @@ object Build { case Bootstrapped => commonBootstrappedSettings }) } + + /* Tests TASTy version invariants during NIGHLY, RC or Stable releases */ + def checkReleasedTastyVersion(): Unit = { + lazy val (scalaMinor, scalaPatch, scalaIsRC) = baseVersion.split("\\.|-").take(4) match { + case Array("3", minor, patch) => (minor.toInt, patch.toInt, false) + case Array("3", minor, patch, _) => (minor.toInt, patch.toInt, true) + case other => sys.error(s"Invalid Scala base version string: $baseVersion") + } + lazy val (tastyMinor, tastyIsExperimental) = expectedTastyVersion.split("\\.|-").take(4) match { + case Array("28", minor) => (minor.toInt, false) + case Array("28", minor, "experimental", _) => (minor.toInt, true) + case other => sys.error(s"Invalid TASTy version string: $expectedTastyVersion") + } + + if(isNightly) { + assert(tastyIsExperimental, "TASTY needs to be experimental in nightly builds") + val expectedTastyMinor = if(scalaPatch == 0) scalaMinor else scalaMinor + 1 + assert(tastyMinor == expectedTastyMinor, "Invalid TASTy minor version") + } + + if(isRelease) { + assert(scalaMinor == tastyMinor, "Minor versions of TASTY vesion and Scala version should match in release builds") + if (scalaIsRC && scalaPatch == 0) + assert(tastyIsExperimental, "TASTy should be experimental when releasing a new minor version RC") + else + assert(!tastyIsExperimental, "Stable version cannot use experimental TASTY") + } + } } object ScaladocConfigs { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 88a97721ee3c..427b53e45221 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -28,5 +28,5 @@ object Dependencies { "com.vladsch.flexmark" % "flexmark-ext-yaml-front-matter" % flexmarkVersion, ) - val compilerInterface = "org.scala-sbt" % "compiler-interface" % "1.9.6" + val compilerInterface = "org.scala-sbt" % "compiler-interface" % "1.10.4" } diff --git a/project/build.properties b/project/build.properties index 04267b14af69..db1723b08622 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.9 +sbt.version=1.10.5 diff --git a/project/scripts/check-cla.sh b/project/scripts/check-cla.sh deleted file mode 100755 index dbb148d3c652..000000000000 --- a/project/scripts/check-cla.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -eux - -echo "Pull request submitted by $AUTHOR"; -if [[ "$AUTHOR" == "github-actions[bot]" || "$AUTHOR" == "dependabot[bot]" ]] ; then - echo "CLA check for $AUTHOR successful"; -else - signed=$(curl -L -s "https://contribute.akka.io/contribute/cla/scala/check/$AUTHOR" | jq -r ".signed"); - if [ "$signed" = "true" ] ; then - echo "CLA check for $AUTHOR successful"; - else - echo "CLA check for $AUTHOR failed"; - echo "Please sign the Scala CLA to contribute to the Scala compiler."; - echo "Go to https://contribute.akka.io/contribute/cla/scala and then"; - echo "comment on the pull request to ask for a new check."; - echo ""; - echo "Check if CLA is signed: https://contribute.akka.io/contribute/cla/scala/check/$AUTHOR"; - exit 1; - fi; -fi; diff --git a/scala2-library-cc/src/scala/collection/Iterator.scala b/scala2-library-cc/src/scala/collection/Iterator.scala index 4d1b0ed4ff95..91a22caa288c 100644 --- a/scala2-library-cc/src/scala/collection/Iterator.scala +++ b/scala2-library-cc/src/scala/collection/Iterator.scala @@ -17,7 +17,7 @@ import scala.annotation.tailrec import scala.annotation.unchecked.uncheckedVariance import scala.runtime.Statics import language.experimental.captureChecking - +import caps.unsafe.unsafeAssumePure /** Iterators are data structures that allow to iterate over a sequence * of elements. They have a `hasNext` method for checking @@ -1008,7 +1008,7 @@ object Iterator extends IterableFactory[Iterator] { 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 } - }.asInstanceOf // !!! CC unsafe op + }.unsafeAssumePure /** Creates iterator that produces the results of some element computation a number of times. * @@ -1159,8 +1159,8 @@ object Iterator extends IterableFactory[Iterator] { // 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.asInstanceOf // !!! CC unsafe op + val c: ConcatIterator[A] = current.asInstanceOf + current = c.current.unsafeAssumePure // !!! CC unsafe op currentHasNextChecked = c.currentHasNextChecked if (c.tail != null) { if (last == null) last = c.last diff --git a/scala2-library-cc/src/scala/collection/SeqView.scala b/scala2-library-cc/src/scala/collection/SeqView.scala index 292dc61ddaa8..30207fca46ff 100644 --- a/scala2-library-cc/src/scala/collection/SeqView.scala +++ b/scala2-library-cc/src/scala/collection/SeqView.scala @@ -16,7 +16,6 @@ package collection import scala.annotation.nowarn import language.experimental.captureChecking import caps.unsafe.unsafeAssumePure -import scala.annotation.unchecked.uncheckedCaptures /** !!! Scala 2 difference: Need intermediate trait SeqViewOps to collect the * necessary functionality over which SeqViews are defined, and at the same diff --git a/scala2-library-cc/src/scala/collection/Stepper.scala b/scala2-library-cc/src/scala/collection/Stepper.scala index 2f8abee4cffb..1723a110ad8a 100644 --- a/scala2-library-cc/src/scala/collection/Stepper.scala +++ b/scala2-library-cc/src/scala/collection/Stepper.scala @@ -53,7 +53,7 @@ trait Stepper[@specialized(Double, Int, Long) +A] { * * See method `trySplit` in [[java.util.Spliterator]]. */ - def trySplit(): Stepper[A] + def trySplit(): Stepper[A]^{this} /** Returns an estimate of the number of elements of this Stepper, or [[Long.MaxValue]]. See * method `estimateSize` in [[java.util.Spliterator]]. @@ -71,7 +71,7 @@ trait Stepper[@specialized(Double, Int, Long) +A] { * a [[java.util.Spliterator.OfInt]] (which is a `Spliterator[Integer]`) in the subclass [[IntStepper]] * (which is a `Stepper[Int]`). */ - def spliterator[B >: A]: Spliterator[_] + def spliterator[B >: A]: Spliterator[_]^{this} /** Returns a Java [[java.util.Iterator]] corresponding to this Stepper. * @@ -79,7 +79,7 @@ trait Stepper[@specialized(Double, Int, Long) +A] { * a [[java.util.PrimitiveIterator.OfInt]] (which is a `Iterator[Integer]`) in the subclass * [[IntStepper]] (which is a `Stepper[Int]`). */ - def javaIterator[B >: A]: JIterator[_] + def javaIterator[B >: A]: JIterator[_]^{this} /** Returns an [[Iterator]] corresponding to this Stepper. Note that Iterators corresponding to * primitive Steppers box the elements. diff --git a/scaladoc-testcases/src/tests/namedTuples.scala b/scaladoc-testcases/src/tests/namedTuples.scala new file mode 100644 index 000000000000..30a83e7e01b0 --- /dev/null +++ b/scaladoc-testcases/src/tests/namedTuples.scala @@ -0,0 +1,23 @@ +package tests.namedTuples + +import language.experimental.namedTuples +import NamedTuple.* + +type Person = (name: String, age: Int) + +type Person2 = NamedTuple[("age2", "name2"), (Int, String)] //expected: type Person2 = (age2: Int, name2: String) + +val x = (name = "Bob", age = 25) //expected: val x: (name: String, age: Int) + +def foo(p1: (age: Int, name: String), p2: (name: String, age: Int)): Nothing + = ??? + +def invalid1: NamedTuple[("age", String), (Int, Int)] + = ??? + +def invalid2: NamedTuple[("age", "name"), (Int, Int, Int)] + = ??? + +def invalid3: NamedTuple[("age", "name", "something"), (Int, Int)] + = ??? + diff --git a/scaladoc/resources/dotty_res/scripts/ux.js b/scaladoc/resources/dotty_res/scripts/ux.js index 97f9bf14939d..29a610f62b0c 100644 --- a/scaladoc/resources/dotty_res/scripts/ux.js +++ b/scaladoc/resources/dotty_res/scripts/ux.js @@ -167,6 +167,11 @@ function attachAllListeners() { if (url.origin !== window.location.origin) { return; } + // ISSUE-19208, treat as normal link when lacking HTTP server, + // otherwise GET request blocked by CORS protections. + if (window.location.protocol.startsWith("file")) { + return; + } if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) { return; } diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index d3c93aaba8c7..ee12755c7f98 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -577,7 +577,8 @@ trait ClassLikeSupport: def unwrapMemberInfo(c: ClassDef, symbol: Symbol): MemberInfo = - val baseTypeRepr = typeForClass(c).memberType(symbol) + 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 diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 855678a091d2..15c3071c38c9 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -204,36 +204,43 @@ trait TypesSupport: prefix ++ plain("{ ").l ++ refinedElems.flatMap(e => parseRefinedElem(e.name, e.info)) ++ plain(" }").l } } + + case AppliedType(tpe, args) if defn.isTupleClass(tpe.typeSymbol) && args.length > 1 => + inParens(commas(args.map(inner(_)))) + + case AppliedType(namedTuple, List(AppliedType(tuple1, names), AppliedType(tuple2, types))) + if namedTuple.typeSymbol == Symbol.requiredModule("scala.NamedTuple").typeMember("NamedTuple") + && defn.isTupleClass(tuple1.typeSymbol) && defn.isTupleClass(tuple2.typeSymbol) && names.length == types.length + && names.forall { case ConstantType(StringConstant(_)) => true case _ => false } => + val elems = names + .collect { case ConstantType(StringConstant(s)) => s } + .zip(types) + .map((name, tpe) => plain(name) +: plain(": ") +: inner(tpe)) + inParens(commas(elems)) + + case t @ AppliedType(tpe, List(lhs, rhs)) if isInfix(t) => + inParens(inner(lhs), shouldWrapInParens(lhs, t, true)) + ++ plain(" ").l + ++ inner(tpe) + ++ plain(" ").l + ++ inParens(inner(rhs), 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) + case t @ AppliedType(tpe, typeList) => - import dotty.tools.dotc.util.Chars._ - if defn.isTupleClass(tpe.typeSymbol) && typeList.length != 1 then - typeList match - case Nil => Nil - case args => inParens(commas(args.map(inner(_)))) - else if isInfix(t) then - val lhs = typeList.head - val rhs = typeList.last - inParens(inner(lhs), shouldWrapInParens(lhs, t, true)) - ++ plain(" ").l - ++ inner(tpe) - ++ plain(" ").l - ++ inParens(inner(rhs), shouldWrapInParens(rhs, t, false)) - else if t.isFunctionType then - val arrow = if t.isContextFunctionType then " ?=> " else " => " - typeList match - case Nil => - Nil - case Seq(rtpe) => - plain("()").l ++ keyword(arrow).l ++ inner(rtpe) - case Seq(arg, rtpe) => - val partOfSignature = stripAnnotated(arg) match - case _: TermRef | _: TypeRef | _: ConstantType | _: ParamRef => inner(arg) - case at: AppliedType if !isInfix(at) && !at.isFunctionType && !at.isTupleN => inner(arg) - case _ => inParens(inner(arg)) - partOfSignature ++ keyword(arrow).l ++ inner(rtpe) - case args => - plain("(").l ++ commas(args.init.map(inner(_))) ++ plain(")").l ++ keyword(arrow).l ++ inner(args.last) - else inner(tpe) ++ plain("[").l ++ commas(typeList.map { t => t match + inner(tpe) ++ plain("[").l ++ commas(typeList.map { t => t match case _: TypeBounds => keyword("_").l ++ inner(t) case _ => topLevelProcess(t) }) ++ plain("]").l diff --git a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala index bfa2a372827a..4ed17662fff8 100644 --- a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala +++ b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala @@ -124,3 +124,5 @@ class ExtendsCall extends SignatureTest("extendsCall", SignatureTest.all) class RefinedFunctionTypes extends SignatureTest("refinedFunctionTypes", SignatureTest.all) class RightAssocExtension extends SignatureTest("rightAssocExtension", SignatureTest.all) + +class NamedTuples extends SignatureTest("namedTuples", SignatureTest.all) diff --git a/tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala b/tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala new file mode 100644 index 000000000000..d2e62e1f9eb0 --- /dev/null +++ b/tasty/test/dotty/tools/tasty/BuildTastyVersionTest.scala @@ -0,0 +1,26 @@ +package dotty.tools.tasty + +import org.junit.Assert._ +import org.junit.Test + +import TastyBuffer._ + +// Tests ensuring TASTY version emitted by compiler is matching expected TASTY version +class BuildTastyVersionTest { + + val CurrentTastyVersion = TastyVersion(TastyFormat.MajorVersion, TastyFormat.MinorVersion, TastyFormat.ExperimentalVersion) + + // Needs to be defined in build Test/envVars + val ExpectedTastyVersionEnvVar = "EXPECTED_TASTY_VERSION" + + @Test def testBuildTastyVersion(): Unit = { + val expectedVersion = sys.env.get(ExpectedTastyVersionEnvVar) + .getOrElse(fail(s"Env variable $ExpectedTastyVersionEnvVar not defined")) + .match { + case s"$major.$minor-experimental-$experimental" => TastyVersion(major.toInt, minor.toInt, experimental.toInt) + case s"$major.$minor" if minor.forall(_.isDigit) => TastyVersion(major.toInt, minor.toInt, 0) + case other => fail(s"Invalid TASTY version string: $other") + } + assertEquals(CurrentTastyVersion, expectedVersion) + } +} diff --git a/tests/bench/inductive-implicits.scala b/tests/bench/inductive-implicits.scala index 58714214ce33..09f147caef3e 100644 --- a/tests/bench/inductive-implicits.scala +++ b/tests/bench/inductive-implicits.scala @@ -174,49 +174,49 @@ object Test extends App { Int :: Int :: // - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: -// - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: -// - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: - Int :: -// - Int :: - Int :: - Int :: - Int :: - Int :: // Int :: // Int :: // Int :: // Int :: // Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// // +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// // +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// // +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: +// Int :: // // // Int :: // Int :: diff --git a/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties index e8a1e246e8ad..db1723b08622 100644 --- a/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties +++ b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.7 +sbt.version=1.10.5 diff --git a/tests/cmdTest-sbt-tests/sourcepath-with-inline/project/build.properties b/tests/cmdTest-sbt-tests/sourcepath-with-inline/project/build.properties index e8a1e246e8ad..db1723b08622 100644 --- a/tests/cmdTest-sbt-tests/sourcepath-with-inline/project/build.properties +++ b/tests/cmdTest-sbt-tests/sourcepath-with-inline/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.7 +sbt.version=1.10.5 diff --git a/tests/coverage/pos/macro-late-suspend/Test.scala b/tests/coverage/pos/macro-late-suspend/Test.scala new file mode 100644 index 000000000000..bf008583c7d9 --- /dev/null +++ b/tests/coverage/pos/macro-late-suspend/Test.scala @@ -0,0 +1,13 @@ +package example + +sealed trait Test + +object Test { + case object Foo extends Test + + val visitorType = mkVisitorType[Test] + + trait Visitor[A] { + type V[a] = visitorType.Out[a] + } +} diff --git a/tests/coverage/pos/macro-late-suspend/UsesTest.scala b/tests/coverage/pos/macro-late-suspend/UsesTest.scala new file mode 100644 index 000000000000..803e93c328c9 --- /dev/null +++ b/tests/coverage/pos/macro-late-suspend/UsesTest.scala @@ -0,0 +1,3 @@ +package example + +val _ = Test.Foo diff --git a/tests/coverage/pos/macro-late-suspend/VisitorMacros.scala b/tests/coverage/pos/macro-late-suspend/VisitorMacros.scala new file mode 100644 index 000000000000..49ada0ff2180 --- /dev/null +++ b/tests/coverage/pos/macro-late-suspend/VisitorMacros.scala @@ -0,0 +1,12 @@ +package example + +import scala.quoted.* + +private def mkVisitorTypeImpl[T: Type](using q: Quotes): Expr[VisitorType[T]] = + '{new VisitorType[T]{}} + +transparent inline def mkVisitorType[T]: VisitorType[T] = ${ mkVisitorTypeImpl[T] } + +trait VisitorType[T] { + type Out[A] +} diff --git a/tests/coverage/pos/macro-late-suspend/test.scoverage.check b/tests/coverage/pos/macro-late-suspend/test.scoverage.check new file mode 100644 index 000000000000..f962705ed2ce --- /dev/null +++ b/tests/coverage/pos/macro-late-suspend/test.scoverage.check @@ -0,0 +1,88 @@ +# 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 +# ------------------------------------------ +1 +macro-late-suspend/VisitorMacros.scala +example +VisitorMacros$package +Object +example.VisitorMacros$package +mkVisitorTypeImpl +124 +144 +6 +unpickleExprV2 +Apply +false +0 +false +new VisitorType[T]{} + +2 +macro-late-suspend/VisitorMacros.scala +example +VisitorMacros$package +Object +example.VisitorMacros$package +mkVisitorTypeImpl +40 +69 +5 +mkVisitorTypeImpl +DefDef +false +0 +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 +Object +example.UsesTest$package + +22 +22 +3 + +Literal +true +0 +false + + diff --git a/tests/coverage/run/i16940/i16940.check b/tests/coverage/run/i16940/i16940.check new file mode 100644 index 000000000000..0cfbf08886fc --- /dev/null +++ b/tests/coverage/run/i16940/i16940.check @@ -0,0 +1 @@ +2 diff --git a/tests/coverage/run/i16940/i16940.scoverage.check b/tests/coverage/run/i16940/test.scoverage.check similarity index 100% rename from tests/coverage/run/i16940/i16940.scoverage.check rename to tests/coverage/run/i16940/test.scoverage.check diff --git a/tests/coverage/run/i18233-min/i182233-min.check b/tests/coverage/run/i18233-min/i182233-min.check new file mode 100644 index 000000000000..a4bb477e9e1f --- /dev/null +++ b/tests/coverage/run/i18233-min/i182233-min.check @@ -0,0 +1,2 @@ +List() +List(abc, def) diff --git a/tests/coverage/run/i18233-min/i18233-min.scoverage.check b/tests/coverage/run/i18233-min/test.scoverage.check similarity index 100% rename from tests/coverage/run/i18233-min/i18233-min.scoverage.check rename to tests/coverage/run/i18233-min/test.scoverage.check diff --git a/tests/coverage/run/i18233/i18233.check b/tests/coverage/run/i18233/i18233.check new file mode 100644 index 000000000000..a68818270123 --- /dev/null +++ b/tests/coverage/run/i18233/i18233.check @@ -0,0 +1 @@ +Baz diff --git a/tests/coverage/run/i18233/i18233.scoverage.check b/tests/coverage/run/i18233/test.scoverage.check similarity index 100% rename from tests/coverage/run/i18233/i18233.scoverage.check rename to tests/coverage/run/i18233/test.scoverage.check diff --git a/tests/coverage/run/java-methods/test.check b/tests/coverage/run/java-methods/JavaObject.check similarity index 100% rename from tests/coverage/run/java-methods/test.check rename to tests/coverage/run/java-methods/JavaObject.check diff --git a/tests/coverage/run/macro-suspend/Macro.check b/tests/coverage/run/macro-suspend/Macro.check new file mode 100644 index 000000000000..80a62d1af279 --- /dev/null +++ b/tests/coverage/run/macro-suspend/Macro.check @@ -0,0 +1 @@ +>>> hello <<< diff --git a/tests/coverage/run/macro-suspend/Macro.scala b/tests/coverage/run/macro-suspend/Macro.scala new file mode 100644 index 000000000000..dffe91a9a9b1 --- /dev/null +++ b/tests/coverage/run/macro-suspend/Macro.scala @@ -0,0 +1,8 @@ +import scala.quoted.{Expr, Quotes} + +object Macro: + inline def decorate(inline s: String): String = ${ decorateQuotes('s) } + def decorateQuotes(s: Expr[String])(using Quotes): Expr[String] = '{ ">>> " + $s + " <<<" } + +object Greeting: + def greet() = "hello" diff --git a/tests/coverage/run/macro-suspend/Test.scala b/tests/coverage/run/macro-suspend/Test.scala new file mode 100644 index 000000000000..09cf1c36526c --- /dev/null +++ b/tests/coverage/run/macro-suspend/Test.scala @@ -0,0 +1,4 @@ +object Test: + def main(args: Array[String]): Unit = + println(Macro.decorate(Greeting.greet())) + diff --git a/tests/coverage/run/macro-suspend/test.scoverage.check b/tests/coverage/run/macro-suspend/test.scoverage.check new file mode 100644 index 000000000000..759897eb7747 --- /dev/null +++ b/tests/coverage/run/macro-suspend/test.scoverage.check @@ -0,0 +1,173 @@ +# 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 +macro-suspend/Macro.scala + +Macro +Object +.Macro +decorateQuotes +195 +215 +5 +unpickleExprV2 +Apply +false +0 +false +">>> " + $s + " <<<" + +1 +macro-suspend/Macro.scala + +Macro +Object +.Macro +$anonfun +205 +206 +5 +apply +Apply +false +0 +false +s + +2 +macro-suspend/Macro.scala + +Macro +Object +.Macro +decorateQuotes +126 +144 +5 +decorateQuotes +DefDef +false +0 +false +def decorateQuotes + +3 +macro-suspend/Macro.scala + +Greeting +Object +.Greeting +greet +238 +247 +8 +greet +DefDef +false +0 +false +def greet + +4 +macro-suspend/Test.scala + +Test +Object +.Test +main +57 +98 +3 +println +Apply +false +0 +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 + +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 +main +DefDef +false +0 +false +def main + diff --git a/tests/coverage/run/varargs/JavaVarargs_1.check b/tests/coverage/run/varargs/JavaVarargs_1.check new file mode 100644 index 000000000000..24f879b660ff --- /dev/null +++ b/tests/coverage/run/varargs/JavaVarargs_1.check @@ -0,0 +1,3 @@ +first0 +first0 +first3 diff --git a/tests/coverage/run/varargs/test_1.check b/tests/coverage/run/varargs/test.check similarity index 100% rename from tests/coverage/run/varargs/test_1.check rename to tests/coverage/run/varargs/test.check diff --git a/tests/coverage/run/varargs/test_1.scoverage.check b/tests/coverage/run/varargs/test.scoverage.check similarity index 100% rename from tests/coverage/run/varargs/test_1.scoverage.check rename to tests/coverage/run/varargs/test.scoverage.check diff --git a/tests/explicit-nulls/neg/i21380b.scala b/tests/explicit-nulls/neg/i21380b.scala index 83e23053547c..e4d0caa9e32f 100644 --- a/tests/explicit-nulls/neg/i21380b.scala +++ b/tests/explicit-nulls/neg/i21380b.scala @@ -18,4 +18,22 @@ def test3(i: Int) = i match case 1 if x != null => () case _ => x = " " + x.trim() // ok + +def test4(i: Int) = + var x: String | Null = null + var y: String | Null = null + i match + case 1 => x = "1" + case _ => y = " " + x.trim() // error + +def test5(i: Int): String = + var x: String | Null = null + var y: String | Null = null + i match + case 1 => x = "1" + case _ => + y = " " + return y x.trim() // ok \ No newline at end of file diff --git a/tests/explicit-nulls/neg/i21380c.scala b/tests/explicit-nulls/neg/i21380c.scala index f86a5638e4c8..9b7a721fbdf0 100644 --- a/tests/explicit-nulls/neg/i21380c.scala +++ b/tests/explicit-nulls/neg/i21380c.scala @@ -32,9 +32,9 @@ def test4: Int = case npe: NullPointerException => x = "" case _ => x = "" x.length // error - // Although the catch block here is exhaustive, - // it is possible that the exception is thrown and not caught. - // Therefore, the code after the try block can only rely on the retracted info. + // Although the catch block here is exhaustive, it is possible to have non-exhaustive cases, + // and some exceptions are thrown and not caught. Therefore, the code in the finalizer and + // after the try block can only rely on the retracted info from the cases' body. def test5: Int = var x: String | Null = null diff --git a/tests/explicit-nulls/neg/i21619.scala b/tests/explicit-nulls/neg/i21619.scala new file mode 100644 index 000000000000..d7af27e3fe64 --- /dev/null +++ b/tests/explicit-nulls/neg/i21619.scala @@ -0,0 +1,92 @@ +def test1: String = + var x: String | Null = null + x = "" + var i: Int = 1 + try + i match + case _ => + x = null + throw new Exception() + x = "" + catch + case e: Exception => + x.replace("", "") // error + +def test2: String = + var x: String | Null = null + x = "" + var i: Int = 1 + try + i match + case _ => + x = null + throw new Exception() + x = "" + catch + case e: Exception => + x = "e" + x.replace("", "") // error + +def test3: String = + var x: String | Null = null + x = "" + var i: Int = 1 + try + i match + case _ => + x = null + throw new Exception() + x = "" + catch + case e: Exception => + finally + x = "f" + x.replace("", "") // ok + +def test4: String = + var x: String | Null = null + x = "" + var i: Int = 1 + try + try + if i == 1 then + x = null + throw new Exception() + else + x = "" + catch + case _ => + x = "" + catch + case _ => + x.replace("", "") // error + +def test5: Unit = + var x: String | Null = null + var y: String | Null = null + x = "" + y = "" + var i: Int = 1 + try + i match + case _ => + x = null + throw new Exception() + x = "" + catch + case _ => + val z1: String = x.replace("", "") // error + val z2: String = y.replace("", "") + +def test6 = { + var x: String | Null = "" + var y: String = "" + x = "" + y = if (false) x else 1 match { + case _ => { + x = null + y + } + } + x.replace("", "") // error +} \ No newline at end of file diff --git a/tests/explicit-nulls/pos/after-termination.scala b/tests/explicit-nulls/pos/after-termination.scala new file mode 100644 index 000000000000..00a57e371281 --- /dev/null +++ b/tests/explicit-nulls/pos/after-termination.scala @@ -0,0 +1,17 @@ +class C(val x: Int, val next: C | Null) + +def test1(x: String | Null, c: C | Null): Int = + return 0 + // We know that the following code is unreachable, + // so we can treat `x`, `c`, and any variable/path non-nullable. + x.length + c.next.x + +def test2(x: String | Null, c: C | Null): Int = + throw new Exception() + x.length + c.next.x + +def fail(): Nothing = ??? + +def test3(x: String | Null, c: C | Null): Int = + fail() + x.length + c.next.x diff --git a/tests/explicit-nulls/unsafe-common/unsafe-overload.scala b/tests/explicit-nulls/unsafe-common/unsafe-overload.scala index e7e551f1bda1..21af320806d8 100644 --- a/tests/explicit-nulls/unsafe-common/unsafe-overload.scala +++ b/tests/explicit-nulls/unsafe-common/unsafe-overload.scala @@ -16,8 +16,8 @@ class S { val o: O = ??? locally { - def h1(hh: String => String) = ??? - def h2(hh: Array[String] => Array[String]) = ??? + def h1(hh: String => String): Unit = ??? + def h2(hh: Array[String] => Array[String]): Unit = ??? def f1(x: String | Null): String | Null = ??? def f2(x: Array[String | Null]): Array[String | Null] = ??? @@ -29,10 +29,10 @@ class S { } locally { - def h1(hh: String | Null => String | Null) = ??? - def h2(hh: Array[String | Null] => Array[String | Null]) = ??? + def h1(hh: String | Null => String | Null): Unit = ??? + def h2(hh: Array[String | Null] => Array[String | Null]): Unit = ??? def g1(x: String): String = ??? - def g2(x: Array[String]): Array[String] = ??? + def g2(x: Array[String]): Array[String] = ??? h1(g1) // error h1(o.g) // error @@ -51,7 +51,7 @@ class S { locally { def g1(x: String): String = ??? - def g2(x: Array[String]): Array[String] = ??? + def g2(x: Array[String]): Array[String] = ??? o.i(g1) // error o.i(g2) // error diff --git a/tests/init/pos/byname.scala b/tests/init/pos/byname.scala new file mode 100644 index 000000000000..fdfbd101cc93 --- /dev/null +++ b/tests/init/pos/byname.scala @@ -0,0 +1,17 @@ +trait T: + def bar(i: => Int): Int + +class A extends T: + override def bar(i: => Int): Int = i + 1 + +class B extends T: + override def bar(i: => Int): Int = i + 2 + +object A: + val a: T = if ??? then new A else new B + def foo(b: List[Int]) = a.bar(b match { + case x :: xs => 1 + case Nil => 0 + }) + + val f = foo(Nil) diff --git a/tests/neg-custom-args/captures/bad-uses-2.scala b/tests/neg-custom-args/captures/bad-uses-2.scala new file mode 100644 index 000000000000..8dd121b2b134 --- /dev/null +++ b/tests/neg-custom-args/captures/bad-uses-2.scala @@ -0,0 +1,7 @@ +import caps.use +class Test: + @use def F = ??? // error + @use val x = ??? // error + @use type T // error + def foo[@use T](@use c: T): Unit = ??? // OK + diff --git a/tests/neg-custom-args/captures/bad-uses.scala b/tests/neg-custom-args/captures/bad-uses.scala new file mode 100644 index 000000000000..c21976ebb3cf --- /dev/null +++ b/tests/neg-custom-args/captures/bad-uses.scala @@ -0,0 +1,3 @@ +import caps.use +class Test: + val bar = (@use c: Test) => () // error diff --git a/tests/neg-custom-args/captures/box-adapt-cases.scala b/tests/neg-custom-args/captures/box-adapt-cases.scala index 681d699842ed..d9ec0f80a548 100644 --- a/tests/neg-custom-args/captures/box-adapt-cases.scala +++ b/tests/neg-custom-args/captures/box-adapt-cases.scala @@ -4,7 +4,7 @@ def test1(): Unit = { type Id[X] = [T] -> (op: X => T) -> T val x: Id[Cap^] = ??? - x(cap => cap.use()) // error, OK under sealed + x(cap => cap.use()) } def test2(io: Cap^): Unit = { diff --git a/tests/neg-custom-args/captures/byname.check b/tests/neg-custom-args/captures/byname.check index c9530f6aad50..1c113591922d 100644 --- a/tests/neg-custom-args/captures/byname.check +++ b/tests/neg-custom-args/captures/byname.check @@ -1,3 +1,10 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:10:6 ---------------------------------------- +10 | h(f2()) // error + | ^^^^ + | Found: (x$0: Int) ->{cap1} Int + | Required: (x$0: Int) ->? Int + | + | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/byname.scala:19:5 ------------------------------------------------------------- 19 | h(g()) // error | ^^^ @@ -8,22 +15,3 @@ | ^^^ | reference (cap2 : Cap^) is not included in the allowed capture set {cap1} | of an enclosing function literal with expected type () ->{cap1} I --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:4:2 ----------------------------------------- - 4 | def f() = if cap1 == cap1 then g else g // error - | ^ - | Found: ((x$0: Int) ->{cap2} Int)^{} - | Required: Int -> Int - | - | Note that the expected type Int ->{} Int - | is the previously inferred result type of method test - | which is also the type seen in separately compiled sources. - | The new inferred type ((x$0: Int) ->{cap2} Int)^{} - | must conform to this type. - 5 | def g(x: Int) = if cap2 == cap2 then 1 else x - 6 | def g2(x: Int) = if cap1 == cap1 then 1 else x - 7 | def f2() = if cap1 == cap1 then g2 else g2 - 8 | def h(ff: => Int ->{cap2} Int) = ff - 9 | h(f()) -10 | h(f2()) - | - | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/byname.scala b/tests/neg-custom-args/captures/byname.scala index 75ad527dbd2d..dd8fcf1b8818 100644 --- a/tests/neg-custom-args/captures/byname.scala +++ b/tests/neg-custom-args/captures/byname.scala @@ -1,13 +1,13 @@ class Cap extends caps.Capability def test(cap1: Cap, cap2: Cap) = - def f() = if cap1 == cap1 then g else g // error + def f() = if cap1 == cap1 then g else g def g(x: Int) = if cap2 == cap2 then 1 else x def g2(x: Int) = if cap1 == cap1 then 1 else x def f2() = if cap1 == cap1 then g2 else g2 def h(ff: => Int ->{cap2} Int) = ff h(f()) - h(f2()) + h(f2()) // error class I diff --git a/tests/neg-custom-args/captures/capset-bound.scala b/tests/neg-custom-args/captures/capset-bound.scala new file mode 100644 index 000000000000..c00f61240dea --- /dev/null +++ b/tests/neg-custom-args/captures/capset-bound.scala @@ -0,0 +1,18 @@ +import caps.* + +class IO + +case class File(io: IO^) + +def test(io1: IO^, io2: IO^) = + def f[C >: CapSet^{io1} <: CapSet^](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 diff --git a/tests/neg-custom-args/captures/capset-bound2.scala b/tests/neg-custom-args/captures/capset-bound2.scala new file mode 100644 index 000000000000..679606f0e43c --- /dev/null +++ b/tests/neg-custom-args/captures/capset-bound2.scala @@ -0,0 +1,13 @@ +import caps.* + +class IO + +def f[C^](io: IO^{C^}) = ??? + +def test = + f[CapSet](???) + f[CapSet^{}](???) + f[CapSet^](???) + f[Nothing](???) // error + f[String](???) // 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 new file mode 100644 index 000000000000..540216852a43 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members.scala @@ -0,0 +1,30 @@ +import caps.* + +trait Abstract[X^]: + type C >: X <: CapSet^ + // Don't test the return type using Unit, because it is a pure type. + def boom(): AnyRef^{C^} + +class Concrete extends Abstract[CapSet^{}]: + type C = CapSet^{} + // TODO: Why do we get error without the return type here? + def boom(): AnyRef = new Object + +class Concrete2 extends Abstract[CapSet^{}]: + type C = CapSet^{} + def boom(): AnyRef^ = new Object // error + +class Concrete3 extends Abstract[CapSet^{}]: + def boom(): AnyRef = new Object + +class Concrete4(a: AnyRef^) extends Abstract[CapSet^{a}]: + type C = CapSet // error + def boom(): AnyRef^{a} = a // error + +class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]: + type C = CapSet^{a} + def boom(): AnyRef^{b} = b // error + +class Concrete6(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]: + def boom(): AnyRef^{b} = b // error + \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capt-test.scala b/tests/neg-custom-args/captures/capt-test.scala index b202a14d0940..80ee1aba84e1 100644 --- a/tests/neg-custom-args/captures/capt-test.scala +++ b/tests/neg-custom-args/captures/capt-test.scala @@ -20,8 +20,8 @@ def handle[E <: Exception, R <: Top](op: (CT[E] @retains(caps.cap)) => R)(handl catch case ex: E => handler(ex) def test: Unit = - val b = handle[Exception, () => Nothing] { + val b = handle[Exception, () => Nothing] { // error (x: CanThrow[Exception]) => () => raise(new Exception)(using x) - } { // error + } { (ex: Exception) => ??? } diff --git a/tests/neg/capt-wf.scala b/tests/neg-custom-args/captures/capt-wf.scala similarity index 100% rename from tests/neg/capt-wf.scala rename to tests/neg-custom-args/captures/capt-wf.scala diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check index 3d0ed538b2e5..f63c55ca48c4 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -1,12 +1,12 @@ -- Error: tests/neg-custom-args/captures/capt1.scala:6:11 -------------------------------------------------------------- 6 | () => if x == null then y else y // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (x : C^) is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> C -- Error: tests/neg-custom-args/captures/capt1.scala:9:11 -------------------------------------------------------------- 9 | () => if x == null then y else y // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (x : C^) is not included in the allowed capture set {} | of an enclosing function literal with expected type Matchable -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:16:2 ----------------------------------------- 16 | def f(y: Int) = if x == null then y else y // error @@ -33,22 +33,18 @@ 29 | def m() = if x == null then y else y | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/capt1.scala:34:12 ------------------------------------------------------------- +-- Error: tests/neg-custom-args/captures/capt1.scala:34:16 ------------------------------------------------------------- 34 | val z2 = h[() -> Cap](() => x) // error // error - | ^^^^^^^^^^^^ - | Sealed type variable X cannot be instantiated to () -> box C^ since - | the part box C^ of that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method h - | leaking as part of its result. + | ^^^^^^^^^ + | 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`. -- Error: tests/neg-custom-args/captures/capt1.scala:34:30 ------------------------------------------------------------- 34 | val z2 = h[() -> Cap](() => x) // error // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> box C^ --- Error: tests/neg-custom-args/captures/capt1.scala:36:12 ------------------------------------------------------------- + | reference (x : C^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> box C^ +-- Error: tests/neg-custom-args/captures/capt1.scala:36:13 ------------------------------------------------------------- 36 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Sealed type variable X cannot be instantiated to box () ->{x} Cap since - | the part Cap of that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method h - | leaking as part of its result. + | ^^^^^^^^^^^^^^^^^^^^^^^ + | Type variable X of method h cannot be instantiated to box () ->{x} Cap since + | the part Cap of that type captures the root capability `cap`. diff --git a/tests/neg-custom-args/captures/capt1.scala b/tests/neg-custom-args/captures/capt1.scala index cad0bad4ba56..8da7e633ca51 100644 --- a/tests/neg-custom-args/captures/capt1.scala +++ b/tests/neg-custom-args/captures/capt1.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import annotation.retains class C def f(x: C @retains(caps.cap), y: C): () -> C = diff --git a/tests/neg-custom-args/captures/capture-parameters.scala b/tests/neg-custom-args/captures/capture-parameters.scala new file mode 100644 index 000000000000..d59305ae0cb8 --- /dev/null +++ b/tests/neg-custom-args/captures/capture-parameters.scala @@ -0,0 +1,9 @@ +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 diff --git a/tests/neg-custom-args/captures/capture-poly.scala b/tests/neg-custom-args/captures/capture-poly.scala new file mode 100644 index 000000000000..88989b418726 --- /dev/null +++ b/tests/neg-custom-args/captures/capture-poly.scala @@ -0,0 +1,25 @@ +import caps.* + +trait Foo extends Capability + +trait CaptureSet: + type C >: CapSet <: CapSet^ + +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} + + 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 + + val z5: Foo^{x} = capturePoly2(X)(x) + val z6: Foo^{x} = capturePoly2(X)(y) // error \ No newline at end of file diff --git a/tests/neg/cc-ex-conformance.scala b/tests/neg-custom-args/captures/cc-ex-conformance.scala similarity index 100% rename from tests/neg/cc-ex-conformance.scala rename to tests/neg-custom-args/captures/cc-ex-conformance.scala diff --git a/tests/neg/cc-poly-1.check b/tests/neg-custom-args/captures/cc-poly-1.check similarity index 60% rename from tests/neg/cc-poly-1.check rename to tests/neg-custom-args/captures/cc-poly-1.check index abb507078bf4..ea486f55a61f 100644 --- a/tests/neg/cc-poly-1.check +++ b/tests/neg-custom-args/captures/cc-poly-1.check @@ -1,10 +1,10 @@ --- [E057] Type Mismatch Error: tests/neg/cc-poly-1.scala:12:6 ---------------------------------------------------------- +-- [E057] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-1.scala:12:6 ------------------------------------- 12 | f[Any](D()) // error | ^ | Type argument Any does not conform to upper bound caps.CapSet^ | | longer explanation available when compiling with `-explain` --- [E057] Type Mismatch Error: tests/neg/cc-poly-1.scala:13:6 ---------------------------------------------------------- +-- [E057] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-1.scala:13:6 ------------------------------------- 13 | f[String](D()) // error | ^ | Type argument String does not conform to upper bound caps.CapSet^ diff --git a/tests/neg/cc-poly-1.scala b/tests/neg-custom-args/captures/cc-poly-1.scala similarity index 100% rename from tests/neg/cc-poly-1.scala rename to tests/neg-custom-args/captures/cc-poly-1.scala diff --git a/tests/neg-custom-args/captures/cc-poly-2.check b/tests/neg-custom-args/captures/cc-poly-2.check new file mode 100644 index 000000000000..90568de385b3 --- /dev/null +++ b/tests/neg-custom-args/captures/cc-poly-2.check @@ -0,0 +1,14 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-2.scala:14:19 ------------------------------------ +14 | f[CapSet^{c1}](d) // error + | ^ + | Found: (d : Test.D^) + | Required: Test.D^{c1} + | + | 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 + | ^ + | Found: (x : Test.D^{d}) + | Required: Test.D^{c1} + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/cc-poly-2.scala b/tests/neg-custom-args/captures/cc-poly-2.scala similarity index 85% rename from tests/neg/cc-poly-2.scala rename to tests/neg-custom-args/captures/cc-poly-2.scala index c5e5df6540da..c9249ba59437 100644 --- a/tests/neg/cc-poly-2.scala +++ b/tests/neg-custom-args/captures/cc-poly-2.scala @@ -10,7 +10,7 @@ object Test: def test(c1: C, c2: C) = val d: D^ = D() - f[Nothing](d) // error + // f[Nothing](d) // already ruled out at typer f[CapSet^{c1}](d) // error val x = f(d) val _: D^{c1} = x // error diff --git a/tests/neg-custom-args/captures/cc-this5.check b/tests/neg-custom-args/captures/cc-this5.check index 8affe7005e2e..21b5b36e0574 100644 --- a/tests/neg-custom-args/captures/cc-this5.check +++ b/tests/neg-custom-args/captures/cc-this5.check @@ -1,7 +1,7 @@ -- Error: tests/neg-custom-args/captures/cc-this5.scala:16:20 ---------------------------------------------------------- 16 | def f = println(c) // error | ^ - | (c : Cap^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (c : Cap^) is not included in the allowed capture set {} | of the enclosing class A -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-this5.scala:21:15 ------------------------------------- 21 | val x: A = this // error diff --git a/tests/pos-custom-args/captures/curried-closures.scala b/tests/neg-custom-args/captures/curried-closures.scala similarity index 91% rename from tests/pos-custom-args/captures/curried-closures.scala rename to tests/neg-custom-args/captures/curried-closures.scala index 262dd4b66b92..426f0df85022 100644 --- a/tests/pos-custom-args/captures/curried-closures.scala +++ b/tests/neg-custom-args/captures/curried-closures.scala @@ -30,5 +30,5 @@ def Test4(g: OutputStream^) = val _: (f: OutputStream^) ->{} Int ->{f} Unit = later val later2 = () => (y: Int) => xs.foreach(x => g.write(x + y)) - val _: () ->{} Int ->{g} Unit = later2 + val _: () ->{} Int ->{g} Unit = later2 // error, inferred type is () ->{later2} Int ->{g} Unit diff --git a/tests/neg-custom-args/captures/dcs-tvar.check b/tests/neg-custom-args/captures/dcs-tvar.check new file mode 100644 index 000000000000..d3caa720e88a --- /dev/null +++ b/tests/neg-custom-args/captures/dcs-tvar.check @@ -0,0 +1,10 @@ +-- Error: tests/neg-custom-args/captures/dcs-tvar.scala:6:15 ----------------------------------------------------------- +6 | () => runOps(xs) // error + | ^^ + | reference xs* is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Unit +-- Error: tests/neg-custom-args/captures/dcs-tvar.scala:9:15 ----------------------------------------------------------- +9 | () => runOps(xs) // error + | ^^ + | reference xs* is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Unit diff --git a/tests/neg-custom-args/captures/dcs-tvar.scala b/tests/neg-custom-args/captures/dcs-tvar.scala new file mode 100644 index 000000000000..381c08b4d351 --- /dev/null +++ b/tests/neg-custom-args/captures/dcs-tvar.scala @@ -0,0 +1,9 @@ +import caps.use + +def runOps(@use xs: List[() => Unit]): Unit = ??? + +def f[T <: List[() => Unit]](xs: T): () -> Unit = + () => runOps(xs) // error + +def g[T <: List[U], U <: () => Unit](xs: T): () -> Unit = + () => runOps(xs) // error diff --git a/tests/neg-custom-args/captures/delayedRunops.check b/tests/neg-custom-args/captures/delayedRunops.check new file mode 100644 index 000000000000..68da4672acf5 --- /dev/null +++ b/tests/neg-custom-args/captures/delayedRunops.check @@ -0,0 +1,14 @@ +-- Error: tests/neg-custom-args/captures/delayedRunops.scala:16:13 ----------------------------------------------------- +16 | runOps(ops1) // error + | ^^^^ + | reference ops* is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Unit +-- Error: tests/neg-custom-args/captures/delayedRunops.scala:22:13 ----------------------------------------------------- +22 | runOps(ops1) // error + | ^^^^ + | Local reach capability ops1* leaks into capture scope of enclosing function +-- Error: tests/neg-custom-args/captures/delayedRunops.scala:28:13 ----------------------------------------------------- +28 | runOps(ops1) // error + | ^^^^ + | reference ops* is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Unit diff --git a/tests/neg-custom-args/captures/delayedRunops.scala b/tests/neg-custom-args/captures/delayedRunops.scala new file mode 100644 index 000000000000..191118fa19c9 --- /dev/null +++ b/tests/neg-custom-args/captures/delayedRunops.scala @@ -0,0 +1,28 @@ +import language.experimental.captureChecking +import caps.use + + // ok + def runOps(@use ops: List[() => Unit]): Unit = + ops.foreach(op => op()) + + // ok + def delayedRunOps(@use ops: List[() => Unit]): () ->{ops*} Unit = // @use should not be necessary in the future + () => runOps(ops) + + // unsound: impure operation pretended pure + def delayedRunOps1(ops: List[() => Unit]): () ->{} Unit = + () => + val ops1 = ops + runOps(ops1) // error + + // unsound: impure operation pretended pure + def delayedRunOps2(ops: List[() => Unit]): () ->{} Unit = + () => + val ops1: List[() => Unit] = ops + runOps(ops1) // error + + // unsound: impure operation pretended pure + def delayedRunOps3(ops: List[() => Unit]): () ->{} Unit = + () => + val ops1: List[() ->{ops*} Unit] = ops + runOps(ops1) // error diff --git a/tests/neg-custom-args/captures/depfun-reach.check b/tests/neg-custom-args/captures/depfun-reach.check new file mode 100644 index 000000000000..c1d7d05dc8d6 --- /dev/null +++ b/tests/neg-custom-args/captures/depfun-reach.check @@ -0,0 +1,14 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/depfun-reach.scala:13:4 ---------------------------------- +13 | op // error + | ^^ + | Found: (xs: List[(X, box () ->{io} Unit)]) ->{op} List[box () ->{xs*} Unit] + | Required: (xs: List[(X, box () ->{io} Unit)]) => List[() -> Unit] + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/depfun-reach.scala:20:60 --------------------------------- +20 | val b: (xs: List[() ->{io} Unit]) => List[() ->{} Unit] = a // error + | ^ + | Found: (xs: List[box () ->{io} Unit]) ->{a} List[box () ->{xs*} Unit] + | Required: (xs: List[box () ->{io} Unit]) => List[() -> Unit] + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/depfun-reach.scala b/tests/neg-custom-args/captures/depfun-reach.scala new file mode 100644 index 000000000000..94b10f7dbcdb --- /dev/null +++ b/tests/neg-custom-args/captures/depfun-reach.scala @@ -0,0 +1,20 @@ +import language.experimental.captureChecking +import caps.cap + +def test(io: Object^, async: Object^) = + def compose(op: List[(() ->{cap} Unit, () ->{cap} Unit)]): List[() ->{op*} Unit] = + List(() => op.foreach((f,g) => { f(); g() })) + + def compose1(op: List[(() ->{async} Unit, () ->{io} Unit)]): List[() ->{op*} Unit] = + compose(op) + + def foo[X](op: (xs: List[(X, () ->{io} Unit)]) => List[() ->{xs*} Unit]) + : (xs: List[(X, () ->{io} Unit)]) => List[() ->{} Unit] = + op // error + + def boom(op: List[(() ->{async} Unit, () ->{io} Unit)]): List[() ->{} Unit] = + foo(compose1)(op) + +def test2(io: Object^) = + val a: (xs: List[() ->{io} Unit]) => List[() ->{xs*} Unit] = ??? + val b: (xs: List[() ->{io} Unit]) => List[() ->{} Unit] = a // error diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.check b/tests/neg-custom-args/captures/effect-swaps-explicit.check index 264dfa663d39..47559ab97568 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.check +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.check @@ -25,5 +25,5 @@ -- Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:68:15 --------------------------------------------- 68 | Result.make: //lbl ?=> // error, escaping label from Result | ^^^^^^^^^^^ - |local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9, contextual$9}, box E^?]]^): - | box Future[box T^?]^{fr, contextual$9, contextual$9} leaks into outer capture set of type parameter T of method make in object Result + |local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9}, box E^?]]^): + | box Future[box T^?]^{fr, contextual$9} leaks into outer capture set of type parameter T of method make in object Result diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.scala b/tests/neg-custom-args/captures/effect-swaps-explicit.scala index 7474e1711b34..e440271ccf88 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.scala +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + object boundary: final class Label[-T] // extends caps.Capability diff --git a/tests/neg-custom-args/captures/effect-swaps.check b/tests/neg-custom-args/captures/effect-swaps.check index ef5a95d333bf..b74c165fd6b6 100644 --- a/tests/neg-custom-args/captures/effect-swaps.check +++ b/tests/neg-custom-args/captures/effect-swaps.check @@ -22,3 +22,8 @@ 73 | fr.await.ok | | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/effect-swaps.scala:66:15 ------------------------------------------------------ +66 | Result.make: // error: local reference leaks + | ^^^^^^^^^^^ + |local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9}, box E^?]]^): + | box Future[box T^?]^{fr, contextual$9} leaks into outer capture set of type parameter T of method make in object Result diff --git a/tests/neg-custom-args/captures/effect-swaps.scala b/tests/neg-custom-args/captures/effect-swaps.scala index 4bafd6421af3..99c781b963c5 100644 --- a/tests/neg-custom-args/captures/effect-swaps.scala +++ b/tests/neg-custom-args/captures/effect-swaps.scala @@ -63,7 +63,7 @@ def test[T, E](using Async) = fr.await.ok def fail4[T, E](fr: Future[Result[T, E]]^) = - Result.make: // should be errorm but inders Result[Any, Any] + Result.make: // error: local reference leaks Future: fut ?=> fr.await.ok diff --git a/tests/neg-custom-args/captures/eta.check b/tests/neg-custom-args/captures/eta.check index 9850e54a7fdf..b7669e9b68ea 100644 --- a/tests/neg-custom-args/captures/eta.check +++ b/tests/neg-custom-args/captures/eta.check @@ -8,5 +8,5 @@ -- Error: tests/neg-custom-args/captures/eta.scala:6:20 ---------------------------------------------------------------- 6 | bar( () => f ) // error | ^ - | (f : Proc^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (f : Proc^) is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> box () ->? Unit diff --git a/tests/neg-custom-args/captures/exception-definitions.check b/tests/neg-custom-args/captures/exception-definitions.check index 7f915ebd9833..3f2b15f312b9 100644 --- a/tests/neg-custom-args/captures/exception-definitions.check +++ b/tests/neg-custom-args/captures/exception-definitions.check @@ -5,7 +5,7 @@ -- Error: tests/neg-custom-args/captures/exception-definitions.scala:7:12 ---------------------------------------------- 7 | val x = c // error | ^ - |(c : Any^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Err2 + | reference (c : Any^) is not included in the allowed capture set {} of the self type of class Err2 -- Error: tests/neg-custom-args/captures/exception-definitions.scala:8:13 ---------------------------------------------- 8 | class Err3(c: Any^) extends Exception // error | ^ diff --git a/tests/neg/existential-mapping.check b/tests/neg-custom-args/captures/existential-mapping.check similarity index 70% rename from tests/neg/existential-mapping.check rename to tests/neg-custom-args/captures/existential-mapping.check index edfce67f6eef..cd71337868e1 100644 --- a/tests/neg/existential-mapping.check +++ b/tests/neg-custom-args/captures/existential-mapping.check @@ -1,85 +1,85 @@ --- Error: tests/neg/existential-mapping.scala:44:13 -------------------------------------------------------------------- +-- Error: tests/neg-custom-args/captures/existential-mapping.scala:44:13 ----------------------------------------------- 44 | val z1: A^ => Array[C^] = ??? // error | ^^^^^^^^^^^^^^^ | Array[box C^] captures the root capability `cap` in invariant position --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:9:25 ------------------------------------------------ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:9:25 --------------------------- 9 | val _: (x: C^) -> C = x1 // error | ^^ | Found: (x1 : (x: C^) -> (ex$3: caps.Exists) -> C^{ex$3}) | Required: (x: C^) -> C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:12:20 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:12:20 -------------------------- 12 | val _: C^ -> C = x2 // error | ^^ | Found: (x2 : C^ -> (ex$7: caps.Exists) -> C^{ex$7}) | Required: C^ -> C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:15:30 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:15:30 -------------------------- 15 | val _: A^ -> (x: C^) -> C = x3 // error | ^^ | Found: (x3 : A^ -> (x: C^) -> (ex$11: caps.Exists) -> C^{ex$11}) | Required: A^ -> (x: C^) -> C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:18:25 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:18:25 -------------------------- 18 | val _: A^ -> C^ -> C = x4 // error | ^^ | Found: (x4 : A^ -> C^ -> (ex$19: caps.Exists) -> C^{ex$19}) | Required: A^ -> C^ -> C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:21:30 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:21:30 -------------------------- 21 | val _: A^ -> (x: C^) -> C = x5 // error | ^^ | Found: (x5 : A^ -> (ex$27: caps.Exists) -> Fun[C^{ex$27}]) | Required: A^ -> (x: C^) -> C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:24:30 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:24:30 -------------------------- 24 | val _: A^ -> (x: C^) => C = x6 // error | ^^ | Found: (x6 : A^ -> (ex$33: caps.Exists) -> IFun[C^{ex$33}]) | Required: A^ -> (ex$36: caps.Exists) -> (x: C^) ->{ex$36} C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:27:25 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:27:25 -------------------------- 27 | val _: (x: C^) => C = y1 // error | ^^ | Found: (y1 : (x: C^) => (ex$38: caps.Exists) -> C^{ex$38}) | Required: (x: C^) => C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:30:20 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:30:20 -------------------------- 30 | val _: C^ => C = y2 // error | ^^ | Found: (y2 : C^ => (ex$42: caps.Exists) -> C^{ex$42}) | Required: C^ => C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:33:30 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:33:30 -------------------------- 33 | val _: A^ => (x: C^) => C = y3 // error | ^^ | Found: (y3 : A^ => (ex$47: caps.Exists) -> (x: C^) ->{ex$47} (ex$46: caps.Exists) -> C^{ex$46}) | Required: A^ => (ex$50: caps.Exists) -> (x: C^) ->{ex$50} C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:36:25 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:36:25 -------------------------- 36 | val _: A^ => C^ => C = y4 // error | ^^ | Found: (y4 : A^ => (ex$53: caps.Exists) -> C^ ->{ex$53} (ex$52: caps.Exists) -> C^{ex$52}) | Required: A^ => (ex$56: caps.Exists) -> C^ ->{ex$56} C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:39:30 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:39:30 -------------------------- 39 | val _: A^ => (x: C^) -> C = y5 // error | ^^ | Found: (y5 : A^ => (ex$58: caps.Exists) -> Fun[C^{ex$58}]) | Required: A^ => (x: C^) -> C | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:42:30 ----------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:42:30 -------------------------- 42 | val _: A^ => (x: C^) => C = y6 // error | ^^ | Found: (y6 : A^ => (ex$64: caps.Exists) -> IFun[C^{ex$64}]) diff --git a/tests/neg/existential-mapping.scala b/tests/neg-custom-args/captures/existential-mapping.scala similarity index 100% rename from tests/neg/existential-mapping.scala rename to tests/neg-custom-args/captures/existential-mapping.scala diff --git a/tests/neg-custom-args/captures/filevar.scala b/tests/neg-custom-args/captures/filevar.scala index e54f161ef124..5b83f1d29380 100644 --- a/tests/neg-custom-args/captures/filevar.scala +++ b/tests/neg-custom-args/captures/filevar.scala @@ -5,8 +5,8 @@ class File: def write(x: String): Unit = ??? class Service: - var file: File^ = uninitialized // OK, was error under sealed - def log = file.write("log") // error, was OK under sealed + var file: File^ = uninitialized // error, was OK under unsealed + 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) diff --git a/tests/pos/gears-probem-1.scala b/tests/neg-custom-args/captures/gears-problem-1.scala similarity index 79% rename from tests/pos/gears-probem-1.scala rename to tests/neg-custom-args/captures/gears-problem-1.scala index ab71616b72fc..515aecd468f6 100644 --- a/tests/pos/gears-probem-1.scala +++ b/tests/neg-custom-args/captures/gears-problem-1.scala @@ -1,5 +1,5 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use trait Future[+T]: def await: T @@ -17,9 +17,9 @@ 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](@unbox fs: Seq[Future[T]^]) +extension [T](@use fs: Seq[Future[T]^]) def awaitAll = val collector//: Collector[T]{val futures: Seq[Future[T]^{fs*}]} = Collector(fs) // val ch = collector.results // also errors - val fut: Future[T]^{fs*} = collector.results.read().get // found ...^{caps.cap} \ No newline at end of file + val fut: Future[T]^{fs*} = collector.results.read().get // error diff --git a/tests/neg-custom-args/captures/gears-problem.check b/tests/neg-custom-args/captures/gears-problem.check new file mode 100644 index 000000000000..eb37feb6d568 --- /dev/null +++ b/tests/neg-custom-args/captures/gears-problem.check @@ -0,0 +1,15 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/gears-problem.scala:19:62 -------------------------------- +19 | val fut: Future[T]^{fs*} = collector.results.read().right.get // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: Future[T]^{collector.futures*} + | Required: Future[T]^{fs*} + | + | longer explanation available when compiling with `-explain` +-- [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*} + | Required: Future[T]^{fs*} + | + | longer explanation available when compiling with `-explain` +there were 4 deprecation warnings; re-run with -deprecation for details diff --git a/tests/pos/gears-probem.scala b/tests/neg-custom-args/captures/gears-problem.scala similarity index 70% rename from tests/pos/gears-probem.scala rename to tests/neg-custom-args/captures/gears-problem.scala index 2e445c985de2..8dcdaabb2100 100644 --- a/tests/pos/gears-probem.scala +++ b/tests/neg-custom-args/captures/gears-problem.scala @@ -1,4 +1,5 @@ import language.experimental.captureChecking +import caps.use trait Future[+T]: def await: T @@ -10,9 +11,14 @@ class Collector[T](val futures: Seq[Future[T]^]): val results: Channel[Future[T]^{futures*}] = ??? end Collector -extension [T](fs: Seq[Future[T]^]) +extension [T](@use fs: Seq[Future[T]^]) def awaitAll = val collector: Collector[T]{val futures: Seq[Future[T]^{fs*}]} = Collector(fs) // val ch = collector.results // also errors - val fut: Future[T]^{fs*} = collector.results.read().right.get // found ...^{caps.cap} \ No newline at end of file + val fut: Future[T]^{fs*} = collector.results.read().right.get // error + + val ch = collector.results + val item = ch.read() + val r = item.right + val fut2: Future[T]^{fs*} = r.get // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/hk-param.scala b/tests/neg-custom-args/captures/hk-param.scala new file mode 100644 index 000000000000..bfd4f82a0697 --- /dev/null +++ b/tests/neg-custom-args/captures/hk-param.scala @@ -0,0 +1,16 @@ +/** Concrete collection type: View */ +trait View[+A] extends Itable[A], ILike[A, [X] =>> View[X]^]: // error + override def fromIterable[B](c: Itable[B]^): View[B]^{c} = ??? + +trait IPolyTransforms[+A, +C[A]] extends Any: + def fromIterable[B](coll: Itable[B]^): C[B] + +trait ILike[+A, +C[X] <: Itable[X]^] extends IPolyTransforms[A, C] + +/** Base trait for generic collections */ +trait Itable[+A] extends ItableOnce[A] with ILike[A, Itable^] // error + +/** Iterator can be used only once */ +trait ItableOnce[+A] { + def iterator: Iterator[A]^{this} +} diff --git a/tests/neg-custom-args/captures/i15749.scala b/tests/neg-custom-args/captures/i15749.scala index c5b59042085a..9a4e8b0b81ae 100644 --- a/tests/neg-custom-args/captures/i15749.scala +++ b/tests/neg-custom-args/captures/i15749.scala @@ -1,3 +1,5 @@ +//> using options -source 3.5 +// (to make sure we use the unsealed policy) class Unit object unit extends Unit diff --git a/tests/neg-custom-args/captures/i15749a.scala b/tests/neg-custom-args/captures/i15749a.scala index 57fca27fae66..d3c1fce13322 100644 --- a/tests/neg-custom-args/captures/i15749a.scala +++ b/tests/neg-custom-args/captures/i15749a.scala @@ -1,5 +1,5 @@ import caps.cap -import caps.unbox +import caps.use class Unit object u extends Unit @@ -18,7 +18,7 @@ def test = def force[A](thunk: Unit ->{cap} A): A = thunk(u) - def forceWrapper[A](@unbox mx: Wrapper[Unit ->{cap} A]): Wrapper[A] = + def forceWrapper[A](@use mx: Wrapper[Unit ->{cap} A]): Wrapper[A] = // Γ ⊢ mx: Wrapper[□ {cap} Unit => A] // `force` should be typed as ∀(□ {cap} Unit -> A) A, but it can not strictMap[Unit ->{mx*} A, A](mx)(t => force[A](t)) // error // should work diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index 58582423b101..67685d5663b8 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -1,7 +1,7 @@ -- Error: tests/neg-custom-args/captures/i15772.scala:19:26 ------------------------------------------------------------ 19 | val c : C^{x} = new C(x) // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (x : C^) is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> Int -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:20:46 --------------------------------------- 20 | val boxed1 : ((C^) => Unit) -> Unit = box1(c) // error @@ -13,7 +13,7 @@ -- Error: tests/neg-custom-args/captures/i15772.scala:26:26 ------------------------------------------------------------ 26 | val c : C^{x} = new C(x) // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (x : C^) is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> Int -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:27:35 --------------------------------------- 27 | val boxed2 : Observe[C^] = box2(c) // error @@ -25,11 +25,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:34 --------------------------------------- 33 | val boxed2 : Observe[C]^ = box2(c) // error | ^ - | Found: C^ - | Required: box C{val arg: C^?}^ + | Found: box C^ + | Required: box C{val arg: C^?}^? | - | Note that C^ cannot be box-converted to box C{val arg: C^?}^ - | since at least one of their capture sets contains the root capability `cap` + | 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/i15772.scala:44:2 ---------------------------------------- diff --git a/tests/neg-custom-args/captures/i15922.scala b/tests/neg-custom-args/captures/i15922.scala index 89bf91493fcd..848a22fe5341 100644 --- a/tests/neg-custom-args/captures/i15922.scala +++ b/tests/neg-custom-args/captures/i15922.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to force sealed encapsulation checking) + + 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) diff --git a/tests/neg-custom-args/captures/i15923-cases.scala b/tests/neg-custom-args/captures/i15923-cases.scala deleted file mode 100644 index 83cfa554e8b9..000000000000 --- a/tests/neg-custom-args/captures/i15923-cases.scala +++ /dev/null @@ -1,7 +0,0 @@ -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 foo(x: Id[Cap^]) = { - x(_.use()) // error, was OK under sealed policy -} diff --git a/tests/neg-custom-args/captures/i16114.check b/tests/neg-custom-args/captures/i16114.check new file mode 100644 index 000000000000..745ccea1f905 --- /dev/null +++ b/tests/neg-custom-args/captures/i16114.check @@ -0,0 +1,45 @@ +-- 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 + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i16114.scala:20:8 ------------------------------------------------------------- +20 | fs // error (limitation) + | ^^ + | reference (fs : Cap^) is not included in the allowed capture set {io} + | of an enclosing function literal with expected type Unit ->{io} Unit +-- 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 + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i16114.scala:26:8 ------------------------------------------------------------- +26 | io // error (limitation) + | ^^ + | reference (io : Cap^) is not included in the allowed capture set {fs} + | of an enclosing function literal with expected type Unit ->{fs} Unit +-- 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 + | that type captures the root capability `cap`. +-- 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 + | that type captures the root capability `cap`. +-- 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 + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i16114.scala:40:8 ------------------------------------------------------------- +40 | io.use() // error + | ^^ + | reference (io : Cap^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type Unit -> Unit +-- Error: tests/neg-custom-args/captures/i16114.scala:41:8 ------------------------------------------------------------- +41 | io // error + | ^^ + | reference (io : Cap^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type Unit -> Unit diff --git a/tests/neg-custom-args/captures/i16114.scala b/tests/neg-custom-args/captures/i16114.scala index ec04fe9c9827..801ea3b11a3d 100644 --- a/tests/neg-custom-args/captures/i16114.scala +++ b/tests/neg-custom-args/captures/i16114.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + trait Cap { def use(): Int; def close(): Unit } def mkCap(): Cap^ = ??? diff --git a/tests/neg-custom-args/captures/i19330-alt2.scala b/tests/neg-custom-args/captures/i19330-alt2.scala index 86634b45dbe3..3e52e3c65634 100644 --- a/tests/neg-custom-args/captures/i19330-alt2.scala +++ b/tests/neg-custom-args/captures/i19330-alt2.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import language.experimental.captureChecking trait Logger diff --git a/tests/neg-custom-args/captures/i19330.scala b/tests/neg-custom-args/captures/i19330.scala index 5fbdc00db311..715b670860cd 100644 --- a/tests/neg-custom-args/captures/i19330.scala +++ b/tests/neg-custom-args/captures/i19330.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to force sealed encapsulation checking) + + import language.experimental.captureChecking trait Logger diff --git a/tests/neg/i19470.check b/tests/neg-custom-args/captures/i19470.check similarity index 62% rename from tests/neg/i19470.check rename to tests/neg-custom-args/captures/i19470.check index fdb336bef7e5..68eba72c92bd 100644 --- a/tests/neg/i19470.check +++ b/tests/neg-custom-args/captures/i19470.check @@ -1,4 +1,4 @@ --- [E007] Type Mismatch Error: tests/neg/i19470.scala:9:12 ------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i19470.scala:9:12 ---------------------------------------- 9 | List(foo(f())) // error | ^^^^^^^^ | Found: Inv[box IO^{f?}] diff --git a/tests/neg/i19470.scala b/tests/neg-custom-args/captures/i19470.scala similarity index 100% rename from tests/neg/i19470.scala rename to tests/neg-custom-args/captures/i19470.scala diff --git a/tests/neg/i20503.scala b/tests/neg-custom-args/captures/i20503.scala similarity index 89% rename from tests/neg/i20503.scala rename to tests/neg-custom-args/captures/i20503.scala index 3fb0573f6c2f..828b6ce71137 100644 --- a/tests/neg/i20503.scala +++ b/tests/neg-custom-args/captures/i20503.scala @@ -1,5 +1,5 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use class List[+A]: def head: A = ??? @@ -8,7 +8,7 @@ class List[+A]: def foreach[U](f: A => U): Unit = ??? def nonEmpty: Boolean = ??? -def runOps(@unbox ops: List[() => Unit]): Unit = +def runOps(@use ops: List[() => Unit]): Unit = // See i20156, due to limitation in expressiveness of current system, // we could map over the list of impure elements. OK with existentials. ops.foreach(op => op()) diff --git a/tests/neg-custom-args/captures/i21347.check b/tests/neg-custom-args/captures/i21347.check index c680a54d3efc..e1845d9778d5 100644 --- a/tests/neg-custom-args/captures/i21347.check +++ b/tests/neg-custom-args/captures/i21347.check @@ -1,15 +1,12 @@ -- Error: tests/neg-custom-args/captures/i21347.scala:4:15 ------------------------------------------------------------- 4 | ops.foreach: op => // error | ^ - | Local reach capability C leaks into capture scope of method runOps + | Capture set parameter C leaks into capture scope of method runOps. + | To allow this, the type C should be declared with a @use annotation 5 | op() --- Error: tests/neg-custom-args/captures/i21347.scala:8:14 ------------------------------------------------------------- -8 | () => runOps(f :: Nil) // error - | ^^^^^^^^^^^^^^^^ - | reference (caps.cap : caps.Capability) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> Unit -- Error: tests/neg-custom-args/captures/i21347.scala:11:15 ------------------------------------------------------------ 11 | ops.foreach: op => // error | ^ - | Local reach capability ops* leaks into capture scope of method runOpsAlt + | Local reach capability ops* leaks into capture scope of method runOpsAlt. + | To allow this, the parameter ops should be declared with a @use annotation 12 | op() diff --git a/tests/neg-custom-args/captures/i21347.scala b/tests/neg-custom-args/captures/i21347.scala index 41887be6a78a..54fe859caedd 100644 --- a/tests/neg-custom-args/captures/i21347.scala +++ b/tests/neg-custom-args/captures/i21347.scala @@ -5,7 +5,7 @@ def runOps[C^](ops: List[() ->{C^} Unit]): Unit = op() def boom(f: () => Unit): () -> Unit = - () => runOps(f :: Nil) // error + () => runOps(f :: Nil) // now ok def runOpsAlt(ops: List[() => Unit]): Unit = ops.foreach: op => // error diff --git a/tests/neg-custom-args/captures/i21401.check b/tests/neg-custom-args/captures/i21401.check index e204540358ce..679c451949bd 100644 --- a/tests/neg-custom-args/captures/i21401.check +++ b/tests/neg-custom-args/captures/i21401.check @@ -1,13 +1,28 @@ --- Error: tests/neg-custom-args/captures/i21401.scala:15:22 ------------------------------------------------------------ -15 | val a = usingIO[IO^](x => x) // error: The expression's type IO^ is not allowed to capture the root capability `cap` - | ^^^^^^^^^^^^^^^^^^^^ - | The expression's type box IO^ is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. --- Error: tests/neg-custom-args/captures/i21401.scala:16:70 ------------------------------------------------------------ -16 | val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error: The expression's type Res is not allowed to capture the root capability `cap` in its part box IO^ - | ^^^^^^^^^^^^^^^^^^^ - | The expression's type Res is not allowed to capture the root capability `cap` in its part box IO^. - | This usually means that a capability persists longer than its allowed lifetime. +-- 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 + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i21401.scala:15:18 ------------------------------------------------------------ +15 | val a = usingIO[IO^](x => x) // error + | ^^^ + | Type variable R of method usingIO cannot be instantiated to box IO^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i21401.scala:16:66 ------------------------------------------------------------ +16 | val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error + | ^^^ + | Type variable R of method usingIO cannot be instantiated to Res since + | the part box IO^ of that type captures the root capability `cap`. +-- 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 + | ^^^^^^^^^^ + | 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`. +-- Error: tests/neg-custom-args/captures/i21401.scala:17:52 ------------------------------------------------------------ +17 | val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error + | ^^^^^^^^^^^^^^^^^^^^^^^^ + |Type variable X of value leaked cannot be instantiated to Boxed[box IO^] -> (ex$18: caps.Exists) -> Boxed[box IO^{ex$18}] since + |the part box IO^{ex$18} of that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/i21401.scala:18:21 ------------------------------------------------------------ 18 | val y: IO^{x*} = x.unbox // error | ^^^^^^^ diff --git a/tests/neg-custom-args/captures/i21401.scala b/tests/neg-custom-args/captures/i21401.scala index 8284c601cd5f..0b5479376a0a 100644 --- a/tests/neg-custom-args/captures/i21401.scala +++ b/tests/neg-custom-args/captures/i21401.scala @@ -10,10 +10,10 @@ type Res = [R, X <: Boxed[IO^] -> R] -> (op: X) -> R def mkRes(x: IO^): Res = [R, X <: Boxed[IO^] -> R] => (op: X) => val op1: Boxed[IO^] -> R = op - op1(Boxed[IO^](x)) + op1(Boxed[IO^](x)) // error def test2() = - val a = usingIO[IO^](x => x) // error: The expression's type IO^ is not allowed to capture the root capability `cap` - val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error: The expression's type Res is not allowed to capture the root capability `cap` in its part box IO^ - val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) + val a = usingIO[IO^](x => x) // 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 y: IO^{x*} = x.unbox // error y.println("boom") diff --git a/tests/neg-custom-args/captures/i21442.check b/tests/neg-custom-args/captures/i21442.check index a3bbf65c5988..30becfea0215 100644 --- a/tests/neg-custom-args/captures/i21442.check +++ b/tests/neg-custom-args/captures/i21442.check @@ -1,7 +1,8 @@ -- Error: tests/neg-custom-args/captures/i21442.scala:9:13 ------------------------------------------------------------- 9 | val io = x.unbox // error: local reach capability {x*} leaks | ^^^^^^^ - | Local reach capability x* leaks into capture scope of method foo + | Local reach capability x* 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:17:14 ------------------------------------------------------------ 17 | val io = x1.unbox // error | ^^^^^^^^ diff --git a/tests/neg-custom-args/captures/i21614.check b/tests/neg-custom-args/captures/i21614.check index 14b468db4c8e..f4967253455f 100644 --- a/tests/neg-custom-args/captures/i21614.check +++ b/tests/neg-custom-args/captures/i21614.check @@ -1,17 +1,17 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:9:33 ---------------------------------------- -9 | files.map((f: F) => new Logger(f)) // error, Q: can we make this pass (see #19076)? - | ^ - | Found: (f : F^) - | Required: File^ - | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:12:12 --------------------------------------- -12 | files.map(new Logger(_)) // error, Q: can we improve the error message? +-- [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^ + | + | 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: Logger{val f: (_$1 : File^{files*})}^ - | Required: Logger{val f: File^?}^? + | Found: (_$1: box File^{files*}) ->{files*} (ex$16: caps.Exists) -> box Logger{val f: File^{_$1}}^{ex$16} + | Required: (_$1: box File^{files*}) => box Logger{val f: File^?}^? | - | Note that the universal capability `cap` - | cannot be included in capture set ? + | Note that the universal capability `cap` + | cannot be included in capture set ? | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21614.scala b/tests/neg-custom-args/captures/i21614.scala index a5ed25d818a5..f5bab90f543b 100644 --- a/tests/neg-custom-args/captures/i21614.scala +++ b/tests/neg-custom-args/captures/i21614.scala @@ -1,12 +1,15 @@ import language.experimental.captureChecking import caps.Capability -import caps.unbox +import caps.use + +trait List[+T]: + def map[U](f: T => U): List[U] trait File extends Capability class Logger(f: File^) extends Capability // <- will work if we remove the extends clause -def mkLoggers1[F <: File^](@unbox files: List[F]): List[Logger^] = +def mkLoggers1[F <: File^](@use files: List[F]): List[Logger^] = files.map((f: F) => new Logger(f)) // error, Q: can we make this pass (see #19076)? -def mkLoggers2(@unbox files: List[File^]): List[Logger^] = +def mkLoggers2(@use files: List[File^]): List[Logger^] = files.map(new Logger(_)) // error, Q: can we improve the error message? diff --git a/tests/neg-custom-args/captures/i21620.check b/tests/neg-custom-args/captures/i21620.check index 3a09ba978574..d5be3bab9e73 100644 --- a/tests/neg-custom-args/captures/i21620.check +++ b/tests/neg-custom-args/captures/i21620.check @@ -4,10 +4,23 @@ | A pure expression does nothing in statement position | | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg-custom-args/captures/i21620.scala:14:4 ------------------------------------ +14 | x + | ^ + | A pure expression does nothing in statement position + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21620.scala:9:31 ---------------------------------------- 9 | val _: () -> () ->{x} Unit = f // error | ^ - | Found: () ->{f} () ->{x} Unit + | Found: (f : () ->{x} () ->{x} Unit) | Required: () -> () ->{x} Unit | | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21620.scala:20:33 --------------------------------------- +20 | val _: () ->{} () ->{x} Unit = f // error, but could be OK + | ^ + | Found: (f : () ->{x} () ->{x} Unit) + | Required: () -> () ->{x} Unit + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21620.scala b/tests/neg-custom-args/captures/i21620.scala index a21a41a10863..3b7ba5a9fd5a 100644 --- a/tests/neg-custom-args/captures/i21620.scala +++ b/tests/neg-custom-args/captures/i21620.scala @@ -8,3 +8,14 @@ def test(x: C^) = () => foo() val _: () -> () ->{x} Unit = f // error () + +def test2(x: C^) = + def foo() = + x + () + val f = () => + // println() // uncomenting would give an error, but with + // a different way of handling curried functions should be OK + () => foo() + val _: () ->{} () ->{x} Unit = f // error, but could be OK + () diff --git a/tests/neg-custom-args/captures/i21868.scala b/tests/neg-custom-args/captures/i21868.scala new file mode 100644 index 000000000000..876b68ac90a4 --- /dev/null +++ b/tests/neg-custom-args/captures/i21868.scala @@ -0,0 +1,13 @@ +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 new file mode 100644 index 000000000000..70f4e9c9d59c --- /dev/null +++ b/tests/neg-custom-args/captures/i21868b.scala @@ -0,0 +1,49 @@ +import language.experimental.modularity +import caps.* + +class IO + +class File + +trait Abstract: + type C >: CapSet <: CapSet^ + def f(file: File^{C^}): Unit + +class Concrete1 extends Abstract: + type C = CapSet + def f(file: File) = () + +class Concrete2(io: IO^) extends Abstract: + type C = CapSet^{io} + def f(file: File^{io}) = () + +class Concrete3(io: IO^) extends Abstract: + type C = CapSet^{io} + def f(file: File) = () // error + +trait Abstract2(tracked val io: IO^): + type C >: CapSet <: CapSet^{io} + def f(file: File^{C^}): Unit + +class Concrete4(io: IO^) extends Abstract2(io): + type C = CapSet + def f(file: File) = () + +class Concrete5(io1: IO^, io2: IO^) extends Abstract2(io1): + type C = CapSet^{io2} // error + def f(file: File^{io2}) = () + +trait Abstract3[X^]: + type C >: CapSet <: X + def f(file: File^{C^}): Unit + +class Concrete6(io: IO^) extends Abstract3[CapSet^{io}]: + type C = CapSet + def f(file: File) = () + +class Concrete7(io1: IO^, io2: IO^) extends Abstract3[CapSet^{io1}]: + type C = CapSet^{io2} // error + def f(file: File^{io2}) = () + +class Concrete8(io1: IO^, io2: IO^) extends Abstract3[CapSet^{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 new file mode 100644 index 000000000000..8efa24426d01 --- /dev/null +++ b/tests/neg-custom-args/captures/i21920.check @@ -0,0 +1,10 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21920.scala:34:34 --------------------------------------- +34 | val cell: Cell[File] = File.open(f => Cell(Seq(f))) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: Cell[box File^{f, f²}]{val head: () ?->? IterableOnce[box File^{f, f²}]^?}^? + | Required: Cell[File] + | + | where: f is a reference to a value parameter + | f² is a reference to a value parameter + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21920.scala b/tests/neg-custom-args/captures/i21920.scala new file mode 100644 index 000000000000..7ea5a63969b1 --- /dev/null +++ b/tests/neg-custom-args/captures/i21920.scala @@ -0,0 +1,36 @@ +import language.experimental.captureChecking + +trait Iterator[+A] extends IterableOnce[A]: + self: Iterator[A]^ => + def next(): A + +trait IterableOnce[+A] extends Any: + def iterator: Iterator[A]^{this} + +final class Cell[A](head: => IterableOnce[A]^): + def headIterator: Iterator[A]^{this} = head.iterator + +class File private (): + private var closed = false + + def close() = closed = true + + def read() = + assert(!closed, "File closed") + 1 + +object File: + def open[T](f: File^ => T): T = + val file = File() + try + f(file) + finally + file.close() + +object Seq: + def apply[A](xs: A*): IterableOnce[A] = ??? + +@main def Main() = + val cell: Cell[File] = File.open(f => Cell(Seq(f))) // error + val file = cell.headIterator.next() + file.read() diff --git a/tests/neg-custom-args/captures/i22005.scala b/tests/neg-custom-args/captures/i22005.scala new file mode 100644 index 000000000000..a9dca999e42b --- /dev/null +++ b/tests/neg-custom-args/captures/i22005.scala @@ -0,0 +1,8 @@ +import caps.* + +class IO +class File(io: IO^) + +class Handler[C^]: + def f(file: File^): File^{C^} = file // error + def g(file: File^{C^}): File^ = file // ok diff --git a/tests/neg/language-imports.scala b/tests/neg-custom-args/captures/language-imports.scala similarity index 100% rename from tests/neg/language-imports.scala rename to tests/neg-custom-args/captures/language-imports.scala diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index f0fbd1a025b5..bc95a445f3f4 100644 --- a/tests/neg-custom-args/captures/lazylist.check +++ b/tests/neg-custom-args/captures/lazylist.check @@ -8,8 +8,8 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:35:29 ------------------------------------- 35 | val ref1c: LazyList[Int] = ref1 // error | ^^^^ - | Found: lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^?}^{ref1} - | Required: lazylists.LazyList[Int] + | Found: (ref1 : lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^?}^{cap1}) + | Required: lazylists.LazyList[Int] | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:37:36 ------------------------------------- @@ -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, ref2, ref1}) + | Found: (ref4 : lazylists.LazyList[Int]^{cap3, cap2, ref1, cap1}) | Required: lazylists.LazyList[Int]^{cap1, ref3} | | 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 4a8738118609..111719a81f07 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.check +++ b/tests/neg-custom-args/captures/lazylists-exceptions.check @@ -1,8 +1,9 @@ -- Error: tests/neg-custom-args/captures/lazylists-exceptions.scala:36:2 ----------------------------------------------- 36 | try // error | ^ - | The expression's type LazyList[Int]^ is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. + | The result of `try` cannot have type LazyList[Int]^ since + | that type captures the root capability `cap`. + | This is often caused by a locally generated exception capability leaking as part of its result. 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 13b1da6eaf1c..d1883cc54718 100644 --- a/tests/neg-custom-args/captures/lazylists2.check +++ b/tests/neg-custom-args/captures/lazylists2.check @@ -25,11 +25,11 @@ -- Error: tests/neg-custom-args/captures/lazylists2.scala:40:20 -------------------------------------------------------- 40 | def head: B = f(xs.head) // error | ^ - |(f : A => B) cannot be referenced here; it is not included in the allowed capture set {xs} of the self type of class Mapped + | reference (f : A => B) is not included in the allowed capture set {xs} of the self type of class Mapped -- Error: tests/neg-custom-args/captures/lazylists2.scala:41:48 -------------------------------------------------------- 41 | def tail: LazyList[B]^{this}= xs.tail.map(f) // error | ^ - |(f : A => B) cannot be referenced here; it is not included in the allowed capture set {xs} of the self type of class Mapped + | reference (f : A => B) is not included in the allowed capture set {xs} of the self type of class Mapped -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:45:4 ------------------------------------ 45 | final class Mapped extends LazyList[B]: // error | ^ diff --git a/tests/neg-custom-args/captures/leak-problem-2.scala b/tests/neg-custom-args/captures/leak-problem-2.scala index 08a3a6c2d9ca..8ca298dbdd1e 100644 --- a/tests/neg-custom-args/captures/leak-problem-2.scala +++ b/tests/neg-custom-args/captures/leak-problem-2.scala @@ -2,7 +2,7 @@ import language.experimental.captureChecking trait Source[+T] -def race[T](@caps.unbox sources: Seq[Source[T]^]): Source[T]^{sources*} = ??? +def race[T](@caps.use sources: Seq[Source[T]^]): Source[T]^{sources*} = ??? def raceTwo[T](src1: Source[T]^, src2: Source[T]^): Source[T]^{} = race(Seq(src1, src2)) // error diff --git a/tests/neg/leak-problem-unboxed.scala b/tests/neg-custom-args/captures/leak-problem-unboxed.scala similarity index 83% rename from tests/neg/leak-problem-unboxed.scala rename to tests/neg-custom-args/captures/leak-problem-unboxed.scala index 7de3d84bfcca..aedd7c889112 100644 --- a/tests/neg/leak-problem-unboxed.scala +++ b/tests/neg-custom-args/captures/leak-problem-unboxed.scala @@ -1,5 +1,5 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use // Some capabilities that should be used locally trait Async: @@ -9,12 +9,12 @@ def usingAsync[X](op: Async^ => X): X = ??? case class Box[+T](get: T) -def useBoxedAsync(@unbox x: Box[Async^]): Unit = +def useBoxedAsync(@use x: Box[Async^]): Unit = val t0 = x val t1 = t0.get // ok t1.read() -def useBoxedAsync1(@unbox x: Box[Async^]): Unit = x.get.read() // ok +def useBoxedAsync1(@use x: Box[Async^]): Unit = x.get.read() // ok def test(): Unit = diff --git a/tests/neg/leak-problem.scala b/tests/neg-custom-args/captures/leak-problem.scala similarity index 60% rename from tests/neg/leak-problem.scala rename to tests/neg-custom-args/captures/leak-problem.scala index 354d54d86707..c842280c0587 100644 --- a/tests/neg/leak-problem.scala +++ b/tests/neg-custom-args/captures/leak-problem.scala @@ -1,4 +1,5 @@ import language.experimental.captureChecking +import caps.use // Some capabilities that should be used locally trait Async: @@ -16,12 +17,28 @@ def useBoxedAsync(x: Box[Async^]): Unit = def useBoxedAsync1(x: Box[Async^]): Unit = x.get.read() // error def test(): Unit = + def useBoxedAsync(@use x: Box[Async^]): Unit = + val t0 = x + val t1 = t0.get + t1.read() + + def useBoxedAsync1(@use x: Box[Async^]): Unit = x.get.read() + + val xs: Box[Async^] = ??? + val xsLambda = () => useBoxedAsync(xs) // error + val _: () ->{xs*} Unit = xsLambda + val _: () -> Unit = xsLambda // error + val useBoxedAsync2 = (x: Box[Async^]) => val t0 = x val t1 = x.get // error t1.read() - val f: Box[Async^] => Unit = (x: Box[Async^]) => useBoxedAsync(x) + val xsLambda2 = () => useBoxedAsync2(xs) + val _: () ->{xs*} Unit = xsLambda2 + val _: () -> Unit = xsLambda2 + + val f: Box[Async^] => Unit = (x: Box[Async^]) => useBoxedAsync(x) // error def boom(x: Async^): () ->{f} Unit = () => f(Box(x)) diff --git a/tests/neg-custom-args/captures/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index 3f0a9800a4ec..be11aedd74ae 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -1,8 +1,10 @@ -- Error: tests/neg-custom-args/captures/leaked-curried.scala:14:20 ---------------------------------------------------- 14 | () => () => io // error | ^^ - |(io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Fuzz + | reference (io : Cap^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> () ->{io} (ex$7: caps.Exists) -> Cap^{ex$7} -- Error: tests/neg-custom-args/captures/leaked-curried.scala:17:20 ---------------------------------------------------- 17 | () => () => io // error | ^^ - |(io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Foo + | reference (io : Cap^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> () ->{io} (ex$15: caps.Exists) -> Cap^{ex$15} diff --git a/tests/neg-custom-args/captures/levels.check b/tests/neg-custom-args/captures/levels.check index ddfa7c051211..b99adefd4b2f 100644 --- a/tests/neg-custom-args/captures/levels.check +++ b/tests/neg-custom-args/captures/levels.check @@ -1,14 +1,15 @@ --- Error: tests/neg-custom-args/captures/levels.scala:19:13 ------------------------------------------------------------ -19 | val _ = Ref[String => String]((x: String) => x) // error - | ^^^^^^^^^^^^^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box String => String since - | that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of constructor Ref - | leaking as part of its result. --- Error: tests/neg-custom-args/captures/levels.scala:24:11 ------------------------------------------------------------ -24 | r.setV(g) // error +-- 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 + | that type captures the root capability `cap`. +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/levels.scala:22:11 --------------------------------------- +22 | r.setV(g) // error | ^ - | reference (cap3 : CC^) is not included in the allowed capture set ? of value r + | Found: box (x: String) ->{cap3} String + | Required: box (x$0: String) ->? String | | Note that reference (cap3 : CC^), defined in method scope | cannot be included in outer capture set ? of value r + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/levels.scala b/tests/neg-custom-args/captures/levels.scala index 4709fd80d9b8..b28e87f03ef7 100644 --- a/tests/neg-custom-args/captures/levels.scala +++ b/tests/neg-custom-args/captures/levels.scala @@ -1,5 +1,3 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) class CC def test1(cap1: CC^) = diff --git a/tests/neg-custom-args/captures/method-uses.scala b/tests/neg-custom-args/captures/method-uses.scala new file mode 100644 index 000000000000..69acef6a99a8 --- /dev/null +++ b/tests/neg-custom-args/captures/method-uses.scala @@ -0,0 +1,30 @@ +def test(xs: List[() => Unit]) = + xs.head // error + + def foo = + xs.head // ok + def bar() = + xs.head // ok + + class Foo: + println(xs.head) // error, but could be OK + + foo // error + bar() // error + Foo() // OK, but could be error + +def test2(xs: List[() => Unit]) = + def foo = xs.head // ok + () + +def test3(xs: List[() => Unit]): () ->{xs*} Unit = () => + println(xs.head) // error, ok under deferredReaches + + def test4(xs: List[() => Unit]) = () => xs.head // error, ok under deferredReaches + + def test5(xs: List[() => Unit]) = new: + println(xs.head) // error, ok under deferredReaches + + def test6(xs: List[() => Unit]) = + val x= new { println(xs.head) } // error + x diff --git a/tests/neg-custom-args/captures/outer-var.check b/tests/neg-custom-args/captures/outer-var.check index 32351a179eab..72af842728a1 100644 --- a/tests/neg-custom-args/captures/outer-var.check +++ b/tests/neg-custom-args/captures/outer-var.check @@ -1,8 +1,8 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:11:8 ------------------------------------- 11 | x = q // error | ^ - | Found: box () ->{q} Unit - | Required: box () ->{p, q²} Unit + | Found: (q : Proc) + | Required: () ->{p, q²} Unit | | where: q is a parameter in method inner | q² is a parameter in method test @@ -12,19 +12,31 @@ 12 | x = (q: Proc) // error | ^^^^^^^ | Found: Proc - | Required: box () ->{p, q} Unit - | - | Note that () => Unit cannot be box-converted to box () ->{p, q} Unit - | since at least one of their capture sets contains the root capability `cap` + | Required: () ->{p, q} Unit | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:13:9 ------------------------------------- 13 | y = (q: Proc) // error | ^^^^^^^ | Found: Proc - | Required: box () => Unit + | Required: () ->{p} Unit + | + | Note that the universal capability `cap` + | cannot be included in capture set {p} of variable y + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:14:8 ------------------------------------- +14 | y = q // error, was OK under unsealed + | ^ + | Found: (q : Proc) + | Required: () ->{p} Unit | - | Note that () => Unit cannot be box-converted to box () => Unit - | since at least one of their capture sets contains the root capability `cap` + | Note that reference (q : Proc), defined in method inner + | cannot be included in outer capture set {p} of variable y | | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/outer-var.scala:16:57 --------------------------------------------------------- +16 | 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`. diff --git a/tests/neg-custom-args/captures/outer-var.scala b/tests/neg-custom-args/captures/outer-var.scala index e26cd631602a..f869bfbfc387 100644 --- a/tests/neg-custom-args/captures/outer-var.scala +++ b/tests/neg-custom-args/captures/outer-var.scala @@ -11,8 +11,8 @@ def test(p: Proc, q: () => Unit) = x = q // error x = (q: Proc) // error y = (q: Proc) // error - y = q // OK, was error under sealed + y = q // error, was OK under unsealed - var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // OK, was error under sealed + var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error, was OK under unsealed diff --git a/tests/neg-custom-args/captures/path-use.check b/tests/neg-custom-args/captures/path-use.check new file mode 100644 index 000000000000..e09ee232dd17 --- /dev/null +++ b/tests/neg-custom-args/captures/path-use.check @@ -0,0 +1,4 @@ +-- Error: tests/neg-custom-args/captures/path-use.scala:18:32 ---------------------------------------------------------- +18 | val g = () => println(c.procs.head) // error, local reach capability c.procs* leaks + | ^^^^^^^^^^^^ + | Local reach capability c.procs* leaks into capture scope of method test diff --git a/tests/neg-custom-args/captures/path-use.scala b/tests/neg-custom-args/captures/path-use.scala new file mode 100644 index 000000000000..31feb4c0adf4 --- /dev/null +++ b/tests/neg-custom-args/captures/path-use.scala @@ -0,0 +1,25 @@ +import language.experimental.namedTuples + +class IO + +class C(val f: IO^): + val procs: List[Proc] = ??? + +type Proc = () => Unit + +def test(io: IO^) = + val c = C(io) + val f = () => println(c.f) + val _: () ->{c.f} Unit = f + + val x = c.procs + val _: List[() ->{c.procs*} Unit] = x + + val g = () => println(c.procs.head) // error, local reach capability c.procs* leaks + val _: () ->{c.procs*} Unit = g + + val cc: C { val f: IO^{io}; val procs: List[() ->{io} Unit] }^{io} = + ??? + + val gg = () => println(cc.procs.head) // OK, since cc.procs* has {io} as underlying capture set + val _: () ->{io} Unit = gg diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index f00fea09ed8c..7c00fa7299fe 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -15,22 +15,27 @@ | cannot be included in outer capture set {xs*} of value cur | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/reaches.scala:38:31 ----------------------------------------------------------- -38 | val next: () => Unit = cur.head // error - | ^^^^^^^^ - | The expression's type box () => Unit is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. --- Error: tests/neg-custom-args/captures/reaches.scala:45:35 ----------------------------------------------------------- -45 | val next: () => Unit = cur.get.head // error - | ^^^^^^^^^^^^ - | The expression's type box () => Unit is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. +-- Error: tests/neg-custom-args/captures/reaches.scala:36:6 ------------------------------------------------------------ +36 | 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:43:16 ----------------------------------------------------------- +43 | 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`. +-- Error: tests/neg-custom-args/captures/reaches.scala:53:51 ----------------------------------------------------------- +53 | 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`. -- Error: tests/neg-custom-args/captures/reaches.scala:55:6 ------------------------------------------------------------ 55 | id(() => f.write()) // error | ^^^^^^^^^^^^^^^^^^^ | Local reach capability id* leaks into capture scope of method test -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:62:27 -------------------------------------- -62 | val f1: File^{id*} = id(f) // error, since now id(f): File^ +62 | val f1: File^{id*} = id(f) // error, since now id(f): File^ // error | ^^^^^ | Found: File^{f} | Required: File^{id*} @@ -39,14 +44,18 @@ -- Error: tests/neg-custom-args/captures/reaches.scala:79:10 ----------------------------------------------------------- 79 | ps.map((x, y) => compose1(x, y)) // error // error | ^ - | Local reach capability ps* leaks into capture scope of method mapCompose + | 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/reaches.scala:79:13 ----------------------------------------------------------- 79 | ps.map((x, y) => compose1(x, y)) // error // error | ^ - | Local reach capability ps* leaks into capture scope of method mapCompose --- [E057] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:53:51 -------------------------------------- -53 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error - | ^ - | Type argument () -> Unit does not conform to lower bound () => Unit - | - | longer explanation available when compiling with `-explain` + | 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/reaches.scala:61:31 ----------------------------------------------------------- +61 | val leaked = usingFile[File^{id*}]: f => // error + | ^^^ + | id* cannot be tracked since its deep capture set is empty +-- Error: tests/neg-custom-args/captures/reaches.scala:62:18 ----------------------------------------------------------- +62 | val f1: File^{id*} = id(f) // error, since now id(f): File^ // error + | ^^^ + | id* cannot be tracked since its deep capture set is empty diff --git a/tests/neg-custom-args/captures/reaches.scala b/tests/neg-custom-args/captures/reaches.scala index c33ba80a668b..a9773b76f445 100644 --- a/tests/neg-custom-args/captures/reaches.scala +++ b/tests/neg-custom-args/captures/reaches.scala @@ -1,4 +1,4 @@ -import caps.unbox +import caps.use class File: def write(): Unit = ??? @@ -11,7 +11,7 @@ class Ref[T](init: T): def get: T = x def set(y: T) = { x = y } -def runAll0(@unbox xs: List[Proc]): Unit = +def runAll0(@use xs: List[Proc]): Unit = var cur: List[() ->{xs*} Unit] = xs while cur.nonEmpty do val next: () ->{xs*} Unit = cur.head @@ -21,7 +21,7 @@ def runAll0(@unbox xs: List[Proc]): Unit = usingFile: f => cur = (() => f.write()) :: Nil // error -def runAll1(@unbox xs: List[Proc]): Unit = +def runAll1(@use xs: List[Proc]): Unit = val cur = Ref[List[() ->{xs*} Unit]](xs) // OK, by revised VAR while cur.get.nonEmpty do val next: () ->{xs*} Unit = cur.get.head @@ -33,16 +33,16 @@ def runAll1(@unbox xs: List[Proc]): Unit = (() => f.write()) :: Nil // error def runAll2(xs: List[Proc]): Unit = - var cur: List[Proc] = xs + var cur: List[Proc] = xs // error while cur.nonEmpty do - val next: () => Unit = cur.head // error + val next: () => Unit = cur.head next() cur = cur.tail def runAll3(xs: List[Proc]): Unit = - val cur = Ref[List[Proc]](xs) + val cur = Ref[List[Proc]](xs) // error while cur.get.nonEmpty do - val next: () => Unit = cur.get.head // error + val next: () => Unit = cur.get.head next() cur.set(cur.get.tail: List[Proc]) @@ -58,8 +58,8 @@ def attack2 = val id: File^ -> File^ = x => x // val id: File^ -> EX C.File^C - val leaked = usingFile[File^{id*}]: f => - val f1: File^{id*} = id(f) // error, since now id(f): File^ + val leaked = usingFile[File^{id*}]: f => // error + val f1: File^{id*} = id(f) // error, since now id(f): File^ // error f1 class List[+A]: @@ -78,5 +78,5 @@ def compose1[A, B, C](f: A => B, g: B => C): A ->{f, g} C = def mapCompose[A](ps: List[(A => A, A => A)]): List[A ->{ps*} A] = ps.map((x, y) => compose1(x, y)) // error // error -def mapCompose2[A](@unbox ps: List[(A => A, A => A)]): List[A ->{ps*} A] = +def mapCompose2[A](@use ps: List[(A => A, A => A)]): List[A ->{ps*} A] = ps.map((x, y) => compose1(x, y)) diff --git a/tests/neg-custom-args/captures/reaches2.check b/tests/neg-custom-args/captures/reaches2.check index 03860ee4a01b..1e921ee92072 100644 --- a/tests/neg-custom-args/captures/reaches2.check +++ b/tests/neg-custom-args/captures/reaches2.check @@ -2,9 +2,9 @@ 8 | ps.map((x, y) => compose1(x, y)) // error // error | ^ |reference ps* is not included in the allowed capture set {} - |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? (ex$15: caps.Exists) -> A^? + |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? A^? -- Error: tests/neg-custom-args/captures/reaches2.scala:8:13 ----------------------------------------------------------- 8 | ps.map((x, y) => compose1(x, y)) // error // error | ^ |reference ps* is not included in the allowed capture set {} - |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? (ex$15: caps.Exists) -> A^? + |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? A^? diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index 7f8ab50bc222..7a4b12ac08f6 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -1,13 +1,13 @@ --- [E190] Potential Issue Warning: tests/neg-custom-args/captures/real-try.scala:38:4 ---------------------------------- -38 | b.x - | ^^^ - | Discarded non-Unit value of type () -> Unit. You may want to use `()`. +-- [E190] Potential Issue Warning: tests/neg-custom-args/captures/real-try.scala:38:2 ---------------------------------- +38 | b + | ^ + | Discarded non-Unit value of type Cell[() -> Unit]. Add `: Unit` to discard silently. | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/real-try.scala:14:2 ----------------------------------------------------------- 14 | try // error | ^ - | result of `try` cannot have type () => Unit since + | The result of `try` cannot have type () => 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. 15 | () => foo(1) @@ -17,7 +17,7 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:20:10 ---------------------------------------------------------- 20 | val x = try // error | ^ - | result of `try` cannot have type () => Unit since + | The result of `try` cannot have type () => 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. 21 | () => foo(1) @@ -27,7 +27,7 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:26:10 ---------------------------------------------------------- 26 | val y = try // error | ^ - | result of `try` cannot have type () => Cell[Unit]^? since + | The result of `try` cannot have type () => 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. 27 | () => Cell(foo(1)) @@ -37,7 +37,7 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:32:10 ---------------------------------------------------------- 32 | val b = try // error | ^ - | result of `try` cannot have type Cell[box () => Unit]^? since + | The result of `try` cannot have type Cell[box () => Unit]^? since | the part box () => 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. 33 | Cell(() => foo(1)) diff --git a/tests/neg-custom-args/captures/real-try.scala b/tests/neg-custom-args/captures/real-try.scala index 51f1a0fdea5a..32e976f654a9 100644 --- a/tests/neg-custom-args/captures/real-try.scala +++ b/tests/neg-custom-args/captures/real-try.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import language.experimental.saferExceptions class Ex1 extends Exception("Ex1") @@ -35,4 +35,4 @@ def test(): Unit = case _: Ex1 => ??? case _: Ex2 => ??? - b.x + b diff --git a/tests/neg-custom-args/captures/refine-reach-shallow.scala b/tests/neg-custom-args/captures/refine-reach-shallow.scala index 525d33fdb7c5..f78c99f919af 100644 --- a/tests/neg-custom-args/captures/refine-reach-shallow.scala +++ b/tests/neg-custom-args/captures/refine-reach-shallow.scala @@ -5,14 +5,15 @@ def test1(): Unit = val g: IO^ => IO^{f*} = f // error def test2(): Unit = val f: [R] -> (IO^ => R) -> R = ??? - val g: [R] -> (IO^{f*} => R) -> R = f // error + val ff = f + val g: [R] -> (IO^{f*} => R) -> R = f // error // error def test3(): Unit = val f: [R] -> (IO^ -> R) -> R = ??? - val g: [R] -> (IO^{f*} -> R) -> R = f // error + val g: [R] -> (IO^{f*} -> R) -> R = f // error // error def test4(): Unit = val xs: List[IO^] = ??? 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 - val h: [R] -> (IO^{f*} -> R) -> IO^ = f // error + val g: [R] -> (IO^ -> R) -> IO^{f*} = f // error // error + val h: [R] -> (IO^{f*} -> R) -> IO^ = f // error // error diff --git a/tests/neg-custom-args/captures/refine-withFile.scala b/tests/neg-custom-args/captures/refine-withFile.scala index 823b62711d05..e7958ab66fc8 100644 --- a/tests/neg-custom-args/captures/refine-withFile.scala +++ b/tests/neg-custom-args/captures/refine-withFile.scala @@ -4,5 +4,5 @@ trait File val useFile: [R] -> (path: String) -> (op: File^ -> R) -> R = ??? def main(): Unit = val f: [R] -> (path: String) -> (op: File^ -> R) -> R = useFile - val g: [R] -> (path: String) -> (op: File^{f*} -> R) -> R = f // error - val leaked = g[File^{f*}]("test")(f => f) // boom + val g: [R] -> (path: String) -> (op: File^{f*} -> R) -> R = f // error // error + val leaked = g[File^{f*}]("test")(f => f) // error diff --git a/tests/neg-custom-args/captures/spread-problem.scala b/tests/neg-custom-args/captures/spread-problem.scala index 579c7817b9c1..75f3a615cde8 100644 --- a/tests/neg-custom-args/captures/spread-problem.scala +++ b/tests/neg-custom-args/captures/spread-problem.scala @@ -2,7 +2,7 @@ import language.experimental.captureChecking trait Source[+T] -def race[T](@caps.unbox sources: (Source[T]^)*): Source[T]^{sources*} = ??? +def race[T](@caps.use sources: (Source[T]^)*): Source[T]^{sources*} = ??? def raceTwo[T](src1: Source[T]^, src2: Source[T]^): Source[T]^{} = race(Seq(src1, src2)*) // error diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index 77a5fc06e05a..72604451472c 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -1,17 +1,13 @@ --- Error: tests/neg-custom-args/captures/try.scala:25:3 ---------------------------------------------------------------- -23 | val a = handle[Exception, CanThrow[Exception]] { -24 | (x: CanThrow[Exception]) => x -25 | }{ // error (but could be better) - | ^ - | The expression's type box CT[Exception]^ is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. -26 | (ex: Exception) => ??? -27 | } +-- Error: tests/neg-custom-args/captures/try.scala:23:28 --------------------------------------------------------------- +23 | val a = handle[Exception, CanThrow[Exception]] { // error + | ^^^^^^^^^^^^^^^^^^^ + | Type variable R of method handle cannot be instantiated to box CT[Exception]^ since + | that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/try.scala:30:65 --------------------------------------------------------------- 30 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error | ^ - | (x : CanThrow[Exception]) cannot be referenced here; it is not included in the allowed capture set {} - | of an enclosing function literal with expected type () ->? Nothing + | reference (x : CanThrow[Exception]) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () ->? Nothing -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:52:2 ------------------------------------------- 47 |val global: () -> Int = handle { 48 | (x: CanThrow[Exception]) => diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala index 45a1b346a512..a85a18f69caa 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -20,9 +20,9 @@ def handle[E <: Exception, R <: Top](op: CT[E]^ => R)(handler: E => R): R = catch case ex: E => handler(ex) def test = - val a = handle[Exception, CanThrow[Exception]] { + val a = handle[Exception, CanThrow[Exception]] { // error (x: CanThrow[Exception]) => x - }{ // error (but could be better) + }{ (ex: Exception) => ??? } diff --git a/tests/neg-custom-args/captures/unbox-overrides.check b/tests/neg-custom-args/captures/unbox-overrides.check index b9a3be7bffbc..dbffc164b5c5 100644 --- a/tests/neg-custom-args/captures/unbox-overrides.check +++ b/tests/neg-custom-args/captures/unbox-overrides.check @@ -2,20 +2,20 @@ 8 | def foo(x: C): C // error | ^ |error overriding method foo in trait A of type (x: C): C; - | method foo of type (x: C): C has a parameter x with different @unbox status than the corresponding parameter in the overridden definition + | 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` -- [E164] Declaration Error: tests/neg-custom-args/captures/unbox-overrides.scala:9:6 ---------------------------------- -9 | def bar(@unbox x: C): C // error +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 @unbox status than the corresponding parameter in the overridden definition + | method bar 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` -- [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; - | method foo in trait B2 of type (x: C): C has a parameter x with different @unbox status than the corresponding parameter in the overridden definition + | 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/unbox-overrides.scala b/tests/neg-custom-args/captures/unbox-overrides.scala index 5abb5013bfbe..6164ca274eaf 100644 --- a/tests/neg-custom-args/captures/unbox-overrides.scala +++ b/tests/neg-custom-args/captures/unbox-overrides.scala @@ -1,15 +1,15 @@ -import caps.unbox +import caps.use trait A[X]: - def foo(@unbox x: X): X + def foo(@use x: X): X def bar(x: X): X trait B extends A[C]: def foo(x: C): C // error - def bar(@unbox x: C): C // error + def bar(@use x: C): C // error trait B2: def foo(x: C): C - def bar(@unbox x: C): C + def bar(@use x: C): C abstract class C extends A[C], B2 // error diff --git a/tests/neg-custom-args/captures/unbox.scala b/tests/neg-custom-args/captures/unbox.scala index 33702a954068..28feb5f89aff 100644 --- a/tests/neg-custom-args/captures/unbox.scala +++ b/tests/neg-custom-args/captures/unbox.scala @@ -1,4 +1,4 @@ -import language.`3.2` +import language.`3.5` type Proc = () => Unit val xs: List[Proc] = ??? diff --git a/tests/neg-custom-args/captures/unsound-reach-2.scala b/tests/neg-custom-args/captures/unsound-reach-2.scala index 5bea18bdccba..c7dfa117a2fe 100644 --- a/tests/neg-custom-args/captures/unsound-reach-2.scala +++ b/tests/neg-custom-args/captures/unsound-reach-2.scala @@ -1,5 +1,3 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) import language.experimental.captureChecking trait Consumer[-T]: def apply(x: T): Unit @@ -11,7 +9,7 @@ def withFile[R](path: String)(op: Consumer[File]): R = ??? trait Foo[+X]: def use(x: File^)(op: Consumer[X]): Unit -class Bar extends Foo[File^]: +class Bar extends Foo[File^]: // error def use(x: File^)(op: Consumer[File^]): Unit = op.apply(x) def bad(): Unit = diff --git a/tests/neg-custom-args/captures/unsound-reach-3.scala b/tests/neg-custom-args/captures/unsound-reach-3.scala index 0063216e957e..c5cdfca9d87a 100644 --- a/tests/neg-custom-args/captures/unsound-reach-3.scala +++ b/tests/neg-custom-args/captures/unsound-reach-3.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import language.experimental.captureChecking trait File: def close(): Unit @@ -8,7 +8,7 @@ def withFile[R](path: String)(op: File^ => R): R = ??? trait Foo[+X]: def use(x: File^): X -class Bar extends Foo[File^]: +class Bar extends Foo[File^]: // error def use(x: File^): File^ = x def bad(): Unit = @@ -16,8 +16,8 @@ def bad(): Unit = val boom: Foo[File^{backdoor*}] = backdoor var escaped: File^{backdoor*} = null - withFile("hello.txt"): f => // error - escaped = boom.use(f) + withFile("hello.txt"): f => + escaped = boom.use(f) // error // boom.use: (x: File^) -> File^{backdoor*}, it is a selection so reach capabilities are allowed // f: File^, so there is no reach capabilities diff --git a/tests/neg-custom-args/captures/unsound-reach-4.check b/tests/neg-custom-args/captures/unsound-reach-4.check index d359b298555e..ca95bf42ba59 100644 --- a/tests/neg-custom-args/captures/unsound-reach-4.check +++ b/tests/neg-custom-args/captures/unsound-reach-4.check @@ -1,6 +1,9 @@ --- Error: tests/neg-custom-args/captures/unsound-reach-4.scala:21:25 --------------------------------------------------- -21 | withFile("hello.txt"): f => // error - | ^ - | Reach capability backdoor* and universal capability cap cannot both - | appear in the type (f: File^) ->{backdoor*} Unit of this expression -22 | escaped = boom.use(f) +-- Error: tests/neg-custom-args/captures/unsound-reach-4.scala:13:18 --------------------------------------------------- +13 |class Bar extends Foo[File^]: // error + | ^^^^^^^^^^ + | Type variable X of trait Foo cannot be instantiated to File^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/unsound-reach-4.scala:22:22 --------------------------------------------------- +22 | escaped = boom.use(f) // error + | ^^^^^^^^^^^ + | Local reach capability backdoor* leaks into capture scope of method bad diff --git a/tests/neg-custom-args/captures/unsound-reach-4.scala b/tests/neg-custom-args/captures/unsound-reach-4.scala index bc66085614f2..88fbc2f5c1de 100644 --- a/tests/neg-custom-args/captures/unsound-reach-4.scala +++ b/tests/neg-custom-args/captures/unsound-reach-4.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import language.experimental.captureChecking trait File: def close(): Unit @@ -10,7 +10,7 @@ type F = File^ trait Foo[+X]: def use(x: F): X -class Bar extends Foo[File^]: +class Bar extends Foo[File^]: // error def use(x: F): File^ = x def bad(): Unit = @@ -18,5 +18,5 @@ def bad(): Unit = val boom: Foo[File^{backdoor*}] = backdoor var escaped: File^{backdoor*} = null - withFile("hello.txt"): f => // error - escaped = boom.use(f) + withFile("hello.txt"): f => + escaped = boom.use(f) // error diff --git a/tests/neg-custom-args/captures/unsound-reach-5.scala b/tests/neg-custom-args/captures/unsound-reach-5.scala new file mode 100644 index 000000000000..806de5093ecd --- /dev/null +++ b/tests/neg-custom-args/captures/unsound-reach-5.scala @@ -0,0 +1,22 @@ +class IO + +def f(xs: List[() => Unit]): () ->{xs*} Unit = () => + println(xs.head) // error + +def test(io: IO^)(ys: List[() ->{io} Unit]) = + val x = () => + val z = f(ys) + z() + val _: () -> Unit = x // !!! ys* gets lost + () + +def test(io: IO^) = + def ys: List[() ->{io} Unit] = ??? + val x = () => + val z = f(ys) + z() + val _: () -> Unit = x // !!! io gets lost + () + + + diff --git a/tests/neg-custom-args/captures/unsound-reach-6.scala b/tests/neg-custom-args/captures/unsound-reach-6.scala new file mode 100644 index 000000000000..b7306dca4190 --- /dev/null +++ b/tests/neg-custom-args/captures/unsound-reach-6.scala @@ -0,0 +1,22 @@ +class IO + +def f(xs: List[() => Unit]): () => Unit = () => + println(xs.head) // error + +def test(io: IO^)(ys: List[() ->{io} Unit]) = + val x = () => + val z = f(ys) + z() + val _: () -> Unit = x // !!! ys* gets lost + () + +def test(io: IO^) = + def ys: List[() ->{io} Unit] = ??? + val x = () => + val z = f(ys) + z() + val _: () -> Unit = x // !!! io gets lost + () + + + diff --git a/tests/neg-custom-args/captures/unsound-reach-7.scala b/tests/neg-custom-args/captures/unsound-reach-7.scala new file mode 100644 index 000000000000..df345cdf7f6d --- /dev/null +++ b/tests/neg-custom-args/captures/unsound-reach-7.scala @@ -0,0 +1,15 @@ +import language.experimental.captureChecking +import caps.{cap, use} + +trait IO +trait Async + +def main(io: IO^, async: Async^) = + def bad[X](ops: List[(X, () ->{io} Unit)])(f: () ->{ops*} Unit): () ->{io} Unit = f // error + def runOps(@use ops: List[(() => Unit, () => Unit)]): () ->{ops*} Unit = + () => ops.foreach((f1, f2) => { f1(); f2() }) + def delayOps(@use ops: List[(() ->{async} Unit, () ->{io} Unit)]): () ->{io} Unit = + val runner: () ->{ops*} Unit = runOps(ops) + val badRunner: () ->{io} Unit = bad[() ->{async} Unit](ops)(runner) + // it uses both async and io, but we losed track of async. + badRunner \ No newline at end of file diff --git a/tests/neg-custom-args/captures/unsound-reach.check b/tests/neg-custom-args/captures/unsound-reach.check index 4a6793d204c5..69794f569edb 100644 --- a/tests/neg-custom-args/captures/unsound-reach.check +++ b/tests/neg-custom-args/captures/unsound-reach.check @@ -1,12 +1,15 @@ --- Error: tests/neg-custom-args/captures/unsound-reach.scala:18:21 ----------------------------------------------------- -18 | boom.use(f): (f1: File^{backdoor*}) => // error +-- Error: tests/neg-custom-args/captures/unsound-reach.scala:9:18 ------------------------------------------------------ +9 |class Bar extends Foo[File^]: // error + | ^^^^^^^^^^ + | Type variable X of trait Foo cannot be instantiated to File^ since + | that type captures the root capability `cap`. +-- 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 + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/unsound-reach.scala:23:21 ----------------------------------------------------- +23 | boom.use(f): (f1: File^{backdoor*}) => // error | ^ | Local reach capability backdoor* leaks into capture scope of method bad -19 | escaped = f1 --- [E164] Declaration Error: tests/neg-custom-args/captures/unsound-reach.scala:10:8 ----------------------------------- -10 | def use(x: File^)(op: File^ => Unit): Unit = op(x) // error, was OK using sealed checking - | ^ - | error overriding method use in trait Foo of type (x: File^)(op: box File^ => Unit): Unit; - | method use of type (x: File^)(op: File^ => Unit): Unit has incompatible type - | - | longer explanation available when compiling with `-explain` +24 | escaped = f1 diff --git a/tests/neg-custom-args/captures/unsound-reach.scala b/tests/neg-custom-args/captures/unsound-reach.scala index c3c31a7f32ff..3fb666c7c1fc 100644 --- a/tests/neg-custom-args/captures/unsound-reach.scala +++ b/tests/neg-custom-args/captures/unsound-reach.scala @@ -6,8 +6,13 @@ def withFile[R](path: String)(op: File^ => R): R = ??? trait Foo[+X]: def use(x: File^)(op: X => Unit): Unit -class Bar extends Foo[File^]: - def use(x: File^)(op: File^ => Unit): Unit = op(x) // error, was OK using sealed checking +class Bar extends Foo[File^]: // error + def use(x: File^)(op: File^ => Unit): Unit = op(x) // OK using sealed checking + +abstract class Foo2[+X](): + def use(x: File^)(op: X => Unit): Unit +class Bar2 extends Foo2[File^]: // error + def use(x: File^)(op: File^ => Unit): Unit = op(x) // OK using sealed checking def bad(): Unit = val backdoor: Foo[File^] = new Bar diff --git a/tests/neg-custom-args/captures/use-capset.check b/tests/neg-custom-args/captures/use-capset.check new file mode 100644 index 000000000000..74afaa05890f --- /dev/null +++ b/tests/neg-custom-args/captures/use-capset.check @@ -0,0 +1,19 @@ +-- 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} + | ^ + | Found: (h : () ->{io} Unit) + | Required: () -> Unit + | + | longer explanation available when compiling with `-explain` +-- [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 : () ->? (x$0: List[box Object^{io}]^{}) ->{io} Object^{io}) + | Required: () -> List[box Object^{io}] -> Object^{io} + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/use-capset.scala b/tests/neg-custom-args/captures/use-capset.scala new file mode 100644 index 000000000000..74288d616396 --- /dev/null +++ b/tests/neg-custom-args/captures/use-capset.scala @@ -0,0 +1,14 @@ +import caps.{use, CapSet} + +def f[C^](@use xs: List[Object^{C^}]): Unit = ??? + +private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error + +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 _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} + diff --git a/tests/neg-custom-args/captures/use-override.scala b/tests/neg-custom-args/captures/use-override.scala new file mode 100644 index 000000000000..febb59ca4208 --- /dev/null +++ b/tests/neg-custom-args/captures/use-override.scala @@ -0,0 +1,15 @@ +import caps.use + +def test(io: Object^, async: Object^) = + + trait A: + def f(@use x: List[() ->{io} Unit]): Unit + + class B extends A: + def f(@use x: List[() => Unit]): Unit = // error, would be unsound if allowed + x.foreach(_()) + + class C extends A: + def f(@use x: List[() ->{io, async} Unit]): Unit = // error, this one could be soundly allowed actually + x.foreach(_()) + diff --git a/tests/neg-custom-args/captures/uses.check b/tests/neg-custom-args/captures/uses.check new file mode 100644 index 000000000000..d201d79133cf --- /dev/null +++ b/tests/neg-custom-args/captures/uses.check @@ -0,0 +1,28 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:8:17 ------------------------------------------ +8 | val _: D^{y} = d // error, should be ok + | ^ + | Found: (d : D^{x, y}) + | Required: D^{y} + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:9:13 ------------------------------------------ +9 | val _: D = d // error + | ^ + | Found: (d : D^{x, y}) + | Required: D + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:18:34 ----------------------------------------- +18 | val _: () ->{x} () ->{y} Unit = g // error, should be ok + | ^ + | Found: () ->{x, y} () ->{y} Unit + | Required: () ->{x} () ->{y} Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:19:28 ----------------------------------------- +19 | val _: () -> () -> Unit = g // error + | ^ + | Found: () ->{x, y} () ->{y} Unit + | Required: () -> () -> Unit + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/uses.scala b/tests/neg-custom-args/captures/uses.scala new file mode 100644 index 000000000000..b872c7b03ec7 --- /dev/null +++ b/tests/neg-custom-args/captures/uses.scala @@ -0,0 +1,20 @@ +class C +def test(x: C^, y: C^) = + class D { + println(x) + def foo() = println(y) + } + val d = D() + val _: D^{y} = d // error, should be ok + val _: D = d // error + + val f = () => println(D()) + val _: () ->{x} Unit = f // ok + val _: () -> Unit = f // should be error + + def g = () => + println(x) + () => println(y) + val _: () ->{x} () ->{y} Unit = g // error, should be ok + val _: () -> () -> Unit = g // error + diff --git a/tests/neg-custom-args/captures/vars-simple.check b/tests/neg-custom-args/captures/vars-simple.check index e9671f775c22..2bc014e9a4e7 100644 --- a/tests/neg-custom-args/captures/vars-simple.check +++ b/tests/neg-custom-args/captures/vars-simple.check @@ -2,17 +2,16 @@ 15 | a = (g: String => String) // error | ^^^^^^^^^^^^^^^^^^^ | Found: String => String - | Required: box String ->{cap1, cap2} String - | - | Note that String => String cannot be box-converted to box String ->{cap1, cap2} String - | since at least one of their capture sets contains the root capability `cap` + | Required: String ->{cap1, cap2} String | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/vars-simple.scala:16:8 -------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars-simple.scala:16:8 ----------------------------------- 16 | a = g // error | ^ - | reference (cap3 : Cap) is not included in the allowed capture set {cap1, cap2} - | of an enclosing function literal with expected type box String ->{cap1, cap2} String + | Found: (x: String) ->{cap3} String + | Required: (x: 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 | ^^^^^^^ diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index 0d3c2e0f2e11..e4b1e71a2000 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -5,13 +5,16 @@ | | Note that reference (cap3 : Cap), defined in method scope | cannot be included in outer capture set {cap1} of variable a --- Error: tests/neg-custom-args/captures/vars.scala:25:8 --------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:25:8 ------------------------------------------ 25 | a = g // error | ^ - | reference (cap3 : Cap) is not included in the allowed capture set {cap1} of variable a + | Found: (x: String) ->{cap3} String + | Required: (x$0: String) ->{cap1} String | | Note that reference (cap3 : Cap), defined in method scope | cannot be included in outer capture set {cap1} of variable a + | + | 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 | ^^^^^^^ diff --git a/tests/neg-custom-args/captures/vars.scala b/tests/neg-custom-args/captures/vars.scala index 5eb1e3fedda9..eb9719cd2adf 100644 --- a/tests/neg-custom-args/captures/vars.scala +++ b/tests/neg-custom-args/captures/vars.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + class CC type Cap = CC^ diff --git a/tests/neg-custom-args/captures/wf-reach-1.check b/tests/neg-custom-args/captures/wf-reach-1.check new file mode 100644 index 000000000000..6a3ac9771a11 --- /dev/null +++ b/tests/neg-custom-args/captures/wf-reach-1.check @@ -0,0 +1,4 @@ +-- Error: tests/neg-custom-args/captures/wf-reach-1.scala:2:17 --------------------------------------------------------- +2 | val y: Object^{x*} = ??? // error + | ^^ + | x* cannot be tracked since its deep capture set is empty diff --git a/tests/neg-custom-args/captures/wf-reach-1.scala b/tests/neg-custom-args/captures/wf-reach-1.scala new file mode 100644 index 000000000000..c8901c7ae4a8 --- /dev/null +++ b/tests/neg-custom-args/captures/wf-reach-1.scala @@ -0,0 +1,2 @@ +def test(x: List[() -> Unit]) = + val y: Object^{x*} = ??? // error diff --git a/tests/neg-custom-args/captures/widen-reach.check b/tests/neg-custom-args/captures/widen-reach.check index 06d21ff445d8..9fe1f2bd5de6 100644 --- a/tests/neg-custom-args/captures/widen-reach.check +++ b/tests/neg-custom-args/captures/widen-reach.check @@ -1,15 +1,15 @@ +-- Error: tests/neg-custom-args/captures/widen-reach.scala:8:18 -------------------------------------------------------- +8 |trait Bar extends Foo[IO^]: // error + | ^^^^^^^^ + | Type variable T of trait Foo cannot be instantiated to IO^ since + | that type captures the root capability `cap`. -- 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 + | 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 ------------------------------------------------------- 14 | val y3: IO^ -> IO^{x*} = y1.foo // error | ^^^^^^ - | Local reach capability x* leaks into capture scope of method test --- [E164] Declaration Error: tests/neg-custom-args/captures/widen-reach.scala:9:6 -------------------------------------- -9 | val foo: IO^ -> IO^ = x => x // error - | ^ - | error overriding value foo in trait Foo of type IO^ -> box IO^; - | value foo of type IO^ -> (ex$3: caps.Exists) -> IO^{ex$3} has incompatible type - | - | longer explanation available when compiling with `-explain` + | Local reach capability x* leaks into capture scope of method test. + | To allow this, the parameter x should be declared with a @use annotation diff --git a/tests/neg-custom-args/captures/widen-reach.scala b/tests/neg-custom-args/captures/widen-reach.scala index fa5eee1232df..9a9305640473 100644 --- a/tests/neg-custom-args/captures/widen-reach.scala +++ b/tests/neg-custom-args/captures/widen-reach.scala @@ -5,8 +5,8 @@ trait IO trait Foo[+T]: val foo: IO^ -> T -trait Bar extends Foo[IO^]: - val foo: IO^ -> IO^ = x => x // error +trait Bar extends Foo[IO^]: // error + val foo: IO^ -> IO^ = x => x def test(x: Foo[IO^]): Unit = val y1: Foo[IO^{x*}] = x diff --git a/tests/neg/abstract-tracked-1.scala b/tests/neg/abstract-tracked-1.scala new file mode 100644 index 000000000000..0aef9f938816 --- /dev/null +++ b/tests/neg/abstract-tracked-1.scala @@ -0,0 +1,12 @@ +import scala.language.experimental.modularity +import scala.language.future + +trait F: + tracked val a: Int + +class G: + val a: Int = 1 + +def Test = + val g = new G + summon[g.a.type <:< 1] // error diff --git a/tests/neg/abstract-tracked.check b/tests/neg/abstract-tracked.check new file mode 100644 index 000000000000..70a85e81df85 --- /dev/null +++ b/tests/neg/abstract-tracked.check @@ -0,0 +1,20 @@ +-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:4:14 ---------------------------------------------------------- +4 |tracked trait F // error + |^^^^^^^^^^^^^^^ + |Modifier tracked is not allowed for this definition +-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:9:15 ---------------------------------------------------------- +9 |tracked object O // error + |^^^^^^^^^^^^^^^^ + |Modifier tracked is not allowed for this definition +-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:11:14 --------------------------------------------------------- +11 |tracked class C // error + |^^^^^^^^^^^^^^^ + |Modifier tracked is not allowed for this definition +-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:7:14 ---------------------------------------------------------- +7 | tracked def f: F // error + | ^^^^^^^^^^^^^^^^ + | Modifier tracked is not allowed for this definition +-- [E156] Syntax Error: tests/neg/abstract-tracked.scala:14:14 --------------------------------------------------------- +14 | tracked val x = 1 // error + | ^^^^^^^^^^^^^^^^^ + | Modifier tracked is not allowed for this definition diff --git a/tests/neg/abstract-tracked.scala b/tests/neg/abstract-tracked.scala new file mode 100644 index 000000000000..ff4a7ea8174f --- /dev/null +++ b/tests/neg/abstract-tracked.scala @@ -0,0 +1,14 @@ +import scala.language.experimental.modularity +import scala.language.future + +tracked trait F // error + +trait G: + tracked def f: F // error + +tracked object O // error + +tracked class C // error + +def f = + tracked val x = 1 // error diff --git a/tests/neg/cc-poly-2.check b/tests/neg/cc-poly-2.check deleted file mode 100644 index 0615ce19b5ea..000000000000 --- a/tests/neg/cc-poly-2.check +++ /dev/null @@ -1,21 +0,0 @@ --- [E007] Type Mismatch Error: tests/neg/cc-poly-2.scala:13:15 --------------------------------------------------------- -13 | f[Nothing](d) // error - | ^ - | Found: (d : Test.D^) - | Required: Test.D - | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/cc-poly-2.scala:14:19 --------------------------------------------------------- -14 | f[CapSet^{c1}](d) // error - | ^ - | Found: (d : Test.D^) - | Required: Test.D^{c1} - | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/cc-poly-2.scala:16:20 --------------------------------------------------------- -16 | val _: D^{c1} = x // error - | ^ - | Found: (x : Test.D^{d}) - | Required: Test.D^{c1} - | - | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i13091.check b/tests/neg/i13091.check index 5cd793a9cfcb..bf8207d85e51 100644 --- a/tests/neg/i13091.check +++ b/tests/neg/i13091.check @@ -1,15 +1,9 @@ --- [E190] Potential Issue Warning: tests/neg/i13091.scala:7:17 --------------------------------------------------------- -7 |def test: Unit = new Foo // error: class Foo is marked @experimental ... - | ^^^^^^^ - | Discarded non-Unit value of type Foo. You may want to use `()`. +-- Error: tests/neg/i13091.scala:7:16 ---------------------------------------------------------------------------------- +7 |def test = (new Foo): Unit // error: class Foo is marked @experimental ... + | ^^^ + | class Foo is marked @experimental | - | longer explanation available when compiling with `-explain` --- Error: tests/neg/i13091.scala:7:21 ---------------------------------------------------------------------------------- -7 |def test: Unit = new Foo // error: class Foo is marked @experimental ... - | ^^^ - | class Foo is marked @experimental - | - | Experimental definition may only be used under experimental mode: - | 1. in a definition marked as @experimental, or - | 2. an experimental feature is imported at the package level, or - | 3. compiling with the -experimental compiler flag. + | Experimental definition may only be used under experimental mode: + | 1. in a definition marked as @experimental, or + | 2. an experimental feature is imported at the package level, or + | 3. compiling with the -experimental compiler flag. diff --git a/tests/neg/i13091.scala b/tests/neg/i13091.scala index 549fdf6d0fae..8ee77efa37c1 100644 --- a/tests/neg/i13091.scala +++ b/tests/neg/i13091.scala @@ -4,4 +4,4 @@ import annotation.experimental @experimental class Foo -def test: Unit = new Foo // error: class Foo is marked @experimental ... +def test = (new Foo): Unit // error: class Foo is marked @experimental ... diff --git a/tests/neg/i18408a.check b/tests/neg/i18408a.check index ff278e6fe5cb..2fb701f90712 100644 --- a/tests/neg/i18408a.check +++ b/tests/neg/i18408a.check @@ -7,7 +7,7 @@ -- [E190] Potential Issue Warning: tests/neg/i18408a.scala:3:15 -------------------------------------------------------- 3 |def test1 = fa(42) | ^^ - | Discarded non-Unit value of type Int. You may want to use `()`. + | Discarded non-Unit value of type Int. Add `: Unit` to discard silently. | | longer explanation available when compiling with `-explain` -- [E129] Potential Issue Warning: tests/neg/i18408a.scala:4:16 -------------------------------------------------------- diff --git a/tests/neg/i18408b.check b/tests/neg/i18408b.check index 7c72833fe5ad..069c8345742b 100644 --- a/tests/neg/i18408b.check +++ b/tests/neg/i18408b.check @@ -7,7 +7,7 @@ -- [E190] Potential Issue Warning: tests/neg/i18408b.scala:3:15 -------------------------------------------------------- 3 |def test1 = fa(42) | ^^ - | Discarded non-Unit value of type Int. You may want to use `()`. + | Discarded non-Unit value of type Int. Add `: Unit` to discard silently. | | longer explanation available when compiling with `-explain` -- [E129] Potential Issue Warning: tests/neg/i18408b.scala:4:16 -------------------------------------------------------- diff --git a/tests/neg/i18408c.check b/tests/neg/i18408c.check index 078f42bb0006..994e6b499371 100644 --- a/tests/neg/i18408c.check +++ b/tests/neg/i18408c.check @@ -7,7 +7,7 @@ -- [E190] Potential Issue Warning: tests/neg/i18408c.scala:3:15 -------------------------------------------------------- 3 |def test1 = fa(42) | ^^ - | Discarded non-Unit value of type Int. You may want to use `()`. + | Discarded non-Unit value of type Int. Add `: Unit` to discard silently. | | longer explanation available when compiling with `-explain` -- [E129] Potential Issue Warning: tests/neg/i18408c.scala:4:16 -------------------------------------------------------- diff --git a/tests/neg/i21841.check b/tests/neg/i21841.check new file mode 100644 index 000000000000..e119d650b360 --- /dev/null +++ b/tests/neg/i21841.check @@ -0,0 +1,6 @@ +-- [E108] Declaration Error: tests/neg/i21841.scala:20:13 -------------------------------------------------------------- +20 | case v[T](l, r) => () // error + | ^^^^^^^^^^ + | Option[(Test.Expr[Test.T], Test.Expr[Test.T])] is not a valid result type of an unapplySeq method of an extractor. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i21841.scala b/tests/neg/i21841.scala new file mode 100644 index 000000000000..8a280abc2cf8 --- /dev/null +++ b/tests/neg/i21841.scala @@ -0,0 +1,22 @@ +object Test { + + sealed trait T + sealed trait Arrow[A, B] + + type ArgsTo[S1, Target] <: NonEmptyTuple = S1 match { + case Arrow[a, Target] => Tuple1[Expr[a]] + case Arrow[a, b] => Expr[a] *: ArgsTo[b, Target] + } + + sealed trait Expr[S] : + def unapplySeq[Target](e: Expr[Target]): Option[ArgsTo[S, Target]] = ??? + + case class Variable[S](id: String) extends Expr[S] + + val v = Variable[Arrow[T, Arrow[T, T]]]("v") + val e : Expr[T] = ??? + + e match + case v[T](l, r) => () // error + case _ => () +} diff --git a/tests/neg/i21944.check b/tests/neg/i21944.check new file mode 100644 index 000000000000..591447c6a510 --- /dev/null +++ b/tests/neg/i21944.check @@ -0,0 +1,4 @@ +-- [E206] Syntax Error: tests/neg/i21944.scala:1:5 --------------------------------------------------------------------- +1 |enum Orientation extends AnyVal: // error + | ^ + | class Orientation may not be a value class diff --git a/tests/neg/i21944.scala b/tests/neg/i21944.scala new file mode 100644 index 000000000000..bf335e56c671 --- /dev/null +++ b/tests/neg/i21944.scala @@ -0,0 +1,2 @@ +enum Orientation extends AnyVal: // error + case North, South, East, West diff --git a/tests/neg/i22192.check b/tests/neg/i22192.check new file mode 100644 index 000000000000..35ed426ccb11 --- /dev/null +++ b/tests/neg/i22192.check @@ -0,0 +1,30 @@ +-- Error: tests/neg/i22192.scala:6:12 ---------------------------------------------------------------------------------- +6 | case City(iam = n, confused = p) => // error // error + | ^^^^^^^ + | No element named `iam` is defined in selector type City +-- Error: tests/neg/i22192.scala:6:21 ---------------------------------------------------------------------------------- +6 | case City(iam = n, confused = p) => // error // error + | ^^^^^^^^^^^^ + | No element named `confused` is defined in selector type City +-- [E006] Not Found Error: tests/neg/i22192.scala:7:7 ------------------------------------------------------------------ +7 | s"$n has a population of $p" // error // error + | ^ + | Not found: n + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/i22192.scala:7:30 ----------------------------------------------------------------- +7 | s"$n has a population of $p" // error // error + | ^ + | Not found: p + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg/i22192.scala:10:12 --------------------------------------------------------------------------------- +10 | case Some(iam = n) => // error + | ^^^^^^^ + | No element named `iam` is defined in selector type City +-- [E006] Not Found Error: tests/neg/i22192.scala:11:4 ----------------------------------------------------------------- +11 | n // error + | ^ + | Not found: n + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i22192.scala b/tests/neg/i22192.scala new file mode 100644 index 000000000000..16eeb643dee0 --- /dev/null +++ b/tests/neg/i22192.scala @@ -0,0 +1,11 @@ +import scala.language.experimental.namedTuples + +case class City(name: String, population: Int) + +def getCityInfo(city: City) = city match + case City(iam = n, confused = p) => // error // error + s"$n has a population of $p" // error // error + +def getCityInfo1(city: Option[City]) = city match + case Some(iam = n) => // error + n // error \ No newline at end of file diff --git a/tests/neg/i22192a.check b/tests/neg/i22192a.check new file mode 100644 index 000000000000..8bfbcb127c54 --- /dev/null +++ b/tests/neg/i22192a.check @@ -0,0 +1,20 @@ +-- Error: tests/neg/i22192a.scala:6:12 --------------------------------------------------------------------------------- +6 | case Some(iam = n) => // error + | ^^^^^^^ + | No element named `iam` is defined in selector type (name : String) +-- [E006] Not Found Error: tests/neg/i22192a.scala:7:4 ----------------------------------------------------------------- +7 | n // error + | ^ + | Not found: n + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg/i22192a.scala:11:12 -------------------------------------------------------------------------------- +11 | case Some(iam = n) => // error + | ^^^^^^^ + | No element named `iam` is defined in selector type (name : String, population : Int) +-- [E006] Not Found Error: tests/neg/i22192a.scala:12:4 ---------------------------------------------------------------- +12 | n // error + | ^ + | Not found: n + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i22192a.scala b/tests/neg/i22192a.scala new file mode 100644 index 000000000000..6460e613d517 --- /dev/null +++ b/tests/neg/i22192a.scala @@ -0,0 +1,13 @@ +import scala.language.experimental.namedTuples + +type City = (name: String) + +def getCityInfo(city: Option[City]) = city match + case Some(iam = n) => // error + n // error + case _ => + +def getCiryInfo1(city: Option[(name: String, population: Int)]) = city match + case Some(iam = n) => // error + n // error + case _ => \ No newline at end of file diff --git a/tests/neg/i22320.check b/tests/neg/i22320.check new file mode 100644 index 000000000000..3bada9a0b73c --- /dev/null +++ b/tests/neg/i22320.check @@ -0,0 +1,12 @@ +-- [E008] Not Found Error: tests/neg/i22320.scala:19:19 ---------------------------------------------------------------- +19 | val z = system.z // error + | ^^^^^^^^ + | value z is not a member of a.System. + | An extension method was tried, but could not be fully constructed: + | + | a.z(system) + | + | failed with: + | + | Found: (system : a.System) + | Required: a.SimulatedSystem diff --git a/tests/neg/i22320.scala b/tests/neg/i22320.scala new file mode 100644 index 000000000000..4a9eccf08474 --- /dev/null +++ b/tests/neg/i22320.scala @@ -0,0 +1,19 @@ +package a: + opaque type System = Any + opaque type SimulatedSystem <: System = System + + extension (system: System) + def x: BigInt = ??? + def y: BigInt = ??? + end extension + + extension (system: SimulatedSystem) + def z: BigInt = ??? + end extension + +package b: + import a.* + def issue(system: System) = + val x = system.x + val y = system.y + val z = system.z // error \ No newline at end of file diff --git a/tests/neg/i22333.check b/tests/neg/i22333.check new file mode 100644 index 000000000000..8a4e5acd5464 --- /dev/null +++ b/tests/neg/i22333.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/i22333.scala:5:33 ------------------------------------------------------------- +5 |val _ = Container[(1,2,3)].guard((6, 8): Tuple.Tail[(5, 6, 8)]) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: ((6 : Int), (8 : Int)) + | Required: ((2 : Int), (3 : Int)) + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i22333.scala b/tests/neg/i22333.scala new file mode 100644 index 000000000000..8cab77967d0c --- /dev/null +++ b/tests/neg/i22333.scala @@ -0,0 +1,5 @@ + +class Container[Rs <: Tuple]: + def guard(a: Tuple.Tail[Rs]): Unit = () + +val _ = Container[(1,2,3)].guard((6, 8): Tuple.Tail[(5, 6, 8)]) // error diff --git a/tests/neg/i2887b.check b/tests/neg/i2887b.check index 5bd5f570fbf7..eb89f8582e5a 100644 --- a/tests/neg/i2887b.check +++ b/tests/neg/i2887b.check @@ -4,7 +4,7 @@ | 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-decode-stacktraces. + | For the unprocessed stack trace, compile with -Xno-enrich-error-messages. | A recurring operation is (inner to outer): | | try to instantiate Z[Z] diff --git a/tests/neg/named-tuples-4.check b/tests/neg/named-tuples-4.check new file mode 100644 index 000000000000..2ebf9981b93c --- /dev/null +++ b/tests/neg/named-tuples-4.check @@ -0,0 +1,16 @@ +-- Error: tests/neg/named-tuples-4.scala:10:35 ------------------------------------------------------------------------- +10 | case PersonCaseClass(name = n, age) => () // error + | ^^^ + | Illegal combination of named and unnamed tuple elements +-- Error: tests/neg/named-tuples-4.scala:11:31 ------------------------------------------------------------------------- +11 | case PersonCaseClass(name, age = a) => () // error + | ^^^^^^^ + | Illegal combination of named and unnamed tuple elements +-- Error: tests/neg/named-tuples-4.scala:15:20 ------------------------------------------------------------------------- +15 | case (name = n, age) => () // error + | ^^^ + | Illegal combination of named and unnamed tuple elements +-- Error: tests/neg/named-tuples-4.scala:16:16 ------------------------------------------------------------------------- +16 | case (name, age = a) => () // error + | ^^^^^^^ + | Illegal combination of named and unnamed tuple elements diff --git a/tests/neg/named-tuples-4.scala b/tests/neg/named-tuples-4.scala new file mode 100644 index 000000000000..60621c57baf5 --- /dev/null +++ b/tests/neg/named-tuples-4.scala @@ -0,0 +1,16 @@ +import language.experimental.namedTuples +import scala.annotation.experimental + +@experimental object Test: + + case class PersonCaseClass(name: String, age: Int) + + val personCaseClass = PersonCaseClass("Bob", 33) + personCaseClass match + case PersonCaseClass(name = n, age) => () // error + case PersonCaseClass(name, age = a) => () // error + + val person = (name = "Bob", age = 33): (name: String, age: Int) + person match + case (name = n, age) => () // error + case (name, age = a) => () // error diff --git a/tests/neg/spaces-vs-tabs.check b/tests/neg/spaces-vs-tabs.check index f8374618f0fd..ce55267b0cce 100644 --- a/tests/neg/spaces-vs-tabs.check +++ b/tests/neg/spaces-vs-tabs.check @@ -28,9 +28,3 @@ | The start of this line does not match any of the previous indentation widths. | Indentation width of current line : 1 tab, 2 spaces | This falls between previous widths: 1 tab and 1 tab, 4 spaces --- [E190] Potential Issue Warning: tests/neg/spaces-vs-tabs.scala:13:7 ------------------------------------------------- -13 | 1 - | ^ - | Discarded non-Unit value of type Int. You may want to use `()`. - | - | longer explanation available when compiling with `-explain` diff --git a/tests/neg/spaces-vs-tabs.scala b/tests/neg/spaces-vs-tabs.scala index 4f48d784eb7d..eafc5209ca47 100644 --- a/tests/neg/spaces-vs-tabs.scala +++ b/tests/neg/spaces-vs-tabs.scala @@ -10,6 +10,6 @@ object Test: object Test2: if true then - 1 + () else 2 // error diff --git a/tests/neg/tracked.check b/tests/neg/tracked.check index 14a4d2a08300..3494c401a007 100644 --- a/tests/neg/tracked.check +++ b/tests/neg/tracked.check @@ -6,22 +6,6 @@ 7 | def foo(tracked a: Int) = // error | ^ | ':' expected, but identifier found --- Error: tests/neg/tracked.scala:8:12 --------------------------------------------------------------------------------- -8 | tracked val b: Int = 2 // error - | ^^^ - | end of statement expected but 'val' found --- Error: tests/neg/tracked.scala:11:10 -------------------------------------------------------------------------------- -11 | tracked object Foo // error // error - | ^^^^^^ - | end of statement expected but 'object' found --- Error: tests/neg/tracked.scala:14:10 -------------------------------------------------------------------------------- -14 | tracked class D // error // error - | ^^^^^ - | end of statement expected but 'class' found --- Error: tests/neg/tracked.scala:17:10 -------------------------------------------------------------------------------- -17 | tracked type T = Int // error // error - | ^^^^ - | end of statement expected but 'type' found -- Error: tests/neg/tracked.scala:20:25 -------------------------------------------------------------------------------- 20 | given g2: (tracked val x: Int) => C = C(x) // error | ^^^^^^^^^^^^^^^^^^ @@ -30,21 +14,19 @@ 4 |class C2(tracked var x: Int) // error | ^ | mutable variables may not be `tracked` --- [E006] Not Found Error: tests/neg/tracked.scala:11:2 ---------------------------------------------------------------- -11 | tracked object Foo // error // error - | ^^^^^^^ - | Not found: tracked - | - | longer explanation available when compiling with `-explain` --- [E006] Not Found Error: tests/neg/tracked.scala:14:2 ---------------------------------------------------------------- -14 | tracked class D // error // error - | ^^^^^^^ - | Not found: tracked - | - | longer explanation available when compiling with `-explain` --- [E006] Not Found Error: tests/neg/tracked.scala:17:2 ---------------------------------------------------------------- -17 | tracked type T = Int // error // error - | ^^^^^^^ - | Not found: tracked - | - | longer explanation available when compiling with `-explain` +-- [E156] Syntax Error: tests/neg/tracked.scala:8:16 ------------------------------------------------------------------- +8 | tracked val b: Int = 2 // error + | ^^^^^^^^^^^^^^^^^^^^^^ + | Modifier tracked is not allowed for this definition +-- [E156] Syntax Error: tests/neg/tracked.scala:11:17 ------------------------------------------------------------------ +11 | tracked object Foo // error + | ^^^^^^^^^^^^^^^^^^ + | Modifier tracked is not allowed for this definition +-- [E156] Syntax Error: tests/neg/tracked.scala:14:16 ------------------------------------------------------------------ +14 | tracked class D // error + | ^^^^^^^^^^^^^^^ + | Modifier tracked is not allowed for this definition +-- [E156] Syntax Error: tests/neg/tracked.scala:17:15 ------------------------------------------------------------------ +17 | tracked type T = Int // error + | ^^^^^^^^^^^^^^^^^^^^ + | Modifier tracked is not allowed for this definition diff --git a/tests/neg/tracked.scala b/tests/neg/tracked.scala index 9f874ca3c0da..3d6c1a14fc55 100644 --- a/tests/neg/tracked.scala +++ b/tests/neg/tracked.scala @@ -8,13 +8,13 @@ object A: tracked val b: Int = 2 // error object B: - tracked object Foo // error // error + tracked object Foo // error object C: - tracked class D // error // error + tracked class D // error object D: - tracked type T = Int // error // error + tracked type T = Int // error object E: given g2: (tracked val x: Int) => C = C(x) // error diff --git a/tests/pending/pos/function-contravariance.scala b/tests/pending/pos/function-contravariance.scala new file mode 100644 index 000000000000..4df88f890bed --- /dev/null +++ b/tests/pending/pos/function-contravariance.scala @@ -0,0 +1,10 @@ +import language.experimental.namedTuples + +class A: + type T + +class B extends A + +val f: (x: A) => x.T = ??? +val g: (x: B) => x.T = f // OK +val h: (x: A) => x.T = g // error diff --git a/tests/pos-custom-args/captures/Buffer.scala b/tests/pos-custom-args/captures/Buffer.scala index 2412e5b388ca..9ecd51ffa62a 100644 --- a/tests/pos-custom-args/captures/Buffer.scala +++ b/tests/pos-custom-args/captures/Buffer.scala @@ -10,7 +10,7 @@ trait Buffer[A]: val s = 10 // capture checking: we need the copy since we box/unbox on g* on the next line // TODO: This looks fishy, need to investigate - // Alternative would be to mark `f` as @unbox. It's not inferred + // Alternative would be to mark `f` as @use. It's not inferred // since `^ appears in a function result, not under a box. val newElems = new Array[(IterableOnce[A]^{f})](s) var i = 0 diff --git a/tests/pos-custom-args/captures/annot-i20272a.scala b/tests/pos-custom-args/captures/annot-i20272a.scala new file mode 100644 index 000000000000..e04ee1efcd99 --- /dev/null +++ b/tests/pos-custom-args/captures/annot-i20272a.scala @@ -0,0 +1,20 @@ +import language.experimental.captureChecking + + trait Iterable[T] { self: Iterable[T]^ => + def map[U](f: T => U): Iterable[U]^{this, f} + } + + object Test { + def assertEquals[A, B](a: A, b: B): Boolean = ??? + + def foo[T](level: Int, lines: Iterable[T]) = + lines.map(x => x) + + def bar(messages: Iterable[String]) = + foo(1, messages) + + val it: Iterable[String] = ??? + val msgs = bar(it) + + assertEquals(msgs, msgs) + } diff --git a/tests/pos-custom-args/captures/boxed-use.scala b/tests/pos-custom-args/captures/boxed-use.scala new file mode 100644 index 000000000000..5dbdab7a6935 --- /dev/null +++ b/tests/pos-custom-args/captures/boxed-use.scala @@ -0,0 +1,17 @@ +class Box[A](val elem: A) +class CoBox[+A](val elem: A) + +def applyAll[A](fs: Box[A => Unit], x: A): Box[() ->{fs*} Unit] = + Box(() => fs.elem(x)) + +def applyAllCo[A](fs: CoBox[A => Unit], x: A): CoBox[() ->{fs*} Unit] = + CoBox(() => fs.elem(x)) + +// Same with inferred result types +def test = + def applyAll[A](fs: Box[A => Unit], x: A) = + Box(() => fs.elem(x)) + + def applyAllCo[A](fs: CoBox[A => Unit], x: A) = + CoBox(() => fs.elem(x)) + diff --git a/tests/pos/byname-purefuns-adapt/A_1.scala b/tests/pos-custom-args/captures/byname-purefuns-adapt/A_1.scala similarity index 100% rename from tests/pos/byname-purefuns-adapt/A_1.scala rename to tests/pos-custom-args/captures/byname-purefuns-adapt/A_1.scala diff --git a/tests/pos/byname-purefuns-adapt/B_2.scala b/tests/pos-custom-args/captures/byname-purefuns-adapt/B_2.scala similarity index 100% rename from tests/pos/byname-purefuns-adapt/B_2.scala rename to tests/pos-custom-args/captures/byname-purefuns-adapt/B_2.scala diff --git a/tests/pos/cc-experimental.scala b/tests/pos-custom-args/captures/cc-experimental.scala similarity index 100% rename from tests/pos/cc-experimental.scala rename to tests/pos-custom-args/captures/cc-experimental.scala diff --git a/tests/pos/cc-poly-1.scala b/tests/pos-custom-args/captures/cc-poly-1.scala similarity index 100% rename from tests/pos/cc-poly-1.scala rename to tests/pos-custom-args/captures/cc-poly-1.scala diff --git a/tests/pos/cc-poly-source-capability.scala b/tests/pos-custom-args/captures/cc-poly-source-capability.scala similarity index 73% rename from tests/pos/cc-poly-source-capability.scala rename to tests/pos-custom-args/captures/cc-poly-source-capability.scala index 3b6c0bde1398..6f6bdd91d20a 100644 --- a/tests/pos/cc-poly-source-capability.scala +++ b/tests/pos-custom-args/captures/cc-poly-source-capability.scala @@ -1,7 +1,7 @@ import language.experimental.captureChecking import annotation.experimental import caps.{CapSet, Capability} -import caps.unbox +import caps.use @experimental object Test: @@ -18,15 +18,16 @@ import caps.unbox def allListeners: Set[Listener^{X^}] = listeners - def test1(async1: Async, @unbox others: List[Async]) = + def test1(async1: Async, @use others: List[Async]) = val src = Source[CapSet^{async1, others*}] + val _: Set[Listener^{async1, others*}] = src.allListeners val lst1 = listener(async1) val lsts = others.map(listener) val _: List[Listener^{others*}] = lsts src.register{lst1} src.register(listener(async1)) - lsts.foreach(src.register) - others.map(listener).foreach(src.register) + lsts.foreach(src.register(_)) // TODO: why we need to use _ explicitly here? + others.map(listener).foreach(src.register(_)) val ls = src.allListeners val _: Set[Listener^{async1, others*}] = ls diff --git a/tests/pos/cc-poly-source.scala b/tests/pos-custom-args/captures/cc-poly-source.scala similarity index 93% rename from tests/pos/cc-poly-source.scala rename to tests/pos-custom-args/captures/cc-poly-source.scala index 4cfbbaa06936..2de5c6d67340 100644 --- a/tests/pos/cc-poly-source.scala +++ b/tests/pos-custom-args/captures/cc-poly-source.scala @@ -1,7 +1,7 @@ import language.experimental.captureChecking import annotation.experimental import caps.{CapSet, Capability} -import caps.unbox +import caps.use @experimental object Test: @@ -25,7 +25,7 @@ import caps.unbox val ls = src.allListeners val _: Set[Listener^{lbl1, lbl2}] = ls - def test2(@unbox lbls: List[Label^]) = + def test2(@use lbls: List[Label^]) = def makeListener(lbl: Label^): Listener^{lbl} = ??? val listeners = lbls.map(makeListener) val src = Source[CapSet^{lbls*}] diff --git a/tests/pos-custom-args/captures/cc-poly-varargs.scala b/tests/pos-custom-args/captures/cc-poly-varargs.scala index ac76c47d6dd5..7f04ed987b28 100644 --- a/tests/pos-custom-args/captures/cc-poly-varargs.scala +++ b/tests/pos-custom-args/captures/cc-poly-varargs.scala @@ -1,20 +1,19 @@ -trait Cancellable +abstract class Source[+T, Cap^]: + def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{this, f} = ??? -abstract class Source[+T, Cap^] - -extension[T, Cap^](src: Source[T, Cap]^) - def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{src, f} = ??? +// TODO: The extension version of `transformValuesWith` doesn't work currently. +// extension[T, Cap^](src: Source[T, Cap]^) +// def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{src, f} = ??? 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^} = +def either[T1, T2, 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) - - - - - - - + race[Either[T1, T2], Cap](left, right) + // Explicit type arguments are required here because the second argument + // is inferred as `CapSet^{Cap^}` instead of `Cap`. + // Although `CapSet^{Cap^}` subsumes `Cap` in terms of capture sets, + // `Cap` is not a subtype of `CapSet^{Cap^}` in terms of subtyping. diff --git a/tests/pos-custom-args/captures/dep-reach.scala b/tests/pos-custom-args/captures/dep-reach.scala index c81197aa738d..1ee6fc3d17f9 100644 --- a/tests/pos-custom-args/captures/dep-reach.scala +++ b/tests/pos-custom-args/captures/dep-reach.scala @@ -1,10 +1,10 @@ -import caps.unbox +import caps.use object Test: class C type Proc = () => Unit def f(c: C^, d: C^): () ->{c, d} Unit = - def foo(@unbox xs: Proc*): () ->{xs*} Unit = + def foo(@use xs: Proc*): () ->{xs*} Unit = xs.head val a: () ->{c} Unit = () => () val b: () ->{d} Unit = () => () @@ -13,7 +13,7 @@ object Test: def g(c: C^, d: C^): () ->{c, d} Unit = - def foo(@unbox xs: Seq[() => Unit]): () ->{xs*} Unit = + def foo(@use xs: Seq[() => Unit]): () ->{xs*} Unit = xs.head val a: () ->{c} Unit = () => () diff --git a/tests/pos/dotty-experimental.scala b/tests/pos-custom-args/captures/dotty-experimental.scala similarity index 100% rename from tests/pos/dotty-experimental.scala rename to tests/pos-custom-args/captures/dotty-experimental.scala diff --git a/tests/pos-custom-args/captures/erased-methods.scala b/tests/pos-custom-args/captures/erased-methods.scala new file mode 100644 index 000000000000..911c779e08e5 --- /dev/null +++ b/tests/pos-custom-args/captures/erased-methods.scala @@ -0,0 +1,20 @@ +import language.experimental.saferExceptions +import language.experimental.erasedDefinitions +import language.experimental.captureChecking + +class Ex1 extends Exception("Ex1") +class Ex2 extends Exception("Ex2") +class Ex3 extends Exception("Ex3") + +def foo8a(i: Int) = + (erased xx1: CanThrow[Ex2]^) ?=> throw new Ex2 + +def foo9a(i: Int) + : (erased x$0: CanThrow[Ex3]^) + ?=> (erased x$1: CanThrow[Ex2]^) + ?=> (erased x$2: CanThrow[Ex1]^) + ?=> Unit + = (erased x$1: CanThrow[Ex3]^) + ?=> (erased x$2: CanThrow[Ex2]^) + ?=> (erased x$3: CanThrow[Ex1]^) + ?=> throw new Ex3 diff --git a/tests/pos-custom-args/captures/gears-problem-poly.scala b/tests/pos-custom-args/captures/gears-problem-poly.scala new file mode 100644 index 000000000000..fdbcf37a35a6 --- /dev/null +++ b/tests/pos-custom-args/captures/gears-problem-poly.scala @@ -0,0 +1,29 @@ +import language.experimental.captureChecking +import caps.{use, CapSet} + +trait Future[+T]: + def await: T + +trait Channel[+T]: + def read(): Ok[T] + +class Collector[T, C^](val futures: Seq[Future[T]^{C^}]): + val results: Channel[Future[T]^{C^}] = ??? +end Collector + +class Result[+T, +E]: + def get: T = ??? + +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^}]) + def awaitAllPoly = + val collector = Collector(fs) + 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) diff --git a/tests/pos-custom-args/captures/hk-param.scala b/tests/pos-custom-args/captures/hk-param.scala index df4335069bbb..325a2b55a480 100644 --- a/tests/pos-custom-args/captures/hk-param.scala +++ b/tests/pos-custom-args/captures/hk-param.scala @@ -1,3 +1,5 @@ +//> using options -source 3.5 +// (to make sure we use the unsealed policy) /** Concrete collection type: View */ trait View[+A] extends Itable[A], ILike[A, [X] =>> View[X]^]: override def fromIterable[B](c: Itable[B]^): View[B]^{c} = ??? diff --git a/tests/pos-custom-args/captures/i15923-cases.scala b/tests/pos-custom-args/captures/i15923-cases.scala index 4b5a36f208ec..c48ca430c440 100644 --- a/tests/pos-custom-args/captures/i15923-cases.scala +++ b/tests/pos-custom-args/captures/i15923-cases.scala @@ -2,6 +2,10 @@ 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 foo(x: Id[Cap^]) = { + x(_.use()) // OK under sealed policy +} + def bar(io: Cap^, x: Id[Cap^{io}]) = { x(_.use()) } diff --git a/tests/pos/i18699.scala b/tests/pos-custom-args/captures/i18699.scala similarity index 72% rename from tests/pos/i18699.scala rename to tests/pos-custom-args/captures/i18699.scala index 1937d7dca8c5..5a0a9357f774 100644 --- a/tests/pos/i18699.scala +++ b/tests/pos-custom-args/captures/i18699.scala @@ -1,9 +1,9 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use trait Cap: def use: Int = 42 -def test2(@unbox cs: List[Cap^]): Unit = +def test2(@use cs: List[Cap^]): Unit = val t0: Cap^{cs*} = cs.head // error var t1: Cap^{cs*} = cs.head // error diff --git a/tests/pos/i18909.scala b/tests/pos-custom-args/captures/i18909.scala similarity index 100% rename from tests/pos/i18909.scala rename to tests/pos-custom-args/captures/i18909.scala diff --git a/tests/pos/i20135.scala b/tests/pos-custom-args/captures/i20135.scala similarity index 100% rename from tests/pos/i20135.scala rename to tests/pos-custom-args/captures/i20135.scala diff --git a/tests/pos/i21390.TrieMap.scala b/tests/pos-custom-args/captures/i21390.TrieMap.scala similarity index 100% rename from tests/pos/i21390.TrieMap.scala rename to tests/pos-custom-args/captures/i21390.TrieMap.scala diff --git a/tests/pos-custom-args/captures/i21620.scala b/tests/pos-custom-args/captures/i21620.scala deleted file mode 100644 index b2c382aa4c75..000000000000 --- a/tests/pos-custom-args/captures/i21620.scala +++ /dev/null @@ -1,11 +0,0 @@ -class C -def test(x: C^) = - def foo() = - x - () - val f = () => - // println() // uncomenting would give an error, but with - // a different way of handling curried functions should be OK - () => foo() - val _: () -> () ->{x} Unit = f - () diff --git a/tests/pos-custom-args/captures/i21646.scala b/tests/pos-custom-args/captures/i21646.scala new file mode 100644 index 000000000000..92aba9fda5d1 --- /dev/null +++ b/tests/pos-custom-args/captures/i21646.scala @@ -0,0 +1,13 @@ +import language.experimental.captureChecking +import caps.Capability + +trait File extends Capability + +class Resource[T <: Capability](gen: T): + def use[U](f: T => U): U = + f(gen) // OK, was error under unsealed + +@main def run = + val myFile: File = ??? + val r = Resource(myFile) // now ok, was error + () diff --git a/tests/pos/infer-exists.scala b/tests/pos-custom-args/captures/infer-exists.scala similarity index 100% rename from tests/pos/infer-exists.scala rename to tests/pos-custom-args/captures/infer-exists.scala diff --git a/tests/pos/invariant-cc.scala b/tests/pos-custom-args/captures/invariant-cc.scala similarity index 100% rename from tests/pos/invariant-cc.scala rename to tests/pos-custom-args/captures/invariant-cc.scala diff --git a/tests/pos-custom-args/captures/levels.scala b/tests/pos-custom-args/captures/levels.scala index cabd537442a5..4d9d759e86db 100644 --- a/tests/pos-custom-args/captures/levels.scala +++ b/tests/pos-custom-args/captures/levels.scala @@ -1,3 +1,5 @@ +//> using options -source 3.5 +// (to make sure we use the unsealed policy) class CC def test1(cap1: CC^) = @@ -14,10 +16,10 @@ def test2(cap1: CC^) = def setV(x: T): Unit = v = x def getV: T = v - val _ = Ref[String => String]((x: String) => x) // ok + val _ = Ref[String => String]((x: String) => x) val r = Ref((x: String) => x) def scope(cap3: CC^) = def g(x: String): String = if cap3 == cap3 then "" else "a" - r.setV(g) // error + r.setV(g) () diff --git a/tests/pos-custom-args/captures/path-use.scala b/tests/pos-custom-args/captures/path-use.scala index 5eb2b60fd218..629fa04315a7 100644 --- a/tests/pos-custom-args/captures/path-use.scala +++ b/tests/pos-custom-args/captures/path-use.scala @@ -1,4 +1,5 @@ import language.experimental.namedTuples +import caps.use class IO @@ -8,12 +9,14 @@ class C(val f: IO^): type Proc = () => Unit def test(io: IO^) = - val c = C(io) - val f = () => println(c.f) - val _: () ->{c.f} Unit = f + def test1(@use c: C { val f: IO^{io}}^{io}) = + val f = () => println(c.f) + val _: () ->{c.f} Unit = f - val x = c.procs - val _: List[() ->{c.procs*} Unit] = x + val x = c.procs + val _: List[() ->{c.procs*} Unit] = x + + val g = () => println(c.procs.head) + val _: () ->{c.procs*} Unit = g + test1(C(io)) - val g = () => println(c.procs.head) - val _: () ->{c.procs*} Unit = g diff --git a/tests/pos/polycap.scala b/tests/pos-custom-args/captures/polycap.scala similarity index 100% rename from tests/pos/polycap.scala rename to tests/pos-custom-args/captures/polycap.scala diff --git a/tests/pos/reach-capability.scala b/tests/pos-custom-args/captures/reach-capability.scala similarity index 85% rename from tests/pos/reach-capability.scala rename to tests/pos-custom-args/captures/reach-capability.scala index 50ea479ec3c1..08e82a1dabe9 100644 --- a/tests/pos/reach-capability.scala +++ b/tests/pos-custom-args/captures/reach-capability.scala @@ -1,7 +1,7 @@ import language.experimental.captureChecking import annotation.experimental import caps.Capability -import caps.unbox +import caps.use @experimental object Test2: @@ -12,7 +12,7 @@ import caps.unbox class Listener - def test2(@unbox lbls: List[Label]) = + def test2(@use lbls: List[Label]) = def makeListener(lbl: Label): Listener^{lbl} = ??? val listeners = lbls.map(makeListener) // should work diff --git a/tests/pos/reach-problem.scala b/tests/pos-custom-args/captures/reach-problem.scala similarity index 84% rename from tests/pos/reach-problem.scala rename to tests/pos-custom-args/captures/reach-problem.scala index d6b7b79011a6..f36a4af14ad2 100644 --- a/tests/pos/reach-problem.scala +++ b/tests/pos-custom-args/captures/reach-problem.scala @@ -1,11 +1,11 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use class Box[T](items: Seq[T^]): def getOne: T^{items*} = ??? object Box: - def getOne[T](@unbox items: Seq[T^]): T^{items*} = + def getOne[T](@use items: Seq[T^]): T^{items*} = val bx = Box(items) bx.getOne /* diff --git a/tests/pos-custom-args/captures/reaches.scala b/tests/pos-custom-args/captures/reaches.scala index ab0da9b67d18..cbe88e60020b 100644 --- a/tests/pos-custom-args/captures/reaches.scala +++ b/tests/pos-custom-args/captures/reaches.scala @@ -1,4 +1,4 @@ -import caps.unbox +import caps.use class C def f(xs: List[C^]) = @@ -22,7 +22,7 @@ extension [A](x: A) def :: (xs: List[A]): List[A] = ??? object Nil extends List[Nothing] -def runAll(@unbox xs: List[Proc]): Unit = +def runAll(@use xs: List[Proc]): Unit = var cur: List[() ->{xs*} Unit] = xs // OK, by revised VAR while cur.nonEmpty do val next: () ->{xs*} Unit = cur.head diff --git a/tests/pos-custom-args/captures/vars1.scala b/tests/pos-custom-args/captures/vars1.scala index 451b8988364f..c8d887e38463 100644 --- a/tests/pos-custom-args/captures/vars1.scala +++ b/tests/pos-custom-args/captures/vars1.scala @@ -8,7 +8,7 @@ object Test: var defaultIncompleteHandler: ErrorHandler = ??? @uncheckedCaptures var incompleteHandler: ErrorHandler = defaultIncompleteHandler - private val x = incompleteHandler.unsafeUnbox + private val x = incompleteHandler val _ : ErrorHandler = x val _ = x(1, "a") diff --git a/tests/pos-deep-subtype/inductive-implicits-bench.scala b/tests/pos-deep-subtype/inductive-implicits-bench.scala deleted file mode 100644 index aff8bffd8024..000000000000 --- a/tests/pos-deep-subtype/inductive-implicits-bench.scala +++ /dev/null @@ -1,127 +0,0 @@ -// Adapted from https://github.com/scala/compiler-benchmark/blob/master/corpus/induction/latest/inductive-implicits-bench.scala - -// With polymorphic implicit pruning: -// set resolvers in compilation += "pr-scala snapshots" at "https://scala-ci.typesafe.com/artifactory/scala-pr-validation-snapshots/" -// set scalaVersion in compilation := "2.13.0-pre-765b3ed-SNAPSHOT" -// -// Without polymorphic implicit pruning: -// set resolvers in compilation += "scala-integration" at "https://scala-ci.typesafe.com/artifactory/scala-integration/" -// set scalaVersion in compilation := "2.13.0-pre-1c56f0a" -// -// Then: -// cold -psource=induction -jvmArgs -Xss4M -jvmArgs -Xmx2G -// -// Nb. this is *very* slow without the pruning (> 400s). -// With the pruning: 10-20s on reasonable hardware. - -package shapeless { - sealed trait HList extends Product with Serializable - - final case class ::[+H, +T <: HList](head : H, tail : T) extends HList { - def ::[HH](h : HH) : HH :: H :: T = shapeless.::(h, this) - - override def toString = head match { - case _: ::[_, _] => "("+head.toString+") :: "+tail.toString - case _ => head.toString+" :: "+tail.toString - } - } - - sealed trait HNil extends HList { - def ::[H](h : H) = shapeless.::(h, this) - override def toString = "HNil" - } - - case object HNil extends HNil - - //@annotation.inductive - trait Selector[L <: HList, U] { - def apply(l: L): U - } - - object Selector { - def apply[L <: HList, U](implicit selector: Selector[L, U]): Selector[L, U] = selector - - implicit def inHead[H, T <: HList]: Selector[H :: T, H] = - new Selector[H :: T, H] { - def apply(l : H :: T) = l.head - } - - implicit def inTail[H, T <: HList, U] - (implicit st : Selector[T, U]): Selector[H :: T, U] = - new Selector[H :: T, U] { - def apply(l : H :: T) = st(l.tail) - } - } -} - -import shapeless.* - -object Test extends App { - val sel = Selector[L, Boolean] - - type L = - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - /* - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: Int :: - // - */ - Boolean :: - HNil -} diff --git a/tests/pos-macros/i21844/Macro.scala b/tests/pos-macros/i21844/Macro.scala new file mode 100644 index 000000000000..31a2c3a5e76f --- /dev/null +++ b/tests/pos-macros/i21844/Macro.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +object Macro: + inline def foo = ${ fooImpl } + def fooImpl(using Quotes): Expr[Int] = + '{ 123 } diff --git a/tests/pos-macros/i21844/SubClass.scala b/tests/pos-macros/i21844/SubClass.scala new file mode 100644 index 000000000000..54b0d970e515 --- /dev/null +++ b/tests/pos-macros/i21844/SubClass.scala @@ -0,0 +1,3 @@ +class SubClass extends SuperClass +object SubClass: + val foo: Int = Macro.foo diff --git a/tests/pos-macros/i21844/SuperClassWithLazyVal.scala b/tests/pos-macros/i21844/SuperClassWithLazyVal.scala new file mode 100644 index 000000000000..60c270f2b277 --- /dev/null +++ b/tests/pos-macros/i21844/SuperClassWithLazyVal.scala @@ -0,0 +1,2 @@ +class SuperClass: + lazy val xyz: Int = 123 diff --git a/tests/pos-macros/i21983/Test.scala b/tests/pos-macros/i21983/Test.scala new file mode 100644 index 000000000000..bf008583c7d9 --- /dev/null +++ b/tests/pos-macros/i21983/Test.scala @@ -0,0 +1,13 @@ +package example + +sealed trait Test + +object Test { + case object Foo extends Test + + val visitorType = mkVisitorType[Test] + + trait Visitor[A] { + type V[a] = visitorType.Out[a] + } +} diff --git a/tests/pos-macros/i21983/UsesTest.scala b/tests/pos-macros/i21983/UsesTest.scala new file mode 100644 index 000000000000..803e93c328c9 --- /dev/null +++ b/tests/pos-macros/i21983/UsesTest.scala @@ -0,0 +1,3 @@ +package example + +val _ = Test.Foo diff --git a/tests/pos-macros/i21983/VisitorMacros.scala b/tests/pos-macros/i21983/VisitorMacros.scala new file mode 100644 index 000000000000..5a5cc453c525 --- /dev/null +++ b/tests/pos-macros/i21983/VisitorMacros.scala @@ -0,0 +1,13 @@ +package example + +import scala.deriving.Mirror +import scala.quoted.* + +private def mkVisitorTypeImpl[T: Type](using q: Quotes): Expr[VisitorType[T]] = + '{new VisitorType[T]{}} + +transparent inline def mkVisitorType[T]: VisitorType[T] = ${ mkVisitorTypeImpl[T] } + +trait VisitorType[T] { + type Out[A] +} diff --git a/tests/pos/20519.scala b/tests/pos/20519.scala new file mode 100644 index 000000000000..866e24e8fb6f --- /dev/null +++ b/tests/pos/20519.scala @@ -0,0 +1,10 @@ +class Box[T](val value: T) + +def boo[F[_], A](e: F[Box[A]]): F[A] = ??? + +type Result[G[_], B] = G[Box[B]] + +def main = + val b: Result[Option, Int] = ??? + val c = boo(b) + c: Option[Int] diff --git a/tests/pos/20519b.scala b/tests/pos/20519b.scala new file mode 100644 index 000000000000..4dadf2aa70b6 --- /dev/null +++ b/tests/pos/20519b.scala @@ -0,0 +1,16 @@ +trait TCl[F[_]] + +def boo[F[_], A](e: F[Option[A]], ev: TCl[F]): Unit = () + +type Result[F[_], A] = F[Option[A]] + +@main def main = + summon[Result[Option, Int] =:= Option[Option[Int]]] + + val ev = new TCl[Option] {} + + val b: Result[Option, Int] = None + boo(b, ev) + + val b2: Option[Option[Int]] = None + boo(b2, ev) diff --git a/tests/pos/20519c.scala b/tests/pos/20519c.scala new file mode 100644 index 000000000000..91eb33a7b892 --- /dev/null +++ b/tests/pos/20519c.scala @@ -0,0 +1,20 @@ +object Main { + trait TCl[F[_]] + + implicit class Stx[F[_], A](e: F[Option[A]]) { + def boo(implicit ev: TCl[F]): Unit = () + } + + type Result[F[_], A] = F[Option[A]] + + implicit val t: TCl[Option] = new TCl[Option] {} + + def main(args: Array[String]): Unit = { + val b: Result[Option, Int] = None + b.boo + + // works without the alias: + val b2: Option[Option[Int]] = None + b2.boo + } +} diff --git a/tests/pos/Buffer.scala b/tests/pos/Buffer.scala deleted file mode 100644 index 2412e5b388ca..000000000000 --- a/tests/pos/Buffer.scala +++ /dev/null @@ -1,22 +0,0 @@ -import language.experimental.captureChecking - -// Extract of the problem in collection.mutable.Buffers -trait Buffer[A]: - - def apply(i: Int): A = ??? - - def flatMapInPlace(f: A => IterableOnce[A]^): this.type = { - val g = f - val s = 10 - // capture checking: we need the copy since we box/unbox on g* on the next line - // TODO: This looks fishy, need to investigate - // Alternative would be to mark `f` as @unbox. It's not inferred - // since `^ appears in a function result, not under a box. - val newElems = new Array[(IterableOnce[A]^{f})](s) - var i = 0 - while i < s do - val x = g(this(i)) - newElems(i) = x - i += 1 - this - } \ No newline at end of file diff --git a/tests/pos/abstract-tracked-2.scala b/tests/pos/abstract-tracked-2.scala new file mode 100644 index 000000000000..01e4ee84c548 --- /dev/null +++ b/tests/pos/abstract-tracked-2.scala @@ -0,0 +1,11 @@ +import scala.language.experimental.modularity +import scala.language.future + +abstract class Vec: + tracked val size: Int + +@main def main = + val v = new Vec: + val size0: size.type = 10 + val size = 10 + val size1: size.type = 10 diff --git a/tests/pos/abstract-tracked.scala b/tests/pos/abstract-tracked.scala new file mode 100644 index 000000000000..21812db9c04d --- /dev/null +++ b/tests/pos/abstract-tracked.scala @@ -0,0 +1,55 @@ +import scala.language.experimental.modularity +import scala.language.future + +trait F: + tracked val a: Int + +trait G: + tracked val b: Int + +trait H: + tracked val c: Int = 3 + +trait I extends F + +trait J extends F: + val a: Int = 1 + +class K(tracked val d: Int) + +class L + +trait M: + val f: Int + +class N extends F: + val a = 10 + +object Test: + val f = new F: + val a = 1 + val g = new G: + val b: 2 = 2 + val h = new H: + override val c = 4 + val i = new I: + val a = 5 + val j = new J: + override val a = 6 + val k = new K(7) + val l = new L { + tracked val e = 8 + } + val m = new M: + tracked val f = 9 + val n = new N + + summon[f.a.type <:< 1] + summon[g.b.type <:< 2] + summon[h.c.type <:< 4] + summon[i.a.type <:< 5] + summon[j.a.type <:< 6] + summon[k.d.type <:< 7] + // summon[l.e.type <:< 8] // unrelated issue -- error: e is not a member of L + summon[m.f.type <:< 9] + summon[n.a.type <:< 10] diff --git a/tests/pos/annot-17939.scala b/tests/pos/annot-17939.scala new file mode 100644 index 000000000000..604143183af2 --- /dev/null +++ b/tests/pos/annot-17939.scala @@ -0,0 +1,8 @@ +import scala.annotation.Annotation +class myRefined[T](f: T => Boolean) extends Annotation + +class Box[T](val x: T) +class Box2(val x: Int) + +class A(a: String @myRefined((x: Int) => Box(3).x == 3)) // crash +class A2(a2: String @myRefined((x: Int) => Box2(3).x == 3)) // works diff --git a/tests/pos/annot-19846.scala b/tests/pos/annot-19846.scala new file mode 100644 index 000000000000..ff2f8f632eab --- /dev/null +++ b/tests/pos/annot-19846.scala @@ -0,0 +1,9 @@ +package dependentAnnotation + +class lambdaAnnot(g: () => Int) extends annotation.StaticAnnotation + +def f(x: Int): Int @lambdaAnnot(() => x + 1) = x + +@main def main = + val y: Int = 5 + val z = f(y) diff --git a/tests/pos/annot-19846b.scala b/tests/pos/annot-19846b.scala new file mode 100644 index 000000000000..09c24a5cf3cf --- /dev/null +++ b/tests/pos/annot-19846b.scala @@ -0,0 +1,8 @@ +class qualified[T](predicate: T => Boolean) extends annotation.StaticAnnotation + +class EqualPair(val x: Int, val y: Int @qualified[Int](it => it == x)) + +@main def main = + val p = EqualPair(42, 42) + val y = p.y + println(42) diff --git a/tests/pos/annot-body.scala b/tests/pos/annot-body.scala new file mode 100644 index 000000000000..d8f6f79ae674 --- /dev/null +++ b/tests/pos/annot-body.scala @@ -0,0 +1,15 @@ +// This test checks that symbols in `BodyAnnotation` are not copied in +// `transformAnnot` during `PostTyper`. + +package json + +trait Reads[A] { + def reads(a: Any): A +} + +object JsMacroImpl { + inline def reads[A]: Reads[A] = + new Reads[A] { self => + def reads(a: Any) = ??? + } +} diff --git a/tests/pos/dependent-annot-type-param.scala b/tests/pos/dependent-annot-type-param.scala new file mode 100644 index 000000000000..174e9f78fba6 --- /dev/null +++ b/tests/pos/dependent-annot-type-param.scala @@ -0,0 +1,5 @@ +class annot[T] extends annotation.Annotation +class Box[T]() +def f(x: Int): Int @annot[Box[x.type]] = x +def test = + val foo = f(42) diff --git a/tests/pos/i17222.izumi.min.scala b/tests/pos/i17222.izumi.min.scala new file mode 100644 index 000000000000..06eadca73130 --- /dev/null +++ b/tests/pos/i17222.izumi.min.scala @@ -0,0 +1,7 @@ +class Foo: + type In + type Bar = { def go(in: In): Unit } + type False = false + +class Test: + def t1: Unit = valueOf[Foo#False] diff --git a/tests/pos/i17222.izumi.rep/InspectorBase.scala b/tests/pos/i17222.izumi.rep/InspectorBase.scala new file mode 100644 index 000000000000..56075831898d --- /dev/null +++ b/tests/pos/i17222.izumi.rep/InspectorBase.scala @@ -0,0 +1,59 @@ +package izumi.reflect.dottyreflection + +import scala.quoted.Quotes + +trait InspectorBase extends ReflectionUtil { + + val qctx: Quotes + import qctx.reflect._ + + protected def shift: Int + + // FIXME reimplement TrivialMacroLogger on Scala 3 + inline def debug: debug = valueOf[debug] + final type debug = false + + // println instead of report.info because report.info eats all the subsequent report.info's after first. + inline final protected def logStart(inline s: String): Unit = { + inline if (debug) println(" " * shift + currentPositionStr + s) + } + + inline final protected def log(inline s: String): Unit = { + inline if (debug) println(" " * shift + currentPositionStr + " -> " + s) + } + + inline final protected def logTpeAttrs[T](inline typeRepr: TypeRepr): Unit = { + inline if (debug) { + val tree = TypeTree.of(using typeRepr.asType) + val symbol = tree.symbol + System + .err.println( + currentPositionStr + ": " + + s"Attrs[${tree.show}]: type=${symbol.isType}, term=${symbol.isTerm}, packageDef=${symbol.isPackageDef}, classDef=${symbol.isClassDef}, typeDef=${symbol.isValDef}, defdef=${symbol.isDefDef}, bind=${symbol.isBind}, nosymbol=${symbol.isNoSymbol}" + ) + } + } + + private def currentPositionStr: String = { + val pos = qctx.reflect.Position.ofMacroExpansion + s"${pos.sourceFile.name}:${pos.endLine}" + } + +} + +object InspectorBase { + + private[reflect] inline def ifDebug[A](inline f: => Unit): Unit = { + inline if (valueOf[InspectorBase#debug]) { +//[error] ^^^^^^^^^^^^^ +//[error] izumi.reflect.dottyreflection.InspectorBase is not a legal path +//[error] since it has a member InternalTypeRefOrParamRef with possibly conflicting bounds Object{def underlying(ctx: Any): Nothing} <: ... <: Object{def underlying(ctx: Nothing): Matchable} + f + } + } + + private[reflect] inline def log(inline shift: Int, s: String): Unit = { + inline if (valueOf[InspectorBase#debug]) println(" " * shift + " -> " + s) + } + +} diff --git a/tests/pos/i17222.izumi.rep/ReflectionUtil.scala b/tests/pos/i17222.izumi.rep/ReflectionUtil.scala new file mode 100644 index 000000000000..c8ff667191af --- /dev/null +++ b/tests/pos/i17222.izumi.rep/ReflectionUtil.scala @@ -0,0 +1,331 @@ +package izumi.reflect.dottyreflection + +import scala.annotation.{tailrec, unused} +import scala.collection.immutable.Queue +import scala.quoted.Quotes + +private[dottyreflection] trait ReflectionUtil { this: InspectorBase => + + import qctx.reflect.* + + private final lazy val ignoredInIntersections0: Set[TypeRepr] = { + Set( + defn.AnyClass.typeRef, + defn.MatchableClass.typeRef, + defn.AnyRefClass.typeRef, + defn.ObjectClass.typeRef + ) + } + def ignoredInIntersections(repr: qctx.reflect.TypeRepr): Boolean = { + ignoredInIntersections0.exists(_ =:= repr) + } + def ignoredInUnions(repr: qctx.reflect.TypeRepr): Boolean = { + repr =:= defn.NothingClass.typeRef + } + + protected final def flattenAnd(tpe: TypeRepr): List[TypeRepr] = + tpe.dealias match { + case AndType(lhs, rhs) => flattenAnd(lhs) ++ flattenAnd(rhs) + case _ => List(tpe) + } + + protected final def flattenOr(tpe: TypeRepr): List[TypeRepr] = + tpe.dealias match { + case OrType(lhs, rhs) => flattenOr(lhs) ++ flattenOr(rhs) + case _ => List(tpe) + } + + protected final def intersectionUnionRefinementClassPartsOf(tpe: TypeRepr): List[TypeRepr] = { + tpe.dealias match { + case AndType(lhs, rhs) => + intersectionUnionRefinementClassPartsOf(lhs) ++ intersectionUnionRefinementClassPartsOf(rhs) + case OrType(lhs, rhs) => + intersectionUnionRefinementClassPartsOf(lhs) ++ intersectionUnionRefinementClassPartsOf(rhs) + case refinement: Refinement => + intersectionUnionRefinementClassPartsOf(refinement.parent) + case _ => + List(tpe) + } + } + + protected final def refinementInfoToParts(tpe0: TypeRepr): List[TypeRepr] = { + tpe0 match { + case ByNameType(tpe) => + refinementInfoToParts(tpe) + case MethodType(_, args, res) => + args.flatMap(refinementInfoToParts) ++ refinementInfoToParts(res) + case PolyType(_, tbounds, res) => + // FIXME we need to do FullDbInspector.inspectTypeReprToFullBases.lambdify/LightTypeTagImpl.makeLambdaOnlyBases.makeLambdaParents + // to wrap the unresolved type params in `res` into a lambda. + // As is, if type parameters are used in `res`, we'll add lots of trash types into db + tbounds.flatMap { case TypeBounds(lo, hi) => List(lo, hi) } ++ refinementInfoToParts(res) + case tpe => + List(tpe) + } + } + + protected final def flattenRefinements(ref: Refinement): (Queue[(Symbol, String, TypeRepr)], TypeRepr) = { + val refinementDecl = (ref.typeSymbol, ref.name, ref.info) + ref.parent match { + case innerRefinement: Refinement => + val (innerRefs, nonRefinementParent) = flattenRefinements(innerRefinement) + (innerRefs :+ refinementDecl, nonRefinementParent) + case nonRefinementParent => + (Queue(refinementDecl), nonRefinementParent) + } + } + + protected final def allPartsStrong(outerOwnerClassDefs: Set[Symbol], typeRepr: TypeRepr): Boolean = { + ReflectionUtil.allPartsStrong(using qctx)(shift, outerOwnerClassDefs, Set.empty, typeRepr) + } + + protected final def getClassDefOwners(symbol: Symbol): Set[Symbol] = { + ReflectionUtil.getClassDefOwners(using qctx)(symbol) + } + + import ReflectionUtil.reflectiveUncheckedNonOverloadedSelectable + import InternalContext.InternalContext + + extension (typeRef: TypeRef | ParamRef) { + protected final def _underlying: TypeRepr = { + // This works as a substitution for `TypeRef#underlying` call, + // but I'm not sure if it's a reliable substitution. + +// typeRef.typeSymbol.owner._typeRef.memberType(typeRef.typeSymbol) + + // No, It's not a reliable substitution. When used on a TypeParamRef it returns Any instead of the underlying TypeBounds + // https://github.com/lampepfl/dotty/issues/15799 + +// val underlying = typeRef +// .getClass.getMethods.collect { case m if m.getName == "underlying" => m }.head.invoke( +// typeRef, +// qctx.getClass.getMethods.collect { case m if m.getName == "ctx" => m }.head.invoke(qctx) +// ) +// underlying.asInstanceOf[TypeRepr] + + typeRef.asInstanceOf[InternalTypeRefOrParamRef].underlying(qctx._ctx) + } + } + + extension (typeRepr: TypeRepr) { + protected final def _paramVariancesIfHKTypeLambda: Option[List[Flags]] = { + try { + val params = typeRepr.asInstanceOf[InternalHKTypeLambda].typeParams + val flags = params.map(_.paramVariance(qctx._ctx)) + Some(flags) + } catch { + case _: NoSuchMethodException => None + } + } + + @tailrec + protected final def _dealiasSimplifiedFull: TypeRepr = { +// val res = typeRepr.dealias.simplified + // simplified does everything below functions do, with exception of `_removeTautologicalUnions` for some reason + // All of these would be more useful, if not for forced type simplification on implicit macro - https://github.com/lampepfl/dotty/issues/17544 + val res = typeRepr.dealias._removeTautologicalIntersections._removeTautologicalUnions._simplifyMatchCase + if (res.asInstanceOf[AnyRef] eq typeRepr.asInstanceOf[AnyRef]) { + res + } else { + res._dealiasSimplifiedFull + } + } + + // Calling .simplified will remove too many intersections - we only want to remove those with Any/AnyRef/Object/Matchable + @tailrec private def _removeTautologicalIntersections: TypeRepr = { + typeRepr match { + case AndType(a, b) => + if (ignoredInIntersections(a)) { + b._removeTautologicalIntersections + } else if (ignoredInIntersections(b)) { + a._removeTautologicalIntersections + } else { + removeTautologicalIntersectionsNonTailRec(a, b) + } + case _ => + typeRepr + } + } + + private def removeTautologicalIntersectionsNonTailRec(a: TypeRepr, b: TypeRepr): TypeRepr = { + val a0 = a._removeTautologicalIntersections + val b0 = b._removeTautologicalIntersections + if ((a.asInstanceOf[AnyRef] ne a0.asInstanceOf[AnyRef]) || (b.asInstanceOf[AnyRef] ne b0.asInstanceOf[AnyRef])) { + AndType(a0, b0) + } else { + typeRepr + } + } + + @tailrec private def _removeTautologicalUnions: TypeRepr = { + typeRepr match { + case OrType(a, b) => + if (ignoredInUnions(a)) { + b._removeTautologicalUnions + } else if (ignoredInUnions(b)) { + a._removeTautologicalUnions + } else { + removeTautologicaUnionsNonTailRec(a, b) + } + case _ => + typeRepr + } + } + + private def removeTautologicaUnionsNonTailRec(a: TypeRepr, b: TypeRepr): TypeRepr = { + val superA = ignoredInIntersections(a) + val superB = ignoredInIntersections(b) + if (superA && superB) { + (if (a <:< b) b else a)._removeTautologicalUnions + } else if (superA) { + a + } else if (superB) { + b + } else { + val a0 = a._removeTautologicalUnions + val b0 = b._removeTautologicalUnions + if ((a.asInstanceOf[AnyRef] ne a0.asInstanceOf[AnyRef]) || (b.asInstanceOf[AnyRef] ne b0.asInstanceOf[AnyRef])) { + AndType(a0, b0) + } else { + typeRepr + } + } + } + + inline private def _simplifyMatchCase: TypeRepr = { + typeRepr match { + case _: MatchCase | _: MatchType => + // no other way to evaluate a match type other than calling simplified, + // even though that'll also cause a collapse of tautological intersections + // other than with Any/AnyRef/Object/Matchable + typeRepr.simplified + case _ => + typeRepr + } + } + + } + + extension (qctx: Quotes) { + final def _ctx: InternalContext = qctx.asInstanceOf[{ def ctx: InternalContext }].ctx + } + + type InternalTypeRefOrParamRef = { + def underlying(ctx: InternalContext): TypeRepr + } + + type InternalHKTypeLambda = { + val typeParams: List[InternalLambdaParam] + } + + type InternalLambdaParam = { + def paramVariance(ctx: InternalContext): Flags + } + + object InternalContext { + opaque type InternalContext = Any + } + +} + +private[reflect] object ReflectionUtil { + + private[reflect] inline implicit def reflectiveUncheckedNonOverloadedSelectable(x: Any): UncheckedNonOverloadedSelectable = new UncheckedNonOverloadedSelectable(x) + + /** + * Returns true if the given type contains no type parameters + * (this means the type is not "weak" https://stackoverflow.com/questions/29435985/weaktypetag-v-typetag) + */ + private[reflect] def allPartsStrong( + using qctx: Quotes + )(shift: Int, + outerOwnerClassDefs: Set[qctx.reflect.Symbol], + outerLambdas: Set[qctx.reflect.TypeRepr], + typeRepr: qctx.reflect.TypeRepr + ): Boolean = { + import qctx.reflect.* + typeRepr.dealias match { + case x if topLevelWeakType(outerOwnerClassDefs, outerLambdas, x) => false + case AppliedType(tpe, args) => + allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) && args.forall(allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, _)) + case AndType(lhs, rhs) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, lhs) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, rhs) + case OrType(lhs, rhs) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, lhs) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, rhs) + case TypeRef(tpe, _) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) + case TermRef(tpe, _) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) + case ThisType(tpe) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) + case NoPrefix() => true + case TypeBounds(lo, hi) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, lo) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, hi) + case lam @ TypeLambda(_, _, body) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas + lam, body) + case Refinement(parent, _, tpe) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, parent) + case ByNameType(tpe) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) + case strange => + InspectorBase.log(shift, s"Got unknown type component when checking strength: $strange") + true + } + } + + private[reflect] def topLevelWeakType( + using qctx: Quotes + )(outerOwnerClassDefs: Set[qctx.reflect.Symbol], + outerLambdas: Set[qctx.reflect.TypeRepr], + typeRepr: qctx.reflect.TypeRepr + ): Boolean = { + import qctx.reflect.* + typeRepr match { + case x if x.typeSymbol.isTypeParam => + x match { + case t: ParamRef if outerLambdas.contains(t.binder) => false + case _ => true + } + // we regard abstract types like T in trait X { type T; Tag[this.T] } - when we are _inside_ the definition template + // as 'type parameters' too. So that you could define `implicit def tagForT: Tag[this.T]` and the tag would be resolved + // to this implicit correctly, instead of generating a useless `X::this.type::T` tag. + // TODO: Due to https://github.com/lampepfl/dotty/issues/16107 not being fixed we have to make sure we're actually + // inside the definition of the this-type prefix to count it as 'weak' - unlike Scala 2 we're not protected + // from this-types leaking in and have to carry the owner chain here - until that issue is fixed. + case x @ TypeRef(ThisType(prefix), _) if x.typeSymbol.isAbstractType && !x.typeSymbol.isClassDef && outerOwnerClassDefs.contains(prefix.typeSymbol) => + true + case _ => false + } + } + + private[reflect] def getClassDefOwners(using qctx: Quotes)(symbol: qctx.reflect.Symbol): Set[qctx.reflect.Symbol] = { + Iterator + .iterate(symbol) { + s => + val owner = s.owner + if (owner == null || owner.isNoSymbol || owner == qctx.reflect.defn.RootClass) { + null.asInstanceOf[qctx.reflect.Symbol] + } else { + owner + } + } + .takeWhile(_ ne null) + .filter(s => s.isClassDef && !s.isAbstractType) + .toSet + } + + private[reflect] final class UncheckedNonOverloadedSelectable(private val selectable: Any) extends AnyVal with Selectable { + + inline def selectDynamic(name: String): Any = { + applyDynamic(name)() + } + + def applyDynamic(name: String, @unused paramTypes: Class[_]*)(args: Any*): Any = { + val cls = selectable.getClass + val method = { + if (args.isEmpty) { + cls.getMethod(name) + } else { + cls.getMethods.collectFirst { case m if m.getName == name => m } match { + case Some(m) => m + case None => throw new NoSuchMethodException(s"No method named `$name` found in class `$cls`") + } + } + } + method.invoke(selectable, args*) + } + + } + +} diff --git a/tests/pos/i17394/_1.scala b/tests/pos/i17394/_1.scala new file mode 100644 index 000000000000..fdab17ec3f19 --- /dev/null +++ b/tests/pos/i17394/_1.scala @@ -0,0 +1,9 @@ +package example: + def xd: Int = ??? + +package bar: + trait A: + def foo: String = ??? + +package object example extends bar.A: + def foo(x: String): String = ??? diff --git a/tests/pos/i17394/_2.scala b/tests/pos/i17394/_2.scala new file mode 100644 index 000000000000..5ac6a31a66b2 --- /dev/null +++ b/tests/pos/i17394/_2.scala @@ -0,0 +1,4 @@ +import example.* + +@main def main = + val _ = foo diff --git a/tests/pos/i17394b/_1.scala b/tests/pos/i17394b/_1.scala new file mode 100644 index 000000000000..fdab17ec3f19 --- /dev/null +++ b/tests/pos/i17394b/_1.scala @@ -0,0 +1,9 @@ +package example: + def xd: Int = ??? + +package bar: + trait A: + def foo: String = ??? + +package object example extends bar.A: + def foo(x: String): String = ??? diff --git a/tests/pos/i17394b/_2.scala b/tests/pos/i17394b/_2.scala new file mode 100644 index 000000000000..8c86ca45e215 --- /dev/null +++ b/tests/pos/i17394b/_2.scala @@ -0,0 +1,5 @@ +//> using options -Wunused:imports + +import example.{given, *} + +@main def main = () diff --git a/tests/pos/i18529/JCode1.java b/tests/pos/i18529/JCode1.java new file mode 100644 index 000000000000..e1f12f852c00 --- /dev/null +++ b/tests/pos/i18529/JCode1.java @@ -0,0 +1,9 @@ +package bug.code; + +import bug.util.List; +import bug.util.*; +import java.util.*; + +public class JCode1 { + public void m1(List xs) { return; } +} diff --git a/tests/pos/i18529/JCode2.java b/tests/pos/i18529/JCode2.java new file mode 100644 index 000000000000..2a1ec812852c --- /dev/null +++ b/tests/pos/i18529/JCode2.java @@ -0,0 +1,9 @@ +package bug.code; + +import bug.util.*; +import bug.util.List; +import java.util.*; + +public class JCode2 { + public void m1(List xs) { return; } +} diff --git a/tests/pos/i18529/List.java b/tests/pos/i18529/List.java new file mode 100644 index 000000000000..caf3c0b8036b --- /dev/null +++ b/tests/pos/i18529/List.java @@ -0,0 +1,3 @@ +package bug.util; + +public final class List {} diff --git a/tests/pos/i18529/SCode1.scala b/tests/pos/i18529/SCode1.scala new file mode 100644 index 000000000000..b6796b1540c6 --- /dev/null +++ b/tests/pos/i18529/SCode1.scala @@ -0,0 +1,9 @@ +package bug.code + +import bug.util.List +import bug.util.* +import java.util.* + +class SCode1 { + def work(xs: List[Int]): Unit = {} +} diff --git a/tests/pos/i18529/SCode2.scala b/tests/pos/i18529/SCode2.scala new file mode 100644 index 000000000000..30fc7d0e6f91 --- /dev/null +++ b/tests/pos/i18529/SCode2.scala @@ -0,0 +1,9 @@ +package bug.code + +import bug.util.* +import bug.util.List +import java.util.* + +class SCode2 { + def work(xs: List[Int]): Unit = {} +} diff --git a/tests/pos/i18529/Test.scala b/tests/pos/i18529/Test.scala new file mode 100644 index 000000000000..be7795442a7a --- /dev/null +++ b/tests/pos/i18529/Test.scala @@ -0,0 +1 @@ +class Test diff --git a/tests/pos/i20512.scala b/tests/pos/i20512.scala new file mode 100644 index 000000000000..d30a3df9fc7c --- /dev/null +++ b/tests/pos/i20512.scala @@ -0,0 +1,26 @@ +import language.experimental.namedTuples + +import NamedTuple.* + +trait Selector1 extends Selectable { + type Fields = (int: Int, str: String) + + def selectDynamic(name: String)(using name.type <:< Tuple.Union[NamedTuple.Names[Fields]]) = ??? +} + +def test20512 = { + val s: Selector1 = new Selector1 {} + val int = s.int + val str = s.str +} + +trait Ctx + +class Selector2 extends Selectable: + type Fields = (bar: Int, baz: Int) + def selectDynamic(fieldName: String)(using Ctx): Any = ??? + +def test22023(using Ctx) = + val f = Selector2() + val bar = f.bar + val baz = f.baz \ No newline at end of file diff --git a/tests/pos/i21256.scala b/tests/pos/i21256.scala new file mode 100644 index 000000000000..e51484c73ef7 --- /dev/null +++ b/tests/pos/i21256.scala @@ -0,0 +1,5 @@ +object Test { + type MTWithBind[X] = X match { + case List[t] => t + } +} diff --git a/tests/pos/i22018.scala b/tests/pos/i22018.scala new file mode 100644 index 000000000000..14f4733732be --- /dev/null +++ b/tests/pos/i22018.scala @@ -0,0 +1,18 @@ +import scala.language.experimental.namedTuples + +class SelectableNT[A <: NamedTuple.AnyNamedTuple](val nt: A) extends Selectable: + type Fields = A + def selectDynamic(x: String) = ??? + +object Test: + + val a = (name = "foo", age = 1) + + val sa = SelectableNT(a) + sa.name // ok + + type B = a.type + val b: B = a + + val sb = SelectableNT(b) + sb.name // fails \ No newline at end of file diff --git a/tests/pos/i22062.scala b/tests/pos/i22062.scala new file mode 100644 index 000000000000..73289deac0a4 --- /dev/null +++ b/tests/pos/i22062.scala @@ -0,0 +1,32 @@ +class Label +class Component + +trait RenderableCellsCompanion: + type Renderer[-A] <: CellRenderer[A] + type DefaultRenderer[-A] <: Label & Renderer[A] + + trait CellRendererCompanion: + type CellInfo + def labeled[A](): DefaultRenderer[A] + protected trait LabelRenderer[-A] extends CellRenderer[A]: + override abstract def componentFor(info: companion.CellInfo): Component = super.componentFor(info) + + trait CellRenderer[-A]: + val companion: CellRendererCompanion + def componentFor(cellInfo: companion.CellInfo): Component + +sealed trait TreeRenderers extends RenderableCellsCompanion: + this: Tree.type => + + trait Renderer[-A] extends CellRenderer[A]: + final override val companion = Renderer + + object Renderer extends CellRendererCompanion: + final override class CellInfo + override def labeled[A]() = new DefaultRenderer[A] with LabelRenderer[A] {} + + class DefaultRenderer[-A] extends Label with Renderer[A]: + override def componentFor(info: Renderer.CellInfo): Component = ??? + +class Tree extends Component +object Tree extends TreeRenderers diff --git a/tests/pos/i22068.less-min.scala b/tests/pos/i22068.less-min.scala new file mode 100644 index 000000000000..e520d1d717c0 --- /dev/null +++ b/tests/pos/i22068.less-min.scala @@ -0,0 +1,21 @@ +class A +class B extends A +class C extends A + +object foos: + opaque type Tag[A] = String + object Tag: + inline given mkTag[A]: Tag[A] = ??? + type Full[A] = Tag[A] | Set[A] + sealed trait Set[A] extends Any + case class Union[A](tags: Seq[Tag[Any]]) extends AnyVal with Set[A]: + infix def and[B](t2: Full[B]): Unit = ??? + object Union: + inline given mkUnion[A]: Union[A] = ??? +import foos.Tag.* + +class Test: + inline def m1[K1, K2](using b1: Union[K1], b2: Union[K2]): Unit = + b1.and(b2) + + def t1(): Unit = m1[B | C, A] diff --git a/tests/pos/i22068.orig.scala b/tests/pos/i22068.orig.scala new file mode 100644 index 000000000000..9b2b54037a5a --- /dev/null +++ b/tests/pos/i22068.orig.scala @@ -0,0 +1,29 @@ +trait AnyFreeSpecLike: + inline implicit def convertToFreeSpecStringWrapper(s: String): FreeSpecStringWrapper = ??? + protected final class FreeSpecStringWrapper(string: String): + infix def in(testFun: => Any): Unit = ??? + + +import types.Tag.* +class TagTest extends AnyFreeSpecLike{ + inline def test[T1, T2](using k1: Union[T1], k2: Union[T2]): Unit = + "T1 <:< T2" in { + val kresult = k1 <:< k2 + ??? + } + class A + class B extends A + class C extends A + test[B | C, A] +} + +object types: + opaque type Tag[A] = String + object Tag: + inline given apply[A]: Tag[A] = ??? + type Full[A] = Tag[A] | Set[A] + sealed trait Set[A] extends Any + case class Union[A](tags: Seq[Tag[Any]]) extends AnyVal with Set[A]: + infix def <:<[B](t2: Full[B]): Boolean = ??? + object Union: + inline given apply[A]: Union[A] = ??? diff --git a/tests/pos/i22068.scala b/tests/pos/i22068.scala new file mode 100644 index 000000000000..1af942a2c46e --- /dev/null +++ b/tests/pos/i22068.scala @@ -0,0 +1,14 @@ +object foos: + opaque type Foo[T] = String + object bars: + class Bar1[A] { def and(b: Bar2): Unit = () } + class Bar2 + inline def mkBar1[A]: Bar1[A] = new Bar1[A] + def mkBar2 : Bar2 = new Bar2 +import foos.*, bars.* + +class Test: + inline def m1[X](b1: Bar1[X], b2: Bar2): Unit = + b1.and(b2) + + def t1(): Unit = m1(mkBar1[Int], mkBar2) diff --git a/tests/pos/i22070/macro.scala b/tests/pos/i22070/macro.scala new file mode 100644 index 000000000000..bcf6ec6dd70f --- /dev/null +++ b/tests/pos/i22070/macro.scala @@ -0,0 +1,18 @@ +trait Featureful[T]: + def toFeatures(value: T): IArray[Float] + +object Featureful: + inline def derived[T](using scala.deriving.Mirror.Of[T]) = ${ derivedImpl[T] } + + import scala.quoted.* + private def derivedImpl[T: Type](using Quotes): Expr[Featureful[T]] = + import quotes.reflect.* + '{ + new Featureful[T]: + def toFeatures(value: T) = + val feats = IArray.empty[Featureful[?]] + val product = value.asInstanceOf[Product] + product.productIterator.zipWithIndex.foreach: (any, idx) => + feats(idx).toFeatures(any.asInstanceOf) + IArray.empty + } diff --git a/tests/pos/i22070/usage.scala b/tests/pos/i22070/usage.scala new file mode 100644 index 000000000000..6fc662dfcc13 --- /dev/null +++ b/tests/pos/i22070/usage.scala @@ -0,0 +1 @@ +case class Breaks(x: Boolean, y: Boolean) derives Featureful diff --git a/tests/pos/i22137.scala b/tests/pos/i22137.scala new file mode 100644 index 000000000000..b52dd9171146 --- /dev/null +++ b/tests/pos/i22137.scala @@ -0,0 +1,5 @@ +enum Parser[+Value]: + case Success(value: Value, issues: Seq[Failure] = Seq.empty) extends Parser[Value] + case Failure(exception: Throwable) extends Parser[Nothing] + +val v = Parser.Success(1) \ No newline at end of file diff --git a/tests/pos/i22192.scala b/tests/pos/i22192.scala new file mode 100644 index 000000000000..4214a56f4b38 --- /dev/null +++ b/tests/pos/i22192.scala @@ -0,0 +1,15 @@ +import scala.language.experimental.namedTuples + +case class City(name: String, population: Int) + +def getCityInfo(city: City) = city match + case City(population = p, name = n) => + s"$n has a population of $p" + +def getCityInfo1(city: Option[(name: String)]) = city match + case Some(name = n) => n + case _ => + +def getCityInfo2(city: Option[(name: String, population: Int)]) = city match + case Some(name = n) => n + case _ => diff --git a/tests/pos/i22300.scala b/tests/pos/i22300.scala new file mode 100644 index 000000000000..156d913951b6 --- /dev/null +++ b/tests/pos/i22300.scala @@ -0,0 +1,16 @@ +class Foo: + + inline def inlineMatch[T]: String = + inline compiletime.erasedValue[T] match + case _: EmptyTuple => "" + case _: (h *: _) if checkType[h](0) => "" + + private inline def checkType[T](i: Int): Boolean = + inline compiletime.erasedValue[T] match + case _: Int => true + case _ => false + +val foo = Foo() + +@main def main () = + foo.inlineMatch[(Int, Boolean)] diff --git a/tests/pos/i22324.scala b/tests/pos/i22324.scala new file mode 100644 index 000000000000..b35f82d52ac9 --- /dev/null +++ b/tests/pos/i22324.scala @@ -0,0 +1,10 @@ +import scala.language.experimental.namedTuples + +opaque type System = (wires: Any) + +extension (system: System) + inline def foo = system.wires +end extension + +val simulation: System = ??? +val _ = simulation.foo // was error diff --git a/tests/pos/named-tuple-downcast.scala b/tests/pos/named-tuple-downcast.scala new file mode 100644 index 000000000000..b9876623faf2 --- /dev/null +++ b/tests/pos/named-tuple-downcast.scala @@ -0,0 +1,22 @@ +import scala.language.experimental.namedTuples + +type Person = (name: String, age: Int) + +val Bob: Person = (name = "Bob", age = 33) + +type SI = (String, Int) + +def id[X](x: X): X = x +val x: (String, Int) = Bob +val y: SI = id(Bob) +val and: Person & String = ??? +val _: SI = and +val or: Person | (name: "Bob", age: 33) = ??? +val _: SI = or + +class C[P <: Person](p: P): + val x: (String, Int) = p + + + + diff --git a/tests/printing/dependent-annot-default-args.check b/tests/printing/dependent-annot-default-args.check new file mode 100644 index 000000000000..f457d5d62edb --- /dev/null +++ b/tests/printing/dependent-annot-default-args.check @@ -0,0 +1,48 @@ +[[syntax trees at end of typer]] // tests/printing/dependent-annot-default-args.scala +package { + class annot(x: Any, y: Any) extends annotation.Annotation() { + private[this] val x: Any + private[this] val y: Any + } + final lazy module val annot: annot = new annot() + final module class annot() extends AnyRef() { this: annot.type => + def $lessinit$greater$default$2: Any @uncheckedVariance = 42 + } + class annot2(x: Any, y: Array[Any]) extends annotation.Annotation() { + private[this] val x: Any + private[this] val y: Array[Any] + } + final lazy module val annot2: annot2 = new annot2() + final module class annot2() extends AnyRef() { this: annot2.type => + def $lessinit$greater$default$1: Any @uncheckedVariance = -1 + def $lessinit$greater$default$2: Array[Any] @uncheckedVariance = + Array.apply[Any](["Hello" : Any]*)(scala.reflect.ClassTag.Any) + } + final lazy module val dependent-annot-default-args$package: + dependent-annot-default-args$package = + new dependent-annot-default-args$package() + final module class dependent-annot-default-args$package() extends Object() { + this: dependent-annot-default-args$package.type => + def f(x: Int): Int @annot(x) = x + def f2(x: Int): + Int @annot2( + y = Array.apply[Any](["Hello",x : Any]*)(scala.reflect.ClassTag.Any)) + = x + def test: Unit = + { + val y: Int = ??? + val z: Int @annot(y) = f(y) + val z2: + Int @annot2( + y = Array.apply[Any](["Hello",y : Any]*)(scala.reflect.ClassTag.Any) + ) + = f2(y) + @annot(44) val z3: Int = 45 + @annot2( + y = Array.apply[Any](["Hello",y : Any]*)(scala.reflect.ClassTag.Any)) + val z4: Int = 45 + () + } + } +} + diff --git a/tests/printing/dependent-annot-default-args.scala b/tests/printing/dependent-annot-default-args.scala new file mode 100644 index 000000000000..11fc9ef52cc9 --- /dev/null +++ b/tests/printing/dependent-annot-default-args.scala @@ -0,0 +1,15 @@ +class annot(x: Any, y: Any = 42) extends annotation.Annotation +class annot2(x: Any = -1, y: Array[Any] = Array("Hello")) extends annotation.Annotation + +def f(x: Int): Int @annot(x) = x +def f2(x: Int): Int @annot2(y = Array("Hello", x)) = x + +def test = + val y: Int = ??? + + val z = f(y) + val z2 = f2(y) + + @annot(44) val z3 = 45 + @annot2(y = Array("Hello", y)) val z4 = 45 + diff --git a/tests/printing/i22349.check b/tests/printing/i22349.check new file mode 100644 index 000000000000..7869f5f7fc4f --- /dev/null +++ b/tests/printing/i22349.check @@ -0,0 +1,15 @@ +[[syntax trees at end of typer]] // tests/printing/i22349.scala +package { + final lazy module val i22349$package: i22349$package = new i22349$package() + final module class i22349$package() extends Object() { + this: i22349$package.type => + val x: Int = 0 + val _$1: x.type = x + inline val _$2: true = true + inline val _$3: "abc" = "abc" + inline val _$4: 'c' = 'c' + inline val _$5: 1.2f = 1.2f + inline val _$6: 1.2d = 1.2d + } +} + diff --git a/tests/printing/i22349.scala b/tests/printing/i22349.scala new file mode 100644 index 000000000000..33122f0b91e8 --- /dev/null +++ b/tests/printing/i22349.scala @@ -0,0 +1,7 @@ +val x = 0 +val _: x.type = x +inline val _: true = true // boolean literal +inline val _: "abc" = "abc" // string literal +inline val _: 'c' = 'c' // character literal +inline val _: 1.2f = 1.2f // floating point literal +inline val _: 1.2d = 1.2d // double number literal diff --git a/tests/rewrites/i21382.scala b/tests/rewrites/i21382.scala new file mode 100644 index 000000000000..75f4007be218 --- /dev/null +++ b/tests/rewrites/i21382.scala @@ -0,0 +1,8 @@ +def check(element: Any)(expected: String): Unit = ??? + +def test = + check { + ??? + }{ + 42.toString + } diff --git a/tests/run-macros/i20052.check b/tests/run-macros/i20052.check new file mode 100644 index 000000000000..ca45222cf4cf --- /dev/null +++ b/tests/run-macros/i20052.check @@ -0,0 +1,5 @@ +method (Flags.JavaDefined | Flags.Method) List(List((x$0,scala.Int))) +method (Flags.JavaDefined | Flags.Method) List(List()) +method (Flags.JavaDefined | Flags.Method | Flags.Private) List(List()) +method (Flags.JavaDefined | Flags.Method | Flags.Private) List(List()) +method (Flags.JavaDefined | Flags.Method) List(List((A,_ >: scala.Nothing <: .)), List((x$0,A))) diff --git a/tests/run-macros/i20052/JavaClass.java b/tests/run-macros/i20052/JavaClass.java new file mode 100644 index 000000000000..368bbf47830a --- /dev/null +++ b/tests/run-macros/i20052/JavaClass.java @@ -0,0 +1,4 @@ +public class JavaClass { + public JavaClass(int a) {} + public JavaClass(float a) {} +} diff --git a/tests/run-macros/i20052/JavaClassEmpty.java b/tests/run-macros/i20052/JavaClassEmpty.java new file mode 100644 index 000000000000..22710a8b7337 --- /dev/null +++ b/tests/run-macros/i20052/JavaClassEmpty.java @@ -0,0 +1 @@ +public class JavaClassEmpty {} diff --git a/tests/run-macros/i20052/JavaClassParam.java b/tests/run-macros/i20052/JavaClassParam.java new file mode 100644 index 000000000000..ddbb7b0a6a5f --- /dev/null +++ b/tests/run-macros/i20052/JavaClassParam.java @@ -0,0 +1,3 @@ +public class JavaClassParam { + public JavaClassParam(A a) {} +} diff --git a/tests/run-macros/i20052/JavaClassPrivate.java b/tests/run-macros/i20052/JavaClassPrivate.java new file mode 100644 index 000000000000..d2f0b3cda023 --- /dev/null +++ b/tests/run-macros/i20052/JavaClassPrivate.java @@ -0,0 +1,3 @@ +class JavaClassPrivate { + private JavaClassPrivate() {} +} diff --git a/tests/run-macros/i20052/JavaClassStartsWithPrivate.java b/tests/run-macros/i20052/JavaClassStartsWithPrivate.java new file mode 100644 index 000000000000..d27724424f34 --- /dev/null +++ b/tests/run-macros/i20052/JavaClassStartsWithPrivate.java @@ -0,0 +1,4 @@ +class JavaClassStartsWithPrivate { + private JavaClassStartsWithPrivate() {} + public JavaClassStartsWithPrivate(int a) {} +} diff --git a/tests/run-macros/i20052/Macro.scala b/tests/run-macros/i20052/Macro.scala new file mode 100644 index 000000000000..e38bb63257a3 --- /dev/null +++ b/tests/run-macros/i20052/Macro.scala @@ -0,0 +1,16 @@ +import scala.quoted.* + +object Macro { + inline def logPrimaryConstructor[A]: String = ${ logPrimaryConstructorImpl[A] } + + def logPrimaryConstructorImpl[A](using Type[A], Quotes): Expr[String] = { + import quotes.reflect.* + + val primaryConstructor = TypeRepr.of[A].typeSymbol.primaryConstructor + val flags = primaryConstructor.flags.show + val paramSymss = primaryConstructor.paramSymss + val clauses = paramSymss.map(_.map(param => (param.name, TypeRepr.of[A].memberType(param).show))) + val str = s"${primaryConstructor} (${primaryConstructor.flags.show}) ${clauses}" + Expr(str) + } +} diff --git a/tests/run-macros/i20052/Test_2.scala b/tests/run-macros/i20052/Test_2.scala new file mode 100644 index 000000000000..ef4fc8bd1887 --- /dev/null +++ b/tests/run-macros/i20052/Test_2.scala @@ -0,0 +1,6 @@ +@main def Test() = + println(Macro.logPrimaryConstructor[JavaClass]) + println(Macro.logPrimaryConstructor[JavaClassEmpty]) + println(Macro.logPrimaryConstructor[JavaClassPrivate]) + println(Macro.logPrimaryConstructor[JavaClassStartsWithPrivate]) + println(Macro.logPrimaryConstructor[JavaClassParam[Int]]) diff --git a/tests/run/i18150.check b/tests/run/i18150.check new file mode 100644 index 000000000000..ea0b653719cd --- /dev/null +++ b/tests/run/i18150.check @@ -0,0 +1,2 @@ +List(Error(illegal inheritance: self type Banana of class Banana does not conform to self type Apple +of parent trait RecursiveSelfTypeEntity,class Banana extends RecursiveSelfTypeEntity[Apple]:,6,Typer)) diff --git a/tests/run/i18150.scala b/tests/run/i18150.scala new file mode 100644 index 000000000000..2a94f7d29549 --- /dev/null +++ b/tests/run/i18150.scala @@ -0,0 +1,31 @@ +object Test: + def main(args: Array[String]): Unit = + val result = + scala.compiletime.testing.typeCheckErrors( + "trait RecursiveSelfTypeEntity[E <: RecursiveSelfTypeEntity[E]]: \n" + + " self: E => \n" + + " def create(): E \n" + + " def read(id: Long): Option[E] \n" + + " def update(f: E => E): E \n" + + " def delete(id: Long): Unit \n" + + "\n" + + "class Apple extends RecursiveSelfTypeEntity[Apple]: \n" + + " override def create(): Apple = ??? \n" + + " override def read(id: Long): Option[Apple] = ??? \n" + + " override def update(f: Apple => Apple): Apple = ??? \n" + + " override def delete(id: Long): Unit = ??? \n" + + " \n" + + "class Orange extends RecursiveSelfTypeEntity[Orange]: \n" + + " override def create(): Orange = ??? \n" + + " override def read(id: Long): Option[Orange] = ??? \n" + + " override def update(f: Orange => Orange): Orange = ??? \n" + + " override def delete(id: Long): Unit = ??? \n" + + " \n" + + "class Banana extends RecursiveSelfTypeEntity[Apple]: \n" + + " override def create(): Apple = ??? \n" + + " override def read(id: Long): Option[Apple] = ??? \n" + + " override def update(f: Apple => Apple): Apple = ??? \n" + + " override def delete(id: Long): Unit = ???\n" + ) + assert(!result.isEmpty, "Should fail type check, but it didn't.") + println(result) diff --git a/tests/run/i22150.check b/tests/run/i22150.check new file mode 100644 index 000000000000..4539bbf2d22d --- /dev/null +++ b/tests/run/i22150.check @@ -0,0 +1,3 @@ +0 +1 +2 diff --git a/tests/run/i22150.scala b/tests/run/i22150.scala new file mode 100644 index 000000000000..80c2222a98e7 --- /dev/null +++ b/tests/run/i22150.scala @@ -0,0 +1,26 @@ +//> using options -experimental -language:experimental.namedTuples +import language.experimental.namedTuples + +val directionsNT = IArray( + (dx = 0, dy = 1), // up + (dx = 1, dy = 0), // right + (dx = 0, dy = -1), // down + (dx = -1, dy = 0), // left +) +val IArray(UpNT @ _, _, _, _) = directionsNT + +object NT: + def foo[T <: (x: Int, y: String)](tup: T): Int = + tup.x + + def union[T](tup: (x: Int, y: String) | (x: Int, y: String)): Int = + tup.x + + def intersect[T](tup: (x: Int, y: String) & T): Int = + tup.x + + +@main def Test = + println(UpNT.dx) + println(NT.union((1, "a"))) + println(NT.intersect((2, "b"))) diff --git a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala index de260f3481ed..02cb08789232 100644 --- a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala +++ b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala @@ -145,6 +145,17 @@ class RegressionTestScala3 { assertEquals(5, Issue14289.Container.b()) assertEquals(true, Issue14289.Container.c()) } + + @Test def createArrayOfUnitIssue22226(): Unit = { + val a = Array.ofDim[Unit](0) + assertSame(classOf[Array[Unit]], a.getClass()) + + val b = new Array[Unit](0) + assertSame(classOf[Array[Unit]], b.getClass()) + + val c = Array.ofDim[Unit](0, 0) + assertSame(classOf[Array[Array[Unit]]], c.getClass()) + } } object RegressionTestScala3 { diff --git a/tests/warn/21557.check b/tests/warn/21557.check new file mode 100644 index 000000000000..1753960b852a --- /dev/null +++ b/tests/warn/21557.check @@ -0,0 +1,18 @@ +-- [E190] Potential Issue Warning: tests/warn/21557.scala:9:16 --------------------------------------------------------- +9 | val x: Unit = 1 + 1 // warn + | ^^^^^ + | Discarded non-Unit value of type Int. Add `: Unit` to discard silently. + | + | longer explanation available when compiling with `-explain` +-- [E176] Potential Issue Warning: tests/warn/21557.scala:10:2 --------------------------------------------------------- +10 | 1 + 1 // warn + | ^^^^^ + | unused value of type (2 : Int) +-- [E175] Potential Issue Warning: tests/warn/21557.scala:15:52 -------------------------------------------------------- +15 | val x1: Unit = new Assertion("another").shouldPass() // warn (enabled by -Wvalue-discard) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | discarded non-Unit value of type Assertion. Add `: Unit` to discard silently. +-- [E176] Potential Issue Warning: tests/warn/21557.scala:16:41 -------------------------------------------------------- +16 | new Assertion("yet another").shouldPass() // warn (enabled by -Wnonunit-statement) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | unused value of type Assertion diff --git a/tests/warn/21557.scala b/tests/warn/21557.scala new file mode 100644 index 000000000000..ed7028b6a48a --- /dev/null +++ b/tests/warn/21557.scala @@ -0,0 +1,19 @@ +//> using options -Wvalue-discard -Wnonunit-statement + +class Assertion(assert: => Any): + def shouldPass(): Assertion = ??? + +def test: Unit = + 1 + 1: Unit + (1 + 1): Unit + val x: Unit = 1 + 1 // warn + 1 + 1 // warn + val y: Int = 1 + 1 + + new Assertion("").shouldPass(): Unit + (new Assertion("").shouldPass()): Unit + val x1: Unit = new Assertion("another").shouldPass() // warn (enabled by -Wvalue-discard) + new Assertion("yet another").shouldPass() // warn (enabled by -Wnonunit-statement) + val y1: Assertion = new Assertion("another other").shouldPass() + + () diff --git a/tests/warn/i16743.check b/tests/warn/i16743.check index 3010338cfb45..6fa1f2c83357 100644 --- a/tests/warn/i16743.check +++ b/tests/warn/i16743.check @@ -1,84 +1,84 @@ -- [E194] Potential Issue Warning: tests/warn/i16743.scala:30:6 -------------------------------------------------------- 30 | def t = 27 // warn | ^ - | Extension method t will never be selected + | Extension method t will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:32:6 -------------------------------------------------------- 32 | def g(x: String)(i: Int): String = x*i // warn | ^ - | Extension method g will never be selected + | Extension method g will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:33:6 -------------------------------------------------------- 33 | def h(x: String): String = x // warn | ^ - | Extension method h will never be selected + | Extension method h will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:35:6 -------------------------------------------------------- 35 | def j(x: Any, y: Int): String = (x.toString)*y // warn | ^ - | Extension method j will never be selected + | Extension method j will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:36:6 -------------------------------------------------------- 36 | def k(x: String): String = x // warn | ^ - | Extension method k will never be selected + | Extension method k will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:38:6 -------------------------------------------------------- 38 | def m(using String): String = "m" + summon[String] // warn | ^ - | Extension method m will never be selected + | Extension method m will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:39:6 -------------------------------------------------------- 39 | def n(using String): String = "n" + summon[String] // warn | ^ - | Extension method n will never be selected + | Extension method n will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:40:6 -------------------------------------------------------- 40 | def o: String = "42" // warn | ^ - | Extension method o will never be selected + | Extension method o will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:41:6 -------------------------------------------------------- 41 | def u: Int = 27 // warn | ^ - | Extension method u will never be selected + | Extension method u will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:44:6 -------------------------------------------------------- 44 | def at: Int = 42 // warn | ^ - | Extension method at will never be selected + | Extension method at will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:46:6 -------------------------------------------------------- 46 | def x(using String)(n: Int): Int = summon[String].toInt + n // warn | ^ - | Extension method x will never be selected + | Extension method x will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:47:6 -------------------------------------------------------- 47 | def y(using String)(s: String): String = s + summon[String] // warn | ^ - | Extension method y will never be selected + | Extension method y will never be selected from type T | because T already has a member with the same name and compatible parameter types. | | longer explanation available when compiling with `-explain` diff --git a/tests/warn/i18722.check b/tests/warn/i18722.check index 4fae07e15204..4b32cb418167 100644 --- a/tests/warn/i18722.check +++ b/tests/warn/i18722.check @@ -1,7 +1,7 @@ -- [E190] Potential Issue Warning: tests/warn/i18722.scala:3:15 -------------------------------------------------------- 3 |def f1: Unit = null // warn | ^^^^ - | Discarded non-Unit value of type Null. You may want to use `()`. + | Discarded non-Unit value of type Null. Add `: Unit` to discard silently. |--------------------------------------------------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -12,7 +12,7 @@ -- [E190] Potential Issue Warning: tests/warn/i18722.scala:4:15 -------------------------------------------------------- 4 |def f2: Unit = 1 // warn | ^ - | Discarded non-Unit value of type Int. You may want to use `()`. + | Discarded non-Unit value of type Int. Add `: Unit` to discard silently. |--------------------------------------------------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -23,7 +23,7 @@ -- [E190] Potential Issue Warning: tests/warn/i18722.scala:5:15 -------------------------------------------------------- 5 |def f3: Unit = "a" // warn | ^^^ - | Discarded non-Unit value of type String. You may want to use `()`. + | Discarded non-Unit value of type String. Add `: Unit` to discard silently. |--------------------------------------------------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -34,7 +34,7 @@ -- [E190] Potential Issue Warning: tests/warn/i18722.scala:7:15 -------------------------------------------------------- 7 |def f4: Unit = i // warn | ^ - | Discarded non-Unit value of type Int. You may want to use `()`. + | Discarded non-Unit value of type Int. Add `: Unit` to discard silently. |--------------------------------------------------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/warn/i21420.scala b/tests/warn/i21420.scala new file mode 100644 index 000000000000..0ee4aa3f28f6 --- /dev/null +++ b/tests/warn/i21420.scala @@ -0,0 +1,13 @@ +//> using options -Wunused:imports + +object decisions4s{ + trait HKD + trait DecisionTable +} + +object DiagnosticsExample { + import decisions4s.HKD + val _ = new HKD {} + import decisions4s.* + val _ = new DecisionTable {} +} diff --git a/tests/warn/i22051.scala b/tests/warn/i22051.scala new file mode 100644 index 000000000000..e1e902e16379 --- /dev/null +++ b/tests/warn/i22051.scala @@ -0,0 +1,17 @@ +def boundary[T](body: (T => RuntimeException) => T): T = + case class Break(value: T) extends RuntimeException + try body(Break.apply) + catch case Break(t) => t // warn: pattern matching on local classes is unsound currently + +def test = + boundary[Int]: EInt => + val v: String = boundary[String]: EString => + throw EInt(3) + v.length // a runtime error: java.lang.ClassCastException + +def boundaryCorrectBehaviour[T](body: (T => RuntimeException) => T): T = + object local: + // A correct implementation, but is still treated as a local class right now + case class Break(value: T) extends RuntimeException + try body(local.Break.apply) + catch case local.Break(t) => t // warn diff --git a/tests/warn/i22267.check b/tests/warn/i22267.check new file mode 100644 index 000000000000..a6b1ccbdef52 --- /dev/null +++ b/tests/warn/i22267.check @@ -0,0 +1,14 @@ +-- [E194] Potential Issue Warning: tests/warn/i22267.scala:13:26 ------------------------------------------------------- +13 | extension (self: C) def m(n: Double): Unit = println(2->n) // warn + | ^ + | Extension method m will never be selected from type C + | because C already has a member with the same name and compatible parameter types. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Although extensions can be overloaded, they do not overload existing member methods. + | An extension method can be invoked as a regular method, but if that is the intended usage, + | it should not be defined as an extension. + | + | The extension may be invoked as though selected from an arbitrary type if conversions are in play. + -------------------------------------------------------------------------------------------------------------------- diff --git a/tests/warn/i22267.scala b/tests/warn/i22267.scala new file mode 100644 index 000000000000..d1f6699342e9 --- /dev/null +++ b/tests/warn/i22267.scala @@ -0,0 +1,28 @@ +//> using options -explain + +import language.implicitConversions + +case class M[T](value: T) +given [T]: Conversion[M[T], T] = _.value +class C: + def m(n: Double): Unit = println(0->n) +object C: + given Ops = Ops() +class Ops: + extension (self: C) def m(n: Int): Unit = println(1->n) + extension (self: C) def m(n: Double): Unit = println(2->n) // warn + extension (self: C) def m(s: String): Unit = println(3->s) + +@main def test() = + val c = M(C()) + def i = 42 + def pi = 3.14 + c.value.m(i) + c.value.m(pi) + c.m(i) // conversion + c.m(pi) // conversion + c.m("hello, world") // extension + //m(c)(pi) + val c0 = C() + c0.m(pi) + c0.m("hello, world") diff --git a/tests/warn/i22279.scala b/tests/warn/i22279.scala new file mode 100644 index 000000000000..af69ce8b61e9 --- /dev/null +++ b/tests/warn/i22279.scala @@ -0,0 +1,24 @@ + +import scala.io.Source + +object Lengths: + opaque type Length = Int + object Length: + def apply(i: Int): Length = i + extension (source: Source) + def take(length: Length): IndexedSeq[Char] = // no warn + source.take(length).to(IndexedSeq) +end Lengths + +trait Taken: + def take(n: Lengths.Length): Taken = ??? + +object Lengthy: + import Lengths.* + extension (taken: Taken) def take(n: Length): Taken = ??? // warn + +@main def test() = println: + import Lengths.* + val src = Source.fromString("hello, world") + val len = Length("hello".length) + src.take(len) diff --git a/tests/warn/nonunit-statement.check b/tests/warn/nonunit-statement.check index 742a9fe911e8..7e7939e1c163 100644 --- a/tests/warn/nonunit-statement.check +++ b/tests/warn/nonunit-statement.check @@ -27,15 +27,15 @@ -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:58:19 -------------------------------------------- 58 | if (!isEmpty) f(a) // warn (if) | ^^^^ - | discarded non-Unit value of type U + | discarded non-Unit value of type U. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:62:7 --------------------------------------------- 62 | f(a) // warn (if) | ^^^^ - | discarded non-Unit value of type Boolean + | discarded non-Unit value of type Boolean. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:73:25 -------------------------------------------- 73 | if (!fellback) action(z) // warn (if) | ^^^^^^^^^ - | discarded non-Unit value of type U + | discarded non-Unit value of type U. Add `: Unit` to discard silently. -- [E176] Potential Issue Warning: tests/warn/nonunit-statement.scala:79:6 --------------------------------------------- 79 | g // warn block statement | ^ @@ -43,7 +43,7 @@ -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:81:6 --------------------------------------------- 81 | g // warn (if) | ^ - | discarded non-Unit value of type (g : => Int) + | discarded non-Unit value of type Int. Add `: Unit` to discard silently. -- [E176] Potential Issue Warning: tests/warn/nonunit-statement.scala:84:6 --------------------------------------------- 84 | g // warn | ^ @@ -51,7 +51,7 @@ -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:86:6 --------------------------------------------- 86 | g // warn | ^ - | discarded non-Unit value of type (g : => Int) + | discarded non-Unit value of type Int. Add `: Unit` to discard silently. -- [E176] Potential Issue Warning: tests/warn/nonunit-statement.scala:96:4 --------------------------------------------- 96 | if (b) { // warn, at least one branch looks interesting | ^ @@ -70,20 +70,20 @@ -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:126:37 ------------------------------------------- 126 | if (start.length != 0) jsb.append(start) // warn (value-discard) | ^^^^^^^^^^^^^^^^^ - | discarded non-Unit value of type StringBuilder + | discarded non-Unit value of type StringBuilder. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:132:18 ------------------------------------------- 132 | jsb.append(it.next()) // warn (value-discard) | ^^^^^^^^^^^^^^^^^^^^^ - | discarded non-Unit value of type StringBuilder + | discarded non-Unit value of type StringBuilder. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:135:35 ------------------------------------------- 135 | if (end.length != 0) jsb.append(end) // warn (value-discard) | ^^^^^^^^^^^^^^^ - | discarded non-Unit value of type StringBuilder + | discarded non-Unit value of type StringBuilder. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:141:14 ------------------------------------------- 141 | b.append(it.next()) // warn (value-discard) | ^^^^^^^^^^^^^^^^^^^ - | discarded non-Unit value of type StringBuilder + | discarded non-Unit value of type StringBuilder. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/nonunit-statement.scala:146:30 ------------------------------------------- 146 | while (it.hasNext) it.next() // warn | ^^^^^^^^^ - | discarded non-Unit value of type String + | discarded non-Unit value of type String. Add `: Unit` to discard silently. diff --git a/tests/warn/warn-value-discard.check b/tests/warn/warn-value-discard.check index ca6fedb29053..dcd6d62c00e0 100644 --- a/tests/warn/warn-value-discard.check +++ b/tests/warn/warn-value-discard.check @@ -1,20 +1,20 @@ -- [E175] Potential Issue Warning: tests/warn/warn-value-discard.scala:27:36 ------------------------------------------- 27 | mutable.Set.empty[String].remove("") // warn | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | discarded non-Unit value of type Boolean + | 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 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | discarded non-Unit value of type scala.collection.mutable.Set[String] + | 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 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | discarded non-Unit value of type scala.collection.mutable.Set[String] + | 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 ------------------------------------------- 15 | firstThing().map(_ => secondThing()) // warn | ^^^^^^^^^^^^^ - | discarded non-Unit value of type Either[Failed, Unit] + | discarded non-Unit value of type Either[Failed, Unit]. Add `: Unit` to discard silently. -- [E175] Potential Issue Warning: tests/warn/warn-value-discard.scala:18:35 ------------------------------------------- 18 | firstThing().map(_ => secondThing()) // warn | ^^^^^^^^^^^^^ - | discarded non-Unit value of type Either[Failed, Unit] + | discarded non-Unit value of type Either[Failed, Unit]. Add `: Unit` to discard silently.