diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2cd314dbf1..9b5b322dde 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: "CI" on: push: - branches: [ "master" ] + branches: ["master", "restaumatic"] pull_request: - branches: [ "master" ] + branches: ["master", "restaumatic"] paths: - .github/workflows/**/*.yml - app/**/* @@ -20,10 +20,11 @@ on: - purescript.cabal - Setup.hs - stack.yaml + - stack.yaml.lock - update-changelog.hs - weeder.dhall release: - types: [ "published" ] + types: ["published"] defaults: run: @@ -32,7 +33,7 @@ defaults: env: CI_PRERELEASE: "${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}" CI_RELEASE: "${{ github.event_name == 'release' }}" - STACK_VERSION: "2.15.1" + STACK_VERSION: "3.3.1" concurrency: # We never want two prereleases building at the same time, since they would @@ -53,16 +54,18 @@ jobs: fail-fast: false # do not cancel builds for other OSes if one fails matrix: include: - - # If upgrading the Haskell image, also upgrade it in the lint job below - os: ["ubuntu-latest"] - image: haskell:9.2.8@sha256:b3b2f3909c7381bb96b8f18766f9407a3d6f61e0f07ea95e812583ac4f442cbb - - os: ["macOS-14"] - - os: ["windows-2019"] - - os: ["self-hosted", "macos", "ARM64"] - - os: ["self-hosted", "Linux", "ARM64"] + - image: haskell:9.6.6 # Also upgrade version in the lint job below + os: ubuntu-latest # Exact version is not important, as it's only the container host) + + - image: haskell:9.6.6 + os: ubuntu-24.04-arm # Exact version is not important, as it's only the container host + + - os: macos-13 # x64 + - os: macos-14 # arm64 + - os: windows-2019 # x64 runs-on: "${{ matrix.os }}" - container: "${{ matrix.image }}" + container: "${{ matrix.image }}" outputs: do-not-prerelease: "${{ steps.build.outputs.do-not-prerelease }}" @@ -71,43 +74,40 @@ jobs: steps: - # We need `gh` installed on the Linux version. Otherwise, release artifacts won't be uploaded. name: "(Linux only) Install gh" - if: "contains(matrix.os, 'ubuntu-latest')" + if: startsWith(matrix.image, 'haskell') run: | curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null apt-get update - apt-get install gh + apt-get install -y gh - - uses: "actions/checkout@v2" - - uses: "actions/setup-node@v2" + - uses: "actions/checkout@v4" + - uses: "actions/setup-node@v4" with: - node-version: "16" + node-version: "22" - id: "haskell" name: "(Non-Linux only) Install Haskell" - # Note: here we exclude the self-hosted runners because this action does not work on ARM - # and their Haskell environment is instead provided by a nix-shell - # See https://github.com/purescript/purescript/pulls/4455 - if: "!contains(matrix.os, 'ubuntu-latest') && !contains(matrix.os, 'self-hosted')" + if: startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'windows') uses: "haskell-actions/setup@v2" with: + ghc-version: "9.6.6" enable-stack: true stack-version: "${{ env.STACK_VERSION }}" stack-no-global: true - - name: "(Linux only) Check Stack version and fix working directory ownership" - if: "contains(matrix.os, 'ubuntu-latest')" + - name: "(Linux only) Fix working directory ownership" + if: startsWith(matrix.image, 'haskell') run: | - [ "$(stack --numeric-version)" = "$STACK_VERSION" ] chown root:root . - - uses: "actions/cache@v2" + - uses: "actions/cache@v4" with: path: | /root/.stack ${{ steps.haskell.outputs.stack-root }} - key: "${{ matrix.image || runner.os }}--MdyPsf-${{ hashFiles('stack.yaml') }}" + key: "${{ matrix.image || runner.os }}--MdyPsf-${{ hashFiles('stack.yaml.lock', 'purescript.cabal') }}" - name: "(Windows only) Configure Stack to store its programs in STACK_ROOT" # This ensures that the local GHC and MSYS binaries that Stack installs @@ -122,16 +122,16 @@ jobs: run: "ci/fix-home ci/build.sh" - name: "(Linux only) Glob tests" - if: "contains(matrix.os, 'ubuntu-latest')" + if: startsWith(matrix.image, 'haskell') working-directory: "sdist-test" # We build in this directory in build.sh, so this is where we need to # launch `stack exec`. The actual glob checks happen in a temporary directory. run: | - apt-get install tree + apt-get install -y tree ../ci/fix-home stack exec bash ../glob-test.sh - name: "(Linux only) Build the entire package set" - if: "contains(matrix.os, 'ubuntu-latest')" + if: startsWith(matrix.image, 'haskell') # We build in this directory in build.sh, so this is where we need to # launch `stack exec`. The actual package-set building happens in a # temporary directory. @@ -144,11 +144,11 @@ jobs: # Moreover, npm has a hook issue that will cause spago to fail to install # We upgrade npm to fix this run: | - npm i -g npm@8.8.0 + apt-get install -y jq ../ci/fix-home stack --haddock exec ../ci/build-package-set.sh - name: Verify that 'libtinfo' isn't in binary - if: "runner.os == 'Linux'" + if: runner.os == 'Linux' working-directory: "sdist-test" run: | if [ $(ldd $(../ci/fix-home stack path --local-doc-root)/../bin/purs | grep 'libtinfo' | wc -l) -ge 1 ]; then @@ -157,17 +157,6 @@ jobs: exit 1 fi - - name: "(Self-hosted Linux ARM64 only) Patch the binary to work on non-Nix systems" - if: "runner.os == 'Linux' && runner.arch == 'ARM64'" - working-directory: "sdist-test" - # The self-hosted build happens inside a nix-shell that provides a working stack binary - # on ARM systems, and while the macOS binary is fine - because macOS binaries are almost - # statically linked), the linux ones are all pointing at the nix store. - # So here we first point the binary to the right linker that should work on a generic linux, - # and then fix the RUNPATH with the right location to load the shared libraries from - run: | - patchelf --set-interpreter /usr/lib/ld-linux-aarch64.so.1 --set-rpath /usr/lib/aarch64-linux-gnu $(stack path --local-doc-root)/../bin/purs - - name: "(Release/prerelease only) Create bundle" if: "${{ env.CI_RELEASE == 'true' || env.CI_PRERELEASE == 'true' && steps.build.outputs.do-not-prerelease != 'true' }}" run: | @@ -199,7 +188,7 @@ jobs: - name: "(Prerelease only) Upload bundle" if: "${{ env.CI_PRERELEASE == 'true' && steps.build.outputs.do-not-prerelease != 'true' }}" - uses: "actions/upload-artifact@v3" + uses: "actions/upload-artifact@v4.6.0" with: name: "${{ runner.os }}-${{ runner.arch }}-bundle" path: | @@ -208,59 +197,39 @@ jobs: - name: "(Release only) Publish bundle" if: "${{ env.CI_RELEASE == 'true' }}" - # This requires the gh command line tool to be installed on our - # self-hosted runners env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" run: "gh release upload --clobber ${{ github.ref_name }} sdist-test/bundle/*.{tar.gz,sha}" lint: - runs-on: "ubuntu-latest" - # At the moment, this is a different image from the image used for - # compilation, though the GHC versions match. This is because the - # compilation image uses an old version of glibc, which we want because it - # means our published binaries will work on the widest number of platforms. - # But the HLint binary downloaded by this job requires a newer glibc - # version. - container: haskell:9.2.8@sha256:b3b2f3909c7381bb96b8f18766f9407a3d6f61e0f07ea95e812583ac4f442cbb + container: haskell:9.6.6 + runs-on: ubuntu-latest # Exact version is not important, as it's only the container host steps: - - uses: "actions/checkout@v2" + - uses: "actions/checkout@v4" - name: "Fix working directory ownership" run: | chown root:root . - - uses: "actions/cache@v2" + - uses: "actions/cache@v4" with: path: | /root/.stack - key: "lint-${{ hashFiles('stack.yaml') }}" + key: "lint-${{ hashFiles('stack.yaml.lock', 'purescript.cabal') }}" - run: "ci/fix-home ci/run-hlint.sh --git" env: VERSION: "3.5" - # Note: the weeder version will need to be updated when we next update our version - # of GHC. - # - # weeder-2.2.0 has somewhat strange version deps. It doesn't appear to - # support the exact versions of dhall and generic-lens in LTS-18. - # However, forcing it to use the versions of dhall and generic-lens in - # LTS-18 doesn't cause any problems when building, so the following - # commands build weeder while ignoring version constraints. - name: Install weeder run: | - # The `stack.yaml` file is copied to a separate file so that - # adding `allow-newer: true` doesn't affect any subsequant - # calls to `stack`. - cp stack.yaml stack-weeder.yaml - # `allow-newer: true` is needed so that weeder-2.2.0 can be - # installed with the dependencies present in LTS-18. - echo 'allow-newer: true' >> stack-weeder.yaml - ci/fix-home stack --no-terminal --jobs=2 build --copy-compiler-tool --stack-yaml ./stack-weeder.yaml weeder-2.4.0 + ci/fix-home stack --no-terminal --jobs=2 \ + build --copy-compiler-tool weeder-2.8.0 - - run: "ci/fix-home stack --no-terminal --jobs=2 build --fast --ghc-options -fwrite-ide-info" + - run: | + ci/fix-home stack --no-terminal --jobs=2 \ + build --fast --ghc-options -fwrite-ide-info - run: "ci/fix-home stack exec weeder" @@ -268,26 +237,28 @@ jobs: # reference from our test suite to count in the above check; the fact # that a function is tested is not evidence that it's needed. But we also # don't want to leave weeds lying around in our test suite either. - - run: "ci/fix-home stack --no-terminal --jobs=2 build --fast --test --no-run-tests --ghc-options -fwrite-ide-info" + - run: | + ci/fix-home stack --no-terminal --jobs=2 \ + build --fast --test --no-run-tests --ghc-options -fwrite-ide-info - run: "ci/fix-home stack exec weeder" make-prerelease: - runs-on: "ubuntu-latest" + runs-on: ubuntu-latest needs: - "build" - "lint" if: "${{ github.event_name == 'push' && needs.build.outputs.do-not-prerelease != 'true' }}" steps: - - uses: "actions/download-artifact@v3" + - uses: "actions/download-artifact@v4" - uses: "ncipollo/release-action@v1.10.0" with: tag: "v${{ needs.build.outputs.version }}" artifacts: "*-bundle/*" prerelease: true body: "This is an automated preview release. Get the latest stable release [here](https://github.com/purescript/purescript/releases/latest)." - - uses: "actions/checkout@v3" - - uses: "actions/setup-node@v3" + - uses: "actions/checkout@v4" + - uses: "actions/setup-node@v4" with: node-version: "16.x" registry-url: "https://registry.npmjs.org" diff --git a/.gitignore b/.gitignore index 0454beffcb..73b2b4678f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ bin dist cabal-dev .cabal-sandbox -stack.yaml.lock cabal.sandbox.config dist-newstyle/ cabal.project.local* diff --git a/CHANGELOG.d/internal_upgrade_to_ghc_9.6.md b/CHANGELOG.d/internal_upgrade_to_ghc_9.6.md new file mode 100644 index 0000000000..6622b6baed --- /dev/null +++ b/CHANGELOG.d/internal_upgrade_to_ghc_9.6.md @@ -0,0 +1,2 @@ +* Upgrade GHC to [`9.6.6`](https://downloads.haskell.org/~ghc/9.6.6/docs/users_guide/9.6.6-notes.html), Stackage LTS `22.43` +* Minimum required glibc version is bumped from [`2.28` to `2.31`](https://sourceware.org/glibc/wiki/Glibc%20Timeline) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index aa5ddefd3f..cfbb98e362 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -16,6 +16,7 @@ If you would prefer to use different terms, please use the section below instead | :------- | :--- | :------ | | [@5outh](https://github.com/5outh) | Benjamin Kovach | [MIT license] | | [@actionshrimp](https://github.com/actionshrimp) | David Aitken | [MIT license] | +| [@ad-si](https://github.com/ad-si) | Adrian Sieber | [MIT license] | | [@adnelson](https://github.com/adnelson) | Allen Nelson | [MIT license] | | [@alexbiehl](https://github.com/alexbiehl) | Alexander Biehl | [MIT license] | | [@andreypopp](https://github.com/andreypopp) | Andrey Popp | [MIT license] | diff --git a/INSTALL.md b/INSTALL.md index 0bccc516c7..03f7748636 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -4,12 +4,13 @@ If you are having difficulty installing the PureScript compiler, feel free to as ## Requirements -The PureScript compiler is built using GHC 9.2.8, and should be able to run on any operating system supported by GHC 9.2.8. In particular: +The PureScript compiler is built using GHC 9.6.6, and should be able to run on any operating system supported by GHC 9.6.6. +In particular: * for Windows users, versions predating Vista are not officially supported, * for macOS / OS X users, versions predating Mac OS X 10.7 (Lion) are not officially supported. -See also for more details about the operating systems which GHC 9.2.8 supports. +See also for more details about the operating systems which GHC 9.6.6 supports. ## Official prebuilt binaries diff --git a/LICENSE b/LICENSE index 490ff3651c..713d3371a3 100644 --- a/LICENSE +++ b/LICENSE @@ -107,7 +107,6 @@ PureScript uses the following Haskell library packages. Their license files foll optparse-applicative parallel parsec - pattern-arrows pretty primitive process @@ -3186,29 +3185,6 @@ parsec LICENSE file: negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage. -pattern-arrows LICENSE file: - - The MIT License (MIT) - - Copyright (c) 2013 Phil Freeman - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - pretty LICENSE file: This library (libraries/pretty) is derived from code from diff --git a/Makefile b/Makefile index 034a2d33eb..fc5f7c1add 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ package = purescript exe_target = purs stack_yaml = STACK_YAML="stack.yaml" stack = $(stack_yaml) stack +stack_dir = .stack-work .DEFAULT_GOAL := help @@ -14,6 +15,10 @@ $(bin_dir)/hlint: ci/install-hlint.sh clean: ## Remove build artifacts rm -fr $(bin_dir) rm -fr $(build_dir) + rm -fr $(stack_dir) + rm -fr dist-newstyle + rm -fr .psci_modules + rm -fr .test_modules help: ## Print documentation @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/app/Command/Compile.hs b/app/Command/Compile.hs index e861601cfd..ca5c11940d 100644 --- a/app/Command/Compile.hs +++ b/app/Command/Compile.hs @@ -75,7 +75,7 @@ compile PSCMakeOptions{..} = do let filePathMap = M.fromList $ map (\(fp, pm) -> (P.getModuleName $ CST.resPartial pm, Right fp)) ms foreigns <- inferForeignModules filePathMap let makeActions = buildMakeActions pscmOutputDir filePathMap foreigns pscmUsePrefix - P.make makeActions (map snd ms) + P.make_ makeActions (map snd ms) printWarningsAndErrors (P.optionsVerboseErrors pscmOpts) pscmJSONErrors moduleFiles makeWarnings makeErrors exitSuccess diff --git a/app/Command/Docs.hs b/app/Command/Docs.hs index 987023c98c..22bd6bdd3f 100644 --- a/app/Command/Docs.hs +++ b/app/Command/Docs.hs @@ -15,7 +15,8 @@ import Language.PureScript.Docs qualified as D import Language.PureScript.Docs.Tags (dumpCtags, dumpEtags) import Language.PureScript.Glob (PSCGlobs(..), toInputGlobs, warnFileTypeNotFound) import Options.Applicative qualified as Opts -import Text.PrettyPrint.ANSI.Leijen qualified as PP +import Prettyprinter qualified as PP +import Prettyprinter.Render.Terminal (AnsiStyle) import SharedCLI qualified import System.Directory (getCurrentDirectory, createDirectoryIfMissing, removeFile) import System.Exit (exitFailure) @@ -113,10 +114,10 @@ defaultOutputForFormat fmt = Ctags -> "tags" pscDocsOptions :: Opts.Parser PSCDocsOptions -pscDocsOptions = - PSCDocsOptions <$> format - <*> output - <*> compileOutputDir +pscDocsOptions = + PSCDocsOptions <$> format + <*> output + <*> compileOutputDir <*> many SharedCLI.inputFile <*> SharedCLI.globInputFile <*> many SharedCLI.excludeFiles @@ -150,9 +151,9 @@ infoModList :: Opts.InfoMod a infoModList = Opts.fullDesc <> footerInfo where footerInfo = Opts.footerDoc $ Just examples -examples :: PP.Doc +examples :: PP.Doc AnsiStyle examples = - PP.vcat $ map PP.text + PP.vcat [ "Examples:" , " write documentation for all modules to ./generated-docs:" , " purs docs \"src/**/*.purs\" \".psc-package/*/*/*/src/**/*.purs\"" diff --git a/app/Main.hs b/app/Main.hs index 374fcee282..26a3048cd5 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -14,9 +14,10 @@ import Command.REPL qualified as REPL import Control.Monad (join) import Data.Foldable (fold) import Options.Applicative qualified as Opts +import Prettyprinter qualified as Doc +import Prettyprinter.Render.Terminal (AnsiStyle) import System.Environment (getArgs) import System.IO qualified as IO -import Text.PrettyPrint.ANSI.Leijen qualified as Doc import Version (versionString) @@ -40,11 +41,11 @@ main = do "For example, `purs compile --help` displays options specific to the `compile` command." , Doc.hardline , Doc.hardline - , Doc.text $ "purs " ++ versionString + , Doc.pretty $ "purs " ++ versionString ] - para :: String -> Doc.Doc - para = foldr (Doc.) Doc.empty . map Doc.text . words + para :: String -> Doc.Doc AnsiStyle + para = foldr (\x y -> x <> Doc.softline <> y) mempty . map Doc.pretty . words -- | Displays full command help when invoked with no arguments. execParserPure :: Opts.ParserInfo a -> [String] -> Opts.ParserResult a diff --git a/app/static/index.js b/app/static/index.js new file mode 100644 index 0000000000..f496540c4c --- /dev/null +++ b/app/static/index.js @@ -0,0 +1,74 @@ +var get = function get(uri, callback, onError) { + var request = new XMLHttpRequest(); + request.addEventListener('load', function() { + callback(request.responseText); + }); + request.addEventListener('error', onError); + request.open('GET', uri); + request.send(); +}; +var evaluate = function evaluate(js) { + var buffer = []; + // Save the old console.log function + var oldLog = console.log; + console.log = function(s) { + // Push log output into a temporary buffer + // which will be returned to PSCi. + buffer.push(s); + }; + // Replace any require and import statements with lookups on the PSCI object + // and export statements with assignments to module.exports. + var replaced = js.replace(/require\("[^"]*"\)/g, function(s) { + return "PSCI['" + s.split('/')[1] + "']"; + }).replace(/import \* as ([^\s]+) from "([^"]*)"/g, function (_, as, from) { + return "var " + as + " = PSCI['" + from.split('/')[1] + "']"; + }).replace(/export \{([^}]+)\} from "\.\/foreign\.js";?/g, function (_, exports) { + return exports.replace(/^\s*([^,\s]+),?\s*$/gm, function (_, exported) { + return "module.exports." + exported + " = $foreign." + exported + ";"; + }); + }).replace(/export \{([^}]+)\};?/g, function (_, exports) { + return exports.replace(/^\s*([^,\s]+)(?: as ([^\s]+))?,?\s*$/gm, function (_, exported, as) { + return "module.exports." + (as || exported) + " = " + exported + ";"; + }); + }); + // Wrap the module and evaluate it. + var wrapped = + [ 'var module = { exports: {} };' + , '(function(module) {' + , replaced + , '})(module);' + , 'return module.exports["$main"] && module.exports["$main"]();' + ].join('\n'); + new Function(wrapped)(); + // Restore console.log + console.log = oldLog; + return buffer.join('\n'); +}; +window.onload = function() { + var socket = new WebSocket('ws://localhost:' + location.port); + var evalNext = function reload() { + get('js/latest.js', function(response) { + try { + var result = evaluate(response); + socket.send(result); + } catch (ex) { + socket.send(ex.stack); + } + }, function(err) { + socket.send('Error sending JavaScript'); + }); + }; + socket.onopen = function() { + console.log('Connected'); + socket.onmessage = function(event) { + switch (event.data) { + case 'eval': + evalNext(); + break; + case 'reload': + location.reload(); + break; + } + }; + }; +}; diff --git a/cabal.project b/cabal.project index 51c7ecb87d..61c5c9bd35 100644 --- a/cabal.project +++ b/cabal.project @@ -1,2 +1,7 @@ packages: purescript.cabal + +source-repository-package + type: git + location: https://github.com/purescript/cheapskate.git + tag: 8bfaf4beeb108e97a274ed51303f278905979e87 diff --git a/ci/build-package-set.sh b/ci/build-package-set.sh index 12a6fcb34c..f11b556871 100755 --- a/ci/build-package-set.sh +++ b/ci/build-package-set.sh @@ -5,7 +5,7 @@ shopt -s nullglob psroot=$(dirname "$(dirname "$(realpath "$0")")") -if [[ "${CI:-}" && "$(echo $psroot/CHANGELOG.d/breaking_*)" ]]; then +if [[ "${CI:-}" && "$(echo "$psroot"/CHANGELOG.d/breaking_*)" ]]; then echo "Skipping package-set build due to unreleased breaking changes" exit 0 fi @@ -16,23 +16,17 @@ export PATH="$tmpdir/node_modules/.bin:$PATH" cd "$tmpdir" echo ::group::Ensure Spago is available -which spago || npm install spago@0.20.8 +which spago || npm install spago@0.93.43 echo ::endgroup:: echo ::group::Create dummy project -echo 'let upstream = https://github.com/purescript/package-sets/releases/download/XXX/packages.dhall in upstream' > packages.dhall -echo '{ name = "my-project", dependencies = [] : List Text, packages = ./packages.dhall, sources = [] : List Text }' > spago.dhall -spago upgrade-set -# Override the `metadata` package's version to match `purs` version -# so that `spago build` actually works -sed -i'' "\$c in upstream with metadata.version = \"v$(purs --version | { read v z && echo $v; })\"" packages.dhall -spago install $(spago ls packages | while read name z; do if [[ $name != metadata ]]; then echo $name; fi; done) +spago init --name purescript-dummy echo ::endgroup:: echo ::group::Compile package set -spago build +spago ls packages --json | jq -r 'keys[]' | xargs spago install echo ::endgroup:: echo ::group::Document package set -spago docs --no-search +spago docs echo ::endgroup:: diff --git a/hie.yaml b/hie.yaml new file mode 100644 index 0000000000..86de29471c --- /dev/null +++ b/hie.yaml @@ -0,0 +1,2 @@ +cradle: + stack: {} diff --git a/profile-admin.txt b/profile-admin.txt index e05582b977..814732e30f 100644 --- a/profile-admin.txt +++ b/profile-admin.txt @@ -1,27 +1,27 @@ 'purs' 'compile' '--source-globs-file' '.spago/sources.txt' +RTS '-N' '-A256m' '-n16m' '-sprofile.txt' - 764,684,334,200 bytes allocated in the heap - 66,592,890,256 bytes copied during GC - 5,965,029,568 bytes maximum residency (7 sample(s)) - 55,635,776 bytes maximum slop - 19931 MiB total memory in use (0 MB lost due to fragmentation) + 615,026,369,264 bytes allocated in the heap + 62,526,339,128 bytes copied during GC + 4,808,133,160 bytes maximum residency (7 sample(s)) + 30,469,184 bytes maximum slop + 16158 MiB total memory in use (0 MiB lost due to fragmentation) Tot time (elapsed) Avg pause Max pause - Gen 0 344 colls, 344 par 109.657s 24.231s 0.0704s 0.9072s - Gen 1 7 colls, 6 par 27.205s 6.308s 0.9011s 2.6484s + Gen 0 239 colls, 239 par 59.390s 7.687s 0.0322s 0.1657s + Gen 1 7 colls, 6 par 17.637s 4.178s 0.5969s 1.2704s - Parallel GC work balance: 87.29% (serial 0%, perfect 100%) + Parallel GC work balance: 91.91% (serial 0%, perfect 100%) - TASKS: 61 (1 bound, 60 peak workers (60 total), using -N10) + TASKS: 67 (1 bound, 65 peak workers (66 total), using -N10) SPARKS: 7516 (7516 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled) - INIT time 0.003s ( 0.153s elapsed) - MUT time 345.596s ( 91.867s elapsed) - GC time 136.862s ( 30.539s elapsed) - EXIT time 0.170s ( 0.001s elapsed) - Total time 482.631s (122.559s elapsed) + INIT time 0.151s ( 0.151s elapsed) + MUT time 304.986s ( 90.340s elapsed) + GC time 77.027s ( 11.865s elapsed) + EXIT time 0.249s ( 0.009s elapsed) + Total time 382.413s (102.365s elapsed) - Alloc rate 2,212,653,609 bytes per MUT second + Alloc rate 2,016,569,461 bytes per MUT second - Productivity 71.6% of total user, 75.0% of total elapsed + Productivity 79.8% of total user, 88.3% of total elapsed diff --git a/profile.txt b/profile.txt index bb86538d5f..d46e022a49 100644 --- a/profile.txt +++ b/profile.txt @@ -1,27 +1,27 @@ 'purs' 'compile' '--source-globs-file' '.spago/sources.txt' +RTS '-N' '-A256m' '-n16m' '-sprofile.txt' -1,514,656,409,184 bytes allocated in the heap - 98,339,524,248 bytes copied during GC - 3,076,113,760 bytes maximum residency (13 sample(s)) - 48,308,232 bytes maximum slop - 11349 MiB total memory in use (0 MB lost due to fragmentation) +1,239,641,572,944 bytes allocated in the heap + 92,117,540,648 bytes copied during GC + 2,525,848,440 bytes maximum residency (16 sample(s)) + 41,515,920 bytes maximum slop + 9680 MiB total memory in use (0 MiB lost due to fragmentation) Tot time (elapsed) Avg pause Max pause - Gen 0 697 colls, 697 par 202.812s 43.916s 0.0630s 0.6948s - Gen 1 13 colls, 12 par 32.681s 4.654s 0.3580s 0.5375s + Gen 0 462 colls, 462 par 98.367s 12.592s 0.0273s 0.1880s + Gen 1 16 colls, 15 par 26.135s 3.701s 0.2313s 0.3772s - Parallel GC work balance: 83.67% (serial 0%, perfect 100%) + Parallel GC work balance: 90.30% (serial 0%, perfect 100%) - TASKS: 69 (1 bound, 66 peak workers (68 total), using -N10) + TASKS: 70 (1 bound, 69 peak workers (69 total), using -N10) SPARKS: 7516 (7516 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled) - INIT time 0.003s ( 0.157s elapsed) - MUT time 702.424s (133.463s elapsed) - GC time 235.493s ( 48.571s elapsed) - EXIT time 0.067s ( 0.008s elapsed) - Total time 937.987s (182.199s elapsed) + INIT time 0.174s ( 0.173s elapsed) + MUT time 612.239s (126.344s elapsed) + GC time 124.502s ( 16.293s elapsed) + EXIT time 0.107s ( 0.009s elapsed) + Total time 737.021s (142.819s elapsed) - Alloc rate 2,156,328,748 bytes per MUT second + Alloc rate 2,024,767,533 bytes per MUT second - Productivity 74.9% of total user, 73.3% of total elapsed + Productivity 83.1% of total user, 88.5% of total elapsed diff --git a/purescript.cabal b/purescript.cabal index 02f64daa51..401775a5e2 100644 --- a/purescript.cabal +++ b/purescript.cabal @@ -159,22 +159,23 @@ common defaults -- modules must be parseable by this library otherwise the compiler -- will reject them. It should therefore always be pinned to a single -- specific version. - aeson >=2.0.3.0 && <2.1, - aeson-better-errors >=0.9.1.1 && <0.10, - ansi-terminal >=0.11.3 && <0.12, + aeson >=2.0.3.0 && <2.2, + aeson-better-errors >=0.9.1.3 && <0.10, + ansi-terminal >=0.11.3 && <1.1, array >=0.5.4.0 && <0.6, - base >=4.16.2.0 && <4.17, - intern ==0.9.4, + intern, + base >=4.16.2.0 && <4.19, blaze-html >=0.9.1.2 && <0.10, bower-json >=1.1.0.0 && <1.2, boxes >=0.1.5 && <0.2, bytestring >=0.11.3.1 && <0.12, - Cabal >=3.6.3.0 && <3.7, + Cabal >=3.10.3.0 && <3.11, cborg >=0.2.7.0 && <0.3, - serialise >=0.2.5.0 && <0.3, cheapskate >=0.1.1.2 && <0.2, clock >=0.8.3 && <0.9, containers >=0.6.5.1 && <0.7, + -- unordered-containers, + -- hashable, cryptonite ==0.30.*, data-ordlist >=0.4.7.0 && <0.5, deepseq >=1.4.6.1 && <1.5, @@ -184,39 +185,39 @@ common defaults file-embed >=0.0.15.0 && <0.1, filepath >=1.4.2.2 && <1.5, Glob >=0.10.2 && <0.11, - haskeline >=0.8.2 && <0.9, + haskeline ==0.8.2, language-javascript ==0.7.0.0, - lens >=5.1.1 && <5.2, + lens >=5.1.1 && <5.3, lifted-async >=0.10.2.2 && <0.11, lifted-base >=0.2.3.12 && <0.3, - memory >=0.17.0 && <0.18, + memory >=0.17.0 && <0.19, monad-control >=1.0.3.1 && <1.1, monad-logger >=0.3.36 && <0.4, monoidal-containers >=0.6.2.0 && <0.7, - mtl >=2.2.2 && <2.3, + mtl >=2.2.2 && <2.4, parallel >=3.2.2.0 && <3.3, parsec >=3.1.15.0 && <3.2, - pattern-arrows >=0.0.2 && <0.1, - process ==1.6.13.1, + process >=1.6.19.0 && <1.7, protolude >=0.3.1 && <0.4, regex-tdfa >=1.3.1.2 && <1.4, safe >=0.3.19 && <0.4, scientific >=0.3.7.0 && <0.4, + semialign >=1.2.0.1 && <1.4, semigroups ==0.20.*, - semialign >=1.2.0.1 && <1.3, + serialise >=0.2.5.0 && <0.3, sourcemap >=0.1.7 && <0.2, - sqlite-simple ==0.4.18.2, + sqlite-simple, stm >=2.5.0.2 && <2.6, stringsearch >=0.3.6.6 && <0.4, - template-haskell >=2.18.0.0 && <2.19, - text >=1.2.5.0 && <1.3, - these >=1.1.1.1 && <1.2, - time >=1.11.1.1 && <1.12, - transformers >=0.5.6.2 && <0.6, + template-haskell >=2.18.0.0 && <2.21, + text >=1.2.5.0 && <2.1, + these >=1.1.1.1 && <1.3, + time >=1.11.1.1 && <1.13, + transformers >=0.5.6.2 && <0.7, transformers-base >=0.4.6 && <0.5, utf8-string >=1.0.2 && <1.1, - vector >=0.12.3.1 && <0.13, - witherable >=0.4.2 && <0.5 + vector >=0.12.3.1 && <0.14, + witherable >=0.4.2 && <0.5, library import: defaults @@ -225,6 +226,7 @@ library Control.Monad.Logger Control.Monad.Supply Control.Monad.Supply.Class + Control.PatternArrows Language.PureScript Language.PureScript.AST Language.PureScript.AST.Binders @@ -346,6 +348,7 @@ library Language.PureScript.Make.BuildPlan Language.PureScript.Make.Cache Language.PureScript.Make.IdeCache + Language.PureScript.Make.ExternsDiff Language.PureScript.Make.Monad Language.PureScript.ModuleDependencies Language.PureScript.Names @@ -363,6 +366,7 @@ library Language.PureScript.Renamer Language.PureScript.Roles Language.PureScript.Sugar + Language.PureScript.Sugar.Accessor Language.PureScript.Sugar.AdoNotation Language.PureScript.Sugar.BindingGroups Language.PureScript.Sugar.CaseDeclarations @@ -412,12 +416,13 @@ executable purs import: defaults hs-source-dirs: app main-is: Main.hs - ghc-options: -fno-warn-unused-do-bind -threaded -rtsopts -with-rtsopts=-N -Wno-unused-packages -eventlog + ghc-options: -fno-warn-unused-do-bind -threaded -rtsopts -with-rtsopts=-N -Wno-unused-packages build-depends: - ansi-wl-pprint >=0.6.9 && <0.7, + prettyprinter >=1.6 && <1.8, + prettyprinter-ansi-terminal >=1.1.1 && <1.2, exceptions >=0.10.4 && <0.11, network >=3.1.2.7 && <3.2, - optparse-applicative >=0.17.0.0 && <0.18, + optparse-applicative >=0.17.0.0 && <0.19, purescript if flag(release) cpp-options: -DRELEASE @@ -452,7 +457,7 @@ test-suite tests build-depends: purescript, generic-random >=1.5.0.1 && <1.6, - hspec >= 2.10.7 && < 3, + hspec >= 2.11.10 && < 3, HUnit >=1.6.2.0 && <1.7, newtype >=0.2.2.0 && <0.3, QuickCheck >=2.14.2 && <2.15, diff --git a/src/Control/Monad/Supply.hs b/src/Control/Monad/Supply.hs index dd447a9c39..1aa6c3df89 100644 --- a/src/Control/Monad/Supply.hs +++ b/src/Control/Monad/Supply.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE UndecidableInstances #-} -- | -- Fresh variable supply -- @@ -9,21 +10,29 @@ import Control.Applicative (Alternative) import Control.Monad.Error.Class (MonadError(..)) import Control.Monad.Reader (MonadReader, MonadTrans) import Control.Monad (MonadPlus) -import Control.Monad.State (StateT(..)) +import Control.Monad.State (MonadState(..)) +import Control.Monad.State.Strict (StateT(..)) import Control.Monad.Writer (MonadWriter) +import Data.Int (Int64) import Data.Functor.Identity (Identity(..)) +import Control.Monad.IO.Class (MonadIO) +import Control.Monad.Trans (lift) -newtype SupplyT m a = SupplyT { unSupplyT :: StateT Integer m a } - deriving (Functor, Applicative, Monad, MonadTrans, MonadError e, MonadWriter w, MonadReader r, Alternative, MonadPlus) +newtype SupplyT m a = SupplyT { unSupplyT :: StateT Int64 m a } + deriving (Functor, Applicative, Monad, MonadTrans, MonadError e, MonadWriter w, MonadReader r, Alternative, MonadPlus, MonadIO) -runSupplyT :: Integer -> SupplyT m a -> m (a, Integer) +runSupplyT :: Int64 -> SupplyT m a -> m (a, Int64) runSupplyT n = flip runStateT n . unSupplyT -evalSupplyT :: (Functor m) => Integer -> SupplyT m a -> m a +evalSupplyT :: (Functor m) => Int64 -> SupplyT m a -> m a evalSupplyT n = fmap fst . runSupplyT n type Supply = SupplyT Identity -runSupply :: Integer -> Supply a -> (a, Integer) +runSupply :: Int64 -> Supply a -> (a, Int64) runSupply n = runIdentity . runSupplyT n + +instance MonadState s m => MonadState s (SupplyT m) where + get = lift get + put = lift . put diff --git a/src/Control/Monad/Supply/Class.hs b/src/Control/Monad/Supply/Class.hs index e8656f0c69..8acdc36344 100644 --- a/src/Control/Monad/Supply/Class.hs +++ b/src/Control/Monad/Supply/Class.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE TypeOperators #-} + -- | -- A class for monads supporting a supply of fresh names -- @@ -11,14 +13,18 @@ import Control.Monad.State (StateT) import Control.Monad.Supply (SupplyT(..)) import Control.Monad.Writer (WriterT) import Data.Text (Text, pack) +import Data.Int (Int64) class Monad m => MonadSupply m where - fresh :: m Integer - peek :: m Integer - default fresh :: (MonadTrans t, MonadSupply n, m ~ t n) => m Integer + fresh :: m Int64 + peek :: m Int64 + consumeUpTo :: Int64 -> m () + default fresh :: (MonadTrans t, MonadSupply n, m ~ t n) => m Int64 fresh = lift fresh - default peek :: (MonadTrans t, MonadSupply n, m ~ t n) => m Integer + default peek :: (MonadTrans t, MonadSupply n, m ~ t n) => m Int64 peek = lift peek + default consumeUpTo :: (MonadTrans t, MonadSupply n, m ~ t n) => Int64 -> m () + consumeUpTo n = lift (consumeUpTo n) instance Monad m => MonadSupply (SupplyT m) where fresh = SupplyT $ do @@ -26,6 +32,9 @@ instance Monad m => MonadSupply (SupplyT m) where put (n + 1) return n peek = SupplyT get + consumeUpTo n = SupplyT $ do + m <- get + put $ max n m instance MonadSupply m => MonadSupply (StateT s m) instance (Monoid w, MonadSupply m) => MonadSupply (WriterT w m) @@ -33,3 +42,4 @@ instance (Monoid w, MonadSupply m) => MonadSupply (RWST r w s m) freshName :: MonadSupply m => m Text freshName = fmap (("$" <> ) . pack . show) fresh + diff --git a/src/Control/PatternArrows.hs b/src/Control/PatternArrows.hs new file mode 100644 index 0000000000..b01d1cccdc --- /dev/null +++ b/src/Control/PatternArrows.hs @@ -0,0 +1,118 @@ +----------------------------------------------------------------------------- +-- +-- Module : Control.PatternArrows +-- Copyright : (c) Phil Freeman 2013 +-- License : MIT +-- +-- Maintainer : Phil Freeman +-- Stability : experimental +-- Portability : +-- +-- | +-- Arrows for Pretty Printing +-- +----------------------------------------------------------------------------- + +{-# LANGUAGE GeneralizedNewtypeDeriving, GADTs #-} + +module Control.PatternArrows where + +import Prelude + +import Control.Arrow ((***), (<+>)) +import Control.Arrow qualified as A +import Control.Category ((>>>)) +import Control.Category qualified as C +import Control.Monad.State +import Control.Monad.Fix (fix) + +-- | +-- A first-order pattern match +-- +-- A pattern is a Kleisli arrow for the @StateT Maybe@ monad. That is, patterns can fail, and can carry user-defined state. +-- +newtype Pattern u a b = Pattern { runPattern :: A.Kleisli (StateT u Maybe) a b } deriving (A.Arrow, A.ArrowZero, A.ArrowPlus) + +instance C.Category (Pattern u) where + id = Pattern C.id + Pattern p1 . Pattern p2 = Pattern (p1 C.. p2) + +instance Functor (Pattern u a) where + fmap f (Pattern p) = Pattern $ A.Kleisli $ fmap f . A.runKleisli p + +-- | +-- Run a pattern with an input and initial user state +-- +-- Returns Nothing if the pattern fails to match +-- +pattern_ :: Pattern u a b -> u -> a -> Maybe b +pattern_ p u = flip evalStateT u . A.runKleisli (runPattern p) + +-- | +-- Construct a pattern from a function +-- +mkPattern :: (a -> Maybe b) -> Pattern u a b +mkPattern f = Pattern $ A.Kleisli (lift . f) + +-- | +-- Construct a pattern from a stateful function +-- +mkPattern' :: (a -> StateT u Maybe b) -> Pattern u a b +mkPattern' = Pattern . A.Kleisli + +-- | +-- Construct a pattern which recursively matches on the left-hand-side +-- +chainl :: Pattern u a (a, a) -> (r -> r -> r) -> Pattern u a r -> Pattern u a r +chainl g f p = fix $ \c -> g >>> ((c <+> p) *** p) >>> A.arr (uncurry f) + +-- | +-- Construct a pattern which recursively matches on the right-hand side +-- +chainr :: Pattern u a (a, a) -> (r -> r -> r) -> Pattern u a r -> Pattern u a r +chainr g f p = fix $ \c -> g >>> (p *** (c <+> p)) >>> A.arr (uncurry f) + +-- | +-- Construct a pattern which recursively matches on one-side of a tuple +-- +wrap :: Pattern u a (s, a) -> (s -> r -> r) -> Pattern u a r -> Pattern u a r +wrap g f p = fix $ \c -> g >>> (C.id *** (c <+> p)) >>> A.arr (uncurry f) + +-- | +-- Construct a pattern which matches a part of a tuple +-- +split :: Pattern u a (s, t) -> (s -> t -> r) -> Pattern u a r +split s f = s >>> A.arr (uncurry f) + +-- | +-- A table of operators +-- +data OperatorTable u a r = OperatorTable { runOperatorTable :: [ [Operator u a r] ] } + +-- | +-- An operator: +-- +-- [@AssocL@] A left-associative operator +-- +-- [@AssocR@] A right-associative operator +-- +-- [@Wrap@] A prefix-like or postfix-like operator +-- +-- [@Split@] A prefix-like or postfix-like operator which does not recurse into its operand +-- +data Operator u a r where + AssocL :: Pattern u a (a, a) -> (r -> r -> r) -> Operator u a r + AssocR :: Pattern u a (a, a) -> (r -> r -> r) -> Operator u a r + Wrap :: Pattern u a (s, a) -> (s -> r -> r) -> Operator u a r + Split :: Pattern u a (s, t) -> (s -> t -> r) -> Operator u a r + +-- | +-- Build a pretty printer from an operator table and an indecomposable pattern +-- +buildPrettyPrinter :: OperatorTable u a r -> Pattern u a r -> Pattern u a r +buildPrettyPrinter table p = foldl (\p' ops -> foldl1 (<+>) (flip map ops $ \case + AssocL pat g -> chainl pat g p' + AssocR pat g -> chainr pat g p' + Wrap pat g -> wrap pat g p' + Split pat g -> split pat g + ) <+> p') p $ runOperatorTable table diff --git a/src/Language/PureScript/AST/Declarations.hs b/src/Language/PureScript/AST/Declarations.hs index cf0c83a42d..7184cbb812 100644 --- a/src/Language/PureScript/AST/Declarations.hs +++ b/src/Language/PureScript/AST/Declarations.hs @@ -165,7 +165,7 @@ importPrim = . addDefaultImport (Qualified ByNullSourcePos primModName) data NameSource = UserNamed | CompilerNamed - deriving (Show, Generic, NFData, Serialise) + deriving (Eq, Show, Generic, NFData, Serialise) -- | -- An item in a list of explicit imports or exports diff --git a/src/Language/PureScript/AST/Declarations/ChainId.hs b/src/Language/PureScript/AST/Declarations/ChainId.hs index 12e64154ac..e55ebdb748 100644 --- a/src/Language/PureScript/AST/Declarations/ChainId.hs +++ b/src/Language/PureScript/AST/Declarations/ChainId.hs @@ -1,5 +1,5 @@ module Language.PureScript.AST.Declarations.ChainId - ( ChainId + ( ChainId(..) , mkChainId ) where diff --git a/src/Language/PureScript/CodeGen/JS.hs b/src/Language/PureScript/CodeGen/JS.hs index 14d122a37d..3a4e371187 100644 --- a/src/Language/PureScript/CodeGen/JS.hs +++ b/src/Language/PureScript/CodeGen/JS.hs @@ -9,7 +9,6 @@ module Language.PureScript.CodeGen.JS import Prelude import Protolude (ordNub) -import Control.Applicative (liftA2) import Control.Monad (forM, replicateM, void) import Control.Monad.Except (MonadError, throwError) import Control.Monad.Reader (MonadReader, asks) diff --git a/src/Language/PureScript/Constants/Libs.hs b/src/Language/PureScript/Constants/Libs.hs index 75c7385e0e..0b44d3e408 100644 --- a/src/Language/PureScript/Constants/Libs.hs +++ b/src/Language/PureScript/Constants/Libs.hs @@ -1,13 +1,15 @@ {-# LANGUAGE BlockArguments #-} {-# LANGUAGE TemplateHaskell #-} + -- | Various constants which refer to things in the Prelude and other core libraries module Language.PureScript.Constants.Libs where import Protolude qualified as P import Data.String (IsString) -import Language.PureScript.PSString (PSString) import Language.PureScript.Constants.TH qualified as TH +import Language.PureScript.PSString (PSString) +import Language.PureScript.Names (Ident (..), Qualified (..), QualifiedBy (..)) -- Core lib values @@ -166,6 +168,7 @@ $(TH.declare do TH.mod "Data.Symbol" do TH.cls "IsSymbol" + TH.asIdent do TH.var "IsSymbol" -- purescript-arrays @@ -261,4 +264,16 @@ $(TH.declare do TH.mod "Unsafe.Coerce" do TH.asPair do TH.var "unsafeCoerce" + TH.mod "Type.Proxy" do + TH.dty "Proxy" ["Proxy"] + TH.asIdent do + TH.var "Proxy" + TH.mod "Data.Record" do + TH.asIdent do + TH.var "getField" + TH.var "hasFieldRecord" + ) + +pattern IsSymbolDict :: Qualified Ident +pattern IsSymbolDict = Qualified (ByModuleName M_Data_Symbol) (Ident "IsSymbol$Dict") diff --git a/src/Language/PureScript/CoreFn/Optimizer.hs b/src/Language/PureScript/CoreFn/Optimizer.hs index 722893c439..9e2c9fa37c 100644 --- a/src/Language/PureScript/CoreFn/Optimizer.hs +++ b/src/Language/PureScript/CoreFn/Optimizer.hs @@ -9,7 +9,8 @@ import Language.PureScript.CoreFn.Expr (Bind, Expr(..)) import Language.PureScript.CoreFn.Module (Module(..)) import Language.PureScript.CoreFn.Traversals (everywhereOnValues) import Language.PureScript.Constants.Libs qualified as C - +-- import Language.PureScript.CoreImp.AST (AST(StringLiteral, ObjectLiteral)) +import Language.PureScript.AST.Literals (Literal(..)) -- | -- CoreFn optimization pass. -- @@ -22,6 +23,32 @@ optimizeModuleDecls = map transformBinds (transformBinds, _, _) = everywhereOnValues identity transformExprs identity transformExprs = optimizeDataFunctionApply + . optimizeRecordGetField + +-- | Optimize +-- `Data_Record.getField(Data_Record.hasFieldRecord(new Data_Symbol.IsSymbol(function() { return "f"; }))())(Type_Proxy.Proxy.value)(x)` +-- into +-- `x.f` +optimizeRecordGetField :: Expr a -> Expr a +optimizeRecordGetField + (App ann + (App _ + (App _ + (Var _ C.I_getField) + (App _ + (App _ + (Var _ C.I_hasFieldRecord) + (App _ + (Var _ C.IsSymbolDict) + (Literal _ (ObjectLiteral + [ ("reflectSymbol", Abs _ _ + (Literal _ (StringLiteral label))) + ])))) + _)) + (Var _ C.I_Proxy)) + object) = + Accessor ann label object +optimizeRecordGetField e = e optimizeDataFunctionApply :: Expr a -> Expr a optimizeDataFunctionApply e = case e of diff --git a/src/Language/PureScript/CoreImp/AST.hs b/src/Language/PureScript/CoreImp/AST.hs index 9711890a3e..adedeb4369 100644 --- a/src/Language/PureScript/CoreImp/AST.hs +++ b/src/Language/PureScript/CoreImp/AST.hs @@ -108,6 +108,10 @@ data AST -- ^ instanceof check | Comment CIComments AST -- ^ Commented JavaScript + -- | Import (Maybe SourceSpan) Text PSString + -- -- ^ Imported identifier and path to its module + -- | Export (Maybe SourceSpan) (NEL.NonEmpty Text) (Maybe PSString) + -- -- ^ Exported identifiers and optional path to their module (for re-exports) deriving (Show, Eq) withSourceSpan :: SourceSpan -> AST -> AST @@ -140,6 +144,8 @@ withSourceSpan withSpan = go where go (Throw _ js) = Throw ss js go (InstanceOf _ j1 j2) = InstanceOf ss j1 j2 go c@Comment{} = c + -- go (Import _ ident from) = Import ss ident from + -- go (Export _ idents from) = Export ss idents from getSourceSpan :: AST -> Maybe SourceSpan getSourceSpan = go where @@ -168,6 +174,8 @@ getSourceSpan = go where go (Throw ss _) = ss go (InstanceOf ss _ _) = ss go (Comment _ _) = Nothing + -- go (Import ss _ _) = ss + -- go (Export ss _ _) = ss everywhere :: (AST -> AST) -> AST -> AST everywhere f = go where diff --git a/src/Language/PureScript/CoreImp/Optimizer/TCO.hs b/src/Language/PureScript/CoreImp/Optimizer/TCO.hs index 34746ae3db..a1d4a47c2b 100644 --- a/src/Language/PureScript/CoreImp/Optimizer/TCO.hs +++ b/src/Language/PureScript/CoreImp/Optimizer/TCO.hs @@ -3,7 +3,7 @@ module Language.PureScript.CoreImp.Optimizer.TCO (tco) where import Prelude -import Control.Applicative (empty, liftA2) +import Control.Applicative (empty) import Control.Monad (guard) import Control.Monad.State (State, evalState, get, modify) import Data.Functor (($>), (<&>)) diff --git a/src/Language/PureScript/Docs/Collect.hs b/src/Language/PureScript/Docs/Collect.hs index 0da65d2251..ad538c1ae4 100644 --- a/src/Language/PureScript/Docs/Collect.hs +++ b/src/Language/PureScript/Docs/Collect.hs @@ -96,7 +96,7 @@ compileForDocs outputDir inputFiles = do foreigns <- P.inferForeignModules filePathMap let makeActions = (P.buildMakeActions outputDir filePathMap foreigns False) - { P.progress = liftIO . TIO.hPutStr stdout . (<> "\n") . P.renderProgressMessage "Compiling documentation for " + { P.progress = liftIO . TIO.hPutStr stdout . (<> "\n") . P.renderProgressMessage "documentation for " } P.make makeActions (map snd ms) either throwError return result diff --git a/src/Language/PureScript/Docs/RenderedCode/RenderType.hs b/src/Language/PureScript/Docs/RenderedCode/RenderType.hs index a082b4b833..c6a985b09b 100644 --- a/src/Language/PureScript/Docs/RenderedCode/RenderType.hs +++ b/src/Language/PureScript/Docs/RenderedCode/RenderType.hs @@ -233,7 +233,7 @@ renderTypeWithRole = \case renderType' :: PrettyPrintType -> RenderedCode renderType' = fromMaybe (internalError "Incomplete pattern") - . PA.pattern matchType () + . PA.pattern_ matchType () renderTypeVars :: [(TypeVarVisibility, Text, Maybe PrettyPrintType)] -> RenderedCode renderTypeVars tyVars = mintersperse sp (map renderTypeVar tyVars) @@ -252,4 +252,4 @@ renderTypeAtom = renderTypeAtom' . convertPrettyPrintType maxBound renderTypeAtom' :: PrettyPrintType -> RenderedCode renderTypeAtom' = fromMaybe (internalError "Incomplete pattern") - . PA.pattern matchTypeAtom () + . PA.pattern_ matchTypeAtom () diff --git a/src/Language/PureScript/Environment.hs b/src/Language/PureScript/Environment.hs index adb694d32f..1f7d2e6f16 100644 --- a/src/Language/PureScript/Environment.hs +++ b/src/Language/PureScript/Environment.hs @@ -82,7 +82,7 @@ data FunctionalDependency = FunctionalDependency -- ^ the type arguments which determine the determined type arguments , fdDetermined :: [Int] -- ^ the determined type arguments - } deriving (Show, Generic) + } deriving (Eq, Show, Generic) instance NFData FunctionalDependency instance Serialise FunctionalDependency diff --git a/src/Language/PureScript/Errors.hs b/src/Language/PureScript/Errors.hs index 6a15c3690c..309a4e9ba9 100644 --- a/src/Language/PureScript/Errors.hs +++ b/src/Language/PureScript/Errors.hs @@ -28,7 +28,7 @@ import Data.List (transpose, nubBy, partition, dropWhileEnd, sortOn, uncons) import Data.List.NonEmpty qualified as NEL import Data.List.NonEmpty (NonEmpty((:|))) import Data.Maybe (maybeToList, fromMaybe, isJust, mapMaybe) -import Data.Map qualified as M +import Data.IntMap.Strict qualified as M import Data.Ord (Down(..)) import Data.Set qualified as S import Data.Text qualified as T @@ -418,9 +418,9 @@ addHints hints = onErrorMessages $ \(ErrorMessage hints' se) -> ErrorMessage (hi -- | A map from rigid type variable name/unknown variable pairs to new variables. data TypeMap = TypeMap - { umSkolemMap :: M.Map Int (String, Int, Maybe SourceSpan) + { umSkolemMap :: M.IntMap (String, Int, Maybe SourceSpan) -- ^ a map from skolems to their new names, including source and naming info - , umUnknownMap :: M.Map Int Int + , umUnknownMap :: M.IntMap Int -- ^ a map from unification variables to their new names , umNextIndex :: Int -- ^ unknowns and skolems share a source of names during renaming, to diff --git a/src/Language/PureScript/Externs.hs b/src/Language/PureScript/Externs.hs index 778bd7da43..3a310676d1 100644 --- a/src/Language/PureScript/Externs.hs +++ b/src/Language/PureScript/Externs.hs @@ -13,6 +13,7 @@ module Language.PureScript.Externs , moduleToExternsFile , applyExternsFileToEnvironment , externsFileName + , currentVersion ) where import Prelude @@ -93,7 +94,7 @@ data ExternsFixity = ExternsFixity , efOperator :: OpName 'ValueOpName -- | The value the operator is an alias for , efAlias :: Qualified (Either Ident (ProperName 'ConstructorName)) - } deriving (Show, Generic, NFData) + } deriving (Eq, Show, Generic, NFData) instance Serialise ExternsFixity instance ToJSON ExternsFixity @@ -110,7 +111,7 @@ data ExternsTypeFixity = ExternsTypeFixity , efTypeOperator :: OpName 'TypeOpName -- | The value the operator is an alias for , efTypeAlias :: Qualified (ProperName 'TypeName) - } deriving (Show, Generic, NFData, ToJSON, FromJSON) + } deriving (Eq, Show, Generic, NFData, ToJSON, FromJSON) instance Serialise ExternsTypeFixity @@ -163,16 +164,18 @@ data ExternsDeclaration = , edInstanceNameSource :: NameSource , edInstanceSourceSpan :: SourceSpan } - deriving (Show, Generic, NFData, ToJSON, FromJSON) + deriving (Eq, Show, Generic, NFData, ToJSON, FromJSON) instance Serialise ExternsDeclaration +currentVersion :: String +currentVersion = showVersion Paths.version -- | Check whether the version in an externs file matches the currently running -- version. externsIsCurrentVersion :: ExternsFile -> Bool externsIsCurrentVersion ef = - T.unpack (efVersion ef) == showVersion Paths.version + T.unpack (efVersion ef) == currentVersion -- | Convert an externs file back into a module applyExternsFileToEnvironment :: ExternsFile -> Environment -> Environment @@ -215,7 +218,7 @@ moduleToExternsFile :: Module -> Environment -> M.Map Ident Ident -> ExternsFile moduleToExternsFile (Module _ _ _ _ Nothing) _ _ = internalError "moduleToExternsFile: module exports were not elaborated" moduleToExternsFile (Module ss _ mn ds (Just exps)) env renamedIdents = ExternsFile{..} where - efVersion = T.pack (showVersion Paths.version) + efVersion = T.pack currentVersion efModuleName = mn efExports = map renameRef exps efImports = mapMaybe importDecl ds diff --git a/src/Language/PureScript/Ide/Externs.hs b/src/Language/PureScript/Ide/Externs.hs index 32bf3e7ccc..67b973b2f6 100644 --- a/src/Language/PureScript/Ide/Externs.hs +++ b/src/Language/PureScript/Ide/Externs.hs @@ -10,7 +10,6 @@ import Protolude hiding (to, from, (&)) import Codec.CBOR.Term as Term import Control.Lens (preview, view, (&), (^.)) import "monad-logger" Control.Monad.Logger (MonadLogger, logErrorN) -import Data.Version (showVersion) import Data.Text qualified as Text import Language.PureScript.Make.Monad qualified as Make import Language.PureScript.Ide.Error (IdeError (..)) @@ -44,7 +43,7 @@ readExternFile fp = do _ -> throwError (GeneralError ("Parsing the extern at: " <> toS fp <> " failed")) where - version = toS (showVersion P.version) + version = toS P.currentVersion convertExterns :: P.ExternsFile -> ([IdeDeclarationAnn], [(P.ModuleName, P.DeclarationRef)]) convertExterns ef = diff --git a/src/Language/PureScript/Ide/Rebuild.hs b/src/Language/PureScript/Ide/Rebuild.hs index 4ad36942b7..72d428904f 100644 --- a/src/Language/PureScript/Ide/Rebuild.hs +++ b/src/Language/PureScript/Ide/Rebuild.hs @@ -38,6 +38,7 @@ import Unsafe.Coerce (unsafeCoerce) import Database.SQLite.Simple (Query(fromQuery), ToRow, SQLData (SQLText)) import Data.String (String) import Codec.Serialise (deserialise) +import System.FilePath (makeRelative) -- | Given a filepath performs the following steps: -- @@ -65,7 +66,8 @@ rebuildFile -> (ReaderT IdeEnvironment (LoggingT IO) () -> m ()) -- ^ A runner for the second build with open exports -> m Success -rebuildFile file actualFile codegenTargets _ = do +rebuildFile file actualFile codegenTargets runOpenBuild = do + currentDir <- liftIO getCurrentDirectory (fp, input) <- case List.stripPrefix "data:" file of Just source -> pure ("", Text.pack source) diff --git a/src/Language/PureScript/Interactive.hs b/src/Language/PureScript/Interactive.hs index 5f88b079c3..8248b6796a 100644 --- a/src/Language/PureScript/Interactive.hs +++ b/src/Language/PureScript/Interactive.hs @@ -1,5 +1,7 @@ {-# LANGUAGE DoAndIfThenElse #-} +{-# OPTIONS_GHC -Wwarn #-} + module Language.PureScript.Interactive ( handleCommand , module Interactive @@ -42,6 +44,7 @@ import Language.PureScript.Interactive.Types as Interactive import System.Directory (getCurrentDirectory) import System.FilePath (()) import System.FilePath.Glob (glob) +import Language.PureScript.TypeChecker.Monad (liftTypeCheckM) -- | Pretty-print errors printErrors :: MonadIO m => P.MultipleErrors -> m () @@ -294,10 +297,10 @@ handleKindOf print' typ = do case M.lookup (P.Qualified (P.ByModuleName mName) $ P.ProperName "IT") (P.typeSynonyms env') of Just (_, typ') -> do let chk = (P.emptyCheckState env') { P.checkCurrentModule = Just mName } - k = check (snd <$> P.kindOf typ') chk + k = check (snd <$> liftTypeCheckM (P.kindOf typ')) chk - check :: StateT P.CheckState (ExceptT P.MultipleErrors (Writer P.MultipleErrors)) a -> P.CheckState -> Either P.MultipleErrors (a, P.CheckState) - check sew = fst . runWriter . runExceptT . runStateT sew + check :: P.SupplyT (StateT P.CheckState (ExceptT P.MultipleErrors (Writer P.MultipleErrors))) a -> P.CheckState -> Either P.MultipleErrors (a, P.CheckState) + check sew = fst . runWriter . runExceptT . runStateT (P.evalSupplyT 0 sew) case k of Left err -> printErrors err Right (kind, _) -> print' . P.prettyPrintType 1024 $ kind diff --git a/src/Language/PureScript/Linter/Exhaustive.hs b/src/Language/PureScript/Linter/Exhaustive.hs index 697fefe8a0..eb03da41e0 100644 --- a/src/Language/PureScript/Linter/Exhaustive.hs +++ b/src/Language/PureScript/Linter/Exhaustive.hs @@ -11,7 +11,6 @@ module Language.PureScript.Linter.Exhaustive import Prelude import Protolude (ordNub) -import Control.Applicative (Applicative(..)) import Control.Arrow (first, second) import Control.Monad (unless) import Control.Monad.Writer.Class (MonadWriter(..)) diff --git a/src/Language/PureScript/Make.hs b/src/Language/PureScript/Make.hs index 7850ae79ee..b8697e421e 100644 --- a/src/Language/PureScript/Make.hs +++ b/src/Language/PureScript/Make.hs @@ -1,9 +1,8 @@ module Language.PureScript.Make - ( - -- * Make API - rebuildModule + ( make + , make_ + , rebuildModule , rebuildModule' - , make , inferForeignModules , module Monad , module Actions @@ -14,7 +13,7 @@ import Prelude import Control.Concurrent.Lifted as C import Control.DeepSeq (force) import Control.Exception.Lifted (onException, bracket_, evaluate) -import Control.Monad (foldM, unless, when, (<=<)) +import Control.Monad (foldM, unless, void, when, (<=<)) import Control.Monad.Base (MonadBase(liftBase)) import Control.Monad.Error.Class (MonadError(..)) import Control.Monad.IO.Class (MonadIO(..)) @@ -27,7 +26,7 @@ import Data.Function (on) import Data.Foldable (fold, for_) import Data.List (foldl', sortOn) import Data.List.NonEmpty qualified as NEL -import Data.Maybe (fromMaybe) +import Data.Maybe (fromMaybe, mapMaybe) import Data.Map qualified as M import Data.Set qualified as S import Data.Text qualified as T @@ -37,26 +36,46 @@ import Language.PureScript.Crash (internalError) import Language.PureScript.CST qualified as CST import Language.PureScript.Docs.Convert qualified as Docs import Language.PureScript.Environment (initEnvironment) -import Language.PureScript.Errors (MultipleErrors, SimpleErrorMessage(..), addHint, defaultPPEOptions, errorMessage', errorMessage'', prettyPrintMultipleErrors) +import Language.PureScript.Errors (MultipleErrors(..), SimpleErrorMessage(..), addHint, defaultPPEOptions, errorMessage', errorMessage'', prettyPrintMultipleErrors) import Language.PureScript.Externs (ExternsFile, applyExternsFileToEnvironment, moduleToExternsFile) import Language.PureScript.Linter (Name(..), lint, lintImports) import Language.PureScript.ModuleDependencies (DependencyDepth(..), moduleSignature, sortModules) -import Language.PureScript.Names (ModuleName, isBuiltinModuleName, runModuleName) +import Language.PureScript.Names (ModuleName(..), isBuiltinModuleName, runModuleName) import Language.PureScript.Renamer (renameInModule) import Language.PureScript.Sugar (Env, collapseBindingGroups, createBindingGroups, desugar, desugarCaseGuards, externsEnv, primEnv) import Language.PureScript.TypeChecker (CheckState(..), emptyCheckState, typeCheckModule) -import Language.PureScript.Make.BuildPlan (BuildJobResult(..), BuildPlan(..), getResult) +import Language.PureScript.Make.BuildPlan (BuildJobResult(..), BuildPlan(..), getResult, isUpToDate) import Language.PureScript.Make.BuildPlan qualified as BuildPlan +import Language.PureScript.Make.ExternsDiff (checkDiffs, emptyDiff, diffExterns) import Language.PureScript.Make.Cache qualified as Cache import Language.PureScript.Make.Actions as Actions import Language.PureScript.Make.Monad as Monad + ( Make(..), + writeTextFile, + writeJSONFile, + writeCborFileIO, + writeCborFile, + setTimestamp, + runMake, + readTextFile, + readJSONFileIO, + readJSONFile, + readExternsFile, + readCborFileIO, + readCborFile, + makeIO, + hashFile, + getTimestampMaybe, + getTimestamp, + getCurrentTime, + copyFile ) import Language.PureScript.CoreFn qualified as CF import System.Directory (doesFileExist) import System.FilePath (replaceExtension) +import Language.PureScript.TypeChecker.Monad (liftTypeCheckM) -- | Rebuild a single module. -- --- This function is used for fast-rebuild workflows (PSCi and psc-ide are examples). rebuildModule :: forall m . (MonadError MultipleErrors m, MonadWriter MultipleErrors m) @@ -96,8 +115,7 @@ rebuildModuleWithIndex MakeActions{..} exEnv externs m@(Module _ _ moduleName _ ((Module ss coms _ elaborated exps, env'), nextVar) <- runSupplyT 0 $ do (desugared, (exEnv', usedImports)) <- runStateT (desugar externs withPrim) (exEnv, mempty) let modulesExports = (\(_, _, exports) -> exports) <$> exEnv' - (checked, CheckState{..}) <- runStateT (typeCheckModule modulesExports desugared) $ emptyCheckState env - -- Debug.traceM $ show checkEnv + (checked, CheckState{..}) <- runStateT (liftTypeCheckM $ typeCheckModule modulesExports desugared) $ emptyCheckState env let usedImports' = foldl' (flip $ \(fromModuleName, newtypeCtorName) -> M.alter (Just . (fmap DctorName newtypeCtorName :) . fold) fromModuleName) usedImports checkConstructorImportsForCoercible -- Imports cannot be linted before type checking because we need to @@ -136,21 +154,46 @@ rebuildModuleWithIndex MakeActions{..} exEnv externs m@(Module _ _ moduleName _ evalSupplyT nextVar'' $ codegen withPrim renamed docs exts return exts --- | Compiles in "make" mode, compiling each module separately to a @.js@ file and an @externs.cbor@ file. +data MakeOptions = MakeOptions + { moCollectAllExterns :: Bool + } + +-- | Compiles in "make" mode, compiling each module separately to a @.js@ file +-- and an @externs.cbor@ file. +-- +-- If timestamps or hashes have not changed, existing externs files can be used +-- to provide upstream modules' types without having to typecheck those modules +-- again. +-- +-- It collects and returns externs for all modules passed. +make :: forall m. (MonadIO m, MonadBaseControl IO m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + => MakeActions m + -> [CST.PartialResult Module] + -> m [ExternsFile] +make = make' (MakeOptions {moCollectAllExterns = True}) + +-- | Compiles in "make" mode, compiling each module separately to a @.js@ file +-- and an @externs.cbor@ file. -- --- If timestamps or hashes have not changed, existing externs files can be used to provide upstream modules' types without --- having to typecheck those modules again. -make :: forall m. (MonadBaseControl IO m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) +-- This version of make returns nothing. +make_ :: forall m. (MonadIO m, MonadBaseControl IO m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) => MakeActions m -> [CST.PartialResult Module] + -> m () +make_ ma ms = void $ make' (MakeOptions {moCollectAllExterns = False}) ma ms + +make' :: forall m. (MonadIO m, MonadBaseControl IO m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + => MakeOptions + -> MakeActions m + -> [CST.PartialResult Module] -> m [ExternsFile] -make ma@MakeActions{..} ms = do +make' MakeOptions{..} ma@MakeActions{..} ms = do checkModuleNames cacheDb <- readCacheDb (sorted, graph) <- sortModules Transitive (moduleSignature . CST.resPartial) ms - - (buildPlan, newCacheDb) <- BuildPlan.construct ma cacheDb (sorted, graph) + let opts = BuildPlan.Options {optPreloadAllExterns = moCollectAllExterns} + (buildPlan, newCacheDb) <- BuildPlan.construct opts ma cacheDb (sorted, graph) -- Limit concurrent module builds to the number of capabilities as -- (by default) inferred from `+RTS -N -RTS` or set explicitly like `-N4`. @@ -161,6 +204,7 @@ make ma@MakeActions{..} ms = do let concurrency = max 1 capabilities lock <- C.newQSem concurrency + let sortedModuleNames = getModuleName . CST.resPartial <$> sorted let toBeRebuilt = filter (BuildPlan.needsRebuild buildPlan . getModuleName . CST.resPartial) sorted let totalModuleCount = length toBeRebuilt for_ toBeRebuilt $ \m -> fork $ do @@ -170,7 +214,7 @@ make ma@MakeActions{..} ms = do (spanName . getModuleSourceSpan . CST.resPartial $ m) (fst $ CST.resFull m) (fmap importPrim . snd $ CST.resFull m) - (deps `inOrderOf` map (getModuleName . CST.resPartial) sorted) + (deps `inOrderOf` sortedModuleNames) -- Prevent hanging on other modules when there is an internal error -- (the exception is thrown, but other threads waiting on MVars are released) @@ -180,7 +224,7 @@ make ma@MakeActions{..} ms = do (failures, successes) <- let splitResults = \case - BuildJobSucceeded _ exts -> + BuildJobSucceeded _ exts _ -> Right exts BuildJobFailed errs -> Left errs @@ -196,7 +240,6 @@ make ma@MakeActions{..} ms = do -- If generating docs, also generate them for the Prim modules outputPrimDocs - -- All threads have completed, rethrow any caught errors. let errors = M.elems failures unless (null errors) $ throwError (mconcat errors) @@ -204,10 +247,15 @@ make ma@MakeActions{..} ms = do -- Here we return all the ExternsFile in the ordering of the topological sort, -- so they can be folded into an Environment. This result is used in the tests -- and in PSCI. - let lookupResult mn = - fromMaybe (internalError "make: module not found in results") + let lookupResult mn@(ModuleName name) = + fromMaybe (internalError $ "make: module not found in results: " <> T.unpack name) $ M.lookup mn successes - return (map (lookupResult . getModuleName . CST.resPartial) sorted) + + pure $ + if moCollectAllExterns then + map lookupResult sortedModuleNames + else + mapMaybe (flip M.lookup successes) sortedModuleNames where checkModuleNames :: m () @@ -252,8 +300,29 @@ make ma@MakeActions{..} ms = do mexterns <- fmap unzip . sequence <$> traverse (getResult buildPlan) deps case mexterns of - Just (_, externs) -> do - -- We need to ensure that all dependencies have been included in Env + Just (_, depsDiffExterns) -> do + let externs = fst <$> depsDiffExterns + let prevResult = BuildPlan.getPrevResult buildPlan moduleName + let depsDiffs = traverse snd depsDiffExterns + let maySkipBuild moduleIndex + -- We may skip built only for up-to-date modules. + | Just (status, exts) <- prevResult + , isUpToDate status + -- Check if no dep's externs have changed. If any of the diffs + -- is Nothing means we can not check and need to rebuild. + , Just False <- checkDiffs m <$> depsDiffs = do + -- We should update modification times to mark existing + -- compilation results as actual. If it fails to update timestamp + -- on any of exiting codegen targets, it will run the build process. + updated <- updateOutputTimestamp moduleName + if updated then do + progress $ SkippingModule moduleName moduleIndex + pure $ Just (exts, MultipleErrors [], Just (emptyDiff moduleName)) + else + pure Nothing + | otherwise = pure Nothing + + -- We need to ensure that all dependencies have been included in Env. C.modifyMVar_ (bpEnv buildPlan) $ \env -> do let go :: Env -> ModuleName -> m Env @@ -266,20 +335,34 @@ make ma@MakeActions{..} ms = do idx <- C.takeMVar (bpIndex buildPlan) C.putMVar (bpIndex buildPlan) (idx + 1) - -- Bracket all of the per-module work behind the semaphore, including - -- forcing the result. This is done to limit concurrency and keep - -- memory usage down; see comments above. - (exts, warnings) <- bracket_ (C.waitQSem lock) (C.signalQSem lock) $ do - -- Eventlog markers for profiling; see debug/eventlog.js - liftBase $ traceMarkerIO $ T.unpack (runModuleName moduleName) <> " start" - -- Force the externs and warnings to avoid retaining excess module - -- data after the module is finished compiling. - extsAndWarnings <- evaluate . force <=< listen $ do - rebuildModuleWithIndex ma env externs m (Just (idx, cnt)) - liftBase $ traceMarkerIO $ T.unpack (runModuleName moduleName) <> " end" - return extsAndWarnings - return $ BuildJobSucceeded (pwarnings' <> warnings) exts - Nothing -> return BuildJobSkipped + (exts, warnings, diff) <- do + let doBuild = do + -- Bracket all of the per-module work behind the semaphore, including + -- forcing the result. This is done to limit concurrency and keep + -- memory usage down; see comments above. + (exts, warnings) <- bracket_ (C.waitQSem lock) (C.signalQSem lock) $ do + -- Eventlog markers for profiling; see debug/eventlog.js + liftBase $ traceMarkerIO $ T.unpack (runModuleName moduleName) <> " start" + -- Force the externs and warnings to avoid retaining excess module + -- data after the module is finished compiling. + extsAndWarnings <- evaluate . force <=< listen $ do + rebuildModuleWithIndex ma env externs m (Just (idx, cnt)) + liftBase $ traceMarkerIO $ T.unpack (runModuleName moduleName) <> " end" + return extsAndWarnings + let diff = diffExterns exts <$> (snd <$> prevResult) <*> depsDiffs + pure (exts, warnings, diff) + maySkipBuild (Just (idx, cnt)) >>= maybe doBuild pure + return $ BuildJobSucceeded (pwarnings' <> warnings) exts diff + + -- If we got Nothing for deps externs, that means one of the deps failed + -- to compile. Though if we have a previous built result we will keep to + -- avoid potentially unnecessary recompilation next time. + Nothing -> return $ + case BuildPlan.getPrevResult buildPlan moduleName of + Just (_, exts) -> + BuildJobSucceeded (MultipleErrors []) exts (Just (emptyDiff moduleName)) + Nothing -> + BuildJobSkipped BuildPlan.markComplete buildPlan moduleName result diff --git a/src/Language/PureScript/Make/Actions.hs b/src/Language/PureScript/Make/Actions.hs index 3803c54efb..8cb8a747e0 100644 --- a/src/Language/PureScript/Make/Actions.hs +++ b/src/Language/PureScript/Make/Actions.hs @@ -13,7 +13,7 @@ module Language.PureScript.Make.Actions import Prelude -import Control.Monad (unless, when) +import Control.Monad (guard, unless, when) import Control.Monad.Error.Class (MonadError(..)) import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad.Reader (asks) @@ -46,8 +46,8 @@ import Language.PureScript.Docs.Prim qualified as Docs.Prim import Language.PureScript.Docs.Types qualified as Docs import Language.PureScript.Errors (MultipleErrors, SimpleErrorMessage(..), errorMessage, errorMessage') import Language.PureScript.Externs (ExternsFile, externsFileName) -import Language.PureScript.Make.Monad (Make, copyFile, getTimestamp, getTimestampMaybe, hashFile, makeIO, readExternsFile, readJSONFile, readTextFile, writeCborFile, writeJSONFile, writeTextFile) -import Language.PureScript.Make.Cache (CacheDb, ContentHash, normaliseForCache) +import Language.PureScript.Make.Monad (Make, copyFile, getCurrentTime, getTimestamp, getTimestampMaybe, hashFile, makeIO, readExternsFile, readJSONFile, readTextFile, setTimestamp, writeCborFile, writeJSONFile, writeTextFile) +import Language.PureScript.Make.Cache (CacheDb, ContentHash, cacheDbIsCurrentVersion, fromCacheDbVersioned, normaliseForCache, toCacheDbVersioned) import Language.PureScript.Names (Ident(..), ModuleName, runModuleName) import Language.PureScript.Options (CodegenTarget(..), Options(..)) import Language.PureScript.Pretty.Common (SMap(..)) @@ -72,16 +72,26 @@ data RebuildPolicy data ProgressMessage = CompilingModule ModuleName (Maybe (Int, Int)) -- ^ Compilation started for the specified module + | SkippingModule ModuleName (Maybe (Int, Int)) deriving (Show, Eq, Ord) -- | Render a progress message renderProgressMessage :: T.Text -> ProgressMessage -> T.Text -renderProgressMessage infx (CompilingModule mn mi) = - T.concat - [ renderProgressIndex mi - , infx - , runModuleName mn - ] +renderProgressMessage infx msg = case msg of + CompilingModule mn mi -> + T.concat + [ renderProgressIndex mi + , "Compiling " + , infx + , runModuleName mn + ] + SkippingModule mn mi -> + T.concat + [renderProgressIndex mi + , "Skipping " + , infx + , runModuleName mn + ] where renderProgressIndex :: Maybe (Int, Int) -> T.Text renderProgressIndex = maybe "" $ \(start, end) -> @@ -110,6 +120,9 @@ data MakeActions m = MakeActions -- externs file, or if any of the requested codegen targets were not produced -- the last time this module was compiled, this function must return Nothing; -- this indicates that the module will have to be recompiled. + , updateOutputTimestamp :: ModuleName -> m Bool + -- ^ Updates the modification time of existing output files to mark them as + -- actual. , readExterns :: ModuleName -> m (FilePath, Maybe ExternsFile) -- ^ Read the externs file for a module as a string and also return the actual -- path for the file. @@ -143,7 +156,11 @@ readCacheDb' -- ^ The path to the output directory -> m CacheDb readCacheDb' outputDir = do - fromMaybe mempty <$> readJSONFile (cacheDbFile outputDir) + mdb <- readJSONFile (cacheDbFile outputDir) + pure $ fromMaybe mempty $ do + db <- mdb + guard $ cacheDbIsCurrentVersion db + pure $ fromCacheDbVersioned db writeCacheDb' :: (MonadIO m, MonadError MultipleErrors m) @@ -152,7 +169,7 @@ writeCacheDb' -> CacheDb -- ^ The CacheDb to be written -> m () -writeCacheDb' = writeJSONFile . cacheDbFile +writeCacheDb' = (. toCacheDbVersioned) . writeJSONFile . cacheDbFile writePackageJson' :: (MonadIO m, MonadError MultipleErrors m) @@ -175,7 +192,18 @@ buildMakeActions -- ^ Generate a prefix comment? -> MakeActions Make buildMakeActions outputDir filePathMap foreigns usePrefix = - MakeActions getInputTimestampsAndHashes getOutputTimestamp readExterns codegen ffiCodegen progress readCacheDb writeCacheDb writePackageJson outputPrimDocs + MakeActions + getInputTimestampsAndHashes + getOutputTimestamp + updateOutputTimestamp + readExterns + codegen + ffiCodegen + progress + readCacheDb + writeCacheDb + writePackageJson + outputPrimDocs where getInputTimestampsAndHashes @@ -235,6 +263,18 @@ buildMakeActions outputDir filePathMap foreigns usePrefix = then Just externsTimestamp else Nothing + updateOutputTimestamp :: ModuleName -> Make Bool + updateOutputTimestamp mn = do + curTime <- getCurrentTime + ok <- setTimestamp (outputFilename mn externsFileName) curTime + -- Then update timestamps of all actual codegen targets. + codegenTargets <- asks optionsCodegenTargets + let outputPaths = fmap (targetFilename mn) (S.toList codegenTargets) + results <- traverse (flip setTimestamp curTime) outputPaths + -- If something goes wrong (any of targets doesn't exit, a file system + -- error), return False. + pure $ and (ok : results) + readExterns :: ModuleName -> Make (FilePath, Maybe ExternsFile) readExterns mn = do let path = outputDir T.unpack (runModuleName mn) externsFileName @@ -316,7 +356,7 @@ buildMakeActions outputDir filePathMap foreigns usePrefix = requiresForeign = not . null . CF.moduleForeign progress :: ProgressMessage -> Make () - progress = liftIO . TIO.hPutStr stderr . (<> "\n") . renderProgressMessage "Compiling " + progress = liftIO . TIO.hPutStr stderr . (<> "\n") . renderProgressMessage "" readCacheDb :: Make CacheDb readCacheDb = readCacheDb' outputDir @@ -332,6 +372,7 @@ data ForeignModuleType = ESModule | CJSModule deriving (Show) -- | Check that the declarations in a given PureScript module match with those -- in its corresponding foreign module. checkForeignDecls :: CF.Module ann -> FilePath -> Make (Either MultipleErrors (ForeignModuleType, S.Set Ident)) +-- checkForeignDecls :: CF.Module ann -> FilePath -> Make (ForeignModuleType, S.Set Ident checkForeignDecls m path = do jsStr <- T.unpack <$> readTextFile path diff --git a/src/Language/PureScript/Make/BuildPlan.hs b/src/Language/PureScript/Make/BuildPlan.hs index 3eba2359a3..21a221f55f 100644 --- a/src/Language/PureScript/Make/BuildPlan.hs +++ b/src/Language/PureScript/Make/BuildPlan.hs @@ -1,9 +1,11 @@ module Language.PureScript.Make.BuildPlan ( BuildPlan(bpEnv, bpIndex) , BuildJobResult(..) - , buildJobSuccess + , Options(..) + , isUpToDate , construct , getResult + , getPrevResult , collectResults , markComplete , needsRebuild @@ -11,15 +13,16 @@ module Language.PureScript.Make.BuildPlan import Prelude -import Control.Concurrent.Async.Lifted as A -import Control.Concurrent.Lifted as C +import Control.Concurrent.Async.Lifted qualified as A +import Control.Concurrent.Lifted qualified as C import Control.Monad.Base (liftBase) -import Control.Monad (foldM) +import Control.Monad (foldM, guard) import Control.Monad.Trans.Control (MonadBaseControl(..)) -import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT) import Data.Foldable (foldl') import Data.Map qualified as M -import Data.Maybe (fromMaybe, mapMaybe) +import Data.Maybe (fromMaybe, isNothing, catMaybes) +import Data.Set qualified as S +import Data.Text qualified as T import Data.Time.Clock (UTCTime) import Language.PureScript.AST (Module, getModuleName) import Language.PureScript.Crash (internalError) @@ -28,23 +31,40 @@ import Language.PureScript.Errors (MultipleErrors(..)) import Language.PureScript.Externs (ExternsFile) import Language.PureScript.Make.Actions as Actions import Language.PureScript.Make.Cache (CacheDb, CacheInfo, checkChanged) +import Language.PureScript.Make.ExternsDiff (ExternsDiff, emptyDiff) import Language.PureScript.Names (ModuleName) import Language.PureScript.Sugar.Names.Env (Env, primEnv) import System.Directory (getCurrentDirectory) +-- This status tells if a module's exiting build artifacts are up to date with a +-- current module's content. It would be safe to re-use them, but only if +-- changes in its dependencies do require the module's rebuild. +newtype UpToDateStatus = UpToDateStatus Bool + +isUpToDate :: UpToDateStatus -> Bool +isUpToDate (UpToDateStatus b) = b + +data Prebuilt = Prebuilt + { pbExternsFile :: ExternsFile + } + -- | The BuildPlan tracks information about our build progress, and holds all -- prebuilt modules for incremental builds. data BuildPlan = BuildPlan { bpPrebuilt :: M.Map ModuleName Prebuilt + -- ^ Valid prebuilt results for modules, that are needed for rebuild, but + -- their rebuild is not required. + , bpPreviousBuilt :: M.Map ModuleName (UpToDateStatus, Prebuilt) + -- ^ Previously built results for modules that are potentially required to be + -- rebuilt. We will always rebuild not up to date modules. But we will only + -- rebuild up to date modules, if their deps' externs have effectively + -- changed. Previously built result is needed to compare previous and newly + -- built externs to know what have changed. , bpBuildJobs :: M.Map ModuleName BuildJob , bpEnv :: C.MVar Env , bpIndex :: C.MVar Int } -data Prebuilt = Prebuilt - { pbModificationTime :: UTCTime - , pbExternsFile :: ExternsFile - } newtype BuildJob = BuildJob { bjResult :: C.MVar BuildJobResult @@ -52,33 +72,43 @@ newtype BuildJob = BuildJob } data BuildJobResult - = BuildJobSucceeded !MultipleErrors !ExternsFile - -- ^ Succeeded, with warnings and externs + = BuildJobSucceeded !MultipleErrors !ExternsFile (Maybe ExternsDiff) + -- ^ Succeeded, with warnings and externs, also holds externs diff with + -- previous build result if any (lazily evaluated). -- | BuildJobFailed !MultipleErrors - -- ^ Failed, with errors + -- ^ Failed, with errors. | BuildJobSkipped - -- ^ The build job was not run, because an upstream build job failed + -- ^ The build job was not run, because an upstream build job failed. -buildJobSuccess :: BuildJobResult -> Maybe (MultipleErrors, ExternsFile) -buildJobSuccess (BuildJobSucceeded warnings externs) = Just (warnings, externs) +type SuccessResult = (MultipleErrors, (ExternsFile, Maybe ExternsDiff)) + +buildJobSuccess :: BuildJobResult -> Maybe SuccessResult +buildJobSuccess (BuildJobSucceeded warnings externs diff) = Just (warnings, (externs, diff)) buildJobSuccess _ = Nothing -- | Information obtained about a particular module while constructing a build -- plan; used to decide whether a module needs rebuilding. data RebuildStatus = RebuildStatus - { statusModuleName :: ModuleName - , statusRebuildNever :: Bool - , statusNewCacheInfo :: Maybe CacheInfo + { rsModuleName :: ModuleName + , rsRebuildNever :: Bool + , rsNewCacheInfo :: Maybe CacheInfo -- ^ New cache info for this module which should be stored for subsequent -- incremental builds. A value of Nothing indicates that cache info for -- this module should not be stored in the build cache, because it is being -- rebuilt according to a RebuildPolicy instead. - , statusPrebuilt :: Maybe Prebuilt - -- ^ Prebuilt externs and timestamp for this module, if any. + , rsPrebuilt :: Maybe UTCTime + -- ^ Prebuilt timestamp (compilation time) for this module. + , rsUpToDate :: Bool + -- ^ Whether or not module (timestamp or content) changed since previous + -- compilation (checked against provided cache-db info). } +-- | Construct common error message indicating a bug in the internal logic +barrierError :: T.Text -> a +barrierError infx = internalError $ "make: " <> T.unpack infx <> " no barrier" + -- | Called when we finished compiling a module and want to report back the -- compilation result, as well as any potential errors that were thrown. markComplete @@ -88,8 +118,9 @@ markComplete -> BuildJobResult -> m () markComplete buildPlan moduleName result = do - let BuildJob rVar = fromMaybe (internalError "make: markComplete no barrier") $ M.lookup moduleName (bpBuildJobs buildPlan) - putMVar rVar result + let BuildJob rVar = + fromMaybe (barrierError "markComplete") $ M.lookup moduleName (bpBuildJobs buildPlan) + C.putMVar rVar result -- | Whether or not the module with the given ModuleName needs to be rebuilt needsRebuild :: BuildPlan -> ModuleName -> Bool @@ -103,8 +134,10 @@ collectResults => BuildPlan -> m (M.Map ModuleName BuildJobResult) collectResults buildPlan = do - let prebuiltResults = M.map (BuildJobSucceeded (MultipleErrors []) . pbExternsFile) (bpPrebuilt buildPlan) - barrierResults <- traverse (readMVar . bjResult) $ bpBuildJobs buildPlan + let mapExts exts = BuildJobSucceeded (MultipleErrors []) exts Nothing + let prebuiltResults = + M.map (mapExts . pbExternsFile) (bpPrebuilt buildPlan) + barrierResults <- traverse (C.readMVar . bjResult) $ bpBuildJobs buildPlan pure (M.union prebuiltResults barrierResults) -- | Gets the the build result for a given module name independent of whether it @@ -113,14 +146,26 @@ getResult :: (MonadBaseControl IO m) => BuildPlan -> ModuleName - -> m (Maybe (MultipleErrors, ExternsFile)) + -> m (Maybe SuccessResult) getResult buildPlan moduleName = - case M.lookup moduleName (bpPrebuilt buildPlan) of - Just es -> - pure (Just (MultipleErrors [], pbExternsFile es)) + case M.lookup moduleName (bpBuildJobs buildPlan) of + Just bj -> + buildJobSuccess <$> C.readMVar (bjResult bj) Nothing -> do - r <- readMVar $ bjResult $ fromMaybe (internalError "make: no barrier") $ M.lookup moduleName (bpBuildJobs buildPlan) - pure $ buildJobSuccess r + let exts = pbExternsFile + $ fromMaybe (barrierError "getResult") + $ M.lookup moduleName (bpPrebuilt buildPlan) + pure (Just (MultipleErrors [], (exts, Just $ emptyDiff moduleName ))) + +-- | Gets preloaded previous built result for modules that are going to be built. This +-- will be used to skip compilation if dep's externs have not changed. +getPrevResult :: BuildPlan -> ModuleName -> Maybe (UpToDateStatus, ExternsFile) +getPrevResult buildPlan moduleName = + fmap pbExternsFile <$> M.lookup moduleName (bpPreviousBuilt buildPlan) + +data Options = Options + { optPreloadAllExterns :: Bool + } -- | Constructs a BuildPlan for the given module graph. -- @@ -128,29 +173,77 @@ getResult buildPlan moduleName = -- determine whether a module needs rebuilding. construct :: forall m. MonadBaseControl IO m - => MakeActions m + => Options + -> MakeActions m -> CacheDb -> ([CST.PartialResult Module], [(ModuleName, [ModuleName])]) -> m (BuildPlan, CacheDb) -construct MakeActions{..} cacheDb (sorted, graph) = do +construct Options{..} MakeActions{..} cacheDb (sorted, graph) = do let sortedModuleNames = map (getModuleName . CST.resPartial) sorted rebuildStatuses <- A.forConcurrently sortedModuleNames getRebuildStatus - let prebuilt = - foldl' collectPrebuiltModules M.empty $ - mapMaybe (\s -> (statusModuleName s, statusRebuildNever s,) <$> statusPrebuilt s) rebuildStatuses - let toBeRebuilt = filter (not . flip M.member prebuilt) sortedModuleNames - buildJobs <- foldM makeBuildJob M.empty toBeRebuilt + + -- Split modules into those that have to be rebuilt and those that have a valid + -- prebuilt input. The Bool value in rebuildMap means if we may skip the + -- compilation (if externs of dependencies have not changed). If it is False we + -- should re-compile the module due to the following: the module's source have + -- changed or some of dependencies were compiled later than the module. + let (rebuildMap, prebuiltMap) = splitModules rebuildStatuses + + let toBeRebuilt = M.keys rebuildMap + + -- Set of all dependencies of modules to be rebuilt. + let allBuildDeps = S.unions (S.fromList . moduleDeps <$> toBeRebuilt) + let inBuildDeps = flip S.member allBuildDeps + + -- We only need prebuilt results for deps of the modules to be build. + let toLoadPrebuilt = + if optPreloadAllExterns + then prebuiltMap + else M.filterWithKey (const . inBuildDeps) prebuiltMap + + -- We will need previously built results for modules to be built + -- to skip rebuilding if deps have not changed. + let toLoadPrev = + M.mapMaybeWithKey + ( \mn prev -> do + -- We load previous build result for all up-to-date modules, and + -- also for changed modules that have dependants. + status <- fst <$> prev + guard (isUpToDate status || inBuildDeps mn) + prev + ) + rebuildMap + + (prebuiltLoad, prevLoad) <- + A.concurrently + (A.mapConcurrently id $ M.mapWithKey loadPrebuilt toLoadPrebuilt) + (A.mapConcurrently id $ M.mapWithKey + (\mn (up, ts) -> fmap (up,) <$> loadPrebuilt mn ts) toLoadPrev) + + let prebuilt = M.mapMaybe id prebuiltLoad + let previous = M.mapMaybe id prevLoad + + -- If for some reason (wrong version, files corruption, etc) prebuilt + -- externs loading fails, those modules should be rebuilt too. + let failedLoads = M.keys $ M.filter isNothing prebuiltLoad + buildJobs <- foldM makeBuildJob M.empty (toBeRebuilt <> failedLoads) + env <- C.newMVar primEnv idx <- C.newMVar 1 pure - ( BuildPlan prebuilt buildJobs env idx + ( BuildPlan prebuilt previous buildJobs env idx , let update = flip $ \s -> - M.alter (const (statusNewCacheInfo s)) (statusModuleName s) + M.alter (const (rsNewCacheInfo s)) (rsModuleName s) in foldl' update cacheDb rebuildStatuses ) where + -- Timestamp here is just to ensure that we will only try to load modules + -- that have previous built results available. + loadPrebuilt :: ModuleName -> UTCTime -> m (Maybe Prebuilt) + loadPrebuilt = const . fmap (fmap Prebuilt . snd) . readExterns + makeBuildJob prev moduleName = do buildJob <- BuildJob <$> C.newEmptyMVar pure (M.insert moduleName buildJob prev) @@ -160,56 +253,68 @@ construct MakeActions{..} cacheDb (sorted, graph) = do inputInfo <- getInputTimestampsAndHashes moduleName case inputInfo of Left RebuildNever -> do - prebuilt <- findExistingExtern moduleName + timestamp <- getOutputTimestamp moduleName pure (RebuildStatus - { statusModuleName = moduleName - , statusRebuildNever = True - , statusPrebuilt = prebuilt - , statusNewCacheInfo = Nothing + { rsModuleName = moduleName + , rsRebuildNever = True + , rsPrebuilt = timestamp + , rsUpToDate = True + , rsNewCacheInfo = Nothing }) Left RebuildAlways -> do pure (RebuildStatus - { statusModuleName = moduleName - , statusRebuildNever = False - , statusPrebuilt = Nothing - , statusNewCacheInfo = Nothing + { rsModuleName = moduleName + , rsRebuildNever = False + , rsPrebuilt = Nothing + , rsUpToDate = False + , rsNewCacheInfo = Nothing }) Right cacheInfo -> do cwd <- liftBase getCurrentDirectory - (newCacheInfo, isUpToDate) <- checkChanged cacheDb moduleName cwd cacheInfo - prebuilt <- - if isUpToDate - then findExistingExtern moduleName - else pure Nothing + (newCacheInfo, upToDate) <- checkChanged cacheDb moduleName cwd cacheInfo + timestamp <- getOutputTimestamp moduleName pure (RebuildStatus - { statusModuleName = moduleName - , statusRebuildNever = False - , statusPrebuilt = prebuilt - , statusNewCacheInfo = Just newCacheInfo + { rsModuleName = moduleName + , rsRebuildNever = False + , rsPrebuilt = timestamp + , rsUpToDate = upToDate + , rsNewCacheInfo = Just newCacheInfo }) - findExistingExtern :: ModuleName -> m (Maybe Prebuilt) - findExistingExtern moduleName = runMaybeT $ do - timestamp <- MaybeT $ getOutputTimestamp moduleName - externs <- MaybeT $ snd <$> readExterns moduleName - pure (Prebuilt timestamp externs) - - collectPrebuiltModules :: M.Map ModuleName Prebuilt -> (ModuleName, Bool, Prebuilt) -> M.Map ModuleName Prebuilt - collectPrebuiltModules prev (moduleName, rebuildNever, pb) - | rebuildNever = M.insert moduleName pb prev - | otherwise = do - let deps = fromMaybe (internalError "make: module not found in dependency graph.") (lookup moduleName graph) - case traverse (fmap pbModificationTime . flip M.lookup prev) deps of - Nothing -> - -- If we end up here, one of the dependencies didn't exist in the - -- prebuilt map and so we know a dependency needs to be rebuilt, which - -- means we need to be rebuilt in turn. - prev - Just modTimes -> - case maximumMaybe modTimes of - Just depModTime | pbModificationTime pb < depModTime -> - prev - _ -> M.insert moduleName pb prev + moduleDeps = fromMaybe graphError . flip lookup graph + where + graphError = internalError "make: module not found in dependency graph." + + splitModules :: [RebuildStatus] -> (M.Map ModuleName (Maybe (UpToDateStatus, UTCTime)), M.Map ModuleName UTCTime) + splitModules = foldl' collectByStatus (M.empty, M.empty) + + collectByStatus (build, prebuilt) (RebuildStatus mn rebuildNever _ mbPb upToDate) + -- To build if no prebuilt result exits. + | Nothing <- mbPb = (M.insert mn Nothing build, prebuilt) + -- To build if not up to date. + | Just pb <- mbPb, not upToDate = toRebuild (False, pb) + -- To prebuilt because of policy. + | Just pb <- mbPb, rebuildNever = toPrebuilt pb + -- In other case analyze compilation times of dependencies. + | Just pb <- mbPb = do + let deps = moduleDeps mn + let modTimes = map (flip M.lookup prebuilt) deps + + case maximumMaybe (catMaybes modTimes) of + -- Check if any of deps where build later. This means we should + -- recompile even if the module's source is up-to-date. This may + -- happen due to some partial builds or ide compilation + -- workflows involved that do not assume full project + -- compilation. We should treat those modules as NOT up to date + -- to ensure they are rebuilt. + Just depModTime | pb < depModTime -> toRebuild (False, pb) + -- If one of the deps is not in the prebuilt, though the module + -- is up to date, we should add it in the rebuild queue. + _ | any isNothing modTimes -> toRebuild (upToDate, pb) + _ -> toPrebuilt pb + where + toRebuild (up, t) = (M.insert mn (Just (UpToDateStatus up, t)) build, prebuilt) + toPrebuilt v = (build, M.insert mn v prebuilt) maximumMaybe :: Ord a => [a] -> Maybe a maximumMaybe [] = Nothing diff --git a/src/Language/PureScript/Make/Cache.hs b/src/Language/PureScript/Make/Cache.hs index 092544fa73..4582d2fdf7 100644 --- a/src/Language/PureScript/Make/Cache.hs +++ b/src/Language/PureScript/Make/Cache.hs @@ -6,6 +6,9 @@ module Language.PureScript.Make.Cache , checkChanged , removeModules , normaliseForCache + , cacheDbIsCurrentVersion + , toCacheDbVersioned + , fromCacheDbVersioned ) where import Prelude @@ -23,14 +26,19 @@ import Data.Map qualified as Map import Data.Maybe (fromMaybe) import Data.Monoid (All(..)) import Data.Set (Set) -import Data.Text (Text) +import Data.Text (Text, pack, unpack) import Data.Text.Encoding (encodeUtf8, decodeUtf8) import Data.These (These(..)) import Data.Time.Clock (UTCTime) import Data.Traversable (for) import System.FilePath qualified as FilePath +import Paths_purescript as Paths + import Language.PureScript.Names (ModuleName) +import Data.Version (showVersion) +import Data.Aeson ((.=)) +import Data.Aeson.Types ((.:)) digestToHex :: Digest a -> Text digestToHex = decodeUtf8 . convertToBase Base16 @@ -63,6 +71,34 @@ hash = ContentHash . Hash.hash type CacheDb = Map ModuleName CacheInfo +data CacheDbVersioned = CacheDbVersioned { cdbVersion :: Text, cdbModules :: CacheDb } + deriving (Eq, Ord) + +instance Aeson.FromJSON CacheDbVersioned where + parseJSON = Aeson.withObject "CacheDb" $ \v -> + CacheDbVersioned + <$> v .: "version" + <*> v .: "modules" + +instance Aeson.ToJSON CacheDbVersioned where + toJSON CacheDbVersioned{..} = + Aeson.object + [ "version" .= cdbVersion + , "modules" .= cdbModules + ] + +cacheDbIsCurrentVersion :: CacheDbVersioned -> Bool +cacheDbIsCurrentVersion ef = + unpack (cdbVersion ef) == showVersion Paths.version + +toCacheDbVersioned :: CacheDb -> CacheDbVersioned +toCacheDbVersioned = + CacheDbVersioned (pack $ showVersion Paths.version) + +fromCacheDbVersioned :: CacheDbVersioned -> CacheDb +fromCacheDbVersioned = + cdbModules + -- | A CacheInfo contains all of the information we need to store about a -- particular module in the cache database. newtype CacheInfo = CacheInfo diff --git a/src/Language/PureScript/Make/ExternsDiff.hs b/src/Language/PureScript/Make/ExternsDiff.hs new file mode 100644 index 0000000000..21ef9ab38a --- /dev/null +++ b/src/Language/PureScript/Make/ExternsDiff.hs @@ -0,0 +1,490 @@ +module Language.PureScript.Make.ExternsDiff + ( ExternsDiff + , emptyDiff + , diffExterns + , checkDiffs + ) where + +import Protolude hiding (check, moduleName, trace) + +import Data.Graph as G (graphFromEdges, reachable) +import Data.List qualified as L +import Data.Map qualified as M +import Data.Set qualified as S + +import Language.PureScript.AST qualified as P +import Language.PureScript.AST.Declarations.ChainId (ChainId (..)) +import Language.PureScript.Constants.Prim (primModules) +import Language.PureScript.Crash (internalError) +import Language.PureScript.Environment qualified as P +import Language.PureScript.Externs qualified as P +import Language.PureScript.Names (ModuleName) +import Language.PureScript.Names qualified as P +import Language.PureScript.Types qualified as P + +-- Refs structure appropriate for storing and checking externs diffs. +data Ref + = TypeClassRef (P.ProperName 'P.ClassName) + | TypeOpRef (P.OpName 'P.TypeOpName) + | TypeRef (P.ProperName 'P.TypeName) + | -- We use separate ref for a data constructor and keep here origin type as well. + ConstructorRef (P.ProperName 'P.TypeName) (P.ProperName 'P.ConstructorName) + | -- A ad-hoc ref that points to the type with a set of constructors that changed. + -- It is needed to correctly handle effects of adding/removing of ctors. + CtorsSetRef (P.ProperName 'P.TypeName) + | ValueRef P.Ident + | ValueOpRef (P.OpName 'P.ValueOpName) + | -- Instance ref points to the class and types defined in the same module. + TypeInstanceRef P.Ident (ModuleName, P.ProperName 'P.ClassName) [P.ProperName 'P.TypeName] + deriving (Show, Eq, Ord) + +data RefStatus = Removed | Updated + deriving (Show) + +type RefWithDeps = (Ref, S.Set (ModuleName, Ref)) + +type RefsWithStatus = M.Map Ref RefStatus + +type ModuleRefsMap = Map ModuleName (Set Ref) + +data ExternsDiff = ExternsDiff + {edModuleName :: ModuleName, edRefs :: Map Ref RefStatus} + deriving (Show) + +-- | Empty diff means no effective difference between externs. +emptyDiff :: P.ModuleName -> ExternsDiff +emptyDiff mn = ExternsDiff mn mempty + +isRefRemoved :: RefStatus -> Bool +isRefRemoved Removed = True +isRefRemoved _ = False + +-- To get changed reexported refs, we take those which were removed (not +-- present in new extern's exports) or changed in dependencies. +getReExported :: P.ExternsFile -> P.ExternsFile -> ModuleRefsMap -> RefsWithStatus +getReExported newExts oldExts depsDiffsMap = + M.fromList $ mapMaybe checkRe oldExports + where + goRe (P.ReExportRef _ es ref) = (P.exportSourceDefinedIn es,) <$> toRefs ref + goRe _ = [] + + oldExports = concatMap goRe (P.efExports oldExts) + newReExports = concatMap goRe (P.efExports newExts) + checkRe (mn, ref) + | (mn, ref) `notElem` newReExports = Just (ref, Removed) + | Just True <- elem ref <$> M.lookup mn depsDiffsMap = Just (ref, Updated) + | otherwise = Nothing + +-- Extracts declarations from old and new externs and compares them. Returns a +-- tuple of changed refs (a form of which have changed) and unchanged refs with +-- dependencies (refs they depend upon). +getChanged :: P.ExternsFile -> P.ExternsFile -> ModuleRefsMap -> (RefsWithStatus, [RefWithDeps]) +getChanged newExts oldExts depsDiffsMap = + (changedRefs, unchangedRefs) + where + modName = P.efModuleName newExts + + getDecls = map stripDeclaration . P.efDeclarations + getTypeFixities = P.efTypeFixities + getFixities = P.efFixities + + -- Type class instances if changed (added/removed) indirectly effect back + -- the class or the types that are defined in the module, meaning if the + -- instance is added/removed we will recompile modules that use the type + -- class or (if the type class defined in another module) we have to + -- recompile modules that use types defined in this module affected by the + -- instance. + applyInstances (a, r, c, u) = + let checkType t (TypeRef t') = t' == t + checkType _ _ = False + uRefs = map fst u -- Unchanged refs. + go (TypeInstanceRef _ (clsMod, cls) types) + | clsRef <- TypeClassRef cls = + if clsMod == modName + then -- If the class is defined in this module we ensure that is marked as changed. + maybe [] pure $ find ((==) clsRef) uRefs + else case S.member clsRef <$> M.lookup clsMod depsDiffsMap of + Just True -> + -- If the type class is in another module and it has + -- changed we don't need to care about instance types + -- (because the instance change affects modules that use + -- the type class/its methods). + [] + _ -> + -- Otherwise mark instance types as changed. + foldMap (\t -> filter (checkType t) uRefs) types + go _ = mempty + + -- Check class instances in added, removed and changed. + affected = foldMap (S.fromList . go . fst) (a <> r <> c) + (uc, uu) = L.partition (flip S.member affected . fst) u + in (a, r, c <> uc, uu) + + -- Group/split exported refs of the module into (added, removed, changed, + -- unchanged) - (a, r, c, u). + declsSplit = + applyInstances $ + splitRefs (getDecls newExts) (getDecls oldExts) (externsDeclarationToRef modName) + + -- Make the context for fixity's data constructor search: place all + -- known refs in the map. + getRefsSet (a, r, c, u) = S.fromList $ map fst (a <> r <> c <> u) + fixityCtx = M.insert modName (getRefsSet declsSplit) depsDiffsMap + + -- Determine which declarations where directly changed or removed by + -- combining Declarations, Fixities and Type Fixities - as they are + -- separated in externs we handle them separately. We don't care about added things. + (_, removed, changed, unchangedRefs) = + fold + [ declsSplit + , splitRefs (getFixities newExts) (getFixities oldExts) (pure . externsFixityToRef fixityCtx) + , splitRefs (getTypeFixities newExts) (getTypeFixities oldExts) (pure . externsTypeFixityToRef) + ] + + changedRefs = + M.fromList $ + map ((,Removed) . fst) removed <> map ((,Updated) . fst) changed + +-- Gets set of type constructors from new externs that have changed. +getCtorsSets :: P.ExternsFile -> P.ExternsFile -> Set Ref +getCtorsSets newExts oldExts = + S.map CtorsSetRef $ + M.keysSet $ + M.differenceWith comp (getSets newExts) (getSets oldExts) + where + getSets = M.fromList . foldMap goDecl . P.efDeclarations + goDecl = \case + P.EDType n _ (P.DataType _ _ ctors) -> + [(n, S.fromList $ fst <$> ctors)] + _ -> [] + comp a b = if a == b then Nothing else Just a + +-- Takes a list unchanged local refs with dependencies and finds that are affected by +-- changed refs. Cyclic dependencies between local refs are searched using +-- directed graph. +getAffectedLocal :: ModuleName -> ModuleRefsMap -> [RefWithDeps] -> Set Ref +getAffectedLocal modName diffsMap unchangedRefs = + affectedLocalRefs + where + hasChangedDeps (mn, ref) = + Just True == (S.member ref <$> M.lookup mn diffsMap) + (affectedByChanged, restLocalRefs) = + L.partition (any hasChangedDeps . snd) unchangedRefs + + -- Use graph to go though local refs and their cyclic dependencies on each other. + -- The graph includes only local refs that depend on other local refs. + toNode (ref, deps) = (ref, ref, map snd $ filter ((== modName) . fst) (S.toList deps)) + + -- Make graph vertexes from the rest local refs with deps and affected refs + -- with no deps. + vtxs = toNode <$> restLocalRefs <> (map (const mempty) <$> affectedByChanged) + (graph, fromVtx, toVtx) = G.graphFromEdges vtxs + + -- Graph is a list of refs with (refs) dependencies. + refsGraph = do + (_, t, _) <- vtxs + let v = fromMaybe (internalError "diffExterns: vertex not found") $ toVtx t + let deps = G.reachable graph v + let toKey = (\(_, k, _) -> k) . fromVtx + pure (t, map toKey deps) + + -- Get local refs that depend on affected refs (affected refs are included + -- in the graph result because a node's reachable list includes the node + -- itself). + affectedLocalRefs = + S.fromList $ + map fst $ + filter (any (flip elem (fst <$> affectedByChanged)) . snd) refsGraph + +diffExterns :: P.ExternsFile -> P.ExternsFile -> [ExternsDiff] -> ExternsDiff +diffExterns newExts oldExts depsDiffs = + ExternsDiff modName $ + affectedReExported <> changedRefs <> affectedLocalRefs + where + modName = P.efModuleName newExts + + depsDiffsMap = M.fromList (map (liftM2 (,) edModuleName (M.keysSet . edRefs)) depsDiffs) + + -- To get changed reexported refs, we take those which were removed (not + -- present in new extern's exports) or changed in dependencies. + affectedReExported = getReExported newExts oldExts depsDiffsMap + + (changedRefs, unchangedRefs) = getChanged newExts oldExts depsDiffsMap + + ctorsSets = getCtorsSets newExts oldExts + + -- Extend dependencies' diffs map with local changes. + diffsMapWithLocal + | null changedRefs && null ctorsSets = depsDiffsMap + | otherwise = M.insert modName (M.keysSet changedRefs <> ctorsSets) depsDiffsMap + + affectedLocalRefs = + M.fromSet (const Updated) $ getAffectedLocal modName diffsMapWithLocal unchangedRefs + +-- Checks if the externs diffs effect the module (the module uses any diff's +-- entries). True if uses, False if not. +checkDiffs :: P.Module -> [ExternsDiff] -> Bool +checkDiffs (P.Module _ _ _ decls exports) diffs + | all isEmpty diffs = False + | isNothing mbSearch = True + | null searches = False + | otherwise = checkReExports || checkUsage searches decls + where + mbSearch = makeSearches decls diffs + searches = fromMaybe S.empty mbSearch + + -- Check if the module reexports any of searched refs. + checkReExports = flip (maybe False) exports $ any $ \case + P.ModuleRef _ mn -> not . null $ S.filter ((== Just mn) . fst) searches + _ -> False + +-- Goes though the module and try to find any usage of the refs. +-- Takes a set of refs to search in module's declarations, if found returns True. +checkUsage :: Set (Maybe ModuleName, Ref) -> [P.Declaration] -> Bool +checkUsage searches decls = anyUsages + where + -- Two traversals: one to pick up usages of types, one for the rest. + Any anyUsages = + foldMap checkUsageInTypes decls + <> foldMap checkOtherUsages decls + + -- To check data constructors we remove an origin type from it (see `checkCtor`). + searches' = S.map (map stripCtorType) searches + + -- To check data constructors we remove an origin type from it. + emptyName = P.ProperName "" + stripCtorType (ConstructorRef _ n) = ConstructorRef emptyName n + stripCtorType x = x + + check q = Any $ S.member (P.getQual q, P.disqualify q) searches' + + checkType = check . map TypeRef + checkTypeOp = check . map TypeOpRef + checkValue = check . map ValueRef + checkValueOp = check . map ValueOpRef + checkCtor = check . map (ConstructorRef emptyName) + checkClass = check . map TypeClassRef + + -- A nested traversal: pick up types in the module then traverse the structure of the types + (checkUsageInTypes, _, _, _, _) = + P.accumTypes $ P.everythingOnTypes (<>) $ \case + P.TypeConstructor _ n -> checkType n + P.TypeOp _ n -> checkTypeOp n + P.ConstrainedType _ c _ -> checkClass (P.constraintClass c) + _ -> mempty + + checkOtherUsages = + let (extr, _, _, _, _) = P.everythingWithScope goDecl goExpr goBinder mempty mempty + in extr mempty + + goDecl _ = \case + P.TypeInstanceDeclaration _ _ _ _ _ _ tc _ _ -> + checkClass tc + _ -> mempty + + isLocal scope ident = P.LocalIdent ident `S.member` scope + goExpr scope expr = case expr of + P.Var _ n + | P.isUnqualified n && isLocal scope (P.disqualify n) -> mempty + | otherwise -> checkValue n + P.Constructor _ n -> checkCtor n + P.Op _ n -> checkValueOp n + _ -> mempty + + goBinder _ binder = case binder of + P.ConstructorBinder _ n _ -> checkCtor n + P.OpBinder _ n -> checkValueOp n + _ -> mempty + +-- | Traverses imports and returns a set of refs to be searched though the +-- module. Returns Nothing if removed refs found in imports (no need to search +-- through the module - the module needs to be recompiled). If an empty set is +-- returned then no changes apply to the module. +makeSearches :: [P.Declaration] -> [ExternsDiff] -> Maybe (Set (Maybe ModuleName, Ref)) +makeSearches decls depsDiffs = + foldM go mempty decls + where + diffsMap = M.fromList (map (liftM2 (,) edModuleName edRefs) depsDiffs) + + -- Add data constructors to refs if all are implicitly imported using (..). + getCtor n (ConstructorRef tn _) = tn == n + getCtor _ _ = False + getCtors n = M.keys . M.filterWithKey (const . getCtor n) + addCtors mn (P.TypeRef _ n Nothing) = maybe [] (getCtors n) (M.lookup mn diffsMap) + addCtors _ _ = [] + getRefs = (toRefs <>) . addCtors + + go s (P.ImportDeclaration _ mn dt qual) + -- We return Nothing if we encounter removed refs in imports. + | Just diffs <- M.lookup mn diffsMap + , removed <- M.keysSet $ M.filter isRefRemoved diffs = + fmap ((s <>) . S.map (qual,) . M.keysSet) $ case dt of + P.Explicit dRefs + | any (flip S.member removed) refs -> Nothing + | otherwise -> + -- Search only refs encountered in the import. + Just $ M.filterWithKey (const . flip elem refs) diffs + where + refs = foldMap (getRefs mn) dRefs + P.Hiding dRefs + | any (flip S.member removed) refs -> Nothing + | otherwise -> + -- Search only refs not encountered in the import. + Just $ M.filterWithKey (const . not . flip elem refs) diffs + where + refs = foldMap (getRefs mn) dRefs + -- Search all changed refs. + P.Implicit -> Just diffs + go s _ = Just s + +toRefs :: P.DeclarationRef -> [Ref] +toRefs = \case + P.TypeClassRef _ n -> [TypeClassRef n] + P.TypeOpRef _ n -> [TypeOpRef n] + P.TypeRef _ n c -> [TypeRef n] <> (ConstructorRef n <$> fromMaybe [] c) + P.ValueRef _ i -> [ValueRef i] + P.ValueOpRef _ n -> [ValueOpRef n] + _ -> [] + +isEmpty :: ExternsDiff -> Bool +isEmpty (ExternsDiff _ refs) + | null refs = True + | otherwise = False + +type Tuple4 m a = (m a, m a, m a, m a) + +-- | Returns refs as a tuple of four (added, removed, changed, unchanged). +splitRefs :: forall ref a deps. Monoid deps => Ord ref => Eq a => [a] -> [a] -> (a -> Maybe (ref, deps)) -> Tuple4 [] (ref, deps) +splitRefs new old toRef = + M.foldrWithKey go (added, [], [], []) oldMap + where + toMap :: [a] -> Map ref (deps, [a]) + toMap = M.fromListWith (<>) . mapMaybe (\decl -> do (ref, deps) <- toRef decl; pure (ref, (deps, [decl]))) + newMap = toMap new + oldMap = toMap old + added = fmap (\(ref, (deps, _)) -> (ref, deps)) $ M.toList $ M.difference newMap oldMap + go :: ref -> (deps, [a]) -> Tuple4 [] (ref, deps) -> Tuple4 [] (ref, deps) + go ref (deps, decls) (a, r, c, u) = case M.lookup ref newMap of + Nothing -> (a, r <> [(ref, deps)], c, u) + Just (_, newDecls) + | decls /= newDecls -> (a, r, (ref, deps) : c, u) + | otherwise -> (a, r, c, (ref, deps) : u) + +-- | Traverses the type and finds all the refs within. +typeDeps :: P.Type a -> S.Set (ModuleName, Ref) +typeDeps = P.everythingOnTypes (<>) $ + \case + P.TypeConstructor _ (P.Qualified (P.ByModuleName mn) tn) + | isPrimModule mn -> mempty + | otherwise -> S.singleton (mn, TypeRef tn) + P.TypeConstructor _ _ -> + internalError "typeDeps: type is not qualified" + P.TypeOp _ (P.Qualified (P.ByModuleName mn) tn) + | isPrimModule mn -> mempty + | otherwise -> S.singleton (mn, TypeOpRef tn) + P.ConstrainedType _ c _ -> + S.singleton (map TypeClassRef (qualified $ P.constraintClass c)) + P.TypeOp _ _ -> + internalError "typeDeps: type is not qualified" + _ -> mempty + +qualified :: P.Qualified b -> (ModuleName, b) +qualified (P.Qualified (P.ByModuleName mn) v) = (mn, v) +qualified _ = internalError "ExternsDiff: type is not qualified" + +-- | To get fixity's data constructor dependency we should provide it with the +-- context (that contains all known refs) to search in. +externsFixityToRef :: Map ModuleName (Set Ref) -> P.ExternsFixity -> RefWithDeps +externsFixityToRef refs (P.ExternsFixity _ _ n alias) = + (ValueOpRef n, maybe mempty S.singleton $ getDep (qualified alias)) + where + getDep (mn, Left i) = Just (mn, ValueRef i) + getDep (mn, Right p) = + (mn,) <$> (M.lookup mn refs >>= S.lookupMin . S.filter (goRef p)) + goRef c (ConstructorRef _ c') = c' == c + goRef _ _ = False + +externsTypeFixityToRef :: P.ExternsTypeFixity -> RefWithDeps +externsTypeFixityToRef (P.ExternsTypeFixity _ _ n alias) = + ( TypeOpRef n + , S.singleton (map TypeRef (qualified alias)) + ) + +externsDeclarationToRef :: ModuleName -> P.ExternsDeclaration -> Maybe RefWithDeps +externsDeclarationToRef moduleName = \case + P.EDType n t tk + | P.isDictTypeName n -> Nothing + | otherwise -> Just (TypeRef n, typeDeps t <> typeKindDeps tk) + -- + P.EDTypeSynonym n args t -> + Just (TypeRef n, typeDeps t <> foldArgs args) + -- + P.EDDataConstructor n _ tn t _ + | P.isDictTypeName n -> Nothing + | otherwise -> + Just + ( ConstructorRef tn n + , -- Add the type as a dependency: if the type has changed (e.g. left side + -- param is added) we should recompile the module which uses the + -- constructor (even if there no the explicit type import). + -- Aso add the ad-hoc constructors set ref dependency: if a ctor + -- added/removed it should affect all constructors in the type, + -- because case statement's validity may be affected by newly added + -- or removed constructors. + typeDeps t <> S.fromList [(moduleName, TypeRef tn), (moduleName, CtorsSetRef tn)] + ) + -- + P.EDValue n t -> + Just (ValueRef n, typeDeps t) + -- + P.EDClass n args members constraints _ _ -> + Just + ( TypeClassRef n + , foldArgs args <> constraintsDeps constraints <> foldMap (typeDeps . snd) members + ) + -- + P.EDInstance cn n args kinds types constraints _ _ _ _ -> + Just + ( TypeInstanceRef n (qualified cn) (mapMaybe myType types) + , maybe mempty constraintsDeps constraints <> instanceArgsDeps args <> foldMap typeDeps kinds + ) + where + goDataTypeArg (_, st, _) = maybe mempty typeDeps st + typeKindDeps (P.DataType _ args _) = foldMap goDataTypeArg args + typeKindDeps _ = mempty + + myType (P.TypeConstructor _ (P.Qualified (P.ByModuleName mn) tn)) + | isPrimModule mn || moduleName /= mn = Nothing + | otherwise = Just tn + myType _ = Nothing + + foldArgs = foldMap typeDeps . mapMaybe snd + instanceArgsDeps = foldMap (typeDeps . snd) + constraintsDeps = + foldMap + ( \(P.Constraint _ cls kArgs args _) -> + S.singleton (TypeClassRef <$> qualified cls) + <> foldMap typeDeps kArgs + <> foldMap typeDeps args + ) + +-- | Removes excessive info from declarations before comparing. +-- +-- TODO: params renaming will be needed to avoid recompilation because of params +-- name changes. +stripDeclaration :: P.ExternsDeclaration -> P.ExternsDeclaration +stripDeclaration = \case + P.EDType n t (P.DataType dt args _) -> + -- Remove the notion of data constructors, we only compare type's left side. + P.EDType n t (P.DataType dt args []) + -- + P.EDInstance cn n fa ks ts cs ch chi ns ss -> + P.EDInstance cn n fa ks ts cs (map stripChain ch) chi ns ss + -- + decl -> decl + where + emptySP = P.SourcePos 0 0 + stripChain (ChainId (n, _)) = ChainId (n, emptySP) + +isPrimModule :: ModuleName -> Bool +isPrimModule = flip S.member (S.fromList primModules) diff --git a/src/Language/PureScript/Make/Monad.hs b/src/Language/PureScript/Make/Monad.hs index 8c86144e9a..ed553cf28f 100644 --- a/src/Language/PureScript/Make/Monad.hs +++ b/src/Language/PureScript/Make/Monad.hs @@ -5,6 +5,8 @@ module Language.PureScript.Make.Monad , makeIO , getTimestamp , getTimestampMaybe + , getCurrentTime + , setTimestamp , readTextFile , readJSONFile , readJSONFileIO @@ -35,14 +37,16 @@ import Control.Monad.Trans.Except (ExceptT, runExceptT) import Control.Monad.Writer.Class (MonadWriter(..)) import Data.Aeson qualified as Aeson import Data.ByteString qualified as B +import Data.Maybe (isJust) import Data.Text (Text) import Data.Text qualified as Text import Data.Time.Clock (UTCTime) +import Data.Time.Clock qualified as Time import Language.PureScript.Errors (ErrorMessage(..), MultipleErrors, SimpleErrorMessage(..), singleError) import Language.PureScript.Externs (ExternsFile, externsIsCurrentVersion) import Language.PureScript.Make.Cache (ContentHash, hash) import Language.PureScript.Options (Options) -import System.Directory (createDirectoryIfMissing, getModificationTime) +import System.Directory (createDirectoryIfMissing, getModificationTime, setModificationTime) import System.Directory qualified as Directory import System.FilePath (takeDirectory) import System.IO.Error (tryIOError, isDoesNotExistError) @@ -85,6 +89,18 @@ getTimestampMaybe :: (MonadIO m, MonadError MultipleErrors m) => FilePath -> m ( getTimestampMaybe path = makeIO ("get a timestamp for file: " <> Text.pack path) $ catchDoesNotExist $ getModificationTime path +-- | Get current system time. +getCurrentTime :: (MonadIO m) => m UTCTime +getCurrentTime = + liftIO Time.getCurrentTime + +-- | Set a file's modification time in the 'Make' monad, returning False if +-- the file does not exist. +setTimestamp :: (MonadIO m, MonadError MultipleErrors m) => FilePath -> UTCTime -> m Bool +setTimestamp path time = + makeIO ("set a timestamp for file: " <> Text.pack path) $ (fmap isJust . catchDoesNotExist) $ setModificationTime path time + + -- | Read a text file strictly in the 'Make' monad, capturing any errors using -- the 'MonadError' instance. readTextFile :: (MonadIO m, MonadError MultipleErrors m) => FilePath -> m Text diff --git a/src/Language/PureScript/Names.hs b/src/Language/PureScript/Names.hs index e5df3610bf..094ae5773d 100644 --- a/src/Language/PureScript/Names.hs +++ b/src/Language/PureScript/Names.hs @@ -19,6 +19,7 @@ import Data.Aeson (FromJSON(..), FromJSONKey(..), Options(..), SumEncoding(..), import Data.Aeson.TH (deriveJSON) import Data.Text (Text) import Data.Text qualified as T +import Data.Int (Int64) import Language.PureScript.AST.SourcePos (SourcePos, pattern SourcePos) @@ -86,7 +87,7 @@ data Ident -- | -- A generated name for an identifier -- - | GenIdent (Maybe Text) Integer + | GenIdent (Maybe Text) Int64 -- | -- A generated name used only for type-checking -- diff --git a/src/Language/PureScript/Pretty/Types.hs b/src/Language/PureScript/Pretty/Types.hs index 20de0ed9e2..9b3be46937 100644 --- a/src/Language/PureScript/Pretty/Types.hs +++ b/src/Language/PureScript/Pretty/Types.hs @@ -238,7 +238,7 @@ forall_ = mkPattern match typeAtomAsBox' :: PrettyPrintType -> Box typeAtomAsBox' = fromMaybe (internalError "Incomplete pattern") - . PA.pattern (matchTypeAtom defaultOptions) () + . PA.pattern_ (matchTypeAtom defaultOptions) () typeAtomAsBox :: Int -> Type a -> Box typeAtomAsBox maxDepth = typeAtomAsBox' . convertPrettyPrintType maxDepth @@ -280,7 +280,7 @@ unicodeOptions = TypeRenderOptions False True False typeAsBoxImpl :: TypeRenderOptions -> PrettyPrintType -> Box typeAsBoxImpl tro = fromMaybe (internalError "Incomplete pattern") - . PA.pattern (matchType tro) () + . PA.pattern_ (matchType tro) () -- | Generate a pretty-printed string representing a 'Type' prettyPrintType :: Int -> Type a -> String diff --git a/src/Language/PureScript/Sugar.hs b/src/Language/PureScript/Sugar.hs index 4d713d5418..f898bc6a44 100644 --- a/src/Language/PureScript/Sugar.hs +++ b/src/Language/PureScript/Sugar.hs @@ -3,6 +3,8 @@ -- module Language.PureScript.Sugar (desugar, module S) where +import Prelude + import Control.Category ((>>>)) import Control.Monad ((>=>)) import Control.Monad.Error.Class (MonadError) @@ -25,6 +27,7 @@ import Language.PureScript.Sugar.Operators as S import Language.PureScript.Sugar.TypeClasses as S import Language.PureScript.Sugar.TypeClasses.Deriving as S import Language.PureScript.Sugar.TypeDeclarations as S +import Language.PureScript.Sugar.Accessor as S -- | -- The desugaring pipeline proceeds as follows: @@ -62,6 +65,7 @@ desugar desugar externs = desugarSignedLiterals >>> desugarObjectConstructors + >>> fmap (desugarAccessorModule externs) >=> desugarDoModule >=> desugarAdoModule >=> desugarLetPatternModule diff --git a/src/Language/PureScript/Sugar/Accessor.hs b/src/Language/PureScript/Sugar/Accessor.hs new file mode 100644 index 0000000000..1aaa010717 --- /dev/null +++ b/src/Language/PureScript/Sugar/Accessor.hs @@ -0,0 +1,51 @@ +-- | +module Language.PureScript.Sugar.Accessor + ( desugarAccessorModule + ) where + +import Prelude + +import Control.Monad.Writer + +import Data.Monoid (Any(..)) +import Language.PureScript.AST +import Language.PureScript.Constants.Libs qualified as C +import Language.PureScript.Externs +import Language.PureScript.Names +import Language.PureScript.Types + +-- | Replace every @BoundValueDeclaration@ in @Let@ expressions with @Case@ +-- expressions. +desugarAccessorModule :: [ExternsFile] -> Module -> Module +desugarAccessorModule externs m + | not (any (\e -> efModuleName e == ModuleName "Data.Record") externs) = m +desugarAccessorModule _externs (Module ss coms mn ds exts) = + let (ds', Any used) = runWriter $ traverse desugarAccessor ds + extraImports = if used + then addDefaultImport (Qualified (ByModuleName C.M_Data_Record) C.M_Data_Record) + . addDefaultImport (Qualified (ByModuleName C.M_Type_Proxy) C.M_Type_Proxy) + else id + in extraImports $ Module ss coms mn ds' exts + +-- | Desugar a single let expression +desugarAccessor :: Declaration -> Writer Any Declaration +desugarAccessor decl = + let (f, _, _) = everywhereOnValuesM pure replace pure in f decl + where + replace :: Expr -> Writer Any Expr + replace (Accessor label e) = do + tell (Any True) + pure $ App + (App + (Var nullSourceSpan C.I_getField) + (TypedValue + False + (Constructor nullSourceSpan C.C_Proxy) + (TypeApp nullSourceAnn + (TypeConstructor nullSourceAnn C.Proxy) + (TypeLevelString nullSourceAnn label) + ) + ) + ) + e + replace other = pure other diff --git a/src/Language/PureScript/Sugar/BindingGroups.hs b/src/Language/PureScript/Sugar/BindingGroups.hs index d2f9aebf2b..835e775f81 100644 --- a/src/Language/PureScript/Sugar/BindingGroups.hs +++ b/src/Language/PureScript/Sugar/BindingGroups.hs @@ -114,8 +114,8 @@ createBindingGroups moduleName = mapM f <=< handleDecls valueDeclarationInfo = M.fromList $ swap <$> valueDeclarationKeys findDeclarationInfo i = (M.findWithDefault False i valueDeclarationInfo, i) - computeValueDependencies = (`intersect` valueDeclarationKeys) . fmap findDeclarationInfo . usedIdents moduleName - + computeValueDependencies = (`intersect` valueDeclarationKeys) . fmap findDeclarationInfo . usedIdents moduleName + makeValueDeclarationVert = (,,) <$> id <*> makeValueDeclarationKey <*> computeValueDependencies valueDeclarationVerts = makeValueDeclarationVert <$> values @@ -267,7 +267,7 @@ toDataBindingGroup -> m Declaration toDataBindingGroup (AcyclicSCC (d, _, _)) = return d toDataBindingGroup (CyclicSCC ds') - | Just kds@((ss, _):|_) <- nonEmpty $ concatMap (kindDecl . getDecl) ds' = throwError . errorMessage' ss . CycleInKindDeclaration $ fmap snd kds + | Just kds@((ss, _) :| _) <- nonEmpty $ concatMap (kindDecl . getDecl) ds' = throwError . errorMessage' ss . CycleInKindDeclaration $ fmap snd kds | not (null typeSynonymCycles) = throwError . MultipleErrors diff --git a/src/Language/PureScript/TypeChecker.hs b/src/Language/PureScript/TypeChecker.hs index 479a01f012..fd4e7c7982 100644 --- a/src/Language/PureScript/TypeChecker.hs +++ b/src/Language/PureScript/TypeChecker.hs @@ -13,9 +13,8 @@ import Protolude (headMay, maybeToLeft, ordNub) import Control.Lens ((^..), _2) import Control.Monad (when, unless, void, forM, zipWithM_) import Control.Monad.Error.Class (MonadError(..)) -import Control.Monad.State.Class (MonadState(..), modify, gets) -import Control.Monad.Supply.Class (MonadSupply) -import Control.Monad.Writer.Class (MonadWriter, tell) +import Control.Monad.State.Class (modify, gets) +import Control.Monad.Writer.Class (tell) import Data.Foldable (for_, traverse_, toList) import Data.List (nubBy, (\\), sort, group) @@ -24,6 +23,7 @@ import Data.Either (partitionEithers) import Data.Text (Text) import Data.List.NonEmpty qualified as NEL import Data.Map qualified as M +import Data.IntMap.Lazy qualified as IM import Data.Set qualified as S import Data.Text qualified as T @@ -32,7 +32,7 @@ import Language.PureScript.AST.Declarations.ChainId (ChainId) import Language.PureScript.Constants.Libs qualified as Libs import Language.PureScript.Crash (internalError) import Language.PureScript.Environment (DataDeclType(..), Environment(..), FunctionalDependency, NameKind(..), NameVisibility(..), TypeClassData(..), TypeKind(..), isDictTypeName, kindArity, makeTypeClassData, nominalRolesForKind, tyFunction) -import Language.PureScript.Errors (MultipleErrors, SimpleErrorMessage(..), addHint, errorMessage, errorMessage', positionedError, rethrow, warnAndRethrow) +import Language.PureScript.Errors (SimpleErrorMessage(..), addHint, errorMessage, errorMessage', positionedError, rethrow, warnAndRethrow, MultipleErrors) import Language.PureScript.Linter (checkExhaustiveExpr) import Language.PureScript.Linter.Wildcards (ignoreWildcardsUnderCompleteTypeSignatures) import Language.PureScript.Names (Ident, ModuleName, ProperName, ProperNameType(..), Qualified(..), QualifiedBy(..), coerceProperName, disqualify, isPlainIdent, mkQualified) @@ -48,14 +48,13 @@ import Language.PureScript.TypeClassDictionaries (NamedDict, TypeClassDictionary import Language.PureScript.Types (Constraint(..), SourceConstraint, SourceType, Type(..), containsForAll, eqType, everythingOnTypes, overConstraintArgs, srcInstanceType, unapplyTypes) addDataType - :: (MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) - => ModuleName + :: ModuleName -> DataDeclType -> ProperName 'TypeName -> [(Text, Maybe SourceType, Role)] -> [(DataConstructorDeclaration, SourceType)] -> SourceType - -> m () + -> TypeCheckM () addDataType moduleName dtype name args dctors ctorKind = do env <- getEnv let mapDataCtor (DataConstructorDeclaration _ ctorName vars) = (ctorName, snd <$> vars) @@ -69,14 +68,13 @@ addDataType moduleName dtype name args dctors ctorKind = do addDataConstructor moduleName dtype name dctor fields polyType addDataConstructor - :: (MonadState CheckState m, MonadError MultipleErrors m) - => ModuleName + :: ModuleName -> DataDeclType -> ProperName 'TypeName -> ProperName 'ConstructorName -> [(Ident, SourceType)] -> SourceType - -> m () + -> TypeCheckM () addDataConstructor moduleName dtype name dctor dctorArgs polyType = do let fields = fst <$> dctorArgs env <- getEnv @@ -84,10 +82,9 @@ addDataConstructor moduleName dtype name dctor dctorArgs polyType = do putEnv $ env { dataConstructors = M.insert (Qualified (ByModuleName moduleName) dctor) (dtype, name, polyType, fields) (dataConstructors env) } checkRoleDeclaration - :: (MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) - => ModuleName + :: ModuleName -> RoleDeclarationData - -> m () + -> TypeCheckM () checkRoleDeclaration moduleName (RoleDeclarationData (ss, _) name declaredRoles) = do warnAndRethrow (addHint (ErrorInRoleDeclaration name) . addHint (positionedError ss)) $ do env <- getEnv @@ -104,13 +101,12 @@ checkRoleDeclaration moduleName (RoleDeclarationData (ss, _) name declaredRoles) _ -> internalError "Unsupported role declaration" addTypeSynonym - :: (MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) - => ModuleName + :: ModuleName -> ProperName 'TypeName -> [(Text, Maybe SourceType)] -> SourceType -> SourceType - -> m () + -> TypeCheckM () addTypeSynonym moduleName name args ty kind = do env <- getEnv checkTypeSynonyms ty @@ -122,10 +118,9 @@ addTypeSynonym moduleName name args ty kind = do , typeSynonyms = M.insert qualName (args, ty) (typeSynonyms env) } valueIsNotDefined - :: (MonadState CheckState m, MonadError MultipleErrors m) - => ModuleName + :: ModuleName -> Ident - -> m () + -> TypeCheckM () valueIsNotDefined moduleName name = do env <- getEnv case M.lookup (Qualified (ByModuleName moduleName) name) (names env) of @@ -133,27 +128,24 @@ valueIsNotDefined moduleName name = do Nothing -> return () addValue - :: (MonadState CheckState m) - => ModuleName + :: ModuleName -> Ident -> SourceType -> NameKind - -> m () + -> TypeCheckM () addValue moduleName name ty nameKind = do env <- getEnv putEnv (env { names = M.insert (Qualified (ByModuleName moduleName) name) (ty, nameKind, Defined) (names env) }) addTypeClass - :: forall m - . (MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) - => ModuleName + :: ModuleName -> Qualified (ProperName 'ClassName) -> [(Text, Maybe SourceType)] -> [SourceConstraint] -> [FunctionalDependency] -> [Declaration] -> SourceType - -> m () + -> TypeCheckM () addTypeClass _ qualifiedClassName args implies dependencies ds kind = do env <- getEnv newClass <- mkNewClass @@ -167,7 +159,7 @@ addTypeClass _ qualifiedClassName args implies dependencies ds kind = do classMembers :: [(Ident, SourceType)] classMembers = map toPair ds - mkNewClass :: m TypeClassData + mkNewClass :: TypeCheckM TypeClassData mkNewClass = do env <- getEnv implies' <- (traverse . overConstraintArgs . traverse) replaceAllTypeSynonyms implies @@ -182,18 +174,16 @@ addTypeClass _ qualifiedClassName args implies dependencies ds kind = do toPair _ = internalError "Invalid declaration in TypeClassDeclaration" addTypeClassDictionaries - :: (MonadState CheckState m) - => QualifiedBy + :: QualifiedBy -> M.Map (Qualified (ProperName 'ClassName)) (M.Map (Qualified Ident) (NEL.NonEmpty NamedDict)) - -> m () + -> TypeCheckM () addTypeClassDictionaries mn entries = modify $ \st -> st { checkEnv = (checkEnv st) { typeClassDictionaries = insertState st } } where insertState st = M.insertWith (M.unionWith (M.unionWith (<>))) mn entries (typeClassDictionaries . checkEnv $ st) checkDuplicateTypeArguments - :: (MonadState CheckState m, MonadError MultipleErrors m) - => [Text] - -> m () + :: [Text] + -> TypeCheckM () checkDuplicateTypeArguments args = for_ firstDup $ \dup -> throwError . errorMessage $ DuplicateTypeArgument dup where @@ -201,11 +191,10 @@ checkDuplicateTypeArguments args = for_ firstDup $ \dup -> firstDup = listToMaybe $ args \\ ordNub args checkTypeClassInstance - :: (MonadState CheckState m, MonadError MultipleErrors m) - => TypeClassData + :: TypeClassData -> Int -- ^ index of type class argument -> SourceType - -> m () + -> TypeCheckM () checkTypeClassInstance cls i = check where -- If the argument is determined via fundeps then we are less restrictive in -- what type is allowed. This is because the type cannot be used to influence @@ -228,9 +217,8 @@ checkTypeClassInstance cls i = check where -- Check that type synonyms are fully-applied in a type -- checkTypeSynonyms - :: (MonadState CheckState m, MonadError MultipleErrors m) - => SourceType - -> m () + :: SourceType + -> TypeCheckM () checkTypeSynonyms = void . replaceAllTypeSynonyms -- | @@ -249,14 +237,12 @@ checkTypeSynonyms = void . replaceAllTypeSynonyms -- * Process module imports -- typeCheckAll - :: forall m - . (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) - => ModuleName + :: ModuleName -> [Declaration] - -> m [Declaration] + -> TypeCheckM [Declaration] typeCheckAll moduleName = traverse go where - go :: Declaration -> m Declaration + go :: Declaration -> TypeCheckM Declaration go (DataDeclaration sa@(ss, _) dtype name args dctors) = do warnAndRethrow (addHint (ErrorInTypeConstructor name) . addHint (positionedError ss)) $ do when (dtype == Newtype) $ void $ checkNewtype name dctors @@ -413,14 +399,14 @@ typeCheckAll moduleName = traverse go addTypeClassDictionaries (ByModuleName moduleName) . M.singleton className $ M.singleton (tcdValue dict) (pure dict) return d - checkInstanceArity :: Ident -> Qualified (ProperName 'ClassName) -> TypeClassData -> [SourceType] -> m () + checkInstanceArity :: Ident -> Qualified (ProperName 'ClassName) -> TypeClassData -> [SourceType] -> TypeCheckM () checkInstanceArity dictName className typeClass tys = do let typeClassArity = length (typeClassArguments typeClass) instanceArity = length tys when (typeClassArity /= instanceArity) $ throwError . errorMessage $ ClassInstanceArityMismatch dictName className typeClassArity instanceArity - checkInstanceMembers :: [Declaration] -> m [Declaration] + checkInstanceMembers :: [Declaration] -> TypeCheckM [Declaration] checkInstanceMembers instDecls = do let idents = sort . map head . group . map memberName $ instDecls for_ (firstDuplicate idents) $ \ident -> @@ -458,11 +444,11 @@ typeCheckAll moduleName = traverse go typeModule (KindedType _ t1 _) = typeModule t1 typeModule _ = internalError "Invalid type in instance in findNonOrphanModules" - modulesByTypeIndex :: M.Map Int (Maybe ModuleName) - modulesByTypeIndex = M.fromList (zip [0 ..] (typeModule <$> tys')) + modulesByTypeIndex :: IM.IntMap (Maybe ModuleName) + modulesByTypeIndex = IM.fromList (zip [0 ..] (typeModule <$> tys')) lookupModule :: Int -> S.Set ModuleName - lookupModule idx = case M.lookup idx modulesByTypeIndex of + lookupModule idx = case IM.lookup idx modulesByTypeIndex of Just ms -> S.fromList (toList ms) Nothing -> internalError "Unknown type index in findNonOrphanModules" @@ -489,7 +475,7 @@ typeCheckAll moduleName = traverse go -> TypeClassData -> [SourceType] -> S.Set ModuleName - -> m () + -> TypeCheckM () checkOverlappingInstance ss ch dictName vars className typeClass tys' nonOrphanModules = do for_ nonOrphanModules $ \m -> do dicts <- M.toList <$> lookupTypeClassDictionariesForClass (ByModuleName m) className @@ -534,7 +520,7 @@ typeCheckAll moduleName = traverse go -> Qualified (ProperName 'ClassName) -> [SourceType] -> S.Set ModuleName - -> m () + -> TypeCheckM () checkOrphanInstance dictName className tys' nonOrphanModules | moduleName `S.member` nonOrphanModules = return () | otherwise = throwError . errorMessage $ OrphanInstance dictName className nonOrphanModules tys' @@ -552,7 +538,7 @@ typeCheckAll moduleName = traverse go withRoles :: [(Text, Maybe SourceType)] -> [Role] -> [(Text, Maybe SourceType, Role)] withRoles = zipWith $ \(v, k) r -> (v, k, r) - replaceTypeSynonymsInDataConstructor :: DataConstructorDeclaration -> m DataConstructorDeclaration + replaceTypeSynonymsInDataConstructor :: DataConstructorDeclaration -> TypeCheckM DataConstructorDeclaration replaceTypeSynonymsInDataConstructor DataConstructorDeclaration{..} = do dataCtorFields' <- traverse (traverse replaceAllTypeSynonyms) dataCtorFields return DataConstructorDeclaration @@ -565,8 +551,7 @@ typeCheckAll moduleName = traverse go -- data constructor declaration and the single field, as a 'proof' that the -- newtype was indeed a valid newtype. checkNewtype - :: forall m - . MonadError MultipleErrors m + :: MonadError MultipleErrors m => ProperName 'TypeName -> [DataConstructorDeclaration] -> m (DataConstructorDeclaration, (Ident, SourceType)) @@ -578,11 +563,9 @@ checkNewtype name _ = throwError . errorMessage $ InvalidNewtype name -- required by exported members are also exported. -- typeCheckModule - :: forall m - . (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) - => M.Map ModuleName Exports + :: M.Map ModuleName Exports -> Module - -> m Module + -> TypeCheckM Module typeCheckModule _ (Module _ _ _ _ Nothing) = internalError "exports should have been elaborated before typeCheckModule" typeCheckModule modulesExports (Module ss coms mn decls (Just exps)) = @@ -662,7 +645,7 @@ typeCheckModule modulesExports (Module ss coms mn decls (Just exps)) = untilSame :: Eq a => (a -> a) -> a -> a untilSame f a = let a' = f a in if a == a' then a else untilSame f a' - checkMemberExport :: (SourceType -> [DeclarationRef]) -> DeclarationRef -> m () + checkMemberExport :: (SourceType -> [DeclarationRef]) -> DeclarationRef -> TypeCheckM () checkMemberExport extract dr@(TypeRef _ name dctors) = do env <- getEnv for_ (M.lookup (qualify' name) (types env)) $ \(k, _) -> do @@ -686,7 +669,7 @@ typeCheckModule modulesExports (Module ss coms mn decls (Just exps)) = :: (Qualified (ProperName 'ClassName) -> S.Set (Qualified (ProperName 'ClassName))) -> (Qualified (ProperName 'ClassName) -> S.Set (Qualified (ProperName 'ClassName))) -> DeclarationRef - -> m () + -> TypeCheckM () checkSuperClassExport superClassesFor transitiveSuperClassesFor dr@(TypeClassRef drss className) = do let superClasses = superClassesFor (qualify' className) -- thanks to laziness, the computation of the transitive @@ -703,7 +686,7 @@ typeCheckModule modulesExports (Module ss coms mn decls (Just exps)) = checkSuperClassExport _ _ _ = return () - checkExport :: DeclarationRef -> [DeclarationRef] -> m () + checkExport :: DeclarationRef -> [DeclarationRef] -> TypeCheckM () checkExport dr drs = case filter (not . exported) drs of [] -> return () hidden -> throwError . errorMessage' (declRefSourceSpan dr) $ TransitiveExportError dr (nubBy nubEq hidden) @@ -721,7 +704,7 @@ typeCheckModule modulesExports (Module ss coms mn decls (Just exps)) = -- Check that all the type constructors defined in the current module that appear in member types -- have also been exported from the module - checkTypesAreExported :: DeclarationRef -> m () + checkTypesAreExported :: DeclarationRef -> TypeCheckM () checkTypesAreExported ref = checkMemberExport findTcons ref where findTcons :: SourceType -> [DeclarationRef] @@ -733,7 +716,7 @@ typeCheckModule modulesExports (Module ss coms mn decls (Just exps)) = -- Check that all the classes defined in the current module that appear in member types have also -- been exported from the module - checkClassesAreExported :: DeclarationRef -> m () + checkClassesAreExported :: DeclarationRef -> TypeCheckM () checkClassesAreExported ref = checkMemberExport findClasses ref where findClasses :: SourceType -> [DeclarationRef] @@ -745,7 +728,7 @@ typeCheckModule modulesExports (Module ss coms mn decls (Just exps)) = extractCurrentModuleClass (Qualified (ByModuleName mn') name) | mn == mn' = [name] extractCurrentModuleClass _ = [] - checkClassMembersAreExported :: DeclarationRef -> m () + checkClassMembersAreExported :: DeclarationRef -> TypeCheckM () checkClassMembersAreExported dr@(TypeClassRef ss' name) = do let members = ValueRef ss' `map` head (mapMaybe findClassMembers decls) let missingMembers = members \\ exps @@ -762,7 +745,7 @@ typeCheckModule modulesExports (Module ss coms mn decls (Just exps)) = -- If a type is exported without data constructors, we warn on `Generic` or `Newtype` instances. -- On the other hand if any data constructors are exported, we require all of them to be exported. - checkDataConstructorsAreExported :: DeclarationRef -> m () + checkDataConstructorsAreExported :: DeclarationRef -> TypeCheckM () checkDataConstructorsAreExported dr@(TypeRef ss' name (fromMaybe [] -> exportedDataConstructorsNames)) | null exportedDataConstructorsNames = for_ [ Libs.Generic diff --git a/src/Language/PureScript/TypeChecker/Deriving.hs b/src/Language/PureScript/TypeChecker/Deriving.hs index 8d5dcde9b6..502a3dc05d 100644 --- a/src/Language/PureScript/TypeChecker/Deriving.hs +++ b/src/Language/PureScript/TypeChecker/Deriving.hs @@ -15,20 +15,19 @@ import Data.List (init, last, zipWith3, (!!)) import Data.Map qualified as M import Data.These (These(..), mergeTheseWith, these) -import Control.Monad.Supply.Class (MonadSupply) import Language.PureScript.AST (Binder(..), CaseAlternative(..), ErrorMessageHint(..), Expr(..), InstanceDerivationStrategy(..), Literal(..), SourceSpan, nullSourceSpan) import Language.PureScript.AST.Utils (UnwrappedTypeConstructor(..), lam, lamCase, lamCase2, mkBinder, mkCtor, mkCtorBinder, mkLit, mkRef, mkVar, unguarded, unwrapTypeConstructor, utcQTyCon) import Language.PureScript.Constants.Libs qualified as Libs import Language.PureScript.Constants.Prim qualified as Prim import Language.PureScript.Crash (internalError) import Language.PureScript.Environment (DataDeclType(..), Environment(..), FunctionalDependency(..), TypeClassData(..), TypeKind(..), kindType, (-:>)) -import Language.PureScript.Errors (MultipleErrors, SimpleErrorMessage(..), addHint, errorMessage, internalCompilerError) +import Language.PureScript.Errors (SimpleErrorMessage(..), addHint, errorMessage, internalCompilerError) import Language.PureScript.Label (Label(..)) import Language.PureScript.Names (pattern ByNullSourcePos, Ident(..), ModuleName(..), Name(..), ProperName(..), ProperNameType(..), Qualified(..), QualifiedBy(..), coerceProperName, freshIdent, qualify) import Language.PureScript.PSString (PSString, mkString) import Language.PureScript.Sugar.TypeClasses (superClassDictionaryNames) import Language.PureScript.TypeChecker.Entailment (InstanceContext, findDicts) -import Language.PureScript.TypeChecker.Monad (CheckState, getEnv, getTypeClassDictionaries, unsafeCheckCurrentModule) +import Language.PureScript.TypeChecker.Monad (getEnv, getTypeClassDictionaries, unsafeCheckCurrentModule, TypeCheckM) import Language.PureScript.TypeChecker.Synonyms (replaceAllTypeSynonyms) import Language.PureScript.TypeClassDictionaries (TypeClassDictionaryInScope(..)) import Language.PureScript.Types (Constraint(..), pattern REmptyKinded, SourceType, Type(..), completeBinderList, eqType, everythingOnTypes, replaceAllTypeVars, srcTypeVar, usedTypeVariables) @@ -46,15 +45,10 @@ extractNewtypeName mn . (unwrapTypeConstructor <=< lastMay) deriveInstance - :: forall m - . MonadError MultipleErrors m - => MonadState CheckState m - => MonadSupply m - => MonadWriter MultipleErrors m - => SourceType + :: SourceType -> Qualified (ProperName 'ClassName) -> InstanceDerivationStrategy - -> m Expr + -> TypeCheckM Expr deriveInstance instType className strategy = do mn <- unsafeCheckCurrentModule env <- getEnv @@ -67,7 +61,7 @@ deriveInstance instType className strategy = do case strategy of KnownClassStrategy -> let - unaryClass :: (UnwrappedTypeConstructor -> m [(PSString, Expr)]) -> m Expr + unaryClass :: (UnwrappedTypeConstructor -> TypeCheckM [(PSString, Expr)]) -> TypeCheckM Expr unaryClass f = case tys of [ty] -> case unwrapTypeConstructor ty of Just utc | mn == utcModuleName utc -> do @@ -107,14 +101,10 @@ deriveInstance instType className strategy = do _ -> throwError . errorMessage $ InvalidNewtypeInstance className tys deriveNewtypeInstance - :: forall m - . MonadError MultipleErrors m - => MonadState CheckState m - => MonadWriter MultipleErrors m - => Qualified (ProperName 'ClassName) + :: Qualified (ProperName 'ClassName) -> [SourceType] -> UnwrappedTypeConstructor - -> m Expr + -> TypeCheckM Expr deriveNewtypeInstance className tys (UnwrappedTypeConstructor mn tyConNm dkargs dargs) = do verifySuperclasses (dtype, tyKindNames, tyArgNames, ctors) <- lookupTypeDecl mn tyConNm @@ -149,7 +139,7 @@ deriveNewtypeInstance className tys (UnwrappedTypeConstructor mn tyConNm dkargs | arg == arg' = stripRight args t stripRight _ _ = Nothing - verifySuperclasses :: m () + verifySuperclasses :: TypeCheckM () verifySuperclasses = do env <- getEnv for_ (M.lookup className (typeClasses env)) $ \TypeClassData{ typeClassArguments = args, typeClassSuperclasses = superclasses } -> @@ -195,29 +185,22 @@ data TypeInfo = TypeInfo } lookupTypeInfo - :: forall m - . MonadError MultipleErrors m - => MonadState CheckState m - => UnwrappedTypeConstructor - -> m TypeInfo + :: UnwrappedTypeConstructor + -> TypeCheckM TypeInfo lookupTypeInfo UnwrappedTypeConstructor{..} = do (_, kindParams, map fst -> tiTypeParams, tiCtors) <- lookupTypeDecl utcModuleName utcTyCon let tiArgSubst = zip tiTypeParams utcArgs <> zip kindParams utcKindArgs pure TypeInfo{..} deriveEq - :: forall m - . MonadError MultipleErrors m - => MonadState CheckState m - => MonadSupply m - => UnwrappedTypeConstructor - -> m [(PSString, Expr)] + :: UnwrappedTypeConstructor + -> TypeCheckM [(PSString, Expr)] deriveEq utc = do TypeInfo{..} <- lookupTypeInfo utc eqFun <- mkEqFunction tiCtors pure [(Libs.S_eq, eqFun)] where - mkEqFunction :: [(ProperName 'ConstructorName, [SourceType])] -> m Expr + mkEqFunction :: [(ProperName 'ConstructorName, [SourceType])] -> TypeCheckM Expr mkEqFunction ctors = do x <- freshIdent "x" y <- freshIdent "y" @@ -239,7 +222,7 @@ deriveEq utc = do where catchAll = CaseAlternative [NullBinder, NullBinder] (unguarded (mkLit (BooleanLiteral False))) - mkCtorClause :: (ProperName 'ConstructorName, [SourceType]) -> m CaseAlternative + mkCtorClause :: (ProperName 'ConstructorName, [SourceType]) -> TypeCheckM CaseAlternative mkCtorClause (ctorName, tys) = do identsL <- replicateM (length tys) (freshIdent "l") identsR <- replicateM (length tys) (freshIdent "r") @@ -267,18 +250,14 @@ deriveEq1 :: forall m. Applicative m => m [(PSString, Expr)] deriveEq1 = pure [(Libs.S_eq1, mkRef Libs.I_eq)] deriveOrd - :: forall m - . MonadError MultipleErrors m - => MonadState CheckState m - => MonadSupply m - => UnwrappedTypeConstructor - -> m [(PSString, Expr)] + :: UnwrappedTypeConstructor + -> TypeCheckM [(PSString, Expr)] deriveOrd utc = do TypeInfo{..} <- lookupTypeInfo utc compareFun <- mkCompareFunction tiCtors pure [(Libs.S_compare, compareFun)] where - mkCompareFunction :: [(ProperName 'ConstructorName, [SourceType])] -> m Expr + mkCompareFunction :: [(ProperName 'ConstructorName, [SourceType])] -> TypeCheckM Expr mkCompareFunction ctors = do x <- freshIdent "x" y <- freshIdent "y" @@ -311,7 +290,7 @@ deriveOrd utc = do ordCompare1 :: Expr -> Expr -> Expr ordCompare1 = App . App (mkRef Libs.I_compare1) - mkCtorClauses :: ((ProperName 'ConstructorName, [SourceType]), Bool) -> m [CaseAlternative] + mkCtorClauses :: ((ProperName 'ConstructorName, [SourceType]), Bool) -> TypeCheckM [CaseAlternative] mkCtorClauses ((ctorName, tys), isLast) = do identsL <- replicateM (length tys) (freshIdent "l") identsR <- replicateM (length tys) (freshIdent "r") @@ -354,12 +333,9 @@ deriveOrd1 :: forall m. Applicative m => m [(PSString, Expr)] deriveOrd1 = pure [(Libs.S_compare1, mkRef Libs.I_compare)] lookupTypeDecl - :: forall m - . MonadError MultipleErrors m - => MonadState CheckState m - => ModuleName + :: ModuleName -> ProperName 'TypeName - -> m (Maybe DataDeclType, [Text], [(Text, Maybe SourceType)], [(ProperName 'ConstructorName, [SourceType])]) + -> TypeCheckM (Maybe DataDeclType, [Text], [(Text, Maybe SourceType)], [(ProperName 'ConstructorName, [SourceType])]) lookupTypeDecl mn typeName = do env <- getEnv note (errorMessage $ CannotFindDerivingType typeName) $ do @@ -436,15 +412,13 @@ filterThese :: forall a. (a -> Bool) -> These a a -> Maybe (These a a) filterThese p = uncurry align . over both (mfilter p) . unalign . Just validateParamsInTypeConstructors - :: forall c m - . MonadError MultipleErrors m - => MonadState CheckState m - => Qualified (ProperName 'ClassName) + :: forall c + . Qualified (ProperName 'ClassName) -> UnwrappedTypeConstructor -> Bool -> CovariantClasses -> Maybe (ContravarianceSupport c) - -> m [(ProperName 'ConstructorName, [Maybe (ParamUsage c)])] + -> TypeCheckM [(ProperName 'ConstructorName, [Maybe (ParamUsage c)])] validateParamsInTypeConstructors derivingClass utc isBi CovariantClasses{..} contravarianceSupport = do TypeInfo{..} <- lookupTypeInfo utc (mbLParam, param) <- liftEither . first (errorMessage . flip KindsDoNotUnify kindType . (kindType -:>)) $ @@ -548,7 +522,7 @@ validateParamsInTypeConstructors derivingClass utc isBi CovariantClasses{..} con TypeConstructor _ (Qualified qb nm) -> Qualified qb (Right nm) ty -> internalError $ "headOfType missing a case: " <> show (void ty) -usingLamIdent :: forall m. MonadSupply m => (Expr -> m Expr) -> m Expr +usingLamIdent :: (Expr -> TypeCheckM Expr) -> TypeCheckM Expr usingLamIdent cb = do ident <- freshIdent "v" lam ident <$> cb (mkVar ident) @@ -562,14 +536,12 @@ unnestRecords f = fix $ \go -> \case usage -> f usage mkCasesForTraversal - :: forall c f m - . Applicative f -- this effect distinguishes the semantics of maps, folds, and traversals - => MonadSupply m + :: Applicative f => ModuleName -> (ParamUsage c -> Expr -> f Expr) -- how to handle constructor arguments - -> (f Expr -> m Expr) -- resolve the applicative effect into an expression + -> (f Expr -> TypeCheckM Expr) -- resolve the applicative effect into an expression -> [(ProperName 'ConstructorName, [Maybe (ParamUsage c)])] - -> m Expr + -> TypeCheckM Expr mkCasesForTraversal mn handleArg extractExpr ctors = do m <- freshIdent "m" fmap (lamCase m) . for ctors $ \(ctorName, ctorUsages) -> do @@ -605,15 +577,13 @@ data TraversalOps m = forall f. Applicative f => TraversalOps } mkTraversal - :: forall c m - . MonadSupply m - => ModuleName + :: forall c. ModuleName -> Bool -> TraversalExprs -> (c -> ContraversalExprs) - -> TraversalOps m + -> TraversalOps TypeCheckM -> [(ProperName 'ConstructorName, [Maybe (ParamUsage c)])] - -> m Expr + -> TypeCheckM Expr mkTraversal mn isBi te@TraversalExprs{..} getContraversalExprs (TraversalOps @_ @f visitExpr extractExpr) ctors = do f <- freshIdent "f" g <- if isBi then freshIdent "g" else pure f @@ -621,7 +591,7 @@ mkTraversal mn isBi te@TraversalExprs{..} getContraversalExprs (TraversalOps @_ handleValue :: ParamUsage c -> Expr -> f Expr handleValue = unnestRecords $ \usage inputExpr -> visitExpr $ flip App inputExpr <$> mkFnExprForValue usage - mkFnExprForValue :: ParamUsage c -> m Expr + mkFnExprForValue :: ParamUsage c -> TypeCheckM Expr mkFnExprForValue = \case IsParam -> pure $ mkVar g @@ -644,16 +614,12 @@ mkTraversal mn isBi te@TraversalExprs{..} getContraversalExprs (TraversalOps @_ lam f . applyWhen isBi (lam g) <$> mkCasesForTraversal mn handleValue extractExpr ctors deriveFunctor - :: forall m - . MonadError MultipleErrors m - => MonadState CheckState m - => MonadSupply m - => Maybe Bool -- does left parameter exist, and is it contravariant? + :: Maybe Bool -- does left parameter exist, and is it contravariant? -> Bool -- is the (right) parameter contravariant? -> PSString -- name of the map function for this functor type -> Qualified (ProperName 'ClassName) -> UnwrappedTypeConstructor - -> m [(PSString, Expr)] + -> TypeCheckM [(PSString, Expr)] deriveFunctor mbLParamIsContravariant paramIsContravariant mapName nm utc = do ctors <- validateParamsInTypeConstructors nm utc isBi functorClasses $ Just $ ContravarianceSupport { contravarianceWitness = () @@ -690,14 +656,10 @@ applyWhen :: forall a. Bool -> (a -> a) -> a -> a applyWhen cond f = if cond then f else identity deriveFoldable - :: forall m - . MonadError MultipleErrors m - => MonadState CheckState m - => MonadSupply m - => Bool -- is there a left parameter (are we deriving Bifoldable)? + :: Bool -- is there a left parameter (are we deriving Bifoldable)? -> Qualified (ProperName 'ClassName) -> UnwrappedTypeConstructor - -> m [(PSString, Expr)] + -> TypeCheckM [(PSString, Expr)] deriveFoldable isBi nm utc = do ctors <- validateParamsInTypeConstructors nm utc isBi foldableClasses Nothing foldlFun <- mkAsymmetricFoldFunction False foldlExprs ctors @@ -737,7 +699,7 @@ deriveFoldable isBi nm utc = do identityVar = mkRef Libs.I_identity memptyVar = mkRef Libs.I_mempty - mkAsymmetricFoldFunction :: Bool -> TraversalExprs -> [(ProperName 'ConstructorName, [Maybe (ParamUsage Void)])] -> m Expr + mkAsymmetricFoldFunction :: Bool -> TraversalExprs -> [(ProperName 'ConstructorName, [Maybe (ParamUsage Void)])] -> TypeCheckM Expr mkAsymmetricFoldFunction isRightFold te@TraversalExprs{..} ctors = do f <- freshIdent "f" g <- if isBi then freshIdent "g" else pure f @@ -746,13 +708,13 @@ deriveFoldable isBi nm utc = do appCombiner :: (Bool, Expr) -> Expr -> Expr -> Expr appCombiner (isFlipped, fn) = applyWhen (isFlipped == isRightFold) flip $ App . App fn - mkCombinerExpr :: ParamUsage Void -> m Expr + mkCombinerExpr :: ParamUsage Void -> TypeCheckM Expr mkCombinerExpr = fmap (uncurry $ \isFlipped -> applyWhen isFlipped $ App flipVar) . getCombiner - handleValue :: ParamUsage Void -> Expr -> Const [m (Expr -> Expr)] Expr + handleValue :: ParamUsage Void -> Expr -> Const [TypeCheckM (Expr -> Expr)] Expr handleValue = unnestRecords $ \usage inputExpr -> toConst $ flip appCombiner inputExpr <$> getCombiner usage - getCombiner :: ParamUsage Void -> m (Bool, Expr) + getCombiner :: ParamUsage Void -> TypeCheckM (Bool, Expr) getCombiner = \case IsParam -> pure (False, mkVar g) @@ -770,7 +732,7 @@ deriveFoldable isBi nm utc = do then flip extractExprStartingWith $ foldFieldsOf lVar else extractExprStartingWith lVar . foldFieldsOf - extractExprStartingWith :: Expr -> Const [m (Expr -> Expr)] Expr -> m Expr + extractExprStartingWith :: Expr -> Const [TypeCheckM (Expr -> Expr)] Expr -> TypeCheckM Expr extractExprStartingWith = consumeConst . if isRightFold then foldr ($) else foldl' (&) lam f . applyWhen isBi (lam g) . lam z <$> mkCasesForTraversal mn handleValue (extractExprStartingWith $ mkVar z) ctors @@ -787,14 +749,10 @@ foldMapOps = TraversalOps { visitExpr = toConst, .. } exprs -> foldr1 (App . App appendVar) exprs deriveTraversable - :: forall m - . MonadError MultipleErrors m - => MonadState CheckState m - => MonadSupply m - => Bool -- is there a left parameter (are we deriving Bitraversable)? + :: Bool -- is there a left parameter (are we deriving Bitraversable)? -> Qualified (ProperName 'ClassName) -> UnwrappedTypeConstructor - -> m [(PSString, Expr)] + -> TypeCheckM [(PSString, Expr)] deriveTraversable isBi nm utc = do ctors <- validateParamsInTypeConstructors nm utc isBi traversableClasses Nothing traverseFun <- mkTraversal (utcModuleName utc) isBi traverseExprs absurd traverseOps ctors @@ -815,19 +773,19 @@ deriveTraversable isBi nm utc = do bitraverseVar = mkRef Libs.I_bitraverse identityVar = mkRef Libs.I_identity -traverseOps :: forall m. MonadSupply m => TraversalOps m +traverseOps :: TraversalOps TypeCheckM traverseOps = TraversalOps { .. } where pureVar = mkRef Libs.I_pure mapVar = mkRef Libs.I_map applyVar = mkRef Libs.I_apply - visitExpr :: m Expr -> WriterT [(Ident, m Expr)] m Expr + visitExpr :: TypeCheckM Expr -> WriterT [(Ident, TypeCheckM Expr)] TypeCheckM Expr visitExpr traversedExpr = do ident <- freshIdent "v" tell [(ident, traversedExpr)] $> mkVar ident - extractExpr :: WriterT [(Ident, m Expr)] m Expr -> m Expr + extractExpr :: WriterT [(Ident, TypeCheckM Expr)] TypeCheckM Expr -> TypeCheckM Expr extractExpr = runWriterT >=> \(result, unzip -> (ctx, args)) -> flip mkApps (foldr lam result ctx) <$> sequenceA args mkApps :: [Expr] -> Expr -> Expr diff --git a/src/Language/PureScript/TypeChecker/Entailment.hs b/src/Language/PureScript/TypeChecker/Entailment.hs index 85bdfee4aa..7895e541b1 100644 --- a/src/Language/PureScript/TypeChecker/Entailment.hs +++ b/src/Language/PureScript/TypeChecker/Entailment.hs @@ -17,7 +17,6 @@ import Control.Arrow (second, (&&&)) import Control.Monad.Error.Class (MonadError(..)) import Control.Monad.State (MonadState(..), MonadTrans(..), StateT(..), evalStateT, execStateT, gets, modify) import Control.Monad (foldM, guard, join, zipWithM, zipWithM_, (<=<)) -import Control.Monad.Supply.Class (MonadSupply(..)) import Control.Monad.Writer (MonadWriter(..), WriterT(..)) import Data.Monoid (Any(..)) @@ -39,12 +38,12 @@ import Language.PureScript.AST (Binder(..), ErrorMessageHint(..), Expr(..), Lite import Language.PureScript.AST.Declarations (UnknownsHint(..)) import Language.PureScript.Crash (internalError) import Language.PureScript.Environment (Environment(..), FunctionalDependency(..), TypeClassData(..), dictTypeName, kindRow, tyBoolean, tyInt, tyString) -import Language.PureScript.Errors (MultipleErrors, SimpleErrorMessage(..), addHint, addHints, errorMessage, rethrow) +import Language.PureScript.Errors (SimpleErrorMessage(..), addHint, addHints, errorMessage, rethrow) import Language.PureScript.Names (pattern ByNullSourcePos, Ident(..), ModuleName, ProperName(..), ProperNameType(..), Qualified(..), QualifiedBy(..), byMaybeModuleName, coerceProperName, disqualify, freshIdent, getQual) import Language.PureScript.TypeChecker.Entailment.Coercible (GivenSolverState(..), WantedSolverState(..), initialGivenSolverState, initialWantedSolverState, insoluble, solveGivens, solveWanteds) import Language.PureScript.TypeChecker.Entailment.IntCompare (mkFacts, mkRelation, solveRelation) import Language.PureScript.TypeChecker.Kinds (elaborateKind, unifyKinds') -import Language.PureScript.TypeChecker.Monad (CheckState(..), withErrorMessageHint) +import Language.PureScript.TypeChecker.Monad (CheckState(..), withErrorMessageHint, TypeCheckM) import Language.PureScript.TypeChecker.Synonyms (replaceAllTypeSynonyms) import Language.PureScript.TypeChecker.Unify (freshTypeWithKind, substituteType, unifyTypes) import Language.PureScript.TypeClassDictionaries (NamedDict, TypeClassDictionaryInScope(..), superclassName) @@ -112,11 +111,10 @@ combineContexts = M.unionWith (M.unionWith (M.unionWith (<>))) -- | Replace type class dictionary placeholders with inferred type class dictionaries replaceTypeClassDictionaries - :: forall m - . (MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m, MonadSupply m) - => Bool + :: + Bool -> Expr - -> m (Expr, [(Ident, InstanceContext, SourceConstraint)]) + -> TypeCheckM (Expr, [(Ident, InstanceContext, SourceConstraint)]) replaceTypeClassDictionaries shouldGeneralize expr = flip evalStateT M.empty $ do -- Loop, deferring any unsolved constraints, until there are no more -- constraints which can be solved, then make a generalization pass. @@ -128,18 +126,18 @@ replaceTypeClassDictionaries shouldGeneralize expr = flip evalStateT M.empty $ d loop expr >>= generalizePass where -- This pass solves constraints where possible, deferring constraints if not. - deferPass :: Expr -> StateT InstanceContext m (Expr, Any) + deferPass :: Expr -> StateT InstanceContext TypeCheckM (Expr, Any) deferPass = fmap (second fst) . runWriterT . f where - f :: Expr -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext m) Expr + f :: Expr -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext TypeCheckM) Expr (_, f, _) = everywhereOnValuesTopDownM return (go True) return -- This pass generalizes any remaining constraints - generalizePass :: Expr -> StateT InstanceContext m (Expr, [(Ident, InstanceContext, SourceConstraint)]) + generalizePass :: Expr -> StateT InstanceContext TypeCheckM (Expr, [(Ident, InstanceContext, SourceConstraint)]) generalizePass = fmap (second snd) . runWriterT . f where - f :: Expr -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext m) Expr + f :: Expr -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext TypeCheckM) Expr (_, f, _) = everywhereOnValuesTopDownM return (go False) return - go :: Bool -> Expr -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext m) Expr + go :: Bool -> Expr -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext TypeCheckM) Expr go deferErrors (TypeClassDictionary constraint context hints) = rethrow (addHints hints) $ entails (SolverOptions shouldGeneralize deferErrors) constraint context hints go _ other = return other @@ -180,9 +178,8 @@ instance Monoid t => Monoid (Matched t) where -- | Check that the current set of type class dictionaries entail the specified type class goal, and, if so, -- return a type class dictionary reference. entails - :: forall m - . (MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m, MonadSupply m) - => SolverOptions + :: + SolverOptions -- ^ Solver options -> SourceConstraint -- ^ The constraint to solve @@ -190,11 +187,11 @@ entails -- ^ The contexts in which to solve the constraint -> [ErrorMessageHint] -- ^ Error message hints to apply to any instance errors - -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext m) Expr + -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext TypeCheckM) Expr entails SolverOptions{..} constraint context hints = overConstraintArgsAll (lift . lift . traverse replaceAllTypeSynonyms) constraint >>= solve where - forClassNameM :: Environment -> InstanceContext -> Qualified (ProperName 'ClassName) -> [SourceType] -> [SourceType] -> m [TypeClassDict] + forClassNameM :: Environment -> InstanceContext -> Qualified (ProperName 'ClassName) -> [SourceType] -> [SourceType] -> TypeCheckM [TypeClassDict] forClassNameM env ctx cn@C.Coercible kinds args = fromMaybe (forClassName env ctx cn kinds args) <$> solveCoercible env ctx kinds args @@ -234,10 +231,10 @@ entails SolverOptions{..} constraint context hints = valUndefined :: Expr valUndefined = Var nullSourceSpan C.I_undefined - solve :: SourceConstraint -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext m) Expr + solve :: SourceConstraint -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext TypeCheckM) Expr solve = go 0 hints where - go :: Int -> [ErrorMessageHint] -> SourceConstraint -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext m) Expr + go :: Int -> [ErrorMessageHint] -> SourceConstraint -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext TypeCheckM) Expr go work _ (Constraint _ className' _ tys' _) | work > 1000 = throwError . errorMessage $ PossiblyInfiniteInstance className' tys' go work hints' con@(Constraint _ className' kinds' tys' conInfo) = WriterT . StateT . (withErrorMessageHint (ErrorSolvingConstraint con) .) . runStateT . runWriterT $ do -- We might have unified types by solving other constraints, so we need to @@ -343,7 +340,7 @@ entails SolverOptions{..} constraint context hints = withFreshTypes :: TypeClassDict -> Matching SourceType - -> m (Matching SourceType) + -> TypeCheckM (Matching SourceType) withFreshTypes TypeClassDictionaryInScope{..} initSubst = do subst <- foldM withFreshType initSubst $ filter (flip M.notMember initSubst . fst) tcdForAll for_ (M.toList initSubst) $ unifySubstKind subst @@ -361,7 +358,7 @@ entails SolverOptions{..} constraint context hints = (substituteType currentSubst . replaceAllTypeVars (M.toList subst) $ instKind) (substituteType currentSubst tyKind) - unique :: [SourceType] -> [SourceType] -> [Qualified (Either SourceType Ident)] -> [(a, TypeClassDict)] -> UnknownsHint -> m (EntailsResult a) + unique :: [SourceType] -> [SourceType] -> [Qualified (Either SourceType Ident)] -> [(a, TypeClassDict)] -> UnknownsHint -> TypeCheckM (EntailsResult a) unique kindArgs tyArgs ambiguous [] unks | solverDeferErrors = return Deferred -- We need a special case for nullary type classes, since we want @@ -401,7 +398,7 @@ entails SolverOptions{..} constraint context hints = -- Create dictionaries for subgoals which still need to be solved by calling go recursively -- E.g. the goal (Show a, Show b) => Show (Either a b) can be satisfied if the current type -- unifies with Either a b, and we can satisfy the subgoals Show a and Show b recursively. - solveSubgoals :: Matching SourceType -> ErrorMessageHint -> Maybe [SourceConstraint] -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext m) (Maybe [Expr]) + solveSubgoals :: Matching SourceType -> ErrorMessageHint -> Maybe [SourceConstraint] -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext TypeCheckM) (Maybe [Expr]) solveSubgoals _ _ Nothing = return Nothing solveSubgoals subst hint (Just subgoals) = Just <$> traverse (rethrow (addHint hint) . go (work + 1) (hints' <> [hint]) . mapConstraintArgsAll (map (replaceAllTypeVars (M.toList subst)))) subgoals @@ -412,7 +409,7 @@ entails SolverOptions{..} constraint context hints = useEmptyDict args = Unused (foldl (App . Abs (VarBinder nullSourceSpan UnusedIdent)) valUndefined (fold args)) -- Make a dictionary from subgoal dictionaries by applying the correct function - mkDictionary :: Evidence -> Maybe [Expr] -> m Expr + mkDictionary :: Evidence -> Maybe [Expr] -> TypeCheckM Expr mkDictionary (NamedInstance n) args = return $ foldl App (Var nullSourceSpan n) (fold args) mkDictionary EmptyClassInstance args = return (useEmptyDict args) mkDictionary (WarnInstance msg) args = do @@ -470,7 +467,7 @@ entails SolverOptions{..} constraint context hints = subclassDictionaryValue dict className index = App (Accessor (mkString (superclassName className index)) dict) valUndefined - solveCoercible :: Environment -> InstanceContext -> [SourceType] -> [SourceType] -> m (Maybe [TypeClassDict]) + solveCoercible :: Environment -> InstanceContext -> [SourceType] -> [SourceType] -> TypeCheckM (Maybe [TypeClassDict]) solveCoercible env ctx kinds [a, b] = do let coercibleDictsInScope = findDicts ctx C.Coercible ByNullSourcePos givens = flip mapMaybe coercibleDictsInScope $ \case diff --git a/src/Language/PureScript/TypeChecker/Entailment/Coercible.hs b/src/Language/PureScript/TypeChecker/Entailment/Coercible.hs index 8abaac31ca..18826f3a40 100644 --- a/src/Language/PureScript/TypeChecker/Entailment/Coercible.hs +++ b/src/Language/PureScript/TypeChecker/Entailment/Coercible.hs @@ -18,12 +18,12 @@ import Prelude hiding (interact) import Control.Applicative ((<|>), empty) import Control.Arrow ((&&&)) import Control.Monad ((<=<), guard, unless, when) -import Control.Monad.Error.Class (MonadError, catchError, throwError) -import Control.Monad.State (MonadState, StateT, get, gets, modify, put) +import Control.Monad.Error.Class (catchError, throwError) +import Control.Monad.State (StateT, get, gets, modify, put) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT) import Control.Monad.Trans.Except (ExceptT(..), runExceptT) -import Control.Monad.Writer.Strict (MonadWriter, Writer, execWriter, runWriter, runWriterT, tell) +import Control.Monad.Writer (Writer, execWriter, runWriter, runWriterT, tell, WriterT) import Data.Either (partitionEithers) import Data.Foldable (fold, foldl', for_, toList) import Data.Functor (($>)) @@ -40,7 +40,7 @@ import Language.PureScript.Environment (DataDeclType(..), Environment(..), TypeK import Language.PureScript.Errors (DeclarationRef(..), ErrorMessageHint(..), ExportSource, ImportDeclarationType(..), MultipleErrors, SimpleErrorMessage(..), SourceAnn, errorMessage, UnknownsHint(..)) import Language.PureScript.Names (ModuleName, ProperName, ProperNameType(..), Qualified(..), byMaybeModuleName, toMaybeModuleName) import Language.PureScript.TypeChecker.Kinds (elaborateKind, freshKindWithKind, unifyKinds') -import Language.PureScript.TypeChecker.Monad (CheckState(..)) +import Language.PureScript.TypeChecker.Monad (CheckState(..), TypeCheckM) import Language.PureScript.TypeChecker.Roles (lookupRoles) import Language.PureScript.TypeChecker.Synonyms (replaceAllTypeSynonyms) import Language.PureScript.TypeChecker.Unify (alignRowsWith, freshTypeWithKind, substituteType) @@ -118,10 +118,8 @@ initialGivenSolverState = -- 3c. Otherwise canonicalization can succeed with derived constraints which we -- add to the unsolved queue and then go back to 1. solveGivens - :: MonadError MultipleErrors m - => MonadState CheckState m - => Environment - -> StateT GivenSolverState m () + :: Environment + -> StateT GivenSolverState TypeCheckM () solveGivens env = go (0 :: Int) where go n = do when (n > 1000) . throwError . errorMessage $ PossiblyInfiniteCoercibleInstance @@ -206,18 +204,15 @@ initialWantedSolverState givens a b = -- interact the latter with the former, we would report an insoluble -- @Coercible Boolean Char@. solveWanteds - :: MonadError MultipleErrors m - => MonadWriter [ErrorMessageHint] m - => MonadState CheckState m - => Environment - -> StateT WantedSolverState m () + :: Environment + -> StateT WantedSolverState CanonM () solveWanteds env = go (0 :: Int) where go n = do when (n > 1000) . throwError . errorMessage $ PossiblyInfiniteCoercibleInstance gets unsolvedWanteds >>= \case [] -> pure () wanted : unsolved -> do - (k, a, b) <- lift $ unify wanted + (k, a, b) <- lift $ lift $ unify wanted WantedSolverState{..} <- get lift (canon env (Just inertGivens) k a b `catchError` recover (a, b) inertGivens) >>= \case Irreducible -> case interact env (a, b) inertGivens of @@ -271,10 +266,8 @@ solveWanteds env = go (0 :: Int) where -- @Coercible (D \@k) (D \@k)@ constraint which could be trivially solved by -- reflexivity instead of having to saturate the type constructors. unify - :: MonadError MultipleErrors m - => MonadState CheckState m - => (SourceType, SourceType) - -> m (SourceType, SourceType, SourceType) + :: (SourceType, SourceType) + -> TypeCheckM (SourceType, SourceType, SourceType) unify (a, b) = do let kindOf = sequence . (id &&& elaborateKind) <=< replaceAllTypeSynonyms (a', kind) <- kindOf a @@ -475,18 +468,17 @@ data Canonicalized -- necessarily an error, we may make further progress by interacting with -- inerts. +type CanonM = WriterT [ErrorMessageHint] TypeCheckM + -- | Canonicalization takes a wanted constraint and try to reduce it to a set of -- simpler constraints whose satisfaction will imply the goal. canon - :: MonadError MultipleErrors m - => MonadWriter [ErrorMessageHint] m - => MonadState CheckState m - => Environment + :: Environment -> Maybe [(SourceType, SourceType, SourceType)] -> SourceType -> SourceType -> SourceType - -> m Canonicalized + -> CanonM Canonicalized canon env givens k a b = maybe (throwError $ insoluble k a b) pure <=< runMaybeT $ canonRefl a b @@ -538,10 +530,9 @@ insoluble k a b = -- are the same. Since we currently don't support higher-rank arguments in -- instance heads, term equality is a sufficient notion of "the same". canonRefl - :: Monad m - => SourceType + :: SourceType -> SourceType - -> MaybeT m Canonicalized + -> MaybeT CanonM Canonicalized canonRefl a b = guard (a == b) $> Canonicalized mempty @@ -550,12 +541,10 @@ canonRefl a b = -- @Coercible (T1 a_0 .. a_n c_0 .. c_m) (T2 b_0 .. b_n c_0 .. c_m)@, where both -- arguments are fully saturated with the same unknowns and have kind @Type@. canonUnsaturatedHigherKindedType - :: MonadError MultipleErrors m - => MonadState CheckState m - => Environment + :: Environment -> SourceType -> SourceType - -> MaybeT m Canonicalized + -> MaybeT CanonM Canonicalized canonUnsaturatedHigherKindedType env a b | (TypeConstructor _ aTyName, akapps, axs) <- unapplyTypes a , (ak, _) <- fromMaybe (internalError "canonUnsaturatedHigherKindedType: type lookup failed") $ M.lookup aTyName (types env) @@ -564,10 +553,10 @@ canonUnsaturatedHigherKindedType env a b ak' <- lift $ do let (kvs, ak') = fromMaybe (internalError "canonUnsaturatedHigherKindedType: unkinded forall binder") $ completeBinderList ak instantiatedKinds = zipWith (\(_, (kv, _)) k -> (kv, k)) kvs akapps - unknownKinds <- traverse (\((ss, _), (kv, k)) -> (kv,) <$> freshKindWithKind ss k) $ drop (length akapps) kvs + unknownKinds <- traverse (\((ss, _), (kv, k)) -> (kv,) <$> lift (freshKindWithKind ss k)) $ drop (length akapps) kvs pure $ replaceAllTypeVars (instantiatedKinds <> unknownKinds) ak' let (aks', _) = unapplyKinds ak' - tys <- traverse freshTypeWithKind $ drop (length axs) aks' + tys <- traverse (lift . lift . freshTypeWithKind) $ drop (length axs) aks' let a' = foldl' srcTypeApp a tys b' = foldl' srcTypeApp b tys pure . Canonicalized $ S.singleton (a', b') @@ -578,11 +567,9 @@ canonUnsaturatedHigherKindedType env a b -- yield a constraint @Coercible r s@ and constraints on the types for each -- label in both rows. Labels exclusive to one row yield a failure. canonRow - :: MonadError MultipleErrors m - => MonadState CheckState m - => SourceType + :: SourceType -> SourceType - -> MaybeT m Canonicalized + -> MaybeT CanonM Canonicalized canonRow a b | RCons{} <- a = case alignRowsWith (const (,)) a b of @@ -591,10 +578,10 @@ canonRow a b -- and the unification error thrown when the rows are misaligned should -- not mention unknowns. (_, (([], u@TUnknown{}), rl2)) -> do - k <- elaborateKind u + k <- lift $ lift $ elaborateKind u throwError $ insoluble k u (rowFromList rl2) (_, (rl1, ([], u@TUnknown{}))) -> do - k <- elaborateKind u + k <- lift $ lift $ elaborateKind u throwError $ insoluble k (rowFromList rl1) u (deriveds, (([], tail1), ([], tail2))) -> do pure . Canonicalized . S.fromList $ (tail1, tail2) : deriveds @@ -628,11 +615,9 @@ data UnwrapNewtypeError -- | Unwraps a newtype and yields its underlying type with the newtype arguments -- substituted in (e.g. @N[D/a] = D@ given @newtype N a = N a@ and @data D = D@). unwrapNewtype - :: MonadState CheckState m - => MonadWriter [ErrorMessageHint] m - => Environment + :: Environment -> SourceType - -> m (Either UnwrapNewtypeError SourceType) + -> CanonM (Either UnwrapNewtypeError SourceType) unwrapNewtype env = go (0 :: Int) where go n ty = runExceptT $ do when (n > 1000) $ throwError CannotUnwrapInfiniteNewtypeChain @@ -712,14 +697,12 @@ lookupNewtypeConstructorInScope env currentModuleName currentModuleImports quali -- | Constraints of the form @Coercible (N a_0 .. a_n) b@ yield a constraint -- @Coercible a b@ if unwrapping the newtype yields @a@. canonNewtypeLeft - :: MonadState CheckState m - => MonadWriter [ErrorMessageHint] m - => Environment + :: Environment -> SourceType -> SourceType - -> MaybeT m Canonicalized + -> MaybeT CanonM Canonicalized canonNewtypeLeft env a b = - unwrapNewtype env a >>= \case + lift (unwrapNewtype env a) >>= \case Left CannotUnwrapInfiniteNewtypeChain -> empty Left CannotUnwrapConstructor -> empty Right a' -> pure . Canonicalized $ S.singleton (a', b) @@ -727,12 +710,10 @@ canonNewtypeLeft env a b = -- | Constraints of the form @Coercible a (N b_0 .. b_n)@ yield a constraint -- @Coercible a b@ if unwrapping the newtype yields @b@. canonNewtypeRight - :: MonadState CheckState m - => MonadWriter [ErrorMessageHint] m - => Environment + :: Environment -> SourceType -> SourceType - -> MaybeT m Canonicalized + -> MaybeT CanonM Canonicalized canonNewtypeRight env = flip $ canonNewtypeLeft env @@ -750,12 +731,11 @@ canonNewtypeRight env = -- We can decompose @Coercible (D a b d) (D a c e)@ into @Coercible b c@, but -- decomposing @Coercible (D a c d) (D b c d)@ would fail. decompose - :: MonadError MultipleErrors m - => Environment + :: Environment -> Qualified (ProperName 'TypeName) -> [SourceType] -> [SourceType] - -> m Canonicalized + -> TypeCheckM Canonicalized decompose env tyName axs bxs = do let roles = lookupRoles env tyName f role ax bx = case role of @@ -779,29 +759,27 @@ decompose env tyName axs bxs = do -- | Constraints of the form @Coercible (D a_0 .. a_n) (D b_0 .. b_n)@, where -- @D@ is not a newtype, yield constraints on their arguments. canonDecomposition - :: MonadError MultipleErrors m - => Environment + :: Environment -> SourceType -> SourceType - -> MaybeT m Canonicalized + -> MaybeT CanonM Canonicalized canonDecomposition env a b | (TypeConstructor _ aTyName, _, axs) <- unapplyTypes a , (TypeConstructor _ bTyName, _, bxs) <- unapplyTypes b , aTyName == bTyName , Nothing <- lookupNewtypeConstructor env aTyName [] = - decompose env aTyName axs bxs + lift $ lift $ decompose env aTyName axs bxs | otherwise = empty -- | Constraints of the form @Coercible (D1 a_0 .. a_n) (D2 b_0 .. b_n)@, where -- @D1@ and @D2@ are different type constructors and neither of them are -- newtypes, are insoluble. canonDecompositionFailure - :: MonadError MultipleErrors m - => Environment + :: Environment -> SourceType -> SourceType -> SourceType - -> MaybeT m Canonicalized + -> MaybeT CanonM Canonicalized canonDecompositionFailure env k a b | (TypeConstructor _ aTyName, _, _) <- unapplyTypes a , (TypeConstructor _ bTyName, _, _) <- unapplyTypes b @@ -845,12 +823,11 @@ canonDecompositionFailure env k a b -- @Coercible (Const a a) (Const a b)@ to @Coercible a b@ we would not be able -- to discharge it with the given. canonNewtypeDecomposition - :: MonadError MultipleErrors m - => Environment + :: Environment -> Maybe [(SourceType, SourceType, SourceType)] -> SourceType -> SourceType - -> MaybeT m Canonicalized + -> MaybeT CanonM Canonicalized canonNewtypeDecomposition env (Just givens) a b | (TypeConstructor _ aTyName, _, axs) <- unapplyTypes a , (TypeConstructor _ bTyName, _, bxs) <- unapplyTypes b @@ -858,17 +835,16 @@ canonNewtypeDecomposition env (Just givens) a b , Just _ <- lookupNewtypeConstructor env aTyName [] = do let givensCanDischarge = any (\given -> canDischarge given (a, b)) givens guard $ not givensCanDischarge - decompose env aTyName axs bxs + lift $ lift $ decompose env aTyName axs bxs canonNewtypeDecomposition _ _ _ _ = empty -- | Constraints of the form @Coercible (N1 a_0 .. a_n) (N2 b_0 .. b_n)@, where -- @N1@ and @N2@ are different type constructors and either of them is a -- newtype whose constructor is out of scope, are irreducible. canonNewtypeDecompositionFailure - :: Monad m - => SourceType + :: SourceType -> SourceType - -> MaybeT m Canonicalized + -> MaybeT CanonM Canonicalized canonNewtypeDecompositionFailure a b | (TypeConstructor{}, _, _) <- unapplyTypes a , (TypeConstructor{}, _, _) <- unapplyTypes b @@ -890,10 +866,9 @@ canonNewtypeDecompositionFailure a b -- repeatedly kick each other out the inert set whereas reordering the latter to -- @Coercible a b@ makes it redundant and let us discharge it. canonTypeVars - :: Monad m - => SourceType + :: SourceType -> SourceType - -> MaybeT m Canonicalized + -> MaybeT CanonM Canonicalized canonTypeVars a b | Skolem _ tv1 _ _ _ <- a , Skolem _ tv2 _ _ _ <- b @@ -905,10 +880,9 @@ canonTypeVars a b -- | Constraints of the form @Coercible tv ty@ are irreducibles. canonTypeVarLeft - :: Monad m - => SourceType + :: SourceType -> SourceType - -> MaybeT m Canonicalized + -> MaybeT CanonM Canonicalized canonTypeVarLeft a _ | Skolem{} <- a = pure Irreducible | otherwise = empty @@ -917,30 +891,27 @@ canonTypeVarLeft a _ -- @Coercible tv ty@ to satisfy the canonicality requirement of having the type -- variable on the left. canonTypeVarRight - :: Monad m - => SourceType + :: SourceType -> SourceType - -> MaybeT m Canonicalized + -> MaybeT CanonM Canonicalized canonTypeVarRight a b | Skolem{} <- b = pure . Canonicalized $ S.singleton (b, a) | otherwise = empty -- | Constraints of the form @Coercible (f a_0 .. a_n) b@ are irreducibles. canonApplicationLeft - :: Monad m - => SourceType + :: SourceType -> SourceType - -> MaybeT m Canonicalized + -> MaybeT CanonM Canonicalized canonApplicationLeft a _ | TypeApp{} <- a = pure Irreducible | otherwise = empty -- | Constraints of the form @Coercible a (f b_0 .. b_n) b@ are irreducibles. canonApplicationRight - :: Monad m - => SourceType + :: SourceType -> SourceType - -> MaybeT m Canonicalized + -> MaybeT CanonM Canonicalized canonApplicationRight _ b | TypeApp{} <- b = pure Irreducible | otherwise = empty diff --git a/src/Language/PureScript/TypeChecker/Kinds.hs b/src/Language/PureScript/TypeChecker/Kinds.hs index 5be87c0057..bc1d8f329d 100644 --- a/src/Language/PureScript/TypeChecker/Kinds.hs +++ b/src/Language/PureScript/TypeChecker/Kinds.hs @@ -31,7 +31,7 @@ import Control.Arrow ((***)) import Control.Lens ((^.), _1, _2, _3) import Control.Monad (join, unless, void, when, (<=<)) import Control.Monad.Error.Class (MonadError(..)) -import Control.Monad.State (MonadState, gets, modify) +import Control.Monad.State (gets, modify) import Control.Monad.Supply.Class (MonadSupply(..)) import Data.Bifunctor (first, second) @@ -42,6 +42,7 @@ import Data.Functor (($>)) import Data.IntSet qualified as IS import Data.List (nubBy, sortOn, (\\)) import Data.Map qualified as M +import Data.IntMap.Lazy qualified as IM import Data.Maybe (fromJust, fromMaybe) import Data.Text (Text) import Data.Text qualified as T @@ -51,7 +52,7 @@ import Language.PureScript.Crash (HasCallStack, internalError) import Language.PureScript.Environment qualified as E import Language.PureScript.Errors import Language.PureScript.Names (pattern ByNullSourcePos, ModuleName, Name(..), ProperName(..), ProperNameType(..), Qualified(..), QualifiedBy(..), coerceProperName, mkQualified) -import Language.PureScript.TypeChecker.Monad (CheckState(..), Substitution(..), UnkLevel(..), Unknown, bindLocalTypeVariables, debugType, getEnv, lookupTypeVariable, unsafeCheckCurrentModule, withErrorMessageHint, withFreshSubstitution) +import Language.PureScript.TypeChecker.Monad (CheckState(..), Substitution(..), UnkLevel(..), Unknown, bindLocalTypeVariables, debugType, getEnv, lookupTypeVariable, unsafeCheckCurrentModule, withErrorMessageHint, withFreshSubstitution, TypeCheckM) import Language.PureScript.TypeChecker.Skolems (newSkolemConstant, newSkolemScope, skolemize) import Language.PureScript.TypeChecker.Synonyms (replaceAllTypeSynonyms) import Language.PureScript.Types @@ -87,65 +88,65 @@ unknownVarNames used unks = vars :: [Text] vars = fmap (("k" <>) . T.pack . show) ([1..] :: [Int]) -apply :: (MonadState CheckState m) => SourceType -> m SourceType +apply :: SourceType -> TypeCheckM SourceType apply ty = flip substituteType ty <$> gets checkSubstitution substituteType :: Substitution -> SourceType -> SourceType substituteType sub = everywhereOnTypes $ \case TUnknown ann u -> - case M.lookup u (substType sub) of + case IM.lookup u (substType sub) of Nothing -> TUnknown ann u Just (TUnknown ann' u1) | u1 == u -> TUnknown ann' u1 Just t -> substituteType sub t other -> other -freshUnknown :: (MonadState CheckState m) => m Unknown +freshUnknown :: TypeCheckM Unknown freshUnknown = do k <- gets checkNextType modify $ \st -> st { checkNextType = k + 1 } pure k -freshKind :: (MonadState CheckState m) => SourceSpan -> m SourceType +freshKind :: SourceSpan -> TypeCheckM SourceType freshKind ss = freshKindWithKind ss E.kindType -freshKindWithKind :: (MonadState CheckState m) => SourceSpan -> SourceType -> m SourceType +freshKindWithKind :: SourceSpan -> SourceType -> TypeCheckM SourceType freshKindWithKind ss kind = do u <- freshUnknown addUnsolved Nothing u kind pure $ TUnknown (ss, []) u -addUnsolved :: (MonadState CheckState m) => Maybe UnkLevel -> Unknown -> SourceType -> m () +addUnsolved :: Maybe UnkLevel -> Unknown -> SourceType -> TypeCheckM () addUnsolved lvl unk kind = modify $ \st -> do let newLvl = UnkLevel $ case lvl of Nothing -> pure unk Just (UnkLevel lvl') -> lvl' <> pure unk subs = checkSubstitution st - uns = M.insert unk (newLvl, kind) $ substUnsolved subs + uns = IM.insert unk (newLvl, kind) $ substUnsolved subs st { checkSubstitution = subs { substUnsolved = uns } } -solve :: (MonadState CheckState m) => Unknown -> SourceType -> m () +solve :: Unknown -> SourceType -> TypeCheckM () solve unk solution = modify $ \st -> do let subs = checkSubstitution st - tys = M.insert unk solution $ substType subs + tys = IM.insert unk solution $ substType subs st { checkSubstitution = subs { substType = tys } } lookupUnsolved - :: (MonadState CheckState m, MonadError MultipleErrors m, HasCallStack) + :: HasCallStack => Unknown - -> m (UnkLevel, SourceType) + -> TypeCheckM (UnkLevel, SourceType) lookupUnsolved u = do uns <- gets (substUnsolved . checkSubstitution) - case M.lookup u uns of + case IM.lookup u uns of Nothing -> internalCompilerError $ "Unsolved unification variable ?" <> T.pack (show u) <> " is not bound" Just res -> return res unknownsWithKinds - :: forall m. (MonadState CheckState m, MonadError MultipleErrors m, HasCallStack) + :: HasCallStack => [Unknown] - -> m [(Unknown, SourceType)] + -> TypeCheckM [(Unknown, SourceType)] unknownsWithKinds = fmap (fmap snd . nubBy ((==) `on` fst) . sortOn fst . join) . traverse go where go u = do @@ -154,9 +155,9 @@ unknownsWithKinds = fmap (fmap snd . nubBy ((==) `on` fst) . sortOn fst . join) pure $ (lvl, (u, ty)) : rest inferKind - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => SourceType - -> m (SourceType, SourceType) + -> TypeCheckM (SourceType, SourceType) inferKind = \tyToInfer -> withErrorMessageHint (ErrorInferringKind tyToInfer) . rethrowWithPosition (fst $ getAnnForType tyToInfer) @@ -242,11 +243,11 @@ inferKind = \tyToInfer -> internalError $ "inferKind: Unimplemented case \n" <> prettyPrintType 100 ty inferAppKind - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => SourceAnn -> (SourceType, SourceType) -> SourceType - -> m (SourceType, SourceType) + -> TypeCheckM (SourceType, SourceType) inferAppKind ann (fn, fnKind) arg = case fnKind of TypeApp _ (TypeApp _ arrKind argKind) resKind | eqType arrKind E.tyFunction -> do expandSynonyms <- requiresSynonymsToExpand fn @@ -275,20 +276,20 @@ inferAppKind ann (fn, fnKind) arg = case fnKind of _ -> pure True cannotApplyTypeToType - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => SourceType -> SourceType - -> m a + -> TypeCheckM a cannotApplyTypeToType fn arg = do argKind <- snd <$> inferKind arg _ <- checkKind fn . srcTypeApp (srcTypeApp E.tyFunction argKind) =<< freshKind nullSourceSpan internalCompilerError . T.pack $ "Cannot apply type to type: " <> debugType (srcTypeApp fn arg) cannotApplyKindToType - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => SourceType -> SourceType - -> m a + -> TypeCheckM a cannotApplyKindToType poly arg = do let ann = getAnnForType arg argKind <- snd <$> inferKind arg @@ -296,10 +297,10 @@ cannotApplyKindToType poly arg = do internalCompilerError . T.pack $ "Cannot apply kind to type: " <> debugType (srcKindApp poly arg) checkKind - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => SourceType -> SourceType - -> m SourceType + -> TypeCheckM SourceType checkKind = checkKind' False -- | `checkIsSaturatedType t` is identical to `checkKind t E.kindType` except @@ -310,17 +311,17 @@ checkKind = checkKind' False -- error. -- checkIsSaturatedType - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => SourceType - -> m SourceType + -> TypeCheckM SourceType checkIsSaturatedType ty = checkKind' True ty E.kindType checkKind' - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => Bool -> SourceType -> SourceType - -> m SourceType + -> TypeCheckM SourceType checkKind' requireSynonymsToExpand ty kind2 = do withErrorMessageHint (ErrorCheckingKind ty kind2) . rethrowWithPosition (fst $ getAnnForType ty) $ do @@ -331,10 +332,10 @@ checkKind' requireSynonymsToExpand ty kind2 = do instantiateKind (ty', kind1') kind2' instantiateKind - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => (SourceType, SourceType) -> SourceType - -> m SourceType + -> TypeCheckM SourceType instantiateKind (ty, kind1) kind2 = case kind1 of ForAll _ _ a (Just k) t _ | shouldInstantiate kind2 -> do let ann = getAnnForType ty @@ -349,10 +350,10 @@ instantiateKind (ty, kind1) kind2 = case kind1 of _ -> False subsumesKind - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => SourceType -> SourceType - -> m () + -> TypeCheckM () subsumesKind = go where go = curry $ \case @@ -380,10 +381,9 @@ subsumesKind = go unifyKinds a b unifyKinds - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) - => SourceType + :: SourceType -> SourceType - -> m () + -> TypeCheckM () unifyKinds = unifyKindsWithFailure $ \w1 w2 -> throwError . errorMessage''' (fst . getAnnForType <$> [w1, w2]) @@ -393,10 +393,10 @@ unifyKinds = unifyKindsWithFailure $ \w1 w2 -> -- | local position context. This is useful when invoking kind unification -- | outside of kind checker internals. unifyKinds' - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => SourceType -> SourceType - -> m () + -> TypeCheckM () unifyKinds' = unifyKindsWithFailure $ \w1 w2 -> throwError . errorMessage @@ -404,19 +404,19 @@ unifyKinds' = unifyKindsWithFailure $ \w1 w2 -> -- | Check the kind of a type, failing if it is not of kind *. checkTypeKind - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => SourceType -> SourceType - -> m () + -> TypeCheckM () checkTypeKind ty kind = unifyKindsWithFailure (\_ _ -> throwError . errorMessage $ ExpectedType ty kind) kind E.kindType unifyKindsWithFailure - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) - => (SourceType -> SourceType -> m ()) + :: HasCallStack + => (SourceType -> SourceType -> TypeCheckM ()) -> SourceType -> SourceType - -> m () + -> TypeCheckM () unifyKindsWithFailure onFailure = go where goWithLabel l t1 t2 = withErrorMessageHint (ErrorInRowLabel l) $ go t1 t2 @@ -464,10 +464,10 @@ unifyKindsWithFailure onFailure = go onFailure (rowFromList w1) (rowFromList w2) solveUnknown - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => Unknown -> SourceType - -> m () + -> TypeCheckM () solveUnknown a' p1 = do p2 <- promoteKind a' p1 w1 <- snd <$> lookupUnsolved a' @@ -475,10 +475,10 @@ solveUnknown a' p1 = do solve a' p2 solveUnknownAsFunction - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => SourceAnn -> Unknown - -> m SourceType + -> TypeCheckM SourceType solveUnknownAsFunction ann u = do lvl <- fst <$> lookupUnsolved u u1 <- freshUnknown @@ -490,10 +490,10 @@ solveUnknownAsFunction ann u = do pure uarr promoteKind - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => Unknown -> SourceType - -> m SourceType + -> TypeCheckM SourceType promoteKind u2 ty = do lvl2 <- fst <$> lookupUnsolved u2 flip everywhereOnTypesM ty $ \case @@ -512,9 +512,9 @@ promoteKind u2 ty = do pure ty' elaborateKind - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => SourceType - -> m SourceType + -> TypeCheckM SourceType elaborateKind = \case TypeLevelString ann _ -> pure $ E.kindSymbol $> ann @@ -572,7 +572,7 @@ elaborateKind = \case ty -> throwError . errorMessage' (fst (getAnnForType ty)) $ UnsupportedTypeInKind ty -checkEscapedSkolems :: MonadError MultipleErrors m => SourceType -> m () +checkEscapedSkolems :: SourceType -> TypeCheckM () checkEscapedSkolems ty = traverse_ (throwError . toSkolemError) . everythingWithContextOnTypes ty [] (<>) go @@ -588,9 +588,9 @@ checkEscapedSkolems ty = errorMessage' (fst $ getAnnForType ty') $ EscapedSkolem name (Just ss) ty' kindOfWithUnknowns - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => SourceType - -> m (([(Unknown, SourceType)], SourceType), SourceType) + -> TypeCheckM (([(Unknown, SourceType)], SourceType), SourceType) kindOfWithUnknowns ty = do (ty', kind) <- kindOf ty unks <- unknownsWithKinds . IS.toList $ unknowns ty' @@ -598,16 +598,16 @@ kindOfWithUnknowns ty = do -- | Infer the kind of a single type kindOf - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => SourceType - -> m (SourceType, SourceType) + -> TypeCheckM (SourceType, SourceType) kindOf = fmap (first snd) . kindOfWithScopedVars -- | Infer the kind of a single type, returning the kinds of any scoped type variables kindOfWithScopedVars - :: (MonadError MultipleErrors m, MonadState CheckState m, HasCallStack) + :: HasCallStack => SourceType - -> m (([(Text, SourceType)], SourceType), SourceType) + -> TypeCheckM (([(Text, SourceType)], SourceType), SourceType) kindOfWithScopedVars ty = do (ty', kind) <- bitraverse apply (replaceAllTypeSynonyms <=< apply) =<< inferKind ty let binders = fst . fromJust $ completeBinderList ty' @@ -628,18 +628,18 @@ type DataDeclarationResult = ) kindOfData - :: forall m. (MonadError MultipleErrors m, MonadState CheckState m) - => ModuleName + :: + ModuleName -> DataDeclarationArgs - -> m DataDeclarationResult + -> TypeCheckM DataDeclarationResult kindOfData moduleName dataDecl = head . (^. _2) <$> kindsOfAll moduleName [] [dataDecl] [] inferDataDeclaration - :: forall m. (MonadError MultipleErrors m, MonadState CheckState m) - => ModuleName + :: + ModuleName -> DataDeclarationArgs - -> m [(DataConstructorDeclaration, SourceType)] + -> TypeCheckM [(DataConstructorDeclaration, SourceType)] inferDataDeclaration moduleName (ann, tyName, tyArgs, ctors) = do tyKind <- apply =<< lookupTypeVariable moduleName (Qualified ByNullSourcePos tyName) let (sigBinders, tyKind') = fromJust . completeBinderList $ tyKind @@ -656,10 +656,10 @@ inferDataDeclaration moduleName (ann, tyName, tyArgs, ctors) = do fmap (fmap (addVisibility visibility . mkForAll ctorBinders)) . inferDataConstructor tyCtor' inferDataConstructor - :: forall m. (MonadError MultipleErrors m, MonadState CheckState m) - => SourceType + :: + SourceType -> DataConstructorDeclaration - -> m (DataConstructorDeclaration, SourceType) + -> TypeCheckM (DataConstructorDeclaration, SourceType) inferDataConstructor tyCtor DataConstructorDeclaration{..} = do dataCtorFields' <- traverse (traverse checkIsSaturatedType) dataCtorFields dataCtor <- flip (foldr ((E.-:>) . snd)) dataCtorFields' <$> checkKind tyCtor E.kindType @@ -680,18 +680,18 @@ type TypeDeclarationResult = ) kindOfTypeSynonym - :: forall m. (MonadError MultipleErrors m, MonadState CheckState m) - => ModuleName + :: + ModuleName -> TypeDeclarationArgs - -> m TypeDeclarationResult + -> TypeCheckM TypeDeclarationResult kindOfTypeSynonym moduleName typeDecl = head . (^. _1) <$> kindsOfAll moduleName [typeDecl] [] [] inferTypeSynonym - :: forall m. (MonadError MultipleErrors m, MonadState CheckState m) - => ModuleName + :: + ModuleName -> TypeDeclarationArgs - -> m SourceType + -> TypeCheckM SourceType inferTypeSynonym moduleName (ann, tyName, tyArgs, tyBody) = do tyKind <- apply =<< lookupTypeVariable moduleName (Qualified ByNullSourcePos tyName) let (sigBinders, tyKind') = fromJust . completeBinderList $ tyKind @@ -710,9 +710,9 @@ inferTypeSynonym moduleName (ann, tyName, tyArgs, tyBody) = do -- | ill-scoped. We require that users explicitly generalize this kind -- | in such a case. checkQuantification - :: forall m. (MonadError MultipleErrors m) - => SourceType - -> m () + :: + SourceType + -> TypeCheckM () checkQuantification = collectErrors . go [] [] . fst . fromJust . completeBinderList where @@ -737,9 +737,9 @@ checkQuantification = elem karg $ freeTypeVariables k checkVisibleTypeQuantification - :: forall m. (MonadError MultipleErrors m) - => SourceType - -> m () + :: + SourceType + -> TypeCheckM () checkVisibleTypeQuantification = collectErrors . freeTypeVariables where @@ -754,9 +754,9 @@ checkVisibleTypeQuantification = -- | implicitly generalize unknowns, such as on the right-hand-side of -- | a type synonym, or in arguments to data constructors. checkTypeQuantification - :: forall m. (MonadError MultipleErrors m) - => SourceType - -> m () + :: + SourceType + -> TypeCheckM () checkTypeQuantification = collectErrors . everythingWithContextOnTypes True [] (<>) unknownsInKinds where @@ -797,18 +797,18 @@ type ClassDeclarationResult = ) kindOfClass - :: forall m. (MonadError MultipleErrors m, MonadState CheckState m) - => ModuleName + :: + ModuleName -> ClassDeclarationArgs - -> m ClassDeclarationResult + -> TypeCheckM ClassDeclarationResult kindOfClass moduleName clsDecl = head . (^. _3) <$> kindsOfAll moduleName [] [] [clsDecl] inferClassDeclaration - :: forall m. (MonadError MultipleErrors m, MonadState CheckState m) - => ModuleName + :: + ModuleName -> ClassDeclarationArgs - -> m ([(Text, SourceType)], [SourceConstraint], [Declaration]) + -> TypeCheckM ([(Text, SourceType)], [SourceConstraint], [Declaration]) inferClassDeclaration moduleName (ann, clsName, clsArgs, superClasses, decls) = do clsKind <- apply =<< lookupTypeVariable moduleName (Qualified ByNullSourcePos $ coerceProperName clsName) let (sigBinders, clsKind') = fromJust . completeBinderList $ clsKind @@ -821,18 +821,18 @@ inferClassDeclaration moduleName (ann, clsName, clsArgs, superClasses, decls) = <*> for decls checkClassMemberDeclaration checkClassMemberDeclaration - :: forall m. (MonadError MultipleErrors m, MonadState CheckState m) - => Declaration - -> m Declaration + :: + Declaration + -> TypeCheckM Declaration checkClassMemberDeclaration = \case TypeDeclaration (TypeDeclarationData ann ident ty) -> TypeDeclaration . TypeDeclarationData ann ident <$> checkKind ty E.kindType _ -> internalError "Invalid class member declaration" applyClassMemberDeclaration - :: forall m. (MonadError MultipleErrors m, MonadState CheckState m) - => Declaration - -> m Declaration + :: + Declaration + -> TypeCheckM Declaration applyClassMemberDeclaration = \case TypeDeclaration (TypeDeclarationData ann ident ty) -> TypeDeclaration . TypeDeclarationData ann ident <$> apply ty @@ -846,18 +846,18 @@ mapTypeDeclaration f = \case other checkConstraint - :: forall m. (MonadError MultipleErrors m, MonadState CheckState m) - => SourceConstraint - -> m SourceConstraint + :: + SourceConstraint + -> TypeCheckM SourceConstraint checkConstraint (Constraint ann clsName kinds args dat) = do let ty = foldl (TypeApp ann) (foldl (KindApp ann) (TypeConstructor ann (fmap coerceProperName clsName)) kinds) args (_, kinds', args') <- unapplyTypes <$> checkKind ty E.kindConstraint pure $ Constraint ann clsName kinds' args' dat applyConstraint - :: forall m. (MonadError MultipleErrors m, MonadState CheckState m) - => SourceConstraint - -> m SourceConstraint + :: + SourceConstraint + -> TypeCheckM SourceConstraint applyConstraint (Constraint ann clsName kinds args dat) = do let ty = foldl (TypeApp ann) (foldl (KindApp ann) (TypeConstructor ann (fmap coerceProperName clsName)) kinds) args (_, kinds', args') <- unapplyTypes <$> apply ty @@ -878,10 +878,10 @@ type InstanceDeclarationResult = ) checkInstanceDeclaration - :: forall m. (MonadError MultipleErrors m, MonadState CheckState m) - => ModuleName + :: + ModuleName -> InstanceDeclarationArgs - -> m InstanceDeclarationResult + -> TypeCheckM InstanceDeclarationResult checkInstanceDeclaration moduleName (ann, constraints, clsName, args) = do let ty = foldl (TypeApp ann) (TypeConstructor ann (fmap coerceProperName clsName)) args tyWithConstraints = foldr srcConstrainedType ty constraints @@ -899,10 +899,10 @@ checkInstanceDeclaration moduleName (ann, constraints, clsName, args) = do pure (allConstraints, allKinds, allArgs, varKinds) checkKindDeclaration - :: forall m. (MonadSupply m, MonadError MultipleErrors m, MonadState CheckState m) - => ModuleName + :: + ModuleName -> SourceType - -> m SourceType + -> TypeCheckM SourceType checkKindDeclaration _ ty = do (ty', kind) <- kindOf ty checkTypeKind kind E.kindType @@ -934,11 +934,11 @@ checkKindDeclaration _ ty = do other -> pure other existingSignatureOrFreshKind - :: forall m. MonadState CheckState m - => ModuleName + :: + ModuleName -> SourceSpan -> ProperName 'TypeName - -> m SourceType + -> TypeCheckM SourceType existingSignatureOrFreshKind moduleName ss name = do env <- getEnv case M.lookup (Qualified (ByModuleName moduleName) name) (E.types env) of @@ -946,12 +946,12 @@ existingSignatureOrFreshKind moduleName ss name = do Just (kind, _) -> pure kind kindsOfAll - :: forall m. (MonadError MultipleErrors m, MonadState CheckState m) - => ModuleName + :: + ModuleName -> [TypeDeclarationArgs] -> [DataDeclarationArgs] -> [ClassDeclarationArgs] - -> m ([TypeDeclarationResult], [DataDeclarationResult], [ClassDeclarationResult]) + -> TypeCheckM ([TypeDeclarationResult], [DataDeclarationResult], [ClassDeclarationResult]) kindsOfAll moduleName syns dats clss = withFreshSubstitution $ do synDict <- for syns $ \(sa, synName, _, _) -> (synName,) <$> existingSignatureOrFreshKind moduleName (fst sa) synName datDict <- for dats $ \(sa, datName, _, _) -> (datName,) <$> existingSignatureOrFreshKind moduleName (fst sa) datName diff --git a/src/Language/PureScript/TypeChecker/Monad.hs b/src/Language/PureScript/TypeChecker/Monad.hs index b6382e6707..dbcd78087c 100644 --- a/src/Language/PureScript/TypeChecker/Monad.hs +++ b/src/Language/PureScript/TypeChecker/Monad.hs @@ -10,10 +10,10 @@ import Prelude import Control.Arrow (second) import Control.Monad.Error.Class (MonadError(..)) import Control.Monad.State (MonadState(..), StateT(..), gets, modify) -import Control.Monad (forM_, guard, join, when, (<=<)) -import Control.Monad.Writer.Class (MonadWriter(..), censor) +import Control.Monad.State.Strict qualified as StrictState import Data.Maybe (fromMaybe) +import Data.IntMap.Lazy qualified as IM import Data.Map qualified as M import Data.Set qualified as S import Data.Text (Text, isPrefixOf, unpack) @@ -28,6 +28,34 @@ import Language.PureScript.Pretty.Values (prettyPrintValue) import Language.PureScript.TypeClassDictionaries (NamedDict, TypeClassDictionaryInScope(..)) import Language.PureScript.Types (Constraint(..), SourceType, Type(..), srcKindedType, srcTypeVar) import Text.PrettyPrint.Boxes (render) +import Control.Monad.Supply (SupplyT (unSupplyT)) +import Control.Monad.Supply.Class (MonadSupply) +import Control.Monad.Except (ExceptT, runExceptT) +import Control.Monad.Trans.Writer.CPS qualified as SW +import Control.Monad.Writer (MonadWriter(..), censor) +import Control.Monad.Supply.Class qualified as Supply +import Control.Monad.Identity (Identity(runIdentity)) +import Control.Monad (forM_, when, join, (<=<), guard) + +newtype TypeCheckM a = TypeCheckM { unTypeCheckM :: StateT CheckState (SupplyT (ExceptT MultipleErrors (SW.Writer MultipleErrors))) a } + deriving newtype (Functor, Applicative, Monad, MonadSupply, MonadState CheckState, MonadWriter MultipleErrors, MonadError MultipleErrors) + +-- | Lift a TypeCheckM computation into another monad that satisfies all its constraints +liftTypeCheckM :: + (MonadSupply m, MonadError MultipleErrors m, MonadState CheckState m, MonadWriter MultipleErrors m) => + TypeCheckM a -> m a +liftTypeCheckM (TypeCheckM m) = do + st <- get + freshId <- Supply.peek + let (result, errors) = runIdentity $ SW.runWriterT $ runExceptT $ flip StrictState.runStateT freshId $ unSupplyT $ runStateT m st + tell errors + case result of + Left err -> + throwError err + Right ((a, st'), freshId') -> do + put st' + Supply.consumeUpTo freshId' + return a newtype UnkLevel = UnkLevel (NEL.NonEmpty Unknown) deriving (Eq, Show) @@ -46,11 +74,11 @@ instance Ord UnkLevel where -- | A substitution of unification variables for types. data Substitution = Substitution - { substType :: M.Map Int SourceType + { substType :: IM.IntMap SourceType -- ^ Type substitution - , substUnsolved :: M.Map Int (UnkLevel, SourceType) + , substUnsolved :: IM.IntMap (UnkLevel, SourceType) -- ^ Unsolved unification variables with their level (scope ordering) and kind - , substNames :: M.Map Int Text + , substNames :: IM.IntMap Text -- ^ The original names of unknowns } @@ -59,17 +87,17 @@ insertUnkName u t = do modify (\s -> s { checkSubstitution = (checkSubstitution s) { substNames = - M.insert u t $ substNames $ checkSubstitution s + IM.insert u t $ substNames $ checkSubstitution s } } ) lookupUnkName :: (MonadState CheckState m) => Unknown -> m (Maybe Text) -lookupUnkName u = gets $ M.lookup u . substNames . checkSubstitution +lookupUnkName u = gets $ IM.lookup u . substNames . checkSubstitution -- | An empty substitution emptySubstitution :: Substitution -emptySubstitution = Substitution M.empty M.empty M.empty +emptySubstitution = Substitution IM.empty IM.empty IM.empty -- | State required for type checking data CheckState = CheckState @@ -105,21 +133,21 @@ data CheckState = CheckState , checkConstructorImportsForCoercible :: S.Set (ModuleName, Qualified (ProperName 'ConstructorName)) -- ^ Newtype constructors imports required to solve Coercible constraints. -- We have to keep track of them so that we don't emit unused import warnings. + , unificationCache :: S.Set (SourceType, SourceType) } -- | Create an empty @CheckState@ emptyCheckState :: Environment -> CheckState -emptyCheckState env = CheckState env 0 0 0 Nothing [] emptySubstitution [] mempty +emptyCheckState env = CheckState env 0 0 0 Nothing [] emptySubstitution [] mempty mempty -- | Unification variables type Unknown = Int -- | Temporarily bind a collection of names to values bindNames - :: MonadState CheckState m - => M.Map (Qualified Ident) (SourceType, NameKind, NameVisibility) - -> m a - -> m a + :: M.Map (Qualified Ident) (SourceType, NameKind, NameVisibility) + -> TypeCheckM a + -> TypeCheckM a bindNames newNames action = do orig <- get modify $ \st -> st { checkEnv = (checkEnv st) { names = newNames `M.union` (names . checkEnv $ st) } } @@ -129,10 +157,9 @@ bindNames newNames action = do -- | Temporarily bind a collection of names to types bindTypes - :: MonadState CheckState m - => M.Map (Qualified (ProperName 'TypeName)) (SourceType, TypeKind) - -> m a - -> m a + :: M.Map (Qualified (ProperName 'TypeName)) (SourceType, TypeKind) + -> TypeCheckM a + -> TypeCheckM a bindTypes newNames action = do orig <- get modify $ \st -> st { checkEnv = (checkEnv st) { types = newNames `M.union` (types . checkEnv $ st) } } @@ -142,11 +169,10 @@ bindTypes newNames action = do -- | Temporarily bind a collection of names to types withScopedTypeVars - :: (MonadState CheckState m, MonadWriter MultipleErrors m) - => ModuleName + :: ModuleName -> [(Text, SourceType)] - -> m a - -> m a + -> TypeCheckM a + -> TypeCheckM a withScopedTypeVars mn ks ma = do orig <- get forM_ ks $ \(name, _) -> @@ -169,29 +195,26 @@ withErrorMessageHint hint action = do -- | These hints are added at the front, so the most nested hint occurs -- at the front, but the simplifier assumes the reverse order. -getHints :: MonadState CheckState m => m [ErrorMessageHint] +getHints :: TypeCheckM [ErrorMessageHint] getHints = gets (reverse . checkHints) rethrowWithPositionTC - :: (MonadState CheckState m, MonadError MultipleErrors m) - => SourceSpan - -> m a - -> m a + :: SourceSpan + -> TypeCheckM a + -> TypeCheckM a rethrowWithPositionTC pos = withErrorMessageHint (positionedError pos) warnAndRethrowWithPositionTC - :: (MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) - => SourceSpan - -> m a - -> m a + :: SourceSpan + -> TypeCheckM a + -> TypeCheckM a warnAndRethrowWithPositionTC pos = rethrowWithPositionTC pos . warnWithPosition pos -- | Temporarily make a collection of type class dictionaries available withTypeClassDictionaries - :: MonadState CheckState m - => [NamedDict] - -> m a - -> m a + :: [NamedDict] + -> TypeCheckM a + -> TypeCheckM a withTypeClassDictionaries entries action = do orig <- get @@ -209,54 +232,49 @@ withTypeClassDictionaries entries action = do -- | Get the currently available map of type class dictionaries getTypeClassDictionaries - :: (MonadState CheckState m) - => m (M.Map QualifiedBy (M.Map (Qualified (ProperName 'ClassName)) (M.Map (Qualified Ident) (NEL.NonEmpty NamedDict)))) + :: TypeCheckM (M.Map QualifiedBy (M.Map (Qualified (ProperName 'ClassName)) (M.Map (Qualified Ident) (NEL.NonEmpty NamedDict)))) getTypeClassDictionaries = gets $ typeClassDictionaries . checkEnv -- | Lookup type class dictionaries in a module. lookupTypeClassDictionaries - :: (MonadState CheckState m) - => QualifiedBy - -> m (M.Map (Qualified (ProperName 'ClassName)) (M.Map (Qualified Ident) (NEL.NonEmpty NamedDict))) + :: QualifiedBy + -> TypeCheckM (M.Map (Qualified (ProperName 'ClassName)) (M.Map (Qualified Ident) (NEL.NonEmpty NamedDict))) lookupTypeClassDictionaries mn = gets $ fromMaybe M.empty . M.lookup mn . typeClassDictionaries . checkEnv -- | Lookup type class dictionaries in a module. lookupTypeClassDictionariesForClass - :: (MonadState CheckState m) - => QualifiedBy + :: QualifiedBy -> Qualified (ProperName 'ClassName) - -> m (M.Map (Qualified Ident) (NEL.NonEmpty NamedDict)) + -> TypeCheckM (M.Map (Qualified Ident) (NEL.NonEmpty NamedDict)) lookupTypeClassDictionariesForClass mn cn = fromMaybe M.empty . M.lookup cn <$> lookupTypeClassDictionaries mn -- | Temporarily bind a collection of names to local variables bindLocalVariables - :: (MonadState CheckState m) - => [(SourceSpan, Ident, SourceType, NameVisibility)] - -> m a - -> m a + :: [(SourceSpan, Ident, SourceType, NameVisibility)] + -> TypeCheckM a + -> TypeCheckM a bindLocalVariables bindings = bindNames (M.fromList $ flip map bindings $ \(ss, name, ty, visibility) -> (Qualified (BySourcePos $ spanStart ss) name, (ty, Private, visibility))) -- | Temporarily bind a collection of names to local type variables bindLocalTypeVariables - :: (MonadState CheckState m) - => ModuleName + :: ModuleName -> [(ProperName 'TypeName, SourceType)] - -> m a - -> m a + -> TypeCheckM a + -> TypeCheckM a bindLocalTypeVariables moduleName bindings = bindTypes (M.fromList $ flip map bindings $ \(pn, kind) -> (Qualified (ByModuleName moduleName) pn, (kind, LocalTypeVariable))) -- | Update the visibility of all names to Defined -makeBindingGroupVisible :: (MonadState CheckState m) => m () +makeBindingGroupVisible :: TypeCheckM () makeBindingGroupVisible = modifyEnv $ \e -> e { names = M.map (\(ty, nk, _) -> (ty, nk, Defined)) (names e) } -- | Update the visibility of all names to Defined in the scope of the provided action -withBindingGroupVisible :: (MonadState CheckState m) => m a -> m a +withBindingGroupVisible :: TypeCheckM a -> TypeCheckM a withBindingGroupVisible action = preservingNames $ makeBindingGroupVisible >> action -- | Perform an action while preserving the names from the @Environment@. -preservingNames :: (MonadState CheckState m) => m a -> m a +preservingNames :: TypeCheckM a -> TypeCheckM a preservingNames action = do orig <- gets (names . checkEnv) a <- action @@ -265,9 +283,8 @@ preservingNames action = do -- | Lookup the type of a value by name in the @Environment@ lookupVariable - :: (e ~ MultipleErrors, MonadState CheckState m, MonadError e m) - => Qualified Ident - -> m SourceType + :: Qualified Ident + -> TypeCheckM SourceType lookupVariable qual = do env <- getEnv case M.lookup qual (names env) of @@ -276,9 +293,8 @@ lookupVariable qual = do -- | Lookup the visibility of a value by name in the @Environment@ getVisibility - :: (e ~ MultipleErrors, MonadState CheckState m, MonadError e m) - => Qualified Ident - -> m NameVisibility + :: Qualified Ident + -> TypeCheckM NameVisibility getVisibility qual = do env <- getEnv case M.lookup qual (names env) of @@ -287,9 +303,8 @@ getVisibility qual = do -- | Assert that a name is visible checkVisibility - :: (e ~ MultipleErrors, MonadState CheckState m, MonadError e m) - => Qualified Ident - -> m () + :: Qualified Ident + -> TypeCheckM () checkVisibility name@(Qualified _ var) = do vis <- getVisibility name case vis of @@ -298,10 +313,9 @@ checkVisibility name@(Qualified _ var) = do -- | Lookup the kind of a type by name in the @Environment@ lookupTypeVariable - :: (e ~ MultipleErrors, MonadState CheckState m, MonadError e m) - => ModuleName + :: ModuleName -> Qualified (ProperName 'TypeName) - -> m SourceType + -> TypeCheckM SourceType lookupTypeVariable currentModule (Qualified qb name) = do env <- getEnv case M.lookup (Qualified qb' name) (types env) of @@ -313,46 +327,44 @@ lookupTypeVariable currentModule (Qualified qb name) = do BySourcePos _ -> currentModule -- | Get the current @Environment@ -getEnv :: (MonadState CheckState m) => m Environment +getEnv :: TypeCheckM Environment getEnv = gets checkEnv -- | Get locally-bound names in context, to create an error message. -getLocalContext :: MonadState CheckState m => m Context +getLocalContext :: TypeCheckM Context getLocalContext = do env <- getEnv return [ (ident, ty') | (Qualified (BySourcePos _) ident@Ident{}, (ty', _, Defined)) <- M.toList (names env) ] -- | Update the @Environment@ -putEnv :: (MonadState CheckState m) => Environment -> m () +putEnv :: Environment -> TypeCheckM () putEnv env = modify (\s -> s { checkEnv = env }) -- | Modify the @Environment@ -modifyEnv :: (MonadState CheckState m) => (Environment -> Environment) -> m () +modifyEnv :: (Environment -> Environment) -> TypeCheckM () modifyEnv f = modify (\s -> s { checkEnv = f (checkEnv s) }) -- | Run a computation in the typechecking monad, failing with an error, or succeeding with a return value and the final @Environment@. -runCheck :: (Functor m) => CheckState -> StateT CheckState m a -> m (a, Environment) +runCheck :: Functor m => CheckState -> StateT CheckState m a -> m (a, Environment) runCheck st check = second checkEnv <$> runStateT check st -- | Make an assertion, failing with an error message -guardWith :: (MonadError e m) => e -> Bool -> m () +guardWith :: MonadError MultipleErrors m => MultipleErrors -> Bool -> m () guardWith _ True = return () guardWith e False = throwError e capturingSubstitution - :: MonadState CheckState m - => (a -> Substitution -> b) - -> m a - -> m b + :: (a -> Substitution -> b) + -> TypeCheckM a + -> TypeCheckM b capturingSubstitution f ma = do a <- ma subst <- gets checkSubstitution return (f a subst) withFreshSubstitution - :: MonadState CheckState m - => m a - -> m a + :: TypeCheckM a + -> TypeCheckM a withFreshSubstitution ma = do orig <- get modify $ \st -> st { checkSubstitution = emptySubstitution } @@ -361,9 +373,8 @@ withFreshSubstitution ma = do return a withoutWarnings - :: MonadWriter w m - => m a - -> m (a, w) + :: TypeCheckM a + -> TypeCheckM (a, MultipleErrors) withoutWarnings = censor (const mempty) . listen unsafeCheckCurrentModule @@ -467,13 +478,13 @@ debugValue = init . render . prettyPrintValue 100 debugSubstitution :: Substitution -> [String] debugSubstitution (Substitution solved unsolved names) = concat - [ fmap go1 (M.toList solved) - , fmap go2 (M.toList unsolved') - , fmap go3 (M.toList names) + [ fmap go1 (IM.toList solved) + , fmap go2 (IM.toList unsolved') + , fmap go3 (IM.toList names) ] where unsolved' = - M.filterWithKey (\k _ -> M.notMember k solved) unsolved + IM.filterWithKey (\k _ -> IM.notMember k solved) unsolved go1 (u, ty) = "?" <> show u <> " = " <> debugType ty diff --git a/src/Language/PureScript/TypeChecker/Subsumption.hs b/src/Language/PureScript/TypeChecker/Subsumption.hs index 26da5e980f..9e360462a9 100644 --- a/src/Language/PureScript/TypeChecker/Subsumption.hs +++ b/src/Language/PureScript/TypeChecker/Subsumption.hs @@ -9,7 +9,6 @@ import Prelude import Control.Monad (when) import Control.Monad.Error.Class (MonadError(..)) -import Control.Monad.State.Class (MonadState(..)) import Data.Foldable (for_) import Data.List (uncons) @@ -19,8 +18,8 @@ import Data.Ord (comparing) import Language.PureScript.AST (ErrorMessageHint(..), Expr(..), pattern NullSourceAnn) import Language.PureScript.Crash (internalError) import Language.PureScript.Environment (tyFunction, tyRecord) -import Language.PureScript.Errors (MultipleErrors, SimpleErrorMessage(..), errorMessage, internalCompilerError) -import Language.PureScript.TypeChecker.Monad (CheckState, getHints, getTypeClassDictionaries, withErrorMessageHint) +import Language.PureScript.Errors (SimpleErrorMessage(..), errorMessage, internalCompilerError) +import Language.PureScript.TypeChecker.Monad (getHints, getTypeClassDictionaries, withErrorMessageHint, TypeCheckM) import Language.PureScript.TypeChecker.Skolems (newSkolemConstant, skolemize) import Language.PureScript.TypeChecker.Unify (alignRowsWith, freshTypeWithKind, unifyTypes) import Language.PureScript.Types (RowListItem(..), SourceType, Type(..), eqType, isREmpty, replaceTypeVars, rowFromList) @@ -59,21 +58,21 @@ defaultCoercion SNoElaborate = () -- | Check that one type subsumes another, rethrowing errors to provide a better error message subsumes - :: (MonadError MultipleErrors m, MonadState CheckState m) + :: () => SourceType -> SourceType - -> m (Expr -> Expr) + -> TypeCheckM (Expr -> Expr) subsumes ty1 ty2 = withErrorMessageHint (ErrorInSubsumption ty1 ty2) $ subsumes' SElaborate ty1 ty2 -- | Check that one type subsumes another subsumes' - :: (MonadError MultipleErrors m, MonadState CheckState m) + :: () => ModeSing mode -> SourceType -> SourceType - -> m (Coercion mode) + -> TypeCheckM (Coercion mode) subsumes' mode (ForAll _ _ ident mbK ty1 _) ty2 = do u <- maybe (internalCompilerError "Unelaborated forall") freshTypeWithKind mbK let replaced = replaceTypeVars ident u ty1 diff --git a/src/Language/PureScript/TypeChecker/Synonyms.hs b/src/Language/PureScript/TypeChecker/Synonyms.hs index 567ae415ef..9672836d6a 100644 --- a/src/Language/PureScript/TypeChecker/Synonyms.hs +++ b/src/Language/PureScript/TypeChecker/Synonyms.hs @@ -12,14 +12,13 @@ module Language.PureScript.TypeChecker.Synonyms import Prelude import Control.Monad.Error.Class (MonadError(..)) -import Control.Monad.State (MonadState) import Data.Maybe (fromMaybe) import Data.Map qualified as M import Data.Text (Text) import Language.PureScript.Environment (Environment(..), TypeKind) import Language.PureScript.Errors (MultipleErrors, SimpleErrorMessage(..), SourceSpan, errorMessage') import Language.PureScript.Names (ProperName, ProperNameType(..), Qualified) -import Language.PureScript.TypeChecker.Monad (CheckState, getEnv) +import Language.PureScript.TypeChecker.Monad (getEnv, TypeCheckM) import Language.PureScript.Types (SourceType, Type(..), completeBinderList, everywhereOnTypesTopDownM, getAnnForType, replaceAllTypeVars) -- | Type synonym information (arguments with kinds, aliased type), indexed by name @@ -56,7 +55,7 @@ replaceAllTypeSynonyms' syns kinds = everywhereOnTypesTopDownM try lookupKindArgs ctor = fromMaybe [] $ fmap (fmap (fst . snd) . fst) . completeBinderList . fst =<< M.lookup ctor kinds -- | Replace fully applied type synonyms -replaceAllTypeSynonyms :: (e ~ MultipleErrors, MonadState CheckState m, MonadError e m) => SourceType -> m SourceType +replaceAllTypeSynonyms :: SourceType -> TypeCheckM SourceType replaceAllTypeSynonyms d = do env <- getEnv either throwError return $ replaceAllTypeSynonyms' (typeSynonyms env) (types env) d diff --git a/src/Language/PureScript/TypeChecker/TypeSearch.hs b/src/Language/PureScript/TypeChecker/TypeSearch.hs index 6158f48a82..580befa288 100644 --- a/src/Language/PureScript/TypeChecker/TypeSearch.hs +++ b/src/Language/PureScript/TypeChecker/TypeSearch.hs @@ -12,21 +12,21 @@ import Language.PureScript.TypeChecker.Monad qualified as TC import Language.PureScript.TypeChecker.Subsumption (subsumes) import Language.PureScript.TypeChecker.Unify as P -import Control.Monad.Supply as P import Language.PureScript.AST as P import Language.PureScript.Environment as P -import Language.PureScript.Errors as P import Language.PureScript.Label (Label) import Language.PureScript.Names as P import Language.PureScript.Pretty.Types as P import Language.PureScript.TypeChecker.Skolems as Skolem import Language.PureScript.TypeChecker.Synonyms as P import Language.PureScript.Types as P +import Control.Monad.Supply qualified as P +import Language.PureScript.TypeChecker.Monad qualified as P checkInEnvironment :: Environment -> TC.CheckState - -> StateT TC.CheckState (SupplyT (WriterT b (Except P.MultipleErrors))) a + -> TC.TypeCheckM a -> Maybe (a, Environment) checkInEnvironment env st = either (const Nothing) Just @@ -34,6 +34,7 @@ checkInEnvironment env st = . evalWriterT . P.evalSupplyT 0 . TC.runCheck (st { TC.checkEnv = env }) + . P.liftTypeCheckM evalWriterT :: Monad m => WriterT b m r -> m r evalWriterT m = fmap fst (runWriterT m) diff --git a/src/Language/PureScript/TypeChecker/Types.hs b/src/Language/PureScript/TypeChecker/Types.hs index 3f758805c6..6fe4cbf117 100644 --- a/src/Language/PureScript/TypeChecker/Types.hs +++ b/src/Language/PureScript/TypeChecker/Types.hs @@ -1,6 +1,7 @@ -- | -- This module implements the type checker -- +{-# OPTIONS_GHC -Wno-redundant-constraints #-} module Language.PureScript.TypeChecker.Types ( BindingGroupType(..) , typesOf @@ -76,7 +77,7 @@ tvToExpr :: TypedValue' -> Expr tvToExpr (TypedValue' c e t) = TypedValue c e t -- | Lookup data about a type class in the @Environment@ -lookupTypeClass :: MonadState CheckState m => Qualified (ProperName 'ClassName) -> m TypeClassData +lookupTypeClass :: MonadState CheckState TypeCheckM => Qualified (ProperName 'ClassName) -> TypeCheckM TypeClassData lookupTypeClass name = let findClass = fromMaybe (internalError "entails: type class not found in environment") . M.lookup name in gets (findClass . typeClasses . checkEnv) @@ -84,11 +85,11 @@ lookupTypeClass name = -- | Infer the types of multiple mutually-recursive values, and return elaborated values including -- type class dictionaries and type annotations. typesOf - :: (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + :: (MonadSupply TypeCheckM, MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM, MonadWriter MultipleErrors TypeCheckM) => BindingGroupType -> ModuleName -> [((SourceAnn, Ident), Expr)] - -> m [((SourceAnn, Ident), (Expr, SourceType))] + -> TypeCheckM [((SourceAnn, Ident), (Expr, SourceType))] typesOf bindingGroupType moduleName vals = withFreshSubstitution $ do (tys, wInfer) <- capturingSubstitution tidyUp $ do (SplitBindingGroup untyped typed dict, w) <- withoutWarnings $ typeDictionaryForBindingGroup (Just moduleName) vals @@ -245,10 +246,10 @@ data SplitBindingGroup = SplitBindingGroup -- This function also generates fresh unification variables for the types of -- declarations without type annotations, returned in the 'UntypedData' structure. typeDictionaryForBindingGroup - :: (MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + :: (MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM, MonadWriter MultipleErrors TypeCheckM) => Maybe ModuleName -> [((SourceAnn, Ident), Expr)] - -> m SplitBindingGroup + -> TypeCheckM SplitBindingGroup typeDictionaryForBindingGroup moduleName vals = do -- Filter the typed and untyped declarations and make a map of names to typed declarations. -- Replace type wildcards here so that the resulting dictionary of types contains the @@ -282,13 +283,13 @@ typeDictionaryForBindingGroup moduleName vals = do -- | Check the type annotation of a typed value in a binding group. checkTypedBindingGroupElement - :: (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + :: (MonadSupply TypeCheckM, MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM, MonadWriter MultipleErrors TypeCheckM) => ModuleName -> ((SourceAnn, Ident), (Expr, [(Text, SourceType)], SourceType, Bool)) -- ^ The identifier we are trying to define, along with the expression and its type annotation -> M.Map (Qualified Ident) (SourceType, NameKind, NameVisibility) -- ^ Names brought into scope in this binding group - -> m ((SourceAnn, Ident), (Expr, SourceType)) + -> TypeCheckM ((SourceAnn, Ident), (Expr, SourceType)) checkTypedBindingGroupElement mn (ident, (val, args, ty, checkType)) dict = do -- We replace type synonyms _after_ kind-checking, since we don't want type -- synonym expansion to bring type variables into scope. See #2542. @@ -301,13 +302,13 @@ checkTypedBindingGroupElement mn (ident, (val, args, ty, checkType)) dict = do -- | Infer a type for a value in a binding group which lacks an annotation. typeForBindingGroupElement - :: (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + :: (MonadSupply TypeCheckM, MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM, MonadWriter MultipleErrors TypeCheckM) => ((SourceAnn, Ident), (Expr, SourceType)) -- ^ The identifier we are trying to define, along with the expression and its assigned type -- (at this point, this should be a unification variable) -> M.Map (Qualified Ident) (SourceType, NameKind, NameVisibility) -- ^ Names brought into scope in this binding group - -> m ((SourceAnn, Ident), (Expr, SourceType)) + -> TypeCheckM ((SourceAnn, Ident), (Expr, SourceType)) typeForBindingGroupElement (ident, (val, ty)) dict = do -- Infer the type with the new names in scope TypedValue' _ val' ty' <- bindNames dict $ infer val @@ -321,10 +322,10 @@ typeForBindingGroupElement (ident, (val, ty)) dict = do -- This is necessary during type checking to avoid unifying a polymorphic type with a -- unification variable. instantiatePolyTypeWithUnknowns - :: (MonadState CheckState m, MonadError MultipleErrors m) + :: (MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM) => Expr -> SourceType - -> m (Expr, SourceType) + -> TypeCheckM (Expr, SourceType) instantiatePolyTypeWithUnknowns val (ForAll _ _ ident mbK ty _) = do u <- maybe (internalCompilerError "Unelaborated forall") freshTypeWithKind mbK insertUnkName' u ident @@ -336,17 +337,17 @@ instantiatePolyTypeWithUnknowns val (ConstrainedType _ con ty) = do instantiatePolyTypeWithUnknowns val ty = return (val, ty) instantiatePolyTypeWithUnknownsUntilVisible - :: (MonadState CheckState m, MonadError MultipleErrors m) + :: (MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM) => Expr -> SourceType - -> m (Expr, SourceType) + -> TypeCheckM (Expr, SourceType) instantiatePolyTypeWithUnknownsUntilVisible val (ForAll _ TypeVarInvisible ident mbK ty _) = do u <- maybe (internalCompilerError "Unelaborated forall") freshTypeWithKind mbK insertUnkName' u ident instantiatePolyTypeWithUnknownsUntilVisible val $ replaceTypeVars ident u ty instantiatePolyTypeWithUnknownsUntilVisible val ty = return (val, ty) -instantiateConstraint :: MonadState CheckState m => Expr -> Type SourceAnn -> m (Expr, Type SourceAnn) +instantiateConstraint :: MonadState CheckState TypeCheckM => Expr -> Type SourceAnn -> TypeCheckM (Expr, Type SourceAnn) instantiateConstraint val (ConstrainedType _ con ty) = do dicts <- getTypeClassDictionaries hints <- getHints @@ -354,23 +355,21 @@ instantiateConstraint val (ConstrainedType _ con ty) = do instantiateConstraint val ty = pure (val, ty) -- | Match against TUnknown and call insertUnkName, failing otherwise. -insertUnkName' :: (MonadState CheckState m, MonadError MultipleErrors m) => SourceType -> Text -> m () +insertUnkName' :: (MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM) => SourceType -> Text -> TypeCheckM () insertUnkName' (TUnknown _ i) n = insertUnkName i n insertUnkName' _ _ = internalCompilerError "type is not TUnknown" -- | Infer a type for a value, rethrowing any error to provide a more useful error message infer - :: (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + :: (MonadSupply TypeCheckM, MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM, MonadWriter MultipleErrors TypeCheckM) => Expr - -> m TypedValue' + -> TypeCheckM TypedValue' infer val = withErrorMessageHint (ErrorInferringType val) $ infer' val -- | Infer a type for a value infer' - :: forall m - . (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) - => Expr - -> m TypedValue' + :: Expr + -> TypeCheckM TypedValue' infer' v@(Literal _ (NumericLiteral (Left _))) = return $ TypedValue' True v tyInt infer' v@(Literal _ (NumericLiteral (Right _))) = return $ TypedValue' True v tyNumber infer' v@(Literal _ (StringLiteral _)) = return $ TypedValue' True v tyString @@ -537,25 +536,25 @@ infer' v = internalError $ "Invalid argument to infer: " ++ show v -- | -- Infer the types of named record fields. inferProperties - :: ( MonadSupply m - , MonadState CheckState m - , MonadError MultipleErrors m - , MonadWriter MultipleErrors m + :: ( MonadSupply TypeCheckM + , MonadState CheckState TypeCheckM + , MonadError MultipleErrors TypeCheckM + , MonadWriter MultipleErrors TypeCheckM ) => [(PSString, Expr)] - -> m [(PSString, (Expr, SourceType))] + -> TypeCheckM [(PSString, (Expr, SourceType))] inferProperties = traverse (traverse inferWithinRecord) -- | -- Infer the type of a value when used as a record field. inferWithinRecord - :: ( MonadSupply m - , MonadState CheckState m - , MonadError MultipleErrors m - , MonadWriter MultipleErrors m + :: ( MonadSupply TypeCheckM + , MonadState CheckState TypeCheckM + , MonadError MultipleErrors TypeCheckM + , MonadWriter MultipleErrors TypeCheckM ) => Expr - -> m (Expr, SourceType) + -> TypeCheckM (Expr, SourceType) inferWithinRecord e = do TypedValue' _ v t <- infer e if propertyShouldInstantiate e @@ -574,12 +573,12 @@ propertyShouldInstantiate = \case _ -> False inferLetBinding - :: (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + :: (MonadSupply TypeCheckM, MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM, MonadWriter MultipleErrors TypeCheckM) => [Declaration] -> [Declaration] -> Expr - -> (Expr -> m TypedValue') - -> m ([Declaration], TypedValue') + -> (Expr -> TypeCheckM TypedValue') + -> TypeCheckM ([Declaration], TypedValue') inferLetBinding seen [] ret j = (seen, ) <$> withBindingGroupVisible (j ret) inferLetBinding seen (ValueDecl sa@(ss, _) ident nameKind [] [MkUnguarded (TypedValue checkType val ty)] : rest) ret j = do moduleName <- unsafeCheckCurrentModule @@ -614,11 +613,9 @@ inferLetBinding _ _ _ _ = internalError "Invalid argument to inferLetBinding" -- | Infer the types of variables brought into scope by a binder inferBinder - :: forall m - . (MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) - => SourceType + :: SourceType -> Binder - -> m (M.Map Ident (SourceSpan, SourceType)) + -> TypeCheckM (M.Map Ident (SourceSpan, SourceType)) inferBinder _ NullBinder = return M.empty inferBinder val (LiteralBinder _ (StringLiteral _)) = unifyTypes val tyString >> return M.empty inferBinder val (LiteralBinder _ (CharLiteral _)) = unifyTypes val tyChar >> return M.empty @@ -652,7 +649,7 @@ inferBinder val (LiteralBinder _ (ObjectLiteral props)) = do unifyTypes val (srcTypeApp tyRecord row) return m1 where - inferRowProperties :: SourceType -> SourceType -> [(PSString, Binder)] -> m (M.Map Ident (SourceSpan, SourceType)) + inferRowProperties :: SourceType -> SourceType -> [(PSString, Binder)] -> TypeCheckM (M.Map Ident (SourceSpan, SourceType)) inferRowProperties nrow row [] = unifyTypes nrow row >> return M.empty inferRowProperties nrow row ((name, binder):binders) = do propTy <- freshTypeWithKind kindType @@ -695,10 +692,10 @@ binderRequiresMonotype _ = True -- | Instantiate polytypes only when necessitated by a binder. instantiateForBinders - :: (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + :: (MonadSupply TypeCheckM, MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM, MonadWriter MultipleErrors TypeCheckM) => [Expr] -> [CaseAlternative] - -> m ([Expr], [SourceType]) + -> TypeCheckM ([Expr], [SourceType]) instantiateForBinders vals cas = unzip <$> zipWithM (\val inst -> do TypedValue' _ val' ty <- infer val if inst @@ -712,11 +709,11 @@ instantiateForBinders vals cas = unzip <$> zipWithM (\val inst -> do -- Check the types of the return values in a set of binders in a case statement -- checkBinders - :: (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + :: (MonadSupply TypeCheckM, MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM, MonadWriter MultipleErrors TypeCheckM) => [SourceType] -> SourceType -> [CaseAlternative] - -> m [CaseAlternative] + -> TypeCheckM [CaseAlternative] checkBinders _ _ [] = return [] checkBinders nvals ret (CaseAlternative binders result : bs) = do guardWith (errorMessage $ OverlappingArgNames Nothing) $ @@ -728,10 +725,10 @@ checkBinders nvals ret (CaseAlternative binders result : bs) = do return $ r : rs checkGuardedRhs - :: (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + :: (MonadSupply TypeCheckM, MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM, MonadWriter MultipleErrors TypeCheckM) => GuardedExpr -> SourceType - -> m GuardedExpr + -> TypeCheckM GuardedExpr checkGuardedRhs (GuardedExpr [] rhs) ret = do rhs' <- TypedValue True <$> (tvToExpr <$> check rhs ret) <*> pure ret return $ GuardedExpr [] rhs' @@ -752,21 +749,19 @@ checkGuardedRhs (GuardedExpr (PatternGuard binder expr : guards) rhs) ret = do -- Check the type of a value, rethrowing errors to provide a better error message -- check - :: (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + :: (MonadSupply TypeCheckM, MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM, MonadWriter MultipleErrors TypeCheckM) => Expr -> SourceType - -> m TypedValue' + -> TypeCheckM TypedValue' check val ty = withErrorMessageHint' val (ErrorCheckingType val ty) $ check' val ty -- | -- Check the type of a value -- check' - :: forall m - . (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) - => Expr + :: Expr -> SourceType - -> m TypedValue' + -> TypeCheckM TypedValue' check' val (ForAll ann vis ident mbK ty _) = do env <- getEnv mn <- gets checkCurrentModule @@ -918,12 +913,12 @@ check' val ty = do -- The @lax@ parameter controls whether or not every record member has to be provided. For object updates, this is not the case. -- checkProperties - :: (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + :: (MonadSupply TypeCheckM, MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM, MonadWriter MultipleErrors TypeCheckM) => Expr -> [(PSString, Expr)] -> SourceType -> Bool - -> m [(PSString, Expr)] + -> TypeCheckM [(PSString, Expr)] checkProperties expr ps row lax = convert <$> go ps (toRowPair <$> ts') r' where convert = fmap (fmap tvToExpr) (ts', r') = rowToList row @@ -965,14 +960,14 @@ checkProperties expr ps row lax = convert <$> go ps (toRowPair <$> ts') r' where -- * The elaborated expression for the function application (since we might need to -- insert type class dictionaries, etc.) checkFunctionApplication - :: (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + :: (MonadSupply TypeCheckM, MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM, MonadWriter MultipleErrors TypeCheckM) => Expr -- ^ The function expression -> SourceType -- ^ The type of the function -> Expr -- ^ The argument expression - -> m (SourceType, Expr) + -> TypeCheckM (SourceType, Expr) -- ^ The result type, and the elaborated term checkFunctionApplication fn fnTy arg = withErrorMessageHint' fn (ErrorInApplication fn fnTy arg) $ do subst <- gets checkSubstitution @@ -980,11 +975,11 @@ checkFunctionApplication fn fnTy arg = withErrorMessageHint' fn (ErrorInApplicat -- | Check the type of a function application checkFunctionApplication' - :: (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) + :: (MonadSupply TypeCheckM, MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM, MonadWriter MultipleErrors TypeCheckM) => Expr -> SourceType -> Expr - -> m (SourceType, Expr) + -> TypeCheckM (SourceType, Expr) checkFunctionApplication' fn (TypeApp _ (TypeApp _ tyFunction' argTy) retTy) arg = do unifyTypes tyFunction' tyFunction arg' <- tvToExpr <$> check arg argTy @@ -1014,7 +1009,7 @@ checkFunctionApplication' fn u arg = do -- | -- Ensure a set of property names and value does not contain duplicate labels -- -ensureNoDuplicateProperties :: (MonadError MultipleErrors m) => [(PSString, Expr)] -> m () +ensureNoDuplicateProperties :: (MonadError MultipleErrors TypeCheckM) => [(PSString, Expr)] -> TypeCheckM () ensureNoDuplicateProperties ps = let ls = map fst ps in case ls \\ ordNub ls of @@ -1032,9 +1027,9 @@ isInternal = \case -- | Introduce a hint only if the given expression is not internal withErrorMessageHint' - :: (MonadState CheckState m, MonadError MultipleErrors m) + :: (MonadState CheckState TypeCheckM, MonadError MultipleErrors TypeCheckM) => Expr -> ErrorMessageHint - -> m a - -> m a + -> TypeCheckM a + -> TypeCheckM a withErrorMessageHint' expr = if isInternal expr then const id else withErrorMessageHint diff --git a/src/Language/PureScript/TypeChecker/Unify.hs b/src/Language/PureScript/TypeChecker/Unify.hs index 7ce208fd24..d47cd91de6 100644 --- a/src/Language/PureScript/TypeChecker/Unify.hs +++ b/src/Language/PureScript/TypeChecker/Unify.hs @@ -16,33 +16,34 @@ module Language.PureScript.TypeChecker.Unify import Prelude -import Control.Monad (forM_, void) +import Control.Monad (forM_, void, when) import Control.Monad.Error.Class (MonadError(..)) import Control.Monad.State.Class (MonadState(..), gets, modify, state) import Control.Monad.Writer.Class (MonadWriter(..)) import Data.Foldable (traverse_) import Data.Maybe (fromMaybe) -import Data.Map qualified as M +import Data.IntMap.Lazy qualified as IM import Data.Text qualified as T import Language.PureScript.Crash (internalError) import Language.PureScript.Environment qualified as E -import Language.PureScript.Errors (ErrorMessageHint(..), MultipleErrors, SimpleErrorMessage(..), SourceAnn, errorMessage, internalCompilerError, onErrorMessages, rethrow, warnWithPosition, withoutPosition) +import Language.PureScript.Errors (ErrorMessageHint(..), SimpleErrorMessage(..), SourceAnn, errorMessage, internalCompilerError, onErrorMessages, rethrow, warnWithPosition, withoutPosition) import Language.PureScript.TypeChecker.Kinds (elaborateKind, instantiateKind, unifyKinds') -import Language.PureScript.TypeChecker.Monad (CheckState(..), Substitution(..), UnkLevel(..), Unknown, getLocalContext, guardWith, lookupUnkName, withErrorMessageHint) +import Language.PureScript.TypeChecker.Monad (CheckState(..), Substitution(..), UnkLevel(..), Unknown, getLocalContext, guardWith, lookupUnkName, withErrorMessageHint, TypeCheckM) import Language.PureScript.TypeChecker.Skolems (newSkolemConstant, skolemize) import Language.PureScript.Types (Constraint(..), pattern REmptyKinded, RowListItem(..), SourceType, Type(..), WildcardData(..), alignRowsWith, everythingOnTypes, everywhereOnTypes, everywhereOnTypesM, getAnnForType, mkForAll, rowFromList, srcTUnknown) +import Data.Set qualified as S -- | Generate a fresh type variable with an unknown kind. Avoid this if at all possible. -freshType :: (MonadState CheckState m) => m SourceType +freshType :: TypeCheckM SourceType freshType = state $ \st -> do let t = checkNextType st st' = st { checkNextType = t + 2 , checkSubstitution = - (checkSubstitution st) { substUnsolved = M.insert t (UnkLevel (pure t), E.kindType) - . M.insert (t + 1) (UnkLevel (pure (t + 1)), srcTUnknown t) + (checkSubstitution st) { substUnsolved = IM.insert t (UnkLevel (pure t), E.kindType) + . IM.insert (t + 1) (UnkLevel (pure (t + 1)), srcTUnknown t) . substUnsolved $ checkSubstitution st } @@ -50,18 +51,18 @@ freshType = state $ \st -> do (srcTUnknown (t + 1), st') -- | Generate a fresh type variable with a known kind. -freshTypeWithKind :: (MonadState CheckState m) => SourceType -> m SourceType +freshTypeWithKind :: SourceType -> TypeCheckM SourceType freshTypeWithKind kind = state $ \st -> do let t = checkNextType st st' = st { checkNextType = t + 1 , checkSubstitution = - (checkSubstitution st) { substUnsolved = M.insert t (UnkLevel (pure t), kind) (substUnsolved (checkSubstitution st)) } + (checkSubstitution st) { substUnsolved = IM.insert t (UnkLevel (pure t), kind) (substUnsolved (checkSubstitution st)) } } (srcTUnknown t, st') -- | Update the substitution to solve a type constraint -solveType :: (MonadError MultipleErrors m, MonadState CheckState m) => Int -> SourceType -> m () +solveType :: Int -> SourceType -> TypeCheckM () solveType u t = rethrow (onErrorMessages withoutPosition) $ do -- We strip the position so that any errors get rethrown with the position of -- the original unification constraint. Otherwise errors may arise from arbitrary @@ -70,11 +71,11 @@ solveType u t = rethrow (onErrorMessages withoutPosition) $ do occursCheck u t k1 <- elaborateKind t subst <- gets checkSubstitution - k2 <- maybe (internalCompilerError ("No kind for unification variable ?" <> T.pack (show u))) (pure . substituteType subst . snd) . M.lookup u . substUnsolved $ subst + k2 <- maybe (internalCompilerError ("No kind for unification variable ?" <> T.pack (show u))) (pure . substituteType subst . snd) . IM.lookup u . substUnsolved $ subst t' <- instantiateKind (t, k1) k2 modify $ \cs -> cs { checkSubstitution = (checkSubstitution cs) { substType = - M.insert u t' $ substType $ checkSubstitution cs + IM.insert u t' $ substType $ checkSubstitution cs } } @@ -83,14 +84,14 @@ substituteType :: Substitution -> SourceType -> SourceType substituteType sub = everywhereOnTypes go where go (TUnknown ann u) = - case M.lookup u (substType sub) of + case IM.lookup u (substType sub) of Nothing -> TUnknown ann u Just (TUnknown ann' u1) | u1 == u -> TUnknown ann' u1 Just t -> substituteType sub t go other = other -- | Make sure that an unknown does not occur in a type -occursCheck :: (MonadError MultipleErrors m) => Int -> SourceType -> m () +occursCheck :: Int -> SourceType -> TypeCheckM () occursCheck _ TUnknown{} = return () occursCheck u t = void $ everywhereOnTypesM go t where @@ -106,12 +107,17 @@ unknownsInType t = everythingOnTypes (.) go t [] go _ = id -- | Unify two types, updating the current substitution -unifyTypes :: (MonadError MultipleErrors m, MonadState CheckState m) => SourceType -> SourceType -> m () +unifyTypes :: SourceType -> SourceType -> TypeCheckM () unifyTypes t1 t2 | t1 == t2 = return () unifyTypes t1 t2 = do sub <- gets checkSubstitution - withErrorMessageHint (ErrorUnifyingTypes t1 t2) $ unifyTypes' (substituteType sub t1) (substituteType sub t2) + withErrorMessageHint (ErrorUnifyingTypes t1 t2) $ unifyTypes'' (substituteType sub t1) (substituteType sub t2) where + unifyTypes'' t1' t2'= do + cache <- gets unificationCache + when (S.notMember (t1', t2') cache) $ do + modify $ \st -> st { unificationCache = S.insert (t1', t2') cache } + unifyTypes' t1' t2' unifyTypes' (TUnknown _ u1) (TUnknown _ u2) | u1 == u2 = return () unifyTypes' (TUnknown _ u) t = solveType u t unifyTypes' t (TUnknown _ u) = solveType u t @@ -161,13 +167,13 @@ unifyTypes t1 t2 = do -- -- Common labels are identified and unified. Remaining labels and types are unified with a -- trailing row unification variable, if appropriate. -unifyRows :: forall m. (MonadError MultipleErrors m, MonadState CheckState m) => SourceType -> SourceType -> m () +unifyRows :: SourceType -> SourceType -> TypeCheckM () unifyRows r1 r2 = sequence_ matches *> uncurry unifyTails rest where unifyTypesWithLabel l t1 t2 = withErrorMessageHint (ErrorInRowLabel l) $ unifyTypes t1 t2 (matches, rest) = alignRowsWith unifyTypesWithLabel r1 r2 - unifyTails :: ([RowListItem SourceAnn], SourceType) -> ([RowListItem SourceAnn], SourceType) -> m () + unifyTails :: ([RowListItem SourceAnn], SourceType) -> ([RowListItem SourceAnn], SourceType) -> TypeCheckM () unifyTails ([], TUnknown _ u) (sd, r) = solveType u (rowFromList (sd, r)) unifyTails (sd, r) ([], TUnknown _ u) = solveType u (rowFromList (sd, r)) unifyTails ([], REmptyKinded _ _) ([], REmptyKinded _ _) = return () @@ -185,7 +191,7 @@ unifyRows r1 r2 = sequence_ matches *> uncurry unifyTails rest where -- | -- Replace type wildcards with unknowns -- -replaceTypeWildcards :: (MonadWriter MultipleErrors m, MonadState CheckState m) => SourceType -> m SourceType +replaceTypeWildcards :: SourceType -> TypeCheckM SourceType replaceTypeWildcards = everywhereOnTypesM replace where replace (TypeWildcard ann wdata) = do @@ -202,22 +208,22 @@ replaceTypeWildcards = everywhereOnTypesM replace -- | -- Replace outermost unsolved unification variables with named type variables -- -varIfUnknown :: forall m. (MonadState CheckState m) => [(Unknown, SourceType)] -> SourceType -> m SourceType +varIfUnknown :: [(Unknown, SourceType)] -> SourceType -> TypeCheckM SourceType varIfUnknown unks ty = do bn' <- traverse toBinding unks ty' <- go ty pure $ mkForAll bn' ty' where - toName :: Unknown -> m T.Text + toName :: Unknown -> TypeCheckM T.Text toName u = (<> T.pack (show u)) . fromMaybe "t" <$> lookupUnkName u - toBinding :: (Unknown, SourceType) -> m (SourceAnn, (T.Text, Maybe SourceType)) + toBinding :: (Unknown, SourceType) -> TypeCheckM (SourceAnn, (T.Text, Maybe SourceType)) toBinding (u, k) = do u' <- toName u k' <- go k pure (getAnnForType ty, (u', Just k')) - go :: SourceType -> m SourceType + go :: SourceType -> TypeCheckM SourceType go = everywhereOnTypesM $ \case (TUnknown ann u) -> TypeVar ann <$> toName u diff --git a/src/Language/PureScript/Types.hs b/src/Language/PureScript/Types.hs index ef00e21a07..063c1ebc32 100644 --- a/src/Language/PureScript/Types.hs +++ b/src/Language/PureScript/Types.hs @@ -716,6 +716,7 @@ everywhereOnTypesM f = go where go (BinaryNoParensType ann t1 t2 t3) = (BinaryNoParensType ann <$> go t1 <*> go t2 <*> go t3) >>= f go (ParensInType ann t) = (ParensInType ann <$> go t) >>= f go other = f other +{-# INLINE everywhereOnTypesM #-} everywhereOnTypesTopDownM :: Monad m => (Type a -> m (Type a)) -> Type a -> m (Type a) everywhereOnTypesTopDownM f = go <=< f where @@ -729,6 +730,7 @@ everywhereOnTypesTopDownM f = go <=< f where go (BinaryNoParensType ann t1 t2 t3) = BinaryNoParensType ann <$> (f t1 >>= go) <*> (f t2 >>= go) <*> (f t3 >>= go) go (ParensInType ann t) = ParensInType ann <$> (f t >>= go) go other = pure other +{-# INLINE everywhereOnTypesTopDownM #-} everythingOnTypes :: (r -> r -> r) -> (Type a -> r) -> Type a -> r everythingOnTypes (<+>) f = go where @@ -743,6 +745,7 @@ everythingOnTypes (<+>) f = go where go t@(BinaryNoParensType _ t1 t2 t3) = f t <+> go t1 <+> go t2 <+> go t3 go t@(ParensInType _ t1) = f t <+> go t1 go other = f other +{-# INLINE everythingOnTypes #-} everythingWithContextOnTypes :: s -> r -> (r -> r -> r) -> (s -> Type a -> (s, r)) -> Type a -> r everythingWithContextOnTypes s0 r0 (<+>) f = go' s0 where @@ -758,6 +761,7 @@ everythingWithContextOnTypes s0 r0 (<+>) f = go' s0 where go s (BinaryNoParensType _ t1 t2 t3) = go' s t1 <+> go' s t2 <+> go' s t3 go s (ParensInType _ t1) = go' s t1 go _ _ = r0 +{-# INLINE everythingWithContextOnTypes #-} annForType :: Lens' (Type a) a annForType k (TUnknown a b) = (\z -> TUnknown z b) <$> k a diff --git a/stack.yaml b/stack.yaml index c0865a9910..858a1b929d 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,6 +1,6 @@ # Please update Haskell image versions under .github/workflows/ci.yml together to use the same GHC version # (or the CI build will fail) -resolver: lts-20.26 +resolver: lts-22.43 pvp-bounds: both packages: - '.' @@ -13,20 +13,14 @@ extra-deps: # `async` to be used as an object key: # https://github.com/erikd/language-javascript/issues/131 - language-javascript-0.7.0.0 -# Fix issue with libtinfo. -# See https://github.com/purescript/purescript/issues/4253 -- process-1.6.13.1 -# The Cabal library is not in Stackage -- Cabal-3.6.3.0 -# hspec versions 2.9.3 to 2.10.6 depend on ghc -# ghc depends on terminfo by default, but that can be ignored -# if one uses the '-terminfo' flag. -# Unfortunately, hspec doesn't expose a similar flag. -# -# Using hspec >= 2.10.7 addresses this. -- hspec-2.10.9 -- hspec-core-2.10.9 -- hspec-discover-2.10.9 +- bower-json-1.1.0.0 +- haskeline-0.8.2 +- these-1.2.1 +- aeson-better-errors-0.9.1.3 + +- github: purescript/cheapskate + commit: 8bfaf4beeb108e97a274ed51303f278905979e87 + nix: packages: - zlib @@ -37,8 +31,10 @@ nix: flags: aeson-pretty: lib-only: true - these: - assoc: false haskeline: # Avoids a libtinfo dynamic library dependency terminfo: false + +allow-newer: true +allow-newer-deps: +- haskeline diff --git a/stack.yaml.lock b/stack.yaml.lock new file mode 100644 index 0000000000..0af2cebb41 --- /dev/null +++ b/stack.yaml.lock @@ -0,0 +1,58 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/topics/lock_files + +packages: +- completed: + hackage: language-javascript-0.7.0.0@sha256:3eab0262b8ac5621936a4beab6a0f97d0e00a63455a8b0e3ac1547b4088dae7d,3898 + pantry-tree: + sha256: b0f28d836cb3fbde203fd7318a896c3a20acd8653a905e1950ae2d9a64bccebf + size: 2244 + original: + hackage: language-javascript-0.7.0.0 +- completed: + hackage: bower-json-1.1.0.0@sha256:a136aaca67bf0d15c336f5864f7e9d40ebe046ca2cb4b25bc4895617ea35f9f6,1864 + pantry-tree: + sha256: 3acd48e7012f246ad44c7c17cd6340362b1dc448c1d93156280814e76d9e0589 + size: 419 + original: + hackage: bower-json-1.1.0.0 +- completed: + hackage: haskeline-0.8.2@sha256:3b4b594095d64f5fa199b07bdca7d6b790313ed7f380a1b061845507e6563880,6005 + pantry-tree: + sha256: 17ee6b093c5135399b8e6bc3a63d9c6a4b0bc2100b495d2d974bc1464769de39 + size: 2955 + original: + hackage: haskeline-0.8.2 +- completed: + hackage: these-1.2.1@sha256:35c57aede96c15ea1fed559ac287b1168eb2b2869d79e62ed8c845780b7ea136,2294 + pantry-tree: + sha256: dc6366ac715dfdf5338a615f71b9ed0542c403a6afcbedcddbc879e947aea6b3 + size: 351 + original: + hackage: these-1.2.1 +- completed: + hackage: aeson-better-errors-0.9.1.3@sha256:1bfdda3982368cafc7317b9f0c1f7267a6b0bbac9515ae1fad37f2b19178f567,2071 + pantry-tree: + sha256: 1c14247866dfb8052506c179e4725b8a7ce1472a4fb227d61576d862d9494551 + size: 492 + original: + hackage: aeson-better-errors-0.9.1.3 +- completed: + name: cheapskate + pantry-tree: + sha256: a2253619f50d26f0137a802e51e5e7103ee52b1f71bc060d93a0979dcbefa2c8 + size: 12069 + sha256: 959fc7a6ca7e0a743b06b0c287aa4a1c3ec7fc740e5830a4a841d43e925a6d73 + size: 62502 + url: https://github.com/purescript/cheapskate/archive/8bfaf4beeb108e97a274ed51303f278905979e87.tar.gz + version: 0.1.1.2 + original: + url: https://github.com/purescript/cheapskate/archive/8bfaf4beeb108e97a274ed51303f278905979e87.tar.gz +snapshots: +- completed: + sha256: 08bd13ce621b41a8f5e51456b38d5b46d7783ce114a50ab604d6bbab0d002146 + size: 720271 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/43.yaml + original: lts-22.43 diff --git a/tests/TestBundle.hs b/tests/TestBundle.hs new file mode 100644 index 0000000000..626344e288 --- /dev/null +++ b/tests/TestBundle.hs @@ -0,0 +1,78 @@ +{-# LANGUAGE DoAndIfThenElse #-} + +module TestBundle where + +import Prelude () +import Prelude.Compat + +import qualified Language.PureScript as P +import Language.PureScript.Bundle +import Language.PureScript.Interactive.IO (readNodeProcessWithExitCode) + +import Data.Function (on) +import Data.List (minimumBy) + +import Control.Monad +import Control.Monad.IO.Class (liftIO) +import Control.Monad.Trans.Except + +import System.Exit +import System.FilePath +import System.IO +import System.IO.UTF8 +import qualified System.FilePath.Glob as Glob + +import TestUtils +import Test.Hspec + +spec :: SpecWith SupportModules +spec = + context "Bundle examples" $ + beforeAllWith ((<$> createOutputFile logfile) . (,)) $ do + bundleTestCases <- runIO $ getTestFiles "bundle" + forM_ bundleTestCases $ \testPurs -> do + it ("'" <> takeFileName (getTestMain testPurs) <> "' should compile, bundle and run without error") $ \(support, outputFile) -> + assertBundles support testPurs outputFile + where + + -- Takes the test entry point from a group of purs files - this is determined + -- by the file with the shortest path name, as everything but the main file + -- will be under a subdirectory. + getTestMain :: [FilePath] -> FilePath + getTestMain = minimumBy (compare `on` length) + +assertBundles + :: SupportModules + -> [FilePath] + -> Handle + -> Expectation +assertBundles support inputFiles outputFile = do + (result, _) <- compile support inputFiles + case result of + Left errs -> expectationFailure . P.prettyPrintMultipleErrors P.defaultPPEOptions $ errs + Right _ -> do + jsFiles <- concat <$> Glob.globDir [Glob.compile "*/*.js", Glob.compile "*/foreign.cjs"] modulesDir + let entryPoint = modulesDir "index.cjs" + let entryModule = [(`ModuleIdentifier` Regular) "Main"] + bundled <- runExceptT $ do + input <- forM jsFiles $ \filename -> do + js <- liftIO $ readUTF8File filename + mid <- guessModuleIdentifier filename + length js `seq` return (mid, Just filename, js) + bundleSM input entryModule (Just "Main") "PS" (Just entryPoint) Nothing + case bundled of + Right (_, js) -> do + writeUTF8File entryPoint js + nodeResult <- readNodeProcessWithExitCode Nothing [entryPoint] "" + hPutStrLn outputFile $ "\n" <> takeFileName (last inputFiles) <> ":" + case nodeResult of + Right (ExitSuccess, out, err) + | not (null err) -> expectationFailure $ "Test wrote to stderr:\n\n" <> err + | not (null out) && trim (last (lines out)) == "Done" -> hPutStr outputFile out + | otherwise -> expectationFailure $ "Test did not finish with 'Done':\n\n" <> out + Right (ExitFailure _, _, err) -> expectationFailure err + Left err -> expectationFailure err + Left err -> expectationFailure $ "Could not bundle: " ++ show err + +logfile :: FilePath +logfile = "bundle-tests.out" diff --git a/tests/TestMake.hs b/tests/TestMake.hs index 610e8465c8..cf3e422c6f 100644 --- a/tests/TestMake.hs +++ b/tests/TestMake.hs @@ -1,15 +1,15 @@ -- Tests for the compiler's handling of incremental builds, i.e. the code in -- Language.PureScript.Make. -module TestMake where +module TestMake (spec) where -import Prelude +import Prelude hiding (writeFile) import Language.PureScript qualified as P import Language.PureScript.CST qualified as CST import Control.Concurrent (threadDelay) -import Control.Monad (guard, void) +import Control.Monad (guard, void, forM_, when) import Control.Exception (tryJust) import Control.Monad.IO.Class (liftIO) import Control.Concurrent.MVar (readMVar, newMVar, modifyMVar_) @@ -19,11 +19,13 @@ import Data.Text qualified as T import Data.Set (Set) import Data.Set qualified as Set import Data.Map qualified as M +import Data.Version (showVersion) +import Paths_purescript qualified as Paths import System.FilePath (()) import System.Directory (createDirectory, removeDirectoryRecursive, removeFile, setModificationTime) import System.IO.Error (isDoesNotExistError) -import System.IO.UTF8 (readUTF8FilesT, writeUTF8FileT) +import System.IO.UTF8 (readUTF8FilesT, readUTF8FileT, writeUTF8FileT) import Test.Hspec (Spec, before_, it, shouldReturn) @@ -36,141 +38,171 @@ timestampB = utcMidnightOnDate 2019 1 2 timestampC = utcMidnightOnDate 2019 1 3 timestampD = utcMidnightOnDate 2019 1 4 +oneSecond :: Int +oneSecond = 10 ^ (6::Int) -- microseconds. + spec :: Spec spec = do let sourcesDir = "tests/purs/make" let moduleNames = Set.fromList . map P.moduleNameFromString + let modulePath name = sourcesDir (T.unpack name <> ".purs") + let foreignJsPath name = sourcesDir (T.unpack name <> ".js") + + -- Test helpers. + let testN itFn name modules compileFn res = + itFn name $ do + let names = map (\(mn, _, _) -> mn) modules + let paths = map modulePath names + let timestamp = utcMidnightOnDate 2019 1 + + forM_ (zip [0..] modules) $ \(idx, (mn, content, _)) -> do + writeFile (modulePath mn) (timestamp idx) content + -- Write a fake foreign module to bypass compiler's check. + when (T.isInfixOf "\nforeign import" content) $ + writeFile (foreignJsPath mn) (timestamp idx) content + + compile paths `shouldReturn` moduleNames names + + forM_ (zip [length modules..] modules) $ \(idx, (mn, _, mbContent)) -> do + maybe (pure ()) (writeFile (modulePath mn) (timestamp idx)) mbContent + + compileFn paths `shouldReturn` moduleNames res + + let test2 fn name (mAContent1, mAContent2, mBContent) res = + testN fn name + [ ("A", mAContent1, Just mAContent2) + , ("B", mBContent, Nothing) + ] compile res + + let testWithFailure2 fn name (mAContent1, mAContent2, mBContent) res = + testN fn name + [ ("A", mAContent1, Just mAContent2) + , ("B", mBContent, Nothing) + ] compileWithFailure res + + let test3 fn name (mAContent1, mAContent2, mBContent, mCContent) res = + testN fn name + [ ("A", mAContent1, Just mAContent2) + , ("B", mBContent, Nothing) + , ("C", mCContent, Nothing) + ] compile res + + let testWithFailure3 fn name (mAContent1, mAContent2, mBContent, mCContent) res = + testN fn name + [ ("A", mAContent1, Just mAContent2) + , ("B", mBContent, Nothing) + , ("C", mCContent, Nothing) + ] compileWithFailure res + + let recompile2 fn name ms = + test2 fn ("recompiles downstream when " <> name) ms ["A", "B"] + + let recompileWithFailure2 fn name ms = + testWithFailure2 fn ("recompiles downstream when " <> name) ms ["A", "B"] + + let noRecompile2 fn name ms = + test2 fn ("does not recompile downstream when " <> name) ms ["A"] + before_ (rimraf modulesDir >> rimraf sourcesDir >> createDirectory sourcesDir) $ do it "does not recompile if there are no changes" $ do - let modulePath = sourcesDir "Module.purs" + let mPath = sourcesDir "Module.purs" - writeFileWithTimestamp modulePath timestampA "module Module where\nfoo = 0\n" - compile [modulePath] `shouldReturn` moduleNames ["Module"] - compile [modulePath] `shouldReturn` moduleNames [] + writeFile mPath timestampA "module Module where\nfoo = 0\n" + compile [mPath] `shouldReturn` moduleNames ["Module"] + compile [mPath] `shouldReturn` moduleNames [] it "recompiles if files have changed" $ do - let modulePath = sourcesDir "Module.purs" + let mPath = sourcesDir "Module.purs" - writeFileWithTimestamp modulePath timestampA "module Module where\nfoo = 0\n" - compile [modulePath] `shouldReturn` moduleNames ["Module"] - writeFileWithTimestamp modulePath timestampB "module Module where\nfoo = 1\n" - compile [modulePath] `shouldReturn` moduleNames ["Module"] + writeFile mPath timestampA "module Module where\nfoo = 0\n" + compile [mPath] `shouldReturn` moduleNames ["Module"] + writeFile mPath timestampB "module Module where\nfoo = 1\n" + compile [mPath] `shouldReturn` moduleNames ["Module"] it "does not recompile if hashes have not changed" $ do - let modulePath = sourcesDir "Module.purs" + let mPath = modulePath "Module" moduleContent = "module Module where\nfoo = 0\n" - writeFileWithTimestamp modulePath timestampA moduleContent - compile [modulePath] `shouldReturn` moduleNames ["Module"] - writeFileWithTimestamp modulePath timestampB moduleContent - compile [modulePath] `shouldReturn` moduleNames [] + writeFile mPath timestampA moduleContent + compile [mPath] `shouldReturn` moduleNames ["Module"] + writeFile mPath timestampB moduleContent + compile [mPath] `shouldReturn` moduleNames [] it "recompiles if the file path for a module has changed" $ do let modulePath1 = sourcesDir "Module1.purs" modulePath2 = sourcesDir "Module2.purs" moduleContent = "module Module where\nfoo = 0\n" - writeFileWithTimestamp modulePath1 timestampA moduleContent - writeFileWithTimestamp modulePath2 timestampA moduleContent + writeFile modulePath1 timestampA moduleContent + writeFile modulePath2 timestampA moduleContent compile [modulePath1] `shouldReturn` moduleNames ["Module"] compile [modulePath2] `shouldReturn` moduleNames ["Module"] it "recompiles if an FFI file was added" $ do - let moduleBasePath = sourcesDir "Module" - modulePath = moduleBasePath ++ ".purs" - moduleFFIPath = moduleBasePath ++ ".js" + let mPath = modulePath "Module" + mFFIPath = foreignJsPath "Module" moduleContent = "module Module where\nfoo = 0\n" - writeFileWithTimestamp modulePath timestampA moduleContent - compile [modulePath] `shouldReturn` moduleNames ["Module"] + writeFile mPath timestampA moduleContent + compile [mPath] `shouldReturn` moduleNames ["Module"] - writeFileWithTimestamp moduleFFIPath timestampB "export var bar = 1;\n" - compile [modulePath] `shouldReturn` moduleNames ["Module"] + writeFile mFFIPath timestampB "export var bar = 1;\n" + compile [mPath] `shouldReturn` moduleNames ["Module"] it "recompiles if an FFI file was removed" $ do - let moduleBasePath = sourcesDir "Module" - modulePath = moduleBasePath ++ ".purs" - moduleFFIPath = moduleBasePath ++ ".js" + let mPath = modulePath "Module" + mFFIPath = foreignJsPath "Module" moduleContent = "module Module where\nfoo = 0\n" - writeFileWithTimestamp modulePath timestampA moduleContent - writeFileWithTimestamp moduleFFIPath timestampB "export var bar = 1;\n" - compile [modulePath] `shouldReturn` moduleNames ["Module"] - - removeFile moduleFFIPath - compile [modulePath] `shouldReturn` moduleNames ["Module"] - - it "recompiles downstream modules when a module is rebuilt" $ do - let moduleAPath = sourcesDir "A.purs" - moduleBPath = sourcesDir "B.purs" - moduleAContent1 = "module A where\nfoo = 0\n" - moduleAContent2 = "module A where\nfoo = 1\n" - moduleBContent = "module B where\nimport A (foo)\nbar = foo\n" - - writeFileWithTimestamp moduleAPath timestampA moduleAContent1 - writeFileWithTimestamp moduleBPath timestampB moduleBContent - compile [moduleAPath, moduleBPath] `shouldReturn` moduleNames ["A", "B"] - - writeFileWithTimestamp moduleAPath timestampC moduleAContent2 - compile [moduleAPath, moduleBPath] `shouldReturn` moduleNames ["A", "B"] - - it "only recompiles downstream modules when a module is rebuilt" $ do - let moduleAPath = sourcesDir "A.purs" - moduleBPath = sourcesDir "B.purs" - moduleCPath = sourcesDir "C.purs" - modulePaths = [moduleAPath, moduleBPath, moduleCPath] - moduleAContent1 = "module A where\nfoo = 0\n" - moduleAContent2 = "module A where\nfoo = 1\n" - moduleBContent = "module B where\nimport A (foo)\nbar = foo\n" - moduleCContent = "module C where\nbaz = 3\n" - - writeFileWithTimestamp moduleAPath timestampA moduleAContent1 - writeFileWithTimestamp moduleBPath timestampB moduleBContent - writeFileWithTimestamp moduleCPath timestampC moduleCContent - compile modulePaths `shouldReturn` moduleNames ["A", "B", "C"] + writeFile mPath timestampA moduleContent + writeFile mFFIPath timestampB "export var bar = 1;\n" + compile [mPath] `shouldReturn` moduleNames ["Module"] - writeFileWithTimestamp moduleAPath timestampD moduleAContent2 - compile modulePaths `shouldReturn` moduleNames ["A", "B"] + removeFile mFFIPath + compile [mPath] `shouldReturn` moduleNames ["Module"] it "does not necessarily recompile modules which were not part of the previous batch" $ do - let moduleAPath = sourcesDir "A.purs" - moduleBPath = sourcesDir "B.purs" - moduleCPath = sourcesDir "C.purs" - modulePaths = [moduleAPath, moduleBPath, moduleCPath] - batch1 = [moduleAPath, moduleBPath] - batch2 = [moduleAPath, moduleCPath] - moduleAContent = "module A where\nfoo = 0\n" - moduleBContent = "module B where\nimport A (foo)\nbar = foo\n" - moduleCContent = "module C where\nbaz = 3\n" - - writeFileWithTimestamp moduleAPath timestampA moduleAContent - writeFileWithTimestamp moduleBPath timestampB moduleBContent - writeFileWithTimestamp moduleCPath timestampC moduleCContent + let mAPath = modulePath "A" + mBPath = modulePath "B" + mCPath = modulePath "C" + modulePaths = [mAPath, mBPath, mCPath] + + batch1 = [mAPath, mBPath] + batch2 = [mAPath, mCPath] + + mAContent = "module A where\nfoo = 0\n" + mBContent = "module B where\nimport A (foo)\nbar = foo\n" + mCContent = "module C where\nbaz = 3\n" + + writeFile mAPath timestampA mAContent + writeFile mBPath timestampB mBContent + writeFile mCPath timestampC mCContent compile modulePaths `shouldReturn` moduleNames ["A", "B", "C"] compile batch1 `shouldReturn` moduleNames [] compile batch2 `shouldReturn` moduleNames [] it "recompiles if a module fails to compile" $ do - let modulePath = sourcesDir "Module.purs" + let mPath = sourcesDir "Module.purs" moduleContent = "module Module where\nfoo :: Int\nfoo = \"not an int\"\n" - writeFileWithTimestamp modulePath timestampA moduleContent - compileAllowingFailures [modulePath] `shouldReturn` moduleNames ["Module"] - compileAllowingFailures [modulePath] `shouldReturn` moduleNames ["Module"] + writeFile mPath timestampA moduleContent + compileWithFailure [mPath] `shouldReturn` moduleNames ["Module"] + compileWithFailure [mPath] `shouldReturn` moduleNames ["Module"] it "recompiles if docs are requested but not up to date" $ do - let modulePath = sourcesDir "Module.purs" - moduleContent1 = "module Module where\nx :: Int\nx = 1" - moduleContent2 = moduleContent1 <> "\ny :: Int\ny = 1" + let mPath = sourcesDir "Module.purs" + + mContent1 = "module Module where\nx :: Int\nx = 1" + mContent2 = mContent1 <> "\ny :: Int\ny = 1" + optsWithDocs = P.defaultOptions { P.optionsCodegenTargets = Set.fromList [P.JS, P.Docs] } - go opts = compileWithOptions opts [modulePath] >>= assertSuccess - oneSecond = 10 ^ (6::Int) -- microseconds. + go opts = compileWithOptions opts [mPath] >>= assertSuccess - writeFileWithTimestamp modulePath timestampA moduleContent1 + writeFile mPath timestampA mContent1 go optsWithDocs `shouldReturn` moduleNames ["Module"] - writeFileWithTimestamp modulePath timestampB moduleContent2 + writeFile mPath timestampB mContent2 -- See Note [Sleeping to avoid flaky tests] threadDelay oneSecond go P.defaultOptions `shouldReturn` moduleNames ["Module"] @@ -178,30 +210,470 @@ spec = do -- recompiled. go optsWithDocs `shouldReturn` moduleNames ["Module"] - it "recompiles if corefn is requested but not up to date" $ do - let modulePath = sourcesDir "Module.purs" - moduleContent1 = "module Module where\nx :: Int\nx = 1" - moduleContent2 = moduleContent1 <> "\ny :: Int\ny = 1" - optsCorefnOnly = P.defaultOptions { P.optionsCodegenTargets = Set.singleton P.CoreFn } - go opts = compileWithOptions opts [modulePath] >>= assertSuccess - oneSecond = 10 ^ (6::Int) -- microseconds. - - writeFileWithTimestamp modulePath timestampA moduleContent1 - go optsCorefnOnly `shouldReturn` moduleNames ["Module"] - writeFileWithTimestamp modulePath timestampB moduleContent2 + it "recompiles if CoreFn is requested but not up to date" $ do + let mPath = sourcesDir "Module.purs" + mContent1 = "module Module where\nx :: Int\nx = 1" + mContent2 = mContent1 <> "\ny :: Int\ny = 1" + optsCoreFnOnly = P.defaultOptions { P.optionsCodegenTargets = Set.singleton P.CoreFn } + go opts = compileWithOptions opts [mPath] >>= assertSuccess + + writeFile mPath timestampA mContent1 + go optsCoreFnOnly `shouldReturn` moduleNames ["Module"] + writeFile mPath timestampB mContent2 -- See Note [Sleeping to avoid flaky tests] threadDelay oneSecond go P.defaultOptions `shouldReturn` moduleNames ["Module"] - -- Since the existing corefn.json is now outdated, the module should be + -- Since the existing CoreFn.json is now outdated, the module should be -- recompiled. - go optsCorefnOnly `shouldReturn` moduleNames ["Module"] + go optsCoreFnOnly `shouldReturn` moduleNames ["Module"] + + it "recompiles if cache-db version differs from the current" $ do + let mPath = sourcesDir "Module.purs" + mContent = "module Module where\nfoo :: Int\nfoo = 1\n" + + writeFile mPath timestampA mContent + compile [mPath] `shouldReturn` moduleNames ["Module"] + + -- Replace version with illegal in cache-db file. + let cacheDbFilePath = P.cacheDbFile modulesDir + versionText ver = "\"version\":\"" <> ver <> "\"" + + cacheContent <- readUTF8FileT cacheDbFilePath + + let currentVer = T.pack (showVersion Paths.version) + let newContent = + T.replace (versionText currentVer) (versionText "0.0.0") cacheContent + + writeUTF8FileT cacheDbFilePath newContent + + compile [mPath] `shouldReturn` moduleNames ["Module"] + + -- Cut off rebuild tests. + + -- If a module is compiled with effective changes for downstream they should + -- be rebuilt too. + it "recompiles downstream modules when a module is rebuilt and externs changed" $ do + let mAPath = modulePath "A" + mBPath = modulePath "B" + mAContent1 = "module A where\nfoo = 0\n" + mAContent2 = "module A where\nfoo = '1'\n" + mBContent = "module B where\nimport A as A\nbar = A.foo\n" + + writeFile mAPath timestampA mAContent1 + writeFile mBPath timestampB mBContent + compile [mAPath, mBPath] `shouldReturn` moduleNames ["A", "B"] + + writeFile mAPath timestampC mAContent2 + compile [mAPath, mBPath] `shouldReturn` moduleNames ["A", "B"] + + -- If a module is compiled with no effective changes for downstream they should + -- not be rebuilt. + it "recompiles downstream modules only when a module is rebuilt end externs changed" $ do + let mAPath = modulePath "A" + mBPath = modulePath "B" + mCPath = modulePath "C" + modulePaths = [mAPath, mBPath, mCPath] + + mAContent1 = "module A where\nfoo = 0\n" + mAContent2 = "module A where\nfoo = '1'\n" -- change externs here + mBContent = "module B where\nimport A (foo)\nbar = foo\n" + mCContent = "module C where\nbaz = 3\n" + + writeFile mAPath timestampA mAContent1 + writeFile mBPath timestampB mBContent + writeFile mCPath timestampC mCContent + compile modulePaths `shouldReturn` moduleNames ["A", "B", "C"] + + writeFile mAPath timestampD mAContent2 + compile modulePaths `shouldReturn` moduleNames ["A", "B"] + + -- If module is compiled separately (e.g., with ide). Then downstream should + -- be rebuilt during the next build. + it "recompiles downstream after a module has been rebuilt separately" $ do + let mAPath = modulePath "A" + mBPath = modulePath "B" + mCPath = modulePath "C" + mPaths = [mAPath, mBPath, mCPath] + + mAContent1 = "module A where\nfoo = 0\n" + mAContent2 = "module A where\nfoo = 1\n" + mBContent = "module B where\nimport A\nbar = 1\nbaz = foo\n" + mCContent = "module C where\nimport B\nqux = bar" + + writeFile mAPath timestampA mAContent1 + writeFile mBPath timestampB mBContent + writeFile mCPath timestampB mCContent + + compile mPaths `shouldReturn` moduleNames ["A", "B", "C"] + + threadDelay oneSecond + + writeFile mAPath timestampC mAContent2 + compile [mAPath] `shouldReturn` moduleNames ["A"] + + compile mPaths `shouldReturn` moduleNames ["B", "C"] + + -- If a module failed to compile, then the error is fixed and there are no + -- effective changes for downstream modules, they should not be recompiled. + it "does not recompile downstream modules after the error fixed and externs not changed" $ do + let mAPath = modulePath "A" + mBPath = modulePath "B" + mAContent1 = "module A where\nfoo :: Int\nfoo = 0\n" + mAContent2 = "module A where\nfoo :: Char\nfoo = 0\n" + mBContent = "module B where\nimport A as A\nbar :: Int\nbar = A.foo\n" + + writeFile mAPath timestampA mAContent1 + writeFile mBPath timestampB mBContent + compile [mAPath, mBPath] `shouldReturn` moduleNames ["A", "B"] + + writeFile mAPath timestampC mAContent2 + compileWithFailure [mAPath, mBPath] `shouldReturn` moduleNames ["A"] + + writeFile mAPath timestampD mAContent1 + compile [mAPath, mBPath] `shouldReturn` moduleNames ["A"] + + -- If a module failed to compile, then the error is fixed and there are + -- effective changes for downstream modules, they should be recompiled. + it "recompiles downstream modules after the error fixed and externs changed" $ do + let mAPath = modulePath "A" + mBPath = modulePath "B" + mAContent1 = "module A where\nfoo :: Int\nfoo = 0\n" + mAContent2 = "module A where\nfoo :: Char\nfoo = 0\n" + mAContent3 = "module A where\nfoo :: Char\nfoo = '0'\n" + mBContent = "module B where\nimport A as A\nbar :: Int\nbar = A.foo\n" + + writeFile mAPath timestampA mAContent1 + writeFile mBPath timestampB mBContent + compile [mAPath, mBPath] `shouldReturn` moduleNames ["A", "B"] + + writeFile mAPath timestampC mAContent2 + compileWithFailure [mAPath, mBPath] `shouldReturn` moduleNames ["A"] + writeFile mAPath timestampD mAContent3 + compileWithFailure [mAPath, mBPath] `shouldReturn` moduleNames ["A", "B"] + + -- Reexports: original ref is changed. + test3 it "recompiles downstream when a reexported ref changed" + ( "module A where\nfoo = 0\n" + , "module A where\nfoo = '1'\nbar = 1\n" -- change externs here + , "module B (module E) where\nimport A (foo) as E\n" + , "module C where\nimport B as B\nbaz = B.foo\n" + ) + ["A", "B", "C"] + + -- Reexports: original ref is changed. Ref is imported but not used. + test3 it "does not recompile downstream when a reexported ref changed and the ref is imported but not used" + ( "module A where\nfoo = 0\n" + , "module A where\nfoo = '1'\nbar = 1\n" -- change externs here + , "module B (module E) where\nimport A as E\n" + -- Import but not use. + , "module C where\nimport B (foo)\nx = 1\n" + ) + ["A", "B"] + + -- Reexports: original export is removed from module. + testWithFailure3 it "recompiles downstream when a reexported ref removed" + ( "module A where\nfoo = 0\n" + , "module A where\nbar = 1\n" -- change externs here + , "module B (module E) where\nimport A as E\n" + , "module C where\nimport B as B\nbaz = B.foo\n" + ) + ["A", "B", "C"] + + -- Reexports: ref is removed from reexporting module. + testWithFailure3 it "recompiles downstream when a reexported ref removed (from reexported)" + ( "module B (module E) where\nimport A (foo) as E\n" + , "module B where\nimport A (foo) as E\n" + , "module A where\nfoo = 0\n" + , "module C where\nimport B as B\nbaz = B.foo\n" + ) + ["B", "C"] + + -- Reexports: ref is imported but not used. Reexport ref is removed from + -- reexporting module. + testWithFailure3 it "recompiles downstream when a reexported ref removed (imported but not used)" + ( "module B (module E) where\nimport A (foo) as E\n" + , "module B where\nimport A (foo) as E\n" + , "module A where\nfoo = 0\n" + -- Import but not use. + , "module C where\nimport B (foo) as B\nx=1\n" + ) + ["B", "C"] + + -- Reexports: original ref Removed. Ref is imported but not used. + testWithFailure3 it "recompiles downstream when a reexported ref removed in original" + ( "module A where\nfoo = 0\n" + , "module A where\nbar = 1\n" -- change externs here + , "module B (module E) where\nimport A as E\n" + -- Import but not use. + , "module C where\nimport B (foo)\nx = 1\n" + ) + ["A", "B", "C"] + + -- Imports. + testWithFailure2 it "recompiles downstream when removed reference found in imports" + ( "module A where\nfoo = 0\n" + , "module A where\nfoo2 = 1\n" + , "module B where\nimport A (foo)\nbar = 1" + ) + ["A", "B"] + + test2 it "does not recompiles downstream when removed reference is not used" + ( "module A where\nfoo = 0\n" + , "module A where\nfoo2 = 1\n" + , "module B where\nimport A\nbar = 1" + ) + ["A"] + + -- We need to ensure that it finds refs everywhere inside a module. + -- Usage: Inlined type. + testWithFailure2 it "recompiles downstream when found changed inlined type" + ( "module A where\ntype T = Int\n" + , "module A where\ntype T = String\n" + , "module B where\nimport A\nx = (1 :: T)" + ) + ["A", "B"] + + -- Transitive change: module A changes, module B depends on A and module C + -- depends on B are both recompiled. + test3 it "recompiles downstream due to transitive change" + ( "module A where\nfoo = 0\n" + , "module A where\nfoo = '1'\n" + , "module B where\nimport A (foo)\nbar = qux\nqux = foo" + , "module C where\nimport B (bar)\nbaz = bar\n" + ) + ["A", "B", "C"] + + test3 it "does not recompile downstream if no transitive change" + ( "module A where\nfoo = 0\n" + , "module A where\nfoo = '1'\n" + , "module B where\nimport A (foo)\nbar = 1\nqux = foo" + , "module C where\nimport B (bar)\nbaz = bar\n" + ) + ["A", "B"] + + -- Non effective change does not cause downstream rebuild. + test2 it "does not recompile downstream if unused type changed" + ( "module A where\ntype SynA = Int\ntype SynA2 = Int" + , "module A where\ntype SynA = String\ntype SynA2 = Int" + , "module B where\nimport A as A\ntype SynB = A.SynA2" + ) + ["A"] + + -- Type synonym in foreign import. + recompile2 it "type synonym changed in foreign import" + ( "module A where\ntype SynA = Int\n" + , "module A where\ntype SynA = String\n" + , "module B where\nimport A as A\nforeign import a :: A.SynA\n" + ) + + -- Type synonym change. + recompile2 it "type synonym changed" + ( "module A where\ntype SynA = Int\n" + , "module A where\ntype SynA = String\n" + , "module B where\nimport A as A\ntype SynB = Array A.SynA\n" + ) + + -- Type synonym change in value. + recompile2 it "type synonym changed in value" + ( "module A where\ntype SynA = Int\n" + , "module A where\ntype SynA = String\n" + , "module B where\nimport A as A\nvalue = ([] :: Array A.SynA)\n" + ) + + -- Type synonym change in pattern. + recompile2 it "type synonym changed in pattern" + ( "module A where\ntype SynA = Int\n" + , "module A where\ntype SynA = String\n" + , "module B where\nimport A as A\nfn = \\(_ :: Array A.SynA) -> 0\n" + ) + + -- Type synonym indirect change. + recompile2 it "type synonym dependency changed" + ( "module A where\ntype SynA = Int\ntype SynA2 = SynA\n" + , "module A where\ntype SynA = String\ntype SynA2 = SynA\n" + , "module B where\nimport A as A\ntype SynB = Array A.SynA2\n" + ) + + -- Data type: parameter added. + recompile2 it "data type changed (parameter added)" + ( "module A where\ndata T = A Int | B Int\n" + , "module A where\ndata T a = A Int | B a\n" + , "module B where\nimport A (T)\ntype B = T" + ) + + -- Data type: constructor added. + recompile2 it "data type changed (constructor added)" + ( "module A where\ndata T = A | B\n" + , "module A where\ndata T = A | B | C\n" + , "module B where\nimport A (T(B))\nb = B" + ) + + -- Data type: constructor indirectly changed. + recompile2 it "data type constructor dependency changed" + ( "module A where\ntype SynA = Int\ndata AB = A SynA | B Int\n" + , "module A where\ntype SynA = String\ndata AB = A SynA | B Int\n" + , "module B where\nimport A (AB(..))\nb = A" + ) + + -- Data type: constructor changed but not used. + noRecompile2 it "data type constructor changed, but not used" + ( "module A where\ntype SynA = Int\ndata AB = A SynA | B Int\n" + , "module A where\ntype SynA = String\ndata AB = A SynA | B Int\n" + -- use type and other constructor + , "module B where\nimport A (AB(..))\ntype B = AB\nb = B" + ) + + -- Data type: constructor added, but not imported. + noRecompile2 it "data type constructor added, but ctors not imported" + ( "module A where\ntype SynA = Int\ndata AB = A SynA | B Int\n" + , "module A where\ntype SynA = String\ndata AB = A SynA | B Int | C\n" + -- use just type + , "module B where\nimport A (AB)\ntype B = AB\n" + ) + + -- Data type: constructor added, but not used. + noRecompile2 it "data type constructor added, but ctors not imported" + ( "module A where\ntype SynA = Int\ndata AB = A SynA | B Int\n" + , "module A where\ntype SynA = String\ndata AB = A SynA | B Int | C\n" + -- use type + , "module B where\nimport A (AB(..))\ntype B = AB\n" + ) + + -- Data type: constructor added, and constructors are used in the downstream + -- module (this may be need when there is a case statement without wildcard, + -- but we don't analyze the usage that deep). + recompile2 it "data type constructor added and ctors are used" + ( "module A where\ntype SynA = Int\ndata AB = A SynA | B Int\n" + , "module A where\ntype SynA = String\ndata AB = A SynA | B Int | C\n" + -- use type and other constructor + , "module B where\nimport A (AB(..))\ntype B = AB\nb = B\n" + ) + + -- Value operator change. + recompile2 it "value op changed" + ( "module A where\ndata T a = T Int a\ninfixl 2 T as :+:\n" + , "module A where\ndata T a = T Int a\ninfixl 3 T as :+:\n" + , "module B where\nimport A\nt = 1 :+: \"1\" " + ) + + -- Value operator indirect change. + recompile2 it "value op dependency changed" + ( "module A where\ndata T a = T a String\ninfixl 2 T as :+:\n" + , "module A where\ndata T a = T Int a\ninfixl 2 T as :+:\n" + , "module B where\nimport A\nt = 1 :+: \"1\" " + ) + + -- Type operator change. + recompile2 it "type op changed" + ( "module A where\ndata T a b = T a b\ninfixl 2 type T as :+:\n" + , "module A where\ndata T a b = T a b\ninfixl 3 type T as :+:\n" + , "module B where\nimport A\nfn :: Int :+: String -> Int\nfn _ = 1" + ) + + -- Type operator indirect change. + recompile2 it "type op dependency changed" + ( "module A where\ndata T a b = T a b\ninfixl 2 type T as :+:\n" + , "module A where\ndata T b a = T a b\ninfixl 2 type T as :+:\n" + , "module B where\nimport A\nfn :: Int :+: String -> Int\nfn _ = 1" + ) + + -- Type classes changed. Downstream uses type class in signature. + recompile2 it "type class changed" + ( "module A where\nclass Cls a where m1 :: a -> Int\n" + , "module A where\nclass Cls a where m1 :: a -> Char\n" + , T.unlines + [ "module B where" + , "import A as A" + , "fn :: forall a. A.Cls a => a -> Int" + , "fn _ = 1" + ] + ) + + -- Type classes changed. Downstream uses only its member. + recompile2 it "type class changed (member affected)" + ( "module A where\nclass Cls a where m1 :: a -> Int\n" + , "module A where\nclass Cls a where m1 :: a -> Char\n" + , T.unlines + [ "module B where" + , "import A as A" + , "fn x = A.m1 x" + ] + ) + + -- Type class instance added. + recompile2 it "type class instance added" + ( "module A where\nclass Cls a where m1 :: a -> Int\n" + , "module A where\nclass Cls a where m1 :: a -> Int\ninstance Cls Int where m1 _ = 1" + , T.unlines + [ "module B where" + , "import A as A" + , "fn :: forall a. A.Cls a => a -> Int" + , "fn _ = 1" + ] + ) + + -- Type class instance removed. + recompileWithFailure2 it "type class instance removed" + ( "module A where\nclass Cls a where m1 :: a -> Int\ninstance Cls Int where m1 _ = 1" + , "module A where\nclass Cls a where m1 :: a -> Int\n" + , T.unlines + [ "module B where" + , "import A (m1)" + , "x = m1 1" + ] + ) + + -- Type class instance added for a type. We need to recompile downstream + -- modules that use this type, because it can be effected (even if it + -- doesn't use type class as we do not analyze this). + test3 it "recompiles downstream if instance added for a type" + ( "module B where\nimport A\nnewtype T = T Int\n" + , "module B where\nimport A\nnewtype T = T Int\ninstance Cls T where m1 _ = 1" + , "module A where\nclass Cls a where m1 :: a -> Int\n" + , T.unlines + [ "module C where" + , "import B" + , "t = T 1" + ] + ) + ["B", "C"] + + -- Type class instance removed for a type. + testWithFailure3 it "recompiles downstream if type class instance removed for a type" + ( "module B where\nimport A\nnewtype T = T Int\ninstance Cls T where m1 _ = 1" + , "module B where\nimport A\nnewtype T = T Int\n" + , "module A where\nclass Cls a where m1 :: a -> Int\n" + , T.unlines + [ "module C where" + , "import A" + , "import B" + , "i :: Int" + , "i = m1 (T 1)" + ] + ) + ["B", "C"] + + -- Type class instance added for the type and type class in another module + -- it self is modified. We don't need to recompile downstream modules that + -- depend only on type (if they use type class they will be recompiled). + testN it "does not recompile downstream if an instance added for the type and type class changed" + [ ( "A" + , "module A where\nclass Cls a where m1 :: a -> Char\n" + , Just "module A where\nclass Cls a where m1 :: a -> Int\n" + ) + , ( "B" + , "module B where\nimport A\nnewtype T = T Int\n" + , Just "module B where\nimport A\nnewtype T = T Int\ninstance Cls T where m1 _ = 1" + ) + , ("C", "module C where\nimport B\ntype C = T", Nothing) + ] compile ["A", "B"] -- Note [Sleeping to avoid flaky tests] -- -- One of the things we want to test here is that all requested output files -- (via the --codegen CLI option) must be up to date if we are to skip -- recompiling a particular module. Since we check for outdatedness by --- comparing the timestamp of the output files (eg. corefn.json, index.js) to +-- comparing the timestamp of the output files (eg. CoreFn.json, index.js) to -- the timestamp of the externs file, this check is susceptible to flakiness -- if the timestamp resolution is sufficiently coarse. To get around this, we -- delay for one second. @@ -232,8 +704,10 @@ compileWithOptions opts input = do foreigns <- P.inferForeignModules filePathMap let makeActions = (P.buildMakeActions modulesDir filePathMap foreigns True) - { P.progress = \(P.CompilingModule mn _) -> - liftIO $ modifyMVar_ recompiled (return . Set.insert mn) + { P.progress = \case + P.CompilingModule mn _ -> + liftIO $ modifyMVar_ recompiled (return . Set.insert mn) + _ -> pure () } P.make makeActions (map snd ms) @@ -255,17 +729,26 @@ assertSuccess (result, recompiled) = Right _ -> pure recompiled +assertFailure :: (Either P.MultipleErrors a, Set P.ModuleName) -> IO (Set P.ModuleName) +assertFailure (result, recompiled) = + case result of + Left _ -> + pure recompiled + Right _ -> + fail "should compile with errors" + -- | Compile, returning the set of modules which were rebuilt, and failing if -- any errors occurred. compile :: [FilePath] -> IO (Set P.ModuleName) compile input = compileWithResult input >>= assertSuccess -compileAllowingFailures :: [FilePath] -> IO (Set P.ModuleName) -compileAllowingFailures input = fmap snd (compileWithResult input) +compileWithFailure :: [FilePath] -> IO (Set P.ModuleName) +compileWithFailure input = + compileWithResult input >>= assertFailure -writeFileWithTimestamp :: FilePath -> UTCTime -> T.Text -> IO () -writeFileWithTimestamp path mtime contents = do +writeFile :: FilePath -> UTCTime -> T.Text -> IO () +writeFile path mtime contents = do writeUTF8FileT path contents setModificationTime path mtime @@ -273,4 +756,3 @@ writeFileWithTimestamp path mtime contents = do -- from other test results modulesDir :: FilePath modulesDir = ".test_modules" "make" - diff --git a/tests/TestUtils.hs b/tests/TestUtils.hs index 146093c452..97ea465999 100644 --- a/tests/TestUtils.hs +++ b/tests/TestUtils.hs @@ -231,7 +231,7 @@ compile' options expectedModule SupportModules{..} inputFiles = do _ -> do unless hasExpectedModuleName $ error $ "While testing multiple PureScript files, the expected main module was not found: '" <> expectedModuleName <> "'." - compiledModulePath <$ P.make actions (CST.pureResult <$> supportModules ++ map snd ms) + compiledModulePath <$ P.make_ actions (CST.pureResult <$> supportModules ++ map snd ms) getPsModuleName :: (a, AST.Module) -> T.Text getPsModuleName psModule = case snd psModule of diff --git a/tests/purs/bundle/3551/ModuleWithDeadCode.js b/tests/purs/bundle/3551/ModuleWithDeadCode.js new file mode 100644 index 0000000000..faa66d6178 --- /dev/null +++ b/tests/purs/bundle/3551/ModuleWithDeadCode.js @@ -0,0 +1,10 @@ +import * as fs from 'fs'; + +var source = fs.readFileSync(__filename, 'utf-8'); + +export var results = { + fooIsNotEliminated: /^ *var foo =/m.test(source), + barIsExported: /^ *exports\["bar"\] =/m.test(source), + barIsNotEliminated: /^ *var bar =/m.test(source), + exportThatUsesBarIsExported: /^ *exports\["exportThatUsesBar"\] =/m.test(source), +}; diff --git a/tests/purs/bundle/3727.js b/tests/purs/bundle/3727.js new file mode 100644 index 0000000000..d2148a0750 --- /dev/null +++ b/tests/purs/bundle/3727.js @@ -0,0 +1,2 @@ +export var foo = 1; +export { foo as bar }; diff --git a/tests/purs/bundle/ObjectShorthand.js b/tests/purs/bundle/ObjectShorthand.js new file mode 100644 index 0000000000..225e8bf063 --- /dev/null +++ b/tests/purs/bundle/ObjectShorthand.js @@ -0,0 +1,13 @@ +var foo = 1; + +export var bar = { foo }; + +var baz = 2; + +export var quux = function(baz) { + return { baz }; +}; + +import * as fs from 'fs'; +var source = fs.readFileSync(__filename, 'utf-8'); +export var bazIsEliminated = !/^ *var baz =/m.test(source); diff --git a/update-changelog.hs b/update-changelog.hs index b9296440d4..291160ceca 100755 --- a/update-changelog.hs +++ b/update-changelog.hs @@ -25,7 +25,8 @@ , RecordWildCards , TupleSections , ViewPatterns -#-} + #-} -- Hlint requires this leading space + -- | -- This script updates CHANGELOG.md with the contents of CHANGELOG.d, and -- empties CHANGELOG.d. It takes care of: diff --git a/weeder.dhall b/weeder.dhall deleted file mode 100644 index 95686c45e8..0000000000 --- a/weeder.dhall +++ /dev/null @@ -1,41 +0,0 @@ -{ roots = - [ "^Main\\.main$" - , "^PscIdeSpec\\.main$" - - -- These declarations are used in Pursuit. (The Types declarations are - -- reexported in the L.P.Docs module, and referenced from there, but Weeder - -- isn't that smart.) - , "^Language\\.PureScript\\.Docs\\.AsHtml\\.packageAsHtml$" - , "^Language\\.PureScript\\.Docs\\.Types\\.asUploadedPackage$" - , "^Language\\.PureScript\\.Docs\\.Types\\.getLink$" - , "^Language\\.PureScript\\.Docs\\.Types\\.getLinksContext$" - , "^Language\\.PureScript\\.Docs\\.Types\\.packageName$" - , "^Language\\.PureScript\\.Docs\\.Types\\.verifyPackage$" - - -- These declarations are believed to be used in other projects that we want - -- to continue to support. - , "^Language\\.PureScript\\.CoreFn\\.FromJSON\\.moduleFromJSON$" - , "^Language\\.PureScript\\.CST\\.Print\\.printModule$" - - -- These declarations are there to be used during development or testing. - , "^Language\\.PureScript\\.Ide\\.Imports\\.parseImport$" - , "^Language\\.PureScript\\.TypeChecker\\.Monad\\.debug" - - -- These declarations are used by Template Haskell code. - , "^Language\\.PureScript\\.Constants\\.TH\\." - - -- These declarations are produced by Template Haskell when generating - -- pattern synonyms; this confuses Weeder. - , "^Language\\.PureScript\\.Constants\\..*\\.\\$[bm]" - - -- These declarations are unprincipled exceptions that we don't mind - -- supporting just in case they're used now or in the future. - , "^Language\\.PureScript\\.CST\\.Parser\\.parseExpr$" - - -- These declarations are generated by tools; it doesn't matter if they're - -- unused because we can't do anything about them. - , "^Language\\.PureScript\\.CST\\.Parser\\.happy" - , "^Paths_purescript?\\." - ] -, type-class-roots = True -} diff --git a/weeder.toml b/weeder.toml new file mode 100644 index 0000000000..1a8249a2e2 --- /dev/null +++ b/weeder.toml @@ -0,0 +1,40 @@ +roots = [ + "^Main\\.main$", + "^PscIdeSpec\\.main$", + + # These declarations are used in Pursuit. (The Types declarations are + # reexported in the L.P.Docs module, and referenced from there, but Weeder + # isn't that smart.) + "^Language\\.PureScript\\.Docs\\.AsHtml\\.packageAsHtml$", + "^Language\\.PureScript\\.Docs\\.Types\\.asUploadedPackage$", + "^Language\\.PureScript\\.Docs\\.Types\\.getLink$", + "^Language\\.PureScript\\.Docs\\.Types\\.getLinksContext$", + "^Language\\.PureScript\\.Docs\\.Types\\.packageName$", + "^Language\\.PureScript\\.Docs\\.Types\\.verifyPackage$", + + # These declarations are believed to be used in other projects that we want + # to continue to support. + "^Language\\.PureScript\\.CoreFn\\.FromJSON\\.moduleFromJSON$", + "^Language\\.PureScript\\.CST\\.Print\\.printModule$", + + # These declarations are there to be used during development or testing. + "^Language\\.PureScript\\.Ide\\.Imports\\.parseImport$", + "^Language\\.PureScript\\.TypeChecker\\.Monad\\.debug", + + # These declarations are used by Template Haskell code. + "^Language\\.PureScript\\.Constants\\.TH\\.", + + # These declarations are produced by Template Haskell when generating + # pattern synonyms; this confuses Weeder. + "^Language\\.PureScript\\.Constants\\..*\\.\\$[bm]", + + # These declarations are unprincipled exceptions that we don't mind + # supporting just in case they're used now or in the future. + "^Language\\.PureScript\\.CST\\.Parser\\.parseExpr$", + + # These declarations are generated by tools; it doesn't matter if they're + # unused because we can't do anything about them. + "^Language\\.PureScript\\.CST\\.Parser\\.happy", + "^Paths_purescript?\\.", +] +type-class-roots = true