From 83d837291f08a881f88d6c898d520162f219dcdc Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 10 Apr 2024 07:44:34 +0200 Subject: [PATCH 01/20] PyO3 walking skeleton --- poetry.lock | 69 ++-- pyproject.toml | 1 + src/Fable.Build/FableLibrary/Python.fs | 9 + src/Fable.Transforms/Python/Fable2Python.fs | 6 + src/Fable.Transforms/Python/Replacements.fs | 2 +- src/fable-library-py/Cargo.lock | 295 ++++++++++++++++++ src/fable-library-py/Cargo.toml | 20 ++ .../fable_library/bit_converter_python.py | 90 ++++++ .../fable_library/core/__init__.py | 6 + .../fable_library/core/_core.pyi | 11 + .../fable_library/decimal_.py | 11 +- src/fable-library-py/fable_library/long.py | 1 - src/fable-library-py/fable_library/types.py | 15 +- src/fable-library-py/fable_library/util.py | 2 + src/fable-library-py/poetry.lock | 88 +++--- src/fable-library-py/pyproject.toml | 13 +- src/fable-library-py/src/lib.rs | 20 ++ src/fable-library-py/src/types.rs | 153 +++++++++ 18 files changed, 732 insertions(+), 80 deletions(-) create mode 100644 src/fable-library-py/Cargo.lock create mode 100644 src/fable-library-py/Cargo.toml create mode 100644 src/fable-library-py/fable_library/bit_converter_python.py create mode 100644 src/fable-library-py/fable_library/core/__init__.py create mode 100644 src/fable-library-py/fable_library/core/_core.pyi create mode 100644 src/fable-library-py/src/lib.rs create mode 100644 src/fable-library-py/src/types.rs diff --git a/poetry.lock b/poetry.lock index 31925a0e0a..74ba06469e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "colorama" @@ -36,6 +36,35 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "maturin" +version = "1.5.1" +description = "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "maturin-1.5.1-py3-none-linux_armv6l.whl", hash = "sha256:589e9b7024007e130b136ba6f1c2c8393a87e42cf968d12852913ab1e3c69ed3"}, + {file = "maturin-1.5.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a1abda07093b3c8ef897626166c02ed64e3e446c48460b28efb51833abf89cbb"}, + {file = "maturin-1.5.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:48a1fbbdc2514525f27d6d339ab97b098ede28759f8593d110c89cc07bbe40ed"}, + {file = "maturin-1.5.1-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:96d96b1fa3a165db9ca539f764d31da8ebc92e31ca3a1dd6ccd50008d222bd96"}, + {file = "maturin-1.5.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:786bf36a98c4e27cbebb1dc8e432c1bcbbb59e7a9719592cbb89e46a0ccd5bcc"}, + {file = "maturin-1.5.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:d821b37da759884ad09cfee4cd9deac10f4132744cc66e4d9190a1972233bc83"}, + {file = "maturin-1.5.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:62133bf690555bbc8cc6b1c18a0c57b0ab2b4d68d3fcd320eb16df941563fe06"}, + {file = "maturin-1.5.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:6bff165252b1fcc887679ddf7b71b5cc024327ba96ea893133be38c0ed38f163"}, + {file = "maturin-1.5.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c42a95466ffc3de0a3940cd20c57cf0c44fe5ea679375d73422afbb00236c64"}, + {file = "maturin-1.5.1-py3-none-win32.whl", hash = "sha256:d09538b4aa0da4b59fd47cb429003b45bfd5d801714adf1db2511bf8bdea532f"}, + {file = "maturin-1.5.1-py3-none-win_amd64.whl", hash = "sha256:a3db9054222ac79275e082b21cfd234b8e036714a4ff227a0a28f6a3ffa3744d"}, + {file = "maturin-1.5.1-py3-none-win_arm64.whl", hash = "sha256:acf528e51413f6ae489473d64116d8c83f140386349004949d29137c16a82193"}, + {file = "maturin-1.5.1.tar.gz", hash = "sha256:3dd834ece80edb866af18cbd4635e0ecac40139c726428d5f1849ae154b26dca"}, +] + +[package.dependencies] +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +patchelf = ["patchelf"] +zig = ["ziglang (>=0.10.0,<0.11.0)"] + [[package]] name = "packaging" version = "24.0" @@ -100,28 +129,28 @@ six = ">=1.5" [[package]] name = "ruff" -version = "0.3.4" +version = "0.3.5" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:60c870a7d46efcbc8385d27ec07fe534ac32f3b251e4fc44b3cbfd9e09609ef4"}, - {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6fc14fa742e1d8f24910e1fff0bd5e26d395b0e0e04cc1b15c7c5e5fe5b4af91"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3ee7880f653cc03749a3bfea720cf2a192e4f884925b0cf7eecce82f0ce5854"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf133dd744f2470b347f602452a88e70dadfbe0fcfb5fd46e093d55da65f82f7"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f3860057590e810c7ffea75669bdc6927bfd91e29b4baa9258fd48b540a4365"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:986f2377f7cf12efac1f515fc1a5b753c000ed1e0a6de96747cdf2da20a1b369"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fd98e85869603e65f554fdc5cddf0712e352fe6e61d29d5a6fe087ec82b76c"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64abeed785dad51801b423fa51840b1764b35d6c461ea8caef9cf9e5e5ab34d9"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df52972138318bc7546d92348a1ee58449bc3f9eaf0db278906eb511889c4b50"}, - {file = "ruff-0.3.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:98e98300056445ba2cc27d0b325fd044dc17fcc38e4e4d2c7711585bd0a958ed"}, - {file = "ruff-0.3.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:519cf6a0ebed244dce1dc8aecd3dc99add7a2ee15bb68cf19588bb5bf58e0488"}, - {file = "ruff-0.3.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb0acfb921030d00070539c038cd24bb1df73a2981e9f55942514af8b17be94e"}, - {file = "ruff-0.3.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cf187a7e7098233d0d0c71175375c5162f880126c4c716fa28a8ac418dcf3378"}, - {file = "ruff-0.3.4-py3-none-win32.whl", hash = "sha256:af27ac187c0a331e8ef91d84bf1c3c6a5dea97e912a7560ac0cef25c526a4102"}, - {file = "ruff-0.3.4-py3-none-win_amd64.whl", hash = "sha256:de0d5069b165e5a32b3c6ffbb81c350b1e3d3483347196ffdf86dc0ef9e37dd6"}, - {file = "ruff-0.3.4-py3-none-win_arm64.whl", hash = "sha256:6810563cc08ad0096b57c717bd78aeac888a1bfd38654d9113cb3dc4d3f74232"}, - {file = "ruff-0.3.4.tar.gz", hash = "sha256:f0f4484c6541a99862b693e13a151435a279b271cff20e37101116a21e2a1ad1"}, + {file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:aef5bd3b89e657007e1be6b16553c8813b221ff6d92c7526b7e0227450981eac"}, + {file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:89b1e92b3bd9fca249153a97d23f29bed3992cff414b222fcd361d763fc53f12"}, + {file = "ruff-0.3.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e55771559c89272c3ebab23326dc23e7f813e492052391fe7950c1a5a139d89"}, + {file = "ruff-0.3.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dabc62195bf54b8a7876add6e789caae0268f34582333cda340497c886111c39"}, + {file = "ruff-0.3.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a05f3793ba25f194f395578579c546ca5d83e0195f992edc32e5907d142bfa3"}, + {file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dfd3504e881082959b4160ab02f7a205f0fadc0a9619cc481982b6837b2fd4c0"}, + {file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87258e0d4b04046cf1d6cc1c56fadbf7a880cc3de1f7294938e923234cf9e498"}, + {file = "ruff-0.3.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:712e71283fc7d9f95047ed5f793bc019b0b0a29849b14664a60fd66c23b96da1"}, + {file = "ruff-0.3.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a532a90b4a18d3f722c124c513ffb5e5eaff0cc4f6d3aa4bda38e691b8600c9f"}, + {file = "ruff-0.3.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:122de171a147c76ada00f76df533b54676f6e321e61bd8656ae54be326c10296"}, + {file = "ruff-0.3.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d80a6b18a6c3b6ed25b71b05eba183f37d9bc8b16ace9e3d700997f00b74660b"}, + {file = "ruff-0.3.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a7b6e63194c68bca8e71f81de30cfa6f58ff70393cf45aab4c20f158227d5936"}, + {file = "ruff-0.3.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a759d33a20c72f2dfa54dae6e85e1225b8e302e8ac655773aff22e542a300985"}, + {file = "ruff-0.3.5-py3-none-win32.whl", hash = "sha256:9d8605aa990045517c911726d21293ef4baa64f87265896e491a05461cae078d"}, + {file = "ruff-0.3.5-py3-none-win_amd64.whl", hash = "sha256:dc56bb16a63c1303bd47563c60482a1512721053d93231cf7e9e1c6954395a0e"}, + {file = "ruff-0.3.5-py3-none-win_arm64.whl", hash = "sha256:faeeae9905446b975dcf6d4499dc93439b131f1443ee264055c5716dd947af55"}, + {file = "ruff-0.3.5.tar.gz", hash = "sha256:a067daaeb1dc2baf9b82a32dae67d154d95212080c80435eb052d95da647763d"}, ] [[package]] @@ -149,4 +178,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">= 3.10, < 4.0" -content-hash = "8789a81322c96c6dca4695ce9e6ba8cbd0967986b6f1872915578241b12902f3" +content-hash = "2ba4d9e073e29a382f9d349053b020c97fdac497cae2b596d14315cf19e1b6f6" diff --git a/pyproject.toml b/pyproject.toml index 06158aeef9..fc802f30c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ python-dateutil = "^2.9.0" [tool.poetry.dev-dependencies] pytest = "^8.1.1" ruff = "^0.3.4" +maturin = "^1.5.0" [tool.pyright] reportMissingTypeStubs = false diff --git a/src/Fable.Build/FableLibrary/Python.fs b/src/Fable.Build/FableLibrary/Python.fs index 19c46ac288..aa80a780e0 100644 --- a/src/Fable.Build/FableLibrary/Python.fs +++ b/src/Fable.Build/FableLibrary/Python.fs @@ -20,12 +20,21 @@ type BuildFableLibraryPython() = Directory.GetFiles(this.LibraryDir, "*") |> Shell.copyFiles this.BuildDir Directory.GetFiles(this.SourceDir, "*.py") |> Shell.copyFiles this.OutDir + // Python extension modules + Directory.GetFiles(Path.Combine(this.SourceDir, "core"), "*") + |> Shell.copyFiles (Path.Combine(this.OutDir, "core")) + // Rust sources for building the extension modules + Directory.GetFiles(Path.Combine(this.LibraryDir, "src"), "*") + |> Shell.copyFiles (Path.Combine(this.BuildDir, "src")) + override this.PostFableBuildStage() = // Fix issues with Fable .fsproj not supporting links let linkedFileFolder = Path.Combine(this.BuildDir, "fable_library", "fable-library-ts") + Command.Run("maturin", "develop", this.BuildDir) + Directory.GetFiles(linkedFileFolder, "*") |> Shell.copyFiles this.OutDir Shell.deleteDir (this.BuildDir "fable_library/fable-library-ts") diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index d2fb6b8368..6d33cd3d31 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -1203,8 +1203,14 @@ module Util = | Some "B" -> let bytearray = Expression.name "bytearray" Expression.call (bytearray, [ Expression.list expr ]), stmts + | Some l when (l = "f" || l = "d") -> + let array = com.GetImportExpr(ctx, "array", "array") + Expression.call (array, Expression.stringConstant l :: [ Expression.list expr ]), stmts | Some l -> let array = com.GetImportExpr(ctx, "array", "array") + // Make every expr item into a Python int since might be signed and sized (by Rust). + let expr = + expr |> List.map (fun e -> Expression.call (Expression.name "int", [ e ])) Expression.call (array, Expression.stringConstant l :: [ Expression.list expr ]), stmts | _ -> expr |> Expression.list, stmts diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index d6aa82d266..a5b4705fa5 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -75,7 +75,7 @@ let makeDecimalFromExpr com r t (e: Expr) = | Value(Fable.NumberConstant(:? float32 as x, Float32, _), _) -> makeDecimal com r t (decimal x) | Value(Fable.NumberConstant(:? float as x, Float64, _), _) -> makeDecimal com r t (decimal x) | Value(Fable.NumberConstant(:? decimal as x, Decimal, _), _) -> makeDecimal com r t x - | _ -> Helper.LibCall(com, "decimal", "Decimal", t, [ e ], isConstructor = true, ?loc = r) + | _ -> Helper.LibCall(com, "decimal_", "create", t, [ e ], isConstructor = true, ?loc = r) let createAtom com (value: Expr) = let typ = value.Type diff --git a/src/fable-library-py/Cargo.lock b/src/fable-library-py/Cargo.lock new file mode 100644 index 0000000000..663c1c55ae --- /dev/null +++ b/src/fable-library-py/Cargo.lock @@ -0,0 +1,295 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "fable-library-pyo3" +version = "0.1.0" +dependencies = [ + "byteorder", + "pyo3", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "syn" +version = "2.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/src/fable-library-py/Cargo.toml b/src/fable-library-py/Cargo.toml new file mode 100644 index 0000000000..ed86f891d3 --- /dev/null +++ b/src/fable-library-py/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "fable-library-pyo3" +version = "0.1.0" +edition = "2021" +license = "MIT" +repository = "https://github.com/fable-compiler/fable" +readme = "README.md" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "_core" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = "0.20.0" +byteorder = "1.5.0" + +[features] +# must be enabled when building with `cargo build`, maturin enables this automatically +extension-module = ["pyo3/extension-module"] diff --git a/src/fable-library-py/fable_library/bit_converter_python.py b/src/fable-library-py/fable_library/bit_converter_python.py new file mode 100644 index 0000000000..478802b5ac --- /dev/null +++ b/src/fable-library-py/fable_library/bit_converter_python.py @@ -0,0 +1,90 @@ +import struct +import sys + + +def get_bytes_int16(value: int) -> bytes: + return value.to_bytes(length=2, byteorder=sys.byteorder) + + +def get_bytes_uint16(value: int) -> bytes: + return value.to_bytes(length=2, byteorder=sys.byteorder) + + +def get_bytes_int32(value: int) -> bytes: + return value.to_bytes(length=4, byteorder=sys.byteorder) + + +def get_bytes_uint32(value: int) -> bytes: + return value.to_bytes(length=4, byteorder=sys.byteorder) + + +def get_bytes_int64(value: int) -> bytes: + return value.to_bytes(length=8, byteorder=sys.byteorder) + + +def get_bytes_uint64(value: int) -> bytes: + return value.to_bytes(length=8, byteorder=sys.byteorder) + + +def get_bytes_boolean(value: bool) -> bytes: + return value.to_bytes(length=1, byteorder=sys.byteorder) + + +def int64bits_to_double(value: int) -> float: + bytes = value.to_bytes(length=8, byteorder=sys.byteorder) + [number] = struct.unpack("d", bytes) + return number + + +def double_to_int64bits(value: float) -> int: + bytes = bytearray(struct.pack("d", value)) + [number] = struct.unpack("q", bytes) + return number + + +def to_char(bytes: bytearray, offset: int) -> str: + return bytes[offset : offset + 1].decode("utf8") + + +def to_int16(bytes: bytearray, offset: int) -> int: + return int.from_bytes(bytes[offset : offset + 2], byteorder=sys.byteorder, signed=True) + + +def to_uint16(bytes: bytearray, offset: int) -> int: + return int.from_bytes(bytes[offset : offset + 2], byteorder=sys.byteorder, signed=False) + + +def to_int32(bytes: bytearray, offset: int) -> int: + return int.from_bytes(bytes[offset : offset + 4], byteorder=sys.byteorder, signed=True) + + +def to_uint32(bytes: bytearray, offset: int) -> int: + return int.from_bytes(bytes[offset : offset + 4], byteorder=sys.byteorder, signed=False) + + +def to_int64(bytes: bytes, offset: int) -> int: + return int.from_bytes(bytes[offset : offset + 8], byteorder=sys.byteorder, signed=True) + + +def to_uint64(bytes: bytearray, offset: int) -> int: + return int.from_bytes(bytes[offset : offset + 8], byteorder=sys.byteorder, signed=False) + + +__all__ = [ + "get_bytes_int16", + "get_bytes_uint16", + "get_bytes_int32", + "get_bytes_uint32", + "get_bytes_int64", + "get_bytes_uint64", + "get_bytes_boolean", + "int64bits_to_double", + "double_to_int64bits", + "to_char", + "to_int16", + "to_uint16", + "to_int32", + "to_uint32", + "to_uint64", + "to_int64", +] diff --git a/src/fable-library-py/fable_library/core/__init__.py b/src/fable-library-py/fable_library/core/__init__.py new file mode 100644 index 0000000000..e7cd597308 --- /dev/null +++ b/src/fable-library-py/fable_library/core/__init__.py @@ -0,0 +1,6 @@ +from ._core import Int16 as int16 # type: ignore +from ._core import UInt16 as uint16 # type: ignore +from ._core import sum_as_string # type: ignore + + +__all__ = ["sum_as_string", "uint16", "int16"] diff --git a/src/fable-library-py/fable_library/core/_core.pyi b/src/fable-library-py/fable_library/core/_core.pyi new file mode 100644 index 0000000000..6db463febf --- /dev/null +++ b/src/fable-library-py/fable_library/core/_core.pyi @@ -0,0 +1,11 @@ +from typing import final + +@final +class UInt16: + def __init__(self, value: int) -> None: ... + +@final +class Int16: + def __init__(self, value: int) -> None: ... + +def sum_as_string(a: int, b: int) -> int: ... diff --git a/src/fable-library-py/fable_library/decimal_.py b/src/fable-library-py/fable_library/decimal_.py index 35319290f4..76fd5821fe 100644 --- a/src/fable-library-py/fable_library/decimal_.py +++ b/src/fable-library-py/fable_library/decimal_.py @@ -1,6 +1,6 @@ from decimal import MAX_EMAX, MIN_EMIN, Decimal, getcontext -from .types import FSharpRef +from .types import FSharpRef, int16, uint16 getcontext().prec = 29 @@ -156,6 +156,14 @@ def try_parse(string: str, def_value: FSharpRef[Decimal]) -> bool: return False +def create(value: int | float | int16 | uint16 | str) -> Decimal: + match value: + case int16() | uint16(): + return Decimal(int(value)) + case _: + return Decimal(value) + + __all__ = [ "compare", "equals", @@ -187,4 +195,5 @@ def try_parse(string: str, def_value: FSharpRef[Decimal]) -> bool: "to_number", "try_parse", "parse", + "Decimal", ] diff --git a/src/fable-library-py/fable_library/long.py b/src/fable-library-py/fable_library/long.py index 17c0e92b7f..5950e42a3e 100644 --- a/src/fable-library-py/fable_library/long.py +++ b/src/fable-library-py/fable_library/long.py @@ -32,7 +32,6 @@ def op_unary_negation(value: int) -> int: return -value if value != -0x8000000000000000 else -0x8000000000000000 -# def op_unary_negation(a: int) -> int: return -a def op_unary_plus(a: int) -> int: return +a diff --git a/src/fable-library-py/fable_library/types.py b/src/fable-library-py/fable_library/types.py index 438858e1fa..ca5ed1a268 100644 --- a/src/fable-library-py/fable_library/types.py +++ b/src/fable-library-py/fable_library/types.py @@ -10,6 +10,7 @@ cast, ) +from .core import int16, uint16 from .util import Array, IComparable, compare @@ -55,8 +56,7 @@ def __init__(self): @staticmethod @abstractmethod - def cases() -> list[str]: - ... + def cases() -> list[str]: ... @property def name(self) -> str: @@ -204,8 +204,7 @@ def __hash__(self) -> int: return record_get_hashcode(self) -class Attribute: - ... +class Attribute: ... def seq_to_string(self: Iterable[Any]) -> str: @@ -297,10 +296,6 @@ class int8(int): __slots__ = () -class int16(int): - __slots__ = () - - class int32(int): __slots__ = () @@ -313,10 +308,6 @@ class uint8(int): __slots__ = () -class uint16(int): - __slots__ = () - - class uint32(int): __slots__ = () diff --git a/src/fable-library-py/fable_library/util.py b/src/fable-library-py/fable_library/util.py index 7e58af7905..f1f8dc1f48 100644 --- a/src/fable-library-py/fable_library/util.py +++ b/src/fable-library-py/fable_library/util.py @@ -446,6 +446,8 @@ def tohex(val: int, nbits: int | None = None) -> str: def int_to_string(i: int, radix: int = 10, bitsize: int | None = None) -> str: + i = int(i) # Translate core pyo3 integers to Python integers + if radix == 10: return f"{i:d}" if radix == 16: diff --git a/src/fable-library-py/poetry.lock b/src/fable-library-py/poetry.lock index fa5d10da28..123e775d46 100644 --- a/src/fable-library-py/poetry.lock +++ b/src/fable-library-py/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.3" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] @@ -36,26 +36,55 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "maturin" +version = "1.5.1" +description = "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "maturin-1.5.1-py3-none-linux_armv6l.whl", hash = "sha256:589e9b7024007e130b136ba6f1c2c8393a87e42cf968d12852913ab1e3c69ed3"}, + {file = "maturin-1.5.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a1abda07093b3c8ef897626166c02ed64e3e446c48460b28efb51833abf89cbb"}, + {file = "maturin-1.5.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:48a1fbbdc2514525f27d6d339ab97b098ede28759f8593d110c89cc07bbe40ed"}, + {file = "maturin-1.5.1-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:96d96b1fa3a165db9ca539f764d31da8ebc92e31ca3a1dd6ccd50008d222bd96"}, + {file = "maturin-1.5.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:786bf36a98c4e27cbebb1dc8e432c1bcbbb59e7a9719592cbb89e46a0ccd5bcc"}, + {file = "maturin-1.5.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:d821b37da759884ad09cfee4cd9deac10f4132744cc66e4d9190a1972233bc83"}, + {file = "maturin-1.5.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:62133bf690555bbc8cc6b1c18a0c57b0ab2b4d68d3fcd320eb16df941563fe06"}, + {file = "maturin-1.5.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:6bff165252b1fcc887679ddf7b71b5cc024327ba96ea893133be38c0ed38f163"}, + {file = "maturin-1.5.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c42a95466ffc3de0a3940cd20c57cf0c44fe5ea679375d73422afbb00236c64"}, + {file = "maturin-1.5.1-py3-none-win32.whl", hash = "sha256:d09538b4aa0da4b59fd47cb429003b45bfd5d801714adf1db2511bf8bdea532f"}, + {file = "maturin-1.5.1-py3-none-win_amd64.whl", hash = "sha256:a3db9054222ac79275e082b21cfd234b8e036714a4ff227a0a28f6a3ffa3744d"}, + {file = "maturin-1.5.1-py3-none-win_arm64.whl", hash = "sha256:acf528e51413f6ae489473d64116d8c83f140386349004949d29137c16a82193"}, + {file = "maturin-1.5.1.tar.gz", hash = "sha256:3dd834ece80edb866af18cbd4635e0ecac40139c726428d5f1849ae154b26dca"}, +] + +[package.dependencies] +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +patchelf = ["patchelf"] +zig = ["ziglang (>=0.10.0,<0.11.0)"] + [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -64,13 +93,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pytest" -version = "7.4.3" +version = "8.1.1" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, + {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, + {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, ] [package.dependencies] @@ -78,36 +107,11 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +pluggy = ">=1.4,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] +testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "tomli" @@ -123,4 +127,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">= 3.10, < 4.0" -content-hash = "eb5624d372333406d0deaf57d06f018bdf9a030cdd8eed8f2d8904732447806e" +content-hash = "29545c25d8b2cfabee687efc7fd0b78f4849548382fd807ac4b543a9545c380b" diff --git a/src/fable-library-py/pyproject.toml b/src/fable-library-py/pyproject.toml index 9f2baa1031..27e3266751 100644 --- a/src/fable-library-py/pyproject.toml +++ b/src/fable-library-py/pyproject.toml @@ -6,13 +6,20 @@ authors = ["Dag Brattli "] license = "MIT License" readme = "README.md" homepage = "https://fable.io" +packages = [{ include = "fable_library", from = "." }] [tool.poetry.dependencies] python = ">= 3.10, < 4.0" [tool.poetry.dev-dependencies] -pytest = "^7.2.0" +pytest = "^8.1" +maturin = "^1.5.0" + +[tool.maturin] +python-source = "fable_library" +features = ["pyo3/extension-module"] +module-name = "core._core" [build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +requires = ["poetry-core>=1.0.0", "maturin>=1.5,<2.0"] +build-backend = "maturin" diff --git a/src/fable-library-py/src/lib.rs b/src/fable-library-py/src/lib.rs new file mode 100644 index 0000000000..8952ffa89a --- /dev/null +++ b/src/fable-library-py/src/lib.rs @@ -0,0 +1,20 @@ +use pyo3::prelude::*; + +mod types; + +use crate::types::*; + +/// Formats the sum of two numbers as string. +#[pyfunction] +fn sum_as_string(a: usize, b: usize) -> PyResult { + Ok((a + b).to_string()) +} + +/// A Python module implemented in Rust. +#[pymodule] +fn _core(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; + m.add_class::()?; + m.add_class::()?; + Ok(()) +} diff --git a/src/fable-library-py/src/types.rs b/src/fable-library-py/src/types.rs new file mode 100644 index 0000000000..c1c34252de --- /dev/null +++ b/src/fable-library-py/src/types.rs @@ -0,0 +1,153 @@ +#![allow(dead_code)] +use byteorder::{BigEndian, ByteOrder, LittleEndian}; +use pyo3::class::basic::CompareOp; +use pyo3::exceptions; +use pyo3::prelude::*; +use pyo3::types::PyBytes; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +macro_rules! integer_variant { + ($name:ident, $type:ty) => { + #[pyclass] + #[derive(Clone)] + pub struct $name($type); + + #[pymethods] + impl $name { + #[new] + pub fn new(value: &PyAny) -> PyResult { + match value.extract::<$name>() { + Ok(value) => Ok(Self(value.0)), + Err(_) => match value.extract::<$type>() { + Ok(value) => Ok(Self(value)), + Err(_) => Err(PyErr::new::( + "Cannot convert argument to integer", + )), + }, + } + } + + pub fn to_bytes( + &self, + py: Python, + length: usize, + byteorder: &str, + ) -> PyResult { + let mut bytes = vec![0; length]; + match byteorder { + "little" => LittleEndian::write_uint(&mut bytes, self.0 as u64, length), + "big" => BigEndian::write_uint(&mut bytes, self.0 as u64, length), + _ => { + return Err(PyErr::new::( + "Invalid byteorder", + )) + } + } + Ok(PyBytes::new(py, &bytes).into()) + } + + pub fn __add__(&self, other: &PyAny) -> PyResult<$name> { + let other = match other.extract::<$name>() { + Ok(other) => other.0, + Err(_) => match other.extract::<$type>() { + Ok(other) => other, + Err(_) => { + return Err(PyErr::new::( + "Cannot convert argument to integer", + )) + } + }, + }; + Ok($name(self.0 + other)) + } + + pub fn __sub__(&self, other: &PyAny) -> PyResult<$name> { + let other = match other.extract::<$name>() { + Ok(other) => other.0, + Err(_) => match other.extract::<$type>() { + Ok(other) => other, + Err(_) => { + return Err(PyErr::new::( + "Cannot convert argument to integer", + )) + } + }, + }; + Ok($name(self.0 - other)) + } + + pub fn __mul__(&self, other: &PyAny) -> PyResult<$name> { + let other = match other.extract::<$name>() { + Ok(other) => other.0, + Err(_) => match other.extract::<$type>() { + Ok(other) => other, + Err(_) => { + return Err(PyErr::new::( + "Cannot convert argument to integer", + )) + } + }, + }; + Ok($name(self.0 * other)) + } + + pub fn __truediv__(&self, other: &PyAny) -> PyResult<$name> { + let other = other.extract::<$type>()?; + Ok($name(self.0 / other)) + } + + pub fn __richcmp__(&self, other: &PyAny, op: CompareOp) -> PyResult { + let other = match other.extract::<$name>() { + Ok(other) => Ok(other.0), + Err(_) => match other.extract::<$type>() { + Ok(other) => Ok(other), + Err(_) => match other.extract::() { + Ok(other) => Ok(other as $type), + Err(_) => { + Err(PyErr::new::("Cannot compare")) + } + }, + }, + }; + + match other { + Ok(other) => match op { + CompareOp::Eq => Ok(self.0 == other), + CompareOp::Ne => Ok(self.0 != other), + CompareOp::Lt => Ok(self.0 < other), + CompareOp::Le => Ok(self.0 <= other), + CompareOp::Gt => Ok(self.0 > other), + CompareOp::Ge => Ok(self.0 >= other), + }, + Err(_) => Ok(false), + } + } + + fn __bool__(&self) -> bool { + self.0 != 0 + } + + pub fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.0.hash(&mut hasher); + hasher.finish() + } + + pub fn __int__(&self) -> PyResult<$type> { + Ok(self.0) + } + + pub fn __repr__(&self) -> PyResult { + Ok(self.0.to_string()) + } + + pub fn __str__(&self) -> PyResult { + Ok(self.0.to_string()) + } + } + }; +} + +integer_variant!(Int16, i16); +integer_variant!(UInt16, u16); From d17ebb153096b20c52a5012db196f74706bc13ae Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 10 Apr 2024 08:05:13 +0200 Subject: [PATCH 02/20] Install maturin in CI --- .github/workflows/build.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 043c88bb1e..5771ee53d8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -145,9 +145,6 @@ jobs: - name: Setup dotnet tools run: dotnet tool restore - # - name: Check F# formatting (fantomas) - # run: dotnet fantomas src/Fable.Transforms/Python --check - - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: @@ -155,7 +152,8 @@ jobs: - name: Install poetry run: | - pip install poetry + pipx install poetry + pipx install maturin - name: Fable Library - Python (linux) if: matrix.platform == 'ubuntu-latest' From a788b260586c54b54e15f97132b62937df88b07d Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 10 Apr 2024 08:28:39 +0200 Subject: [PATCH 03/20] Remove unused file --- .../fable_library/bit_converter_python.py | 90 ------------------- 1 file changed, 90 deletions(-) delete mode 100644 src/fable-library-py/fable_library/bit_converter_python.py diff --git a/src/fable-library-py/fable_library/bit_converter_python.py b/src/fable-library-py/fable_library/bit_converter_python.py deleted file mode 100644 index 478802b5ac..0000000000 --- a/src/fable-library-py/fable_library/bit_converter_python.py +++ /dev/null @@ -1,90 +0,0 @@ -import struct -import sys - - -def get_bytes_int16(value: int) -> bytes: - return value.to_bytes(length=2, byteorder=sys.byteorder) - - -def get_bytes_uint16(value: int) -> bytes: - return value.to_bytes(length=2, byteorder=sys.byteorder) - - -def get_bytes_int32(value: int) -> bytes: - return value.to_bytes(length=4, byteorder=sys.byteorder) - - -def get_bytes_uint32(value: int) -> bytes: - return value.to_bytes(length=4, byteorder=sys.byteorder) - - -def get_bytes_int64(value: int) -> bytes: - return value.to_bytes(length=8, byteorder=sys.byteorder) - - -def get_bytes_uint64(value: int) -> bytes: - return value.to_bytes(length=8, byteorder=sys.byteorder) - - -def get_bytes_boolean(value: bool) -> bytes: - return value.to_bytes(length=1, byteorder=sys.byteorder) - - -def int64bits_to_double(value: int) -> float: - bytes = value.to_bytes(length=8, byteorder=sys.byteorder) - [number] = struct.unpack("d", bytes) - return number - - -def double_to_int64bits(value: float) -> int: - bytes = bytearray(struct.pack("d", value)) - [number] = struct.unpack("q", bytes) - return number - - -def to_char(bytes: bytearray, offset: int) -> str: - return bytes[offset : offset + 1].decode("utf8") - - -def to_int16(bytes: bytearray, offset: int) -> int: - return int.from_bytes(bytes[offset : offset + 2], byteorder=sys.byteorder, signed=True) - - -def to_uint16(bytes: bytearray, offset: int) -> int: - return int.from_bytes(bytes[offset : offset + 2], byteorder=sys.byteorder, signed=False) - - -def to_int32(bytes: bytearray, offset: int) -> int: - return int.from_bytes(bytes[offset : offset + 4], byteorder=sys.byteorder, signed=True) - - -def to_uint32(bytes: bytearray, offset: int) -> int: - return int.from_bytes(bytes[offset : offset + 4], byteorder=sys.byteorder, signed=False) - - -def to_int64(bytes: bytes, offset: int) -> int: - return int.from_bytes(bytes[offset : offset + 8], byteorder=sys.byteorder, signed=True) - - -def to_uint64(bytes: bytearray, offset: int) -> int: - return int.from_bytes(bytes[offset : offset + 8], byteorder=sys.byteorder, signed=False) - - -__all__ = [ - "get_bytes_int16", - "get_bytes_uint16", - "get_bytes_int32", - "get_bytes_uint32", - "get_bytes_int64", - "get_bytes_uint64", - "get_bytes_boolean", - "int64bits_to_double", - "double_to_int64bits", - "to_char", - "to_int16", - "to_uint16", - "to_int32", - "to_uint32", - "to_uint64", - "to_int64", -] From 0fc40006f1fdc240de65581c569998796ffb1459 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 10 Apr 2024 23:39:08 +0200 Subject: [PATCH 04/20] Better interop --- src/fable-library-py/src/types.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/fable-library-py/src/types.rs b/src/fable-library-py/src/types.rs index c1c34252de..628e05c65d 100644 --- a/src/fable-library-py/src/types.rs +++ b/src/fable-library-py/src/types.rs @@ -1,11 +1,13 @@ #![allow(dead_code)] -use byteorder::{BigEndian, ByteOrder, LittleEndian}; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + use pyo3::class::basic::CompareOp; use pyo3::exceptions; use pyo3::prelude::*; use pyo3::types::PyBytes; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; + +use byteorder::{BigEndian, ByteOrder, LittleEndian}; macro_rules! integer_variant { ($name:ident, $type:ty) => { @@ -62,6 +64,10 @@ macro_rules! integer_variant { Ok($name(self.0 + other)) } + pub fn __radd__(&self, other: &PyAny) -> PyResult<$name> { + self.__add__(other) + } + pub fn __sub__(&self, other: &PyAny) -> PyResult<$name> { let other = match other.extract::<$name>() { Ok(other) => other.0, @@ -77,6 +83,10 @@ macro_rules! integer_variant { Ok($name(self.0 - other)) } + pub fn __rsub__(&self, other: &PyAny) -> PyResult<$name> { + self.__sub__(other) + } + pub fn __mul__(&self, other: &PyAny) -> PyResult<$name> { let other = match other.extract::<$name>() { Ok(other) => other.0, @@ -138,6 +148,12 @@ macro_rules! integer_variant { Ok(self.0) } + // Special method so that arbitrary objects can be used whenever integers + // are explicitly needed in Python + pub fn __index__(&self) -> PyResult<$type> { + Ok(self.0) + } + pub fn __repr__(&self) -> PyResult { Ok(self.0.to_string()) } From 35f520e97f7b5fc27749869a0470776ec623ec9d Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 10 Apr 2024 23:58:42 +0200 Subject: [PATCH 05/20] Try to fix CI --- src/Fable.Build/FableLibrary/Python.fs | 1 + src/Fable.Transforms/Python/Fable2Python.fs | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Fable.Build/FableLibrary/Python.fs b/src/Fable.Build/FableLibrary/Python.fs index aa80a780e0..8c71fe3ebd 100644 --- a/src/Fable.Build/FableLibrary/Python.fs +++ b/src/Fable.Build/FableLibrary/Python.fs @@ -33,6 +33,7 @@ type BuildFableLibraryPython() = let linkedFileFolder = Path.Combine(this.BuildDir, "fable_library", "fable-library-ts") + Command.Run("poetry", "install") // Maturn needs a virtual environment Command.Run("maturin", "develop", this.BuildDir) Directory.GetFiles(linkedFileFolder, "*") |> Shell.copyFiles this.OutDir diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 6d33cd3d31..f4b94fd776 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -1203,15 +1203,8 @@ module Util = | Some "B" -> let bytearray = Expression.name "bytearray" Expression.call (bytearray, [ Expression.list expr ]), stmts - | Some l when (l = "f" || l = "d") -> - let array = com.GetImportExpr(ctx, "array", "array") - Expression.call (array, Expression.stringConstant l :: [ Expression.list expr ]), stmts | Some l -> let array = com.GetImportExpr(ctx, "array", "array") - // Make every expr item into a Python int since might be signed and sized (by Rust). - let expr = - expr |> List.map (fun e -> Expression.call (Expression.name "int", [ e ])) - Expression.call (array, Expression.stringConstant l :: [ Expression.list expr ]), stmts | _ -> expr |> Expression.list, stmts From 1250359c2a93c82a8bc9dc7c39eab3698a005531 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 11 Apr 2024 00:14:05 +0200 Subject: [PATCH 06/20] Fix install directory --- src/Fable.Build/FableLibrary/Python.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fable.Build/FableLibrary/Python.fs b/src/Fable.Build/FableLibrary/Python.fs index 8c71fe3ebd..93dfe403c9 100644 --- a/src/Fable.Build/FableLibrary/Python.fs +++ b/src/Fable.Build/FableLibrary/Python.fs @@ -33,7 +33,7 @@ type BuildFableLibraryPython() = let linkedFileFolder = Path.Combine(this.BuildDir, "fable_library", "fable-library-ts") - Command.Run("poetry", "install") // Maturn needs a virtual environment + Command.Run("poetry", "install", this.BuildDir) // Maturn needs a virtual environment Command.Run("maturin", "develop", this.BuildDir) Directory.GetFiles(linkedFileFolder, "*") |> Shell.copyFiles this.OutDir From 7f0f9fee8d88a10a8d9e506683f8cdf75dcd148e Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 11 Apr 2024 00:24:58 +0200 Subject: [PATCH 07/20] Use local virtualenv --- src/Fable.Build/FableLibrary/Python.fs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Fable.Build/FableLibrary/Python.fs b/src/Fable.Build/FableLibrary/Python.fs index 93dfe403c9..6c77272f54 100644 --- a/src/Fable.Build/FableLibrary/Python.fs +++ b/src/Fable.Build/FableLibrary/Python.fs @@ -33,7 +33,8 @@ type BuildFableLibraryPython() = let linkedFileFolder = Path.Combine(this.BuildDir, "fable_library", "fable-library-ts") - Command.Run("poetry", "install", this.BuildDir) // Maturn needs a virtual environment + Command.Run("poetry", "config virtualenvs.in-project true", this.BuildDir) + Command.Run("poetry", "install", this.BuildDir) // Maturn needs a local virtual environment Command.Run("maturin", "develop", this.BuildDir) Directory.GetFiles(linkedFileFolder, "*") |> Shell.copyFiles this.OutDir From 35412f82bbcc52e572bec6c7e060a9cec0714071 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 12 Apr 2024 08:43:54 +0200 Subject: [PATCH 08/20] Add more integers --- src/Fable.Build/FableLibrary/Python.fs | 2 +- .../fable_library/core/__init__.py | 12 +- .../fable_library/core/_core.pyi | 21 ++ .../fable_library/decimal_.py | 6 +- src/fable-library-py/fable_library/int32.py | 4 +- src/fable-library-py/fable_library/types.py | 10 +- src/fable-library-py/poetry.lock | 2 +- src/fable-library-py/pyproject.toml | 8 + src/fable-library-py/src/lib.rs | 6 + src/fable-library-py/src/types.rs | 316 ++++++++++++++++-- src/fable-library-py/tests/test_types.py | 52 +++ 11 files changed, 399 insertions(+), 40 deletions(-) create mode 100644 src/fable-library-py/tests/test_types.py diff --git a/src/Fable.Build/FableLibrary/Python.fs b/src/Fable.Build/FableLibrary/Python.fs index 6c77272f54..f4571d1d61 100644 --- a/src/Fable.Build/FableLibrary/Python.fs +++ b/src/Fable.Build/FableLibrary/Python.fs @@ -35,7 +35,7 @@ type BuildFableLibraryPython() = Command.Run("poetry", "config virtualenvs.in-project true", this.BuildDir) Command.Run("poetry", "install", this.BuildDir) // Maturn needs a local virtual environment - Command.Run("maturin", "develop", this.BuildDir) + Command.Run("maturin", "develop --release", this.BuildDir) Directory.GetFiles(linkedFileFolder, "*") |> Shell.copyFiles this.OutDir diff --git a/src/fable-library-py/fable_library/core/__init__.py b/src/fable-library-py/fable_library/core/__init__.py index e7cd597308..ae74491cb9 100644 --- a/src/fable-library-py/fable_library/core/__init__.py +++ b/src/fable-library-py/fable_library/core/__init__.py @@ -1,6 +1,14 @@ +from ._core import Int8 as int8 # type: ignore from ._core import Int16 as int16 # type: ignore +from ._core import Int32 as int32 # type: ignore + +# from ._core import Int64 as int64 # type: ignore +from ._core import UInt8 as uint8 # type: ignore from ._core import UInt16 as uint16 # type: ignore -from ._core import sum_as_string # type: ignore +from ._core import UInt32 as uint32 # type: ignore + + +# from ._core import UInt64 as uint64 # type: ignore -__all__ = ["sum_as_string", "uint16", "int16"] +__all__ = ["int8", "uint8", "uint16", "int16", "int32", "uint32", "int64", "uint64"] diff --git a/src/fable-library-py/fable_library/core/_core.pyi b/src/fable-library-py/fable_library/core/_core.pyi index 6db463febf..89f33db434 100644 --- a/src/fable-library-py/fable_library/core/_core.pyi +++ b/src/fable-library-py/fable_library/core/_core.pyi @@ -1,5 +1,26 @@ from typing import final +@final +class Uint8: + def __init__(self, value: int) -> None: ... + def __add__(self, other: Uint8) -> Uint8: ... + def __sub__(self, other: Uint8) -> Uint8: ... + def __mul__(self, other: Uint8) -> Uint8: ... + def __truediv__(self, other: Uint8) -> Uint8: ... + def __mod__(self, other: Uint8) -> Uint8: ... + def __neg__(self) -> Uint8: ... + def __eq__(self, other: Uint8) -> bool: ... + def __ne__(self, other: Uint8) -> bool: ... + def __lt__(self, other: Uint8) -> bool: ... + def __le__(self, other: Uint8) -> bool: ... + def __gt__(self, other: Uint8) -> bool: ... + def __ge__(self, other: Uint8) -> bool: ... + def __int__(self) -> int: ... + def __index__(self) -> int: ... + +class Int8: + def __init__(self, value: int) -> None: ... + @final class UInt16: def __init__(self, value: int) -> None: ... diff --git a/src/fable-library-py/fable_library/decimal_.py b/src/fable-library-py/fable_library/decimal_.py index 76fd5821fe..8c0dc54e9d 100644 --- a/src/fable-library-py/fable_library/decimal_.py +++ b/src/fable-library-py/fable_library/decimal_.py @@ -1,6 +1,6 @@ from decimal import MAX_EMAX, MIN_EMIN, Decimal, getcontext -from .types import FSharpRef, int16, uint16 +from .types import FSharpRef, int8, int16, uint8, uint16 getcontext().prec = 29 @@ -131,7 +131,7 @@ def from_parts(low: int, mid: int, high: int, isNegative: bool, scale: int) -> D value = Decimal((low + mid + high) * sign) if scale: - dscale = Decimal(pow(10, scale)) + dscale = Decimal(pow(10, int(scale))) return value / dscale return value @@ -158,7 +158,7 @@ def try_parse(string: str, def_value: FSharpRef[Decimal]) -> bool: def create(value: int | float | int16 | uint16 | str) -> Decimal: match value: - case int16() | uint16(): + case int8() | uint8() | int16() | uint16(): return Decimal(int(value)) case _: return Decimal(value) diff --git a/src/fable-library-py/fable_library/int32.py b/src/fable-library-py/fable_library/int32.py index a0dd85f21f..7d3747e0ef 100644 --- a/src/fable-library-py/fable_library/int32.py +++ b/src/fable-library-py/fable_library/int32.py @@ -1,9 +1,7 @@ -from typing import Tuple - from .types import FSharpRef -def get_range(unsigned: bool, bitsize: int) -> Tuple[int, int]: +def get_range(unsigned: bool, bitsize: int) -> tuple[int, int]: if bitsize == 8: return (0, 255) if unsigned else (-128, 127) if bitsize == 16: diff --git a/src/fable-library-py/fable_library/types.py b/src/fable-library-py/fable_library/types.py index ca5ed1a268..bc93b0ac30 100644 --- a/src/fable-library-py/fable_library/types.py +++ b/src/fable-library-py/fable_library/types.py @@ -10,7 +10,7 @@ cast, ) -from .core import int16, uint16 +from .core import int8, int16, uint8, uint16 from .util import Array, IComparable, compare @@ -292,10 +292,6 @@ class char(int): __slots__ = () -class int8(int): - __slots__ = () - - class int32(int): __slots__ = () @@ -304,10 +300,6 @@ class int64(int): __slots__ = () -class uint8(int): - __slots__ = () - - class uint32(int): __slots__ = () diff --git a/src/fable-library-py/poetry.lock b/src/fable-library-py/poetry.lock index 123e775d46..0a18f6bbb6 100644 --- a/src/fable-library-py/poetry.lock +++ b/src/fable-library-py/poetry.lock @@ -127,4 +127,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">= 3.10, < 4.0" -content-hash = "29545c25d8b2cfabee687efc7fd0b78f4849548382fd807ac4b543a9545c380b" +content-hash = "7875ebe0604d00664069ed044fcd1ed9ab45087aa360b32b029c2a07715fc4c3" diff --git a/src/fable-library-py/pyproject.toml b/src/fable-library-py/pyproject.toml index 27e3266751..38acecf2e7 100644 --- a/src/fable-library-py/pyproject.toml +++ b/src/fable-library-py/pyproject.toml @@ -15,11 +15,19 @@ python = ">= 3.10, < 4.0" pytest = "^8.1" maturin = "^1.5.0" +[tool.poetry.group.dev.dependencies] +pytest = "^8.1.1" + [tool.maturin] python-source = "fable_library" features = ["pyo3/extension-module"] module-name = "core._core" +[tool.pytest.ini_options] +minversion = "8.0" +pythonpath = "." +testpaths = ["tests"] + [build-system] requires = ["poetry-core>=1.0.0", "maturin>=1.5,<2.0"] build-backend = "maturin" diff --git a/src/fable-library-py/src/lib.rs b/src/fable-library-py/src/lib.rs index 8952ffa89a..7df00a13c9 100644 --- a/src/fable-library-py/src/lib.rs +++ b/src/fable-library-py/src/lib.rs @@ -14,7 +14,13 @@ fn sum_as_string(a: usize, b: usize) -> PyResult { #[pymodule] fn _core(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; + m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + //m.add_class::()?; + //m.add_class::()?; Ok(()) } diff --git a/src/fable-library-py/src/types.rs b/src/fable-library-py/src/types.rs index 628e05c65d..d8f21a693c 100644 --- a/src/fable-library-py/src/types.rs +++ b/src/fable-library-py/src/types.rs @@ -11,7 +11,7 @@ use byteorder::{BigEndian, ByteOrder, LittleEndian}; macro_rules! integer_variant { ($name:ident, $type:ty) => { - #[pyclass] + #[pyclass(module = "fable", frozen)] #[derive(Clone)] pub struct $name($type); @@ -19,14 +19,11 @@ macro_rules! integer_variant { impl $name { #[new] pub fn new(value: &PyAny) -> PyResult { - match value.extract::<$name>() { - Ok(value) => Ok(Self(value.0)), - Err(_) => match value.extract::<$type>() { - Ok(value) => Ok(Self(value)), - Err(_) => Err(PyErr::new::( - "Cannot convert argument to integer", - )), - }, + match value.extract::<$type>() { + Ok(value) => Ok(Self(value)), + Err(_) => Err(PyErr::new::( + "Cannot convert argument to integer", + )), } } @@ -50,16 +47,13 @@ macro_rules! integer_variant { } pub fn __add__(&self, other: &PyAny) -> PyResult<$name> { - let other = match other.extract::<$name>() { - Ok(other) => other.0, - Err(_) => match other.extract::<$type>() { - Ok(other) => other, - Err(_) => { - return Err(PyErr::new::( - "Cannot convert argument to integer", - )) - } - }, + let other = match other.extract::<$type>() { + Ok(other) => other, + Err(_) => { + return Err(PyErr::new::( + "Cannot convert argument to integer", + )) + } }; Ok($name(self.0 + other)) } @@ -102,6 +96,10 @@ macro_rules! integer_variant { Ok($name(self.0 * other)) } + pub fn __rmul__(&self, other: &PyAny) -> PyResult<$name> { + self.__mul__(other) + } + pub fn __truediv__(&self, other: &PyAny) -> PyResult<$name> { let other = other.extract::<$type>()?; Ok($name(self.0 / other)) @@ -134,14 +132,81 @@ macro_rules! integer_variant { } } + pub fn __rshift__(&self, other: &PyAny) -> PyResult<$name> { + let other = other.extract::<$type>()?; + Ok($name(self.0 >> other)) + } + + pub fn __lshift__(&self, other: &PyAny) -> PyResult<$name> { + let other = other.extract::<$type>()?; + Ok($name(self.0 << other)) + } + + pub fn __and__(&self, other: &PyAny) -> PyResult<$name> { + let other = match other.extract::<$name>() { + Ok(other) => Ok(other.0), + Err(_) => match other.extract::<$type>() { + Ok(other) => Ok(other), + Err(_) => Err(PyErr::new::("Cannot compare")), + }, + }; + + match other { + Ok(other) => Ok($name(self.0 & other)), + Err(_) => Err(PyErr::new::("Cannot and")), + } + } + + pub fn __rand__(&self, other: &PyAny) -> PyResult<$name> { + self.__and__(other) + } + + pub fn __or__(&self, other: &PyAny) -> PyResult<$name> { + let other = match other.extract::<$name>() { + Ok(other) => Ok(other.0), + Err(_) => match other.extract::<$type>() { + Ok(other) => Ok(other), + Err(_) => Err(PyErr::new::("Cannot compare")), + }, + }; + + match other { + Ok(other) => Ok($name(self.0 | other)), + Err(_) => Err(PyErr::new::("Cannot or")), + } + } + + pub fn __ror__(&self, other: &PyAny) -> PyResult<$name> { + self.__or__(other) + } + + pub fn __xor__(&self, other: &PyAny) -> PyResult<$name> { + let other = match other.extract::<$name>() { + Ok(other) => Ok(other.0), + Err(_) => match other.extract::<$type>() { + Ok(other) => Ok(other), + Err(_) => Err(PyErr::new::("Cannot compare")), + }, + }; + + match other { + Ok(other) => Ok($name(self.0 ^ other)), + Err(_) => Err(PyErr::new::("Cannot xor")), + } + } + + pub fn __rxor__(&self, other: &PyAny) -> PyResult<$name> { + self.__xor__(other) + } + fn __bool__(&self) -> bool { self.0 != 0 } - pub fn __hash__(&self) -> u64 { + pub fn __hash__(&self) -> $type { let mut hasher = DefaultHasher::new(); self.0.hash(&mut hasher); - hasher.finish() + hasher.finish().try_into().unwrap() } pub fn __int__(&self) -> PyResult<$type> { @@ -165,5 +230,214 @@ macro_rules! integer_variant { }; } +integer_variant!(Int8, i8); +//integer_variant!(UInt8, u8); integer_variant!(Int16, i16); integer_variant!(UInt16, u16); +integer_variant!(Int32, i32); +integer_variant!(UInt32, u32); +//integer_variant!(Int64, i64); +//integer_variant!(UInt64, u64); + +#[pyclass(module = "fable", frozen)] +#[derive(Clone)] +pub struct UInt8(u8); + +#[pymethods] +impl UInt8 { + #[new] + pub fn new(value: &PyAny) -> PyResult { + match value.extract::() { + Ok(value) => Ok(Self(value)), + Err(_) => Err(PyErr::new::( + "Cannot convert argument to integer", + )), + } + } + + pub fn to_bytes(&self, py: Python, length: usize, byteorder: &str) -> PyResult { + let mut bytes = vec![0; length]; + match byteorder { + "little" => LittleEndian::write_uint(&mut bytes, self.0 as u64, length), + "big" => BigEndian::write_uint(&mut bytes, self.0 as u64, length), + _ => { + return Err(PyErr::new::( + "Invalid byteorder", + )) + } + } + Ok(PyBytes::new(py, &bytes).into()) + } + + pub fn __add__(&self, other: &PyAny) -> PyResult { + let other = match other.extract::() { + Ok(other) => other, + Err(_) => match other.extract::() { + Ok(_other) => { + //return Ok(other + (f64.from(self.0))); + return Err(PyErr::new::( + "Cannot convert argument to float", + )); + } + Err(_) => { + return Err(PyErr::new::( + "Cannot convert argument to integer", + )) + } + }, + }; + Ok(UInt8(self.0.wrapping_add(other))) + } + + pub fn __radd__(&self, other: &PyAny) -> PyResult { + self.__add__(other) + } + + pub fn __sub__(&self, other: &PyAny) -> PyResult { + let other = match other.extract::() { + Ok(other) => other, + Err(_) => { + return Err(PyErr::new::( + "Cannot convert argument to integer", + )) + } + }; + Ok(UInt8(self.0.wrapping_sub(other))) + } + + pub fn __rsub__(&self, other: &PyAny) -> PyResult { + self.__sub__(other) + } + + pub fn __mul__(&self, other: &PyAny) -> PyResult { + let other = match other.extract::() { + Ok(other) => other.0, + Err(_) => match other.extract::() { + Ok(other) => other, + Err(_) => { + return Err(PyErr::new::( + "Cannot convert argument to integer", + )) + } + }, + }; + Ok(UInt8(self.0.wrapping_mul(other))) + } + + pub fn __rmul__(&self, other: &PyAny) -> PyResult { + self.__mul__(other) + } + + pub fn __truediv__(&self, other: &PyAny) -> PyResult { + let other = other.extract::()?; + Ok(UInt8(self.0 / other)) + } + + pub fn __richcmp__(&self, other: &PyAny, op: CompareOp) -> PyResult { + let other = match other.extract::() { + Ok(other) => Ok(other), + Err(_) => match other.extract::() { + Ok(other) => Ok(other as u8), + Err(_) => Err(PyErr::new::("Cannot compare")), + }, + }; + + match other { + Ok(other) => match op { + CompareOp::Eq => Ok(self.0 == other), + CompareOp::Ne => Ok(self.0 != other), + CompareOp::Lt => Ok(self.0 < other), + CompareOp::Le => Ok(self.0 <= other), + CompareOp::Gt => Ok(self.0 > other), + CompareOp::Ge => Ok(self.0 >= other), + }, + Err(_) => Ok(false), + } + } + + pub fn __rshift__(&self, other: &PyAny) -> PyResult { + let other = other.extract::()?; + Ok(UInt8(self.0 >> other)) + } + + pub fn __lshift__(&self, other: &PyAny) -> PyResult { + let other = other.extract::()?; + Ok(UInt8(self.0 << other)) + } + + pub fn __and__(&self, other: &PyAny) -> PyResult { + let other = match other.extract::() { + Ok(other) => Ok(other), + Err(_) => Err(PyErr::new::("Cannot compare")), + }; + + match other { + Ok(other) => Ok(UInt8(self.0 & other)), + Err(_) => Err(PyErr::new::("Cannot and")), + } + } + + pub fn __rand__(&self, other: &PyAny) -> PyResult { + self.__and__(other) + } + + pub fn __or__(&self, other: &PyAny) -> PyResult { + let other = match other.extract::() { + Ok(other) => Ok(other), + Err(_) => Err(PyErr::new::("Cannot compare")), + }; + + match other { + Ok(other) => Ok(UInt8(self.0 | other)), + Err(_) => Err(PyErr::new::("Cannot or")), + } + } + + pub fn __ror__(&self, other: &PyAny) -> PyResult { + self.__or__(other) + } + + pub fn __xor__(&self, other: &PyAny) -> PyResult { + let other = match other.extract::() { + Ok(other) => Ok(other), + Err(_) => Err(PyErr::new::("Cannot compare")), + }; + + match other { + Ok(other) => Ok(UInt8(self.0 ^ other)), + Err(_) => Err(PyErr::new::("Cannot xor")), + } + } + + pub fn __rxor__(&self, other: &PyAny) -> PyResult { + self.__xor__(other) + } + + fn __bool__(&self) -> bool { + self.0 != 0 + } + + pub fn __hash__(&self) -> u8 { + let mut hasher = DefaultHasher::new(); + self.0.hash(&mut hasher); + hasher.finish().try_into().unwrap() + } + + pub fn __int__(&self) -> PyResult { + Ok(self.0) + } + + // Special method so that arbitrary objects can be used whenever integers + // are explicitly needed in Python + pub fn __index__(&self) -> PyResult { + Ok(self.0) + } + + pub fn __repr__(&self) -> PyResult { + Ok(self.0.to_string()) + } + + pub fn __str__(&self) -> PyResult { + Ok(self.0.to_string()) + } +} diff --git a/src/fable-library-py/tests/test_types.py b/src/fable-library-py/tests/test_types.py new file mode 100644 index 0000000000..8d753d90ee --- /dev/null +++ b/src/fable-library-py/tests/test_types.py @@ -0,0 +1,52 @@ +from array import array + +from fable_library.core import uint8, uint16 + + +def test_uint8_create() -> None: + assert uint8(0) == 0 + assert uint8(uint8(42)) == 42 + assert uint8(uint16(42)) == 42 + + +def test_uint8_add() -> None: + assert uint8(42) + uint8(42) == 84 + assert uint8(255) + uint8(1) == 0 + assert uint8(255) + 1 == 0 + assert 42 + uint8(42) == 84 # Uses the __radd__ method + assert uint8(42) + 42 == 84 + assert uint8(1) + 1.1 == 2.1 + + +def test_uint8_coerce(): + assert int(uint8(42)) == 42 # Uses the __int__ method + assert array("B", [uint8(42), uint8(42)]) == array("B", [42, 42]) # Uses the __index__ method + + +def test_uint8_rich_compare(): + assert uint8(42) == 42 + assert uint8(42) != 43 + assert uint8(42) < 43 + assert uint8(42) <= 43 + assert uint8(42) <= 42 + assert uint8(42) > 41 + assert uint8(42) >= 41 + assert uint8(42) >= 42 + assert 42 == uint8(42) + assert 43 != uint8(42) + assert 43 > uint8(42) + assert 43 >= uint8(42) + assert 42 < uint8(43) + assert 42 <= uint8(43) + assert 41 < uint8(42) + assert 41 <= uint8(42) + assert 42 >= uint8(42) + assert uint8(42) == uint8(42) + assert uint8(42) != uint8(43) + assert uint8(42) < uint8(43) + assert uint8(42) <= uint8(43) + assert uint8(42) <= uint8(42) + assert uint8(42) > uint8(41) + assert uint8(42) >= uint8(41) + assert uint8(42) >= uint8(42) + assert not (uint8(42) == 43) From 0f797770da1c0cf8016b838a9c6dcc830d552cb8 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 14 Apr 2024 07:12:25 +0200 Subject: [PATCH 09/20] Upgrade to pyo3 21 --- src/fable-library-py/Cargo.lock | 24 ++--- src/fable-library-py/Cargo.toml | 2 +- src/fable-library-py/src/lib.rs | 2 +- src/fable-library-py/src/types.rs | 123 +++++++++++++++-------- src/fable-library-py/tests/test_types.py | 23 ++++- 5 files changed, 117 insertions(+), 57 deletions(-) diff --git a/src/fable-library-py/Cargo.lock b/src/fable-library-py/Cargo.lock index 663c1c55ae..c05247ced2 100644 --- a/src/fable-library-py/Cargo.lock +++ b/src/fable-library-py/Cargo.lock @@ -117,9 +117,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.20.3" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" +checksum = "a7a8b1990bd018761768d5e608a13df8bd1ac5f678456e0f301bb93e5f3ea16b" dependencies = [ "cfg-if", "indoc", @@ -135,9 +135,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.20.3" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7" +checksum = "650dca34d463b6cdbdb02b1d71bfd6eb6b6816afc708faebb3bac1380ff4aef7" dependencies = [ "once_cell", "target-lexicon", @@ -145,9 +145,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.20.3" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa" +checksum = "09a7da8fc04a8a2084909b59f29e1b8474decac98b951d77b80b26dc45f046ad" dependencies = [ "libc", "pyo3-build-config", @@ -155,9 +155,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.20.3" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158" +checksum = "4b8a199fce11ebb28e3569387228836ea98110e43a804a530a9fd83ade36d513" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -167,9 +167,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.20.3" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" +checksum = "93fbbfd7eb553d10036513cb122b888dcd362a945a00b06c165f2ab480d4cc3b" dependencies = [ "heck", "proc-macro2", @@ -180,9 +180,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] diff --git a/src/fable-library-py/Cargo.toml b/src/fable-library-py/Cargo.toml index ed86f891d3..e8936d6f81 100644 --- a/src/fable-library-py/Cargo.toml +++ b/src/fable-library-py/Cargo.toml @@ -12,7 +12,7 @@ name = "_core" crate-type = ["cdylib"] [dependencies] -pyo3 = "0.20.0" +pyo3 = "0.21.0" byteorder = "1.5.0" [features] diff --git a/src/fable-library-py/src/lib.rs b/src/fable-library-py/src/lib.rs index 7df00a13c9..22d6201622 100644 --- a/src/fable-library-py/src/lib.rs +++ b/src/fable-library-py/src/lib.rs @@ -12,7 +12,7 @@ fn sum_as_string(a: usize, b: usize) -> PyResult { /// A Python module implemented in Rust. #[pymodule] -fn _core(_py: Python, m: &PyModule) -> PyResult<()> { +fn _core(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; m.add_class::()?; m.add_class::()?; diff --git a/src/fable-library-py/src/types.rs b/src/fable-library-py/src/types.rs index d8f21a693c..01e498c541 100644 --- a/src/fable-library-py/src/types.rs +++ b/src/fable-library-py/src/types.rs @@ -18,7 +18,7 @@ macro_rules! integer_variant { #[pymethods] impl $name { #[new] - pub fn new(value: &PyAny) -> PyResult { + pub fn new(value: &Bound<'_, PyAny>) -> PyResult { match value.extract::<$type>() { Ok(value) => Ok(Self(value)), Err(_) => Err(PyErr::new::( @@ -43,10 +43,10 @@ macro_rules! integer_variant { )) } } - Ok(PyBytes::new(py, &bytes).into()) + Ok(PyBytes::new_bound(py, &bytes).into()) } - pub fn __add__(&self, other: &PyAny) -> PyResult<$name> { + pub fn __add__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { let other = match other.extract::<$type>() { Ok(other) => other, Err(_) => { @@ -58,11 +58,11 @@ macro_rules! integer_variant { Ok($name(self.0 + other)) } - pub fn __radd__(&self, other: &PyAny) -> PyResult<$name> { + pub fn __radd__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { self.__add__(other) } - pub fn __sub__(&self, other: &PyAny) -> PyResult<$name> { + pub fn __sub__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { let other = match other.extract::<$name>() { Ok(other) => other.0, Err(_) => match other.extract::<$type>() { @@ -77,11 +77,11 @@ macro_rules! integer_variant { Ok($name(self.0 - other)) } - pub fn __rsub__(&self, other: &PyAny) -> PyResult<$name> { + pub fn __rsub__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { self.__sub__(other) } - pub fn __mul__(&self, other: &PyAny) -> PyResult<$name> { + pub fn __mul__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { let other = match other.extract::<$name>() { Ok(other) => other.0, Err(_) => match other.extract::<$type>() { @@ -96,16 +96,16 @@ macro_rules! integer_variant { Ok($name(self.0 * other)) } - pub fn __rmul__(&self, other: &PyAny) -> PyResult<$name> { + pub fn __rmul__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { self.__mul__(other) } - pub fn __truediv__(&self, other: &PyAny) -> PyResult<$name> { + pub fn __truediv__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { let other = other.extract::<$type>()?; Ok($name(self.0 / other)) } - pub fn __richcmp__(&self, other: &PyAny, op: CompareOp) -> PyResult { + pub fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult { let other = match other.extract::<$name>() { Ok(other) => Ok(other.0), Err(_) => match other.extract::<$type>() { @@ -132,17 +132,15 @@ macro_rules! integer_variant { } } - pub fn __rshift__(&self, other: &PyAny) -> PyResult<$name> { - let other = other.extract::<$type>()?; + pub fn __rshift__(&self, other: usize) -> PyResult<$name> { Ok($name(self.0 >> other)) } - pub fn __lshift__(&self, other: &PyAny) -> PyResult<$name> { - let other = other.extract::<$type>()?; + pub fn __lshift__(&self, other: usize) -> PyResult<$name> { Ok($name(self.0 << other)) } - pub fn __and__(&self, other: &PyAny) -> PyResult<$name> { + pub fn __and__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { let other = match other.extract::<$name>() { Ok(other) => Ok(other.0), Err(_) => match other.extract::<$type>() { @@ -157,11 +155,11 @@ macro_rules! integer_variant { } } - pub fn __rand__(&self, other: &PyAny) -> PyResult<$name> { + pub fn __rand__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { self.__and__(other) } - pub fn __or__(&self, other: &PyAny) -> PyResult<$name> { + pub fn __or__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { let other = match other.extract::<$name>() { Ok(other) => Ok(other.0), Err(_) => match other.extract::<$type>() { @@ -176,11 +174,11 @@ macro_rules! integer_variant { } } - pub fn __ror__(&self, other: &PyAny) -> PyResult<$name> { + pub fn __ror__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { self.__or__(other) } - pub fn __xor__(&self, other: &PyAny) -> PyResult<$name> { + pub fn __xor__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { let other = match other.extract::<$name>() { Ok(other) => Ok(other.0), Err(_) => match other.extract::<$type>() { @@ -195,7 +193,7 @@ macro_rules! integer_variant { } } - pub fn __rxor__(&self, other: &PyAny) -> PyResult<$name> { + pub fn __rxor__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { self.__xor__(other) } @@ -246,7 +244,7 @@ pub struct UInt8(u8); #[pymethods] impl UInt8 { #[new] - pub fn new(value: &PyAny) -> PyResult { + pub fn new(value: &Bound<'_, PyAny>) -> PyResult { match value.extract::() { Ok(value) => Ok(Self(value)), Err(_) => Err(PyErr::new::( @@ -266,10 +264,10 @@ impl UInt8 { )) } } - Ok(PyBytes::new(py, &bytes).into()) + Ok(PyBytes::new_bound(py, &bytes).into()) } - pub fn __add__(&self, other: &PyAny) -> PyResult { + pub fn __add__(&self, other: &Bound<'_, PyAny>) -> PyResult { let other = match other.extract::() { Ok(other) => other, Err(_) => match other.extract::() { @@ -289,11 +287,11 @@ impl UInt8 { Ok(UInt8(self.0.wrapping_add(other))) } - pub fn __radd__(&self, other: &PyAny) -> PyResult { + pub fn __radd__(&self, other: &Bound<'_, PyAny>) -> PyResult { self.__add__(other) } - pub fn __sub__(&self, other: &PyAny) -> PyResult { + pub fn __sub__(&self, other: &Bound<'_, PyAny>) -> PyResult { let other = match other.extract::() { Ok(other) => other, Err(_) => { @@ -305,11 +303,11 @@ impl UInt8 { Ok(UInt8(self.0.wrapping_sub(other))) } - pub fn __rsub__(&self, other: &PyAny) -> PyResult { + pub fn __rsub__(&self, other: &Bound<'_, PyAny>) -> PyResult { self.__sub__(other) } - pub fn __mul__(&self, other: &PyAny) -> PyResult { + pub fn __mul__(&self, other: &Bound<'_, PyAny>) -> PyResult { let other = match other.extract::() { Ok(other) => other.0, Err(_) => match other.extract::() { @@ -324,16 +322,16 @@ impl UInt8 { Ok(UInt8(self.0.wrapping_mul(other))) } - pub fn __rmul__(&self, other: &PyAny) -> PyResult { + pub fn __rmul__(&self, other: &Bound<'_, PyAny>) -> PyResult { self.__mul__(other) } - pub fn __truediv__(&self, other: &PyAny) -> PyResult { + pub fn __truediv__(&self, other: &Bound<'_, PyAny>) -> PyResult { let other = other.extract::()?; Ok(UInt8(self.0 / other)) } - pub fn __richcmp__(&self, other: &PyAny, op: CompareOp) -> PyResult { + pub fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult { let other = match other.extract::() { Ok(other) => Ok(other), Err(_) => match other.extract::() { @@ -355,17 +353,58 @@ impl UInt8 { } } - pub fn __rshift__(&self, other: &PyAny) -> PyResult { - let other = other.extract::()?; - Ok(UInt8(self.0 >> other)) + pub fn __rshift__(&self, other: u32) -> PyResult { + Ok(UInt8(self.0.wrapping_shr(other))) } - pub fn __lshift__(&self, other: &PyAny) -> PyResult { - let other = other.extract::()?; - Ok(UInt8(self.0 << other)) + // Return a python object not u8. First try to extract for a Python integer, then for a Rust u8 + pub fn __rrshift__<'py>( + &self, + py: Python<'py>, + other: &'py Bound<'py, PyAny>, + ) -> PyResult> { + let result = match other.extract::() { + Ok(other) => { + let shifted = other.0.wrapping_shr(self.0.into()); + Ok(UInt8(shifted).into_py(py).into_bound(py)) + } + Err(_) => match other.extract::() { + Ok(other) => { + let shifted = other.wrapping_shr(self.0.into()); + Ok(Int32(shifted).into_py(py).into_bound(py)) + } + Err(_) => Err(PyErr::new::("Cannot shift")), + }, + }; + result + } + + pub fn __lshift__(&self, other: u32) -> PyResult { + Ok(UInt8(self.0.rotate_left(other))) + } + + pub fn __rlshift__<'py>( + &self, + py: Python<'py>, + other: &'py Bound<'py, PyAny>, + ) -> PyResult> { + let result = match other.extract::() { + Ok(other) => { + let shifted = other.0.wrapping_shl(self.0.into()); + Ok(UInt8(shifted).into_py(py).into_bound(py)) + } + Err(_) => match other.extract::() { + Ok(other) => { + let shifted = other.wrapping_shl(self.0.into()); + Ok(Int32(shifted).into_py(py).into_bound(py)) + } + Err(_) => Err(PyErr::new::("Cannot shift")), + }, + }; + result } - pub fn __and__(&self, other: &PyAny) -> PyResult { + pub fn __and__(&self, other: &Bound<'_, PyAny>) -> PyResult { let other = match other.extract::() { Ok(other) => Ok(other), Err(_) => Err(PyErr::new::("Cannot compare")), @@ -377,11 +416,11 @@ impl UInt8 { } } - pub fn __rand__(&self, other: &PyAny) -> PyResult { + pub fn __rand__(&self, other: &Bound<'_, PyAny>) -> PyResult { self.__and__(other) } - pub fn __or__(&self, other: &PyAny) -> PyResult { + pub fn __or__(&self, other: &Bound<'_, PyAny>) -> PyResult { let other = match other.extract::() { Ok(other) => Ok(other), Err(_) => Err(PyErr::new::("Cannot compare")), @@ -393,11 +432,11 @@ impl UInt8 { } } - pub fn __ror__(&self, other: &PyAny) -> PyResult { + pub fn __ror__(&self, other: &Bound<'_, PyAny>) -> PyResult { self.__or__(other) } - pub fn __xor__(&self, other: &PyAny) -> PyResult { + pub fn __xor__(&self, other: &Bound<'_, PyAny>) -> PyResult { let other = match other.extract::() { Ok(other) => Ok(other), Err(_) => Err(PyErr::new::("Cannot compare")), @@ -409,7 +448,7 @@ impl UInt8 { } } - pub fn __rxor__(&self, other: &PyAny) -> PyResult { + pub fn __rxor__(&self, other: &Bound<'_, PyAny>) -> PyResult { self.__xor__(other) } diff --git a/src/fable-library-py/tests/test_types.py b/src/fable-library-py/tests/test_types.py index 8d753d90ee..e45c36b1fd 100644 --- a/src/fable-library-py/tests/test_types.py +++ b/src/fable-library-py/tests/test_types.py @@ -15,7 +15,7 @@ def test_uint8_add() -> None: assert uint8(255) + 1 == 0 assert 42 + uint8(42) == 84 # Uses the __radd__ method assert uint8(42) + 42 == 84 - assert uint8(1) + 1.1 == 2.1 + # assert uint8(1) + 1.1 == 2.1 def test_uint8_coerce(): @@ -50,3 +50,24 @@ def test_uint8_rich_compare(): assert uint8(42) >= uint8(41) assert uint8(42) >= uint8(42) assert not (uint8(42) == 43) + + +def test_uin8_rsift(): + assert uint8(2) >> 1 == 1 + assert uint8(1) >> 1 == 0 + assert uint8(1) >> 8 == 1 + assert uint8(1) >> 9 == 0 + assert 2 >> uint8(1) == 1 + assert 1 >> uint8(1) == 0 + assert 1 >> uint8(8) == 0 + assert 1 >> uint8(9) == 0 + + +def test_uint8_lshift(): + assert uint8(1) << 1 == 2 + assert uint8(1) << 8 == 1 + assert uint8(1) << 9 == 2 + assert 1 << uint8(1) == 2 + print(1 << uint8(8)) + assert 1 << uint8(8) == 256 + assert 1 << uint8(9) == 512 From 4c654ee689f48c997c1a9099e240a01d4cae9782 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 17 Apr 2024 08:38:49 +0200 Subject: [PATCH 10/20] Use new integers --- src/Fable.Transforms/Python/Fable2Python.fs | 52 ++-- src/Fable.Transforms/Python/Replacements.fs | 15 +- .../fable_library/core/__init__.py | 37 ++- .../fable_library/core/_core.pyi | 62 +++-- .../fable_library/decimal_.py | 6 +- src/fable-library-py/fable_library/string_.py | 62 ++--- .../fable_library/time_span.py | 12 +- src/fable-library-py/fable_library/types.py | 14 +- .../{fable_library => }/py.typed | 0 src/fable-library-py/src/types.rs | 253 +++++++++++------- src/fable-library-py/tests/test_types.py | 188 ++++++++----- 11 files changed, 412 insertions(+), 289 deletions(-) rename src/fable-library-py/{fable_library => }/py.typed (100%) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index f4b94fd776..2e8695584e 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -760,8 +760,8 @@ module Annotation = let numberInfo kind = let name = match kind with - | Int8 -> "int8" - | UInt8 -> "uint8" + | Int8 -> "sbyte" + | UInt8 -> "byte" | Int16 -> "int16" | UInt16 -> "uint16" | UInt32 -> "uint32" @@ -1662,12 +1662,16 @@ module Util = libCall com ctx None "util" "to_enumerable" [ xs ], stmts | _ -> com.TransformAsExpr(ctx, e) + | Fable.Number(Float32, _) + | Fable.Number(Float64, _) -> + let cons = libValue com ctx "types" "float32" + let value, stmts = com.TransformAsExpr(ctx, e) + Expression.call (cons, [ value ], ?loc = None), stmts | _ -> com.TransformAsExpr(ctx, e) let transformCurry (com: IPythonCompiler) (ctx: Context) expr arity : Expression * Statement list = com.TransformAsExpr(ctx, Replacements.Api.curryExprAtRuntime com arity expr) - let makeInteger (com: IPythonCompiler) (ctx: Context) r _t intName (x: obj) = let cons = libValue com ctx "types" intName let value = Expression.intConstant (x, ?loc = r) @@ -1678,7 +1682,6 @@ module Util = let value = Expression.floatConstant (x, ?loc = r) Expression.call (cons, [ value ], ?loc = r), [] - let transformValue (com: IPythonCompiler) (ctx: Context) r value : Expression * Statement list = match value with | Fable.BaseValue(None, _) -> Expression.identifier "super()", [] @@ -1714,8 +1717,8 @@ module Util = | Decimal, (:? decimal as x) -> Py.Replacements.makeDecimal com r value.Type x |> transformAsExpr com ctx | Int64, (:? int64 as x) -> makeInteger com ctx r value.Type "int64" x | UInt64, (:? uint64 as x) -> makeInteger com ctx r value.Type "uint64" x - | Int8, (:? int8 as x) -> makeInteger com ctx r value.Type "int8" x - | UInt8, (:? uint8 as x) -> makeInteger com ctx r value.Type "uint8" x + | Int8, (:? int8 as x) -> makeInteger com ctx r value.Type "sbyte" x + | UInt8, (:? uint8 as x) -> makeInteger com ctx r value.Type "byte" x | Int16, (:? int16 as x) -> makeInteger com ctx r value.Type "int16" x | UInt16, (:? uint16 as x) -> makeInteger com ctx r value.Type "uint16" x | Int32, (:? int32 as x) -> Expression.intConstant (x, ?loc = r), [] @@ -2023,26 +2026,6 @@ module Util = let transformOperation com ctx range opKind tags : Expression * Statement list = match opKind with - // | Fable.Unary (UnaryVoid, TransformExpr com ctx (expr, stmts)) -> Expression.none, stmts - // | Fable.Unary (UnaryTypeof, TransformExpr com ctx (expr, stmts)) -> - // let func = Expression.name ("type") - // let args = [ expr ] - // Expression.call (func, args), stmts - - // Transform `~(~(a/b))` to `a // b` - | Fable.Unary(UnaryOperator.UnaryNotBitwise, - Fable.Operation( - kind = Fable.Unary(UnaryOperator.UnaryNotBitwise, - Fable.Operation( - kind = Fable.Binary(BinaryOperator.BinaryDivide, - TransformExpr com ctx (left, stmts), - TransformExpr com ctx (right, stmts')))))) -> - Expression.binOp (left, FloorDiv, right), stmts @ stmts' - | Fable.Unary(UnaryOperator.UnaryNotBitwise, - Fable.Operation( - kind = Fable.Unary(UnaryOperator.UnaryNotBitwise, TransformExpr com ctx (left, stmts)))) -> - let name = Expression.name "int" - Expression.call (name, [ left ]), stmts | Fable.Unary(op, TransformExpr com ctx (expr, stmts)) -> Expression.unaryOp (op, expr, ?loc = range), stmts // | Fable.Binary (BinaryInstanceOf, TransformExpr com ctx (left, stmts), TransformExpr com ctx (right, stmts')) -> @@ -2050,7 +2033,10 @@ module Util = // let args = [ left; right ] // Expression.call (func, args), stmts' @ stmts - | Fable.Binary(op, TransformExpr com ctx (left, stmts), TransformExpr com ctx (right, stmts')) -> + | Fable.Binary(op, TransformExpr com ctx (left, stmts), right: Fable.Expr) -> + let typ = right.Type + let right, stmts' = com.TransformAsExpr(ctx, right) + let compare op = Expression.compare (left, [ op ], [ right ], ?loc = range), stmts @ stmts' @@ -2101,6 +2087,14 @@ module Util = | BinaryLessOrEqual, _ -> compare LtE | BinaryGreater, _ -> compare Gt | BinaryGreaterOrEqual, _ -> compare GtE + | BinaryDivide, _ -> + // For integer division, we need to use the // operator + match typ with + | Fable.Number(Int32, _) + | Fable.Number(Int64, _) + | Fable.Number(UInt32, _) + | Fable.Number(UInt64, _) -> Expression.binOp (left, FloorDiv, right, ?loc = range), stmts @ stmts' + | _ -> Expression.binOp (left, op, right, ?loc = range), stmts @ stmts' | _ -> Expression.binOp (left, op, right, ?loc = range), stmts @ stmts' | Fable.Logical(op, TransformExpr com ctx (left, stmts), TransformExpr com ctx (right, stmts')) -> @@ -2942,13 +2936,9 @@ module Util = // printfn "transformAsExpr: %A" expr match expr with | Fable.Unresolved(_, _, r) -> addErrorAndReturnNull com r "Unexpected unresolved expression", [] - | Fable.TypeCast(e, t) -> transformCast com ctx t e - | Fable.Value(kind, r) -> transformValue com ctx r kind - | Fable.IdentExpr id -> identAsExpr com ctx id, [] - | Fable.Import({ Selector = selector Path = path diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index a5b4705fa5..f872cef4f1 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -68,7 +68,7 @@ let coreModFor = let makeDecimal com r t (x: decimal) = let str = x.ToString(System.Globalization.CultureInfo.InvariantCulture) - Helper.LibCall(com, "decimal", "Decimal", t, [ makeStrConst str ], isConstructor = true, ?loc = r) + Helper.LibCall(com, "decimal_", "create", t, [ makeStrConst str ], isConstructor = true, ?loc = r) let makeDecimalFromExpr com r t (e: Expr) = match e with @@ -239,9 +239,7 @@ let needToCast fromKind toKind = /// Conversions to floating point let toFloat com (ctx: Context) r targetType (args: Expr list) : Expr = match args.Head.Type with - | Char -> - //Helper.InstanceCall(args.Head, "charCodeAt", Int32.Number, [ makeIntConst 0 ]) - Helper.LibCall(com, "char", "char_code_at", targetType, [ args.Head; makeIntConst 0 ]) + | Char -> Helper.LibCall(com, "char", "char_code_at", targetType, [ args.Head; makeIntConst 0 ]) | String -> Helper.LibCall(com, "double", "parse", targetType, args) | Number(kind, _) -> match kind with @@ -276,11 +274,6 @@ let toDecimal com (ctx: Context) r targetType (args: Expr list) : Expr = TypeCast(args.Head, targetType) -// Apparently ~~ is faster than Math.floor (see https://coderwall.com/p/9b6ksa/is-faster-than-math-floor) -let fastIntFloor expr = - let inner = makeUnOp None Any expr UnaryNotBitwise - makeUnOp None (Int32.Number) inner UnaryNotBitwise - let stringToInt com (ctx: Context) r targetType (args: Expr list) : Expr = let kind = match targetType with @@ -344,7 +337,7 @@ let toInt com (ctx: Context) r targetType (args: Expr list) = match typeTo with | Int8 -> emitExpr None Int8.Number [ arg ] "(int($0) + 0x80 & 0xFF) - 0x80" | Int16 -> emitExpr None Int16.Number [ arg ] "(int($0) + 0x8000 & 0xFFFF) - 0x8000" - | Int32 -> fastIntFloor arg + | Int32 -> emitExpr None Int32.Number [ arg ] "int($0)" | UInt8 -> emitExpr None UInt8.Number [ arg ] "int($0+0x100 if $0 < 0 else $0) & 0xFF" | UInt16 -> emitExpr None UInt16.Number [ arg ] "int($0+0x10000 if $0 < 0 else $0) & 0xFFFF" | UInt32 -> emitExpr None UInt32.Number [ arg ] "int($0+0x100000000 if $0 < 0 else $0)" @@ -435,7 +428,7 @@ let applyOp (com: ICompiler) (ctx: Context) r t opName (args: Expr list) = match argTypes with // Floor result of integer divisions (see #172) | Number((Int8 | Int16 | Int32 | UInt8 | UInt16 | UInt32 | Int64 | UInt64 | BigInt), _) :: _ -> - binOp BinaryDivide left right |> fastIntFloor + binOp BinaryDivide left right |> truncateUnsigned | _ -> Helper.LibCall(com, "double", "divide", t, [ left; right ], argTypes, ?loc = r) | Operators.modulus, [ left; right ] -> binOp BinaryModulus left right | Operators.leftShift, [ left; right ] -> binOp BinaryShiftLeft left right |> truncateUnsigned // See #1530 diff --git a/src/fable-library-py/fable_library/core/__init__.py b/src/fable-library-py/fable_library/core/__init__.py index ae74491cb9..668b8d03ca 100644 --- a/src/fable-library-py/fable_library/core/__init__.py +++ b/src/fable-library-py/fable_library/core/__init__.py @@ -1,14 +1,37 @@ -from ._core import Int8 as int8 # type: ignore -from ._core import Int16 as int16 # type: ignore -from ._core import Int32 as int32 # type: ignore +from typing import TypeAlias # from ._core import Int64 as int64 # type: ignore -from ._core import UInt8 as uint8 # type: ignore -from ._core import UInt16 as uint16 # type: ignore -from ._core import UInt32 as uint32 # type: ignore +from ._core import ( + Int8, # type: ignore + Int16, # type: ignore + Int32, # type: ignore + UInt8, # type: ignore + UInt16, # type: ignore + UInt32, # type: ignore +) # from ._core import UInt64 as uint64 # type: ignore +# Type aliases for the built-in types +byte: TypeAlias = UInt8 +sbyte: TypeAlias = Int8 +int16: TypeAlias = Int16 +uint16: TypeAlias = UInt16 +int32: TypeAlias = Int32 +uint32: TypeAlias = UInt32 -__all__ = ["int8", "uint8", "uint16", "int16", "int32", "uint32", "int64", "uint64"] +__all__ = [ + "sbyte", + "byte", + "uint16", + "int16", + "int32", + "uint32", + "Int8", + "UInt8", + "Int16", + "UInt16", + "Int32", + "UInt32", +] diff --git a/src/fable-library-py/fable_library/core/_core.pyi b/src/fable-library-py/fable_library/core/_core.pyi index 89f33db434..cbee1abded 100644 --- a/src/fable-library-py/fable_library/core/_core.pyi +++ b/src/fable-library-py/fable_library/core/_core.pyi @@ -1,32 +1,48 @@ -from typing import final +from typing import Any, Self, final -@final -class Uint8: - def __init__(self, value: int) -> None: ... - def __add__(self, other: Uint8) -> Uint8: ... - def __sub__(self, other: Uint8) -> Uint8: ... - def __mul__(self, other: Uint8) -> Uint8: ... - def __truediv__(self, other: Uint8) -> Uint8: ... - def __mod__(self, other: Uint8) -> Uint8: ... - def __neg__(self) -> Uint8: ... - def __eq__(self, other: Uint8) -> bool: ... - def __ne__(self, other: Uint8) -> bool: ... - def __lt__(self, other: Uint8) -> bool: ... - def __le__(self, other: Uint8) -> bool: ... - def __gt__(self, other: Uint8) -> bool: ... - def __ge__(self, other: Uint8) -> bool: ... +class Numeric: + def __init__(self, value: int | Numeric) -> None: ... + def __add__(self, other: Any) -> Self: ... + def __sub__(self, other: Any) -> Self: ... + def __mul__(self, other: Any) -> Self: ... + def __truediv__(self, other: int | Numeric) -> Self: ... + def __mod__(self, other: Any) -> Self: ... + def __neg__(self) -> Self: ... + def __eq__(self, other: Any) -> bool: ... + def __ne__(self, other: Any) -> bool: ... + def __lt__(self, other: Any) -> bool: ... + def __le__(self, other: Any) -> bool: ... + def __gt__(self, other: Any) -> bool: ... + def __ge__(self, other: Any) -> bool: ... def __int__(self) -> int: ... def __index__(self) -> int: ... + def __hash__(self) -> int: ... + def __lshift__(self, other: int | Numeric) -> Self: ... + def __rshift__(self, other: int | Numeric) -> Self: ... + def __rlshift__(self, other: int) -> Any: ... + def __rrshift__(self, other: int) -> Any: ... + def __and__(self, other: int | Numeric) -> Self: ... + def __or__(self, other: int | Numeric) -> Self: ... + def __xor__(self, other: int | Numeric) -> Self: ... + def __invert__(self) -> Self: ... + def __radd__(self, other: int | float) -> Any: ... + def __rsub__(self, other: int | float) -> Any: ... + def __rmul__(self, other: int | float) -> Any: ... + +@final +class UInt8(Numeric): ... -class Int8: - def __init__(self, value: int) -> None: ... +@final +class Int8(Numeric): ... @final -class UInt16: - def __init__(self, value: int) -> None: ... +class UInt16(Numeric): ... @final -class Int16: - def __init__(self, value: int) -> None: ... +class Int16(Numeric): ... -def sum_as_string(a: int, b: int) -> int: ... +@final +class UInt32(Numeric): ... + +@final +class Int32(Numeric): ... diff --git a/src/fable-library-py/fable_library/decimal_.py b/src/fable-library-py/fable_library/decimal_.py index 8c0dc54e9d..bf578791d2 100644 --- a/src/fable-library-py/fable_library/decimal_.py +++ b/src/fable-library-py/fable_library/decimal_.py @@ -1,6 +1,6 @@ from decimal import MAX_EMAX, MIN_EMIN, Decimal, getcontext -from .types import FSharpRef, int8, int16, uint8, uint16 +from .types import FSharpRef, byte, int16, int32, sbyte, uint16, uint32 getcontext().prec = 29 @@ -129,7 +129,7 @@ def from_parts(low: int, mid: int, high: int, isNegative: bool, scale: int) -> D else: high = high << 64 - value = Decimal((low + mid + high) * sign) + value = Decimal(int(low + mid + high) * sign) if scale: dscale = Decimal(pow(10, int(scale))) return value / dscale @@ -158,7 +158,7 @@ def try_parse(string: str, def_value: FSharpRef[Decimal]) -> bool: def create(value: int | float | int16 | uint16 | str) -> Decimal: match value: - case int8() | uint8() | int16() | uint16(): + case sbyte() | byte() | int16() | uint16() | int32() | uint32(): return Decimal(int(value)) case _: return Decimal(value) diff --git a/src/fable-library-py/fable_library/string_.py b/src/fable-library-py/fable_library/string_.py index 4b47291565..124f3f7de5 100644 --- a/src/fable-library-py/fable_library/string_.py +++ b/src/fable-library-py/fable_library/string_.py @@ -19,7 +19,7 @@ from .date import to_string as date_to_string from .numeric import multiply, to_exponential, to_fixed, to_hex, to_precision from .reg_exp import escape -from .types import to_string +from .types import byte, int16, int32, int64, sbyte, to_string, uint16, uint32, uint64 T = TypeVar("T") @@ -81,35 +81,36 @@ def format_replacement(rep: Any, flags: Any, padLength: Any, precision: Any, for flags = flags or "" format = format or "" - if isinstance(rep, int | float): - if format not in ["x", "X"]: - if rep < 0: - rep = rep * -1 - sign = "-" - else: - if flags.find(" ") >= 0: - sign = " " - elif flags.find("+") >= 0: - sign = "+" - - if format == "x": - rep = to_hex(cast(int, rep)) - elif format == "X": - rep = to_hex(cast(int, rep)).upper() - if format in ("f", "F"): - precision = int(precision) if precision is not None else 6 - rep = to_fixed(rep, precision) - elif format in ("g", "G"): - rep = to_precision(rep, precision) if precision is not None else to_precision(rep) - elif format in ("e", "E"): - rep = to_exponential(rep, precision) if precision is not None else to_exponential(rep) - else: # AOid - rep = to_string(rep) + match rep: + case int() | float() | byte() | uint16() | uint32() | uint64() | sbyte() | int16() | int32() | int64(): + if format not in ["x", "X"]: + if rep < 0: + rep = rep * -1 + sign = "-" + else: + if flags.find(" ") >= 0: + sign = " " + elif flags.find("+") >= 0: + sign = "+" + + if format == "x": + rep = to_hex(cast(int, rep)) + elif format == "X": + rep = to_hex(cast(int, rep)).upper() + if format in ("f", "F"): + precision = int(precision) if precision is not None else 6 + rep = to_fixed(rep, precision) + elif format in ("g", "G"): + rep = to_precision(rep, precision) if precision is not None else to_precision(rep) + elif format in ("e", "E"): + rep = to_exponential(rep, precision) if precision is not None else to_exponential(rep) + else: # AOid + rep = to_string(rep) - elif isinstance(rep, datetime): - rep = date_to_string(rep) - else: - rep = to_string(rep) + case datetime(): + rep = date_to_string(rep) + case _: + rep = to_string(rep) if padLength is not None: padLength = int(padLength) @@ -478,8 +479,7 @@ def compare(string1: str, string2: str, /) -> int: @overload -def compare(string1: str, string2: str, ignore_case: bool, culture: StringComparison, /) -> int: - ... +def compare(string1: str, string2: str, ignore_case: bool, culture: StringComparison, /) -> int: ... def compare(*args: Any) -> int: diff --git a/src/fable-library-py/fable_library/time_span.py b/src/fable-library-py/fable_library/time_span.py index b7cea2b812..c6787e98eb 100644 --- a/src/fable-library-py/fable_library/time_span.py +++ b/src/fable-library-py/fable_library/time_span.py @@ -37,12 +37,12 @@ def create( pass return TimeSpan( - days * 864000000000 - + (hours or 0) * 36000000000 - + (minutes or 0) * 600000000 - + (seconds or 0) * 10000000 - + (milliseconds or 0) * 10000 - + (microseconds or 0) * 10 + float(days) * 864000000000 + + float(hours or 0) * 36000000000 + + float(minutes or 0) * 600000000 + + float(seconds or 0) * 10000000 + + float(milliseconds or 0) * 10000 + + float(microseconds or 0) * 10 ) diff --git a/src/fable-library-py/fable_library/types.py b/src/fable-library-py/fable_library/types.py index bc93b0ac30..2e4762b985 100644 --- a/src/fable-library-py/fable_library/types.py +++ b/src/fable-library-py/fable_library/types.py @@ -10,7 +10,7 @@ cast, ) -from .core import int8, int16, uint8, uint16 +from .core import byte, int16, int32, sbyte, uint16, uint32 from .util import Array, IComparable, compare @@ -292,18 +292,10 @@ class char(int): __slots__ = () -class int32(int): - __slots__ = () - - class int64(int): __slots__ = () -class uint32(int): - __slots__ = () - - class uint64(int): __slots__ = () @@ -356,8 +348,8 @@ def is_exception(x: Any): "Array", "is_exception", "char", - "int8", - "uint8", + "sbyte", + "byte", "int16", "uint16", "int32", diff --git a/src/fable-library-py/fable_library/py.typed b/src/fable-library-py/py.typed similarity index 100% rename from src/fable-library-py/fable_library/py.typed rename to src/fable-library-py/py.typed diff --git a/src/fable-library-py/src/types.rs b/src/fable-library-py/src/types.rs index 01e498c541..c7ef5406c7 100644 --- a/src/fable-library-py/src/types.rs +++ b/src/fable-library-py/src/types.rs @@ -10,7 +10,7 @@ use pyo3::types::PyBytes; use byteorder::{BigEndian, ByteOrder, LittleEndian}; macro_rules! integer_variant { - ($name:ident, $type:ty) => { + ($name:ident, $type:ty, $extract_type:ty, $mask:expr) => { #[pyclass(module = "fable", frozen)] #[derive(Clone)] pub struct $name($type); @@ -19,14 +19,21 @@ macro_rules! integer_variant { impl $name { #[new] pub fn new(value: &Bound<'_, PyAny>) -> PyResult { - match value.extract::<$type>() { - Ok(value) => Ok(Self(value)), - Err(_) => Err(PyErr::new::( - "Cannot convert argument to integer", - )), + let value = value.call_method1("__and__", ($mask,))?; + let value: PyResult = value.extract(); + match value { + Ok(value) => Ok(Self(value as $type)), + Err(_) => Err(PyErr::new::(format!( + "Cannot convert argument to {}", + stringify!($name) + ))), } } + pub fn __neg__(&self) -> PyResult<$name> { + Ok($name(self.0.wrapping_neg())) + } + pub fn to_bytes( &self, py: Python, @@ -55,30 +62,29 @@ macro_rules! integer_variant { )) } }; - Ok($name(self.0 + other)) + Ok($name(self.0.wrapping_add(other))) } - pub fn __radd__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { - self.__add__(other) + // For the case where we are on the right side of the operator we let the other + // object handle the addition and turn ourselves into an integer. + pub fn __radd__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult> { + other.add(self.0) } pub fn __sub__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { - let other = match other.extract::<$name>() { - Ok(other) => other.0, - Err(_) => match other.extract::<$type>() { - Ok(other) => other, - Err(_) => { - return Err(PyErr::new::( - "Cannot convert argument to integer", - )) - } - }, + let other = match other.extract::<$type>() { + Ok(other) => other, + Err(_) => { + return Err(PyErr::new::( + "Cannot convert argument to integer", + )) + } }; Ok($name(self.0 - other)) } - pub fn __rsub__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { - self.__sub__(other) + pub fn __rsub__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult> { + other.sub(self.0) } pub fn __mul__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { @@ -96,8 +102,8 @@ macro_rules! integer_variant { Ok($name(self.0 * other)) } - pub fn __rmul__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { - self.__mul__(other) + pub fn __rmul__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult> { + other.mul(self.0) } pub fn __truediv__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { @@ -105,6 +111,34 @@ macro_rules! integer_variant { Ok($name(self.0 / other)) } + pub fn __floordiv__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { + let other = other.extract::<$type>()?; + Ok($name(self.0 / other)) + } + + pub fn __mod__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { + let other = other.extract::<$type>()?; + Ok($name(self.0 % other)) + } + + pub fn __invert__(&self) -> Self { + Self(!self.0) + } + + pub fn __inv__(&self) -> Self { + Self(!self.0) + } + + #[cfg(any(target_type = "i8", target_type = "i16", target_type = "i32"))] + pub fn __abs__(&self) -> Self { + Self(self.0.abs()) + } + + #[cfg(any(target_type = "u8", target_type = "u16", target_type = "u32"))] + pub fn __abs__(&self) -> Self { + Self(self.0) + } + pub fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult { let other = match other.extract::<$name>() { Ok(other) => Ok(other.0), @@ -132,40 +166,46 @@ macro_rules! integer_variant { } } - pub fn __rshift__(&self, other: usize) -> PyResult<$name> { - Ok($name(self.0 >> other)) + pub fn __rshift__(&self, other: u32) -> PyResult<$name> { + Ok($name(self.0.wrapping_shr(other))) } - pub fn __lshift__(&self, other: usize) -> PyResult<$name> { - Ok($name(self.0 << other)) + pub fn __rrshift__<'py>( + &self, + other: &'py Bound<'py, PyAny>, + ) -> PyResult> { + other.rshift(self.0) } - pub fn __and__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { - let other = match other.extract::<$name>() { - Ok(other) => Ok(other.0), - Err(_) => match other.extract::<$type>() { - Ok(other) => Ok(other), - Err(_) => Err(PyErr::new::("Cannot compare")), - }, - }; + pub fn __lshift__(&self, other: u32) -> PyResult<$name> { + Ok($name(self.0.rotate_left(other))) + } - match other { - Ok(other) => Ok($name(self.0 & other)), - Err(_) => Err(PyErr::new::("Cannot and")), + pub fn __rlshift__<'py>( + &self, + other: &'py Bound<'py, PyAny>, + ) -> PyResult> { + other.lshift(self.0) + } + + pub fn __and__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { + match other.extract::() { + Ok(other) => Ok($name((self.0 as u32 & other) as $type)), + Err(_) => Err(PyErr::new::("Cannot compare")), } } - pub fn __rand__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { - self.__and__(other) + pub fn __rand__<'py>( + &self, + other: &'py Bound<'py, PyAny>, + ) -> PyResult> { + other.bitand(self.0) } pub fn __or__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { - let other = match other.extract::<$name>() { - Ok(other) => Ok(other.0), - Err(_) => match other.extract::<$type>() { - Ok(other) => Ok(other), - Err(_) => Err(PyErr::new::("Cannot compare")), - }, + let other = match other.extract::<$type>() { + Ok(other) => Ok(other), + Err(_) => Err(PyErr::new::("Cannot compare")), }; match other { @@ -174,8 +214,11 @@ macro_rules! integer_variant { } } - pub fn __ror__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { - self.__or__(other) + pub fn __ror__<'py>( + &self, + other: &'py Bound<'py, PyAny>, + ) -> PyResult> { + other.bitor(self.0) } pub fn __xor__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { @@ -193,8 +236,11 @@ macro_rules! integer_variant { } } - pub fn __rxor__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { - self.__xor__(other) + pub fn __rxor__<'py>( + &self, + other: &'py Bound<'py, PyAny>, + ) -> PyResult> { + other.bitxor(self.0) } fn __bool__(&self) -> bool { @@ -224,19 +270,29 @@ macro_rules! integer_variant { pub fn __str__(&self) -> PyResult { Ok(self.0.to_string()) } + + pub fn __format__<'py>(&self, py: Python<'py>, format: &str) -> PyResult { + // This is hard to implement so we just convert to a Python integer and let Python handle it + let int = self.__int__()?; + let int = int.into_py(py); + let result = int.call_method1(py, "__format__", (format,))?; + result.extract::(py) + } } }; } -integer_variant!(Int8, i8); -//integer_variant!(UInt8, u8); -integer_variant!(Int16, i16); -integer_variant!(UInt16, u16); -integer_variant!(Int32, i32); -integer_variant!(UInt32, u32); +//integer_variant!(UInt8, u8, u32, 0xff); +integer_variant!(Int8, i8, i32, 0xff_u32); +integer_variant!(UInt16, u16, u32, 0xffff_u32); +integer_variant!(Int16, i16, i32, 0xffff_u32); +integer_variant!(UInt32, u32, u32, 0xffffffff_u32); +integer_variant!(Int32, i32, i32, 0xffffffff_u32); //integer_variant!(Int64, i64); //integer_variant!(UInt64, u64); +// TODO: Remove code below. We only keep one non-macro integer type for +// TODO: now to make the IDE type inference work properly. #[pyclass(module = "fable", frozen)] #[derive(Clone)] pub struct UInt8(u8); @@ -253,6 +309,10 @@ impl UInt8 { } } + pub fn __neg__(&self) -> PyResult { + Ok(UInt8(self.0.wrapping_neg())) + } + pub fn to_bytes(&self, py: Python, length: usize, byteorder: &str) -> PyResult { let mut bytes = vec![0; length]; match byteorder { @@ -270,25 +330,17 @@ impl UInt8 { pub fn __add__(&self, other: &Bound<'_, PyAny>) -> PyResult { let other = match other.extract::() { Ok(other) => other, - Err(_) => match other.extract::() { - Ok(_other) => { - //return Ok(other + (f64.from(self.0))); - return Err(PyErr::new::( - "Cannot convert argument to float", - )); - } - Err(_) => { - return Err(PyErr::new::( - "Cannot convert argument to integer", - )) - } - }, + Err(_) => { + return Err(PyErr::new::( + "Cannot convert argument to integer", + )) + } }; Ok(UInt8(self.0.wrapping_add(other))) } - pub fn __radd__(&self, other: &Bound<'_, PyAny>) -> PyResult { - self.__add__(other) + pub fn __radd__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult> { + other.add(self.0) } pub fn __sub__(&self, other: &Bound<'_, PyAny>) -> PyResult { @@ -322,8 +374,8 @@ impl UInt8 { Ok(UInt8(self.0.wrapping_mul(other))) } - pub fn __rmul__(&self, other: &Bound<'_, PyAny>) -> PyResult { - self.__mul__(other) + pub fn __rmul__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult> { + other.mul(self.0) } pub fn __truediv__(&self, other: &Bound<'_, PyAny>) -> PyResult { @@ -331,6 +383,20 @@ impl UInt8 { Ok(UInt8(self.0 / other)) } + pub fn __floordiv__(&self, other: &Bound<'_, PyAny>) -> PyResult { + let other = other.extract::()?; + Ok(UInt8(self.0 / other)) + } + + pub fn __mod__(&self, other: &Bound<'_, PyAny>) -> PyResult { + let other = other.extract::()?; + Ok(UInt8(self.0 % other)) + } + + pub fn __invert__(&self) -> Self { + UInt8(!self.0) + } + pub fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult { let other = match other.extract::() { Ok(other) => Ok(other), @@ -383,25 +449,8 @@ impl UInt8 { Ok(UInt8(self.0.rotate_left(other))) } - pub fn __rlshift__<'py>( - &self, - py: Python<'py>, - other: &'py Bound<'py, PyAny>, - ) -> PyResult> { - let result = match other.extract::() { - Ok(other) => { - let shifted = other.0.wrapping_shl(self.0.into()); - Ok(UInt8(shifted).into_py(py).into_bound(py)) - } - Err(_) => match other.extract::() { - Ok(other) => { - let shifted = other.wrapping_shl(self.0.into()); - Ok(Int32(shifted).into_py(py).into_bound(py)) - } - Err(_) => Err(PyErr::new::("Cannot shift")), - }, - }; - result + pub fn __rlshift__<'py>(&self, other: &'py Bound<'py, PyAny>) -> PyResult> { + other.lshift(self.0) } pub fn __and__(&self, other: &Bound<'_, PyAny>) -> PyResult { @@ -416,8 +465,8 @@ impl UInt8 { } } - pub fn __rand__(&self, other: &Bound<'_, PyAny>) -> PyResult { - self.__and__(other) + pub fn __rand__<'py>(&self, other: &'py Bound<'py, PyAny>) -> PyResult> { + other.bitand(self.0) } pub fn __or__(&self, other: &Bound<'_, PyAny>) -> PyResult { @@ -432,8 +481,8 @@ impl UInt8 { } } - pub fn __ror__(&self, other: &Bound<'_, PyAny>) -> PyResult { - self.__or__(other) + pub fn __ror__<'py>(&self, other: &'py Bound<'py, PyAny>) -> PyResult> { + other.bitor(self.0) } pub fn __xor__(&self, other: &Bound<'_, PyAny>) -> PyResult { @@ -448,8 +497,8 @@ impl UInt8 { } } - pub fn __rxor__(&self, other: &Bound<'_, PyAny>) -> PyResult { - self.__xor__(other) + pub fn __rxor__<'py>(&self, other: &'py Bound<'py, PyAny>) -> PyResult> { + other.bitxor(self.0) } fn __bool__(&self) -> bool { @@ -479,4 +528,12 @@ impl UInt8 { pub fn __str__(&self) -> PyResult { Ok(self.0.to_string()) } + + pub fn __format__<'py>(&self, py: Python<'py>, format: &str) -> PyResult { + // This is hard to implement so we just convert to a Python integer and let Python handle it + let int = self.__int__()?; + let int = int.into_py(py); + let result = int.call_method1(py, "__format__", (format,))?; + result.extract::(py) + } } diff --git a/src/fable-library-py/tests/test_types.py b/src/fable-library-py/tests/test_types.py index e45c36b1fd..1b2d79b285 100644 --- a/src/fable-library-py/tests/test_types.py +++ b/src/fable-library-py/tests/test_types.py @@ -1,73 +1,125 @@ from array import array +from decimal import Decimal -from fable_library.core import uint8, uint16 - - -def test_uint8_create() -> None: - assert uint8(0) == 0 - assert uint8(uint8(42)) == 42 - assert uint8(uint16(42)) == 42 - - -def test_uint8_add() -> None: - assert uint8(42) + uint8(42) == 84 - assert uint8(255) + uint8(1) == 0 - assert uint8(255) + 1 == 0 - assert 42 + uint8(42) == 84 # Uses the __radd__ method - assert uint8(42) + 42 == 84 - # assert uint8(1) + 1.1 == 2.1 - - -def test_uint8_coerce(): - assert int(uint8(42)) == 42 # Uses the __int__ method - assert array("B", [uint8(42), uint8(42)]) == array("B", [42, 42]) # Uses the __index__ method - - -def test_uint8_rich_compare(): - assert uint8(42) == 42 - assert uint8(42) != 43 - assert uint8(42) < 43 - assert uint8(42) <= 43 - assert uint8(42) <= 42 - assert uint8(42) > 41 - assert uint8(42) >= 41 - assert uint8(42) >= 42 - assert 42 == uint8(42) - assert 43 != uint8(42) - assert 43 > uint8(42) - assert 43 >= uint8(42) - assert 42 < uint8(43) - assert 42 <= uint8(43) - assert 41 < uint8(42) - assert 41 <= uint8(42) - assert 42 >= uint8(42) - assert uint8(42) == uint8(42) - assert uint8(42) != uint8(43) - assert uint8(42) < uint8(43) - assert uint8(42) <= uint8(43) - assert uint8(42) <= uint8(42) - assert uint8(42) > uint8(41) - assert uint8(42) >= uint8(41) - assert uint8(42) >= uint8(42) - assert not (uint8(42) == 43) +from fable_library.core import byte, int16, sbyte, uint16 + + +def test_byte_create() -> None: + assert byte(0) == 0 + assert byte(byte(42)) == 42 + assert byte(uint16(42)) == 42 + + +def test_sbyte_create() -> None: + assert sbyte(0) == 0 + assert sbyte(sbyte(42)) == 42 + assert sbyte(42) == 42 + assert sbyte(-42) == -42 + + +def test_uint16_create() -> None: + assert uint16(0) == 0 + assert uint16(uint16(42)) == 42 + assert uint16(42) == 42 + assert uint16(65535) == 65535 + assert uint16(65536) == 0 + assert uint16(-1) == 65535 + + +def test_int16_create() -> None: + assert int16(0) == 0 + assert int16(int16(42)) == 42 + assert int16(42) == 42 + assert int16(32767) == 32767 + assert int16(32768) == -32768 + assert int16(-1) == -1 + + +def test_byte_add() -> None: + assert byte(42) + byte(42) == 84 + assert byte(255) + byte(1) == 0 + assert byte(255) + 1 == 0 + assert byte(42) + 42 == 84 + # assert byte(1) + 1.1 == 2.1 + assert 42 + byte(42) == 84 # Uses the __radd__ method + assert Decimal(42) + byte(42) == 84 # Uses the __radd__ method + assert 1.1 + byte(1) == 2.1 # Uses the __radd__ method + + +def test_byte_coerce(): + assert int(byte(42)) == 42 # Uses the __int__ method + assert array("B", [byte(42), byte(42)]) == array("B", [42, 42]) # Uses the __index__ method + # Can be used as slice indices + assert [1, 2, 3][byte(1) : byte(2)] == [2] + + +def test_byte_rich_compare(): + assert byte(42) == 42 + assert byte(42) != 43 + assert byte(42) < 43 + assert byte(42) <= 43 + assert byte(42) <= 42 + assert byte(42) > 41 + assert byte(42) >= 41 + assert byte(42) >= 42 + assert 42 == byte(42) + assert 43 != byte(42) + assert 43 > byte(42) + assert 43 >= byte(42) + assert 42 < byte(43) + assert 42 <= byte(43) + assert 41 < byte(42) + assert 41 <= byte(42) + assert 42 >= byte(42) + assert byte(42) == byte(42) + assert byte(42) != byte(43) + assert byte(42) < byte(43) + assert byte(42) <= byte(43) + assert byte(42) <= byte(42) + assert byte(42) > byte(41) + assert byte(42) >= byte(41) + assert byte(42) >= byte(42) + assert not (byte(42) == 43) def test_uin8_rsift(): - assert uint8(2) >> 1 == 1 - assert uint8(1) >> 1 == 0 - assert uint8(1) >> 8 == 1 - assert uint8(1) >> 9 == 0 - assert 2 >> uint8(1) == 1 - assert 1 >> uint8(1) == 0 - assert 1 >> uint8(8) == 0 - assert 1 >> uint8(9) == 0 - - -def test_uint8_lshift(): - assert uint8(1) << 1 == 2 - assert uint8(1) << 8 == 1 - assert uint8(1) << 9 == 2 - assert 1 << uint8(1) == 2 - print(1 << uint8(8)) - assert 1 << uint8(8) == 256 - assert 1 << uint8(9) == 512 + assert byte(2) >> 1 == 1 + assert byte(1) >> 1 == 0 + assert byte(1) >> 8 == 1 + assert byte(1) >> 9 == 0 + assert 2 >> byte(1) == 1 + assert 1 >> byte(1) == 0 + assert 1 >> byte(8) == 0 + assert 1 >> byte(9) == 0 + + +def test_byte_lshift(): + assert byte(1) << 1 == 2 + assert byte(1) << 8 == 1 + assert byte(1) << 9 == 2 + assert 1 << byte(1) == 2 + assert 1 << byte(8) == 256 + assert 1 << byte(9) == 512 + + +def test_binary_complement(): + assert ~byte(0) == 255 + assert ~byte(1) == 254 + assert ~byte(255) == 0 + assert ~byte(254) == 1 + assert ~byte(42) == 213 + assert ~byte(213) == 42 + + +def test_format(): + assert f"{byte(42)}" == "42" + assert f"{byte(42):02X}" == "2A" + assert f"{byte(42):02x}" == "2a" + assert f"{byte(42):08b}" == "00101010" + assert f"{byte(42):08}" == "00000042" + assert f"{byte(42):08d}" == "00000042" + assert f"{byte(42):08o}" == "00000052" + assert f"{uint16(42):08d}" == "00000042" + assert f"{int16(42):08d}" == "00000042" + assert f"{sbyte(42):08d}" == "00000042" + assert f"{byte(42):08d}" == "00000042" From 8b18b82167fff47bf0b20b9e498f6f1edec80c6a Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 18 Apr 2024 07:30:45 +0200 Subject: [PATCH 11/20] Tweak the build system --- src/Fable.Build/FableLibrary/Core.fs | 31 +++++++++++-------- src/Fable.Build/FableLibrary/Python.fs | 16 ++++++++++ .../fable_library/core/__init__.py | 20 +++++++----- .../fable_library/core/_core.pyi | 10 +++--- .../fable_library/decimal_.py | 8 +++-- src/fable-library-py/fable_library/string_.py | 4 +-- src/fable-library-py/fable_library/types.py | 3 ++ 7 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/Fable.Build/FableLibrary/Core.fs b/src/Fable.Build/FableLibrary/Core.fs index 06680fcbb7..d433f9902d 100644 --- a/src/Fable.Build/FableLibrary/Core.fs +++ b/src/Fable.Build/FableLibrary/Core.fs @@ -49,6 +49,23 @@ type BuildFableLibrary abstract member CopyStage: unit -> unit default _.CopyStage() = () + abstract member FableBuildStage: unit -> unit + + default this.FableBuildStage() = + let args = + CmdLine.appendRaw sourceDir + >> CmdLine.appendPrefix "--outDir" outDir + >> CmdLine.appendPrefix "--fableLib" fableLibArg + >> CmdLine.appendPrefix "--lang" language + >> CmdLine.appendPrefix "--exclude" "Fable.Core" + >> CmdLine.appendPrefix "--define" "FABLE_LIBRARY" + >> CmdLine.appendRaw "--noCache" + // Target implementation can require additional arguments + >> this.FableArgsBuilder + + Command.Fable(args) + + member this.Run(?skipIfExist: bool) = let skipIfExist = defaultArg skipIfExist false @@ -63,19 +80,7 @@ type BuildFableLibrary Directory.Delete(buildDir, true) Calm "Building Fable.Library" |> toConsole - - let args = - CmdLine.appendRaw sourceDir - >> CmdLine.appendPrefix "--outDir" outDir - >> CmdLine.appendPrefix "--fableLib" fableLibArg - >> CmdLine.appendPrefix "--lang" language - >> CmdLine.appendPrefix "--exclude" "Fable.Core" - >> CmdLine.appendPrefix "--define" "FABLE_LIBRARY" - >> CmdLine.appendRaw "--noCache" - // Target implementation can require additional arguments - >> this.FableArgsBuilder - - Command.Fable(args) + this.FableBuildStage() Calm "Copy stage" |> toConsole this.CopyStage() diff --git a/src/Fable.Build/FableLibrary/Python.fs b/src/Fable.Build/FableLibrary/Python.fs index f4571d1d61..10c3050034 100644 --- a/src/Fable.Build/FableLibrary/Python.fs +++ b/src/Fable.Build/FableLibrary/Python.fs @@ -4,6 +4,7 @@ open System.IO open Fake.IO open Build.Utils open SimpleExec +open BlackFox.CommandLine type BuildFableLibraryPython() = inherit @@ -15,6 +16,21 @@ type BuildFableLibraryPython() = Path.Combine("temp", "fable-library-py", "fable_library") ) + override this.FableBuildStage() = + let args = + CmdLine.appendRaw this.SourceDir + >> CmdLine.appendPrefix "--outDir" this.OutDir + >> CmdLine.appendPrefix "--fableLib" "." + >> CmdLine.appendPrefix "--lang" this.Language + >> CmdLine.appendPrefix "--exclude" "Fable.Core" + >> CmdLine.appendPrefix "--define" "FABLE_LIBRARY" + >> CmdLine.appendRaw "--noCache" + // Target implementation can require additional arguments + >> this.FableArgsBuilder + + Command.Fable(args) + + override this.CopyStage() = // // Copy all *.rs files to the build directory Directory.GetFiles(this.LibraryDir, "*") |> Shell.copyFiles this.BuildDir diff --git a/src/fable-library-py/fable_library/core/__init__.py b/src/fable-library-py/fable_library/core/__init__.py index 668b8d03ca..ffc07e5d8b 100644 --- a/src/fable-library-py/fable_library/core/__init__.py +++ b/src/fable-library-py/fable_library/core/__init__.py @@ -1,21 +1,23 @@ from typing import TypeAlias -# from ._core import Int64 as int64 # type: ignore +# from ._core import Int64 as int64 from ._core import ( - Int8, # type: ignore - Int16, # type: ignore - Int32, # type: ignore - UInt8, # type: ignore - UInt16, # type: ignore - UInt32, # type: ignore + Int8, + Int16, + Int32, + UInt8, + UInt16, + UInt32, ) -# from ._core import UInt64 as uint64 # type: ignore +# from ._core import UInt64 as uint64 # Type aliases for the built-in types byte: TypeAlias = UInt8 sbyte: TypeAlias = Int8 +uint8: TypeAlias = UInt8 +int8: TypeAlias = Int8 int16: TypeAlias = Int16 uint16: TypeAlias = UInt16 int32: TypeAlias = Int32 @@ -24,6 +26,8 @@ __all__ = [ "sbyte", "byte", + "uint8", + "int8", "uint16", "int16", "int32", diff --git a/src/fable-library-py/fable_library/core/_core.pyi b/src/fable-library-py/fable_library/core/_core.pyi index cbee1abded..4b9f2072ff 100644 --- a/src/fable-library-py/fable_library/core/_core.pyi +++ b/src/fable-library-py/fable_library/core/_core.pyi @@ -1,4 +1,6 @@ -from typing import Any, Self, final +from typing import Any, final + +from typing_extensions import Self class Numeric: def __init__(self, value: int | Numeric) -> None: ... @@ -25,9 +27,9 @@ class Numeric: def __or__(self, other: int | Numeric) -> Self: ... def __xor__(self, other: int | Numeric) -> Self: ... def __invert__(self) -> Self: ... - def __radd__(self, other: int | float) -> Any: ... - def __rsub__(self, other: int | float) -> Any: ... - def __rmul__(self, other: int | float) -> Any: ... + def __radd__(self, other: Any) -> Any: ... + def __rsub__(self, other: Any) -> Any: ... + def __rmul__(self, other: Any) -> Any: ... @final class UInt8(Numeric): ... diff --git a/src/fable-library-py/fable_library/decimal_.py b/src/fable-library-py/fable_library/decimal_.py index bf578791d2..75d6cf8cc6 100644 --- a/src/fable-library-py/fable_library/decimal_.py +++ b/src/fable-library-py/fable_library/decimal_.py @@ -1,6 +1,6 @@ from decimal import MAX_EMAX, MIN_EMIN, Decimal, getcontext -from .types import FSharpRef, byte, int16, int32, sbyte, uint16, uint32 +from .types import FSharpRef, IntegerTypes, byte, int16, int32, sbyte, uint16, uint32 getcontext().prec = 29 @@ -113,7 +113,9 @@ def op_inequality(a: Decimal, b: Decimal) -> bool: return a != b -def from_parts(low: int, mid: int, high: int, isNegative: bool, scale: int) -> Decimal: +def from_parts( + low: IntegerTypes, mid: IntegerTypes, high: IntegerTypes, isNegative: IntegerTypes, scale: IntegerTypes +) -> Decimal: sign = -1 if isNegative else 1 if low < 0: @@ -156,7 +158,7 @@ def try_parse(string: str, def_value: FSharpRef[Decimal]) -> bool: return False -def create(value: int | float | int16 | uint16 | str) -> Decimal: +def create(value: int | float | byte | sbyte | int16 | uint16 | int32 | uint32 | str) -> Decimal: match value: case sbyte() | byte() | int16() | uint16() | int32() | uint32(): return Decimal(int(value)) diff --git a/src/fable-library-py/fable_library/string_.py b/src/fable-library-py/fable_library/string_.py index 124f3f7de5..f219aab533 100644 --- a/src/fable-library-py/fable_library/string_.py +++ b/src/fable-library-py/fable_library/string_.py @@ -101,9 +101,9 @@ def format_replacement(rep: Any, flags: Any, padLength: Any, precision: Any, for precision = int(precision) if precision is not None else 6 rep = to_fixed(rep, precision) elif format in ("g", "G"): - rep = to_precision(rep, precision) if precision is not None else to_precision(rep) + rep = to_precision(rep, precision) if precision is not None else to_precision(float(rep)) elif format in ("e", "E"): - rep = to_exponential(rep, precision) if precision is not None else to_exponential(rep) + rep = to_exponential(rep, precision) if precision is not None else to_exponential(float(rep)) else: # AOid rep = to_string(rep) diff --git a/src/fable-library-py/fable_library/types.py b/src/fable-library-py/fable_library/types.py index 2e4762b985..d98f4092f2 100644 --- a/src/fable-library-py/fable_library/types.py +++ b/src/fable-library-py/fable_library/types.py @@ -6,6 +6,7 @@ from typing import ( Any, Generic, + TypeAlias, TypeVar, cast, ) @@ -305,6 +306,7 @@ class float32(float): float = float # use native float for float64 +IntegerTypes: TypeAlias = int | byte | sbyte | int16 | uint16 | int32 | uint32 | int64 | uint64 def Int8Array(lst: list[int]) -> MutableSequence[int]: @@ -370,4 +372,5 @@ def is_exception(x: Any): "seq_to_string", "to_string", "Union", + "IntegerTypes", ] From 1460619bbfeaacc7073a7064f9ba6e8052b76b4b Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 19 Apr 2024 08:40:53 +0200 Subject: [PATCH 12/20] Work on uint64/int64 --- src/Fable.Transforms/Python/Fable2Python.fs | 14 +- .../fable_library/core/__init__.py | 11 +- .../fable_library/core/_core.pyi | 7 + src/fable-library-py/fable_library/types.py | 12 +- src/fable-library-py/src/lib.rs | 4 +- src/fable-library-py/src/types.rs | 316 +++--------------- src/fable-library-py/tests/test_types.py | 20 +- 7 files changed, 91 insertions(+), 293 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 2e8695584e..43451736fb 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -438,14 +438,14 @@ module Reflection = | Fable.String -> pyTypeof "" expr | Fable.Number(kind, _b) -> match kind, typ with - | _, Fable.Type.Number(UInt8, _) -> pyTypeof ">" expr - | _, Fable.Type.Number(Int8, _) -> pyTypeof "" expr - | _, Fable.Type.Number(Int16, _) -> pyTypeof "" expr - | _, Fable.Type.Number(UInt16, _) -> pyTypeof "" expr + | _, Fable.Type.Number(UInt8, _) -> pyTypeof ">" expr + | _, Fable.Type.Number(Int8, _) -> pyTypeof "" expr + | _, Fable.Type.Number(Int16, _) -> pyTypeof "" expr + | _, Fable.Type.Number(UInt16, _) -> pyTypeof "" expr | _, Fable.Type.Number(Int32, _) -> pyTypeof "" expr - | _, Fable.Type.Number(UInt32, _) -> pyTypeof "" expr - | _, Fable.Type.Number(Int64, _) -> pyTypeof "" expr - | _, Fable.Type.Number(UInt64, _) -> pyTypeof "" expr + | _, Fable.Type.Number(UInt32, _) -> pyTypeof "" expr + | _, Fable.Type.Number(Int64, _) -> pyTypeof "" expr + | _, Fable.Type.Number(UInt64, _) -> pyTypeof "" expr | _, Fable.Type.Number(Float32, _) -> pyTypeof "" expr | _, Fable.Type.Number(Float64, _) -> pyTypeof "" expr | _ -> pyTypeof "" expr diff --git a/src/fable-library-py/fable_library/core/__init__.py b/src/fable-library-py/fable_library/core/__init__.py index ffc07e5d8b..5383dd844d 100644 --- a/src/fable-library-py/fable_library/core/__init__.py +++ b/src/fable-library-py/fable_library/core/__init__.py @@ -1,18 +1,17 @@ from typing import TypeAlias -# from ._core import Int64 as int64 from ._core import ( Int8, Int16, Int32, + Int64, UInt8, UInt16, UInt32, + UInt64, ) -# from ._core import UInt64 as uint64 - # Type aliases for the built-in types byte: TypeAlias = UInt8 sbyte: TypeAlias = Int8 @@ -22,6 +21,8 @@ uint16: TypeAlias = UInt16 int32: TypeAlias = Int32 uint32: TypeAlias = UInt32 +int64: TypeAlias = Int64 +uint64: TypeAlias = UInt64 __all__ = [ "sbyte", @@ -38,4 +39,8 @@ "UInt16", "Int32", "UInt32", + "Int64", + "UInt64", + "int64", + "uint64", ] diff --git a/src/fable-library-py/fable_library/core/_core.pyi b/src/fable-library-py/fable_library/core/_core.pyi index 4b9f2072ff..bc8136e5d5 100644 --- a/src/fable-library-py/fable_library/core/_core.pyi +++ b/src/fable-library-py/fable_library/core/_core.pyi @@ -30,6 +30,7 @@ class Numeric: def __radd__(self, other: Any) -> Any: ... def __rsub__(self, other: Any) -> Any: ... def __rmul__(self, other: Any) -> Any: ... + def __abs__(self) -> Self: ... @final class UInt8(Numeric): ... @@ -48,3 +49,9 @@ class UInt32(Numeric): ... @final class Int32(Numeric): ... + +@final +class UInt64(Numeric): ... + +@final +class Int64(Numeric): ... diff --git a/src/fable-library-py/fable_library/types.py b/src/fable-library-py/fable_library/types.py index d98f4092f2..ab57a7e872 100644 --- a/src/fable-library-py/fable_library/types.py +++ b/src/fable-library-py/fable_library/types.py @@ -11,7 +11,7 @@ cast, ) -from .core import byte, int16, int32, sbyte, uint16, uint32 +from .core import byte, int16, int32, int64, sbyte, uint16, uint32, uint64 from .util import Array, IComparable, compare @@ -274,7 +274,7 @@ def __lt__(self, other: Any) -> bool: else: return True - return super().__lt__(other) + return False def __hash__(self) -> int: return hash(self.Data0) @@ -293,14 +293,6 @@ class char(int): __slots__ = () -class int64(int): - __slots__ = () - - -class uint64(int): - __slots__ = () - - class float32(float): __slots__ = () diff --git a/src/fable-library-py/src/lib.rs b/src/fable-library-py/src/lib.rs index 22d6201622..7a9e3c56d4 100644 --- a/src/fable-library-py/src/lib.rs +++ b/src/fable-library-py/src/lib.rs @@ -20,7 +20,7 @@ fn _core(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; - //m.add_class::()?; - //m.add_class::()?; + m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/src/fable-library-py/src/types.rs b/src/fable-library-py/src/types.rs index c7ef5406c7..c1bb35729d 100644 --- a/src/fable-library-py/src/types.rs +++ b/src/fable-library-py/src/types.rs @@ -9,8 +9,36 @@ use pyo3::types::PyBytes; use byteorder::{BigEndian, ByteOrder, LittleEndian}; +pub trait Abs { + fn abs(&self) -> Self; +} + +impl Abs for u8 { + fn abs(&self) -> Self { + *self + } +} + +impl Abs for u16 { + fn abs(&self) -> Self { + *self + } +} + +impl Abs for u32 { + fn abs(&self) -> Self { + *self + } +} + +impl Abs for u64 { + fn abs(&self) -> Self { + *self + } +} + macro_rules! integer_variant { - ($name:ident, $type:ty, $extract_type:ty, $mask:expr) => { + ($name:ident, $type:ty, $mask:expr) => { #[pyclass(module = "fable", frozen)] #[derive(Clone)] pub struct $name($type); @@ -20,7 +48,7 @@ macro_rules! integer_variant { #[new] pub fn new(value: &Bound<'_, PyAny>) -> PyResult { let value = value.call_method1("__and__", ($mask,))?; - let value: PyResult = value.extract(); + let value: PyResult = value.extract(); match value { Ok(value) => Ok(Self(value as $type)), Err(_) => Err(PyErr::new::(format!( @@ -75,9 +103,10 @@ macro_rules! integer_variant { let other = match other.extract::<$type>() { Ok(other) => other, Err(_) => { - return Err(PyErr::new::( - "Cannot convert argument to integer", - )) + return Err(PyErr::new::(format!( + "Cannot convert argument to {}", + stringify!($name) + ))) } }; Ok($name(self.0 - other)) @@ -93,9 +122,9 @@ macro_rules! integer_variant { Err(_) => match other.extract::<$type>() { Ok(other) => other, Err(_) => { - return Err(PyErr::new::( - "Cannot convert argument to integer", - )) + return Err(PyErr::new::(format!( + "Cannot convert argument to {}".$name + ))) } }, }; @@ -129,16 +158,10 @@ macro_rules! integer_variant { Self(!self.0) } - #[cfg(any(target_type = "i8", target_type = "i16", target_type = "i32"))] pub fn __abs__(&self) -> Self { Self(self.0.abs()) } - #[cfg(any(target_type = "u8", target_type = "u16", target_type = "u32"))] - pub fn __abs__(&self) -> Self { - Self(self.0) - } - pub fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult { let other = match other.extract::<$name>() { Ok(other) => Ok(other.0), @@ -271,7 +294,7 @@ macro_rules! integer_variant { Ok(self.0.to_string()) } - pub fn __format__<'py>(&self, py: Python<'py>, format: &str) -> PyResult { + pub fn __format__(&self, py: Python<'_>, format: &str) -> PyResult { // This is hard to implement so we just convert to a Python integer and let Python handle it let int = self.__int__()?; let int = int.into_py(py); @@ -282,258 +305,11 @@ macro_rules! integer_variant { }; } -//integer_variant!(UInt8, u8, u32, 0xff); -integer_variant!(Int8, i8, i32, 0xff_u32); -integer_variant!(UInt16, u16, u32, 0xffff_u32); -integer_variant!(Int16, i16, i32, 0xffff_u32); -integer_variant!(UInt32, u32, u32, 0xffffffff_u32); -integer_variant!(Int32, i32, i32, 0xffffffff_u32); -//integer_variant!(Int64, i64); -//integer_variant!(UInt64, u64); - -// TODO: Remove code below. We only keep one non-macro integer type for -// TODO: now to make the IDE type inference work properly. -#[pyclass(module = "fable", frozen)] -#[derive(Clone)] -pub struct UInt8(u8); - -#[pymethods] -impl UInt8 { - #[new] - pub fn new(value: &Bound<'_, PyAny>) -> PyResult { - match value.extract::() { - Ok(value) => Ok(Self(value)), - Err(_) => Err(PyErr::new::( - "Cannot convert argument to integer", - )), - } - } - - pub fn __neg__(&self) -> PyResult { - Ok(UInt8(self.0.wrapping_neg())) - } - - pub fn to_bytes(&self, py: Python, length: usize, byteorder: &str) -> PyResult { - let mut bytes = vec![0; length]; - match byteorder { - "little" => LittleEndian::write_uint(&mut bytes, self.0 as u64, length), - "big" => BigEndian::write_uint(&mut bytes, self.0 as u64, length), - _ => { - return Err(PyErr::new::( - "Invalid byteorder", - )) - } - } - Ok(PyBytes::new_bound(py, &bytes).into()) - } - - pub fn __add__(&self, other: &Bound<'_, PyAny>) -> PyResult { - let other = match other.extract::() { - Ok(other) => other, - Err(_) => { - return Err(PyErr::new::( - "Cannot convert argument to integer", - )) - } - }; - Ok(UInt8(self.0.wrapping_add(other))) - } - - pub fn __radd__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult> { - other.add(self.0) - } - - pub fn __sub__(&self, other: &Bound<'_, PyAny>) -> PyResult { - let other = match other.extract::() { - Ok(other) => other, - Err(_) => { - return Err(PyErr::new::( - "Cannot convert argument to integer", - )) - } - }; - Ok(UInt8(self.0.wrapping_sub(other))) - } - - pub fn __rsub__(&self, other: &Bound<'_, PyAny>) -> PyResult { - self.__sub__(other) - } - - pub fn __mul__(&self, other: &Bound<'_, PyAny>) -> PyResult { - let other = match other.extract::() { - Ok(other) => other.0, - Err(_) => match other.extract::() { - Ok(other) => other, - Err(_) => { - return Err(PyErr::new::( - "Cannot convert argument to integer", - )) - } - }, - }; - Ok(UInt8(self.0.wrapping_mul(other))) - } - - pub fn __rmul__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult> { - other.mul(self.0) - } - - pub fn __truediv__(&self, other: &Bound<'_, PyAny>) -> PyResult { - let other = other.extract::()?; - Ok(UInt8(self.0 / other)) - } - - pub fn __floordiv__(&self, other: &Bound<'_, PyAny>) -> PyResult { - let other = other.extract::()?; - Ok(UInt8(self.0 / other)) - } - - pub fn __mod__(&self, other: &Bound<'_, PyAny>) -> PyResult { - let other = other.extract::()?; - Ok(UInt8(self.0 % other)) - } - - pub fn __invert__(&self) -> Self { - UInt8(!self.0) - } - - pub fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult { - let other = match other.extract::() { - Ok(other) => Ok(other), - Err(_) => match other.extract::() { - Ok(other) => Ok(other as u8), - Err(_) => Err(PyErr::new::("Cannot compare")), - }, - }; - - match other { - Ok(other) => match op { - CompareOp::Eq => Ok(self.0 == other), - CompareOp::Ne => Ok(self.0 != other), - CompareOp::Lt => Ok(self.0 < other), - CompareOp::Le => Ok(self.0 <= other), - CompareOp::Gt => Ok(self.0 > other), - CompareOp::Ge => Ok(self.0 >= other), - }, - Err(_) => Ok(false), - } - } - - pub fn __rshift__(&self, other: u32) -> PyResult { - Ok(UInt8(self.0.wrapping_shr(other))) - } - - // Return a python object not u8. First try to extract for a Python integer, then for a Rust u8 - pub fn __rrshift__<'py>( - &self, - py: Python<'py>, - other: &'py Bound<'py, PyAny>, - ) -> PyResult> { - let result = match other.extract::() { - Ok(other) => { - let shifted = other.0.wrapping_shr(self.0.into()); - Ok(UInt8(shifted).into_py(py).into_bound(py)) - } - Err(_) => match other.extract::() { - Ok(other) => { - let shifted = other.wrapping_shr(self.0.into()); - Ok(Int32(shifted).into_py(py).into_bound(py)) - } - Err(_) => Err(PyErr::new::("Cannot shift")), - }, - }; - result - } - - pub fn __lshift__(&self, other: u32) -> PyResult { - Ok(UInt8(self.0.rotate_left(other))) - } - - pub fn __rlshift__<'py>(&self, other: &'py Bound<'py, PyAny>) -> PyResult> { - other.lshift(self.0) - } - - pub fn __and__(&self, other: &Bound<'_, PyAny>) -> PyResult { - let other = match other.extract::() { - Ok(other) => Ok(other), - Err(_) => Err(PyErr::new::("Cannot compare")), - }; - - match other { - Ok(other) => Ok(UInt8(self.0 & other)), - Err(_) => Err(PyErr::new::("Cannot and")), - } - } - - pub fn __rand__<'py>(&self, other: &'py Bound<'py, PyAny>) -> PyResult> { - other.bitand(self.0) - } - - pub fn __or__(&self, other: &Bound<'_, PyAny>) -> PyResult { - let other = match other.extract::() { - Ok(other) => Ok(other), - Err(_) => Err(PyErr::new::("Cannot compare")), - }; - - match other { - Ok(other) => Ok(UInt8(self.0 | other)), - Err(_) => Err(PyErr::new::("Cannot or")), - } - } - - pub fn __ror__<'py>(&self, other: &'py Bound<'py, PyAny>) -> PyResult> { - other.bitor(self.0) - } - - pub fn __xor__(&self, other: &Bound<'_, PyAny>) -> PyResult { - let other = match other.extract::() { - Ok(other) => Ok(other), - Err(_) => Err(PyErr::new::("Cannot compare")), - }; - - match other { - Ok(other) => Ok(UInt8(self.0 ^ other)), - Err(_) => Err(PyErr::new::("Cannot xor")), - } - } - - pub fn __rxor__<'py>(&self, other: &'py Bound<'py, PyAny>) -> PyResult> { - other.bitxor(self.0) - } - - fn __bool__(&self) -> bool { - self.0 != 0 - } - - pub fn __hash__(&self) -> u8 { - let mut hasher = DefaultHasher::new(); - self.0.hash(&mut hasher); - hasher.finish().try_into().unwrap() - } - - pub fn __int__(&self) -> PyResult { - Ok(self.0) - } - - // Special method so that arbitrary objects can be used whenever integers - // are explicitly needed in Python - pub fn __index__(&self) -> PyResult { - Ok(self.0) - } - - pub fn __repr__(&self) -> PyResult { - Ok(self.0.to_string()) - } - - pub fn __str__(&self) -> PyResult { - Ok(self.0.to_string()) - } - - pub fn __format__<'py>(&self, py: Python<'py>, format: &str) -> PyResult { - // This is hard to implement so we just convert to a Python integer and let Python handle it - let int = self.__int__()?; - let int = int.into_py(py); - let result = int.call_method1(py, "__format__", (format,))?; - result.extract::(py) - } -} +integer_variant!(UInt8, u8, 0xff); +integer_variant!(Int8, i8, 0xff_u32); +integer_variant!(UInt16, u16, 0xffff_u32); +integer_variant!(Int16, i16, 0xffff_u32); +integer_variant!(UInt32, u32, 0xffffffff_u32); +integer_variant!(Int32, i32, 0xffffffff_u32); +integer_variant!(Int64, i64, 0xffffffffffffffff_u64); +integer_variant!(UInt64, u64, 0xffffffffffffffff_u64); diff --git a/src/fable-library-py/tests/test_types.py b/src/fable-library-py/tests/test_types.py index 1b2d79b285..6455db059a 100644 --- a/src/fable-library-py/tests/test_types.py +++ b/src/fable-library-py/tests/test_types.py @@ -1,7 +1,7 @@ from array import array from decimal import Decimal -from fable_library.core import byte, int16, sbyte, uint16 +from fable_library.core import byte, int16, int32, int64, sbyte, uint16, uint32, uint64 def test_byte_create() -> None: @@ -123,3 +123,21 @@ def test_format(): assert f"{int16(42):08d}" == "00000042" assert f"{sbyte(42):08d}" == "00000042" assert f"{byte(42):08d}" == "00000042" + + +def test_abs(): + assert abs(byte(42)) == 42 + assert abs(byte(0)) == 0 + assert abs(sbyte(-42)) == 42 + assert abs(sbyte(-1)) == 1 + assert abs(int16(-42)) == 42 + assert abs(int16(-1)) == 1 + assert abs(int32(-42)) == 42 + assert abs(int32(-1)) == 1 + assert abs(int64(-42)) == 42 + assert abs(int64(-1)) == 1 + assert abs(uint16(42)) == 42 + assert abs(uint16(0)) == 0 + assert abs(uint32(42)) == 42 + assert abs(uint32(0)) == 0 + assert abs(uint64(42)) == 42 From 87a1ab30ddab7457ca0482df152d5d14b09d2ad2 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 19 Apr 2024 12:17:17 +0200 Subject: [PATCH 13/20] Fixes for in64 and uint64 --- src/Fable.Transforms/Python/Replacements.fs | 10 +++---- src/Fable.Transforms/Rust/Replacements.fs | 1 + src/fable-library-py/fable_library/long.py | 16 ++--------- src/fable-library-py/fable_library/numeric.py | 9 +++---- src/fable-library-py/fable_library/string_.py | 6 +++-- src/fable-library-py/fable_library/util.py | 2 +- src/fable-library-py/src/lib.rs | 7 ----- src/fable-library-py/src/types.rs | 27 +++++++++++++++---- src/fable-library-py/tests/test_types.py | 1 + src/fable-library-rust/src/Convert.rs | 6 ++--- tests/Rust/tests/src/CharTests.fs | 14 +++++----- 11 files changed, 49 insertions(+), 50 deletions(-) diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index f872cef4f1..d6a74cb6ea 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -335,12 +335,12 @@ let toInt com (ctx: Context) r targetType (args: Expr list) = let emitCast typeTo arg = match typeTo with - | Int8 -> emitExpr None Int8.Number [ arg ] "(int($0) + 0x80 & 0xFF) - 0x80" - | Int16 -> emitExpr None Int16.Number [ arg ] "(int($0) + 0x8000 & 0xFFFF) - 0x8000" + | Int8 -> Helper.LibCall(com, "types", "sbyte", targetType, args) + | Int16 -> Helper.LibCall(com, "types", "int16", targetType, args) | Int32 -> emitExpr None Int32.Number [ arg ] "int($0)" - | UInt8 -> emitExpr None UInt8.Number [ arg ] "int($0+0x100 if $0 < 0 else $0) & 0xFF" - | UInt16 -> emitExpr None UInt16.Number [ arg ] "int($0+0x10000 if $0 < 0 else $0) & 0xFFFF" - | UInt32 -> emitExpr None UInt32.Number [ arg ] "int($0+0x100000000 if $0 < 0 else $0)" + | UInt8 -> Helper.LibCall(com, "types", "byte", targetType, args) + | UInt16 -> Helper.LibCall(com, "types", "uint16", targetType, args) + | UInt32 -> Helper.LibCall(com, "types", "uint32", targetType, args) | _ -> FableError $"Unexpected non-integer type %A{typeTo}" |> raise match sourceType, targetType with diff --git a/src/Fable.Transforms/Rust/Replacements.fs b/src/Fable.Transforms/Rust/Replacements.fs index 5e3bfaa076..8dfc9b3ca9 100644 --- a/src/Fable.Transforms/Rust/Replacements.fs +++ b/src/Fable.Transforms/Rust/Replacements.fs @@ -1178,6 +1178,7 @@ let chars (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr optio let c = Helper.LibCall(com, "String", "getCharAt", Char, args) makeInstanceCall r t i c methName [] |> Some | _ -> None + | "Parse", _, args -> Helper.LibCall(com, "Convert", "toChar", t, args, ?loc = r) |> Some // | "GetUnicodeCategory" , None, args -> //TODO: // | "IsHighSurrogate" | "IsLowSurrogate" | "IsSurrogate" -> diff --git a/src/fable-library-py/fable_library/long.py b/src/fable-library-py/fable_library/long.py index 5950e42a3e..5e72b53d22 100644 --- a/src/fable-library-py/fable_library/long.py +++ b/src/fable-library-py/fable_library/long.py @@ -1,20 +1,12 @@ from typing import Any -from .types import FSharpRef +from .types import FSharpRef, int32 def compare(x: int, y: int) -> int: return -1 if x < y else 1 if x > y else 0 -def equals(a: int, b: int) -> bool: - return a == b - - -def abs(x: int) -> int: - return -x if x < 0 else x - - def sign(x: int) -> int: return -1 if x < 0 else 1 if x > 0 else 0 @@ -191,17 +183,13 @@ def to_string(x: int) -> str: def to_int(value: int) -> int: - if value > 9223372036854775807: - return value - 0x10000000000000000 - return value + return int32(value) long = int __all__ = [ "compare", - "equals", - "abs", "sign", "max", "min", diff --git a/src/fable-library-py/fable_library/numeric.py b/src/fable-library-py/fable_library/numeric.py index 7b187beb31..09eec82510 100644 --- a/src/fable-library-py/fable_library/numeric.py +++ b/src/fable-library-py/fable_library/numeric.py @@ -1,6 +1,6 @@ from __future__ import annotations -from .types import int64 +from .types import int64, uint32, uint64 def to_fixed(x: float, dp: int | None = None) -> str: @@ -32,11 +32,8 @@ def to_exponential(x: float, dp: int | None = None) -> str: def to_hex(x: int) -> str: - def rshift(val: int, n: int) -> int: - sign = 0x10000000000000000 if isinstance(val, int64) else 0x100000000 - return val >> n if val >= 0 else (val + sign) >> n - - return f"{rshift(x, 0):x}" + val = uint64(x) if isinstance(x, int64 | uint64) else uint32(x) + return f"{val:x}" def multiply(x: float, y: float) -> float: diff --git a/src/fable-library-py/fable_library/string_.py b/src/fable-library-py/fable_library/string_.py index f219aab533..5383777607 100644 --- a/src/fable-library-py/fable_library/string_.py +++ b/src/fable-library-py/fable_library/string_.py @@ -190,7 +190,7 @@ def format(string: str, *args: Any) -> str: def match(m: Match[str]) -> str: idx, padLength, format, precision_, pattern = list(m.groups()) rep = args[int(idx)] - if isinstance(rep, int | float): + if isinstance(rep, int | byte | int16 | int32 | int64 | sbyte | uint16 | uint32 | uint64 | float): precision: int | None = None try: precision: int | None = int(precision_) @@ -214,7 +214,9 @@ def match(m: Match[str]) -> str: elif format in ["d", "D"]: rep = pad_left(str(rep), precision, "0") if precision is not None else str(rep) - elif format in ["x", "X"] and isinstance(rep, int): + elif format in ["x", "X"] and isinstance( + rep, int | byte | int16 | int32 | int64 | sbyte | uint16 | uint32 | uint64 + ): rep = pad_left(to_hex(rep), precision, "0") if precision is not None else to_hex(rep) if format == "X": rep = rep.upper() diff --git a/src/fable-library-py/fable_library/util.py b/src/fable-library-py/fable_library/util.py index f1f8dc1f48..9142e00783 100644 --- a/src/fable-library-py/fable_library/util.py +++ b/src/fable-library-py/fable_library/util.py @@ -2562,7 +2562,7 @@ def is_disposable(x: Any) -> bool: return x is not None and isinstance(x, IDisposable) -def dispose(x: Disposable | AbstractContextManager[Any]) -> None: +def dispose(x: IDisposable | AbstractContextManager[Any]) -> None: """Helper to dispose objects. Also tries to call `__exit__` if the object turns out to be a Python resource manager. diff --git a/src/fable-library-py/src/lib.rs b/src/fable-library-py/src/lib.rs index 7a9e3c56d4..504b05110b 100644 --- a/src/fable-library-py/src/lib.rs +++ b/src/fable-library-py/src/lib.rs @@ -4,16 +4,9 @@ mod types; use crate::types::*; -/// Formats the sum of two numbers as string. -#[pyfunction] -fn sum_as_string(a: usize, b: usize) -> PyResult { - Ok((a + b).to_string()) -} - /// A Python module implemented in Rust. #[pymodule] fn _core(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/src/fable-library-py/src/types.rs b/src/fable-library-py/src/types.rs index c1bb35729d..8208654f2d 100644 --- a/src/fable-library-py/src/types.rs +++ b/src/fable-library-py/src/types.rs @@ -47,7 +47,22 @@ macro_rules! integer_variant { impl $name { #[new] pub fn new(value: &Bound<'_, PyAny>) -> PyResult { - let value = value.call_method1("__and__", ($mask,))?; + let value = match value.call_method1("__and__", ($mask,)) { + Ok(value) => value, + Err(_) => { + // Call __int__ on the value and then mask it + match value.call_method0("__int__") { + Ok(value) => value.call_method1("__and__", ($mask,))?, + Err(_) => { + return Err(PyErr::new::(format!( + "Cannot convert argument to {}", + stringify!($name) + ))) + } + } + } + }; + let value: PyResult = value.extract(); match value { Ok(value) => Ok(Self(value as $type)), @@ -85,9 +100,10 @@ macro_rules! integer_variant { let other = match other.extract::<$type>() { Ok(other) => other, Err(_) => { - return Err(PyErr::new::( - "Cannot convert argument to integer", - )) + return Err(PyErr::new::(format!( + "Cannot convert argument to {}", + stringify!($name) + ))) } }; Ok($name(self.0.wrapping_add(other))) @@ -123,7 +139,8 @@ macro_rules! integer_variant { Ok(other) => other, Err(_) => { return Err(PyErr::new::(format!( - "Cannot convert argument to {}".$name + "Cannot convert argument to {}", + stringify!($name) ))) } }, diff --git a/src/fable-library-py/tests/test_types.py b/src/fable-library-py/tests/test_types.py index 6455db059a..0fb006ae1d 100644 --- a/src/fable-library-py/tests/test_types.py +++ b/src/fable-library-py/tests/test_types.py @@ -8,6 +8,7 @@ def test_byte_create() -> None: assert byte(0) == 0 assert byte(byte(42)) == 42 assert byte(uint16(42)) == 42 + assert byte(1.0) == 1 def test_sbyte_create() -> None: diff --git a/src/fable-library-rust/src/Convert.rs b/src/fable-library-rust/src/Convert.rs index 612cc6c6e4..63ccd6b3cf 100644 --- a/src/fable-library-rust/src/Convert.rs +++ b/src/fable-library-rust/src/Convert.rs @@ -185,9 +185,9 @@ pub mod Convert_ { pub fn toBoolean(n: N) -> bool { !(n == N::default()) } - // pub fn toChar(n: N) -> char { - // core::char::from_u32(n.to_u32().unwrap()).unwrap() - // } + pub fn toChar(n: N) -> char { + core::char::from_u32(n.to_u32().unwrap()).unwrap() + } pub fn toInt8_radix(s: string, radix: i32) -> i8 { from_string_radix(s, radix) } pub fn toInt16_radix(s: string, radix: i32) -> i16 { from_string_radix(s, radix) } diff --git a/tests/Rust/tests/src/CharTests.fs b/tests/Rust/tests/src/CharTests.fs index bb01c13c8e..4c1cb99245 100644 --- a/tests/Rust/tests/src/CharTests.fs +++ b/tests/Rust/tests/src/CharTests.fs @@ -8,10 +8,10 @@ let ``Char addition works`` () = 'A' + 'B' |> int |> equal 131 'A' + char 7 |> int |> equal 72 -// [] -// let ``Char subtraction works`` () = -// 'B' - 'A' |> int |> equal 1 -// char 9 - char 7 |> int |> equal 2 +[] +let ``Char subtraction works`` () = + 'B' - 'A' |> int |> equal 1 + char 9 - char 7 |> int |> equal 2 [] let ``Char.ToUpper works`` () = @@ -237,9 +237,9 @@ let ``Char.IsWhitespace works with two args`` () = // Char.IsSurrogatePair(str,1) |> equal true // Char.IsSurrogatePair(str,2) |> equal false -// [] -// let ``Char.Parse works`` () = -// Char.Parse "A" |> equal 'A' +[] +let ``Char.Parse works`` () = + Char.Parse "A" |> equal 'A' // [] // let ``Char.Parse fails if an empty string is given`` () = From 32ea435680c98058d9128db54692d0a52d6160b5 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 19 Apr 2024 12:25:03 +0200 Subject: [PATCH 14/20] Revert --- src/Fable.Transforms/Rust/Replacements.fs | 1 - tests/Rust/tests/src/CharTests.fs | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Fable.Transforms/Rust/Replacements.fs b/src/Fable.Transforms/Rust/Replacements.fs index 8dfc9b3ca9..5e3bfaa076 100644 --- a/src/Fable.Transforms/Rust/Replacements.fs +++ b/src/Fable.Transforms/Rust/Replacements.fs @@ -1178,7 +1178,6 @@ let chars (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr optio let c = Helper.LibCall(com, "String", "getCharAt", Char, args) makeInstanceCall r t i c methName [] |> Some | _ -> None - | "Parse", _, args -> Helper.LibCall(com, "Convert", "toChar", t, args, ?loc = r) |> Some // | "GetUnicodeCategory" , None, args -> //TODO: // | "IsHighSurrogate" | "IsLowSurrogate" | "IsSurrogate" -> diff --git a/tests/Rust/tests/src/CharTests.fs b/tests/Rust/tests/src/CharTests.fs index 4c1cb99245..bb01c13c8e 100644 --- a/tests/Rust/tests/src/CharTests.fs +++ b/tests/Rust/tests/src/CharTests.fs @@ -8,10 +8,10 @@ let ``Char addition works`` () = 'A' + 'B' |> int |> equal 131 'A' + char 7 |> int |> equal 72 -[] -let ``Char subtraction works`` () = - 'B' - 'A' |> int |> equal 1 - char 9 - char 7 |> int |> equal 2 +// [] +// let ``Char subtraction works`` () = +// 'B' - 'A' |> int |> equal 1 +// char 9 - char 7 |> int |> equal 2 [] let ``Char.ToUpper works`` () = @@ -237,9 +237,9 @@ let ``Char.IsWhitespace works with two args`` () = // Char.IsSurrogatePair(str,1) |> equal true // Char.IsSurrogatePair(str,2) |> equal false -[] -let ``Char.Parse works`` () = - Char.Parse "A" |> equal 'A' +// [] +// let ``Char.Parse works`` () = +// Char.Parse "A" |> equal 'A' // [] // let ``Char.Parse fails if an empty string is given`` () = From 12f3b7c1e07fbdd3ed67a66250f22b79992f5f98 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 19 Apr 2024 12:27:52 +0200 Subject: [PATCH 15/20] Fix typo --- src/Fable.Transforms/Python/Fable2Python.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 43451736fb..646af58295 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -445,7 +445,7 @@ module Reflection = | _, Fable.Type.Number(Int32, _) -> pyTypeof "" expr | _, Fable.Type.Number(UInt32, _) -> pyTypeof "" expr | _, Fable.Type.Number(Int64, _) -> pyTypeof "" expr - | _, Fable.Type.Number(UInt64, _) -> pyTypeof "" expr + | _, Fable.Type.Number(UInt64, _) -> pyTypeof "" expr | _, Fable.Type.Number(Float32, _) -> pyTypeof "" expr | _, Fable.Type.Number(Float64, _) -> pyTypeof "" expr | _ -> pyTypeof "" expr From 4f98258468f951cc5e9e260bffd19f780060f83c Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 19 Apr 2024 13:54:13 +0200 Subject: [PATCH 16/20] Revert --- src/fable-library-py/fable_library/core/_core.pyi | 8 +++++++- src/fable-library-rust/src/Convert.rs | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/fable-library-py/fable_library/core/_core.pyi b/src/fable-library-py/fable_library/core/_core.pyi index bc8136e5d5..ac0d946c8b 100644 --- a/src/fable-library-py/fable_library/core/_core.pyi +++ b/src/fable-library-py/fable_library/core/_core.pyi @@ -1,9 +1,15 @@ +"""Stub file for the Fable library core module. + +This is only needed so that the static type checker can find the types for the extension +methods we have written in Rust. The file will never be used by Python at runtime. +""" + from typing import Any, final from typing_extensions import Self class Numeric: - def __init__(self, value: int | Numeric) -> None: ... + def __init__(self, value: int | Numeric | float) -> None: ... def __add__(self, other: Any) -> Self: ... def __sub__(self, other: Any) -> Self: ... def __mul__(self, other: Any) -> Self: ... diff --git a/src/fable-library-rust/src/Convert.rs b/src/fable-library-rust/src/Convert.rs index 63ccd6b3cf..612cc6c6e4 100644 --- a/src/fable-library-rust/src/Convert.rs +++ b/src/fable-library-rust/src/Convert.rs @@ -185,9 +185,9 @@ pub mod Convert_ { pub fn toBoolean(n: N) -> bool { !(n == N::default()) } - pub fn toChar(n: N) -> char { - core::char::from_u32(n.to_u32().unwrap()).unwrap() - } + // pub fn toChar(n: N) -> char { + // core::char::from_u32(n.to_u32().unwrap()).unwrap() + // } pub fn toInt8_radix(s: string, radix: i32) -> i8 { from_string_radix(s, radix) } pub fn toInt16_radix(s: string, radix: i32) -> i16 { from_string_radix(s, radix) } From 5f3e1822105e74b20ac594c6be94fd34a3d4cba9 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 19 Apr 2024 15:49:11 +0200 Subject: [PATCH 17/20] Minor tweaks --- src/Fable.Transforms/Python/Fable2Python.fs | 2 +- src/Fable.Transforms/Python/Replacements.fs | 4 ++-- src/fable-library-py/fable_library/core/_core.pyi | 2 ++ src/fable-library-py/fable_library/long.py | 8 -------- src/fable-library-py/tests/test_types.py | 4 ++++ 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 646af58295..ed6f221826 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -767,7 +767,7 @@ module Annotation = | UInt32 -> "uint32" | Int64 -> "int64" | UInt64 -> "uint64" - | Int32 + | Int32 -> "int32" | BigInt | Int128 | UInt128 diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index d6a74cb6ea..00a6cb30b0 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -337,7 +337,7 @@ let toInt com (ctx: Context) r targetType (args: Expr list) = match typeTo with | Int8 -> Helper.LibCall(com, "types", "sbyte", targetType, args) | Int16 -> Helper.LibCall(com, "types", "int16", targetType, args) - | Int32 -> emitExpr None Int32.Number [ arg ] "int($0)" + | Int32 -> Helper.LibCall(com, "types", "int16", targetType, args) | UInt8 -> Helper.LibCall(com, "types", "byte", targetType, args) | UInt16 -> Helper.LibCall(com, "types", "uint16", targetType, args) | UInt32 -> Helper.LibCall(com, "types", "uint32", targetType, args) @@ -353,7 +353,7 @@ let toInt com (ctx: Context) r targetType (args: Expr list) = if needToCast typeFrom typeTo then match typeFrom with | Int64 - | UInt64 -> Helper.LibCall(com, "Long", "to_int", targetType, args) // TODO: make no-op + | UInt64 -> Helper.LibCall(com, "types", "int32", targetType, args) | Decimal -> Helper.LibCall(com, "Decimal", "to_number", targetType, args) | _ -> args.Head |> emitCast typeTo diff --git a/src/fable-library-py/fable_library/core/_core.pyi b/src/fable-library-py/fable_library/core/_core.pyi index ac0d946c8b..869a083e4f 100644 --- a/src/fable-library-py/fable_library/core/_core.pyi +++ b/src/fable-library-py/fable_library/core/_core.pyi @@ -29,6 +29,8 @@ class Numeric: def __rshift__(self, other: int | Numeric) -> Self: ... def __rlshift__(self, other: int) -> Any: ... def __rrshift__(self, other: int) -> Any: ... + def __floordiv__(self, other: int | Numeric) -> Self: ... + def __rfloordiv__(self, other: int | Numeric) -> Self: ... def __and__(self, other: int | Numeric) -> Self: ... def __or__(self, other: int | Numeric) -> Self: ... def __xor__(self, other: int | Numeric) -> Self: ... diff --git a/src/fable-library-py/fable_library/long.py b/src/fable-library-py/fable_library/long.py index 5e72b53d22..ed21a15f9d 100644 --- a/src/fable-library-py/fable_library/long.py +++ b/src/fable-library-py/fable_library/long.py @@ -182,12 +182,6 @@ def to_string(x: int) -> str: return str(x) -def to_int(value: int) -> int: - return int32(value) - - -long = int - __all__ = [ "compare", "sign", @@ -222,6 +216,4 @@ def to_int(value: int) -> int: "parse", "try_parse", "to_string", - "to_int", - "long", ] diff --git a/src/fable-library-py/tests/test_types.py b/src/fable-library-py/tests/test_types.py index 0fb006ae1d..2bf4e91dbc 100644 --- a/src/fable-library-py/tests/test_types.py +++ b/src/fable-library-py/tests/test_types.py @@ -142,3 +142,7 @@ def test_abs(): assert abs(uint32(42)) == 42 assert abs(uint32(0)) == 0 assert abs(uint64(42)) == 42 + + +def test_floor_div(): + assert 10 // byte(3) == 3 From ddc2ce0170d6f807673c307e85b53afad811e4ea Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 19 Apr 2024 16:44:08 +0200 Subject: [PATCH 18/20] Add rfloordiv --- src/fable-library-py/src/types.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/fable-library-py/src/types.rs b/src/fable-library-py/src/types.rs index 8208654f2d..25f8d585ed 100644 --- a/src/fable-library-py/src/types.rs +++ b/src/fable-library-py/src/types.rs @@ -162,6 +162,14 @@ macro_rules! integer_variant { Ok($name(self.0 / other)) } + pub fn __rfloordiv__<'py>( + &self, + other: &Bound<'py, PyAny>, + ) -> PyResult> { + let result = other.div(self.0)?; + result.call_method0("__int__") + } + pub fn __mod__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { let other = other.extract::<$type>()?; Ok($name(self.0 % other)) From 8e3d01e3046e6a5f484d5e456ea08cf05cf8cb8f Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 22 Apr 2024 08:38:18 +0200 Subject: [PATCH 19/20] Enable all ints (wip) --- src/Fable.Transforms/Python/Fable2Python.fs | 5 +- src/Fable.Transforms/Python/Replacements.fs | 16 +- .../fable_library/core/_core.pyi | 20 +- src/fable-library-py/fable_library/uri.py | 4 +- src/fable-library-py/fable_library/util.py | 16 - src/fable-library-py/src/types.rs | 369 +++++++++++++++++- src/fable-library-py/tests/test_types.py | 13 + 7 files changed, 406 insertions(+), 37 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index ed6f221826..6b06aa891f 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -442,7 +442,7 @@ module Reflection = | _, Fable.Type.Number(Int8, _) -> pyTypeof "" expr | _, Fable.Type.Number(Int16, _) -> pyTypeof "" expr | _, Fable.Type.Number(UInt16, _) -> pyTypeof "" expr - | _, Fable.Type.Number(Int32, _) -> pyTypeof "" expr + | _, Fable.Type.Number(Int32, _) -> pyTypeof "" expr | _, Fable.Type.Number(UInt32, _) -> pyTypeof "" expr | _, Fable.Type.Number(Int64, _) -> pyTypeof "" expr | _, Fable.Type.Number(UInt64, _) -> pyTypeof "" expr @@ -1721,7 +1721,7 @@ module Util = | UInt8, (:? uint8 as x) -> makeInteger com ctx r value.Type "byte" x | Int16, (:? int16 as x) -> makeInteger com ctx r value.Type "int16" x | UInt16, (:? uint16 as x) -> makeInteger com ctx r value.Type "uint16" x - | Int32, (:? int32 as x) -> Expression.intConstant (x, ?loc = r), [] + | Int32, (:? int32 as x) -> makeInteger com ctx r value.Type "int32" x | UInt32, (:? uint32 as x) -> makeInteger com ctx r value.Type "uint32" x //| _, (:? char as x) -> makeNumber com ctx r value.Type "char" x | _, x when x = infinity -> Expression.name "float('inf')", [] @@ -4340,6 +4340,7 @@ module Compiler = | None -> Expression.none member _.GetAllImports() = + // printfn "GetAllImports: %A" imports imports.Values :> Import seq |> List.ofSeq member _.GetAllTypeVars() = typeVars diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index 00a6cb30b0..b8c0696081 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -155,11 +155,9 @@ let toString com (ctx: Context) r (args: Expr list) = | String -> head | Builtin BclGuid when tail.IsEmpty -> Helper.GlobalCall("str", String, [ head ], ?loc = r) | Builtin(BclGuid | BclTimeSpan as bt) -> Helper.LibCall(com, coreModFor bt, "toString", String, args) - | Number((Int64 | UInt64 | BigInt), _) -> Helper.LibCall(com, "util", "int64_to_string", String, args) - | Number(Int8, _) - | Number(UInt8, _) -> Helper.LibCall(com, "util", "int8_to_string", String, args) - | Number(Int16, _) -> Helper.LibCall(com, "util", "int16_to_string", String, args) - | Number(Int32, _) -> Helper.LibCall(com, "util", "int32_to_string", String, args) + | Number((Int8 | UInt8 | UInt16 | Int16 | UInt32 | Int32 | Int64 | UInt64), _) -> + Helper.InstanceCall(head, "to_string", String, tail, ?loc = r) + | Number(BigInt, _) -> Helper.LibCall(com, "util", "int_to_string", String, args) | Number(Decimal, _) -> Helper.LibCall(com, "decimal", "toString", String, args) | Number _ -> Helper.LibCall(com, "types", "toString", String, [ head ], ?loc = r) | Array _ @@ -292,7 +290,11 @@ let toLong com (ctx: Context) r (unsigned: bool) targetType (args: Expr list) : let fromInteger kind arg = let kind = makeIntConst (kindIndex kind) - Helper.LibCall(com, "long", "fromInteger", targetType, [ arg; makeBoolConst unsigned; kind ]) + //Helper.LibCall(com, "long", "fromInteger", targetType, [ arg; makeBoolConst unsigned; kind ]) + if unsigned then + Helper.LibCall(com, "types", "uint64", targetType, args) + else + Helper.LibCall(com, "types", "int64", targetType, args) let sourceType = args.Head.Type @@ -310,7 +312,7 @@ let toLong com (ctx: Context) r (unsigned: bool) targetType (args: Expr list) : Helper.LibCall(com, "long", "fromNumber", targetType, [ n; makeBoolConst unsigned ]) | BigInt -> Helper.LibCall(com, "big_int", castBigIntMethod targetType, targetType, args) | Int64 - | UInt64 -> Helper.LibCall(com, "long", "fromValue", targetType, args @ [ makeBoolConst unsigned ]) + | UInt64 | Int8 | Int16 | Int32 diff --git a/src/fable-library-py/fable_library/core/_core.pyi b/src/fable-library-py/fable_library/core/_core.pyi index 869a083e4f..2b0691a6ab 100644 --- a/src/fable-library-py/fable_library/core/_core.pyi +++ b/src/fable-library-py/fable_library/core/_core.pyi @@ -9,12 +9,18 @@ from typing import Any, final from typing_extensions import Self class Numeric: + def to_string(self, radix: int = 10) -> str: ... def __init__(self, value: int | Numeric | float) -> None: ... def __add__(self, other: Any) -> Self: ... + def __radd__(self, other: Any) -> Self: ... def __sub__(self, other: Any) -> Self: ... + def __rsub__(self, other: Any) -> Any: ... def __mul__(self, other: Any) -> Self: ... - def __truediv__(self, other: int | Numeric) -> Self: ... + def __rmul__(self, other: Any) -> Any: ... + def __truediv__(self, other: Numeric | int | float) -> Self: ... + def __rtruediv__(self, other: Numeric | int | float) -> Self: ... def __mod__(self, other: Any) -> Self: ... + def __rmod__(self, other: Any) -> Any: ... def __neg__(self) -> Self: ... def __eq__(self, other: Any) -> bool: ... def __ne__(self, other: Any) -> bool: ... @@ -26,18 +32,18 @@ class Numeric: def __index__(self) -> int: ... def __hash__(self) -> int: ... def __lshift__(self, other: int | Numeric) -> Self: ... - def __rshift__(self, other: int | Numeric) -> Self: ... def __rlshift__(self, other: int) -> Any: ... + def __rshift__(self, other: int | Numeric) -> Self: ... def __rrshift__(self, other: int) -> Any: ... - def __floordiv__(self, other: int | Numeric) -> Self: ... - def __rfloordiv__(self, other: int | Numeric) -> Self: ... + def __floordiv__(self, other: Numeric | int | float) -> Self: ... + def __rfloordiv__(self, other: Numeric | int | float) -> Self: ... def __and__(self, other: int | Numeric) -> Self: ... + def __rand__(self, other: int | Numeric) -> Self: ... def __or__(self, other: int | Numeric) -> Self: ... + def __ror__(self, other: int | Numeric) -> Self: ... def __xor__(self, other: int | Numeric) -> Self: ... + def __rxor__(self, other: int | Numeric) -> Self: ... def __invert__(self) -> Self: ... - def __radd__(self, other: Any) -> Any: ... - def __rsub__(self, other: Any) -> Any: ... - def __rmul__(self, other: Any) -> Any: ... def __abs__(self) -> Self: ... @final diff --git a/src/fable-library-py/fable_library/uri.py b/src/fable-library-py/fable_library/uri.py index 78822f951e..655e899ac5 100644 --- a/src/fable-library-py/fable_library/uri.py +++ b/src/fable-library-py/fable_library/uri.py @@ -3,7 +3,7 @@ from enum import IntEnum from urllib.parse import ParseResult, unquote, urljoin, urlparse -from .types import FSharpRef +from .types import FSharpRef, IntegerTypes class UriKind(IntEnum): @@ -22,7 +22,7 @@ def __init__( ) -> None: self.res: ParseResult - kind = kind_or_uri if isinstance(kind_or_uri, int) else UriKind.Absolute + kind = kind_or_uri if isinstance(kind_or_uri, int | IntegerTypes) else UriKind.Absolute uri = urlparse(base_uri) if isinstance(base_uri, str) else base_uri.res relative_uri = ( urlparse(kind_or_uri) diff --git a/src/fable-library-py/fable_library/util.py b/src/fable-library-py/fable_library/util.py index 9142e00783..20a664baa9 100644 --- a/src/fable-library-py/fable_library/util.py +++ b/src/fable-library-py/fable_library/util.py @@ -459,22 +459,6 @@ def int_to_string(i: int, radix: int = 10, bitsize: int | None = None) -> str: return str(i) -def int8_to_string(i: int, radix: int = 10, bitsize: int | None = None) -> str: - return int_to_string(i, radix, 8) - - -def int16_to_string(i: int, radix: int = 10, bitsize: int | None = None) -> str: - return int_to_string(i, radix, 16) - - -def int32_to_string(i: int, radix: int = 10, bitsize: int | None = None) -> str: - return int_to_string(i, radix, 32) - - -def int64_to_string(i: int, radix: int = 10, bitsize: int | None = None) -> str: - return int_to_string(i, radix, 64) - - def count(col: Iterable[Any]) -> int: if isinstance(col, Sized): return len(col) diff --git a/src/fable-library-py/src/types.rs b/src/fable-library-py/src/types.rs index 25f8d585ed..c6168a9c9f 100644 --- a/src/fable-library-py/src/types.rs +++ b/src/fable-library-py/src/types.rs @@ -37,6 +37,14 @@ impl Abs for u64 { } } +#[derive(FromPyObject)] +enum OtherType { + #[pyo3(transparent, annotation = "int")] + Int(u64), + #[pyo3(transparent, annotation = "float")] + Float(f64), +} + macro_rules! integer_variant { ($name:ident, $type:ty, $mask:expr) => { #[pyclass(module = "fable", frozen)] @@ -73,6 +81,20 @@ macro_rules! integer_variant { } } + #[pyo3(signature = (radix=10))] + pub fn to_string(&self, radix: u32) -> String { + match radix { + 10 => self.0.to_string(), + 16 => { + let bitsize = std::mem::size_of::<$type>() as u32 * 8; + format!("{:0width$x}", self.0, width = bitsize as usize / 4) + } + 2 => format!("{:b}", self.0), + 8 => format!("{:o}", self.0), + _ => self.0.to_string(), + } + } + pub fn __neg__(&self) -> PyResult<$name> { Ok($name(self.0.wrapping_neg())) } @@ -153,8 +175,33 @@ macro_rules! integer_variant { } pub fn __truediv__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { - let other = other.extract::<$type>()?; - Ok($name(self.0 / other)) + let value = other.extract::(); + match value { + Ok(OtherType::Int(value)) => { + if value == 0 { + return Err(PyErr::new::( + "Cannot divide by zero", + )); + } + Ok($name(self.0 as $type / value as $type)) + } + Ok(OtherType::Float(value)) => { + if value == 0.0 { + return Err(PyErr::new::( + "Cannot divide by zero", + )); + } + Ok($name((self.0 as f64 / value) as $type)) + } + Err(_) => Err(PyErr::new::("Cannot divide")), + } + } + + pub fn __rtruediv__<'py>( + &self, + other: &Bound<'py, PyAny>, + ) -> PyResult> { + other.div(self.0) } pub fn __floordiv__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> { @@ -175,6 +222,10 @@ macro_rules! integer_variant { Ok($name(self.0 % other)) } + pub fn __rmod__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult> { + other.call_method1("__mod__", (self.0,)) + } + pub fn __invert__(&self) -> Self { Self(!self.0) } @@ -305,6 +356,10 @@ macro_rules! integer_variant { Ok(self.0) } + pub fn __float__(&self) -> PyResult { + Ok(self.0 as f64) + } + // Special method so that arbitrary objects can be used whenever integers // are explicitly needed in Python pub fn __index__(&self) -> PyResult<$type> { @@ -330,7 +385,7 @@ macro_rules! integer_variant { }; } -integer_variant!(UInt8, u8, 0xff); +//integer_variant!(UInt8, u8, 0xff); integer_variant!(Int8, i8, 0xff_u32); integer_variant!(UInt16, u16, 0xffff_u32); integer_variant!(Int16, i16, 0xffff_u32); @@ -338,3 +393,311 @@ integer_variant!(UInt32, u32, 0xffffffff_u32); integer_variant!(Int32, i32, 0xffffffff_u32); integer_variant!(Int64, i64, 0xffffffffffffffff_u64); integer_variant!(UInt64, u64, 0xffffffffffffffff_u64); + +#[pyclass(module = "fable", frozen)] +#[derive(Clone)] +pub struct UInt8(u8); + +#[pymethods] +impl UInt8 { + #[new] + pub fn new(value: &Bound<'_, PyAny>) -> PyResult { + let value = match value.call_method1("__and__", (0xff_u32,)) { + Ok(value) => value, + Err(_) => { + // Call __int__ on the value and then mask it + match value.call_method0("__int__") { + Ok(value) => value.call_method1("__and__", (0xff_u32,))?, + Err(_) => { + return Err(PyErr::new::(format!( + "Cannot convert argument to {}", + stringify!(UInt8) + ))) + } + } + } + }; + + let value: PyResult = value.extract(); + match value { + Ok(value) => Ok(Self(value as u8)), + Err(_) => Err(PyErr::new::(format!( + "Cannot convert argument to {}", + stringify!(UInt8) + ))), + } + } + + #[pyo3(signature = (radix=10))] + pub fn to_string(&self, radix: u32) -> String { + match radix { + 10 => self.0.to_string(), + 16 => { + let bitsize = std::mem::size_of::() as u32 * 8; + format!("{:0width$x}", self.0, width = bitsize as usize / 4) + } + 2 => format!("{:b}", self.0), + 8 => format!("{:o}", self.0), + _ => self.0.to_string(), + } + } + + pub fn __neg__(&self) -> PyResult { + Ok(UInt8(self.0.wrapping_neg())) + } + + pub fn to_bytes(&self, py: Python, length: usize, byteorder: &str) -> PyResult { + let mut bytes = vec![0; length]; + match byteorder { + "little" => LittleEndian::write_uint(&mut bytes, self.0 as u64, length), + "big" => BigEndian::write_uint(&mut bytes, self.0 as u64, length), + _ => { + return Err(PyErr::new::( + "Invalid byteorder", + )) + } + } + Ok(PyBytes::new_bound(py, &bytes).into()) + } + + pub fn __add__(&self, other: &Bound<'_, PyAny>) -> PyResult { + let other = match other.extract::() { + Ok(other) => other, + Err(_) => { + return Err(PyErr::new::(format!( + "Cannot convert argument to {}", + stringify!(UInt8) + ))) + } + }; + Ok(UInt8(self.0.wrapping_add(other))) + } + + // For the case where we are on the right side of the operator we let the other + // object handle the addition and turn ourselves into an integer. + pub fn __radd__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult> { + other.add(self.0) + } + + pub fn __sub__(&self, other: &Bound<'_, PyAny>) -> PyResult { + let other = match other.extract::() { + Ok(other) => other, + Err(_) => { + return Err(PyErr::new::(format!( + "Cannot convert argument to {}", + stringify!(UInt8) + ))) + } + }; + Ok(UInt8(self.0 - other)) + } + + pub fn __rsub__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult> { + other.sub(self.0) + } + + pub fn __mul__(&self, other: &Bound<'_, PyAny>) -> PyResult { + let other = match other.extract::() { + Ok(other) => other.0, + Err(_) => match other.extract::() { + Ok(other) => other, + Err(_) => { + return Err(PyErr::new::(format!( + "Cannot convert argument to {}", + stringify!(UInt8) + ))) + } + }, + }; + Ok(UInt8(self.0 * other)) + } + + pub fn __rmul__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult> { + other.mul(self.0) + } + + pub fn __truediv__(&self, other: &Bound<'_, PyAny>) -> PyResult { + let value = other.extract::(); + match value { + Ok(OtherType::Int(value)) => { + if value == 0 { + return Err(PyErr::new::( + "Cannot divide by zero", + )); + } + Ok(UInt8(self.0 / value as u8)) + } + Ok(OtherType::Float(value)) => { + if value == 0.0 { + return Err(PyErr::new::( + "Cannot divide by zero", + )); + } + Ok(UInt8((self.0 as f64 / value) as u8)) + } + Err(_) => Err(PyErr::new::("Cannot divide")), + } + } + + pub fn __rtruediv__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult> { + other.div(self.0) + } + + pub fn __floordiv__(&self, other: &Bound<'_, PyAny>) -> PyResult { + let other = other.extract::()?; + Ok(UInt8(self.0 / other)) + } + + pub fn __rfloordiv__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult> { + let result = other.div(self.0)?; + result.call_method0("__int__") + } + + pub fn __mod__(&self, other: &Bound<'_, PyAny>) -> PyResult { + let other = other.extract::()?; + Ok(UInt8(self.0 % other)) + } + + pub fn __rmod__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult> { + other.call_method1("__mod__", (self.0,)) + } + + pub fn __invert__(&self) -> Self { + Self(!self.0) + } + + pub fn __inv__(&self) -> Self { + Self(!self.0) + } + + pub fn __abs__(&self) -> Self { + Self(self.0.abs()) + } + + pub fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult { + let other = match other.extract::() { + Ok(other) => Ok(other.0), + Err(_) => match other.extract::() { + Ok(other) => Ok(other), + Err(_) => match other.extract::() { + Ok(other) => Ok(other as u8), + Err(_) => Err(PyErr::new::("Cannot compare")), + }, + }, + }; + + match other { + Ok(other) => match op { + CompareOp::Eq => Ok(self.0 == other), + CompareOp::Ne => Ok(self.0 != other), + CompareOp::Lt => Ok(self.0 < other), + CompareOp::Le => Ok(self.0 <= other), + CompareOp::Gt => Ok(self.0 > other), + CompareOp::Ge => Ok(self.0 >= other), + }, + Err(_) => Ok(false), + } + } + + pub fn __rshift__(&self, other: u32) -> PyResult { + Ok(UInt8(self.0.wrapping_shr(other))) + } + + pub fn __rrshift__<'py>(&self, other: &'py Bound<'py, PyAny>) -> PyResult> { + other.rshift(self.0) + } + + pub fn __lshift__(&self, other: u32) -> PyResult { + Ok(UInt8(self.0.rotate_left(other))) + } + + pub fn __rlshift__<'py>(&self, other: &'py Bound<'py, PyAny>) -> PyResult> { + other.lshift(self.0) + } + + pub fn __and__(&self, other: &Bound<'_, PyAny>) -> PyResult { + match other.extract::() { + Ok(other) => Ok(UInt8((self.0 as u32 & other) as u8)), + Err(_) => Err(PyErr::new::("Cannot compare")), + } + } + + pub fn __rand__<'py>(&self, other: &'py Bound<'py, PyAny>) -> PyResult> { + other.bitand(self.0) + } + + pub fn __or__(&self, other: &Bound<'_, PyAny>) -> PyResult { + let other = match other.extract::() { + Ok(other) => Ok(other), + Err(_) => Err(PyErr::new::("Cannot compare")), + }; + + match other { + Ok(other) => Ok(UInt8(self.0 | other)), + Err(_) => Err(PyErr::new::("Cannot or")), + } + } + + pub fn __ror__<'py>(&self, other: &'py Bound<'py, PyAny>) -> PyResult> { + other.bitor(self.0) + } + + pub fn __xor__(&self, other: &Bound<'_, PyAny>) -> PyResult { + let other = match other.extract::() { + Ok(other) => Ok(other.0), + Err(_) => match other.extract::() { + Ok(other) => Ok(other), + Err(_) => Err(PyErr::new::("Cannot compare")), + }, + }; + + match other { + Ok(other) => Ok(UInt8(self.0 ^ other)), + Err(_) => Err(PyErr::new::("Cannot xor")), + } + } + + pub fn __rxor__<'py>(&self, other: &'py Bound<'py, PyAny>) -> PyResult> { + other.bitxor(self.0) + } + + fn __bool__(&self) -> bool { + self.0 != 0 + } + + pub fn __hash__(&self) -> u8 { + let mut hasher = DefaultHasher::new(); + self.0.hash(&mut hasher); + hasher.finish().try_into().unwrap() + } + + pub fn __int__(&self) -> PyResult { + Ok(self.0) + } + + pub fn __float__(&self) -> PyResult { + Ok(self.0 as f64) + } + + // Special method so that arbitrary objects can be used whenever integers + // are explicitly needed in Python + pub fn __index__(&self) -> PyResult { + Ok(self.0) + } + + pub fn __repr__(&self) -> PyResult { + Ok(self.0.to_string()) + } + + pub fn __str__(&self) -> PyResult { + Ok(self.0.to_string()) + } + + pub fn __format__(&self, py: Python<'_>, format: &str) -> PyResult { + // This is hard to implement so we just convert to a Python integer and let Python handle it + let int = self.__int__()?; + let int = int.into_py(py); + let result = int.call_method1(py, "__format__", (format,))?; + result.extract::(py) + } +} diff --git a/src/fable-library-py/tests/test_types.py b/src/fable-library-py/tests/test_types.py index 2bf4e91dbc..02de46dbb8 100644 --- a/src/fable-library-py/tests/test_types.py +++ b/src/fable-library-py/tests/test_types.py @@ -146,3 +146,16 @@ def test_abs(): def test_floor_div(): assert 10 // byte(3) == 3 + + +def test_divide(): + assert 10 / byte(3) == 3.3333333333333335 + assert 10 / sbyte(3) == 3.3333333333333335 + assert 10 / int16(3) == 3.3333333333333335 + assert 10 / int32(3) == 3.3333333333333335 + assert 10 / int64(3) == 3.3333333333333335 + assert 10 / uint16(3) == 3.3333333333333335 + assert 10 / uint32(3) == 3.3333333333333335 + assert 10 / uint64(3) == 3.3333333333333335 + assert byte(10) / 3 == 3 + assert byte(10) / 3.0 == 3.3333333333333335 From a64ca1697f390db83bc9f371c7a6869bd1d1457e Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 27 Apr 2024 11:23:48 +0200 Subject: [PATCH 20/20] =?UTF-8?q?Make=20int32=20a=20hybrid=20type=20?= =?UTF-8?q?=F0=9F=99=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Fable.Transforms/Python/Fable2Python.fs | 30 ++++++++++------- src/Fable.Transforms/Python/PythonPrinter.fs | 4 +-- src/Fable.Transforms/Python/Replacements.fs | 17 +++++++--- src/fable-library-py/fable_library/async_.py | 6 ++-- .../fable_library/core/_core.pyi | 4 ++- .../fable_library/decimal_.py | 32 ++++++++++--------- .../fable_library/reflection.py | 4 +-- src/fable-library-py/fable_library/reg_exp.py | 4 ++- src/fable-library-py/fable_library/util.py | 2 +- src/fable-library-py/src/types.rs | 21 ++++++------ src/fable-library-py/tests/test_types.py | 23 +++++++++++++ 11 files changed, 96 insertions(+), 51 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index b1b698cbaf..755bd32a45 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -438,17 +438,18 @@ module Reflection = | Fable.String -> pyTypeof "" expr | Fable.Number(kind, _b) -> match kind, typ with - | _, Fable.Type.Number(UInt8, _) -> pyTypeof ">" expr - | _, Fable.Type.Number(Int8, _) -> pyTypeof "" expr - | _, Fable.Type.Number(Int16, _) -> pyTypeof "" expr - | _, Fable.Type.Number(UInt16, _) -> pyTypeof "" expr - | _, Fable.Type.Number(Int32, _) -> pyTypeof "" expr - | _, Fable.Type.Number(UInt32, _) -> pyTypeof "" expr - | _, Fable.Type.Number(Int64, _) -> pyTypeof "" expr - | _, Fable.Type.Number(UInt64, _) -> pyTypeof "" expr + | _, Fable.Type.Number(UInt8, _) -> pyInstanceof (libValue com ctx "types" "uint8") expr + | _, Fable.Type.Number(Int8, _) -> pyInstanceof (libValue com ctx "types" "int8") expr + | _, Fable.Type.Number(Int16, _) -> pyInstanceof (libValue com ctx "types" "int16") expr + | _, Fable.Type.Number(UInt16, _) -> pyInstanceof (libValue com ctx "types" "uint16") expr + | _, Fable.Type.Number(Int32, _) -> + pyInstanceof (Expression.binOp (Expression.name "int", BitOr, libValue com ctx "types" "int32")) expr + | _, Fable.Type.Number(UInt32, _) -> pyInstanceof (libValue com ctx "types" "uint32") expr + | _, Fable.Type.Number(Int64, _) -> pyInstanceof (libValue com ctx "types" "int64") expr + | _, Fable.Type.Number(UInt64, _) -> pyInstanceof (libValue com ctx "types" "uint64") expr | _, Fable.Type.Number(Float32, _) -> pyTypeof "" expr | _, Fable.Type.Number(Float64, _) -> pyTypeof "" expr - | _ -> pyTypeof "" expr + | _ -> pyInstanceof (Expression.name "int") expr | Fable.Regex -> pyInstanceof (com.GetImportExpr(ctx, "typing", "Pattern")) expr | Fable.LambdaType _ @@ -767,7 +768,7 @@ module Annotation = | UInt32 -> "uint32" | Int64 -> "int64" | UInt64 -> "uint64" - | Int32 -> "int32" + | Int32 -> "int" | BigInt | Int128 | UInt128 @@ -1648,6 +1649,7 @@ module Util = com.GetImportExpr(ctx, moduleName, name) |> getParts com ctx parts let transformCast (com: IPythonCompiler) (ctx: Context) t e : Expression * Statement list = + // printfn "transformCast: %A" (t, e) match t with // Optimization for (numeric) array or list literals casted to seq // Done at the very end of the compile pipeline to get more opportunities @@ -1667,6 +1669,10 @@ module Util = let cons = libValue com ctx "types" "float32" let value, stmts = com.TransformAsExpr(ctx, e) Expression.call (cons, [ value ], ?loc = None), stmts + | Fable.Number(Int32, _) -> + let cons = libValue com ctx "types" "int32" + let value, stmts = com.TransformAsExpr(ctx, e) + Expression.call (cons, [ value ], ?loc = None), stmts | _ -> com.TransformAsExpr(ctx, e) let transformCurry (com: IPythonCompiler) (ctx: Context) expr arity : Expression * Statement list = @@ -1721,7 +1727,7 @@ module Util = | UInt8, (:? uint8 as x) -> makeInteger com ctx r value.Type "byte" x | Int16, (:? int16 as x) -> makeInteger com ctx r value.Type "int16" x | UInt16, (:? uint16 as x) -> makeInteger com ctx r value.Type "uint16" x - | Int32, (:? int32 as x) -> makeInteger com ctx r value.Type "int32" x + | Int32, (:? int32 as x) -> Expression.intConstant (x, ?loc = r), [] | UInt32, (:? uint32 as x) -> makeInteger com ctx r value.Type "uint32" x //| _, (:? char as x) -> makeNumber com ctx r value.Type "char" x | _, x when x = infinity -> Expression.name "float('inf')", [] @@ -4438,7 +4444,7 @@ module Compiler = TypeParamsScope = 0 } - //printfn "file: %A" file.Declarations + // printfn "file: %A" file.Declarations let rootDecls = List.collect (transformDeclaration com ctx) file.Declarations let typeVars = com.GetAllTypeVars() |> transformTypeVars com ctx diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index 411e364a86..ab11b07a18 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -44,11 +44,11 @@ module PrinterExtensions = printer.Print(handler) if node.OrElse.Length > 0 then - printer.Print("else: ") + printer.Print("else:") printer.PrintBlock(node.OrElse) if node.FinalBody.Length > 0 then - printer.Print("finally: ") + printer.Print("finally:") printer.PrintBlock(node.FinalBody) member printer.Print(arg: Arg) = diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index b8c0696081..84fbf706eb 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -154,12 +154,18 @@ let toString com (ctx: Context) r (args: Expr list) = | Char -> TypeCast(head, String) | String -> head | Builtin BclGuid when tail.IsEmpty -> Helper.GlobalCall("str", String, [ head ], ?loc = r) - | Builtin(BclGuid | BclTimeSpan as bt) -> Helper.LibCall(com, coreModFor bt, "toString", String, args) - | Number((Int8 | UInt8 | UInt16 | Int16 | UInt32 | Int32 | Int64 | UInt64), _) -> - Helper.InstanceCall(head, "to_string", String, tail, ?loc = r) + | Builtin(BclGuid | BclTimeSpan as bt) -> Helper.LibCall(com, coreModFor bt, "to_string", String, args) + | Number(Int32, _) -> + let expr = Helper.LibCall(com, "types", "int32", head.Type, [ head ], ?loc = r) + Helper.InstanceCall(expr, "to_string", String, tail, ?loc = r) + | Number((Int8 | UInt8 | UInt16 | Int16 | UInt32 | Int64 | UInt64), _) -> + if tail.Length > 0 then + Helper.InstanceCall(head, "to_string", String, tail, ?loc = r) + else + Helper.GlobalCall("str", String, [ head ], ?loc = r) | Number(BigInt, _) -> Helper.LibCall(com, "util", "int_to_string", String, args) - | Number(Decimal, _) -> Helper.LibCall(com, "decimal", "toString", String, args) - | Number _ -> Helper.LibCall(com, "types", "toString", String, [ head ], ?loc = r) + | Number(Decimal, _) -> Helper.LibCall(com, "decimal", "to_string", String, args) + | Number _ -> Helper.LibCall(com, "types", "to_string", String, [ head ], ?loc = r) | Array _ | List _ -> Helper.LibCall(com, "types", "seqToString", String, [ head ], ?loc = r) // | DeclaredType(ent, _) when ent.IsFSharpUnion || ent.IsFSharpRecord || ent.IsValueType -> @@ -2174,6 +2180,7 @@ let errorStrings = | _ -> None let languagePrimitives (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = + // printfn "languagePrimitives: %A" (i.CompiledName, thisArg, args) match i.CompiledName, args with | Naming.EndsWith "Dynamic" operation, arg :: _ -> let operation = diff --git a/src/fable-library-py/fable_library/async_.py b/src/fable-library-py/fable_library/async_.py index 8e96dbb7e0..9cf444ab29 100644 --- a/src/fable-library-py/fable_library/async_.py +++ b/src/fable-library-py/fable_library/async_.py @@ -70,7 +70,7 @@ def cancel(token: CancellationToken) -> None: def cancel_after(token: CancellationToken, ms: int) -> None: - timer = Timer(ms / 1000.0, token.cancel) + timer = Timer(float(ms) / 1000.0, token.cancel) timer.start() @@ -78,7 +78,7 @@ def is_cancellation_requested(token: CancellationToken) -> bool: return token and token.is_cancelled -def sleep(millisecondsDueTime: int) -> Async[None]: +def sleep(milliseconds_duetime: int) -> Async[None]: def cont(ctx: IAsyncContext[None]): def cancel(): ctx.on_cancel(OperationCanceledError()) @@ -89,7 +89,7 @@ def timeout(): ctx.cancel_token.remove_listener(token_id) ctx.on_success(None) - due_time = millisecondsDueTime / 1000.0 + due_time = float(milliseconds_duetime) / 1000.0 ctx.trampoline.run_later(timeout, due_time) return protected_cont(cont) diff --git a/src/fable-library-py/fable_library/core/_core.pyi b/src/fable-library-py/fable_library/core/_core.pyi index 2b0691a6ab..88f8914ac6 100644 --- a/src/fable-library-py/fable_library/core/_core.pyi +++ b/src/fable-library-py/fable_library/core/_core.pyi @@ -62,7 +62,9 @@ class Int16(Numeric): ... class UInt32(Numeric): ... @final -class Int32(Numeric): ... +class Int32(Numeric, int): + # Note that this is not really a subclass of int + ... @final class UInt64(Numeric): ... diff --git a/src/fable-library-py/fable_library/decimal_.py b/src/fable-library-py/fable_library/decimal_.py index 75d6cf8cc6..b2c5a9d2a8 100644 --- a/src/fable-library-py/fable_library/decimal_.py +++ b/src/fable-library-py/fable_library/decimal_.py @@ -1,6 +1,6 @@ from decimal import MAX_EMAX, MIN_EMIN, Decimal, getcontext -from .types import FSharpRef, IntegerTypes, byte, int16, int32, sbyte, uint16, uint32 +from .types import FSharpRef, IntegerTypes, byte, int16, int32, int64, sbyte, uint16, uint32, uint64 getcontext().prec = 29 @@ -114,26 +114,28 @@ def op_inequality(a: Decimal, b: Decimal) -> bool: def from_parts( - low: IntegerTypes, mid: IntegerTypes, high: IntegerTypes, isNegative: IntegerTypes, scale: IntegerTypes + low: IntegerTypes, mid: IntegerTypes, high: IntegerTypes, is_negative: IntegerTypes, scale: IntegerTypes ) -> Decimal: - sign = -1 if isNegative else 1 + sign = -1 if is_negative else 1 - if low < 0: - low = 0x100000000 + low + _low, _mid, _high, _scale = int(low), int(mid), int(high), int(scale) - if mid < 0: - mid = 0xFFFFFFFF00000000 + mid + 1 + if _low < 0: + _low = 0x100000000 + _low + + if _mid < 0: + _mid = 0xFFFFFFFF00000000 + _mid + 1 else: - mid = mid << 32 + _mid = _mid << 32 - if high < 0: - high = 0xFFFFFFFF0000000000000000 + high + 1 + if _high < 0: + _high = 0xFFFFFFFF0000000000000000 + _high + 1 else: - high = high << 64 + _high = _high << 64 - value = Decimal(int(low + mid + high) * sign) + value = Decimal((_low + _mid + _high) * sign) if scale: - dscale = Decimal(pow(10, int(scale))) + dscale = Decimal(pow(10, _scale)) return value / dscale return value @@ -158,9 +160,9 @@ def try_parse(string: str, def_value: FSharpRef[Decimal]) -> bool: return False -def create(value: int | float | byte | sbyte | int16 | uint16 | int32 | uint32 | str) -> Decimal: +def create(value: float | IntegerTypes | str) -> Decimal: match value: - case sbyte() | byte() | int16() | uint16() | int32() | uint32(): + case sbyte() | byte() | int16() | uint16() | int32() | uint32() | int64() | uint64(): return Decimal(int(value)) case _: return Decimal(value) diff --git a/src/fable-library-py/fable_library/reflection.py b/src/fable-library-py/fable_library/reflection.py index f054998862..d1f23417c4 100644 --- a/src/fable-library-py/fable_library/reflection.py +++ b/src/fable-library-py/fable_library/reflection.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from typing import Any, cast -from .types import FSharpRef, Record +from .types import FSharpRef, IntegerTypes, Record from .types import Union as FsUnion from .util import Array, combine_hash_codes, equal_arrays_with @@ -245,7 +245,7 @@ def is_instance_of_type(t: TypeInfo, o: Any) -> bool: if isinstance(o, str): return t.fullname == string_type.fullname - if isinstance(o, int | float): + if isinstance(o, IntegerTypes | float): return is_erased_to_number(t) if callable(o): diff --git a/src/fable-library-py/fable_library/reg_exp.py b/src/fable-library-py/fable_library/reg_exp.py index ba63c5941f..57683f22cf 100644 --- a/src/fable-library-py/fable_library/reg_exp.py +++ b/src/fable-library-py/fable_library/reg_exp.py @@ -4,6 +4,8 @@ from collections.abc import Callable, Iterator from re import Match, Pattern +from .types import IntegerTypes + MatchEvaluator = Callable[[Match[str]], str] @@ -20,7 +22,7 @@ def __len__(self) -> int: return len(self.groups) def __getitem__(self, key: int | str) -> str | None: - if isinstance(key, int): + if isinstance(key, IntegerTypes): return self.groups[key] else: return self.named_groups.get(key) diff --git a/src/fable-library-py/fable_library/util.py b/src/fable-library-py/fable_library/util.py index 20a664baa9..aae34adc4e 100644 --- a/src/fable-library-py/fable_library/util.py +++ b/src/fable-library-py/fable_library/util.py @@ -2618,7 +2618,7 @@ def string_hash(s: str) -> int: def number_hash(x: int) -> int: - return x * 2654435761 | 0 + return int(x) * 2654435761 | 0 def identity_hash(x: Any) -> int: diff --git a/src/fable-library-py/src/types.rs b/src/fable-library-py/src/types.rs index c6168a9c9f..c91d7622a0 100644 --- a/src/fable-library-py/src/types.rs +++ b/src/fable-library-py/src/types.rs @@ -37,6 +37,9 @@ impl Abs for u64 { } } +// The `OtherType` enum is used to handle the case where we need to extract +// a value from a Python where the type can either be an integer or a float. This +// avoid having to unwrap the value twice. #[derive(FromPyObject)] enum OtherType { #[pyo3(transparent, annotation = "int")] @@ -45,6 +48,7 @@ enum OtherType { Float(f64), } +// Note that it's currently not possible to extend the `int` type in Python with pyo3. macro_rules! integer_variant { ($name:ident, $type:ty, $mask:expr) => { #[pyclass(module = "fable", frozen)] @@ -85,10 +89,7 @@ macro_rules! integer_variant { pub fn to_string(&self, radix: u32) -> String { match radix { 10 => self.0.to_string(), - 16 => { - let bitsize = std::mem::size_of::<$type>() as u32 * 8; - format!("{:0width$x}", self.0, width = bitsize as usize / 4) - } + 16 => format!("{:x}", self.0), 2 => format!("{:b}", self.0), 8 => format!("{:o}", self.0), _ => self.0.to_string(), @@ -349,7 +350,7 @@ macro_rules! integer_variant { pub fn __hash__(&self) -> $type { let mut hasher = DefaultHasher::new(); self.0.hash(&mut hasher); - hasher.finish().try_into().unwrap() + hasher.finish() as $type } pub fn __int__(&self) -> PyResult<$type> { @@ -433,10 +434,12 @@ impl UInt8 { match radix { 10 => self.0.to_string(), 16 => { - let bitsize = std::mem::size_of::() as u32 * 8; - format!("{:0width$x}", self.0, width = bitsize as usize / 4) + format!("{:x}", self.0) + } + 2 => { + // Strip leading zeros + format!("{:b}", self.0) } - 2 => format!("{:b}", self.0), 8 => format!("{:o}", self.0), _ => self.0.to_string(), } @@ -668,7 +671,7 @@ impl UInt8 { pub fn __hash__(&self) -> u8 { let mut hasher = DefaultHasher::new(); self.0.hash(&mut hasher); - hasher.finish().try_into().unwrap() + hasher.finish() as u8 } pub fn __int__(&self) -> PyResult { diff --git a/src/fable-library-py/tests/test_types.py b/src/fable-library-py/tests/test_types.py index 02de46dbb8..e9d51bf58f 100644 --- a/src/fable-library-py/tests/test_types.py +++ b/src/fable-library-py/tests/test_types.py @@ -159,3 +159,26 @@ def test_divide(): assert 10 / uint64(3) == 3.3333333333333335 assert byte(10) / 3 == 3 assert byte(10) / 3.0 == 3.3333333333333335 + + +def test_hash(): + assert hash(byte(42)) != 0 + assert hash(sbyte(42)) != 0 + assert hash(int16(42)) != 0 + assert hash(int32(42)) != 0 + assert hash(int64(42)) != 0 + assert hash(uint16(42)) != 0 + assert hash(uint32(42)) != 0 + assert hash(uint64(42)) != 0 + + +def test_to_string(): + assert byte(7).to_string(radix=2) == "111" + assert byte(7).to_string(radix=8) == "7" + assert byte(7).to_string(radix=10) == "7" + assert byte(7).to_string(radix=16) == "7" + assert byte(10).to_string(radix=2) == "1010" + assert byte(10).to_string(radix=8) == "12" + assert byte(10).to_string(radix=10) == "10" + assert byte(10).to_string(radix=16) == "a" + assert byte(255).to_string(radix=2) == "11111111"