diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 6c453be0d..cea3f2d9f 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "fantomas": { - "version": "7.0.5", + "version": "7.0.6", "commands": [ "fantomas" ], diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5aae1394f..cc565590d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -272,7 +272,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - test: ["default", "no_std", "threaded"] + features: [",", "no_std", "threaded", "lrc_ptr"] steps: - uses: actions/checkout@v7.0.1 @@ -296,7 +296,7 @@ jobs: run: ./build.sh fable-library --rust - name: Fable Tests - Rust - run: ./build.sh test rust --${{ matrix.test }} + run: ./build.sh test rust --features "${{ matrix.features }}" # Separate build job for Dart build-dart: diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index d1fbc0ead..4592c2aa8 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -21,6 +21,6 @@ jobs: - name: Checkout repository uses: actions/checkout@v7.0.1 - name: Install gh-aw extension - uses: github/gh-aw-actions/setup-cli@b77e0d501fd2d2243d1f72722617e80d513f674e # v0.87.0 + uses: github/gh-aw-actions/setup-cli@ea4b911d44a5336c74325a122a5fd9110b45ff06 # v0.87.4 with: version: v0.71.5 diff --git a/.github/workflows/repo-assist.lock.yml b/.github/workflows/repo-assist.lock.yml index 8a2253d09..151ac7ba1 100644 --- a/.github/workflows/repo-assist.lock.yml +++ b/.github/workflows/repo-assist.lock.yml @@ -51,7 +51,7 @@ # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@b77e0d501fd2d2243d1f72722617e80d513f674e # v0.87.0 +# - github/gh-aw-actions/setup@ea4b911d44a5336c74325a122a5fd9110b45ff06 # v0.87.4 # # Container images used: # - ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504 @@ -132,7 +132,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b77e0d501fd2d2243d1f72722617e80d513f674e # v0.87.0 + uses: github/gh-aw-actions/setup@ea4b911d44a5336c74325a122a5fd9110b45ff06 # v0.87.4 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -457,7 +457,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b77e0d501fd2d2243d1f72722617e80d513f674e # v0.87.0 + uses: github/gh-aw-actions/setup@ea4b911d44a5336c74325a122a5fd9110b45ff06 # v0.87.4 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1360,7 +1360,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b77e0d501fd2d2243d1f72722617e80d513f674e # v0.87.0 + uses: github/gh-aw-actions/setup@ea4b911d44a5336c74325a122a5fd9110b45ff06 # v0.87.4 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1560,7 +1560,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b77e0d501fd2d2243d1f72722617e80d513f674e # v0.87.0 + uses: github/gh-aw-actions/setup@ea4b911d44a5336c74325a122a5fd9110b45ff06 # v0.87.4 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1743,7 +1743,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b77e0d501fd2d2243d1f72722617e80d513f674e # v0.87.0 + uses: github/gh-aw-actions/setup@ea4b911d44a5336c74325a122a5fd9110b45ff06 # v0.87.4 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1796,7 +1796,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b77e0d501fd2d2243d1f72722617e80d513f674e # v0.87.0 + uses: github/gh-aw-actions/setup@ea4b911d44a5336c74325a122a5fd9110b45ff06 # v0.87.4 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1897,7 +1897,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b77e0d501fd2d2243d1f72722617e80d513f674e # v0.87.0 + uses: github/gh-aw-actions/setup@ea4b911d44a5336c74325a122a5fd9110b45ff06 # v0.87.4 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} diff --git a/package-lock.json b/package-lock.json index 82a40edb5..38749e54d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "concurrently": "^10.0.5", "ghreleases": "^3.0.2", "nodemon": "^3.1.14", - "rollup": "^4.62.4", + "rollup": "^4.62.5", "terser": "^5.50.0", "typescript": "^7.0.2" }, @@ -88,9 +88,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", - "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.5.tgz", + "integrity": "sha512-jfkGfTwhQpsiSckPF8r9bU3pn3vyd72NlWaO+TgEO6WPSDnUhXzrNYCHBMOYj0ACaUgjm6eERLF+XV9a6RstoA==", "cpu": [ "arm" ], @@ -101,9 +101,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", - "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.5.tgz", + "integrity": "sha512-oGVqyQlxnrz9/ty89oHpU857VUHEl5/Xu4R2lS+aivCTrNnSsbiENzTnNaBsjxH0CNWGPhzHArOLFwo+oKXveA==", "cpu": [ "arm64" ], @@ -114,9 +114,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", - "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.5.tgz", + "integrity": "sha512-bW7B8xMEq8n99Q3ieEcPRGuphurdZAaFzQc9Efyyw3FL6DZO6pMy9xhdN+kBoD7Sy05xNXSr4OyPPnpkYriS/A==", "cpu": [ "arm64" ], @@ -127,9 +127,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", - "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.5.tgz", + "integrity": "sha512-YSwBS86QeHOGlrxJ1PSOIZSkzRL/JmKeunhc+lV6M1a6En8QuVCD/T/qIA0J4Gd2Y86RIOBYrLcOUtqGh9+/1w==", "cpu": [ "x64" ], @@ -140,9 +140,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", - "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.5.tgz", + "integrity": "sha512-2fST8lILgl7cKbme/1KDdPCmbXbG+gqoV3bHp19L0ypX/3akYMBVdOunPleRCwonoLnXOZ/0F+Mt/v8POFmfcQ==", "cpu": [ "arm64" ], @@ -153,9 +153,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", - "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.5.tgz", + "integrity": "sha512-cpIxQCP9J+EVad0a6LO1kY3ZGODlk80VlI+2I96B8xMcdHZ4pLVhfQ49JFpYqjPF91FFkQWftf57YlDcTiw9yQ==", "cpu": [ "x64" ], @@ -166,9 +166,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", - "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.5.tgz", + "integrity": "sha512-r9fGh3eFs3e/udWh5ZjXQtxiYK/xoFxQaYR/cELxac/Udkl5Th+IsFm0CX3Kl9hmUH/we7EoMpjJgeQNnE0+IA==", "cpu": [ "arm" ], @@ -182,9 +182,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", - "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.5.tgz", + "integrity": "sha512-xdvFdp7OM6KLJviJT2g/YuRSUjnZgGHk4RNgwIbN7X6cPugOucV60DdHXWzsBVCUdrGb6qSXnJQrrAKMmQuj3Q==", "cpu": [ "arm" ], @@ -198,9 +198,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", - "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.5.tgz", + "integrity": "sha512-rRqILAndyzHzP7T9NFQrq+4HFWNhqkqkKur7eiBpfLmz01PO0JKx5Vchu3YllE4YXI/Ftgq/szrDWg5GJ0mI8g==", "cpu": [ "arm64" ], @@ -214,9 +214,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", - "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.5.tgz", + "integrity": "sha512-Gf4X3qVMucayUvux6aXXPgXovocSFUC0rrffDuPI/S2nHhNMhjcZxsrAFYCOF350PRreW1XwzFj3CT/3bKsWCw==", "cpu": [ "arm64" ], @@ -230,9 +230,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", - "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.5.tgz", + "integrity": "sha512-+s5qA0TNM0qm8PK/a5gt/1Hpx+NV08uSuCncvhziIlQzT6AEV2fnUQo7eBtFTFO0nA9scauvoR2HusfXmQnO4w==", "cpu": [ "loong64" ], @@ -246,9 +246,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", - "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.5.tgz", + "integrity": "sha512-ybb6QvWwWJCbBWqERpc8K3pYVGIrXlG8MEQ8IIuJY6Y9KdHQxoFoNyfkAOtKn1VHu3KuLidXvwrvGR1mEjeWCw==", "cpu": [ "loong64" ], @@ -262,9 +262,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", - "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.5.tgz", + "integrity": "sha512-nZb1DtnOyhCmYvsC8A2CwOkopVg+IS1+fPUa7rMOAXtNw5+lLCLLPqd6XAiNrGtoQKsbvIBOwsHnBH/3wnb4HQ==", "cpu": [ "ppc64" ], @@ -278,9 +278,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", - "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.5.tgz", + "integrity": "sha512-yMbj63Sp89ryrXLWyz+sy+fYD2HpOnMCLGbe4Oa1smclFSUukdtD/BgdiHaAetJNb74URD8U4hM+qG5KVzMEkg==", "cpu": [ "ppc64" ], @@ -294,9 +294,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", - "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.5.tgz", + "integrity": "sha512-mhoan3OJw2kYV/e1jtIdmvUZgyBFeA6zGWsOswmR0Tg19TQbowZuR+JMLID6spbbBN7Zee2ejrgmy3+FxGrIdA==", "cpu": [ "riscv64" ], @@ -310,9 +310,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", - "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.5.tgz", + "integrity": "sha512-5ZTLmjWbb1VZdjuyhe83K/8QO0/h11midQCBP+X5OYn32ra7eOBoM0ZqtaY4nkgNsYgmdVhMYPoyVPTjUpHf3w==", "cpu": [ "riscv64" ], @@ -326,9 +326,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", - "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.5.tgz", + "integrity": "sha512-m53kG+br6PGxOTmgBEM2DHSDs9RVjsyEbUwjJPJGTFm1grWOG8EKJggDCTb60unD4Tjby8fi7/m9XfkEWasVWg==", "cpu": [ "s390x" ], @@ -342,9 +342,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", - "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.5.tgz", + "integrity": "sha512-6RHPJR1g/uvdYU8uXBnfq3nlqyZCP82Fr6NHgfGoaIeSh0YEqnX/x6uA9MmJJbnSH7swqX4F+CkGdUF+6doiQA==", "cpu": [ "x64" ], @@ -358,9 +358,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", - "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.5.tgz", + "integrity": "sha512-xs+OXQtEXgpXT0DmA5+U3qnRZHdCST/5HRQxS8wSPZTUZN/EMWeHuSIod32LQklTBZBV9DyfncKBQ8n5V3eFdw==", "cpu": [ "x64" ], @@ -374,9 +374,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", - "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.5.tgz", + "integrity": "sha512-e7hD+sl3s+mcLQDZ8pbudBVsdG6r5yN4w3LqG2TJ8sQHDpblWj5lrJs/3m01Cvlxbt4x13zu5thLjgypgtkYzw==", "cpu": [ "x64" ], @@ -387,9 +387,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", - "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.5.tgz", + "integrity": "sha512-GiyJaCf+WpMub/17aPcKk27QMl5W6f+KhdPTjlFOn5akH5Wa/DCM9Stdx5cDfmasyKB08MqpVQ1uJE2RkkpbXg==", "cpu": [ "arm64" ], @@ -400,9 +400,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", - "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.5.tgz", + "integrity": "sha512-+OQ8U2DdoEfXl8T4Fb18AjmEwbXMerKDKCL8yCPAYhKCEEKoul7rkbeGCBFCbAlaGaa7pmtRTpkAJM2LE/i5FA==", "cpu": [ "arm64" ], @@ -413,9 +413,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", - "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.5.tgz", + "integrity": "sha512-KanvAZrPKbDBFwrgiU9yEVpQoox9QPV1WZOXX7HudJQY+eSlu82CtWxDU8WtuRRvtN5EGkLczkd6Y6DTcvm9wA==", "cpu": [ "ia32" ], @@ -426,9 +426,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", - "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.5.tgz", + "integrity": "sha512-1aC3UEWTtRl3RK3VpDJ/Tqk1XI4SLTmXIthAq6wRWo8XiSXJNd+VprJM4/1P4+i6HIaFEFlVi9sTTziniD2tOQ==", "cpu": [ "x64" ], @@ -439,9 +439,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", - "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.5.tgz", + "integrity": "sha512-/gDJaRs4gl0NPIwqCz+6PkpmhhjRAD2j6P4rSNHBzUkO3naEx2mIU0pRle1vUNRQ7mE/+8OOeXLTv/J56FKiQg==", "cpu": [ "x64" ], @@ -2330,9 +2330,9 @@ } }, "node_modules/rollup": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", - "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.5.tgz", + "integrity": "sha512-/tqMfgP7GPA3PHhCmuiS4vIjrSVhHLgY++i+dhbG462euyAj7FpM4D9uq1X3BgjlqRdpcOrYhcQtfiQLNc8tqw==", "license": "MIT", "dependencies": { "@types/estree": "1.0.9" @@ -2346,31 +2346,31 @@ }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", - "@rollup/rollup-android-arm-eabi": "4.62.4", - "@rollup/rollup-android-arm64": "4.62.4", - "@rollup/rollup-darwin-arm64": "4.62.4", - "@rollup/rollup-darwin-x64": "4.62.4", - "@rollup/rollup-freebsd-arm64": "4.62.4", - "@rollup/rollup-freebsd-x64": "4.62.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", - "@rollup/rollup-linux-arm-musleabihf": "4.62.4", - "@rollup/rollup-linux-arm64-gnu": "4.62.4", - "@rollup/rollup-linux-arm64-musl": "4.62.4", - "@rollup/rollup-linux-loong64-gnu": "4.62.4", - "@rollup/rollup-linux-loong64-musl": "4.62.4", - "@rollup/rollup-linux-ppc64-gnu": "4.62.4", - "@rollup/rollup-linux-ppc64-musl": "4.62.4", - "@rollup/rollup-linux-riscv64-gnu": "4.62.4", - "@rollup/rollup-linux-riscv64-musl": "4.62.4", - "@rollup/rollup-linux-s390x-gnu": "4.62.4", - "@rollup/rollup-linux-x64-gnu": "4.62.4", - "@rollup/rollup-linux-x64-musl": "4.62.4", - "@rollup/rollup-openbsd-x64": "4.62.4", - "@rollup/rollup-openharmony-arm64": "4.62.4", - "@rollup/rollup-win32-arm64-msvc": "4.62.4", - "@rollup/rollup-win32-ia32-msvc": "4.62.4", - "@rollup/rollup-win32-x64-gnu": "4.62.4", - "@rollup/rollup-win32-x64-msvc": "4.62.4", + "@rollup/rollup-android-arm-eabi": "4.62.5", + "@rollup/rollup-android-arm64": "4.62.5", + "@rollup/rollup-darwin-arm64": "4.62.5", + "@rollup/rollup-darwin-x64": "4.62.5", + "@rollup/rollup-freebsd-arm64": "4.62.5", + "@rollup/rollup-freebsd-x64": "4.62.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.5", + "@rollup/rollup-linux-arm-musleabihf": "4.62.5", + "@rollup/rollup-linux-arm64-gnu": "4.62.5", + "@rollup/rollup-linux-arm64-musl": "4.62.5", + "@rollup/rollup-linux-loong64-gnu": "4.62.5", + "@rollup/rollup-linux-loong64-musl": "4.62.5", + "@rollup/rollup-linux-ppc64-gnu": "4.62.5", + "@rollup/rollup-linux-ppc64-musl": "4.62.5", + "@rollup/rollup-linux-riscv64-gnu": "4.62.5", + "@rollup/rollup-linux-riscv64-musl": "4.62.5", + "@rollup/rollup-linux-s390x-gnu": "4.62.5", + "@rollup/rollup-linux-x64-gnu": "4.62.5", + "@rollup/rollup-linux-x64-musl": "4.62.5", + "@rollup/rollup-openbsd-x64": "4.62.5", + "@rollup/rollup-openharmony-arm64": "4.62.5", + "@rollup/rollup-win32-arm64-msvc": "4.62.5", + "@rollup/rollup-win32-ia32-msvc": "4.62.5", + "@rollup/rollup-win32-x64-gnu": "4.62.5", + "@rollup/rollup-win32-x64-msvc": "4.62.5", "fsevents": "~2.3.2" } }, diff --git a/package.json b/package.json index a572ab555..a35f6a4ba 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "concurrently": "^10.0.5", "ghreleases": "^3.0.2", "nodemon": "^3.1.14", - "rollup": "^4.62.4", + "rollup": "^4.62.5", "terser": "^5.50.0", "typescript": "^7.0.2" }, diff --git a/pyproject.toml b/pyproject.toml index 0ab80c76a..7e720e8d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,13 +17,13 @@ Homepage = "https://fable.io" [dependency-groups] dev = [ "pytest>=9.1.1,<10", - "ruff>=0.16.3,<0.17", + "ruff>=0.16.4,<0.17", "maturin>=1.14.1,<2", "dunamai>=1.26.2,<2", "pydantic>=2.13.4", "attrs>=26.1.0", "pyright>=1.1.411", - "ty>=0.0.72", + "ty>=0.0.74", ] [tool.hatch.build.targets.sdist] diff --git a/src/Directory.Build.props b/src/Directory.Build.props index fb33a570e..af703934c 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -32,7 +32,7 @@ all analyzers - + all analyzers diff --git a/src/Fable.Build/Main.fs b/src/Fable.Build/Main.fs index d2df0287d..4473cda2c 100644 --- a/src/Fable.Build/Main.fs +++ b/src/Fable.Build/Main.fs @@ -63,8 +63,9 @@ Available commands: Options for Rust: --ast-only Run only the tests for the AST (can be run in watch mode) - --no_std Compile and run the tests without the standard library - --threaded Compile and run the tests with the threaded runtime + --no_std Legacy alias for --features no_std + --threaded Legacy alias for --features threaded + --features Compile and run with comma-separated features; omit or use "," for default features Options for Python: --type-check Run type checking on the generated code with Pyright diff --git a/src/Fable.Build/Test/Rust.fs b/src/Fable.Build/Test/Rust.fs index e37805cdb..e4222c197 100644 --- a/src/Fable.Build/Test/Rust.fs +++ b/src/Fable.Build/Test/Rust.fs @@ -27,12 +27,37 @@ let handle (args: string list) = let forceFableLibrary = args |> List.contains "--force-fable-library" let isWatch = args |> List.contains "--watch" let astOnly = args |> List.contains "--ast-only" - let noStd = args |> List.contains "--no_std" - let threaded = args |> List.contains "--threaded" let noDotnet = args |> List.contains "--no-dotnet" + let requestedFeatures = + match args |> List.tryFindIndex ((=) "--features") with + | Some index -> + args + |> List.tryItem (index + 1) + |> Option.defaultValue "," + |> fun features -> + features.Split([| ',' |], System.StringSplitOptions.RemoveEmptyEntries) + |> Array.map (fun feature -> feature.Trim()) + |> Array.filter (fun feature -> feature <> "") + |> Array.toList + | None -> [] + + let legacyFeatures = + [ "--no_std", "no_std"; "--threaded", "threaded" ] + |> List.choose (fun (flag, feature) -> + if args |> List.contains flag then + Some feature + else + None + ) + + let features = requestedFeatures @ legacyFeatures |> List.distinct + + let noStd = features |> List.contains "no_std" + let threaded = features |> List.contains "threaded" + if noStd && threaded then - failwith "Cannot use --no-std and --threaded at the same time" + failwith "Cannot use no_std and threaded features together" BuildFableLibraryRust().Run(forceFableLibrary) @@ -58,13 +83,19 @@ let handle (args: string list) = Shell.copyFile destination file ) + let cargoFeatures = features |> String.concat "," + let cargoTestArgs = - if noStd then - "cargo test --features no_std -- --test-threads=1" - elif threaded then - "cargo test --features threaded" + let cargoTest = + if List.isEmpty features then + "cargo test" + else + $"cargo test --features {cargoFeatures}" + + if threaded then + cargoTest else - "cargo test -- --test-threads=1" + $"{cargoTest} -- --test-threads=1" let fableArgs = CmdLine.concat diff --git a/src/Fable.Transforms/Rust/Fable2Rust.fs b/src/Fable.Transforms/Rust/Fable2Rust.fs index 4bfad14e5..3a2a3d289 100644 --- a/src/Fable.Transforms/Rust/Fable2Rust.fs +++ b/src/Fable.Transforms/Rust/Fable2Rust.fs @@ -55,7 +55,9 @@ type Context = ScopedSymbols: FSharp.Collections.Map // HasMultipleUses: bool //this could be a closure in a map, or a for loop. The point is anything leaving the scope cannot be assumed to be the only reference InferAnyType: bool + SkipRecordTypeRegistration: bool IsAssocMember: bool + IsFluentMember: bool IsLambda: bool IsParamByRefPreferred: bool RequiresSendSync: bool // a way to implicitly propagate Arc's down the hierarchy when it is not possible to explicitly tag @@ -626,6 +628,14 @@ module TypeInfo = let ent = com.GetEntity(entRef) (ent.IsFSharpRecord || ent.IsFSharpUnion) && not ent.IsValueType + let isReferenceClass (com: IRustCompiler) (entRef: Fable.EntityRef) = + let ent = com.GetEntity(entRef) + + not ent.IsInterface + && not ent.IsValueType + && not ent.IsFSharpRecord + && not ent.IsFSharpUnion + // Checks whether the type needs a ref counted wrapper // such as Rc (or Arc in a multithreaded context) let shouldBeRefCountWrapped (com: IRustCompiler) ctx typ = @@ -1024,6 +1034,10 @@ module TypeInfo = let entName = getLibraryImportName com ctx "Random" "Random" let genArgsOpt = transformGenArgs com ctx genArgs makeFullNamePathTy entName genArgsOpt + | _ when entRef.FullName = "System.Uri" -> + let entName = getLibraryImportName com ctx "Uri" "Uri" + let genArgsOpt = transformGenArgs com ctx genArgs + makeFullNamePathTy entName genArgsOpt | ent -> let entName = getEntityFullName com ctx entRef let genArgsOpt = transformGenArgs com ctx genArgs @@ -1177,11 +1191,16 @@ module TypeInfo = // implemented random type | Replacements.Util.IsEntity (Types.random) (_, []) -> transformImportType com ctx [] "Random" "Random" + // implemented uri type + | Replacements.Util.IsEntity ("System.Uri") (_, []) -> transformImportType com ctx [] "Uri" "Uri" + // implemented event type (FSharpEvent`1); the module functions and // the .ctor are routed to Event_ by the events replacement, so the // concrete type must resolve to Event_::FSharpEvent`1 as well. | Replacements.Util.IsEntity ("Microsoft.FSharp.Control.FSharpEvent`1") (_, [ t ]) -> transformImportType com ctx [ t ] "Event" "FSharpEvent`1" + | Replacements.Util.IsEntity ("Microsoft.FSharp.Control.FSharpEvent`2") (_, [ _delegateType; argsType ]) -> + transformImportType com ctx [ argsType ] "Event" "FSharpEvent`2" // implemented regex types | Replacements.Util.IsEntity (Types.regexMatch) (_, []) -> transformImportType com ctx [] "RegExp" "Match" @@ -1619,22 +1638,42 @@ module Util = let ar = makeLibCall com ctx None "HashMap" "entries" [ expr ] makeLibCall com ctx None "Seq" "ofArray" [ ar ] - // boxing value types or wrapped types - | t, Fable.Any when isValueType com t || isWrappedType com t -> expr |> boxValue com ctx + // Boxing reads a value through an owned Rust expression. Clone first so fields + // read from reference-counted records are not moved out of their owner. + | t, Fable.Any when isValueType com t || isWrappedType com t -> expr |> makeClone |> boxValue com ctx // unboxing value types or wrapped types | Fable.Any, t when isValueType com t || isWrappedType com t -> expr |> unboxValue com ctx t - // boxing a reference-typed record/union (Lrc-wrapped) to obj: coerce the - // smart pointer to `dyn Any` so its concrete pointee stays the struct. - | Fable.DeclaredType(entRef, _), Fable.Any when isReferenceRecordOrUnion com entRef -> - [ expr ] |> makeLibCall com ctx None "Native" "box_lrc" + // Boxing a reference-typed record/union must use the same pointer representation + // as the source value. The LrcPtr case preserves identity; the explicit pointer + // attributes use a cloned underlying value so the object still carries its record + // TypeId even when Rc/Arc/Box cannot be unsized to the target object pointer. + | Fable.DeclaredType(entRef, genArgs), Fable.Any when isReferenceRecordOrUnion com entRef -> + let boxMethod = + match shouldBeRefCountWrapped com ctx (Fable.DeclaredType(entRef, genArgs)) with + | Some Lrc -> "box_lrc" + | Some Rc -> "box_rc" + | Some Arc -> "box_arc" + | Some Box -> "box_box" + | None -> "box_" + + [ expr |> makeClone ] |> makeLibCall com ctx None "Native" boxMethod // unboxing obj back to a reference-typed record/union: downcast + re-wrap. | Fable.Any, Fable.DeclaredType(entRef, genArgs) when isReferenceRecordOrUnion com entRef -> let rawTy = transformEntityType com ctx entRef genArgs let genArgsOpt = [ rawTy ] |> mkTypesGenericArgs - [ expr ] |> makeLibCall com ctx genArgsOpt "Native" "unbox_lrc" + + let unboxMethod = + match shouldBeRefCountWrapped com ctx (Fable.DeclaredType(entRef, genArgs)) with + | Some Lrc -> "unbox_lrc" + | Some Rc -> "unbox_rc" + | Some Arc -> "unbox_arc" + | Some Box -> "unbox_box" + | None -> "unbox" + + [ expr ] |> makeLibCall com ctx genArgsOpt "Native" unboxMethod // casts to generic param | _, Fable.GenericParam(name, _isMeasure, _constraints) -> makeCall (name :: "from" :: []) None [ expr ] // e.g. T::from(value) @@ -1642,6 +1681,12 @@ module Util = // casts to IDictionary, for now does nothing // TODO: fix it | Replacements.Util.IsEntity (Types.dictionary) _, Replacements.Util.IsEntity (Types.idictionary) _ -> expr + // IEvent inherits IObservable in F#, but the generated Rust traits are flattened + // rather than related by Rust trait inheritance. Adapt the event view explicitly. + | Replacements.Util.IsEntity ("Microsoft.FSharp.Control.IEvent`2") (_, [ _; eventType ]), + Replacements.Util.IsEntity ("System.IObservable`1") (_, [ observableType ]) when eventType = observableType -> + makeLibCall com ctx None "Event" "asObservable" [ expr ] + // casts from object to interface | t1, t2 when not (isInterface com t1) && (isInterface com t2) -> makeInterfaceCast com ctx t2 expr @@ -1787,7 +1832,35 @@ module Util = let ctx = { ctx with IsParamByRefPreferred = isByRefPreferred || ctx.IsParamByRefPreferred } - transformLeaveContext com ctx argType arg + match argType, arg.Type with + | Some Fable.Any, actualType -> + match arg, actualType with + | Fable.IdentExpr ident, _ when ident.IsThisArgument && ctx.IsAssocMember -> + let expr = transformLeaveContext com ctx (Some Fable.Any) arg + [ expr |> makeClone ] |> makeLibCall com ctx None "Native" "box_lrc" + | MaybeCasted(Fable.IdentExpr ident), _ when ident.IsThisArgument && ctx.IsAssocMember -> + let expr = transformLeaveContext com ctx (Some Fable.Any) arg + [ expr |> makeClone ] |> makeLibCall com ctx None "Native" "box_lrc" + | _, Fable.DeclaredType(entRef, genArgs) when isReferenceClass com entRef -> + let expr = transformLeaveContext com ctx (Some Fable.Any) arg + + let boxMethod = + match shouldBeRefCountWrapped com ctx (Fable.DeclaredType(entRef, genArgs)) with + | Some Lrc -> "box_lrc" + | Some Rc -> "box_rc" + | Some Arc -> "box_arc" + | Some Box -> "box_box" + | None -> "box_" + + [ expr |> makeClone ] |> makeLibCall com ctx None "Native" boxMethod + | _, actualType when actualType <> Fable.Any -> transformCast com ctx Fable.Any arg + | _ -> transformLeaveContext com ctx (Some Fable.Any) arg + | Some(Replacements.Util.IsEntity ("System.IObservable`1") (_, [ observableType ])), + Replacements.Util.IsEntity ("Microsoft.FSharp.Control.IEvent`2") (_, [ _; eventType ]) when + observableType = eventType + -> + transformCast com ctx observableType arg + | _ -> transformLeaveContext com ctx argType arg ) let makeRefForPatternMatch (com: IRustCompiler) ctx typ (nameOpt: string option) fableExpr = @@ -1912,11 +1985,6 @@ module Util = makeLibCall com ctx None "Native" "is_null" [ value |> mkAddrOfExpr ] let makeNull com ctx (typ: Fable.Type) = - let typ = - match typ with - | Fable.Any -> Fable.Unit - | t -> t - let genArgsOpt = transformGenArgs com ctx [ typ ] makeLibCall com ctx genArgsOpt "Native" "null" [] @@ -2070,7 +2138,15 @@ module Util = let entName = getEntityFullName com ctx entRef let path = makeFullNamePath entName genArgsOpt let expr = mkStructExpr path fields // TODO: range - expr |> maybeWrapSmartPtr com ctx ent + let expr = expr |> maybeWrapSmartPtr com ctx ent + + if ctx.SkipRecordTypeRegistration || isFableLibrary com || not ent.IsFSharpRecord then + expr + else + let registrationCtx = { ctx with SkipRecordTypeRegistration = true } + let typ = Fable.DeclaredType(entRef, genArgs) + let registration = makeRecordTypeInfo com registrationCtx r entRef genArgs typ + [ registration |> mkSemiStmt; expr |> mkExprStmt ] |> mkStmtBlockExpr let tryUseKnownUnionCaseNames fullName = match fullName with @@ -2195,7 +2271,7 @@ module Util = |> List.map (fun ident -> let oIdent = makeTypedIdent objType "o" let recExpr = Fable.TypeCast(Fable.IdentExpr oIdent, typ) // unbox obj -> Foo - let fieldInfo = Fable.FieldInfo.Create(ident.Name, ident.Type) + let fieldInfo = Fable.FieldInfo.Create(ident.Name, ident.Type, ident.IsMutable) let fieldGet = Fable.Get(recExpr, fieldInfo, ident.Type, None) let body = Fable.TypeCast(fieldGet, objType) // box the field value Fable.Delegate([ oIdent ], body, None, Fable.Tags.empty) @@ -2332,6 +2408,19 @@ module Util = let ctx = { ctx with IsParamByRefPreferred = false } com.TransformExpr(ctx, e) + let expr = + if isThisArgumentIdentExpr ctx e && not targetIsRef && not ctx.IsFluentMember then + let expr = expr |> makeClone + + match shouldBeRefCountWrapped com ctx e.Type with + | Some Lrc -> makeLrcPtrValue com ctx expr + | Some Rc -> makeRcValue com ctx expr + | Some Arc -> makeArcValue com ctx expr + | Some Box -> makeBoxValue com ctx expr + | None -> expr + else + expr + match sourceIsRef, targetIsRef, implCopy, implClone, mustClone, isUnreachable with | true, true, _, _, _, false -> expr | false, true, _, _, _, false -> expr |> mkAddrOfExpr @@ -2376,7 +2465,6 @@ module Util = assert (ent.IsInterface) FSharp2Fable.Util.getInterfaceMembers com ent - |> Seq.filter (fun (ifc, memb) -> not memb.IsProperty) |> Seq.distinctBy (fun (ifc, memb) -> memb.CompiledName) // skip inherited overwrites let getInterfaceMemberNames (com: IRustCompiler) (entRef: Fable.EntityRef) = @@ -2387,7 +2475,11 @@ module Util = |> Set.ofSeq let makeObjectExprMemberDecl (com: IRustCompiler) ctx (memb: Fable.ObjectExprMember) = - let capturedIdents = getCapturedIdents com ctx (Some memb.Name) memb.Args memb.Body + let capturedIdents = + getCapturedIdents com ctx (Some memb.Name) memb.Args memb.Body + |> Map.toList + |> List.map (fun (_, (ident: Fable.Ident)) -> ident.Name, ident) + |> Map.ofList let thisArg = { makeIdent selfName with IsThisArgument = true } |> Fable.IdentExpr @@ -2458,17 +2550,6 @@ module Util = memberDecl ) - let decl: Fable.ClassDecl = - { - Name = entName - Entity = entRef - Constructor = None - BaseCall = baseCall - AttachedMembers = members - XmlDoc = None - Tags = [] - } - let fieldIdents = fieldsMap.Values // |> Seq.map (fun ident -> { ident with Type = FableTransforms.uncurryType ident.Type }) @@ -2478,6 +2559,18 @@ module Util = fieldIdents |> List.map (fun ident -> let expr = transformIdent com ctx None ident |> makeClone + + let expr = + if ident.IsThisArgument then + match shouldBeRefCountWrapped com ctx ident.Type with + | Some Lrc -> makeLrcPtrValue com ctx expr + | Some Rc -> makeRcValue com ctx expr + | Some Arc -> makeArcValue com ctx expr + | Some Box -> makeBoxValue com ctx expr + | None -> expr + else + expr + let fieldName = ident.Name |> sanitizeMember mkExprField [] fieldName expr false false ) @@ -2490,6 +2583,122 @@ module Util = else fieldIdents + let interfaceGenMap = + let genericNames = + ent.GenericParameters |> List.map (fun parameter -> parameter.Name) + + List.zip genericNames genArgs |> Map + + let existingMemberNames = + members + |> List.map (getDeclMember com >> fun memberValue -> memberValue.CompiledName) + |> Set.ofList + + let eventPropertyNames = + existingMemberNames + |> Seq.choose (fun memberName -> + if memberName.StartsWith("add_", StringComparison.Ordinal) then + Some(memberName.Substring(4)) + elif memberName.StartsWith("remove_", StringComparison.Ordinal) then + Some(memberName.Substring(7)) + else + None + ) + |> Set.ofSeq + + let eventGetters = + FSharp2Fable.Util.getInterfaceMembers com ent + |> Seq.filter (fun (_, interfaceMember) -> + interfaceMember.CompiledName.StartsWith("get_", StringComparison.Ordinal) + ) + |> Seq.distinctBy (fun (_, interfaceMember) -> interfaceMember.CompiledName) + |> Seq.choose (fun (_, interfaceMember) -> + let propertyName = interfaceMember.CompiledName.Substring(4) + + if + Set.contains interfaceMember.CompiledName existingMemberNames + || not (Set.contains propertyName eventPropertyNames) + then + None + else + fieldIdents + |> List.tryFind (fun ident -> + ident.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase) + ) + |> Option.map (fun backingField -> + let propertyType = + interfaceMember.ReturnParameter.Type + |> resolveInlineType interfaceGenMap + |> FableTransforms.uncurryType + + let interfaceType = Fable.DeclaredType(entRef, genArgs) + + let thisArg = { makeTypedIdent interfaceType "this" with IsThisArgument = true } + + let objectSelf = + { makeIdent selfName with IsThisArgument = true } |> Fable.IdentExpr + + let eventExpr = + Fable.Get( + objectSelf, + Fable.FieldInfo.Create(backingField.Name, backingField.Type), + backingField.Type, + None + ) + + let body = + Fable.Get( + eventExpr, + Fable.FieldInfo.Create("Publish", propertyType), + propertyType, + None + ) + + let memberRef = + Fable.MemberRef( + interfaceMember.DeclaringEntity |> Option.defaultValue entRef, + { + IsInstance = true + CompiledName = interfaceMember.CompiledName + NonCurriedArgTypes = Some [] + AttributeFullNames = [] + } + ) + + let memberDecl: Fable.MemberDecl = + { + Name = interfaceMember.CompiledName + Args = [ thisArg ] + Body = body + MemberRef = memberRef + IsMangled = false + ImplementedSignatureRef = None + UsedNames = Set.empty + XmlDoc = None + Tags = [] + } + + memberDecl + ) + ) + |> Seq.toList + + let members = members @ eventGetters + + let decl: Fable.ClassDecl = + { + Name = entName + Entity = entRef + Constructor = None + BaseCall = baseCall + AttachedMembers = members + XmlDoc = None + Tags = [] + } + + let objectGenArgs = + FSharp2Fable.Util.getGenParamTypes (genArgs @ (fieldIdents |> List.map (fun ident -> ident.Type))) + let fields = fieldIdents |> List.map (fun ident -> @@ -2507,13 +2716,13 @@ module Util = exprField :: exprFields | _ -> exprFields - let attrs = [ mkAttr "derive" (makeDerivedFrom com ctx ent) ] - let generics = makeGenerics com ctx genArgs - let genParams = FSharp2Fable.Util.getGenParamTypes genArgs + let attrs = [ mkAttr "derive" [ rawIdent "Clone" ] ] + let generics = makeGenerics com ctx objectGenArgs + let genParams = FSharp2Fable.Util.getGenParamTypes objectGenArgs let structItems = [ mkStructItem attrs entName fields generics ] - let memberItems = transformClassMembers com ctx genArgs decl + let memberItems = transformClassMembers com ctx objectGenArgs decl (Some genArgs) let objExpr = // match baseCall with @@ -2739,6 +2948,12 @@ module Util = if needGenArgs && (mi.IsModuleMember || not mi.IsInstanceMember) then match typ with | Fable.Tuple _ -> transformGenArgs com ctx [ typ ] + // FSharpEvent`2 has two F# generic args but the Rust FSharpEvent_2 + // struct only takes the args type, so drop the delegate type + | Replacements.Util.IsEntity ("Microsoft.FSharp.Control.FSharpEvent`2") (_, + [ _delegateType + argsType ]) -> + transformGenArgs com ctx [ argsType ] | _ -> transformGenArgs com ctx typ.Generics else None @@ -4701,6 +4916,15 @@ module Util = let args = decl.Args let body = decl.Body + let returnType = memb.ReturnParameter.Type |> FableTransforms.uncurryType + + let isFluentMember = + match memb.DeclaringEntity, returnType with + | Some declaringEntity, Fable.DeclaredType(returnEntity, _) -> + declaringEntity.FullName = returnEntity.FullName + | _ -> false + + let ctx = { ctx with IsFluentMember = isFluentMember } let body = if memb.IsInstance && not (memb.IsConstructor) then @@ -5420,14 +5644,81 @@ module Util = let ifcEnt = com.GetEntity(ifcEntRef) FSharp2Fable.Util.getEntityGenArgs ifcEnt + let specializeParameter (genMap: Map) (parameter: Fable.Parameter) = + { new Fable.Parameter with + member _.Attributes = parameter.Attributes + member _.Name = parameter.Name + member _.Type = parameter.Type |> resolveInlineType genMap + member _.IsIn = parameter.IsIn + member _.IsOut = parameter.IsOut + member _.IsNamed = parameter.IsNamed + member _.IsOptional = parameter.IsOptional + member _.DefaultValue = parameter.DefaultValue + } + + let specializeInterfaceMember (genMap: Map) (memberValue: Fable.MemberFunctionOrValue) = + { new Fable.MemberFunctionOrValue with + member _.DisplayName = memberValue.DisplayName + member _.CompiledName = memberValue.CompiledName + member _.FullName = memberValue.FullName + member _.Attributes = memberValue.Attributes + member _.HasSpread = memberValue.HasSpread + member _.IsInline = memberValue.IsInline + member _.IsPublic = memberValue.IsPublic + member _.IsPrivate = memberValue.IsPrivate + member _.IsInternal = memberValue.IsInternal + member _.IsConstructor = memberValue.IsConstructor + member _.IsInstance = memberValue.IsInstance + member _.IsExtension = memberValue.IsExtension + member _.IsValue = memberValue.IsValue + member _.IsMutable = memberValue.IsMutable + member _.IsGetter = memberValue.IsGetter + member _.IsSetter = memberValue.IsSetter + member _.IsProperty = memberValue.IsProperty + + member _.IsOverrideOrExplicitInterfaceImplementation = + memberValue.IsOverrideOrExplicitInterfaceImplementation + + member _.IsDispatchSlot = memberValue.IsDispatchSlot + + member _.GenericParameters = + memberValue.GenericParameters + |> List.filter (fun parameter -> not (Map.containsKey parameter.Name genMap)) + + member _.CurriedParameterGroups = + memberValue.CurriedParameterGroups + |> List.map (List.map (specializeParameter genMap)) + + member _.ReturnParameter = specializeParameter genMap memberValue.ReturnParameter + member _.DeclaringEntity = memberValue.DeclaringEntity + member _.ApparentEnclosingEntity = memberValue.ApparentEnclosingEntity + member _.ImplementedAbstractSignatures = memberValue.ImplementedAbstractSignatures + member _.XmlDoc = memberValue.XmlDoc + } + let ignoredInterfaceNames = set [ Types.ienumerable; Types.ienumerator ] - let transformClassMembers (com: IRustCompiler) ctx genArgs (classDecl: Fable.ClassDecl) = + let transformClassMembers + (com: IRustCompiler) + ctx + genArgs + (classDecl: Fable.ClassDecl) + (objectExprInterfaceGenArgs: Fable.Type list option) + = let entRef = classDecl.Entity let ent = com.GetEntity(entRef) let isObjectExpr = classDecl.Name = "ObjectExpr" + let objectExprInterfaceGenMap = + match objectExprInterfaceGenArgs with + | Some interfaceGenArgs -> + let genericNames = + ent.GenericParameters |> List.map (fun parameter -> parameter.Name) + + List.zip genericNames interfaceGenArgs |> Map + | None -> Map.empty + let entName = if isObjectExpr then classDecl.Name @@ -5454,7 +5745,14 @@ module Util = let interfaceMembers, nonInterfaceMembers = classDecl.AttachedMembers - |> List.map (fun decl -> decl, getDeclMember com decl) + |> List.map (fun decl -> + let memberValue = getDeclMember com decl + + if isObjectExpr then + decl, specializeInterfaceMember objectExprInterfaceGenMap memberValue + else + decl, memberValue + ) |> List.partition (snd >> isInterfaceMember com) let nonInterfaceImpls = @@ -5483,7 +5781,7 @@ module Util = let baseTypeOpt = if isObjectExpr then - Some(Fable.DeclaredType(entRef, genArgs)) + Some(Fable.DeclaredType(entRef, objectExprInterfaceGenArgs |> Option.defaultValue genArgs)) else ent.BaseType |> Option.map (fun t -> Fable.DeclaredType(t.Entity, t.GenericArgs)) @@ -5499,7 +5797,7 @@ module Util = let hasToString = nonInterfaceMembers |> List.exists (fun (d, m) -> m.CompiledName = "ToString") - let hasDebug = not (List.isEmpty ent.GenericParameters) + let hasDebug = isObjectExpr || not (List.isEmpty ent.GenericParameters) makeDisplayTraitImpls com ctx entName genParams hasToString hasDebug let operatorTraitImpls = @@ -5541,7 +5839,7 @@ module Util = // different arity - so blindly reusing genArgs would emit e.g. // `IDisposable`. Look those up in the declared type's hierarchy. if ifcEntRef.FullName = entRef.FullName then - genArgs + objectExprInterfaceGenArgs |> Option.defaultValue genArgs else ifcEntRef |> findInterfaceGenArgs com ent else @@ -5586,7 +5884,7 @@ module Util = |> entityItemWithVis com ctx ent let genArgs = FSharp2Fable.Util.getEntityGenArgs ent - let memberItems = transformClassMembers com ctx genArgs decl + let memberItems = transformClassMembers com ctx genArgs decl None entityItem :: memberItems let getVis (com: IRustCompiler) ctx declaringEntity isInternal isPrivate = @@ -5996,7 +6294,9 @@ module Compiler = ScopedSymbols = Map.empty // HasMultipleUses = false InferAnyType = false + SkipRecordTypeRegistration = false IsAssocMember = false + IsFluentMember = false IsLambda = false IsParamByRefPreferred = false RequiresSendSync = false diff --git a/src/Fable.Transforms/Rust/Replacements.fs b/src/Fable.Transforms/Rust/Replacements.fs index 604d1db1d..448e3f2a6 100644 --- a/src/Fable.Transforms/Rust/Replacements.fs +++ b/src/Fable.Transforms/Rust/Replacements.fs @@ -3130,7 +3130,13 @@ let enumerables (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr let events (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg with | ".ctor", _ -> - Helper.LibCall(com, "Event", "default", t, args, i.SignatureArgTypes, isConstructor = true, ?loc = r) + let constructor = + if i.DeclaringEntityFullName.EndsWith("`2", StringComparison.Ordinal) then + "default2" + else + "default" + + Helper.LibCall(com, "Event", constructor, t, args, i.SignatureArgTypes, isConstructor = true, ?loc = r) |> Some | "get_Publish", Some callee -> getFieldWith r t callee "Publish" |> Some | meth, Some callee -> makeInstanceCall r t i callee meth args |> Some @@ -3314,33 +3320,52 @@ let uris = match i.CompiledName with | ".ctor" -> - Helper.LibCall(com, "Uri", "Uri.create", t, args, i.SignatureArgTypes, ?loc = r) - |> Some + match args with + | [ ExprType String ] -> + Helper.LibCall(com, "Uri", "create", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | [ ExprType String; _ ] -> + Helper.LibCall(com, "Uri", "createWithKind", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | [ _; ExprType String ] -> + Helper.LibCall(com, "Uri", "createFromString", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | [ _; _ ] -> + Helper.LibCall(com, "Uri", "createFromUri", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | _ -> None | "TryCreate" -> - Helper.LibCall(com, "Uri", "Uri.tryCreate", t, args, i.SignatureArgTypes, ?loc = r) - |> Some + match args with + | (ExprType String) :: _ -> + Helper.LibCall(com, "Uri", "tryCreateWithKind", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | _ :: (ExprType String) :: _ -> + Helper.LibCall(com, "Uri", "tryCreateFromString", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | _ -> + Helper.LibCall(com, "Uri", "tryCreateFromUri", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "ToString" -> toString com ctx r [ thisArg.Value ] |> Some | "UnescapeDataString" -> - Helper.LibCall(com, "Util", "unescapeDataString", t, args, i.SignatureArgTypes) + Helper.LibCall(com, "Uri", "unescapeDataString", t, args, i.SignatureArgTypes) |> Some | "EscapeDataString" -> - Helper.LibCall(com, "Util", "escapeDataString", t, args, i.SignatureArgTypes) + Helper.LibCall(com, "Uri", "escapeDataString", t, args, i.SignatureArgTypes) |> Some | "EscapeUriString" -> - Helper.LibCall(com, "Util", "escapeUriString", t, args, i.SignatureArgTypes) + Helper.LibCall(com, "Uri", "escapeUriString", t, args, i.SignatureArgTypes) |> Some | "get_IsAbsoluteUri" | "get_Scheme" | "get_Host" + | "get_Port" + | "get_IsDefaultPort" | "get_AbsolutePath" | "get_AbsoluteUri" | "get_PathAndQuery" | "get_Query" | "get_Fragment" - | "get_OriginalString" -> - Naming.removeGetSetPrefix i.CompiledName - |> Naming.lowerFirst - |> getFieldWith r t thisArg.Value - |> Some + | "get_OriginalString" -> makeMemberCall com ctx r t i "Uri" i.CompiledName thisArg args |> Some | _ -> None let laziness (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = diff --git a/src/fable-library-py/uv.lock b/src/fable-library-py/uv.lock index c74e4a75e..50fd21f86 100644 --- a/src/fable-library-py/uv.lock +++ b/src/fable-library-py/uv.lock @@ -61,94 +61,94 @@ dev = [ [[package]] name = "hypothesis" -version = "6.165.9" +version = "6.165.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/a8/f7afb4b55a7f00307f246fb7c3cf2abdc8f8e0ad7eb81bf23f8025480da6/hypothesis-6.165.9.tar.gz", hash = "sha256:a5d1fa312020b499c517e1217ef4440c56a9b52e20b47b420b48a583a81c412b", size = 503594, upload-time = "2026-08-15T02:50:07.518Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/e2/0fad246d2b6330e1f78479bfc566b5c22be82aee8a865cde9a08f648487d/hypothesis-6.165.10.tar.gz", hash = "sha256:68b45e09834cd80523cb1eb274463073c7a9af4e4ef7cff34d9615f355572d32", size = 503703, upload-time = "2026-08-16T22:56:15.404Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/c7/da4298709de649a01c73ce93c4ee5b5651433217e17c494dc91baded86b0/hypothesis-6.165.9-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:01a2a4106fa90396ffa1c402968a8bd25af056e1fdf7974a369c4057cc4035f1", size = 783107, upload-time = "2026-08-15T02:49:32.01Z" }, - { url = "https://files.pythonhosted.org/packages/58/97/d9f265174464d05a4d818d98dcd4e870ad2c2b60e9844033c23b3eb21eda/hypothesis-6.165.9-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:7812f1ce9e2bfc0958f2a96975f16a1e1f2400f9d8b7043723ff5811647fe0f6", size = 778677, upload-time = "2026-08-15T02:48:16.984Z" }, - { url = "https://files.pythonhosted.org/packages/ae/fe/d3364f99dd76f1275b430b2fc2818119df84e71f0657ad7f89b922e90d55/hypothesis-6.165.9-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e547823eca16eab9c9569f292e5ce8f95d8a59f1d67e504e97a841d17eb0b562", size = 1107895, upload-time = "2026-08-15T02:47:42.954Z" }, - { url = "https://files.pythonhosted.org/packages/71/5d/42b524303219351add359a05463df067be126c9ca576b0547218cf8aa54b/hypothesis-6.165.9-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e50c687c0e86bb5898f36b845ab84439f44ef2369f0f9bbcbf6aa6860640324d", size = 1136455, upload-time = "2026-08-15T02:49:56.069Z" }, - { url = "https://files.pythonhosted.org/packages/af/d5/91948f68b5007a40b2cc356d9365de0f5cb08c200e383b38259ad75130ae/hypothesis-6.165.9-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f82aed6f0631b5073bd87789aa56609e7c83bd762384fd24c84f3435816b2569", size = 1135112, upload-time = "2026-08-15T02:48:51.211Z" }, - { url = "https://files.pythonhosted.org/packages/55/3c/6e04ac80c5a63da9ecff00199851433437f617da9cd1b5208f11af3337b8/hypothesis-6.165.9-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488b88b6856e430763a7ac5a5fc6e20a798766dca8df8f94576de2281ee60afd", size = 1157397, upload-time = "2026-08-15T02:49:27.874Z" }, - { url = "https://files.pythonhosted.org/packages/12/40/4a1aeddc254d8d6a8f43bbeea928ef5b576e041eb0c9459a91fce884f4cd/hypothesis-6.165.9-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:22dd9e264f2a79a1b63a5fae13351e3cf0088f584fb05bb0e23f21b8fba9df7c", size = 1112748, upload-time = "2026-08-15T02:48:35.764Z" }, - { url = "https://files.pythonhosted.org/packages/35/bc/dde8231f1d10ab0c60627c40757b9b832605028ef0780420fcfc3ccba664/hypothesis-6.165.9-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:431ab894062666c93157234f9949a4db3549fbf3c220777abae3482e1b43f9bc", size = 1149484, upload-time = "2026-08-15T02:48:53.088Z" }, - { url = "https://files.pythonhosted.org/packages/1b/b2/cb29a983c7f06770a6b348f76cb33f90a70b0594a1e61c4a803aedfb1d1f/hypothesis-6.165.9-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c7edcc3dcec42b413c900f8e660f3741c21f0055c53bde21d728f72a21713247", size = 1283267, upload-time = "2026-08-15T02:47:55.688Z" }, - { url = "https://files.pythonhosted.org/packages/88/c0/6bfcf3fb379aba87f3d09073b589c7b7e932ad047ed7bc0124300a51f431/hypothesis-6.165.9-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0d4a613404ba216b6f698ce9df6f9c22d04d8e8a571c42525e83905cf1b1cd10", size = 1409841, upload-time = "2026-08-15T02:48:23.939Z" }, - { url = "https://files.pythonhosted.org/packages/98/c0/0c73a985144717272d2afec377a45c149688a477af946ae24c16b59c9788/hypothesis-6.165.9-cp310-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:658c33d8a54cead6081f73fcc4e2c0fbe43d251d3f4170630748d8e5dcbd542f", size = 1264878, upload-time = "2026-08-15T02:50:05.056Z" }, - { url = "https://files.pythonhosted.org/packages/56/91/50d4a6df07a305f5f17578443c93b618a49e8db68c01de8870e539139eb9/hypothesis-6.165.9-cp310-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:7d3297fe95a253cea81b48e581e456105016ebe782d823dfb57ec09c3854301f", size = 1282519, upload-time = "2026-08-15T02:48:22.42Z" }, - { url = "https://files.pythonhosted.org/packages/c9/5f/5a54f4ca30a669a247df7c467aea7bb854d9b4de7e1dc51edb2f91ec603f/hypothesis-6.165.9-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a9a1e17200a2b369a38c19c2184efbc041a40b784675ba72ac6800d8c1260a54", size = 1324669, upload-time = "2026-08-15T02:48:29.144Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b2/6ef8eac3b30d6c8225fc405be3b99790701a942706ec93a3bd638a0b614a/hypothesis-6.165.9-cp310-abi3-win32.whl", hash = "sha256:5d40a92c6d025725da7827431a71b7657ce2e3923ff8f1773d6b15d717f77ea0", size = 668903, upload-time = "2026-08-15T02:48:34.033Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7f/b9514f65d2cc97277f6442e3b05164177a742489940efa53e794a54b62f6/hypothesis-6.165.9-cp310-abi3-win_amd64.whl", hash = "sha256:59f29ef2965caf1ae367ee0598ac0d293551b95744a5d49888737a3b3d9dd944", size = 675051, upload-time = "2026-08-15T02:47:49.697Z" }, - { url = "https://files.pythonhosted.org/packages/96/01/110123106a3702d2f3ffc47b9bc6a1a29c19cd2d15fcf356430777a4883a/hypothesis-6.165.9-cp310-abi3-win_arm64.whl", hash = "sha256:f52fd4f1b95f614c3fec53840d603f0a3b19e68eaa91c8d609222999c3ee5506", size = 673402, upload-time = "2026-08-15T02:47:48.364Z" }, - { url = "https://files.pythonhosted.org/packages/09/c0/42f101a02ba2593c61ca4242b8a228d31599bfc2ab1bc5da3542fbdba9c2/hypothesis-6.165.9-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:d7871212e7a1e9d317b2cc11934c6f28aaaa48b594231165a0963d3b3f5159b2", size = 783820, upload-time = "2026-08-15T02:49:19.948Z" }, - { url = "https://files.pythonhosted.org/packages/09/49/e49c94c9f09542e95785dbdaeb8aeac901571a26497dbeef1eedd8c95e20/hypothesis-6.165.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b80ffe65bed7f42681b845bdfde445c3da46a84eb2c4fdbc928521786b551c65", size = 779547, upload-time = "2026-08-15T02:48:06.225Z" }, - { url = "https://files.pythonhosted.org/packages/a3/74/173a59310db80f896059f96259c336c6a773c07d6298645a83ef8e1353fd/hypothesis-6.165.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7186be7b5ce1aee61614ede14fc6f3f072d94ab3c3549613903cd1c9a37848ea", size = 1108411, upload-time = "2026-08-15T02:49:04.88Z" }, - { url = "https://files.pythonhosted.org/packages/08/94/4f4091f4d8522fa6c9a7ee8ad71b6db30a7820faf18e5736318fac946569/hypothesis-6.165.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0a64a05255caa9a5ad90e1ab57eb26d64123b005234e22c80e2f723832e047", size = 1157885, upload-time = "2026-08-15T02:49:06.742Z" }, - { url = "https://files.pythonhosted.org/packages/bc/4f/7b5afb5037df3f1c5e327824f3589ca94c4584cbeace0f6cae68f9148163/hypothesis-6.165.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4d7bbb89c256d02406b7875dbe4058ab79fba6d8e6b56dd1d771e0c9800a02d4", size = 1284010, upload-time = "2026-08-15T02:48:01.796Z" }, - { url = "https://files.pythonhosted.org/packages/7b/cf/8a98ab9d9476cf282054c399577ab7aba2984d2989dc907f20016b633e09/hypothesis-6.165.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:740074d1b6ae4a5335b7a17f44f776c3952d6c7f4a6c84f23ae93e1907834692", size = 1324995, upload-time = "2026-08-15T02:48:57.245Z" }, - { url = "https://files.pythonhosted.org/packages/54/07/e0a0d86029f6bc709be2cf9bb77c7661d0e644fb631c9835116d96ce0e0c/hypothesis-6.165.9-cp310-cp310-win_amd64.whl", hash = "sha256:3295f35299c6d4f7bc12dc583bdca776a5e41cf22a185e5209800885950bb163", size = 674965, upload-time = "2026-08-15T02:48:14.04Z" }, - { url = "https://files.pythonhosted.org/packages/de/1d/1c2dea64a3b00b9f83ca57bacaf4982255da7797d6c65fc0301f21a0ca94/hypothesis-6.165.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a9995cbf91e0b22cb8d9aaaf6b10994b2ac1ebc3b01b4a91d3e82761a2737043", size = 783582, upload-time = "2026-08-15T02:49:29.881Z" }, - { url = "https://files.pythonhosted.org/packages/aa/8c/d8f51166ef9feaeb08d33737b2874d1effca4ada8bf6598e6f2a634a3ca1/hypothesis-6.165.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f94e4456f7b0dd04f4f80f26a2c499370f6048f393ff61436de4e192e278e2df", size = 779367, upload-time = "2026-08-15T02:48:47.639Z" }, - { url = "https://files.pythonhosted.org/packages/8c/27/749aa12ec03fdcec1e4fa7289145f03ded46e892ae89613e333dd7090a7e/hypothesis-6.165.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bab95b4ef980cbc2b4632e49057588766cda930ffbd727d70edfea289da98dd3", size = 1108251, upload-time = "2026-08-15T02:47:59.983Z" }, - { url = "https://files.pythonhosted.org/packages/2b/cb/a1c417cd39f87ca11e2b5b4a7da0446ae316b43b9baee23e113a59113759/hypothesis-6.165.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8795161db0a7c270c4f3e0eb754bd0ed96b17133d6f851c7444f9733c4c9d37f", size = 1157689, upload-time = "2026-08-15T02:49:40.288Z" }, - { url = "https://files.pythonhosted.org/packages/5d/d5/82abf4d3b171f087d323b179a422472e1c1621ed47adf88f0fcdf7be7e35/hypothesis-6.165.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8c7dcb0a21dab077e65bcb082ee321b7e31402cf980da648b4369c71086ad53c", size = 1283592, upload-time = "2026-08-15T02:48:04.698Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ee/f156d374fd7f49220a789a231cd980ffe53bf1eec11a75306ded76265306/hypothesis-6.165.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:92b264bef7860bf37b5f22b5949083d26f113351da8b99e93adb0554d3e2c110", size = 1324910, upload-time = "2026-08-15T02:48:11.037Z" }, - { url = "https://files.pythonhosted.org/packages/23/57/d14969c84e59356103c0b5e1a27623a4149d5d371799757552b2ec344da2/hypothesis-6.165.9-cp311-cp311-win_amd64.whl", hash = "sha256:ae34d13d8438ea7f3ee18292290b9f608de6070309f3c5826644a88d197c319d", size = 674776, upload-time = "2026-08-15T02:48:59.008Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7e/03a84262b74300714af486e037403db8f5e19170f01bc32605300896b19d/hypothesis-6.165.9-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:41082aff3e438ac1eeb86002c269b9b5d86bab563e911614c2c8d34f405e02db", size = 784696, upload-time = "2026-08-15T02:48:09.314Z" }, - { url = "https://files.pythonhosted.org/packages/49/50/c77dca38074e38e99373ccd5f361bb5ad251e8f2de92dff4d7035111595d/hypothesis-6.165.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:67a0c0346ac0e8e106fad0224269a87c665768cc8d45de6e367e9b260ef36d5a", size = 776265, upload-time = "2026-08-15T02:49:36.232Z" }, - { url = "https://files.pythonhosted.org/packages/1b/80/9de2e558cd966ab1b1d014718c7aaab7d2d8f2d591ccadb27c1ffe639f6d/hypothesis-6.165.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10e826a708456c965e125f15dbf5a6f463e41dcf230a930b7a3e0ce7036f5176", size = 1106706, upload-time = "2026-08-15T02:49:12.466Z" }, - { url = "https://files.pythonhosted.org/packages/04/78/dc89684de1a739347b22beadf3e53a64b8b7eef287dfa34b150432e13d4e/hypothesis-6.165.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fba20d9c60d6b8aec76f32cd22109e1b1f19a9985317255e2b43940d27996097", size = 1156753, upload-time = "2026-08-15T02:48:20.512Z" }, - { url = "https://files.pythonhosted.org/packages/b9/bc/495988f0fc788d33a24849e9ce7303a2c646f48baf3f5f010d9850bcc5a7/hypothesis-6.165.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6564fb9bbe1760719070a6fd37335fbf43696fe2911c386ba55fb3cbaaafb82a", size = 1280667, upload-time = "2026-08-15T02:48:30.763Z" }, - { url = "https://files.pythonhosted.org/packages/36/08/9758ba15cc7dcb408d2699081ee0acbd96842f9c5778c84fd145d19953ba/hypothesis-6.165.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:126e7bf873892649cc5f697c3b3a6548f077a56ae3601b22fddef2ea1ba6f64d", size = 1324018, upload-time = "2026-08-15T02:48:18.804Z" }, - { url = "https://files.pythonhosted.org/packages/97/09/eaef6ee89a792ea82064cef66726023a5933f8704c0d3dbfc5a451829428/hypothesis-6.165.9-cp312-cp312-win_amd64.whl", hash = "sha256:c908edd69e4308c3cb194f0853aa7dfaa15e905242e81646b9f12881cd2683d3", size = 672207, upload-time = "2026-08-15T02:49:50.121Z" }, - { url = "https://files.pythonhosted.org/packages/d2/1f/99069da612aee3a51519f96f4370c4ba42055b57eae92926353c63e68617/hypothesis-6.165.9-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:11bcdfb14b52d41384cc14ecd3f6d6152735bc2f7af0b9f3fa8685d630a1e98d", size = 784591, upload-time = "2026-08-15T02:48:42.765Z" }, - { url = "https://files.pythonhosted.org/packages/94/10/cabf6a8782253e6b547ea8bdc6a48a9eeab415f1036c749b40ca59df32f8/hypothesis-6.165.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8c3d7d54802641c2f8b49f478f153567770d299d639ee38fc0a0fa3d95ee07cb", size = 776216, upload-time = "2026-08-15T02:49:34.179Z" }, - { url = "https://files.pythonhosted.org/packages/15/5f/da966f98d69a8ee4b4fac509d92f7ab18cb4505b8191acc23bbb52f8bb17/hypothesis-6.165.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8f5b63a8fba74d19a12ad4abaa95afb7466d66d1adc90ba3026e40b7924452c", size = 1106621, upload-time = "2026-08-15T02:49:08.529Z" }, - { url = "https://files.pythonhosted.org/packages/0a/5d/1fffd1031b4281e53fff02ad7adeda31db8218e91b82e7a98c7957541e76/hypothesis-6.165.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c01b77160328d8fce22bb5121fe1f5fee36b434a1220603500605b88ab854d7a", size = 1156613, upload-time = "2026-08-15T02:47:45.457Z" }, - { url = "https://files.pythonhosted.org/packages/b1/30/8e93a02016689c0b5cf0c35f8f9199b745601b3db9723296ca6d7366dcd5/hypothesis-6.165.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:88bde76aaab7f8abcd9412d135eaa49e8f7d89d068188fae2768a7803c80df89", size = 1280650, upload-time = "2026-08-15T02:49:44.026Z" }, - { url = "https://files.pythonhosted.org/packages/c0/48/2b0987389f141bd7bccc0785af4af86de9dfe9a6bc1b2b5dc612b083ae8a/hypothesis-6.165.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0df8f8736d176d3a5092890b6fb9030f9c726d272b52af0e1ce887d62fc21699", size = 1323749, upload-time = "2026-08-15T02:48:12.519Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a2/0a2380fdedfe0e16a09a09401b86bdc1eb049b3b6256fd837424d652c88e/hypothesis-6.165.9-cp313-cp313-win_amd64.whl", hash = "sha256:7fc9f46d88732fc74539ca7748aedc736c5e111431b06bd8ef2534440379aff2", size = 672195, upload-time = "2026-08-15T02:49:02.806Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d0/95075d5ae3263e1c9143a3e89d9f19b864b1da303fb426964d9f7c08ce69/hypothesis-6.165.9-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c3432a48e5895cba6b36c3e5abb915163d5169dc755685b4cd4720b710d77e3c", size = 784690, upload-time = "2026-08-15T02:49:23.922Z" }, - { url = "https://files.pythonhosted.org/packages/ed/d9/4a8b58237a36a839abefac22905ce9172b4c5195344708690413c6ef900d/hypothesis-6.165.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:70fe20939f63eb661227fd2b05193eba1e5ea6e547a2fbe158839f2acc519f4f", size = 776366, upload-time = "2026-08-15T02:48:27.261Z" }, - { url = "https://files.pythonhosted.org/packages/74/8d/91d1261f09a87299dc0ba89e12d8f9bbfd454e8922560e332b190a5c0a9f/hypothesis-6.165.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20b177ef8aae2a2817a2223d103353d120118e686a152bcd01832a27ddc9388e", size = 1107128, upload-time = "2026-08-15T02:49:10.617Z" }, - { url = "https://files.pythonhosted.org/packages/2b/01/0198547c8b73d73ee5b885d8f73931c4dd3c62055dc93a80b5e18590efeb/hypothesis-6.165.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d755b56098801a28a419172e57beb536b8c32929e7a37bb5ef12f2c00abd284", size = 1156757, upload-time = "2026-08-15T02:48:25.44Z" }, - { url = "https://files.pythonhosted.org/packages/21/3c/a3bde8c9630d361d85570ed5a6248fec0360a3874e26fdb04d62c8ebabf9/hypothesis-6.165.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d49496271282bf424ddd728108f49b92e1d32066a3d0ba18990d5b01c40c3175", size = 1281013, upload-time = "2026-08-15T02:48:44.399Z" }, - { url = "https://files.pythonhosted.org/packages/f7/2d/017895202e6f2d75928b8e66bb8efe28c5c834771f6a3d118fc936d811d0/hypothesis-6.165.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:be54673f76785ff954b511745d4a7a18c7d7fe1d893cb81a8917cd28878fbd55", size = 1324110, upload-time = "2026-08-15T02:49:18.05Z" }, - { url = "https://files.pythonhosted.org/packages/40/f8/02f9be17e21b55513da624bac18a21c29011351d4e5d089ca5095555d1b4/hypothesis-6.165.9-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:af42ad4c24b69d4482e4813e148f458fc090911c0cf9055225c62fbd1f0b102e", size = 616257, upload-time = "2026-08-15T02:49:00.997Z" }, - { url = "https://files.pythonhosted.org/packages/e9/be/c94631e799bb4ee87c8c80de72f2eb44e93e7d0bff45b22b8801c78e5895/hypothesis-6.165.9-cp314-cp314-win_amd64.whl", hash = "sha256:9bc2d40dd2a71ba3971b902d31c8b1b0c40d3d465591fd71bb8ce6f541add530", size = 672006, upload-time = "2026-08-15T02:48:32.418Z" }, - { url = "https://files.pythonhosted.org/packages/43/15/d23548a12e5670513903b73a42fbd4f5ab7b1287bc6fd5a4a890c5746a62/hypothesis-6.165.9-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d0fb16358e29f3090a9ab529e84df52923ddcd8505785c019bf48cdcd45e094a", size = 783152, upload-time = "2026-08-15T02:50:02.576Z" }, - { url = "https://files.pythonhosted.org/packages/0d/cd/37b5e5c042c557aa50db8c7f554b655b51c83e0b50306fb6ec8b05b03bfe/hypothesis-6.165.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ed61ad45184997f97c3baf255f34aaaf4edff63f1a61aad87919034b4ab9f64e", size = 774782, upload-time = "2026-08-15T02:49:47.988Z" }, - { url = "https://files.pythonhosted.org/packages/56/ed/43b6db3681937ffa191148533d37cdd3c091f707ecb92281424078876d45/hypothesis-6.165.9-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f96f36c3d3677f22ff990d7aab4235576a8429e72dab5382c45bf489630972f5", size = 1105367, upload-time = "2026-08-15T02:49:42.041Z" }, - { url = "https://files.pythonhosted.org/packages/0d/dd/eaab3c678a23ab434c65b4ea5de5b302267ce68aa47b996524d2f34dcd13/hypothesis-6.165.9-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69ab6500e47d7c21310360d1ee5cb6441660a10408e856d3738f07536732ed8", size = 1155499, upload-time = "2026-08-15T02:48:49.363Z" }, - { url = "https://files.pythonhosted.org/packages/10/fa/27049cbfb3e66cc1b68b32cbe137ac69a91cf09ac8520c4eb8ae9b3e5cbe/hypothesis-6.165.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cc98fa897f48127fdf2e7d1b2428f839656cd52a6344095e501371d222b9c19d", size = 1279038, upload-time = "2026-08-15T02:49:26.001Z" }, - { url = "https://files.pythonhosted.org/packages/7f/d4/ab591a445dfeb7fa59b3c12de3eb9ff09901b4ede4025061b2fe1bc302af/hypothesis-6.165.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a8b2d7415b1f7ceec6e689c992b919af41e339eafb35d6367a3abbb5b6db9753", size = 1322697, upload-time = "2026-08-15T02:48:39.467Z" }, - { url = "https://files.pythonhosted.org/packages/40/fd/4ee3b14d2d73a1d9442ac161b953dea2522fdac8ef7ca265fe5dd205982d/hypothesis-6.165.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1000afaf05be59c88c13b458fd74d123e3361ca3d14fe127814a1231cb9cb7df", size = 672011, upload-time = "2026-08-15T02:48:07.583Z" }, - { url = "https://files.pythonhosted.org/packages/81/a1/20b63e3d17e05e82ef47b93ea3fa9a823297249ed7953e7e38f1b57a1e91/hypothesis-6.165.9-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:5f6b4ae650480368e2477e8f875cdbfe8533a7b2e09f049fe206c48b20174622", size = 782729, upload-time = "2026-08-15T02:49:52.008Z" }, - { url = "https://files.pythonhosted.org/packages/7f/13/b43cea9578820a3e41cd70e8395068a0f07ef11c393ab09ff5aeb5d44d7f/hypothesis-6.165.9-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:1aaba05b943345d2a448b8c1c795bb191adf842b450bfc7207bdd1bf139a9b58", size = 774415, upload-time = "2026-08-15T02:47:57.162Z" }, - { url = "https://files.pythonhosted.org/packages/ea/08/222a8f2aeb8429b54cd53624add076ae5c9b39b0b170afd2435d0e81a8a9/hypothesis-6.165.9-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:306c1efc0f01f3d5c29c45996e055437971337aa24b6c6735b0514cfb5df60ce", size = 1104761, upload-time = "2026-08-15T02:47:46.841Z" }, - { url = "https://files.pythonhosted.org/packages/68/6b/09943bad5c1c5ea689a1be6376bcb7c68c34f3e3db5b792a1c0bb4edd79c/hypothesis-6.165.9-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1551df11534d192616dc1b4539b6e526c4704d08b22d2ffb6ea3479ce6b87d94", size = 1133212, upload-time = "2026-08-15T02:47:52.814Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ee/8fa90ca7a7d8b2362f3aaa0b2117405abf7b104fa01b52d5fecc4bb3c3fc/hypothesis-6.165.9-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afd99a069df90c73c15d53e275d11a782fd5f17f5423002868ce9d2438ab567f", size = 1132156, upload-time = "2026-08-15T02:47:58.588Z" }, - { url = "https://files.pythonhosted.org/packages/5f/c3/6dae83eccd7260f5cf3468f145c663ce9b22579a9d9c80e9e7be49bec8b2/hypothesis-6.165.9-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c3e8c45ca5e2e4f519021836e5d86fb2229915c3f92fb50db7bb7364dfb2c1", size = 1155040, upload-time = "2026-08-15T02:49:16.085Z" }, - { url = "https://files.pythonhosted.org/packages/50/90/25eaabe2fecb3af3bcb9b4c3261ba9ffe0e092a3199232adce15399c040b/hypothesis-6.165.9-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:676a3bd04261b7bdd35374a3f4dabd82025fcead110a067a76328799f394a4d0", size = 1109751, upload-time = "2026-08-15T02:50:00.293Z" }, - { url = "https://files.pythonhosted.org/packages/1f/81/e8117e7ed3ff1ef7a36f023469efc925458b52c1243c539566a5308f5d77/hypothesis-6.165.9-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7f51c2297d116c2c025643762269eed0767589b6fc858d8de69d9f80cf33a23", size = 1144793, upload-time = "2026-08-15T02:49:38.317Z" }, - { url = "https://files.pythonhosted.org/packages/c6/6a/da2ea69d1fed780d86895f1ef7f2811240ed810487536ad22298629cc630/hypothesis-6.165.9-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:e500b97f899e375e47b407a48f3450cb1dd44bca757529c0490cb4533d878b30", size = 1278531, upload-time = "2026-08-15T02:48:37.581Z" }, - { url = "https://files.pythonhosted.org/packages/a2/6e/b3b21f15fe9debb9c448b03aaeb725ee007ba68518e55a6810878d7b9765/hypothesis-6.165.9-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:8401f55aba2d51f20b02c42e9e08b7eff2018639bdb1af16a4c279ba7e129c81", size = 1407073, upload-time = "2026-08-15T02:48:41.256Z" }, - { url = "https://files.pythonhosted.org/packages/da/fe/55f8e9c34f2b0a5ccf895a527a499f8562e9a14f014dd018b2535e8aa1c4/hypothesis-6.165.9-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:34a4591db1dcf6aa544b4f884520ff0f1bc02ac3d11c76f78c73535b6b672272", size = 1261243, upload-time = "2026-08-15T02:49:58.076Z" }, - { url = "https://files.pythonhosted.org/packages/0b/37/49fb2374bff4e66d3d8264d083ddbb83269ec13903b441ebbc0d3c51502a/hypothesis-6.165.9-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:0bd589a011b3d0c4b98d78f467e5a5a6b96bf5be5d15b39ef47e65c225e8d790", size = 1279060, upload-time = "2026-08-15T02:48:54.843Z" }, - { url = "https://files.pythonhosted.org/packages/ec/b7/c826e67f5d553d20644ac3da5d25de7ff9d50a1f41467981b00da7b2ab9b/hypothesis-6.165.9-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:84a93a152ffaf92fa6b3f94d366f4c866e26f3f1b643fcc6da4ee2ddfa038aa9", size = 1322247, upload-time = "2026-08-15T02:47:54.398Z" }, - { url = "https://files.pythonhosted.org/packages/24/ec/e70e440805820cfd662d04d05aa34b5d29e46d5a49ee871d6937721b2c17/hypothesis-6.165.9-cp315-abi3.abi3t-win32.whl", hash = "sha256:4a2081699e0b16b35c7b34cf0ff01997e8fc6038b520b9f29c106764158c411a", size = 665906, upload-time = "2026-08-15T02:49:53.901Z" }, - { url = "https://files.pythonhosted.org/packages/23/79/968b149b572e1d4284b04280cc181449090c8bd03e55b24d57b64443978e/hypothesis-6.165.9-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:e1d9492193b52087df4d264edd77618869d447b5c7f6a022c7bb11b88d577cb8", size = 671817, upload-time = "2026-08-15T02:49:46.053Z" }, - { url = "https://files.pythonhosted.org/packages/c2/72/0f951d4f0e07720583e4773638cd556d2b6585605f8a3df54d2fecf2e2ff/hypothesis-6.165.9-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:f67414675f2263948e5a5977b636b5cb1cc5f33b82a9be34f8a6b9a8293d4707", size = 669795, upload-time = "2026-08-15T02:48:03.176Z" }, - { url = "https://files.pythonhosted.org/packages/43/7d/1b6b48629032b22960a5b010a791467670c08d9413a3c6c15a72f7c1da09/hypothesis-6.165.9-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f6caa4194f37a02232ecb964600d17facc23a6aa5b0295008989f31e329eb837", size = 784507, upload-time = "2026-08-15T02:48:45.961Z" }, - { url = "https://files.pythonhosted.org/packages/95/a6/c29939a55cde73886cee7a3975148e132a9ed6881d39b7c9b8c19e3a854b/hypothesis-6.165.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0a4a32dc8e8728417aa13055903dbf742dd0b5cedda7c2031b4b2a0bcc1d463d", size = 780353, upload-time = "2026-08-15T02:49:14.307Z" }, - { url = "https://files.pythonhosted.org/packages/8c/f9/ac8cd423b1c7506dae01c4a9295c434b53525401a182a116aecaa27332cf/hypothesis-6.165.9-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f255b389364f71e6e9305ba99bf13733354cb0dc4a97d07db8aef76278a345e", size = 1109224, upload-time = "2026-08-15T02:48:15.557Z" }, - { url = "https://files.pythonhosted.org/packages/f2/26/64c4dfa2836ec421e35a167a3fba92018950a8c7551a268a5fc799329455/hypothesis-6.165.9-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7f0507d8224e5b44c2651d7ef8df15ac6d38eb56c98e7f451c3090804ce109", size = 1158929, upload-time = "2026-08-15T02:49:21.922Z" }, - { url = "https://files.pythonhosted.org/packages/88/8e/fccbea8a0a03532762fdd56d3809fd8398f46af1ec9018f694d4ec5c22cf/hypothesis-6.165.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fd9e242fabb05715ec2d3b9b96dcddf683fc3891a04b6685b79e400e35ccbe18", size = 675885, upload-time = "2026-08-15T02:47:50.986Z" }, + { url = "https://files.pythonhosted.org/packages/05/c1/9a9538e6d185baf5cc7f15bc3b76e08efbb3de4b3c782f234356449c0dd7/hypothesis-6.165.10-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f839d29d0cc12048cf073d88ca4fdf94d420bc2b8afd69641ff6d496422ccd4f", size = 783243, upload-time = "2026-08-16T22:55:44.058Z" }, + { url = "https://files.pythonhosted.org/packages/a1/30/b70d9d79e871a75cbdeccd9067f20ecdb9eb2a1dfa03c630be3ad13b8b30/hypothesis-6.165.10-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e10858f57ed0e74baa04393845f469fe8ad502c16ece4499bef7700c575611bd", size = 778815, upload-time = "2026-08-16T22:55:46.948Z" }, + { url = "https://files.pythonhosted.org/packages/db/52/6f0a9b7aab24b0635e2238f3fbddea5b54b17879ac813df42a3cc3384c5c/hypothesis-6.165.10-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76a7be86d986223b9f1bdb7e7cbcdb048649901fdb956c598ef73bdab1786cd5", size = 1108009, upload-time = "2026-08-16T22:54:53.082Z" }, + { url = "https://files.pythonhosted.org/packages/f6/06/8d0d4e11ff02350d09ec9f9e90af354158e59e16a8907ba5199a4ff2d7e8/hypothesis-6.165.10-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:717aea574e0e5edba2868aa66b1caae335d8f1ad3fb29f01dd6502953fa823a1", size = 1136596, upload-time = "2026-08-16T22:54:54.443Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/01a1e440f2e38dc1ccf5d597af5b8a0bee5f21b674c99c123b5554de9690/hypothesis-6.165.10-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4334058033e0214475f019e15492a50f3854fe8728cf51fe25c6191a2c3f8e52", size = 1135234, upload-time = "2026-08-16T22:55:08.911Z" }, + { url = "https://files.pythonhosted.org/packages/7d/18/8a26c24d3d9db20265f39df341ab265858c094e209571e3179cf237935f4/hypothesis-6.165.10-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2abb50cf1cf77d721de0a24c3f99d9c4ffdeb2cbd1e12aebb5a7a93e2b6b6d1f", size = 1157528, upload-time = "2026-08-16T22:56:02.159Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8e/ce3c829b1937402d7944420ca26a05a0c8563e894dcff03d34ffa279d306/hypothesis-6.165.10-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:3de69aa8b924b400291a3cc42aaf78e6ab65c905a3e7e1a5dc39d95ef1b428cb", size = 1112870, upload-time = "2026-08-16T22:54:55.919Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1b/4c4926d6c9a2b5d7cc090cc1e91219d6796102aa2a2c4b8f961c939e60b5/hypothesis-6.165.10-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5841331c504e02d7c334591681cb8587cdd59dee7e149db6d3db8e3f9e9f02eb", size = 1149683, upload-time = "2026-08-16T22:55:30.567Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f9/df24eb28412f82465e2b7707f0ff1ec274d580bce389d4d9156617dc7bba/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2d0e0f8263d34dd8fa3b39eaa9a50bba56a8470b3dd9ebf6672d10840abe063e", size = 1283402, upload-time = "2026-08-16T22:54:18.054Z" }, + { url = "https://files.pythonhosted.org/packages/4d/07/c2b2a761300cf60b90ccebba4328175331e67d34f4fbd39429a7ddcdce49/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0c4e6869817c3cfdf5a2b4d348497b95159bdecb3365be732c9b8570e36a4eef", size = 1409948, upload-time = "2026-08-16T22:54:22.343Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ec/1c2bf1acdd0e273d81f833f85caf0ae5423db68a783554992fca36e6c541/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:9f07ae36c3b093e13687a894e79fe69e98a94c0b67fef656c575247682218143", size = 1265023, upload-time = "2026-08-16T22:54:41.402Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a8/7f984908b7391160c7801b84e51ca8e4ba88c89e8d8811aa1aa7c03de73c/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:aff1f584c9538e8979cd180b1d70bf99bc16be19d4666414f49e5942b21a4f2c", size = 1282698, upload-time = "2026-08-16T22:56:06.998Z" }, + { url = "https://files.pythonhosted.org/packages/48/78/3a5d91c2d0250521736c42dfa2402b75049bc5fe2fb716c10bc84bb91ed1/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1f2c4db25fb8ec1a16a8dba580666337b8ffb1887c4cf1750cc954313897cef7", size = 1324816, upload-time = "2026-08-16T22:54:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/6f/99/27450763853a034bca1574d3e0a315164b33ff49c3862df6872dda45e25e/hypothesis-6.165.10-cp310-abi3-win32.whl", hash = "sha256:b33dc30170a7402e03c180f2c5ef69dc077152f35b91621e9cebcde9c7d71746", size = 669039, upload-time = "2026-08-16T22:55:11.962Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fc/ff2988b72b5705ad9ca500444bf3f43e3c2f41edfa034bbfeb23b215791a/hypothesis-6.165.10-cp310-abi3-win_amd64.whl", hash = "sha256:e9f924aa610c0618445e1e8738c822c3190ce2a2699a0cb48ec3a351a96761f2", size = 675213, upload-time = "2026-08-16T22:55:01.697Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8b/821810d36f78d9d9421cd2c5d9d36983b45bb3575c3086276cc5c76f9f73/hypothesis-6.165.10-cp310-abi3-win_arm64.whl", hash = "sha256:1d305448e9bd8e2f4f3cea0eafd809efdaab4e998a0019bc615650c8463e42f1", size = 673537, upload-time = "2026-08-16T22:54:47.898Z" }, + { url = "https://files.pythonhosted.org/packages/26/61/5e89268ce03317fb9f82449a1b3efd9e599dee090288fd0cf7586c532fb1/hypothesis-6.165.10-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:73e6df02a6a62f8045b511c272f894d08e56d174504c793c9effcbc6778051a8", size = 783959, upload-time = "2026-08-16T22:55:29.078Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e9/f4e0832e81bb53b70cf1712e28c867db64245b32595b594217452e7dbd8d/hypothesis-6.165.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8b20f44773a9ab84400465e318712d8c2ca16418d35b9f80aa27fdf2d690ad10", size = 779684, upload-time = "2026-08-16T22:54:57.698Z" }, + { url = "https://files.pythonhosted.org/packages/77/de/ea072d3359d5678771bed407f80439e8ac7ca905d1031b0372f61bf5746e/hypothesis-6.165.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb8c7d05ea27a093a92b250904095d71d924b6b44e5795a415c1b20c265f0c65", size = 1108540, upload-time = "2026-08-16T22:55:03.282Z" }, + { url = "https://files.pythonhosted.org/packages/28/56/e7c395cdaa3d6c28b944c1c3c516dee50d2b7b3aeafa31874b57009ca51f/hypothesis-6.165.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4dafd6d6ababfa3b14dd6e5f0378cb7c7d291895a31a40abcbb7cc74f396131", size = 1158089, upload-time = "2026-08-16T22:54:36.205Z" }, + { url = "https://files.pythonhosted.org/packages/b1/49/1c6d2c465b9c5fc3213f1be89be95ba53819ca0130248c484129ccfefb71/hypothesis-6.165.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fa74636a49fc8077413ce8db3e85f1c4aff880788bb55bda56253118e036fe5b", size = 1284125, upload-time = "2026-08-16T22:54:37.727Z" }, + { url = "https://files.pythonhosted.org/packages/7d/bc/7caf5ac3d0173bd57bd2a5ab854ca49a3664a4309257be1452f81025cc24/hypothesis-6.165.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2b112768cfb67f2b683e53e58c1a33d27811aacf60c942b8eb74635e469a73f6", size = 1325082, upload-time = "2026-08-16T22:54:38.952Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/9d844330f570d6a4f127a683eab1e78c8263e6e72b16189f3534fa6bf6de/hypothesis-6.165.10-cp310-cp310-win_amd64.whl", hash = "sha256:56cb8c9055e50545fe6e3e5a560ec25a724673b2e4051f3c24d44e3ebc35dd72", size = 675082, upload-time = "2026-08-16T22:54:29.872Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c2/b9546ace11f241c9c02d389f258cb80c14447a8c885771c9f1f0bc1d85ca/hypothesis-6.165.10-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:592107a0faf6c9c3a63a8dbf13dfb1cbda1cf599b0bc11c953221b00204b9ce1", size = 783716, upload-time = "2026-08-16T22:55:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/37/10/27c2fdd574fd798caf5e91eb51f7834b098f5d840ce733efb3fba79ef86e/hypothesis-6.165.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9180c362bde06fd05380298ded4e234fbc0d6ede0a864835bfd91c1e24283d5", size = 779507, upload-time = "2026-08-16T22:55:07.633Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b6/70bc23695f3783c4b0486b6cad47b08a20f791db4a3c1b25250add9659fa/hypothesis-6.165.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d623801ae3dcd97b77b983400ef3d48bf976648e4efff19929175322eaae074d", size = 1108406, upload-time = "2026-08-16T22:55:39.653Z" }, + { url = "https://files.pythonhosted.org/packages/71/4c/32e200bd7a352af4b7f4e3729aaa4cd002cb5fe8c4c6aef5599d0019f152/hypothesis-6.165.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20f6236cfb90b7817bb1a6a087589ca4aa46d73170f0dd62963952ed5dadc589", size = 1157850, upload-time = "2026-08-16T22:55:24.394Z" }, + { url = "https://files.pythonhosted.org/packages/03/a5/8efc2a9a484822efc0d0da466f50094e0f2c068187faaf33831fc905873e/hypothesis-6.165.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad0764730e8e3421601c2cc7e1f054a9206c60ea0917165d8d9193dc453f34f1", size = 1283704, upload-time = "2026-08-16T22:54:27.279Z" }, + { url = "https://files.pythonhosted.org/packages/46/2a/90cc8d7463929c04786f29600de45f3227c12fa9bed1d5b7ce319b05e1c9/hypothesis-6.165.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:10d9a650a4666b0914831f769703d36140ed8039fd19bf9b71f615b8541eccf2", size = 1325077, upload-time = "2026-08-16T22:55:16.561Z" }, + { url = "https://files.pythonhosted.org/packages/82/ac/bc16faba4b42883e3d290bfaceff51e258b63fbbdf789bf9fe88df1ce537/hypothesis-6.165.10-cp311-cp311-win_amd64.whl", hash = "sha256:5671d2b2bf83bd4b6f02e55b32d432506eff5358c82f39b460a849ce19a2666e", size = 674920, upload-time = "2026-08-16T22:55:42.613Z" }, + { url = "https://files.pythonhosted.org/packages/e9/45/cde4f78afe2b9e29caecf38319eedc1deb76aebcacbdd128e03cbb2511c3/hypothesis-6.165.10-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:637445c1593a2a9d1024fda50082f07bb56baedda78d90a25f64b8111727ef94", size = 784835, upload-time = "2026-08-16T22:54:45.429Z" }, + { url = "https://files.pythonhosted.org/packages/7f/81/847f30b81cbfd07607296b3ce43067cf4f80799bd9244167f587de9c8081/hypothesis-6.165.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:713f4ce4e82c26b53031f139de959bc9e8b54d3995aa824b89bbdf8229df2a45", size = 776419, upload-time = "2026-08-16T22:55:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/04/66/4c71c5be7a49d84b8c3a9278c1807c4c81181ab5474beb27df9d4c40dc0e/hypothesis-6.165.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9ff356e97e3ab09db07c8b675efa67340103874a0bae7465acb83dad7a35f7f", size = 1106830, upload-time = "2026-08-16T22:55:10.389Z" }, + { url = "https://files.pythonhosted.org/packages/e3/c4/e2cbd2810e79f7a452a8ea9f6c6438ee718ce938d8cc12252cf0b36a81d3/hypothesis-6.165.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a380bc99aa3b035e6a95a2201bf792d4082a04ca75babcc21849c2d0914bb28", size = 1156952, upload-time = "2026-08-16T22:55:53.35Z" }, + { url = "https://files.pythonhosted.org/packages/a8/8b/794ced36864825492ac3712d5acab5a257b4601e6a9dc2ccdd3937198f87/hypothesis-6.165.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9acb2c4d9cb532c3fedea74159f7b923c8c036328c9239b4049e7aa073bdd81", size = 1280780, upload-time = "2026-08-16T22:54:34.983Z" }, + { url = "https://files.pythonhosted.org/packages/5d/2d/550525442cdbcc2daf1f9bdd8ba35bcbde63db7c7a22f2ef137fbb49df2f/hypothesis-6.165.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8660572b2d424bf5369ea8990985225f70bd1615b76ecd9c25588a3b9307009f", size = 1324130, upload-time = "2026-08-16T22:55:48.659Z" }, + { url = "https://files.pythonhosted.org/packages/74/59/6caf69dd5fe03499ada94c9cec016bffcc164511c6b93fe680f01209b9ff/hypothesis-6.165.10-cp312-cp312-win_amd64.whl", hash = "sha256:3376f2594763aef14faa519b0fb27cae7ce9eeaab4c69efa07777499110306c9", size = 672337, upload-time = "2026-08-16T22:54:49.11Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fb/c82c5bd92864ffcf319772fedc8c9bf2dbe4ca14baa0fee6e49e67b5ba1c/hypothesis-6.165.10-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9d77c3be7b429875036ad0f0597c6e5cc6bb17894a4da005e3807de64d2673ad", size = 784726, upload-time = "2026-08-16T22:54:32.371Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b9/3d7acd08506da85557e65147b7f3fca8c47684e33be90bee0acb523920db/hypothesis-6.165.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:490c56b830772b0eca3b4b2cecb3741a1ed26b1d7206a279e1525dbf0aa95ee4", size = 776375, upload-time = "2026-08-16T22:55:13.303Z" }, + { url = "https://files.pythonhosted.org/packages/38/6b/922e8b3f9a706dd89d440b9545d2c6231c65e74da1c1fee3ff36c251b9c4/hypothesis-6.165.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed68e27b8a61e57a3ccdc7c5a14499e00b54dfe223087204d5d40b3b5ef58b6d", size = 1106763, upload-time = "2026-08-16T22:55:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/01/39/f5b9a5d390d4edd1ad472334493ac442963ebeb4daaa74ff4bdac6ef292f/hypothesis-6.165.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6caadcd1afb62630ff5c5ff353626eaa616553a5971295ad6dc2b19ca8a39620", size = 1156778, upload-time = "2026-08-16T22:54:33.824Z" }, + { url = "https://files.pythonhosted.org/packages/b5/5f/5fbe1be4326337fd6acefe2d18ed44007ee1dc1f98fe5b3c0eb22942364d/hypothesis-6.165.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9145fe43ebb22e66672967c3fab411793b226ed776e4fe282271bca6ad3c0bb", size = 1280756, upload-time = "2026-08-16T22:55:54.834Z" }, + { url = "https://files.pythonhosted.org/packages/25/c0/cf6f9e1ef632a1a75694eed0db3a02e6fc75c367a363e94acee52f043c64/hypothesis-6.165.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79900a9920a0b1d3a626c03a90ac6bf7042e78d46906a565b86a0dbe926f1d96", size = 1323889, upload-time = "2026-08-16T22:55:56.567Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cc/662b94880f260b0a88de1fdcf60fc9984f6e2a796da549542adc10a7bc83/hypothesis-6.165.10-cp313-cp313-win_amd64.whl", hash = "sha256:c01dd04044c472e47193b54f68e84e08d6ebf4f29551885aa959b015f7cd9747", size = 672346, upload-time = "2026-08-16T22:56:03.792Z" }, + { url = "https://files.pythonhosted.org/packages/3f/77/55e020c9c576532ff7d20bf8b1dfa052ecbd5ada1949b02f76c44c966f7e/hypothesis-6.165.10-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9ccac776b2ca93b324806facd526ccb45da0fd035001c899a35b02c44431e209", size = 784833, upload-time = "2026-08-16T22:55:21.255Z" }, + { url = "https://files.pythonhosted.org/packages/4f/f2/01da2adf829cf549eaddcabb8e8072077fb3d26da4275f4c1e89b2c0af74/hypothesis-6.165.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e5f95f7b622e4171096d92175dda0a560f0955ade9b8a3a07bdcf151f7359611", size = 776545, upload-time = "2026-08-16T22:56:10.159Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/58d4f842895220b793c53fc94a6489705b3665bb4d0ae4d338ce03fdf9fb/hypothesis-6.165.10-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f76d1562643693b8a40066f1f96af795b93fd9bcfc9690a1af2ff4c5867ee29e", size = 1107271, upload-time = "2026-08-16T22:54:50.266Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b8/206468912d2153306bb8a41afdfc59e45b7a73a0495bbe4b9cb4f0e79c1d/hypothesis-6.165.10-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60cab3ab4ea468d31a33739ffd7e94ec3e37dea891d65a6582ecc8a477175191", size = 1156915, upload-time = "2026-08-16T22:54:25.89Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d3/bf5a22929b70a4cfd3edf69c5642b029b27ddb5cfda48fa295d384b01abb/hypothesis-6.165.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:22cf19388f0ff6ced8eb3e49c903d14938e4ed909d93bf28383eef451511e424", size = 1281205, upload-time = "2026-08-16T22:54:44.083Z" }, + { url = "https://files.pythonhosted.org/packages/07/a2/d7b2ba444d36fc84d4779f4431e74dd9b023dc63bcf282199f6e48ad39f4/hypothesis-6.165.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:057d0232f1224dcd0b7698902551a4341a7399f90670b036db6c4376715fe889", size = 1324243, upload-time = "2026-08-16T22:55:41.123Z" }, + { url = "https://files.pythonhosted.org/packages/d1/95/afe6b531fd01928c6f63d394ee413fa2338d088b2b44efcc23596b54477e/hypothesis-6.165.10-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:ab0f2e9d7d7d4db257f7cf53de3706c2baf124269571f20ffc2bcd6781f03063", size = 616382, upload-time = "2026-08-16T22:55:18.449Z" }, + { url = "https://files.pythonhosted.org/packages/48/86/9b4fb75f520a028edec50ffc904a94d724180395d71feb6d7a0ce7bb6f00/hypothesis-6.165.10-cp314-cp314-win_amd64.whl", hash = "sha256:d1ea02fa8ab3d33eb1125eade81f7136341eb429152c6dbe2ae6f8bc33b3fbdd", size = 672145, upload-time = "2026-08-16T22:54:24.831Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ba/f7bbaae0c789bab7ddb764d2056ee1a463cc95a8acbccc90d4184e48b242/hypothesis-6.165.10-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ed1a5891e59472884a03cb9875483e8fc131c80a275c60967f8afc5458a0c8ff", size = 783287, upload-time = "2026-08-16T22:54:23.751Z" }, + { url = "https://files.pythonhosted.org/packages/3a/83/01ef80772b4abd335c49405576dc503cede94fb5da30ba2643a119013aea/hypothesis-6.165.10-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:09772e328a26e50486ac572be34f9887f9aa185efe7ebb16bde4e8f6038db1f4", size = 774991, upload-time = "2026-08-16T22:55:25.987Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0b/f47506241f9d5a5a2efe4c65b6bf4830e9d9576e5d3779007a260699e608/hypothesis-6.165.10-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cf3b612542ba174c9da4000b59a4f4c81e8d66f87509be85d3a1b71b5c36413", size = 1105499, upload-time = "2026-08-16T22:54:51.864Z" }, + { url = "https://files.pythonhosted.org/packages/84/fe/abb3909b7089835112fbe75bf00d817d733b3a8032759783db0a24ff1e56/hypothesis-6.165.10-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69ec5be85ef508e206153bed8eafd03f7995dc464356c8bbb279a1e2b7d56f3", size = 1155685, upload-time = "2026-08-16T22:54:30.94Z" }, + { url = "https://files.pythonhosted.org/packages/73/2f/1964738921640184067121ae77414522fc3f0463fc26c6e25a4f3b8e42ca/hypothesis-6.165.10-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:dd207497bb985918409a1bb5db85d1875f74e1269487332113b73d1ee7c77647", size = 1279177, upload-time = "2026-08-16T22:54:40.179Z" }, + { url = "https://files.pythonhosted.org/packages/34/c5/312af8ae038d3af9cf3f7f1021c1abfe31c0d9035e4cf63519e0a7dc983e/hypothesis-6.165.10-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:00de0abdcf8c05c9d0eab735a3c49a276376b55151e6fcb903c2b39a90e5e5c3", size = 1322921, upload-time = "2026-08-16T22:54:42.7Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e7/b0a2fde7570c090a1b914026266a421c751ef10138fffe37fe0ef9e675c0/hypothesis-6.165.10-cp314-cp314t-win_amd64.whl", hash = "sha256:cc2da5aa4edf14743fa9257e5ba3513963999f01211635702479d8e92b8207c8", size = 672147, upload-time = "2026-08-16T22:55:27.527Z" }, + { url = "https://files.pythonhosted.org/packages/47/fd/985aa564d6ffd06483d45a62b40d319df0a703cd8bc1d041de17d102fbaa/hypothesis-6.165.10-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:eeab73050ea58c13dd56e329f594c1dfe32ebd7bb169bbdf4f8ceefbc31ec6b5", size = 782882, upload-time = "2026-08-16T22:55:37.93Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2c/6cc11151e450f72353a490940cd0db704680d07b78dc75dcc9f480e0d0e1/hypothesis-6.165.10-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:4c68e983d0007d014bb01ad4bcbba78bc432c73a1755ff36d5102ceefa18299a", size = 774584, upload-time = "2026-08-16T22:55:51.822Z" }, + { url = "https://files.pythonhosted.org/packages/10/39/ef26fa79c1738dfe9cdb1a3584fb6717d26429ca6c9d011cc4fdf08130c2/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7730d8197086f65d8969a991d6728a1d420a51b19fea06535c896cb43a1e05d0", size = 1104876, upload-time = "2026-08-16T22:54:58.937Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f4/3fcc84e7637f42bf00d987093b9418083ac8db81b87392608a60f4b7c5fd/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7a7980a898a3e6ebe4de1896a0507e3d519edb53fb9b4bda478c9fbeb6514558", size = 1133353, upload-time = "2026-08-16T22:54:28.635Z" }, + { url = "https://files.pythonhosted.org/packages/35/59/21c5c14179c38f8d0de3560e7f1825c083311b3013b63f817d7dc78dfcbd/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5820d009aedb7ae9cfd32f98b1ab0c0bbd6268379c4fab042218b6b655c63f8", size = 1132300, upload-time = "2026-08-16T22:56:08.539Z" }, + { url = "https://files.pythonhosted.org/packages/14/af/fbb56059961e416b2de7b9dc5352db2e8572bd5ea46892957e4c1e5548ab/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37a7ac3d34220800e1107871cc391bca1b00439875925d7d821878b8b791f245", size = 1155175, upload-time = "2026-08-16T22:55:19.824Z" }, + { url = "https://files.pythonhosted.org/packages/0f/53/77fb0c2dad445858555429c4e06cf94a59ae8d2407dd6426b5af97c84828/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:dafa7c9dbe3d802f9bcdf261b29c8a70700fb22839947f06e471f62c46b6257f", size = 1109881, upload-time = "2026-08-16T22:55:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/a8/7b/d187f673ff30e6ada640953636f978ffe64a6332f756b64163c2277f8d0c/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:90915635b9648071129b0f72c0673cf8eac9eb84cfd445c5bedef30c714b1ec2", size = 1144963, upload-time = "2026-08-16T22:56:13.428Z" }, + { url = "https://files.pythonhosted.org/packages/e0/60/31d504e364134d60af23e5f6365db0da3cf4a51b3ed3d4836e5a2cff12cf/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:e1bbeb7c506b07ee0422cf9b2f7212fefa4240957f03526d38d27bc6743a0a48", size = 1278684, upload-time = "2026-08-16T22:55:22.971Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e6/89d26834a08c02f8da149e541dd40d7a96f68d9722f43146e69a77436ed7/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:2b36aaffc88625a44f91074c5bbedfdefb9b376c38d1b3c342edcd2e4c8ed16c", size = 1407202, upload-time = "2026-08-16T22:55:14.949Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/20d1e72246867ea195440092e8bb422c7ddc2f271b87b5b65679d5532719/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:18a3ea838ddea183388f8788750afa8494d79abb5358823be9782585f34445d3", size = 1261395, upload-time = "2026-08-16T22:56:05.448Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9b/ebab6c3c2b90a16abb4119198178652d12aff83cc8ec2cfde5276c69fb1e/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:2a2567b3a03a4a5a7c575c191cfcce321a967df3727803817e75bffbbeaecabe", size = 1279213, upload-time = "2026-08-16T22:55:35.066Z" }, + { url = "https://files.pythonhosted.org/packages/23/78/69b219b524231d36eb20c792e1f01e7cb037e02bd0af1c29f77ed9a969c0/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:8001925fa3dde51cb574e4c9de4c7efe77c4e4d64bd2fd2ef61d5651f9d04f3d", size = 1322367, upload-time = "2026-08-16T22:54:21.279Z" }, + { url = "https://files.pythonhosted.org/packages/55/63/ad5cc153dcc72ae5e7905fb9b3585f3e48ce892a2d6366f90163e867a69d/hypothesis-6.165.10-cp315-abi3.abi3t-win32.whl", hash = "sha256:c6559380469295c4009215fe1cab561301591a3bee2e2fb3f4f96d2273a3affc", size = 666038, upload-time = "2026-08-16T22:56:11.797Z" }, + { url = "https://files.pythonhosted.org/packages/80/32/b62307b73fbc99f0a4381d6f9456df76fbcbb7a27ef7256e26f0376f48ea/hypothesis-6.165.10-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:30797f20ca45e57f526d2df872f63ba453cb4e1091ad542184a7a951af8da79d", size = 671941, upload-time = "2026-08-16T22:55:00.235Z" }, + { url = "https://files.pythonhosted.org/packages/c2/dd/e0f98add0548ef73ea7afac45da1fb8efc854d7f9931db568754d0f963f3/hypothesis-6.165.10-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:c53e9b1c36350df9965ec44d6c0d4e0bbbb38f720dd2b0e1256dc6524d411015", size = 669931, upload-time = "2026-08-16T22:55:50.205Z" }, + { url = "https://files.pythonhosted.org/packages/0b/6a/880d6eeed5c451fb40a66733dadec4a5d498628a4a7f6a8a5f633f4c6dcb/hypothesis-6.165.10-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:34ee6402df6f31274d89119f1561b5f7489c97866afc5b7a3ed3a13d7e762802", size = 784644, upload-time = "2026-08-16T22:54:20.127Z" }, + { url = "https://files.pythonhosted.org/packages/27/e0/9e942bd3c3cf5ea0d5c0fd0905893bbfb6cefb7284c70fcc8033f8fdec38/hypothesis-6.165.10-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:277f41801e88dad2eba082f91a75632b7584ff64044ba2cf9dadf511b0d19cd0", size = 780515, upload-time = "2026-08-16T22:55:04.676Z" }, + { url = "https://files.pythonhosted.org/packages/19/32/f11a618415dc5fa9cdde41fea56c489f0814759527ae1ecd11a75a4558b9/hypothesis-6.165.10-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72df95fb1db41755b155c5f02106e0036a339250555c8d351d488704fd112cf9", size = 1109374, upload-time = "2026-08-16T22:56:00.241Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6f/db49b719842297c2b71e0d81e5b8967d31215fb7389421abcb465ce7ed3f/hypothesis-6.165.10-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e20a02775eb3cf0ffb4f0219b6d7c1f240336663d4e5d7028675ec247c790c4", size = 1159092, upload-time = "2026-08-16T22:55:58.57Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2a/bf0bae84ba1cb3923d295973f1fe38ee867eaf90119e0d559116083be300/hypothesis-6.165.10-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1ec53f08732e3cfd0342cbbd75dbd1b193c8f19390660466e536a748bb81f757", size = 676045, upload-time = "2026-08-16T22:55:45.514Z" }, ] [[package]] diff --git a/src/fable-library-rust/src/Event.rs b/src/fable-library-rust/src/Event.rs index 7f873bfce..a19703221 100644 --- a/src/fable-library-rust/src/Event.rs +++ b/src/fable-library-rust/src/Event.rs @@ -11,7 +11,8 @@ pub mod Event_ { use crate::Choice_::Choice_2; use crate::Microsoft::FSharp::Control::{IDelegateEvent_1, IEvent_2}; use crate::Native_::{ - getZeroObj, referenceEquals, refCell, Any, Func0, Func1, Func2, LrcPtr, MutCell, Vec, + getZeroObj, interface_cast, referenceEquals, refCell, Any, Func0, Func1, Func2, Lrc, LrcPtr, + MutCell, Vec, }; use crate::Observable_::{mkDisposable, mkObserver, noCompleted, noError}; use crate::System::{IDisposable, IObservable_1, IObserver_1}; @@ -20,9 +21,7 @@ pub mod Event_ { pub type Handler = Func2, T, ()>; // ----- AnonymousEvent: the published IEvent ----- - // A concrete type so the Publish field can be used through IEvent_2 - // (AddHandler/RemoveHandler) *and* unsize-coerced to `dyn IObservable_1` - // (in F#/JS an IEvent is also an IObservable). + // A concrete implementation of IEvent_2 and IObservable_1. #[derive(Clone)] pub struct AnonymousEvent { add_handler: Func1, ()>, @@ -91,11 +90,43 @@ pub mod Event_ { } } + #[derive(Clone)] + struct EventObservable { + source: LrcPtr, T>>, + } + + impl core::fmt::Debug for EventObservable { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", core::any::type_name::()) + } + } + + impl core::fmt::Display for EventObservable { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", core::any::type_name::()) + } + } + + impl IObservable_1 for EventObservable { + fn Subscribe(&self, observer: LrcPtr>) -> LrcPtr { + self.source.Subscribe(observer) + } + } + + pub fn asObservable( + source: LrcPtr, T>>, + ) -> LrcPtr> { + interface_cast!( + LrcPtr::new(EventObservable { source }), + Lrc>, + ) + } + // ----- FSharpEvent`1 ----- #[derive(Clone)] pub struct FSharpEvent_1 { handlers: LrcPtr>>>, - pub Publish: LrcPtr>, + pub Publish: LrcPtr, T>>, } impl core::fmt::Debug for FSharpEvent_1 { @@ -140,10 +171,10 @@ pub mod Event_ { } } }); - let publish = LrcPtr::new(AnonymousEvent { + let publish = interface_cast!(LrcPtr::new(AnonymousEvent { add_handler, remove_handler, - }); + }), Lrc, T>>,); LrcPtr::new(FSharpEvent_1 { handlers, Publish: publish, @@ -161,6 +192,57 @@ pub mod Event_ { }) } + // ----- FSharpEvent`2 ----- + // Sender-aware events use the same normalized Handler shape as one-generic events. + #[derive(Clone)] + pub struct FSharpEvent_2 { + handlers: LrcPtr>>>, + pub Publish: LrcPtr, T>>, + } + + impl core::fmt::Debug for FSharpEvent_2 { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", core::any::type_name::()) + } + } + + impl core::fmt::Display for FSharpEvent_2 { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", core::any::type_name::()) + } + } + + impl FSharpEvent_2 { + pub fn Trigger(&self, sender: LrcPtr, value: T) { + let hs = self.handlers.get().clone(); + for handler in hs.iter() { + handler(sender.clone(), value.clone()); + } + } + } + + pub fn default2() -> LrcPtr> { + let handlers: LrcPtr>>> = refCell(Vec::new()); + let add_handler = Func1::new({ + let handlers = handlers.clone(); + move |handler: Handler| handlers.get_mut().push(handler) + }); + let remove_handler = Func1::new({ + let handlers = handlers.clone(); + move |handler: Handler| { + let vec = handlers.get_mut(); + if let Some(pos) = vec.iter().position(|existing| referenceEquals(&**existing, &*handler)) { + vec.remove(pos); + } + } + }); + let publish = interface_cast!(LrcPtr::new(AnonymousEvent { + add_handler, + remove_handler, + }), Lrc, T>>,); + LrcPtr::new(FSharpEvent_2 { handlers, Publish: publish }) + } + // ----- module functions ----- pub fn add( @@ -275,14 +357,14 @@ pub mod Event_ { )> { let sp = splitter.clone(); let first = choose( - Func1::new(move |v: T| match &*sp(v) { + Func1::new(move |v: T| match sp(v).as_ref() { Choice_2::Choice1Of2(x) => Some(x.clone()), _ => None, }), source.clone(), ); let second = choose( - Func1::new(move |v: T| match &*splitter(v) { + Func1::new(move |v: T| match splitter(v).as_ref() { Choice_2::Choice2Of2(x) => Some(x.clone()), _ => None, }), diff --git a/src/fable-library-rust/src/Native.rs b/src/fable-library-rust/src/Native.rs index a6c238300..c16859d49 100644 --- a/src/fable-library-rust/src/Native.rs +++ b/src/fable-library-rust/src/Native.rs @@ -118,6 +118,20 @@ pub mod Native_ { } } + #[cfg(not(feature = "lrc_ptr"))] + impl NullableRef for Lrc { + #[inline] + fn null() -> Self { + static NULL: OnceInit> = OnceInit::new(); + NULL.get_or_init(|| Lrc::new(())).clone() + } + + #[inline] + fn is_null(&self) -> bool { + Lrc::ptr_eq(self, &Self::null()) + } + } + impl NullableRef for Option { #[inline] fn null() -> Self { @@ -314,11 +328,10 @@ pub mod Native_ { unsafe { core::mem::zeroed() } // will panic on Rc/Arc/Box } - // A valid placeholder value of type `LrcPtr`. Used to pre-declare - // reference-typed (`obj`) match bindings, as `getZero`/`mem::zeroed` panics on - // the `Rc` fat pointer and `null` is not available for the unsized `dyn Any`. + // A valid null value of type `LrcPtr`, used for `obj` match bindings + // and event sender arguments. pub fn getZeroObj() -> LrcPtr { - box_(()) + null::>() } pub fn defaultOf() -> T { @@ -686,6 +699,7 @@ pub mod Native_ { #[cfg(not(feature = "lrc_ptr"))] pub fn box_(x: T) -> LrcPtr { + crate::Reflection_::register_type_name::(); match (&x as &dyn Any).downcast_ref::>() { Some(o) => o.clone(), None => LrcPtr::new(x) as LrcPtr, @@ -694,6 +708,7 @@ pub mod Native_ { #[cfg(feature = "lrc_ptr")] pub fn box_(x: T) -> LrcPtr { + crate::Reflection_::register_type_name::(); match (&x as &dyn Any).downcast_ref::>() { Some(o) => o.clone(), None => LrcPtr::from(Lrc::new(x) as Lrc), @@ -712,6 +727,7 @@ pub mod Native_ { // site) match the pointee's runtime type id for value-based reflection. #[cfg(not(feature = "lrc_ptr"))] pub fn box_lrc(o: LrcPtr) -> LrcPtr { + crate::Reflection_::register_type_name::(); o } @@ -720,11 +736,24 @@ pub mod Native_ { // pointer identity and the pointee's runtime type id are both preserved. #[cfg(feature = "lrc_ptr")] pub fn box_lrc(o: LrcPtr) -> LrcPtr { + crate::Reflection_::register_type_name::(); let inner: Lrc = Lrc::clone(&*o); let coerced: Lrc = inner; LrcPtr::from(coerced) } + pub fn box_rc(o: Rc) -> LrcPtr { + box_((*o).clone()) + } + + pub fn box_arc(o: Arc) -> LrcPtr { + box_((*o).clone()) + } + + pub fn box_box(o: Box) -> LrcPtr { + box_((*o).clone()) + } + pub fn unbox_lrc(o: LrcPtr) -> LrcPtr { #[cfg(not(feature = "lrc_ptr"))] let inner: Lrc = o; @@ -746,6 +775,18 @@ pub mod Native_ { } } + pub fn unbox_rc(o: LrcPtr) -> Rc { + Rc::new(unbox(o)) + } + + pub fn unbox_arc(o: LrcPtr) -> Arc { + Arc::new(unbox(o)) + } + + pub fn unbox_box(o: LrcPtr) -> Box { + Box::new(unbox(o)) + } + pub fn ofObj(value: T) -> Option { if is_null(&value) { None:: diff --git a/src/fable-library-rust/src/Observable.rs b/src/fable-library-rust/src/Observable.rs index 826370874..9c1db5718 100644 --- a/src/fable-library-rust/src/Observable.rs +++ b/src/fable-library-rust/src/Observable.rs @@ -1,6 +1,7 @@ pub mod Observable_ { use crate::Choice_::Choice_2; - use crate::Native_::{interface_cast, refCell, Func0, Func1, Func2, Lrc, LrcPtr}; + use crate::Microsoft::FSharp::Control::IEvent_2; + use crate::Native_::{interface_cast, refCell, Func0, Func1, Func2, Lrc, LrcPtr, RefCell}; use crate::System::{Exception, IDisposable, IObservable_1, IObserver_1}; // ----------------------------------------------------------- @@ -92,6 +93,22 @@ pub mod Observable_ { } } + pub trait ObservableSource: Clone + 'static { + fn subscribe_source(&self, observer: LrcPtr>) -> LrcPtr; + } + + impl + ?Sized + 'static, T: Clone + 'static> ObservableSource for LrcPtr { + fn subscribe_source(&self, observer: LrcPtr>) -> LrcPtr { + self.Subscribe(observer) + } + } + + impl IObservable_1 for dyn IEvent_2 { + fn Subscribe(&self, observer: LrcPtr>) -> LrcPtr { + IEvent_2::Subscribe(self, observer) + } + } + // ----------------------------------------------------------- // Constructors // ----------------------------------------------------------- @@ -146,46 +163,111 @@ pub mod Observable_ { Func0::new(move || observer.OnCompleted()) } + fn protect(action: F, on_error: Func1, ()>) { + crate::Exception_::try_catch( + || action(), + |error| { + on_error(error); + }, + ); + } + + struct StoppedObserver { + stopped: RefCell, + on_next: Func1, + on_error: Func1, ()>, + on_completed: Func0<()>, + } + + fn stoppedObserver( + observer: LrcPtr>, + ) -> StoppedObserver { + let stopped = refCell(false); + let on_next = { + let stopped = stopped.clone(); + let observer = observer.clone(); + Func1::new(move |value: T| { + if !*stopped.get() { + observer.OnNext(value); + } + }) + }; + let on_error = { + let stopped = stopped.clone(); + let observer = observer.clone(); + Func1::new(move |error: LrcPtr| { + if !*stopped.get() { + stopped.set(true); + observer.OnError(error); + } + }) + }; + let on_completed = { + let stopped = stopped.clone(); + Func0::new(move || { + if !*stopped.get() { + stopped.set(true); + observer.OnCompleted(); + } + }) + }; + StoppedObserver { + stopped, + on_next, + on_error, + on_completed, + } + } + // ----------------------------------------------------------- // Combinators // ----------------------------------------------------------- - pub fn add(callback: Func1, source: LrcPtr>) { - source.Subscribe(mkObserver(callback, noError(), noCompleted())); + pub fn add, T: Clone + 'static>(callback: Func1, source: S) { + source.subscribe_source(mkObserver(callback, noError(), noCompleted())); } - pub fn subscribe( + pub fn subscribe, T: Clone + 'static>( callback: Func1, - source: LrcPtr>, + source: S, ) -> LrcPtr { - source.Subscribe(mkObserver(callback, noError(), noCompleted())) + source.subscribe_source(mkObserver(callback, noError(), noCompleted())) } - pub fn choose( + pub fn choose, T: Clone + 'static, U: Clone + 'static>( chooser: Func1>, - source: LrcPtr>, + source: S, ) -> LrcPtr> { mkObservable(Func1::new( move |observer: LrcPtr>| -> LrcPtr { let chooser = chooser.clone(); - let obs = observer.clone(); + let guard = stoppedObserver(observer.clone()); + let on_next = guard.on_next.clone(); + let errorObserver = observer.clone(); + let on_error = guard.on_error.clone(); + let on_completed = guard.on_completed.clone(); let inner = mkObserver( Func1::new(move |t: T| { - if let Some(u) = chooser(t) { - obs.OnNext(u); - } + protect( + || { + if let Some(u) = chooser(t) { + on_next(u); + } + }, + fwdError(errorObserver.clone()), + ); }), - fwdError(observer.clone()), - fwdCompleted(observer.clone()), + on_error, + on_completed, ); - source.Subscribe(inner) + source.subscribe_source(inner) }, )) } - pub fn filter( + pub fn filter, T: Clone + 'static>( predicate: Func1, - source: LrcPtr>, + source: S, ) -> LrcPtr> { choose( Func1::new(move |x: T| if predicate(x.clone()) { Some(x) } else { None }), @@ -193,55 +275,51 @@ pub mod Observable_ { ) } - pub fn map( + pub fn map, T: Clone + 'static, U: Clone + 'static>( mapping: Func1, - source: LrcPtr>, + source: S, ) -> LrcPtr> { mkObservable(Func1::new( move |observer: LrcPtr>| -> LrcPtr { let mapping = mapping.clone(); - let obs = observer.clone(); + let guard = stoppedObserver(observer.clone()); + let on_next = guard.on_next.clone(); + let errorObserver = observer.clone(); + let on_error = guard.on_error.clone(); + let on_completed = guard.on_completed.clone(); let inner = mkObserver( - Func1::new(move |t: T| obs.OnNext(mapping(t))), - fwdError(observer.clone()), - fwdCompleted(observer.clone()), + Func1::new(move |t: T| { + protect(|| on_next(mapping(t)), fwdError(errorObserver.clone())); + }), + on_error, + on_completed, ); - source.Subscribe(inner) + source.subscribe_source(inner) }, )) } - pub fn merge( - source1: LrcPtr>, - source2: LrcPtr>, + pub fn merge, S2: ObservableSource, T: Clone + 'static>( + source1: S1, + source2: S2, ) -> LrcPtr> { mkObservable(Func1::new( move |observer: LrcPtr>| -> LrcPtr { - let stopped = refCell(false); + let guard = stoppedObserver(observer.clone()); + let stopped = guard.stopped.clone(); + let on_next = guard.on_next.clone(); + let on_error = guard.on_error.clone(); let completed1 = refCell(false); let completed2 = refCell(false); let h1 = { - let stopped = stopped.clone(); let completed1 = completed1.clone(); let completed2 = completed2.clone(); - let obs_n = observer.clone(); - let obs_e = observer.clone(); let obs_c = observer.clone(); - let se = stopped.clone(); let sc = stopped.clone(); - source1.Subscribe(mkObserver( - Func1::new(move |v: T| { - if !*stopped.get() { - obs_n.OnNext(v); - } - }), - Func1::new(move |e: LrcPtr| { - if !*se.get() { - se.set(true); - obs_e.OnError(e); - } - }), + source1.subscribe_source(mkObserver( + on_next.clone(), + on_error.clone(), Func0::new(move || { if !*sc.get() { completed1.set(true); @@ -255,26 +333,13 @@ pub mod Observable_ { }; let h2 = { - let stopped = stopped.clone(); let completed1 = completed1.clone(); let completed2 = completed2.clone(); - let obs_n = observer.clone(); - let obs_e = observer.clone(); let obs_c = observer.clone(); - let se = stopped.clone(); let sc = stopped.clone(); - source2.Subscribe(mkObserver( - Func1::new(move |v: T| { - if !*stopped.get() { - obs_n.OnNext(v); - } - }), - Func1::new(move |e: LrcPtr| { - if !*se.get() { - se.set(true); - obs_e.OnError(e); - } - }), + source2.subscribe_source(mkObserver( + on_next, + on_error, Func0::new(move || { if !*sc.get() { completed2.set(true); @@ -295,8 +360,8 @@ pub mod Observable_ { )) } - pub fn pairwise( - source: LrcPtr>, + pub fn pairwise, T: Clone + 'static>( + source: S, ) -> LrcPtr>> { mkObservable(Func1::new( move |observer: LrcPtr>>| -> LrcPtr { @@ -312,14 +377,14 @@ pub mod Observable_ { fwdError(observer.clone()), fwdCompleted(observer.clone()), ); - source.Subscribe(inner) + source.subscribe_source(inner) }, )) } - pub fn partition( + pub fn partition, T: Clone + 'static>( predicate: Func1, - source: LrcPtr>, + source: S, ) -> LrcPtr<(LrcPtr>, LrcPtr>)> { let pred = predicate.clone(); let first = filter(predicate, source.clone()); @@ -327,44 +392,53 @@ pub mod Observable_ { LrcPtr::new((first, second)) } - pub fn scan( + pub fn scan, U: Clone + 'static, T: Clone + 'static>( collector: Func2, state: U, - source: LrcPtr>, + source: S, ) -> LrcPtr> { mkObservable(Func1::new( move |observer: LrcPtr>| -> LrcPtr { let collector = collector.clone(); let st = refCell(state.clone()); - let obs = observer.clone(); + let guard = stoppedObserver(observer.clone()); + let on_next = guard.on_next.clone(); + let errorObserver = observer.clone(); + let on_error = guard.on_error.clone(); + let on_completed = guard.on_completed.clone(); let inner = mkObserver( Func1::new(move |t: T| { - let u = collector(st.get().clone(), t); - st.set(u.clone()); - obs.OnNext(u); + protect( + || { + let u = collector(st.get().clone(), t); + st.set(u.clone()); + on_next(u); + }, + fwdError(errorObserver.clone()), + ); }), - fwdError(observer.clone()), - fwdCompleted(observer.clone()), + on_error, + on_completed, ); - source.Subscribe(inner) + source.subscribe_source(inner) }, )) } - pub fn split( + pub fn split, T: Clone + 'static, U1: Clone + 'static, U2: Clone + 'static>( splitter: Func1>>, - source: LrcPtr>, + source: S, ) -> LrcPtr<(LrcPtr>, LrcPtr>)> { let sp = splitter.clone(); let first = choose( - Func1::new(move |v: T| match &*sp(v) { + Func1::new(move |v: T| match sp(v).as_ref() { Choice_2::Choice1Of2(x) => Some(x.clone()), _ => None, }), source.clone(), ); let second = choose( - Func1::new(move |v: T| match &*splitter(v) { + Func1::new(move |v: T| match splitter(v).as_ref() { Choice_2::Choice2Of2(x) => Some(x.clone()), _ => None, }), diff --git a/src/fable-library-rust/src/Path.rs b/src/fable-library-rust/src/Path.rs index d40f9d547..39d4129be 100644 --- a/src/fable-library-rust/src/Path.rs +++ b/src/fable-library-rust/src/Path.rs @@ -1,5 +1,6 @@ #[cfg(not(feature = "no_std"))] pub mod Path_ { + use crate::Native_::NullableRef; use crate::String_::{fromString, string}; fn is_sep(c: char) -> bool { @@ -24,12 +25,24 @@ pub mod Path_ { // or an empty string when there is no separator. Trailing separators on // the directory portion are trimmed, matching .NET semantics. pub fn getDirectoryName(path: string) -> string { + if path.is_null() { + return ::null(); + } + let s = path.as_str(); + if s.is_empty() || (s.len() == 1 && is_sep(s.chars().next().unwrap())) { + return ::null(); + } + match s.rfind(is_sep) { Some(i) => { let dir = &s[..i]; let dir = dir.trim_end_matches(is_sep); - fromString(dir.to_string()) + if dir.is_empty() && !s.is_empty() { + fromString(s[..1].to_string()) + } else { + fromString(dir.to_string()) + } } None => string(""), } diff --git a/src/fable-library-rust/src/Quotation.fs b/src/fable-library-rust/src/Quotation.fs index f10f2e517..4cd3b281b 100644 --- a/src/fable-library-rust/src/Quotation.fs +++ b/src/fable-library-rust/src/Quotation.fs @@ -201,9 +201,140 @@ let isFieldGet (e: FSharpExpr) = Some(inst, pi, ([]: FSharpExpr list)) | _ -> None +let private joinStrings (separator: string) (parts: string list) = + match parts with + | [] -> "" + | first :: rest -> List.fold (fun result part -> result + separator + part) first rest + +let private operatorSymbol (methodName: string) = + match methodName with + | "op_Addition" -> Some "+" + | "op_Subtraction" -> Some "-" + | "op_Multiply" -> Some "*" + | "op_Division" -> Some "/" + | "op_Modulus" -> Some "%" + | "op_Exponentiation" -> Some "**" + | "op_Equality" -> Some "=" + | "op_Inequality" -> Some "<>" + | "op_LessThan" -> Some "<" + | "op_LessThanOrEqual" -> Some "<=" + | "op_GreaterThan" -> Some ">" + | "op_GreaterThanOrEqual" -> Some ">=" + | "op_BooleanAnd" -> Some "&&" + | "op_BooleanOr" -> Some "||" + | "op_UnaryNegation" -> Some "-" + | "op_LogicalNot" -> Some "not" + | _ -> None + +let private valueToString (value: obj) (typ: string) = + match typ with + | "string" -> "\"" + unbox value + "\"" + | "unit" -> "()" + | "bool" -> + if unbox value then + "true" + else + "false" + | "char" -> sprintf "%O" (unbox value) + | "int8" -> sprintf "%O" (unbox value) + | "uint8" -> sprintf "%O" (unbox value) + | "int16" -> sprintf "%O" (unbox value) + | "uint16" -> sprintf "%O" (unbox value) + | "int32" -> sprintf "%O" (unbox value) + | "uint32" -> sprintf "%O" (unbox value) + | "int64" -> sprintf "%O" (unbox value) + | "uint64" -> sprintf "%O" (unbox value) + | "nativeint" -> sprintf "%O" (unbox value) + | "unativeint" -> sprintf "%O" (unbox value) + | "float32" -> sprintf "%O" (unbox value) + | "float64" -> sprintf "%O" (unbox value) + | _ -> "" + +let rec exprToString (e: FSharpExpr) : string = + match e with + | ExprValue(value, typ) -> valueToString value typ + | ExprVarExpr v -> v.Name + | ExprLambda(v, body) -> "fun " + v.Name + " -> " + exprToString body + | ExprApplication(func, arg) -> exprToString func + " " + exprToString arg + | ExprLet(v, value, body) -> "let " + v.Name + " = " + exprToString value + " in " + exprToString body + | ExprIfThenElse(guard, thenExpr, elseExpr) -> + "if " + + exprToString guard + + " then " + + exprToString thenExpr + + " else " + + exprToString elseExpr + | ExprCall(_, methodName, args, _) -> + let renderedArgs = args |> List.ofArray |> List.map exprToString + + match operatorSymbol methodName, renderedArgs with + | Some symbol, [ left; right ] -> "(" + left + " " + symbol + " " + right + ")" + | Some symbol, [ operand ] -> symbol + operand + | _ -> methodName + "(" + joinStrings ", " renderedArgs + ")" + | ExprSequential(first, second) -> exprToString first + "; " + exprToString second + | ExprNewTuple elements -> + "(" + + (elements |> List.ofArray |> List.map exprToString |> joinStrings ", ") + + ")" + | ExprTupleGet(inner, index) -> "Item" + string (index + 1) + "(" + exprToString inner + ")" + | ExprFieldGet(inner, fieldName) -> exprToString inner + "." + fieldName + | _ -> "" + +let substitute (e: FSharpExpr) (substitution: FSharpVar -> FSharpExpr option) : FSharpExpr = + let rec substituteExpr expression = + match expression with + | ExprValue _ -> expression + | ExprVarExpr variable -> + match substitution variable with + | Some replacement -> replacement + | None -> expression + | ExprLambda(variable, body) -> ExprLambda(variable, substituteExpr body) + | ExprApplication(func, arg) -> ExprApplication(substituteExpr func, substituteExpr arg) + | ExprLet(variable, value, body) -> ExprLet(variable, substituteExpr value, substituteExpr body) + | ExprIfThenElse(guard, thenExpr, elseExpr) -> + ExprIfThenElse(substituteExpr guard, substituteExpr thenExpr, substituteExpr elseExpr) + | ExprCall(instance, methodName, args, declaringType) -> + let substitutedArgs = ResizeArray() + + for argument in args do + substitutedArgs.Add(substituteExpr argument) + + ExprCall(substituteExpr instance, methodName, substitutedArgs.ToArray(), declaringType) + | ExprSequential(first, second) -> ExprSequential(substituteExpr first, substituteExpr second) + | ExprNewTuple elements -> + let substitutedElements = ResizeArray() + + for element in elements do + substitutedElements.Add(substituteExpr element) + + ExprNewTuple(substitutedElements.ToArray()) + | ExprNewUnion(typeName, tag, caseName, fields) -> + let substitutedFields = ResizeArray() + + for field in fields do + substitutedFields.Add(substituteExpr field) + + ExprNewUnion(typeName, tag, caseName, substitutedFields.ToArray()) + | ExprNewRecord(typeName, fieldNames, values) -> + let substitutedValues = ResizeArray() + + for value in values do + substitutedValues.Add(substituteExpr value) + + ExprNewRecord(typeName, fieldNames, substitutedValues.ToArray()) + | ExprNewList(head, tail) -> ExprNewList(substituteExpr head, substituteExpr tail) + | ExprTupleGet(inner, index) -> ExprTupleGet(substituteExpr inner, index) + | ExprUnionTag inner -> ExprUnionTag(substituteExpr inner) + | ExprUnionField(inner, fieldIndex) -> ExprUnionField(substituteExpr inner, fieldIndex) + | ExprFieldGet(inner, fieldName) -> ExprFieldGet(substituteExpr inner, fieldName) + | ExprFieldSet(inner, fieldName, value) -> ExprFieldSet(substituteExpr inner, fieldName, substituteExpr value) + | ExprVarSet(target, value) -> ExprVarSet(substituteExpr target, substituteExpr value) + + substituteExpr e + // --- Free variables --- -let getFreeVars (e: FSharpExpr) : FSharpVar list = +let getFreeVars (e: FSharpExpr) : FSharpVar seq = let seen = System.Collections.Generic.HashSet() let acc = ResizeArray() @@ -223,7 +354,9 @@ let getFreeVars (e: FSharpExpr) : FSharpVar list = walk bound g walk bound t walk bound el - | ExprCall(_, _, args, _) -> + | ExprCall(instance, _, args, _) -> + walk bound instance + for a in args do walk bound a | ExprSequential(f, s) -> @@ -232,12 +365,29 @@ let getFreeVars (e: FSharpExpr) : FSharpVar list = | ExprNewTuple els -> for el in els do walk bound el + | ExprNewUnion(_, _, _, fields) -> + for field in fields do + walk bound field + | ExprNewRecord(_, _, values) -> + for value in values do + walk bound value + | ExprNewList(head, tail) -> + walk bound head + walk bound tail | ExprTupleGet(inner, _) -> walk bound inner + | ExprUnionTag inner -> walk bound inner + | ExprUnionField(inner, _) -> walk bound inner | ExprFieldGet(inner, _) -> walk bound inner + | ExprFieldSet(inner, _, value) -> + walk bound inner + walk bound value + | ExprVarSet(target, value) -> + walk bound target + walk bound value | _ -> () walk Set.empty e - List.ofSeq acc + acc.ToArray() |> Seq.ofArray // --- Evaluation (structural cases + common operators; SQL translation deconstructs // rather than evaluates, so this covers the tested subset). --- @@ -285,15 +435,29 @@ let private applyOperator (method: string) (args: obj list) : obj = failwithf "Cannot evaluate method: %s" method let evaluate (e: FSharpExpr) : obj = - let rec eval (env: Map) (e: FSharpExpr) : obj = + let rec eval (env: Map ref) (e: FSharpExpr) : obj = + let evalArrayField (values: obj[]) (index: int) = + if index < 0 || index >= values.Length then + failwith "Quotation field index is out of range" + + values.[index] + match e with - | ExprValue(v, _) -> v - | ExprVarExpr v -> Map.find v.Name env - | ExprLambda(v, body) -> box (fun (arg: obj) -> eval (Map.add v.Name arg env) body) + | ExprValue(v, typ) -> + match typ with + | "unit" -> v + | "list" -> box ([||]: obj[]) + | _ -> v + | ExprVarExpr v -> Map.find v.Name env.Value + | ExprLambda(v, body) -> + let capturedEnv = env.Value + box (fun (arg: obj) -> eval (ref (Map.add v.Name arg capturedEnv)) body) | ExprApplication(f, a) -> let func = unbox obj> (eval env f) func (eval env a) - | ExprLet(v, value, body) -> eval (Map.add v.Name (eval env value) env) body + | ExprLet(v, value, body) -> + let value = eval env value + eval (ref (Map.add v.Name value env.Value)) body | ExprIfThenElse(g, t, el) -> if unbox (eval env g) then eval env t @@ -302,7 +466,33 @@ let evaluate (e: FSharpExpr) : obj = | ExprSequential(a, b) -> eval env a |> ignore eval env b - | ExprCall(_, method, args, _) -> applyOperator method [ for a in args -> eval env a ] + | ExprCall(instance, method, args, _) -> + let evaluatedArgs = [ for argument in args -> eval env argument ] + + match method with + | "get_IsSome" + | "get_IsNone" -> + let values = unbox (eval env instance) + let isSome = values.Length > 0 && unbox values.[0] = 1 + + box ( + if method = "get_IsSome" then + isSome + else + not isSome + ) + | "get_IsCons" + | "get_IsEmpty" -> + let values = unbox (eval env instance) + let isCons = values.Length > 0 + + box ( + if method = "get_IsCons" then + isCons + else + not isCons + ) + | _ -> applyOperator method evaluatedArgs // A tuple literal evaluates to a boxed obj[] of its evaluated elements, mirroring // PHP (which produces a plain array). TupleGet then indexes into that array; without // this arm `eval inner` on a tuple literal would fall through to failwith and there @@ -313,7 +503,83 @@ let evaluate (e: FSharpExpr) : obj = // operator wants an owned i32; `index + 0` forces an owned i32 rvalue (the // arithmetic dereferences the borrow) so the indexing type-checks. let i = index + 0 - (unbox (eval env inner)).[i] - | _ -> failwith "Cannot evaluate expression" + evalArrayField (unbox (eval env inner)) i + | ExprNewUnion(_, tag, _, fields) -> + let values = ResizeArray() + values.Add(box tag) + + for field in fields do + values.Add(eval env field) + + box (values.ToArray()) + | ExprNewRecord(_, fieldNames, values) -> + let result = ResizeArray() + + for i = 0 to fieldNames.Length - 1 do + result.Add(box fieldNames.[i]) + result.Add(eval env values.[i]) + + box (result.ToArray()) + | ExprNewList(head, tail) -> + let result = ResizeArray() + result.Add(eval env head) + + for value in unbox (eval env tail) do + result.Add(value) + + box (result.ToArray()) + | ExprUnionTag inner -> + let values = unbox (eval env inner) + evalArrayField values 0 + | ExprUnionField(inner, fieldIndex) -> + let values = unbox (eval env inner) + evalArrayField values (fieldIndex + 1) + | ExprFieldGet(inner, fieldName) -> + let values = unbox (eval env inner) + + if fieldName = "Head" then + evalArrayField values 0 + elif fieldName = "Tail" then + box values.[1..] + elif fieldName = "Value" && values.Length > 1 && unbox values.[0] = 1 then + values.[1] + else + let mutable result = None + let mutable index = 0 + + while index + 1 < values.Length && result.IsNone do + if unbox values.[index] = fieldName then + result <- Some values.[index + 1] + + index <- index + 2 + + match result with + | Some value -> value + | None -> failwithf "Quotation field not found: %s" fieldName + | ExprFieldSet(target, fieldName, value) -> + let target = unbox (eval env target) + let value = eval env value + + let rec findFieldIndex index = + if index + 1 >= target.Length then + None + elif unbox target.[index] = fieldName then + Some index + else + findFieldIndex (index + 2) + + match findFieldIndex 0 with + | Some index -> target.[index + 1] <- value + | None -> failwithf "Quotation field not found: %s" fieldName + + box 0 + | ExprVarSet(target, value) -> + let value = eval env value + + match target with + | ExprVarExpr variable -> env.Value <- Map.add variable.Name value env.Value + | _ -> failwith "Quotation VarSet target must be a variable" + + box 0 - eval Map.empty e + eval (ref Map.empty) e diff --git a/src/fable-library-rust/src/Reflection.rs b/src/fable-library-rust/src/Reflection.rs index 713b62ecc..3a95cf5ce 100644 --- a/src/fable-library-rust/src/Reflection.rs +++ b/src/fable-library-rust/src/Reflection.rs @@ -4,12 +4,14 @@ pub mod Reflection_ { use crate::Microsoft::FSharp::Quotations::FSharpPropertyInfo; use crate::Native_::{box_, Any, Func1, LrcPtr, Vec}; use crate::NativeArray_::{array_from, Array}; - use crate::String_::string; + use crate::String_::{fromString, string}; - #[cfg(not(feature = "no_std"))] + #[cfg(all(not(feature = "no_std"), not(feature = "threaded")))] use core::cell::RefCell; #[cfg(not(feature = "no_std"))] use std::collections::HashMap; + #[cfg(all(not(feature = "no_std"), feature = "threaded"))] + use std::sync::{OnceLock, RwLock}; pub fn name() -> string { // TODO: map some common type names to .NET type names @@ -19,8 +21,105 @@ pub mod Reflection_ { // The object (System.Object / obj) representation on the Rust target. type obj = LrcPtr; + #[cfg(not(feature = "lrc_ptr"))] + fn object_type_id(value: &obj) -> TypeId { + (&**value).type_id() + } + + #[cfg(feature = "lrc_ptr")] + fn object_type_id(value: &obj) -> TypeId { + (***value).type_id() + } + + #[cfg(all(not(feature = "no_std"), not(feature = "threaded")))] + thread_local! { + static TYPE_NAME_REGISTRY: RefCell> = + RefCell::new(HashMap::new()); + } + + #[cfg(all(not(feature = "no_std"), feature = "threaded"))] + static TYPE_NAME_REGISTRY: OnceLock>> = OnceLock::new(); + + #[cfg(all(not(feature = "no_std"), not(feature = "threaded")))] + fn register_type_name_value(tid: TypeId, name: string) { + TYPE_NAME_REGISTRY.with(|r| { + r.borrow_mut().insert(tid, name); + }); + } + + #[cfg(all(not(feature = "no_std"), feature = "threaded"))] + fn register_type_name_value(tid: TypeId, name: string) { + TYPE_NAME_REGISTRY + .get_or_init(|| RwLock::new(HashMap::new())) + .write() + .unwrap() + .insert(tid, name); + } + + #[cfg(feature = "no_std")] + fn register_type_name_value(_tid: TypeId, _name: string) {} + + #[cfg(all(not(feature = "no_std"), not(feature = "threaded")))] + fn registered_type_name(tid: &TypeId) -> Option { + TYPE_NAME_REGISTRY.with(|r| r.borrow().get(tid).cloned()) + } + + #[cfg(all(not(feature = "no_std"), feature = "threaded"))] + fn registered_type_name(tid: &TypeId) -> Option { + TYPE_NAME_REGISTRY + .get_or_init(|| RwLock::new(HashMap::new())) + .read() + .unwrap() + .get(tid) + .cloned() + } + + #[cfg(feature = "no_std")] + fn registered_type_name(_tid: &TypeId) -> Option { + None + } + + #[cfg(not(feature = "no_std"))] + fn canonical_type_name() -> string { + let rust_name = core::any::type_name::(); + let name = if TypeId::of::() == TypeId::of::() { + "System.String" + } else { + match rust_name { + "bool" => "System.Boolean", + "char" => "System.Char", + "i8" => "System.SByte", + "u8" => "System.Byte", + "i16" => "System.Int16", + "u16" => "System.UInt16", + "i32" => "System.Int32", + "u32" => "System.UInt32", + "i64" => "System.Int64", + "u64" => "System.UInt64", + "f32" => "System.Single", + "f64" => "System.Double", + _ => rust_name, + } + }; + fromString(name.to_string()) + } + + #[cfg(all(not(feature = "no_std"), not(feature = "threaded")))] + pub fn register_type_name() { + register_type_name_value(TypeId::of::(), canonical_type_name::()); + } + + #[cfg(all(not(feature = "no_std"), feature = "threaded"))] + pub fn register_type_name() { + register_type_name_value(TypeId::of::(), canonical_type_name::()); + } + + #[cfg(feature = "no_std")] + pub fn register_type_name() {} + // Compile-time helper emitted by `typeof` to obtain a concrete type id. pub fn type_id() -> TypeId { + register_type_name::(); TypeId::of::() } @@ -46,7 +145,7 @@ pub mod Reflection_ { } // Registry mapping a record's *concrete* TypeId to its reflection info. - // Populated when `typeof` is evaluated. This is what makes + // Populated by `typeof` and generated record construction. This is what makes // value-based reflection (GetRecordFields(record)) possible: from a bare // boxed record we can recover its runtime TypeId and look the info up. // @@ -54,29 +153,51 @@ pub mod Reflection_ { // the value-first entry points degrade (like exception catching does). // Type-first reflection (typeof, MakeRecord, GetRecordElements) is // unaffected, as those carry the info in the `System.Type` value itself. - #[cfg(not(feature = "no_std"))] + #[cfg(all(not(feature = "no_std"), not(feature = "threaded")))] thread_local! { static RECORD_REGISTRY: RefCell> = RefCell::new(HashMap::new()); } - #[cfg(not(feature = "no_std"))] + #[cfg(all(not(feature = "no_std"), feature = "threaded"))] + static RECORD_REGISTRY: OnceLock>> = OnceLock::new(); + + #[cfg(all(not(feature = "no_std"), not(feature = "threaded")))] fn registry_insert(tid: TypeId, info: RecordTypeInfo) { RECORD_REGISTRY.with(|r| { r.borrow_mut().insert(tid, info); }); } + #[cfg(all(not(feature = "no_std"), feature = "threaded"))] + fn registry_insert(tid: TypeId, info: RecordTypeInfo) { + RECORD_REGISTRY + .get_or_init(|| RwLock::new(HashMap::new())) + .write() + .unwrap() + .insert(tid, info); + } + #[cfg(feature = "no_std")] fn registry_insert(_tid: TypeId, _info: RecordTypeInfo) { // no registry when no_std } - #[cfg(not(feature = "no_std"))] + #[cfg(all(not(feature = "no_std"), not(feature = "threaded")))] fn registry_get(tid: &TypeId) -> Option { RECORD_REGISTRY.with(|r| r.borrow().get(tid).cloned()) } + #[cfg(all(not(feature = "no_std"), feature = "threaded"))] + fn registry_get(tid: &TypeId) -> Option { + RECORD_REGISTRY + .get_or_init(|| RwLock::new(HashMap::new())) + .read() + .unwrap() + .get(tid) + .cloned() + } + #[cfg(feature = "no_std")] fn registry_get(_tid: &TypeId) -> Option { None // no registry when no_std @@ -105,6 +226,7 @@ pub mod Reflection_ { fields: array_from(fields_vec), make, }; + register_type_name_value(tid, info.name.clone()); registry_insert(tid, info.clone()); box_(info) } @@ -126,7 +248,7 @@ pub mod Reflection_ { // The PropertyInfo carrier holds only a name (a quotation knows nothing more), so the // getter is looked up here, mirroring JS/TS where getValue(pi, v) reads v[pi.Name]. fn getterOf(record: &obj, name: &string) -> Func1 { - let tid = (**record).type_id(); + let tid = object_type_id(record); let info = registry_get(&tid).expect("Record type not registered; evaluate typeof first"); let field = info .fields @@ -152,7 +274,7 @@ pub mod Reflection_ { // FSharpValue.GetRecordFields(record, ?bindingFlags) -> obj[] pub fn getRecordFields(record: obj, _flags: Option) -> Array { - let tid = (*record).type_id(); + let tid = object_type_id(&record); let info = registry_get(&tid).expect("Record type not registered; evaluate typeof first"); let vals: Vec = info .fields @@ -193,6 +315,8 @@ pub mod Reflection_ { info.name.clone() } else if let Some(s) = (*typ).downcast_ref::() { s.clone() + } else if let Some(tid) = (*typ).downcast_ref::() { + registered_type_name(tid).unwrap_or_else(|| crate::String_::string("")) } else { crate::String_::string("") } @@ -201,25 +325,26 @@ pub mod Reflection_ { // obj.GetType() for a statically-erased value (static type = obj/Any). // Reads the boxed value's *runtime* type id and returns the concrete type's // registered reflection info, so `(box record : obj).GetType()` resolves to - // the real record type (not typeof) whenever that type was registered - // via a prior `typeof`. Falls back to returning the object itself as an - // opaque placeholder for unregistered types. + // the real record type (not typeof). Unregistered values receive a + // TypeId carrier, which still supports type identity and primitive names. pub fn getTypeFromObj(o: obj) -> obj { - let tid = (*o).type_id(); + let tid = object_type_id(&o); match registry_get(&tid) { Some(info) => box_(info), - None => o, + None => box_(tid), } } // System.Type equality (typeof = typeof). Compares the carried concrete - // TypeId when both operands are record type infos; otherwise falls back to - // comparing the boxed values' runtime type ids. + // TypeId when both operands are record type infos; otherwise compares the + // TypeId carried by each non-record type value. pub fn typeEquals(a: obj, b: obj) -> bool { let ta = (*a).downcast_ref::(); let tb = (*b).downcast_ref::(); match (ta, tb) { (Some(x), Some(y)) => x.tid == y.tid, + (Some(x), None) => (*b).downcast_ref::().map_or(false, |y| x.tid == *y), + (None, Some(y)) => (*a).downcast_ref::().map_or(false, |x| *x == y.tid), (None, None) => { // Non-record `System.Type` values carry a boxed concrete `TypeId`. // Downcast both and compare the *carried* ids; comparing the boxed @@ -227,11 +352,9 @@ pub mod Reflection_ { // and therefore make every non-record type compare equal. match ((*a).downcast_ref::(), (*b).downcast_ref::()) { (Some(x), Some(y)) => x == y, - _ => (*a).type_id() == (*b).type_id(), + _ => object_type_id(&a) == object_type_id(&b), } } - // One record, one non-record: definitely different types. - _ => false, } } } diff --git a/src/fable-library-rust/src/TimeSpan.rs b/src/fable-library-rust/src/TimeSpan.rs index b387b13c6..238e21179 100644 --- a/src/fable-library-rust/src/TimeSpan.rs +++ b/src/fable-library-rust/src/TimeSpan.rs @@ -24,6 +24,11 @@ pub mod TimeSpan_ { pub const min_value: TimeSpan = TimeSpan { ticks: i64::MIN }; pub const max_value: TimeSpan = TimeSpan { ticks: i64::MAX }; + #[inline] + fn truncate_f64(value: f64) -> f64 { + value as i64 as f64 + } + impl core::fmt::Debug for TimeSpan { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{}", self.toString(string(""))) @@ -231,27 +236,27 @@ pub mod TimeSpan_ { } pub fn hours(&self) -> i32 { - (self.totalHours() - self.totalDays().trunc() * 24.0) as i32 + (self.totalHours() - truncate_f64(self.totalDays()) * 24.0) as i32 } pub fn minutes(&self) -> i32 { - (self.totalMinutes() - self.totalHours().trunc() * 60.0) as i32 + (self.totalMinutes() - truncate_f64(self.totalHours()) * 60.0) as i32 } pub fn seconds(&self) -> i32 { - (self.totalSeconds() - self.totalMinutes().trunc() * 60.0) as i32 + (self.totalSeconds() - truncate_f64(self.totalMinutes()) * 60.0) as i32 } pub fn milliseconds(&self) -> i32 { - (self.totalMilliseconds() - self.totalSeconds().trunc() * 1000.0) as i32 + (self.totalMilliseconds() - truncate_f64(self.totalSeconds()) * 1000.0) as i32 } pub fn microseconds(&self) -> i32 { - (self.totalMicroseconds() - self.totalMilliseconds().trunc() * 1000.0) as i32 + (self.totalMicroseconds() - truncate_f64(self.totalMilliseconds()) * 1000.0) as i32 } pub fn nanoseconds(&self) -> i32 { - (self.totalNanoseconds() - self.totalMicroseconds().trunc() * 1000.0) as i32 + (self.totalNanoseconds() - truncate_f64(self.totalMicroseconds()) * 1000.0) as i32 } pub fn negate(&self) -> TimeSpan { diff --git a/src/fable-library-rust/src/Uri.rs b/src/fable-library-rust/src/Uri.rs new file mode 100644 index 000000000..069ae57be --- /dev/null +++ b/src/fable-library-rust/src/Uri.rs @@ -0,0 +1,591 @@ +#[cfg_attr(rustfmt, rustfmt::skip)] +pub mod Uri_ { + use crate::Native_::{format, LrcPtr, MutCell, String, ToString, Vec}; + use crate::String_::{fromSlice, fromString, string}; + + // UriKind: 0 = RelativeOrAbsolute, 1 = Absolute, 2 = Relative + + #[derive(Clone, Default, PartialEq)] + pub struct Uri { + isAbsolute: bool, + hasAuthority: bool, // true for scheme://authority/... , false for scheme:path (mailto:, tel:, urn:) + original: string, + relative: string, // raw string for relative URIs + schemePart: string, + userInfoPart: string, + hostPart: string, + port: i32, // -1 when no explicit port + pathPart: string, + queryPart: string, // "" or "?..." + fragmentPart: string, // "" or "#..." + } + + // ------------------------------------------------------------------- + // helpers + // ------------------------------------------------------------------- + + // Returns the byte index of the ':' that terminates a valid URI scheme + // prefix (scheme = ALPHA *(ALPHA / DIGIT / "+" / "-" / ".")), or None when + // `s` has no scheme (i.e. it is a relative reference). This recognizes both + // `scheme://authority/...` and authority-less forms like `mailto:a@b.com`. + fn scheme_end(s: &str) -> Option { + let bytes = s.as_bytes(); + match bytes.first() { + Some(&c) if (c as char).is_ascii_alphabetic() => {} + _ => return None, + } + let mut i = 1; + while i < bytes.len() { + let c = bytes[i] as char; + if c == ':' { + return Some(i); + } else if c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.' { + i += 1; + } else { + return None; + } + } + None + } + + fn is_absolute_str(s: &str) -> bool { + scheme_end(s).is_some() + } + + // RFC 3986 §5.2.4 "remove dot segments": collapses `.` and `..` in a path. + fn remove_dot_segments(path: &str) -> String { + let mut input = path.to_string(); + let mut output = String::new(); + while !input.is_empty() { + if input.starts_with("../") { + input.replace_range(0..3, ""); + } else if input.starts_with("./") { + input.replace_range(0..2, ""); + } else if input.starts_with("/./") { + input.replace_range(0..3, "/"); + } else if input == "/." { + input = "/".to_string(); + } else if input.starts_with("/../") { + input.replace_range(0..4, "/"); + remove_last_segment(&mut output); + } else if input == "/.." { + input = "/".to_string(); + remove_last_segment(&mut output); + } else if input == "." || input == ".." { + input.clear(); + } else { + let start = if input.starts_with('/') { 1 } else { 0 }; + let end = match input[start..].find('/') { + Some(i) => start + i, + None => input.len(), + }; + output.push_str(&input[..end]); + input.replace_range(0..end, ""); + } + } + output + } + + fn remove_last_segment(output: &mut String) { + match output.rfind('/') { + Some(i) => output.truncate(i), + None => output.clear(), + } + } + + fn is_default_port(scheme: &str, port: i32) -> bool { + port == -1 + || (scheme == "http" && port == 80) + || (scheme == "https" && port == 443) + || (scheme == "ftp" && port == 21) + || (scheme == "ws" && port == 80) + || (scheme == "wss" && port == 443) + } + + fn authority_str(scheme: &str, user_info: &str, host: &str, port: i32) -> String { + let host_port = if is_default_port(scheme, port) { + host.to_string() + } else { + format!("{}:{}", host, port) + }; + if user_info.is_empty() { + host_port + } else { + format!("{}@{}", user_info, host_port) + } + } + + fn hex_val(b: u8) -> Option { + match b { + b'0'..=b'9' => Some(b - b'0'), + b'a'..=b'f' => Some(b - b'a' + 10), + b'A'..=b'F' => Some(b - b'A' + 10), + _ => None, + } + } + + fn percent_decode(s: &str) -> String { + let bytes = s.as_bytes(); + let mut out: Vec = Vec::new(); + let mut i = 0; + while i < bytes.len() { + if bytes[i] == b'%' && i + 2 < bytes.len() { + if let (Some(h), Some(l)) = (hex_val(bytes[i + 1]), hex_val(bytes[i + 2])) { + out.push(h * 16 + l); + i += 3; + continue; + } + } + out.push(bytes[i]); + i += 1; + } + String::from_utf8_lossy(&out).into_owned() + } + + // parse scheme://host:port/path?query#fragment , or the authority-less + // form scheme:path?query#fragment (mailto:, tel:, urn:, ...). + // Returns (scheme, hasAuthority, userInfo, host, port, path, query, fragment). + fn parse_absolute(s: &str) -> (String, bool, String, String, i32, String, String, String) { + let idx = scheme_end(s).unwrap(); + let scheme = s[..idx].to_ascii_lowercase(); + let after = &s[idx + 1..]; // everything after "scheme:" + + match after.strip_prefix("//") { + Some(rest) => { + // scheme://authority/path?query#fragment + let auth_end = rest.find(|c| c == '/' || c == '?' || c == '#').unwrap_or(rest.len()); + let authority = &rest[..auth_end]; + let remainder = &rest[auth_end..]; + + let (user_info, host_port) = match authority.rfind('@') { + Some(index) => (authority[..index].to_string(), &authority[index + 1..]), + None => (String::new(), authority), + }; + let (host, port) = if host_port.starts_with('[') { + match host_port.find(']') { + Some(end) => { + let host = host_port[..=end].to_ascii_lowercase(); + let port = host_port[end + 1..] + .strip_prefix(':') + .and_then(|value| value.parse::().ok()) + .unwrap_or(-1); + (host, port) + } + None => (host_port.to_ascii_lowercase(), -1), + } + } else { + match host_port.rfind(':') { + Some(ci) => { + let h = &host_port[..ci]; + let p = &host_port[ci + 1..]; + match p.parse::() { + Ok(n) => (h.to_ascii_lowercase(), n), + Err(_) => (host_port.to_ascii_lowercase(), -1), + } + } + None => (host_port.to_ascii_lowercase(), -1), + } + }; + + let (before_frag, fragment) = match remainder.find('#') { + Some(fi) => (&remainder[..fi], remainder[fi..].to_string()), + None => (remainder, String::new()), + }; + let (path, query) = match before_frag.find('?') { + Some(qi) => (before_frag[..qi].to_string(), before_frag[qi..].to_string()), + None => (before_frag.to_string(), String::new()), + }; + let path = if path.is_empty() { "/".to_string() } else { path }; + (scheme, true, user_info, host, port, path, query, fragment) + } + None => { + // authority-less: scheme:path?query#fragment (no host/port) + let (before_frag, fragment) = match after.find('#') { + Some(fi) => (&after[..fi], after[fi..].to_string()), + None => (after, String::new()), + }; + let (path, query) = match before_frag.find('?') { + Some(qi) => (before_frag[..qi].to_string(), before_frag[qi..].to_string()), + None => (before_frag.to_string(), String::new()), + }; + (scheme, false, String::new(), String::new(), -1, path, query, fragment) + } + } + } + + fn parse_abs_uri(orig: &str) -> Uri { + let (scheme, has_authority, user_info, host, port, path, query, fragment) = parse_absolute(orig); + Uri { + isAbsolute: true, + hasAuthority: has_authority, + original: fromSlice(orig), + relative: string(""), + schemePart: fromString(scheme), + userInfoPart: fromString(user_info), + hostPart: fromString(host), + port, + pathPart: fromString(path), + queryPart: fromString(query), + fragmentPart: fromString(fragment), + } + } + + fn make_relative(orig: &str) -> Uri { + Uri { + isAbsolute: false, + hasAuthority: false, + original: fromSlice(orig), + relative: fromSlice(orig), + schemePart: string(""), + userInfoPart: string(""), + hostPart: string(""), + port: -1, + pathPart: string(""), + queryPart: string(""), + fragmentPart: string(""), + } + } + + // resolve a relative reference against an absolute base URI + fn resolve(base: &Uri, rel: &str) -> Uri { + if is_absolute_str(rel) { + return parse_abs_uri(rel); + } + let (before_frag, fragment) = match rel.find('#') { + Some(fi) => (&rel[..fi], rel[fi..].to_string()), + None => (rel, String::new()), + }; + let (rel_path, query) = match before_frag.find('?') { + Some(qi) => (before_frag[..qi].to_string(), Some(before_frag[qi..].to_string())), + None => (before_frag.to_string(), None), + }; + let rel_path_is_empty = rel_path.is_empty(); + let base_path = base.pathPart.as_str(); + let merged_path = if rel_path.is_empty() { + base_path.to_string() + } else if rel_path.starts_with('/') { + rel_path + } else { + let cut = base_path.rfind('/').map(|i| i + 1).unwrap_or(0); + format!("{}{}", &base_path[..cut], rel_path) + }; + // RFC 3986 §5.2.4: collapse `.`/`..` segments in the merged path. + let new_path = remove_dot_segments(&merged_path); + let query = match query { + Some(query) => query, + None if rel_path_is_empty => base.queryPart.as_str().to_string(), + None => String::new(), + }; + let fragment = match rel.find('#') { + Some(_) => fragment, + None if rel.is_empty() => base.fragmentPart.as_str().to_string(), + None => String::new(), + }; + let scheme = base.schemePart.as_str(); + let user_info = base.userInfoPart.as_str(); + let host = base.hostPart.as_str(); + let orig = if base.hasAuthority { + let auth = authority_str(scheme, user_info, host, base.port); + format!("{}://{}{}{}{}", scheme, auth, new_path, query, fragment) + } else { + format!("{}:{}{}{}", scheme, new_path, query, fragment) + }; + Uri { + isAbsolute: true, + hasAuthority: base.hasAuthority, + original: fromString(orig), + relative: string(""), + schemePart: base.schemePart.clone(), + userInfoPart: base.userInfoPart.clone(), + hostPart: base.hostPart.clone(), + port: base.port, + pathPart: fromString(new_path), + queryPart: fromString(query), + fragmentPart: fromString(fragment), + } + } + + fn build_from_string(orig: &str, kind: i32) -> Result { + let is_abs = is_absolute_str(orig); + match kind { + 1 => { + // Absolute + if is_abs { + Ok(parse_abs_uri(orig)) + } else { + Err("Invalid URI: The format of the URI could not be determined.") + } + } + 2 => { + // Relative + if is_abs { + Err("URI is not a relative path.") + } else { + Ok(make_relative(orig)) + } + } + _ => { + // RelativeOrAbsolute + if is_abs { + Ok(parse_abs_uri(orig)) + } else { + Ok(make_relative(orig)) + } + } + } + } + + // ------------------------------------------------------------------- + // instance surface + // ------------------------------------------------------------------- + + impl Uri { + fn ensure_absolute(&self) { + if !self.isAbsolute { + panic!("This operation is not supported for a relative URI."); + } + } + + fn build_absolute(&self) -> String { + let scheme = self.schemePart.as_str(); + if self.hasAuthority { + let auth = authority_str( + scheme, + self.userInfoPart.as_str(), + self.hostPart.as_str(), + self.port, + ); + format!( + "{}://{}{}{}{}", + scheme, + auth, + self.pathPart.as_str(), + self.queryPart.as_str(), + self.fragmentPart.as_str() + ) + } else { + // authority-less scheme (mailto:, tel:, urn:): no `//host`. + format!( + "{}:{}{}{}", + scheme, + self.pathPart.as_str(), + self.queryPart.as_str(), + self.fragmentPart.as_str() + ) + } + } + + pub fn isAbsoluteUri(&self) -> bool { + self.isAbsolute + } + + pub fn scheme(&self) -> string { + self.ensure_absolute(); + self.schemePart.clone() + } + + pub fn host(&self) -> string { + self.ensure_absolute(); + self.hostPart.clone() + } + + pub fn port(&self) -> i32 { + self.ensure_absolute(); + if self.port != -1 { + self.port + } else { + match self.schemePart.as_str() { + "http" => 80, + "https" => 443, + "ftp" => 21, + "ws" => 80, + "wss" => 443, + _ => -1, + } + } + } + + pub fn isDefaultPort(&self) -> bool { + self.ensure_absolute(); + is_default_port(self.schemePart.as_str(), self.port) + } + + pub fn absolutePath(&self) -> string { + self.ensure_absolute(); + self.pathPart.clone() + } + + pub fn query(&self) -> string { + self.ensure_absolute(); + self.queryPart.clone() + } + + pub fn fragment(&self) -> string { + self.ensure_absolute(); + self.fragmentPart.clone() + } + + pub fn pathAndQuery(&self) -> string { + self.ensure_absolute(); + fromString(format!("{}{}", self.pathPart.as_str(), self.queryPart.as_str())) + } + + pub fn absoluteUri(&self) -> string { + self.ensure_absolute(); + fromString(self.build_absolute()) + } + + pub fn originalString(&self) -> string { + self.original.clone() + } + } + + impl core::fmt::Display for Uri { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + if self.isAbsolute { + write!(f, "{}", percent_decode(&self.build_absolute())) + } else { + write!(f, "{}", self.relative.as_str()) + } + } + } + + impl core::fmt::Debug for Uri { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Display::fmt(self, f) + } + } + + // ------------------------------------------------------------------- + // constructors (free functions, mirror System.Uri overloads) + // ------------------------------------------------------------------- + + pub fn create(uriString: string) -> LrcPtr { + createWithKind(uriString, 1) + } + + pub fn createWithKind(uriString: string, kind: i32) -> LrcPtr { + match build_from_string(uriString.as_str(), kind) { + Ok(u) => LrcPtr::new(u), + Err(msg) => panic!("{}", msg), + } + } + + pub fn createFromString(baseUri: LrcPtr, relativeUri: string) -> LrcPtr { + if !baseUri.isAbsolute { + panic!("Invalid URI: A base URI cannot be created because the 'baseUri' parameter represents a relative URI."); + } + LrcPtr::new(resolve(&baseUri, relativeUri.as_str())) + } + + pub fn createFromUri(baseUri: LrcPtr, relativeUri: LrcPtr) -> LrcPtr { + if !baseUri.isAbsolute { + panic!("Invalid URI: A base URI cannot be created because the 'baseUri' parameter represents a relative URI."); + } + if relativeUri.isAbsolute { + // .NET: when the second URI is absolute it wins, and the base is ignored. + relativeUri.clone() + } else { + LrcPtr::new(resolve(&baseUri, relativeUri.relative.as_str())) + } + } + + pub fn tryCreateWithKind(uriString: string, kind: i32, res: &MutCell>) -> bool { + match build_from_string(uriString.as_str(), kind) { + Ok(u) => { + res.set(LrcPtr::new(u)); + true + } + Err(_) => false, + } + } + + pub fn tryCreateFromString( + baseUri: LrcPtr, + relativeUri: string, + res: &MutCell>, + ) -> bool { + if !baseUri.isAbsolute { + return false; + } + res.set(LrcPtr::new(resolve(&baseUri, relativeUri.as_str()))); + true + } + + pub fn tryCreateFromUri( + baseUri: LrcPtr, + relativeUri: LrcPtr, + res: &MutCell>, + ) -> bool { + if !baseUri.isAbsolute { + return false; + } + if relativeUri.isAbsolute { + // .NET: when the second URI is absolute it wins, and the base is ignored. + res.set(relativeUri.clone()); + } else { + res.set(LrcPtr::new(resolve(&baseUri, relativeUri.relative.as_str()))); + } + true + } + + // --- Percent-encoding (RFC 3986). Encoding operates on UTF-8 bytes. --- + + fn is_unreserved(b: u8) -> bool { + b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.' | b'~') + } + + // gen-delims and sub-delims, kept intact by EscapeUriString. + fn is_reserved(b: u8) -> bool { + matches!( + b, + b':' | b'/' | b'?' | b'#' | b'[' | b']' | b'@' + | b'!' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b'+' | b',' | b';' | b'=' + ) + } + + fn escape_with(s: &str, keep: fn(u8) -> bool) -> string { + const HEX: &[u8; 16] = b"0123456789ABCDEF"; + let mut out = String::with_capacity(s.len()); + for b in s.bytes() { + if keep(b) { + out.push(b as char); + } else { + out.push('%'); + out.push(HEX[(b >> 4) as usize] as char); + out.push(HEX[(b & 0x0F) as usize] as char); + } + } + fromString(out) + } + + pub fn escapeDataString(s: string) -> string { + escape_with(s.as_str(), is_unreserved) + } + + pub fn escapeUriString(s: string) -> string { + fn keep(b: u8) -> bool { + is_unreserved(b) || is_reserved(b) + } + escape_with(s.as_str(), keep) + } + + pub fn unescapeDataString(s: string) -> string { + let bytes = s.as_str().as_bytes(); + let mut out: Vec = Vec::with_capacity(bytes.len()); + let mut i = 0; + while i < bytes.len() { + // An invalid or truncated %XX sequence is left untouched, as in .NET. + if bytes[i] == b'%' && i + 2 < bytes.len() { + if let (Some(hi), Some(lo)) = (hex_val(bytes[i + 1]), hex_val(bytes[i + 2])) { + out.push((hi << 4) | lo); + i += 3; + continue; + } + } + out.push(bytes[i]); + i += 1; + } + fromString(String::from_utf8_lossy(&out).into_owned()) + } +} diff --git a/src/fable-library-rust/src/lib.fs b/src/fable-library-rust/src/lib.fs index 464b245a8..fc92c08ee 100644 --- a/src/fable-library-rust/src/lib.fs +++ b/src/fable-library-rust/src/lib.fs @@ -33,4 +33,5 @@ let _imports () = importAll "./String.rs" importAll "./TimeOnly.rs" importAll "./TimeSpan.rs" + importAll "./Uri.rs" () diff --git a/tests/Beam/Fable.Tests.Beam.fsproj b/tests/Beam/Fable.Tests.Beam.fsproj index f8344cb3f..028c3213f 100644 --- a/tests/Beam/Fable.Tests.Beam.fsproj +++ b/tests/Beam/Fable.Tests.Beam.fsproj @@ -1,6 +1,11 @@ Exe + + false + false net10.0 Major false @@ -9,8 +14,8 @@ Fable.Tests + - runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -22,6 +27,12 @@ + + + + diff --git a/tests/Beam/Main.fs b/tests/Beam/Main.fs index b00af3eaa..34ec00761 100644 --- a/tests/Beam/Main.fs +++ b/tests/Beam/Main.fs @@ -1,6 +1,4 @@ -#if FABLE_COMPILER module Program +// No entry point: Fable does not need one here, and on .NET xunit.v3 +// generates the test runner entry point. () -#else -module Program = let [] main _ = 0 -#endif diff --git a/tests/Beam/xunit.runner.json b/tests/Beam/xunit.runner.json new file mode 100644 index 000000000..864173234 --- /dev/null +++ b/tests/Beam/xunit.runner.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "parallelizeTestCollections": false, + "maxParallelThreads": 1 +} diff --git a/tests/Php/Fable.Tests.Php.fsproj b/tests/Php/Fable.Tests.Php.fsproj index 509b2813e..ebeb96969 100644 --- a/tests/Php/Fable.Tests.Php.fsproj +++ b/tests/Php/Fable.Tests.Php.fsproj @@ -1,6 +1,11 @@ Exe + + false + false net10.0 Major false @@ -8,8 +13,8 @@ preview + - runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -18,6 +23,12 @@ + + + + diff --git a/tests/Php/Main.fs b/tests/Php/Main.fs index ae5f6f695..34ec00761 100644 --- a/tests/Php/Main.fs +++ b/tests/Php/Main.fs @@ -1,6 +1,4 @@ -#if FABLE_COMPILER module Program +// No entry point: Fable does not need one here, and on .NET xunit.v3 +// generates the test runner entry point. () -#else -module Program = let [] main _ = 0 -#endif \ No newline at end of file diff --git a/tests/Php/xunit.runner.json b/tests/Php/xunit.runner.json new file mode 100644 index 000000000..864173234 --- /dev/null +++ b/tests/Php/xunit.runner.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "parallelizeTestCollections": false, + "maxParallelThreads": 1 +} diff --git a/tests/Python/Fable.Tests.Python.fsproj b/tests/Python/Fable.Tests.Python.fsproj index e2374f7e1..d0803dcda 100644 --- a/tests/Python/Fable.Tests.Python.fsproj +++ b/tests/Python/Fable.Tests.Python.fsproj @@ -1,6 +1,11 @@ Exe + + false + false net10.0 Major false @@ -9,8 +14,8 @@ Fable.Tests + - runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -22,6 +27,12 @@ + + + + diff --git a/tests/Python/Main.fs b/tests/Python/Main.fs index ae5f6f695..34ec00761 100644 --- a/tests/Python/Main.fs +++ b/tests/Python/Main.fs @@ -1,6 +1,4 @@ -#if FABLE_COMPILER module Program +// No entry point: Fable does not need one here, and on .NET xunit.v3 +// generates the test runner entry point. () -#else -module Program = let [] main _ = 0 -#endif \ No newline at end of file diff --git a/tests/Python/xunit.runner.json b/tests/Python/xunit.runner.json new file mode 100644 index 000000000..864173234 --- /dev/null +++ b/tests/Python/xunit.runner.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "parallelizeTestCollections": false, + "maxParallelThreads": 1 +} diff --git a/tests/React/package-lock.json b/tests/React/package-lock.json index e5df4a513..fb39b0a4e 100644 --- a/tests/React/package-lock.json +++ b/tests/React/package-lock.json @@ -10,7 +10,7 @@ "@fluentui/react": "^8.125.7", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.2", - "@testing-library/user-event": "^14.6.4", + "@testing-library/user-event": "^14.6.6", "jest": "^30", "jest-environment-jsdom": "^30", "react": "^18", @@ -1538,9 +1538,9 @@ } }, "node_modules/@testing-library/user-event": { - "version": "14.6.4", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.4.tgz", - "integrity": "sha512-QCGwP6QrjypBLwyj5cuyfVamkaIEy/XGY+1VDehbtbQqOggYmTFpFOdWR5mPz14vX8vXLMVjDHlRNBcClyO9ew==", + "version": "14.6.6", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.6.tgz", + "integrity": "sha512-Jbs9FpkkIDw8FgSc6kOVsOv8JuuqGAL7J4X1oot77JxAoDlkNn2GRkd0aYRVuQ+pVQAiHWVkE4rX/dkF5fBiCw==", "license": "MIT", "engines": { "node": ">=12", diff --git a/tests/React/package.json b/tests/React/package.json index 109972607..d45fcc8a2 100644 --- a/tests/React/package.json +++ b/tests/React/package.json @@ -12,7 +12,7 @@ "@fluentui/react": "^8.125.7", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.2", - "@testing-library/user-event": "^14.6.4", + "@testing-library/user-event": "^14.6.6", "jest": "^30", "jest-environment-jsdom": "^30", "react": "^18", diff --git a/tests/Rust/Cargo.toml b/tests/Rust/Cargo.toml index 3487151d1..ee81e8cb6 100644 --- a/tests/Rust/Cargo.toml +++ b/tests/Rust/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [features] +lrc_ptr = ["fable_library_rust/lrc_ptr"] no_std = ["fable_library_rust/no_std"] static_do_bindings = ["fable_library_rust/static_do_bindings"] threaded = ["fable_library_rust/threaded"] diff --git a/tests/Rust/Fable.Tests.Rust.fsproj b/tests/Rust/Fable.Tests.Rust.fsproj index 7264061dc..6e1630fbc 100644 --- a/tests/Rust/Fable.Tests.Rust.fsproj +++ b/tests/Rust/Fable.Tests.Rust.fsproj @@ -1,5 +1,12 @@ + Exe + + false + false net10.0 Major false @@ -7,8 +14,8 @@ Preview + - runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -83,7 +90,7 @@ - + diff --git a/tests/Rust/tests/src/EventTests.fs b/tests/Rust/tests/src/EventTests.fs index dde156141..c5ced0c10 100644 --- a/tests/Rust/tests/src/EventTests.fs +++ b/tests/Rust/tests/src/EventTests.fs @@ -2,44 +2,30 @@ module Fable.Tests.EventTests open Util.Testing -// NOTE (Rust): these class-hosted event types are disabled on Rust. They trigger -// with `event.Trigger(this, arg)`, i.e. they pass the enclosing instance (`this`) -// as an event value. The Rust backend cannot recover the reference-counted `this` -// from a `&self` method body (it emits a by-value struct clone of the wrong type), -// and `member Event = event.Publish` exposes the event as `IEvent<_>`, which the -// backend does not coerce to `IObservable<_>` for `.Add`/`.Subscribe`. The plain -// `Event<'T>` module-function tests below (the core of this port) are unaffected. -// These pass on the JS/TS/Python targets. -// type ClassWithCLIEvent() = -// let event = new Event<_>() -// [] -// member _.Event = event.Publish -// member this.TestEvent(arg) = -// event.Trigger(this, arg) -// -// type ClassWithNonCLIEvent() = -// let event = new Event<_>() -// member _.Event = event.Publish -// member this.TestEvent(arg) = -// event.Trigger(this, arg) - -// NOTE (Rust): the two-generic Event<'Delegate,'Args> (FSharpEvent`2, with a -// sender-carrying 2-arg Trigger) and CLI events declared on an *interface* are -// not supported by the Rust runtime port yet. The `Microsoft.FSharp.Control` -// runtime only implements FSharpEvent`1 (single generic, 1-arg Trigger). These -// types and the two tests that use them (below) are therefore disabled on Rust; -// they pass on the JS/TS/Python targets. -// type InterfaceWithCLIEvent<'t> = -// [] -// abstract Event : IEvent,'t> -// -// type ClassWithInterfaceWithCLIEvent<'t>() = -// let event = new Event<_,_>() -// member this.TestEvent(arg) = -// event.Trigger(this, arg) -// interface InterfaceWithCLIEvent<'t> with -// [] -// member _.Event = event.Publish +type ClassWithCLIEvent() = + let event = new Event<_>() + [] + member _.Event = event.Publish + member this.TestEvent(arg) = + event.Trigger(this, arg) + +type ClassWithNonCLIEvent() = + let event = new Event<_>() + member _.Event = event.Publish + member this.TestEvent(arg) = + event.Trigger(this, arg) + +type InterfaceWithCLIEvent<'t> = + [] + abstract Event : IEvent,'t> + +type ClassWithInterfaceWithCLIEvent<'t>() = + let event = new Event<_,_>() + member this.TestEvent(arg) = + event.Trigger(this, arg) + interface InterfaceWithCLIEvent<'t> with + [] + member _.Event = event.Publish module tests = @@ -197,6 +183,60 @@ module tests = source.Trigger 6 equal 6 result + [] + let ``IEvent handlers receive a null sender`` () = + let mutable senderWasNull = false + let source = Event<_>() + source.Publish.AddHandler(new Handler<_>(fun s _ -> senderWasNull <- obj.ReferenceEquals(s, null))) + |> ignore + + source.Trigger 6 + senderWasNull |> equal true + + [] + let ``Classes can trigger CLI events on interfaces`` () = + let mutable result = 0 + let mutable senderWasReceived = false + let classWithEvent = new ClassWithInterfaceWithCLIEvent<_>() + (classWithEvent :> InterfaceWithCLIEvent<_>).Event.AddHandler(fun s x -> + senderWasReceived <- not (obj.ReferenceEquals(s, null)) + result <- x + ) + classWithEvent.TestEvent(5) + equal 5 result + equal true senderWasReceived + + [] + let ``Generic interface expression can have CLI events`` () = + let mutable actualSender = "" + let mutable result = false + let event = Event<_,_>() + let ifaceWithEvent = + { new InterfaceWithCLIEvent<_> with + [] + member _.Event = event.Publish } + ifaceWithEvent.Event.AddHandler(fun sender arg -> + actualSender <- unbox sender + result <- arg) + let expectedSender = "SENDER" + let expectedResult = true + event.Trigger(expectedSender, expectedResult) + equal expectedSender actualSender + equal expectedResult result + + [] + let ``Two-generic event preserves its sender`` () = + let mutable actualSender = "" + let mutable actualValue = false + let source: Event, bool> = Event<_, _>() + source.Publish.AddHandler(fun sender value -> + actualSender <- unbox sender + actualValue <- value) + + source.Trigger("SENDER", true) + equal "SENDER" actualSender + equal true actualValue + [] let ``IEvent.RemoveHandler works`` () = let mutable result = 0 @@ -210,26 +250,23 @@ module tests = source.Trigger 6 equal 0 result - // NOTE (Rust): disabled — see the ClassWithCLIEvent / ClassWithNonCLIEvent - // note above (passing `this` as an event value + IEvent->IObservable coercion - // for `.Add`/`.Subscribe`). These pass on JS/TS/Python. - // [] - // let ``Classes can trigger CLI events`` () = - // let mutable result = 0 - // let classWithEvent = new ClassWithCLIEvent() - // classWithEvent.Event.Add(fun (sender, x) -> result <- x) - // classWithEvent.TestEvent(5) - // equal 5 result - // - // [] - // let ``Classes can trigger non-CLI events`` () = - // let mutable result = "" - // let classWithEvent = new ClassWithNonCLIEvent() - // let disp = classWithEvent.Event.Subscribe(fun (sender, arg) -> result <- arg) - // classWithEvent.TestEvent("Hello") - // disp.Dispose() - // classWithEvent.TestEvent("Bye") - // equal "Hello" result + [] + let ``Classes can trigger CLI events`` () = + let mutable result = 0 + let classWithEvent = new ClassWithCLIEvent() + classWithEvent.Event.Add(fun (sender, x) -> result <- x) + classWithEvent.TestEvent(5) + equal 5 result + + [] + let ``Classes can trigger non-CLI events`` () = + let mutable result = "" + let classWithEvent = new ClassWithNonCLIEvent() + let disp = classWithEvent.Event.Subscribe(fun (sender, arg) -> result <- arg) + classWithEvent.TestEvent("Hello") + disp.Dispose() + classWithEvent.TestEvent("Bye") + equal "Hello" result // NOTE (Rust): disabled — needs the two-generic Event<_,_> (FSharpEvent`2, // 2-arg Trigger with sender) and CLI events declared on interfaces, which the diff --git a/tests/Rust/tests/src/ObservableTests.fs b/tests/Rust/tests/src/ObservableTests.fs index 0ed72d768..d45ff6320 100644 --- a/tests/Rust/tests/src/ObservableTests.fs +++ b/tests/Rust/tests/src/ObservableTests.fs @@ -9,22 +9,24 @@ type MyObserver<'T>(f) = member x.OnError e = () member x.OnCompleted() = () -// NOTE: This mirrors the JS/TS/Python reference `MyObservable`, adapted for two -// Rust-backend limitations (the runtime combinators themselves are unaffected): -// 1. The disposable is a named generic class, not an inline -// `{ new IDisposable with ... }` object expression: the Rust backend does not -// propagate the enclosing type's generic parameter into a generated -// object-expression struct (it emits an inner struct that references `T` -// without declaring it -> rustc E0401). -// 2. Subscriptions are keyed by an int id and removed by id, rather than +type ErrorObserver(onError: exn -> unit) = + interface IObserver with + member _.OnNext _ = () + member _.OnError error = onError error + member _.OnCompleted() = () + +type CountingObserver(onNext: unit -> unit, onError: exn -> unit, onCompleted: unit -> unit) = + interface IObserver with + member _.OnNext _ = onNext () + member _.OnError error = onError error + member _.OnCompleted() = onCompleted () + +// NOTE: This mirrors the JS/TS/Python reference `MyObservable`, adapted for one +// Rust-backend limitation (the runtime combinators themselves are unaffected): +// Subscriptions are keyed by an int id and removed by id, rather than // `listeners.Remove(w)`: interface trait objects (`IObserver<'T>`) do not // implement Rust `PartialEq`, so removal-by-value of an interface element is // not supported. Behaviour is identical to the reference. -type private Unsubscriber<'T>(listeners: ResizeArray>, id: int) = - interface IDisposable with - member _.Dispose() = - let idx = listeners.FindIndex(fun (i, _) -> i = id) - if idx >= 0 then listeners.RemoveAt(idx) type MyObservable<'T>() = let listeners = ResizeArray>() @@ -32,15 +34,41 @@ type MyObservable<'T>() = member x.Trigger v = for (_, lis) in listeners do lis.OnNext v + member x.TriggerError error = + for (_, lis) in listeners do + lis.OnError error + member x.TriggerCompleted() = + for (_, lis) in listeners do + lis.OnCompleted() interface IObservable<'T> with member x.Subscribe w = let id = nextId nextId <- nextId + 1 listeners.Add((id, w)) - new Unsubscriber<'T>(listeners, id) :> IDisposable + { new IDisposable with + member _.Dispose() = + let idx = listeners.FindIndex(fun (i, _) -> i = id) + if idx >= 0 then listeners.RemoveAt(idx) } module tests = + let assertMatchesDotNetAfterCallbackError (source: MyObservable) (observable: IObservable) = + let mutable next = 0 + let mutable errors = 0 + let mutable completed = 0 + observable.Subscribe(CountingObserver( + (fun () -> next <- next + 1), + (fun _ -> errors <- errors + 1), + (fun () -> completed <- completed + 1))) + |> ignore + source.Trigger 1 + source.Trigger 2 + source.TriggerError (Exception "source") + source.TriggerCompleted() + equal 0 next + equal 3 errors + equal 0 completed + [] let ``IObservable.Subscribe works`` () = let source = MyObservable() @@ -81,6 +109,15 @@ module tests = source.Trigger (Choice1Of2 2) source.Trigger (Choice2Of2 3) +#if !NO_STD_NO_EXCEPTIONS + [] + let ``Observable.choose matches .NET after callback error`` () = + let source = MyObservable() + let chosen: IObservable = + Observable.choose (fun _ -> (failwith "boom" : int option)) source + assertMatchesDotNetAfterCallbackError source chosen +#endif + [] let ``Observable.filter works`` () = let source = MyObservable() @@ -96,6 +133,40 @@ module tests = |> Observable.add (equal false) source.Trigger true +#if !NO_STD_NO_EXCEPTIONS + [] + let ``Observable.map matches .NET after callback error`` () = + let source = MyObservable() + let mapped: IObservable = + Observable.map (fun _ -> (failwith "boom" : int)) source + assertMatchesDotNetAfterCallbackError source mapped + + [] + let ``Observable.map forwards callback exceptions to OnError`` () = + let source = MyObservable() + let mutable errors = 0 + let mapped = Observable.map (fun _ -> failwith "boom") source + mapped.Subscribe(ErrorObserver(fun _ -> errors <- errors + 1)) |> ignore + source.Trigger 1 + equal 1 errors + + [] + let ``Observable.scan forwards collector exceptions to OnError`` () = + let source = MyObservable() + let mutable errors = 0 + let scanned = Observable.scan (fun _ _ -> failwith "boom") 0 source + scanned.Subscribe(ErrorObserver(fun _ -> errors <- errors + 1)) |> ignore + source.Trigger 1 + equal 1 errors + + [] + let ``Observable.scan matches .NET after collector error`` () = + let source = MyObservable() + let scanned: IObservable = + Observable.scan (fun _ _ -> (failwith "boom" : int)) 0 source + assertMatchesDotNetAfterCallbackError source scanned +#endif + [] let ``Observable.merge works`` () = let source1 = MyObservable() diff --git a/tests/Rust/tests/src/QuotationTests.fs b/tests/Rust/tests/src/QuotationTests.fs index 353a081eb..984a684dd 100644 --- a/tests/Rust/tests/src/QuotationTests.fs +++ b/tests/Rust/tests/src/QuotationTests.fs @@ -196,6 +196,16 @@ let ``Evaluate nested lambda`` () = let r = LeafExpressionConverter.EvaluateQuotation <@ (fun x -> (fun y -> x + y)) 3 4 @> unbox r |> equal 7 +[] +let ``Evaluate union quotation`` () = + let result = LeafExpressionConverter.EvaluateQuotation (<@ Some 42 @> :> Expr) + obj.ReferenceEquals(result, null) |> equal false + +[] +let ``Evaluate list quotation`` () = + let result = LeafExpressionConverter.EvaluateQuotation (<@ [ 1; 2; 3 ] @> :> Expr) + obj.ReferenceEquals(result, null) |> equal false + // --- NewUnionCase / NewRecord deconstruction --- type private MyRec = { X: int; Y: int } @@ -354,3 +364,44 @@ let ``Instance call inside a quotation has an instance`` () = match q with | Lambda(_, Call(Some _, _mi, _)) -> () | _ -> failwith "Expected Lambda with an instance Call body" + +[] +let ``GetFreeVars includes call instances`` () = + let q = <@ fun (s: string) -> s.Trim() @> + + match q with + | Lambda(_, body) -> + body.GetFreeVars() + |> Seq.map (fun v -> v.Name) + |> Seq.toList + |> equal [ "s" ] + | _ -> failwith "Expected Lambda" + +[] +let ``GetFreeVars visits nested union and list nodes`` () = + let q = <@ fun x -> [ Some x ] @> + + match q with + | Lambda(_, body) -> + body.GetFreeVars() + |> Seq.map (fun v -> v.Name) + |> Seq.toList + |> equal [ "x" ] + | _ -> failwith "Expected Lambda" + +[] +let ``Quotation ToString works`` () = + let text = (<@ 2 + 3 @>).ToString() + (text.Length > 0) |> equal true + +[] +let ``Quotation Substitute replaces variables`` () = + let quotation = (<@ fun x -> x + 1 @> :> Expr) + let replacement = (<@ 42 @> :> Expr) + let substituted = + quotation.Substitute(fun _ -> Some replacement) + + match substituted with + | Lambda(_, Call(_, _, [ Value(value, _); _ ])) -> + unbox value |> equal 42 + | _ -> failwith "Expected the lambda variable to be substituted" diff --git a/tests/Rust/tests/src/RecordReflectionTests.fs b/tests/Rust/tests/src/RecordReflectionTests.fs index bfa746282..206d3acc8 100644 --- a/tests/Rust/tests/src/RecordReflectionTests.fs +++ b/tests/Rust/tests/src/RecordReflectionTests.fs @@ -1,5 +1,5 @@ -// Value-based reflection needs the type registry, which is std-only -// (it uses thread_local), so these tests do not apply to no_std builds. +// Value-based reflection uses a std-only type registry (it uses thread_local), so +// these tests do not apply to no_std builds. [] module Fable.Tests.RecordReflectionTests @@ -13,6 +13,7 @@ open Microsoft.FSharp.Reflection type TestRecord = { String: string; Int: int } type OtherRecord = { Z: int } +type UnprimedRecord = { Value: int } type SampleUnion = | CaseA @@ -49,15 +50,19 @@ let ``FSharpType.GetRecordFields exposes PropertyInfo names`` () = [] let ``FSharpValue.GetRecordFields returns boxed field values`` () = - // Value-based reflection resolves the type via the runtime registry, which is - // populated when typeof is evaluated (as the JS record test also does first). - FSharpType.IsRecord(typeof) |> equal true let record = { String = "a"; Int = 1 } let values = FSharpValue.GetRecordFields(record) values.Length |> equal 2 unbox values.[0] |> equal "a" unbox values.[1] |> equal 1 +[] +let ``FSharpValue.GetRecordFields works without prior typeof`` () = + let record: UnprimedRecord = { Value = 7 } + let values = FSharpValue.GetRecordFields(record) + values.Length |> equal 1 + unbox values.[0] |> equal 7 + [] let ``FSharpValue.GetRecordField reads an individual field`` () = let record = { String = "a"; Int = 1 } @@ -104,7 +109,6 @@ let ``FSharp.Reflection: Record (end to end)`` () = [] let ``obj.GetType round-trips to the concrete record type`` () = let record = { String = "a"; Int = 1 } - FSharpType.IsRecord(typeof) |> equal true // ensure registration ((box record).GetType() = typeof) |> equal true let rebuilt = unbox (FSharpValue.MakeRecord((box record).GetType(), FSharpValue.GetRecordFields(record))) @@ -123,3 +127,10 @@ let ``Boxing a record to obj and unboxing preserves reference identity`` () = let o: obj = box r let r2 = unbox o System.Object.ReferenceEquals(r, r2) |> equal true + +[] +let ``obj.GetType resolves an unregistered primitive`` () = + let typeForEquality = (box 1).GetType() + let typeForName = (box 1).GetType() + (typeForEquality = typeof) |> equal true + typeForName.FullName |> equal "System.Int32" diff --git a/tests/Rust/tests/src/SystemIOTests.fs b/tests/Rust/tests/src/SystemIOTests.fs index 4fcfadb38..4417a5ee4 100644 --- a/tests/Rust/tests/src/SystemIOTests.fs +++ b/tests/Rust/tests/src/SystemIOTests.fs @@ -29,6 +29,21 @@ let ``Path.GetDirectoryName works`` () = Path.GetDirectoryName("temp/test.txt") |> equal "temp" +[] +let ``Path.GetDirectoryName preserves the root`` () = + Path.GetDirectoryName("/foo") + |> equal "/" + +[] +let ``Path.GetDirectoryName returns null for the root itself`` () = + isNull (Path.GetDirectoryName "/") + |> equal true + +[] +let ``Path.GetDirectoryName returns null for a null path`` () = + isNull (Path.GetDirectoryName null) + |> equal true + [] let ``Path.GetExtension works`` () = Path.GetExtension("temp/test.txt") diff --git a/tests/Rust/tests/src/UriTests.fs b/tests/Rust/tests/src/UriTests.fs index d3c6eb552..b2c1f6655 100644 --- a/tests/Rust/tests/src/UriTests.fs +++ b/tests/Rust/tests/src/UriTests.fs @@ -64,6 +64,33 @@ let ``Uri from baseUri with relative string works`` () = equal "#c" uri.Fragment equal "http://www.test2.com/hello?a=b#c" uri.AbsoluteUri +[] +let ``Uri relative resolution preserves base query and fragment when omitted`` () = + let baseUri = Uri("http://host/a/b?old=1#base") + let same = Uri(baseUri, "") + equal "http://host/a/b?old=1#base" same.AbsoluteUri + + let fragmentOnly = Uri(baseUri, "#new") + equal "http://host/a/b?old=1#new" fragmentOnly.AbsoluteUri + + let queryOnly = Uri(baseUri, "?new=2") + equal "http://host/a/b?new=2" queryOnly.AbsoluteUri + +[] +let ``Uri host excludes user info and exposes ports`` () = + let uri = Uri("http://user:pass@host:8080/path") + equal "host" uri.Host + equal 8080 uri.Port + equal false uri.IsDefaultPort + + let defaultUri = Uri("http://host/path") + equal 80 defaultUri.Port + equal true defaultUri.IsDefaultPort + + let ftpUri = Uri("ftp://host/path") + equal 21 ftpUri.Port + equal true ftpUri.IsDefaultPort + [] let ``Uri from baseUri with relativeUri works`` () = let uri = Uri(Uri("http://www.test3.com/"), Uri("/hello?a=b#c", UriKind.Relative)) @@ -85,6 +112,15 @@ let ``Uri from baseUri with absolute Uri works`` () = equal "www.example.com" uri.Host equal "http://www.example.com/" uri.AbsoluteUri +[] +let ``Uri from baseUri with a different absolute Uri yields the absolute one`` () = + // .NET: when the second URI is absolute it wins and the base is ignored. + let baseUri = Uri("http://www.base.com/", UriKind.Absolute) + let absUri = Uri("http://www.other.com/path", UriKind.Absolute) + let uri = Uri(baseUri, absUri) + equal "www.other.com" uri.Host + equal "http://www.other.com/path" uri.AbsoluteUri + [] let ``TryCreate from absolute string with kind Absolute works`` () = let (valid, uri) = Uri.TryCreate("http://www.test0.com/hello?a=b#c", UriKind.Absolute) @@ -140,6 +176,21 @@ let ``TryCreate from absolute uri and absolute uri should work`` () = equal true valid equal absUri uri +[] +let ``TryCreate from absolute uri and relative string works`` () = + let (valid, uri) = Uri.TryCreate(Uri("https://example.com/base"), "child") + equal true valid + equal "https://example.com/child" uri.AbsoluteUri + +[] +let ``TryCreate from absolute uri and different absolute uri yields the second`` () = + // .NET: when the second URI is absolute it wins and the base is ignored. + let baseUri = Uri("http://www.base.com/", UriKind.Absolute) + let other = Uri("http://www.other.com/path", UriKind.Absolute) + let (valid, uri) = Uri.TryCreate(baseUri, other) + equal true valid + equal "www.other.com" uri.Host + [] let ``TryCreate from absolute uri and relative uri should work`` () = let (valid, uri) = Uri.TryCreate(Uri("https://example.com", UriKind.Absolute), Uri("test", UriKind.Relative)) @@ -163,6 +214,27 @@ let ``Uri.ToString works`` () = uri.ToString() |> equal "http://www.test4.com/a b c.html" sprintf "%A" uri |> equal "http://www.test4.com/a b c.html" +[] +let ``Uri from authority-less absolute scheme works`` () = + // mailto:/tel:/urn: are absolute URIs without an authority; must not panic. + let mailto = Uri("mailto:a@b.com") + equal true mailto.IsAbsoluteUri + equal "mailto" mailto.Scheme + equal "mailto:a@b.com" mailto.AbsoluteUri + let tel = Uri("tel:+1") + equal true tel.IsAbsoluteUri + equal "tel" tel.Scheme + let urn = Uri("urn:x:y") + equal true urn.IsAbsoluteUri + equal "urn" urn.Scheme + +[] +let ``Uri relative resolution collapses dot segments`` () = + let uri = Uri(Uri("http://h/a/b"), "../c") + equal "http://h/c" uri.AbsoluteUri + let uri2 = Uri(Uri("http://h/a/b/"), "../../x") + equal "http://h/x" uri2.AbsoluteUri + [] let ``Uri.OriginalString works`` () = let cases = [ @@ -174,3 +246,21 @@ let ``Uri.OriginalString works`` () = for case in cases do let uri = Uri(case) uri.OriginalString |> equal case + +[] +let ``Uri.EscapeDataString works`` () = + Uri.EscapeDataString("Hello World!") |> equal "Hello%20World%21" + Uri.EscapeDataString("aA1-_.~") |> equal "aA1-_.~" + // multi-byte chars percent-encode each UTF-8 byte + Uri.EscapeDataString("ä") |> equal "%C3%A4" + +[] +let ``Uri.UnescapeDataString works`` () = + Uri.UnescapeDataString("Hello%20World%21") |> equal "Hello World!" + Uri.UnescapeDataString("%C3%A4") |> equal "ä" + // an invalid escape sequence is left untouched + Uri.UnescapeDataString("100%") |> equal "100%" + +[] +let ``Uri.EscapeUriString keeps reserved characters`` () = + Uri.EscapeUriString("http://test.com/a b?c=d&e") |> equal "http://test.com/a%20b?c=d&e" diff --git a/tests/Rust/tests/src/main.fs b/tests/Rust/tests/src/main.fs index 6a938fa46..ddaf02e64 100644 --- a/tests/Rust/tests/src/main.fs +++ b/tests/Rust/tests/src/main.fs @@ -7,5 +7,9 @@ let _imports() = () // [] +#if FABLE_COMPILER +// Rust needs a `fn main`, so emit the entry point when transpiling. On .NET, +// xunit.v3 generates the test runner entry point instead (see the .fsproj). [] let main _args = 0 +#endif diff --git a/uv.lock b/uv.lock index 9938ac274..9504d7932 100644 --- a/uv.lock +++ b/uv.lock @@ -68,8 +68,8 @@ dev = [ { name = "pydantic", specifier = ">=2.13.4" }, { name = "pyright", specifier = ">=1.1.411" }, { name = "pytest", specifier = ">=9.1.1,<10" }, - { name = "ruff", specifier = ">=0.16.3,<0.17" }, - { name = "ty", specifier = ">=0.0.72" }, + { name = "ruff", specifier = ">=0.16.4,<0.17" }, + { name = "ty", specifier = ">=0.0.74" }, ] [[package]] @@ -259,52 +259,52 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.3" +version = "0.16.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/61/b3/3213589383f8f1b3938781bd1278713f6d18621a14992b3e81fefb8a5ef9/ruff-0.16.3.tar.gz", hash = "sha256:e76d33a347661a84b5be6d043d0347fdc745dfdcf825a8f4fed64b5e26eebdf2", size = 4891904, upload-time = "2026-08-13T15:17:13.381Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/8f/d8074b1f25e003164087a8bfe79a0f1a3945135764dbb6aaab04103dcaf9/ruff-0.16.4.tar.gz", hash = "sha256:13171aa9d9af2240ee3504e639de73122c67e74036de5ba2e1d01422cd17e3dc", size = 4899731, upload-time = "2026-08-20T17:43:59.196Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/96/493770daebd68c0a67f1549fdf519f53be51fc435186c0585bcc272fd76c/ruff-0.16.3-py3-none-linux_armv6l.whl", hash = "sha256:0c5710e247a58a4521e66e124ba9a74655b414f61ba3a2e9e3811e11098f48f7", size = 10902799, upload-time = "2026-08-13T15:16:27.382Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e6/2becf3942fddc29a29b8df47691d456fb1085391a694f74d84513251418c/ruff-0.16.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fe155130631a2471fd2e14a7a664a4dfbd7194b8229c3d7b2a40b21178639081", size = 11135539, upload-time = "2026-08-13T15:16:30.87Z" }, - { url = "https://files.pythonhosted.org/packages/3e/1e/4b8b72f0d006dbf19326aa99f9ca0ee2ff374187c4d301cf529a51aa06fe/ruff-0.16.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e2ed719e14aa64d895c2ee922594a90a43c861a93f0575a95ff8c47cdbd13eb9", size = 10475095, upload-time = "2026-08-13T15:16:33.259Z" }, - { url = "https://files.pythonhosted.org/packages/92/32/2201fa49ba1f6c101ee321e83f051ac7a4b8d07b0ef6b4d3f2772b302275/ruff-0.16.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e0b1da805eb043654645d74d5de1e5ce2edc686e40790d2b86f56d71cc06a84", size = 10668771, upload-time = "2026-08-13T15:16:35.65Z" }, - { url = "https://files.pythonhosted.org/packages/c3/66/4afc5c8363bd04d45effce1b7c8713ca037d7a6740b7451a2403a6e3a972/ruff-0.16.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a37bdea0bbe21780f590bf437d6412c8c4e1b6cd010f91a65c2c40c5e5f5f870", size = 10699568, upload-time = "2026-08-13T15:16:38.195Z" }, - { url = "https://files.pythonhosted.org/packages/53/fd/c67d246bf36bf1698551c56de39e95cd07f70e64433e0098e6267d77061b/ruff-0.16.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09571e6d1288ed9be475207a3ac04ada404f1cd898104be0f6ab8d7df438575b", size = 11499365, upload-time = "2026-08-13T15:16:40.623Z" }, - { url = "https://files.pythonhosted.org/packages/67/0b/00ecbceb99a263af7b12f6f05ac3c92bc47b905e91adc3f207a836e3bc01/ruff-0.16.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c18c5a101eb540010638cc1ff3c84944d3adb3df62b8d98ca8f22ba484d3413", size = 12311728, upload-time = "2026-08-13T15:16:43.564Z" }, - { url = "https://files.pythonhosted.org/packages/54/b2/b7b3bb54f4d3f7db504e476ad4ab8de530dceebe2c061384b2757ee419e8/ruff-0.16.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8457c44f15033c85ddbb77b15d451df9e24e4bd03b628396dd3610cedc3b8f82", size = 11699896, upload-time = "2026-08-13T15:16:46.209Z" }, - { url = "https://files.pythonhosted.org/packages/c7/30/4c468429ac195addc5ee1b717b6ab1b66632786737ca3b2ed3443fb0c26a/ruff-0.16.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:294b95c4ae0cda9388525c2047778aa758d6b8d4bb876fd4e9eaa3ebc92343eb", size = 11058736, upload-time = "2026-08-13T15:16:48.823Z" }, - { url = "https://files.pythonhosted.org/packages/43/67/7a113cdaddf24b64d7f75b1242a99d04c82fcef4f6921fdbb832beaffb5f/ruff-0.16.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:3d0c7c40c87c2a820509c31ba007968da6e1306468c067b2d82fbfdbcd0e8474", size = 11586911, upload-time = "2026-08-13T15:16:51.913Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c1/2e66f24c0f3ead25a5e660111778685e505e5da353c82802bf49f0cbe7b9/ruff-0.16.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9f738c0fdfa8eed0b2ce7fb27ee7258208a92a68d7949e62aa15164bc7b389da", size = 10954265, upload-time = "2026-08-13T15:16:54.763Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ba/4cee23bf52cba9a058d3726de623624daf50ef9638868edd86f4126157f6/ruff-0.16.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fb785f0be25abe69d320415cd4f833b59e17ba7613d9ba6a958023b6bceb0a50", size = 10709886, upload-time = "2026-08-13T15:16:57.339Z" }, - { url = "https://files.pythonhosted.org/packages/82/df/7da7194fa5d9dc0a285f7e6fa5a4722e7c63faac0b45b614ded9314363a1/ruff-0.16.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c5536e3acfbf9563085aa2be7b13c629c3077e902afc5b941ac44024dbb9f506", size = 11210392, upload-time = "2026-08-13T15:17:00.171Z" }, - { url = "https://files.pythonhosted.org/packages/35/85/7795f6e817af050e7517bf3e7aa9b061cce70ef33d280aad902c956c1ecf/ruff-0.16.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a2d85c02f9b8e165d85e6779184d38c4132de12603dab59c51c28e22584f9e4d", size = 11626910, upload-time = "2026-08-13T15:17:03.299Z" }, - { url = "https://files.pythonhosted.org/packages/78/9b/475b927cf27a5cbbda3c7bafb69ed6ff77e1d7923d5d85f17c2749d7ae32/ruff-0.16.3-py3-none-win32.whl", hash = "sha256:388cdf2166642bd9b13d52b5932d3170f34f8abed7e8d9a855f1d84b83645a0a", size = 10931415, upload-time = "2026-08-13T15:17:05.726Z" }, - { url = "https://files.pythonhosted.org/packages/b2/99/e2a2bfc4fbf0a1e8a916bc9ebe6fe6c58cc34c28e0ffc6ce281d572d1c2e/ruff-0.16.3-py3-none-win_amd64.whl", hash = "sha256:e80a7d69ca2a6d1c4d352ec91458cdca6e56c83cdbcabd93e4abe1e53591d948", size = 11445993, upload-time = "2026-08-13T15:17:08.353Z" }, - { url = "https://files.pythonhosted.org/packages/69/3e/4132e539aed78c148854d4997a2685b0ed4dc4e87110b59ce528564e184e/ruff-0.16.3-py3-none-win_arm64.whl", hash = "sha256:b8ca152da82c1acc1fa8d5874b15951935f0eef46f10e6954c83859011b6178a", size = 11399302, upload-time = "2026-08-13T15:17:10.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/80/779895ef584e089d22f2c6df0d0e99a65ec2df0805f1fffd439415b8c1f0/ruff-0.16.4-py3-none-linux_armv6l.whl", hash = "sha256:df4075f71ddac40b9934af60c3ec8a53047dd5a5fdc43224e6e4e8e9a27cb6f7", size = 10006909, upload-time = "2026-08-20T17:43:16.888Z" }, + { url = "https://files.pythonhosted.org/packages/a9/e6/f553199b5e8927a05cb5c422d921fd0656b29ab976e91c44802107c6b0da/ruff-0.16.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0c95538517af68004306b0fb3214ff2f2af67a65092aee77cd9eb86db6656604", size = 10240201, upload-time = "2026-08-20T17:43:19.337Z" }, + { url = "https://files.pythonhosted.org/packages/1c/70/4a6dc4bb34da4dee35e30f09bbd1bfbdd26f33b62fb9b8df31f08a199cd2/ruff-0.16.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:963f83df8e69e575b64d67dd447ebbc917db41a14bf38d4593a4183e7aaa8255", size = 9835122, upload-time = "2026-08-20T17:43:21.708Z" }, + { url = "https://files.pythonhosted.org/packages/24/12/c6e22d686372c15bcb7af99831f1a1be96df696491babf4f24e4f942c527/ruff-0.16.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a5057c7ff3f6e6480a48fccfb3a412a690f48a3d03ac5cf08177d6c2da3ade", size = 9977162, upload-time = "2026-08-20T17:43:24.236Z" }, + { url = "https://files.pythonhosted.org/packages/46/49/72b10ec912f5ab5854992eaf7aa7cd36729b6937d9dc4e0fb41b3bf428ec/ruff-0.16.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3dce8d9b0c57c265b91885a66a567d8ea1372e8eb4e250fa8e5e3f579e99cff", size = 9829789, upload-time = "2026-08-20T17:43:26.966Z" }, + { url = "https://files.pythonhosted.org/packages/fa/80/0f30e32e7f6ee26edc39075502db9d368d788a44a79b55f763eb4ab03796/ruff-0.16.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dc651db49283c69f8e72c834eec4fe5573e4c646856aebece0ce385dceb2a80", size = 10527949, upload-time = "2026-08-20T17:43:29.384Z" }, + { url = "https://files.pythonhosted.org/packages/52/3d/86e8ad3542169e56cac3859a343afdb9df2ad54d35a59ce1e67baee83421/ruff-0.16.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3817b87dbcabc92f13b05019257c5b89b5b4d51b5fb20f56fb5235ceb723cd07", size = 11333695, upload-time = "2026-08-20T17:43:31.872Z" }, + { url = "https://files.pythonhosted.org/packages/d0/16/481c29b380c20a0054a8261066665e1b3488e23636c49d0a43e75975b9bb/ruff-0.16.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9fce1499134b2c8c68e5166f95705a5812062bb93aacc5f9873bb1a27084bc7", size = 10727741, upload-time = "2026-08-20T17:43:34.596Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b6/56bc0b8cf45b54b28b3a5e6381c8945d51b5b18adf659454c32295209a31/ruff-0.16.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d812e482f5a7e02eee26cd73d2a37ebbdf47d795ea63ba1b89110ae93e9fb3", size = 10286522, upload-time = "2026-08-20T17:43:37.288Z" }, + { url = "https://files.pythonhosted.org/packages/e8/8b/b345b4fb110f2fbe2bd31eabd271e5e8b3b7e4ee6c0e02f2dc6be78db000/ruff-0.16.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6baaf984aa7976edf93d3b627fe2d1d22ee94bbca05fa6f90fc76d73924e3454", size = 10584182, upload-time = "2026-08-20T17:43:39.984Z" }, + { url = "https://files.pythonhosted.org/packages/29/e5/827b34041c35f58774a9681a4213994c164fc987800f4dddabcf451da0bf/ruff-0.16.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bdfcf0b28662eb890372d50f92c283bb94e67e7635ed93c7fd533970acff7b2b", size = 10134195, upload-time = "2026-08-20T17:43:42.351Z" }, + { url = "https://files.pythonhosted.org/packages/0f/10/d0bffcdd6729b87afc82ba0ef377173356a7dc8e972f5179968cf2fdf98c/ruff-0.16.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b66b02cb9b04f537643cadf5768e5f98dc461890d530cb67113d71c8c76e605d", size = 9825821, upload-time = "2026-08-20T17:43:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/f5/32/0db2a863b796ca62d83e92a07a3ccf00921b14db02059347576a2fda3d4b/ruff-0.16.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8528bf9a4b291a60bf02ea453511e8ce6215bd2b982ee80405b66b008b6c30a0", size = 10267658, upload-time = "2026-08-20T17:43:46.989Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a0/fbdeb59e48c6261f523e56c8f12e9c08fbe693786595cc7e3959207a9232/ruff-0.16.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fbd85d2875fdd67e833213a651f613bbf25303abf6aa822a5121f4531195678d", size = 10697071, upload-time = "2026-08-20T17:43:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/aa/28/0c6dd865859c6d17bc8ccc34cb72b0e02d6c7eb25e8a1e22b5bea681e2c0/ruff-0.16.4-py3-none-win32.whl", hash = "sha256:312769988007aaeb8e189b443ccdd03c0e6374489e053467be6d96518ebff76e", size = 10021687, upload-time = "2026-08-20T17:43:52.281Z" }, + { url = "https://files.pythonhosted.org/packages/a3/03/e724450f621698117f9aa6dd241c94d0274ae96781378dc86745ae29f0e7/ruff-0.16.4-py3-none-win_amd64.whl", hash = "sha256:05d9d27a18c4bcbefada602480ec9e01e0bc949d432e0ced5df77edac195919c", size = 10567657, upload-time = "2026-08-20T17:43:54.78Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fe/da8b9e1347696bb22120b77280ec5ce25d500ca5cb39d5ad6e5c18de19c1/ruff-0.16.4-py3-none-win_arm64.whl", hash = "sha256:a3a61621c9b6f6a89573e938a080e648f1695baa3f58570a3a707bc51ff65a21", size = 10451579, upload-time = "2026-08-20T17:43:57.135Z" }, ] [[package]] name = "ty" -version = "0.0.72" +version = "0.0.74" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d5/df/656e684bafb13c1d146e7d5b5f3e7978ca177232acc84998ff36427e9462/ty-0.0.72.tar.gz", hash = "sha256:ec2b8066b618df18cab4cb8e992f8da45d360332acb23fa34df7fa29cd1b9d3a", size = 6654939, upload-time = "2026-08-14T21:35:42.612Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/0f/c767853e88567a2ec7e996dd95e3105b1bc62c95d103689311ef0f4a603c/ty-0.0.74.tar.gz", hash = "sha256:da14344fc8625fc9ff359bafb856ad575636ea86d9bb6a629b146bff27b380e6", size = 6786318, upload-time = "2026-08-22T15:05:54.054Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/3b/f51461239a4e66565d4b362f97a3b55fe7fdba2e944068341f87c62f6743/ty-0.0.72-py3-none-linux_armv6l.whl", hash = "sha256:fda86db153ffd85ee52000cf175d6a3f1c0223772cf7c5b6f726200bf92c7b44", size = 12621989, upload-time = "2026-08-14T21:35:01.676Z" }, - { url = "https://files.pythonhosted.org/packages/ca/fb/79ddf683affc679ca856f3510b5640ec3a88a842ba5f654f5d4bc78f1786/ty-0.0.72-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ceb944c612529b9023acfdc9cf4c0dcbb722549f9d17d46baecd1141baf01d7f", size = 12233910, upload-time = "2026-08-14T21:35:04.334Z" }, - { url = "https://files.pythonhosted.org/packages/5d/45/10562a0d84802158db8fa4ec46de54aa9fdcecdeeaabbfe3639ae7042b66/ty-0.0.72-py3-none-macosx_11_0_arm64.whl", hash = "sha256:108d76218333d6c092e5f1cebf8e9b06f25738613a0236a28e2dd47c936ee52c", size = 12084108, upload-time = "2026-08-14T21:35:06.686Z" }, - { url = "https://files.pythonhosted.org/packages/a1/dc/1fe1aef8d697e3509face271a5331700c7aa1d1e44a4b622707bdfa41d4b/ty-0.0.72-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f3943f186f741a2499a31053872169250c9264a9a49684920e48d8fcf4ef4f5", size = 12132640, upload-time = "2026-08-14T21:35:09.305Z" }, - { url = "https://files.pythonhosted.org/packages/14/46/41ceb265e96969487311a2014bd0e53abb4fbc1395efb2ebe411fcb4db62/ty-0.0.72-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf283c07dc3cc52ca48a3ad8ab100fb5aec3aebbd03ef6a12d5f910b8e596fc5", size = 12402489, upload-time = "2026-08-14T21:35:11.555Z" }, - { url = "https://files.pythonhosted.org/packages/2b/45/30bf43cb4fd505c5c2dd30fda27dde5f05208686cd21217adec77c954204/ty-0.0.72-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95f3b6462c38f9f115d10cee21f47fedf715fcf2040daf36eef210359300bc7c", size = 13130835, upload-time = "2026-08-14T21:35:13.746Z" }, - { url = "https://files.pythonhosted.org/packages/31/2f/03bba754d2613f640df168335c41f83f41db150bb515839c60d80e3a7880/ty-0.0.72-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30caf658feb8ffb250d9e9e47107657a78f5f3425c227df1664d8df2ebe38880", size = 13590392, upload-time = "2026-08-14T21:35:16.839Z" }, - { url = "https://files.pythonhosted.org/packages/04/c7/03c67f00e63005ec41585653dc3096064570b1e6273742baae2798cd242f/ty-0.0.72-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27bdc012ddfbeec8948e4a6036c0dc39ac7cf2c8ec7c7d48dc7d2fd56d57b399", size = 13309629, upload-time = "2026-08-14T21:35:19.169Z" }, - { url = "https://files.pythonhosted.org/packages/c1/df/102d3b264eb7f2a58dd11952f229bb5150bb5668d176a6154976a6675981/ty-0.0.72-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:802c5970a77d7739e6f499921fbb6984fb7ad8a31d95e1ff42fd46f3642e4f3b", size = 12734028, upload-time = "2026-08-14T21:35:22.099Z" }, - { url = "https://files.pythonhosted.org/packages/61/85/d0737c8c54d0ba67366ddfb9f31d88edf0b02299e65923e6945ae60ebcb5/ty-0.0.72-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:47dce65114fdc615c68ca0edb393b433df0956447e4267df0e264137a789598d", size = 13174832, upload-time = "2026-08-14T21:35:24.71Z" }, - { url = "https://files.pythonhosted.org/packages/1e/31/497f5a96c36d9b586ab6afe0574986835c6fd5b835a89773d2bec4711b49/ty-0.0.72-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:325144fa07e2675d0faa337fcc864213c272a499eb0cfe5bde2fdc62282d27bc", size = 12215005, upload-time = "2026-08-14T21:35:26.892Z" }, - { url = "https://files.pythonhosted.org/packages/df/7d/46e65b17b4966c7cd0140f134380d33d8e84fe6efccd761533ce793dc502/ty-0.0.72-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5c9f15d0f58e43707d8848274be1821a0ef408eccb8aa7dda28a4a9eddf7640", size = 12421298, upload-time = "2026-08-14T21:35:29.301Z" }, - { url = "https://files.pythonhosted.org/packages/08/2a/12ada4ec17700b3cb1d4fd3bc3e5b1852df9e6885288429318cade87b3c1/ty-0.0.72-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8ee508d64b381871529cc22c412b41071bf5e908b7aa5d66a38f3f6b2573a806", size = 12669242, upload-time = "2026-08-14T21:35:31.444Z" }, - { url = "https://files.pythonhosted.org/packages/1c/1a/4692536880790fb550ed6d44a6096778dc71bb112f2c6d615cebb01a57e5/ty-0.0.72-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3699e2ec7921d44da79d6b089f7bf239b2cc53c4e45a5a38430adc34ee9e9a55", size = 12988199, upload-time = "2026-08-14T21:35:33.749Z" }, - { url = "https://files.pythonhosted.org/packages/9a/0d/f5e5a50322e9c45865e7b7a428ba6cd6527387cf0f2472492ac3cf746243/ty-0.0.72-py3-none-win32.whl", hash = "sha256:f25f72a67bd36cd247707c4784e52fad0b6b4f42a1b7dd14804110fa95c486ed", size = 11939708, upload-time = "2026-08-14T21:35:36.006Z" }, - { url = "https://files.pythonhosted.org/packages/3f/4e/8af3534b2e4214e6184a5a59c34101e94a68d578f081f97b995866bab1bf/ty-0.0.72-py3-none-win_amd64.whl", hash = "sha256:cdeee869341717e1736cea2e2d7856738c6957c320f584ed2f68c8f90100d2f5", size = 12643876, upload-time = "2026-08-14T21:35:38.141Z" }, - { url = "https://files.pythonhosted.org/packages/ff/ea/a2606e654c7276bd08586391a2525b0af3f3bf60228a8c57b2d248f273f9/ty-0.0.72-py3-none-win_arm64.whl", hash = "sha256:1bd3ac3ed4424a6d6990a85dc388556aea012bd752de21349a84b685951de0d8", size = 12394857, upload-time = "2026-08-14T21:35:40.277Z" }, + { url = "https://files.pythonhosted.org/packages/2c/95/6ded58bc97885c6d88fa1f9cd815031489200738f961cbf0466663213f80/ty-0.0.74-py3-none-linux_armv6l.whl", hash = "sha256:8969ef4e508debf00cf58f9ea85a539f799b1732c59cdfcecd037630b9755b30", size = 12790043, upload-time = "2026-08-22T15:05:05.015Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8a/5e323603b6ab8731144421877ee8a0f8ac5a5511e67857127caa09f6730e/ty-0.0.74-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:51fb6cf5b98e1e1140825b2430943f78d744876a735231656eafbb4c3f7eca3c", size = 12371748, upload-time = "2026-08-22T15:05:08.609Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/ee72e08cb705281e8d8c42917dd577aa598a8a098008495fda5176ee3f6e/ty-0.0.74-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8ebe60b1f0a948c793d6c77fc9e9ddda599e4f023c04ab16e8e03bcb428c3fa0", size = 12282403, upload-time = "2026-08-22T15:05:11.448Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/fd935b694ff68bc278af50f7ad04770b36ce6306399baef7e1847b553a9d/ty-0.0.74-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa97f407a695c890a53615966a663c7d2167e2cabe88db7ca1a24d62635cdfc8", size = 12345164, upload-time = "2026-08-22T15:05:14.19Z" }, + { url = "https://files.pythonhosted.org/packages/54/5c/5b5825268e029ebb164c909780103dbbae367f069801410068bf1cef29b3/ty-0.0.74-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:673ddb733d4a0db31385ba1ed9ff1f6bd9dc5565413ce57b1ca5ac4c7803da5d", size = 12556646, upload-time = "2026-08-22T15:05:16.994Z" }, + { url = "https://files.pythonhosted.org/packages/56/e7/515914e571d62ce0101744fed3f881936eeb1b30dc37beb72b4f7ca1e289/ty-0.0.74-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1028e7c6b4f6145e9704552f43a5fffdcd51b42263ffdcd9c9677762bc395a4a", size = 13311653, upload-time = "2026-08-22T15:05:20.254Z" }, + { url = "https://files.pythonhosted.org/packages/b0/07/d1452babb6f9266c2122cabc095180b70ed306fb770b2996753814d2237d/ty-0.0.74-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79841a8890493021fb308772474983316eb91f7b56cb227a6a05a06b262a36f0", size = 13768284, upload-time = "2026-08-22T15:05:23.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/60/8d4a2fc7842a47210a1cb0a16a187d9de39ad5d509a00fb74c1c073afcde/ty-0.0.74-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94859d321f3c6a6c8f7bfc3f40e8319cda7e6e012e613440f3dfd145d5010e2e", size = 13422306, upload-time = "2026-08-22T15:05:26.248Z" }, + { url = "https://files.pythonhosted.org/packages/de/76/ebbc269a8c4efcc4d44624993bd188145f20d60ebda9680b15aaec42cc50/ty-0.0.74-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:970a8b2c09ff3be04c8a1c6767332d861be4fce85efe7bb205e4ade7c8655274", size = 12970637, upload-time = "2026-08-22T15:05:29.15Z" }, + { url = "https://files.pythonhosted.org/packages/9e/dd/b99f7236acbf856780ca1779a48143d2d9f2c24d7f531a0ce15a022b8a87/ty-0.0.74-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:795f763b3ded85574c2c2846a6fb8acf2aa76e9e83d761143e92b1f0c7ffa2cd", size = 13344891, upload-time = "2026-08-22T15:05:32.033Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d7/9ff7449a4c7e6428f2c6f298e74cf24b70668f29d45c249507a723ff3782/ty-0.0.74-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:dc086db5367d912c31c0cc872deb7387290e779a4b9b54fcb944673a7cd52c7b", size = 12395272, upload-time = "2026-08-22T15:05:34.702Z" }, + { url = "https://files.pythonhosted.org/packages/b1/dd/b23a5b6b35d37df89dc8dc5daa09efd9245a668b50c4c81c25de21567dc1/ty-0.0.74-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0314d7b391cf684e47c2fa093d2ce4c597cfc9b01d9a315fe204aed6359b271b", size = 12573079, upload-time = "2026-08-22T15:05:37.683Z" }, + { url = "https://files.pythonhosted.org/packages/23/c5/ccba16239d6129533c8b3603458d0f4dd2ba69478e47059073968e74261d/ty-0.0.74-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4a45dd2e991e8bdae82ba78c8cd051b253f60bc71a6536598fa3ef580b4fc9b", size = 12832506, upload-time = "2026-08-22T15:05:40.505Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1c/2390912634dff4f341f97b397f2aee341ff062be0a66cda37d59375454f2/ty-0.0.74-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:210e2eac6b018fb934e2b8dac3956a0ba076a3fb1fa6f135058c825e5b759b81", size = 13154752, upload-time = "2026-08-22T15:05:43.355Z" }, + { url = "https://files.pythonhosted.org/packages/c4/33/a8c12188227e6f74f91853a7374e01ed81d6ad21c16c8b70e92dbebfe46a/ty-0.0.74-py3-none-win32.whl", hash = "sha256:db0bb6a8f098ef9bd1be861f73b4f7c0320d40d4c05c7ae0a8677d4e7aa4f6e5", size = 12130002, upload-time = "2026-08-22T15:05:46.058Z" }, + { url = "https://files.pythonhosted.org/packages/21/5c/064f28ccb9c234cfce5a2f7aa69a256663d5ae5bb0290b3a9706cc4d1e4c/ty-0.0.74-py3-none-win_amd64.whl", hash = "sha256:bebff181515255b3c78bd2e7693ae66fab6064ad4feea2065c68bc01022aa678", size = 12771435, upload-time = "2026-08-22T15:05:48.811Z" }, + { url = "https://files.pythonhosted.org/packages/fe/06/d6becdaca0315346c26b6df97cb0eafa81de4f870945d6989e88704374ed/ty-0.0.74-py3-none-win_arm64.whl", hash = "sha256:1a3469eaaf8c85b1c0a15bede25d36daea4b09fce1d913e965b24e24b3f1d6c6", size = 12558299, upload-time = "2026-08-22T15:05:51.543Z" }, ] [[package]]